Android7.0隱藏Immersive Mode提示

一、介紹

Immersive模式是Android提供那些用戶需要充分與屏幕交互的app的一種功能。例如玩游戲、在圖庫中瀏覽圖片,又或者閱讀分頁內容(電子書或ppt)。在這種模式下系統的狀態欄和導航欄會被隱藏,最大化屏幕的使用。而當用戶要“召喚”回系統的狀態欄或導航欄時,只需要從狀態欄或導航欄隱藏的位置邊緣向屏幕中心滑動即可。

如何進入Immersive模式在google的文檔和網上的資料有大量的介紹,這里要說的是當第一次進入Immersive模式時彈出的界面也就是ImmersiveModeConfirmation的提示視圖,它的源碼位于
\frameworks\base\services\core\java\com\android\server\policy\ImmersiveModeConfirmation.java。

二、ImmersiveModeConfirmation的初始化

ImmersiveModeConfirmation是在PhoneWindowManager中init()函數被創建。

/**PhoneWindowManager**/
private ImmersiveModeConfirmation mImmersiveModeConfirmation;

@Override
public void init(Context context, IWindowManager windowManager,
        WindowManagerFuncs windowManagerFuncs) {
   ...
   mImmersiveModeConfirmation = new ImmersiveModeConfirmation(mContext);
   ...
}

當ImmersiveModeConfirmation被創建完成后,PhoneWindowManager會在updateSettings()函數中調用ImmersiveModeConfirmation的loadSetting()函數對它進行初始化的配置。

/**PhoneWindowManager**/
public void updateSettings() {
    ...
    synchronized (mLock) {
        ...
        if (mImmersiveModeConfirmation != null) {
                mImmersiveModeConfirmation.loadSetting(mCurrentUserId);
        }
    }
    synchronized (mWindowManagerFuncs.getWindowManagerLock()) {
            PolicyControl.reloadFromSetting(mContext);
    }
    ...
}

loadSetting()函數用來讀取 Settings.Secure.IMMERSIVE_MODE_CONFIRMATIONS所對應的值也就是ImmersiveModeConfirmation是否被確認。

/***\frameworks\base\services\core\java\com\android\server\policy\ImmersiveModeConfirmation.java*/
public void loadSetting(int currentUserId) {
        mConfirmed = false;
        mCurrentUserId = currentUserId;
        if (DEBUG) Slog.d(TAG, String.format("loadSetting() mCurrentUserId=%d", mCurrentUserId));
        String value = null;
        try {
            value = Settings.Secure.getStringForUser(mContext.getContentResolver(),
                    Settings.Secure.IMMERSIVE_MODE_CONFIRMATIONS,
                    UserHandle.USER_CURRENT);
            mConfirmed = CONFIRMED.equals(value);
            if (DEBUG) Slog.d(TAG, "Loaded mConfirmed=" + mConfirmed);
        } catch (Throwable t) {
            Slog.w(TAG, "Error loading confirmations, value=" + value, t);
        }
    }

然后,在reloadFromSetting()函數中會讀取Settings.Global.POLICY_CONTROL所對應的字符串value,然后通過setFilters()函數初始化三個Filter:immersiveStatusFilter,immersiveNavigationFilter,immersivePreconfirmationsFilter。Filter是PolicControl的內部類,后面會詳細介紹Filter的作用。

public static void reloadFromSetting(Context context) {
        if (DEBUG) Slog.d(TAG, "reloadFromSetting()");
        String value = null;
        try {
            value = Settings.Global.getStringForUser(context.getContentResolver(),
                    Settings.Global.POLICY_CONTROL,
                    UserHandle.USER_CURRENT);
            if (sSettingValue != null && sSettingValue.equals(value)) return;
            setFilters(value);
            sSettingValue = value;
        } catch (Throwable t) {
            Slog.w(TAG, "Error loading policy control, value=" + value, t);
        }
}

三、ImmersiveModeConfirmation的顯示

我的目的是要在目標app中隱藏ImmersiveModeConfirmation提示,與是找到控制ImmersiveModeConfirmation提示顯示的位置。就是在ImmersiveModeConfirmation中的handleShow()函數。

/**\frameworks\base\services\core\java\com\android\server\policy\ImmersiveModeConfirmation.java**/
 private void handleShow() {
        if (DEBUG) Slog.d(TAG, "Showing immersive mode confirmation");

        mClingWindow = new ClingWindowView(mContext, mConfirm);

        // we will be hiding the nav bar, so layout as if it's already hidden
        mClingWindow.setSystemUiVisibility(
                View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
              | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);

        // show the confirmation
        WindowManager.LayoutParams lp = getClingWindowLayoutParams();
        mWindowManager.addView(mClingWindow, lp);
 }

可以看到ImmersiveModeConfirmation所顯示的視圖就是ClingWindowView,然后發現ImmersiveModeConfirmation中的自定義的Handler H 的handleMessage調用了handleShow()。繼續往上找我們發現雖然有幾處最終調用handleShow()的地方,但真正讓ClingWindowView顯示的位置在ImmersiveModeConfirmation的immersiveModeChangedLw()函數中,這個函數在每次SystemUI更新的時候都會執行,也就是說當app第一次進入Immersive模式也會執行它。

/**\frameworks\base\services\core\java\com\android\server\policy\ImmersiveModeConfirmation.java**/
public void immersiveModeChangedLw(String pkg, boolean isImmersiveMode,
            boolean userSetupComplete, boolean navBarEmpty) {
        mHandler.removeMessages(H.SHOW);
        if (isImmersiveMode) {
           
            final boolean disabled = PolicyControl.disableImmersiveConfirmation(pkg);
            
            if (DEBUG) Slog.d(TAG, String.format("immersiveModeChanged() disabled=%s mConfirmed=%s",
                    disabled, mConfirmed));
            if (!disabled
                    && (DEBUG_SHOW_EVERY_TIME || !mConfirmed)
                    && userSetupComplete
                    && !mVrModeEnabled
                    && !navBarEmpty
                    && !UserManager.isDeviceInDemoMode(mContext)) {
                mHandler.sendEmptyMessageDelayed(H.SHOW, mShowDelayMs);
            }
        } else {
            mHandler.sendEmptyMessage(H.HIDE);
        }
    }

由此函數可見在要顯示ImmersiveModeConfirmation提示前會先判斷當前的app是否被允許顯示這個提示,進入PolicyControl的disableImmersiveConfirmation(pkg)函數。

public static boolean disableImmersiveConfirmation(String pkg) {
        return (sImmersivePreconfirmationsFilter != null
                && sImmersivePreconfirmationsFilter.matches(pkg))
                || ActivityManager.isRunningInTestHarness();
}

這里就跟前面的三個Filter對應了,由于我們只關注了隱藏ImmersiveModeConfirmation提示,所以這個函數只涉及到sImmersivePreconfirmationsFilter這個Filter。所以我們知道只要sImmersivePreconfirmationsFilter不為空并且sImmersivePreconfirmationsFilter的matches()函數返回為true就不會顯示ImmersiveModeConfirmation提示。那么進入match()函數。

/**\frameworks\base\services\core\java\com\android\server\policy\PolicyControl.java**/
boolean matches(String packageName) {
            return !onBlacklist(packageName) && onWhitelist(packageName);
}

這下就明白了,只要app的包名不在sImmersivePreconfirmationsFilter的mBlacklist并且在mWhitelist中則不會在當前app中顯示ImmersiveModeConfirmation提示。

private boolean onBlacklist(String packageName) {
        return mBlacklist.contains(packageName) || mBlacklist.contains(ALL);
}
private boolean onWhitelist(String packageName) {
        return mWhitelist.contains(ALL) || mWhitelist.contains(packageName);
}

四、sImmersivePreconfirmationsFilter,sImmersiveStatusFilter,sImmersiveNavigationFilter的初始化

接下來我們就來看一下sImmersivePreconfirmationsFilter與其它兩個Filter是如何創建的。首先是包含它們的PolicyControl。

/**\frameworks\base\services\core\java\com\android\server\policy\PolicyControl.java**/
/**
 * Runtime adjustments applied to the global window policy.
 *
 * This includes forcing immersive mode behavior for one or both system bars (based on a package
 * list) and permanently disabling immersive mode confirmations for specific packages.
 *
 * Control by setting {@link Settings.Global.POLICY_CONTROL} to one or more name-value pairs.
 * e.g.
 *   to force immersive mode everywhere:
 *     "immersive.full=*"
 *   to force transient status for all apps except a specific package:
 *     "immersive.status=apps,-com.package"
 *   to disable the immersive mode confirmations for specific packages:
 *     "immersive.preconfirms=com.package.one,com.package.two"
 *
 * Separate multiple name-value pairs with ':'
 *   e.g. "immersive.status=apps:immersive.preconfirms=*"
 */
public class PolicyControl {

然后我們回到之前的reloadFromSetting(),其中調用了setFilter(),這個函數就是用來解析Settings.Global.POLICY_CONTROL對應的字符串來創建三個Filter。

/**\frameworks\base\services\core\java\com\android\server\policy\PolicyControl.java**/
private static void setFilters(String value) {
        if (DEBUG) Slog.d(TAG, "setFilters: " + value);
        sImmersiveStatusFilter = null;
        sImmersiveNavigationFilter = null;
        sImmersivePreconfirmationsFilter = null;
        if (value != null) {
            String[] nvps = value.split(":");
            for (String nvp : nvps) {
                int i = nvp.indexOf('=');
                if (i == -1) continue;
                String n = nvp.substring(0, i);
                String v = nvp.substring(i + 1);
                Slog.d(TAG, "n: " + n + ", v: " + v);
                if (n.equals(NAME_IMMERSIVE_FULL)) {
                    Filter f = Filter.parse(v);
                    sImmersiveStatusFilter = sImmersiveNavigationFilter = f;
                    if (sImmersivePreconfirmationsFilter == null) {
                        sImmersivePreconfirmationsFilter = f;
                    }
                } else if (n.equals(NAME_IMMERSIVE_STATUS)) {
                    Filter f = Filter.parse(v);
                    sImmersiveStatusFilter = f;
                } else if (n.equals(NAME_IMMERSIVE_NAVIGATION)) {
                    Filter f = Filter.parse(v);
                    sImmersiveNavigationFilter = f;
                    if (sImmersivePreconfirmationsFilter == null) {
                        sImmersivePreconfirmationsFilter = f;
                    }
                } else if (n.equals(NAME_IMMERSIVE_PRECONFIRMATIONS)) {
                    Filter f = Filter.parse(v);
                    sImmersivePreconfirmationsFilter = f;
                }
            }
        }
    ...
}

可以看到setFilter()將value以“:”拆分并存入nvps字符數組中,然將nvps中的各個子字符串再以“=”拆分成n和v然后我們關注Filter.parse()是如何解析v的。

/**\frameworks\base\services\core\java\com\android\server\policy\PolicyControl.java**/
        // value = comma-delimited list of tokens, where token = (package name|apps|*)
        // e.g. "com.package1", or "apps, com.android.keyguard" or "*"
        static Filter parse(String value) {
            if (value == null) return null;
            ArraySet<String> whitelist = new ArraySet<String>();
            ArraySet<String> blacklist = new ArraySet<String>();
            for (String token : value.split(",")) {
                token = token.trim();
                if (token.startsWith("-") && token.length() > 1) {
                    token = token.substring(1);
                    blacklist.add(token);
                } else {
                    whitelist.add(token);
                }
            }
            return new Filter(whitelist, blacklist);
        }

這里我們看到,若value不為空,則Filter的parse()函數會以“,”來拆分value為一個個包名,然后將以“-”為開頭的包名存在blacklist的ArraySet中,不以“-”開頭的包名存在whitelist中,最后以這兩個ArraySet為參數new一個Filter。然后我們回到setFilter()函數,這時已經有了n的值與解析創建出來的Filter,就可以根據n來給三中Filter賦值。

結論

到此就可以知道,可以在Settings.Global.POLICY_CONTROL中依照PolicyControl的規則在PhoneWindowManager的updateSettings()之前,將所要隱藏ImmersiveModeConfirmation提示的app的包名加入sImmersivePreconfirmationsFilter 的mWhitelist中即可。例如:

    String final preconfirmIMCWhitelist = "immersive.preconfirms=com.package.one,com.package.two";
    private void initIMCWhitelist() {
        Settings.Global.putStringForUser(mContext.getContentResolver(),
                Settings.Global.POLICY_CONTROL,
                preconfirmIMCWhitelist ,
                UserHandle.USER_CURRENT);
    }
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 1、通過CocoaPods安裝項目名稱項目信息 AFNetworking網絡請求組件 FMDB本地數據庫組件 SD...
    陽明AGI閱讀 16,009評論 3 119
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 173,372評論 25 708
  • 發現 關注 消息 iOS 第三方庫、插件、知名博客總結 作者大灰狼的小綿羊哥哥關注 2017.06.26 09:4...
    肇東周閱讀 12,245評論 4 61
  • 當你的每一步,都是為了自己清晰而具體的目標而努力的時候,才是真正的努力。
    午后y閱讀 205評論 0 0
  • 周鴻祎說,以打工的心態,你就先輸了;把自己當成打工的,一輩子都是打工的;360公司需要的,不是打工者。把自己放空,...
    e19c0228dcb3閱讀 295評論 0 0