Android14 InputManager-焦點窗口的更新

設置焦點時需要

先設置焦點APP

mFo-cusedApp是一個AppWindowToken,在WMS中用來表示當前處于Resume狀態的Activity。它是由AMS在開始啟動一個Activity時調用WMS的setFocusedApp()函數設置的。

考慮以下應用場景,當用戶從Launcher中啟動一個Activity之后,在新Activity的窗口顯示之前便立刻按下了BACK鍵。很明顯,用戶的意圖是關閉剛剛啟動的Activity,而不是退出Launcher。然而,由于BACK鍵按得過快,新Activity尚未創建窗口,因此按照之前討論的焦點窗口的查找條件,Launcher的窗口將會作為焦點窗口而接收BACK鍵,從而使得Launcher被退出。這與用戶的意圖是相悖的。為了解決這個問題,WMS要求焦點窗口必須屬于mFocusedApp,或者位于mFocu-sedApp的窗口之上。再看添加這個限制之后,上面的應用場景會變成什么樣子。當啟動新Activity時,盡管其窗口尚未創建,但這個窗口的AppWindowToken已經被添加到mAppTokens列表的頂部,并通過setFocusedApp()設置為mFocusedApp。此時再更新焦點窗口時發現第一個符合焦點條件的窗口屬于Launcher,但Launcher的AppWindowToken位于mFocusedApp也就是新Activity之下,因此它不能作為焦點窗口。這樣,在新Activity創建窗口之前的一個短暫時間段內,系統處于無焦點窗口的情況。此時到來的BACK鍵在InputDispatcher中會因找不到目標窗口而觸發handleTargetsNotReadyLocked(),從而進入等待重試狀態。隨后新Activity創建了自己的窗口并添加到WMS里,這時WMS可以成功地將這個窗口作為焦點,并通過IMS.setInputWindows()將其更新到InputDispatcher。這個更新動作會喚醒派發線程立刻對BACK鍵進行重試,這次重試便找到了新Activity的窗口作為目標,并將事件發送過去,從而正確地體現用戶的意圖。

    void setLastResumedActivityUncheckLocked(ActivityRecord r, String reason) {boolean focusedAppChanged = false;if (!getTransitionController().isTransientCollect(r)) {focusedAppChanged = r.mDisplayContent.setFocusedApp(r);if (focusedAppChanged) {mWindowManager.updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL,true /*updateInputWindows*/);}}
WindowManagerService.javaboolean updateFocusedWindowLocked(int mode, boolean updateInputWindows) {Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "wmUpdateFocus");boolean changed = mRoot.updateFocusedWindowLocked(mode, updateInputWindows);Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);return changed;}
RootWindowContainer.java
boolean updateFocusedWindowLocked(int mode, boolean updateInputWindows) {mTopFocusedAppByProcess.clear();boolean changed = false;int topFocusedDisplayId = INVALID_DISPLAY;// Go through the children in z-order starting at the top-mostfor (int i = mChildren.size() - 1; i >= 0; --i) {final DisplayContent dc = mChildren.get(i);changed |= dc.updateFocusedWindowLocked(mode, updateInputWindows, topFocusedDisplayId);final WindowState newFocus = dc.mCurrentFocus;if (newFocus != null) {final int pidOfNewFocus = newFocus.mSession.mPid;if (mTopFocusedAppByProcess.get(pidOfNewFocus) == null) {mTopFocusedAppByProcess.put(pidOfNewFocus, newFocus.mActivityRecord);}if (topFocusedDisplayId == INVALID_DISPLAY) {topFocusedDisplayId = dc.getDisplayId();}} else if (topFocusedDisplayId == INVALID_DISPLAY && dc.mFocusedApp != null) {// The top-most display that has a focused app should still be the top focused// display even when the app window is not ready yet (process not attached or// window not added yet).topFocusedDisplayId = dc.getDisplayId();}}if (topFocusedDisplayId == INVALID_DISPLAY) {topFocusedDisplayId = DEFAULT_DISPLAY;}if (mTopFocusedDisplayId != topFocusedDisplayId) {mTopFocusedDisplayId = topFocusedDisplayId;mWmService.mInputManager.setFocusedDisplay(topFocusedDisplayId);mWmService.mPolicy.setTopFocusedDisplay(topFocusedDisplayId);mWmService.mAccessibilityController.setFocusedDisplay(topFocusedDisplayId);ProtoLog.d(WM_DEBUG_FOCUS_LIGHT, "New topFocusedDisplayId=%d", topFocusedDisplayId);}return changed;}
DisplayContent.javaboolean updateFocusedWindowLocked(int mode, boolean updateInputWindows,int topFocusedDisplayId) {// Don't re-assign focus automatically away from a should-keep-focus app window.// `findFocusedWindow` will always grab the transient-launch app since it is "on top" which// would create a mismatch, so just early-out here.if (mCurrentFocus != null && mTransitionController.shouldKeepFocus(mCurrentFocus)// This is only keeping focus, so don't early-out if the focused-app has been// explicitly changed (eg. via setFocusedTask).&& mFocusedApp != null && mCurrentFocus.isDescendantOf(mFocusedApp)&& mCurrentFocus.isVisible() && mCurrentFocus.isFocusable()) {ProtoLog.v(WM_DEBUG_FOCUS, "Current transition prevents automatic focus change");return false;}WindowState newFocus = findFocusedWindowIfNeeded(topFocusedDisplayId);if (mCurrentFocus == newFocus) {return false;}boolean imWindowChanged = false;final WindowState imWindow = mInputMethodWindow;if (imWindow != null) {final WindowState prevTarget = mImeLayeringTarget;final WindowState newTarget = computeImeTarget(true /* updateImeTarget*/);imWindowChanged = prevTarget != newTarget;if (mode != UPDATE_FOCUS_WILL_ASSIGN_LAYERS&& mode != UPDATE_FOCUS_WILL_PLACE_SURFACES) {assignWindowLayers(false /* setLayoutNeeded */);}if (imWindowChanged) {mWmService.mWindowsChanged = true;setLayoutNeeded();newFocus = findFocusedWindowIfNeeded(topFocusedDisplayId);}}ProtoLog.d(WM_DEBUG_FOCUS_LIGHT, "Changing focus from %s to %s displayId=%d Callers=%s",mCurrentFocus, newFocus, getDisplayId(), Debug.getCallers(4));final WindowState oldFocus = mCurrentFocus;mCurrentFocus = newFocus;if (newFocus != null) {mWinAddedSinceNullFocus.clear();mWinRemovedSinceNullFocus.clear();if (newFocus.canReceiveKeys()) {// Displaying a window implicitly causes dispatching to be unpaused.// This is to protect against bugs if someone pauses dispatching but// forgets to resume.newFocus.mToken.paused = false;}}getDisplayPolicy().focusChangedLw(oldFocus, newFocus);mAtmService.mBackNavigationController.onFocusChanged(newFocus);if (imWindowChanged && oldFocus != mInputMethodWindow) {// Focus of the input method window changed. Perform layout if needed.if (mode == UPDATE_FOCUS_PLACING_SURFACES) {performLayout(true /*initial*/,  updateInputWindows);} else if (mode == UPDATE_FOCUS_WILL_PLACE_SURFACES) {// Client will do the layout, but we need to assign layers// for handleNewWindowLocked() below.assignWindowLayers(false /* setLayoutNeeded */);}}if (mode != UPDATE_FOCUS_WILL_ASSIGN_LAYERS) {// If we defer assigning layers, then the caller is responsible for doing this part.getInputMonitor().setInputFocusLw(newFocus, updateInputWindows);}adjustForImeIfNeeded();updateKeepClearAreas();// We may need to schedule some toast windows to be removed. The toasts for an app that// does not have input focus are removed within a timeout to prevent apps to redress// other apps' UI.scheduleToastWindowsTimeoutIfNeededLocked(oldFocus, newFocus);if (mode == UPDATE_FOCUS_PLACING_SURFACES) {pendingLayoutChanges |= FINISH_LAYOUT_REDO_ANIM;}// Notify the accessibility manager for the change so it has the windows before the newly// focused one starts firing events.// TODO(b/151179149) investigate what info accessibility service needs before input can// dispatch focus to clients.if (mWmService.mAccessibilityController.hasCallbacks()) {mWmService.mH.sendMessage(PooledLambda.obtainMessage(this::updateAccessibilityOnWindowFocusChanged,mWmService.mAccessibilityController));}return true;}

updateFocusedWindowLocked

首先尋找焦點窗口findFocusedWindowIfNeeded

然后將焦點窗口設置到input?:setInputFocusLw

尋找焦點窗口

DisplayContent.javaWindowState findFocusedWindowIfNeeded(int topFocusedDisplayId) {return (hasOwnFocus() || topFocusedDisplayId == INVALID_DISPLAY)? findFocusedWindow() : null;}/*** Find the focused window of this DisplayContent. The search takes the state of the display* content into account* @return The focused window, null if none was found.*/WindowState findFocusedWindow() {mTmpWindow = null;// mFindFocusedWindow will populate mTmpWindow with the new focused window when found.forAllWindows(mFindFocusedWindow, true /* traverseTopToBottom */);if (mTmpWindow == null) {ProtoLog.v(WM_DEBUG_FOCUS_LIGHT, "findFocusedWindow: No focusable windows, display=%d",getDisplayId());return null;}return mTmpWindow;}
 private final ToBooleanFunction<WindowState> mFindFocusedWindow = w -> {final ActivityRecord focusedApp = mFocusedApp;ProtoLog.v(WM_DEBUG_FOCUS, "Looking for focus: %s, flags=%d, canReceive=%b, reason=%s",w, w.mAttrs.flags, w.canReceiveKeys(),w.canReceiveKeysReason(false /* fromUserTouch */));if (!w.canReceiveKeys()) {return false;}// When switching the app task, we keep the IME window visibility for better// transitioning experiences.// However, in case IME created a child window or the IME selection dialog without// dismissing during the task switching to keep the window focus because IME window has// higher window hierarchy, we don't give it focus if the next IME layering target// doesn't request IME visible.if (w.mIsImWindow && w.isChildWindow() && (mImeLayeringTarget == null|| !mImeLayeringTarget.isRequestedVisible(ime()))) {return false;}if (w.mAttrs.type == TYPE_INPUT_METHOD_DIALOG && mImeLayeringTarget != null&& !mImeLayeringTarget.isRequestedVisible(ime())&& !mImeLayeringTarget.isVisibleRequested()) {return false;}final ActivityRecord activity = w.mActivityRecord;if (focusedApp == null) {ProtoLog.v(WM_DEBUG_FOCUS_LIGHT,"findFocusedWindow: focusedApp=null using new focus @ %s", w);mTmpWindow = w;return true;}if (!focusedApp.windowsAreFocusable()) {// Current focused app windows aren't focusable...ProtoLog.v(WM_DEBUG_FOCUS_LIGHT, "findFocusedWindow: focusedApp windows not"+ " focusable using new focus @ %s", w);mTmpWindow = w;return true;}// Descend through all of the app tokens and find the first that either matches// win.mActivityRecord (return win) or mFocusedApp (return null).if (activity != null && w.mAttrs.type != TYPE_APPLICATION_STARTING) {if (focusedApp.compareTo(activity) > 0) {// App root task below focused app root task. No focus for you!!!ProtoLog.v(WM_DEBUG_FOCUS_LIGHT,"findFocusedWindow: Reached focused app=%s", focusedApp);mTmpWindow = null;return true;}// If the candidate activity is currently being embedded in the focused task, the// activity cannot be focused unless it is on the same TaskFragment as the focusedApp's.TaskFragment parent = activity.getTaskFragment();if (parent != null && parent.isEmbedded()) {if (activity.getTask() == focusedApp.getTask()&& activity.getTaskFragment() != focusedApp.getTaskFragment()) {return false;}}}ProtoLog.v(WM_DEBUG_FOCUS_LIGHT, "findFocusedWindow: Found new focus @ %s", w);mTmpWindow = w;return true;};

該方法中,將依次根據如下條件獲得焦點窗口:

  • 如果WindowState不能接收Input事件,則不能作為焦點窗口;
    1. 如果沒有前臺Activity,則當前WindowState作為焦點窗口返回;
    2. 如果前臺Activity是不可獲焦狀態,則當前WindowState作為焦點窗口返回;
    3. 如果當前WindowState由ActivityRecord管理,且該WindowState不是Staring Window類型,那么當前臺Activity在當前WindowState所屬Activity之上時,不存在焦點窗口;
    4. 處于focusedApp之下的窗口不能成為焦點窗口
    5. 如果以上條件都不滿足,則當前WindowState作為焦點窗口返回;
  • 由此可以得出影響窗口焦點的動作有以下幾個:
  • □窗口的增刪操作。
  • □窗口次序的調整。
  • □Activity的啟動與退出。
  • □DisplayContent的增刪操作。
  • 因此當這些動作發生時,WMS都會觸發對updateFocusedWindowLocked()的調用,并更新窗口的布局信息到輸入系統,以實現按鍵事件正確派發。
  •     public boolean canReceiveKeys(boolean fromUserTouch) {if (mActivityRecord != null && mTransitionController.shouldKeepFocus(mActivityRecord)) {// During transient launch, the transient-hide windows are not visibleRequested// or on-top but are kept focusable and thus can receive keys.return true;}final boolean canReceiveKeys = isVisibleRequestedOrAdding()&& (mViewVisibility == View.VISIBLE) && !mRemoveOnExit&& ((mAttrs.flags & WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) == 0)&& (mActivityRecord == null || mActivityRecord.windowsAreFocusable(fromUserTouch))// can it receive touches&& (mActivityRecord == null || mActivityRecord.getTask() == null|| !mActivityRecord.getTask().getRootTask().shouldIgnoreInput());if (!canReceiveKeys) {return false;}// Do not allow untrusted virtual display to receive keys unless user intentionally// touches the display.return fromUserTouch || getDisplayContent().isOnTop()|| getDisplayContent().isTrusted();}

    如果一個WindowState可以接受Input事件,需要同時滿足多個條件:

    1. isVisibleRequestedOrAdding方法為true,表示該WindowState可見或處于添加過程中:
    2. mViewVisibility屬性為View.VISIBLE,表示客戶端View可見;
    3. mRemoveOnExit為false,表示WindowState的退出動畫不存在;
    4. mAttrs.flags中不存在FLAG_NOT_FOCUSABLE標記,該標記如果設置,表示該窗口為不可獲焦窗口;
    5. mActivityRecord為null或者mActivityRecord可獲焦;
    6. cantReceiveTouchInput()方法為false,表示可以接受Touch事件。
    boolean isVisibleRequestedOrAdding() {final ActivityRecord atoken = mActivityRecord;return (mHasSurface || (!mRelayoutCalled && mViewVisibility == View.VISIBLE))&& isVisibleByPolicy() && !isParentWindowHidden()&& (atoken == null || atoken.isVisibleRequested())&& !mAnimatingExit && !mDestroying;}

然后將焦點窗口設置到input?:setInputFocusLw

查看Android14 InputManager-InputWindow的更新過程

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/696121.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/696121.shtml
英文地址,請注明出處:http://en.pswp.cn/news/696121.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

內存管理——線性內存,進程空間

低2G為進程空間 開始地址結束地址大小屬性00xFFFFF1M保留0x1000000x102FFF棧不固定位置、大小0x1030000x143FFF堆不固定位置、大小0x400000主程序文件不固定位置、大小加載dll不固定位置、大小0x7ffdd000TIB位置&#xff0c;大小編譯時固定0x7FFFE000系統與用戶共享數據塊位置…

[newstarctf2023] --RE wp

AndroGenshin: rc4加密表&#xff0c;base64換表&#xff1a; 腳本梭就行 python username b"genshinimpact" base64_table [125, 239, 101, 151, 77, 163, 163, 110, 58, 230, 186, 206, 84, 84, 189, 193, 30, 63, 104, 178, 130, 211,164, 94, 75, 16, 32, 33…

發布 rust 源碼包 (crates.io)

rust 編程語言的包 (或者 庫, library) 叫做 crate, 也就是軟件中的一個組件. 一個完整的軟件通常由多個 crate 組成, rust 編譯器 (rustc) 一次編譯一整個 crate, 不同的 crate 可以同時并行編譯. rust 官方有一個集中發布開源包的網站 crates.io. 發布在這上面的 crate 可以…

uniapp微信公眾號H5分享

如果項目文件node_modules中沒有weixin-js-sdk文件&#xff0c;則直接使用本文章提供的&#xff1b; 如果不生效&#xff0c;則在template.h5.html中引入 <script src"https://res.wx.qq.com/open/js/jweixin-1.6.0.js"></script> 首先引入weixin-js-…

vue3框架組件自動導入unplugin-vue-components

1.安裝 npm i unplugin-vue-components -save-dev 2.配置 我這里用的是Vue CLI&#xff0c;所以要在vue.config.js文件中添加配置&#xff0c;官網中有寫不同打包工具的配置寫法 框架我使用的是Element Plus&#xff0c;使用前去官網查看自己的框架是否支持&#xff0c;主流…

LLM之RAG實戰(二十七)| 如何評估RAG系統

有沒有想過今天的一些應用程序是如何看起來幾乎神奇地智能的&#xff1f;這種魔力很大一部分來自于一種叫做RAG和LLM的東西。把RAG&#xff08;Retrieval Augmented Generation&#xff09;想象成人工智能世界里聰明的書呆子&#xff0c;它會挖掘大量信息&#xff0c;準確地找到…

電腦黑屏什么都不顯示怎么辦 電腦開機黑屏不顯示任何東西的4種解決辦法

相信有很多網友都有經歷電腦開機黑屏不顯示任何東西&#xff0c;找了很多方法都沒處理好&#xff0c;其實關于這個的問題&#xff0c;首先還是要了解清楚開機黑屏的原因&#xff0c;才能夠對癥下藥&#xff0c;下面大家可以跟小編一起來看看怎么解決吧 電腦開機黑屏不顯示任何…

【無刷電機學習】基礎概念及原理介紹(持續更新中...)

目錄&#xff08;2024.02.22版&#xff09; 1 定義 2 各種電機優勢比較 2.1 有刷與無刷比較 2.2 交流與直流比較 2.3 內轉子與外轉子比較 2.4 低壓BLDC的一些優點 3 基本原理 3.1 單相無刷電機 3.2 三相無刷電機 4 驅動方法 4.1 六步換相控制 4.1.1 基本原理 4…

突發!AI獨角獸「竹間智能」被曝停工停產6個月

大家好我是二狗。 今天早上起來刷朋友圈&#xff0c;看到一張截圖——AI創企竹間智能&#xff0c;宣稱因為公司所處的經營環境艱難&#xff0c;部分部門和崗位將從即日起停工停產6個月。 圖源&#xff1a;&#xff08;企服科學&#xff09; 下面是文字版&#xff1a; 由于公司…

Web服務器基礎介紹

目錄 Web服務器基礎介紹 一、HTML是什么&#xff1f; 二、靜態網頁和動態網頁 1、靜態網頁 2、動態網頁 3、動態網頁語言 PHP JSP Python Ruby 三、HTTP協議 1、HTTP協議是什么&#xff1f; 2、HTTP請求訪問的方法 3、GET與POST比較 GET&#xff1a; POST&…

Linux網絡編程(三-UDP協議)

目錄 一、UDP概述 二、UDP的首部格式 三、UDP緩沖區 四、基于UDP的應用層協議 五、常見問題 一、UDP概述 UDP(User Datagram Protocol&#xff0c;用戶數據協議報)是傳輸層協議&#xff0c;提供不可靠服務&#xff0c;其特點包括&#xff1a; 無連接&#xff1a;知道對端…

CSP-202309-3-梯度求解

CSP-202309-3-梯度求解 作為一個算法小白&#xff0c;本人第一次接觸大模擬的題&#xff0c;本題的算法參考自&#xff1a;【CSP】202309-3 梯度求解 解題思路 1.輸入處理 getchar();&#xff1a;從標準輸入讀取一個字符。這里它的作用可能是用來“吃掉”&#xff08;消耗&a…

Kafka_04_Topic和日志

Kafka_04_Topic和日志 Topic/PartitionTopicPartition 日志存儲存儲格式日志清理刪除壓縮 Topic/Partition Topic/Partition: Kafka中消息管理的基礎單位 Topic和Partition并不實際存在(僅邏輯上的概念) 如: Topic和Partition關系 // 每個日志文件可對應多個日志分段, 其還可…

緩存篇—緩存擊穿

在很多場景下&#xff0c;我們的業務通常會有幾個數據會被頻繁地訪問&#xff0c;比如秒殺活動&#xff0c;這類被頻地訪問的數據被稱為熱點數據。 如果緩存中的某個熱點數據過期了&#xff0c;此時大量的請求訪問了該熱點數據&#xff0c;就無法從緩存中讀取&#xff0c;直接…

《UE5_C++多人TPS完整教程》學習筆記22 ——《P23 記錄加入的玩家(Couting Incoming Players)》

本文為B站系列教學視頻 《UE5_C多人TPS完整教程》 —— 《P23 記錄加入的玩家&#xff08;Couting Incoming Players&#xff09;》 的學習筆記&#xff0c;該系列教學視頻為 Udemy 課程 《Unreal Engine 5 C Multiplayer Shooter》 的中文字幕翻譯版&#xff0c;UP主&#xff…

前端面試問題(jwt/布局/vue數組下標/扁平化/菜單樹形/url api/新版本)

前端面試問題(jwt/布局/vue數組下標/扁平化/菜單樹形/url api/新版本) 1. jwt鑒權邏輯 前端 JWT 鑒權邏輯通常涉及在發起請求時攜帶 JWT&#xff0c;并在接收到響應后處理可能的授權問題。 1. 用戶登錄&#xff1a; 用戶提供憑證&#xff1a; 用戶在登錄界面輸入用戶名和密碼…

如何使用Docker部署MongoDB并結合內網穿透實現遠程訪問本地數據庫

文章目錄 前言1. 安裝Docker2. 使用Docker拉取MongoDB鏡像3. 創建并啟動MongoDB容器4. 本地連接測試5. 公網遠程訪問本地MongoDB容器5.1 內網穿透工具安裝5.2 創建遠程連接公網地址5.3 使用固定TCP地址遠程訪問 正文開始前給大家推薦個網站&#xff0c;前些天發現了一個巨牛的 …

2024最佳住宅代理IP服務商有哪些?

跨境出海已成為了近幾年的最熱趨勢&#xff0c;大批量的企業開始開拓海外市場&#xff0c;而海外電商領域則是最受歡迎的切入口。新興的tiktok、Temu&#xff0c;老牌的Amazon、Ebay&#xff0c;熱門的Etsy、Mecari等等都是藍海一片。跨境入門并不難&#xff0c;前期的準備中不…

深入理解文件查看命令:cat、more、less、tail、head

在Linux系統中&#xff0c;有許多命令用于查看文件的內容&#xff0c;其中包括cat、more、less、tail和head。這些命令提供了不同的方式來瀏覽文本文件&#xff0c;適用于各種查看需求。在本篇博客中&#xff0c;我們將深入介紹這些命令&#xff0c;并通過示例演示它們的用法。…

Spring Boot打war包部署到Tomcat,訪問頁面404 !!!

水善利萬物而不爭&#xff0c;處眾人之所惡&#xff0c;故幾于道&#x1f4a6; 文章目錄 Spring Boot打war包部署到Tomcat&#xff0c;訪問頁面404 &#xff01;&#xff01;&#xff01;解決辦法&#xff1a;檢查Tomcat版本和Jdk的對應關系&#xff0c;我的Tomcat是6.x&#x…