一、介绍
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);
}