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);
    }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容