1.简介
主要看下导航模式的切换逻辑,因为我们有个管理app要动态修改这个模式。
2.SystemNavigationGestureSettings
用到的xml就不用看了,选项都是动态添加的
2.1.onAttach
public void onAttach(Context context) {
super.onAttach(context);
SuggestionFeatureProvider suggestionFeatureProvider = FeatureFactory.getFactory(context)
.getSuggestionFeatureProvider(context);
SharedPreferences prefs = suggestionFeatureProvider.getSharedPrefs(context);
prefs.edit().putBoolean(PREF_KEY_SUGGESTION_COMPLETE, true).apply();
mOverlayManager = IOverlayManager.Stub.asInterface(
ServiceManager.getService(Context.OVERLAY_SERVICE));
//顶部的插图动画选项
mVideoPreference = new IllustrationPreference(context);
//不同的模式设置不同的插图,参考补充1,当前使用的key参考2.2
setIllustrationVideo(mVideoPreference, getDefaultKey());
//遗留数据处理
migrateOverlaySensitivityToSettings(context, mOverlayManager);
}
>1.setIllustrationVideo
- 根据不同的导航模式,加载不同的插图动画
private static void setIllustrationVideo(IllustrationPreference videoPref,
String systemNavKey) {
switch (systemNavKey) {
case KEY_SYSTEM_NAV_GESTURAL:
videoPref.setLottieAnimationResId(R.raw.lottie_system_nav_fully_gestural);
break;
case KEY_SYSTEM_NAV_2BUTTONS:
videoPref.setLottieAnimationResId(R.raw.lottie_system_nav_2_button);
break;
case KEY_SYSTEM_NAV_3BUTTONS:
videoPref.setLottieAnimationResId(R.raw.lottie_system_nav_3_button);
break;
}
}
2.2.getDefaultKey
- 如果没有设置,默认是三键导航
protected String getDefaultKey() {
return getCurrentSystemNavigationMode(getContext());//补充1
}
>1.getCurrentSystemNavigationMode
- 不同的导航模式返回不同的key
- 见3.1,发现就是读取一个字段的值,而且读取的是同一个值,难道返回的结果还可以改变?确实可以。
static String getCurrentSystemNavigationMode(Context context) {
//if条件见3.1
if (SystemNavigationPreferenceController.isGestureNavigationEnabled(context)) {
return KEY_SYSTEM_NAV_GESTURAL;
} else if (SystemNavigationPreferenceController.is2ButtonNavigationEnabled(context)) {
return KEY_SYSTEM_NAV_2BUTTONS;
} else {
return KEY_SYSTEM_NAV_3BUTTONS;
}
}
2.3.updateCandidates
public void updateCandidates() {
final String defaultKey = getDefaultKey();//参考2.2
final String systemDefaultKey = getSystemDefaultKey();//父类返回的是null
final PreferenceScreen screen = getPreferenceScreen();
screen.removeAll();
//添加第一个插图动画选项
screen.addPreference(mVideoPreference);
//获取要加载的数据,补充1
final List<? extends CandidateInfo> candidateList = getCandidates();
if (candidateList == null) {
return;
}
for (CandidateInfo info : candidateList) {
SelectorWithWidgetPreference pref =
new SelectorWithWidgetPreference(getPrefContext());
//绑定对应的数据,补充2
bindPreference(pref, info.getKey(), info, defaultKey);
//绑定选项后边的设置按钮点击事件
bindPreferenceExtra(pref, info.getKey(), info, defaultKey, systemDefaultKey);
screen.addPreference(pref);
}
//如果只有一个选项,自动设置为选中状态
mayCheckOnlyRadioButton();
}
>1.getCandidates
- 支持对应的导航模式,就添加对应的数据
- isOverlayPackageAvailable方法就是查询对应的包是否存在,具体包名见小节5.1
protected List<? extends CandidateInfo> getCandidates() {
final Context c = getContext();
List<CandidateInfoExtra> candidates = new ArrayList<>();
//方法见3.4
if (SystemNavigationPreferenceController.isOverlayPackageAvailable(c,
NAV_BAR_MODE_GESTURAL_OVERLAY)) {
candidates.add(new CandidateInfoExtra(
c.getText(R.string.edge_to_edge_navigation_title),
c.getText(R.string.edge_to_edge_navigation_summary),
KEY_SYSTEM_NAV_GESTURAL, true /* enabled */));
}
if (SystemNavigationPreferenceController.isOverlayPackageAvailable(c,
NAV_BAR_MODE_2BUTTON_OVERLAY)) {
candidates.add(new CandidateInfoExtra(
c.getText(R.string.swipe_up_to_switch_apps_title),
c.getText(R.string.swipe_up_to_switch_apps_summary),
KEY_SYSTEM_NAV_2BUTTONS, true /* enabled */));
}
if (SystemNavigationPreferenceController.isOverlayPackageAvailable(c,
NAV_BAR_MODE_3BUTTON_OVERLAY)) {
candidates.add(new CandidateInfoExtra(
c.getText(R.string.legacy_navigation_title),
c.getText(R.string.legacy_navigation_summary),
KEY_SYSTEM_NAV_3BUTTONS, true /* enabled */));
}
return candidates;
}
>2.bindPreference
public SelectorWithWidgetPreference bindPreference(SelectorWithWidgetPreference pref,
String key, CandidateInfo info, String defaultKey) {
pref.setTitle(info.loadLabel());
pref.setIcon(Utils.getSafeIcon(info.loadIcon()));
pref.setKey(key);
//跟默认的key一样,设为选中状态
if (TextUtils.equals(defaultKey, key)) {
pref.setChecked(true);
}
pref.setEnabled(info.enabled);
//点击事件在父类,最终子类实现,见2.4
pref.setOnClickListener(this);
return pref;
}
2.4.onRadioButtonClicked
父类的方法,具体子类实现
public void onRadioButtonClicked(SelectorWithWidgetPreference selected) {
final String selectedKey = selected.getKey();
onRadioButtonConfirmed(selectedKey);//补充1
}
>1.onRadioButtonConfirmed
protected void onRadioButtonConfirmed(String selectedKey) {
final boolean success = setDefaultKey(selectedKey);//参考2.5
if (success) {
//修改导航模式的选中状态,循环所有模式,不是当前点击的就设置为非选中状态
updateCheckedState(selectedKey);
}
onSelectionPerformed(success);
}
2.5.setDefaultKey
protected boolean setDefaultKey(String key) {
//更新导航模式,补充1
setCurrentSystemNavigationMode(mOverlayManager, key);
//更新插图动画
setIllustrationVideo(mVideoPreference, key);
setGestureNavigationTutorialDialog(key);
return true;
}
>1.setCurrentSystemNavigationMode
- 对应的包名见小节5.1
static void setCurrentSystemNavigationMode(IOverlayManager overlayManager, String key) {
String overlayPackage = NAV_BAR_MODE_GESTURAL_OVERLAY;
switch (key) {
case KEY_SYSTEM_NAV_GESTURAL:
overlayPackage = NAV_BAR_MODE_GESTURAL_OVERLAY;
break;
case KEY_SYSTEM_NAV_2BUTTONS:
overlayPackage = NAV_BAR_MODE_2BUTTON_OVERLAY;
break;
case KEY_SYSTEM_NAV_3BUTTONS:
overlayPackage = NAV_BAR_MODE_3BUTTON_OVERLAY;
break;
}
try {
//见小节4,修改RRO包
overlayManager.setEnabledExclusiveInCategory(overlayPackage, USER_CURRENT);
}
}
3.SystemNavigationPreferenceController.java
3.1.isGestureNavigationEnabled
static boolean is2ButtonNavigationEnabled(Context context) {
return NAV_BAR_MODE_2BUTTON == context.getResources().getInteger(
com.android.internal.R.integer.config_navBarInteractionMode);
}
static boolean isGestureNavigationEnabled(Context context) {
return NAV_BAR_MODE_GESTURAL == context.getResources().getInteger(
com.android.internal.R.integer.config_navBarInteractionMode);
}
>1.config_navBarInteractionMode
- 判断导航模式就是读取的这个值,可这个值难道可以改变吗?可以
- 不同的RRO包下这个值不一样,我们通过enable不同的RRO包,那么覆盖后的资源文件内容也就不同
<!-- Controls the navigation bar interaction mode:
0: 3 button mode (back, home, overview buttons)
1: 2 button mode (back, home buttons + swipe up for overview)
2: gestures only for back, home and overview -->
<integer name="config_navBarInteractionMode">0</integer>
3.2.overlay
这里说下上边的config值为啥可以变化,这就牵扯到android的overlay知识了,就是动态替换资源文件
- 我们的3种导航模式,其实有3个对应的overlay包的,每个包里的config值不一样
- 包在路径下:frameworks/base/packages/overlays/NavigationBarModexxx
3.3.isGestureAvailable
static boolean isGestureAvailable(Context context) {
//先看配置里是否允许
if (!context.getResources().getBoolean(
com.android.internal.R.bool.config_swipe_up_gesture_setting_available)) {
return false;
}
// Skip if the recents component is not defined
final ComponentName recentsComponentName = ComponentName.unflattenFromString(
context.getString(com.android.internal.R.string.config_recentsComponentName));
if (recentsComponentName == null) {
return false;
}
// Skip if the overview proxy service exists
final Intent quickStepIntent = new Intent(ACTION_QUICKSTEP)
.setPackage(recentsComponentName.getPackageName());
if (context.getPackageManager().resolveService(quickStepIntent,
PackageManager.MATCH_SYSTEM_ONLY) == null) {
return false;
}
return true;
}
3.4.isOverlayPackageAvailable
- 查看对应的包是否存在
static boolean isOverlayPackageAvailable(Context context, String overlayPackage) {
try {
return context.getPackageManager().getPackageInfo(overlayPackage, 0) != null;
} catch (PackageManager.NameNotFoundException e) {
return false;
}
}
4.OverlayManagerService.java
4.1.setEnabledExclusiveInCategory
public boolean setEnabledExclusiveInCategory(@Nullable String packageName,
final int userIdArg) {
if (packageName == null) {
return false;
}
try {
final OverlayIdentifier overlay = new OverlayIdentifier(packageName);
final int realUserId = handleIncomingUser(userIdArg,
"setEnabledExclusiveInCategory");
//见4.2,这个主要是条件检查
enforceActor(overlay, "setEnabledExclusiveInCategory", realUserId);
final long ident = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
try {
//见4.5
mImpl.setEnabledExclusive(overlay,
true /* withinCategory */, realUserId)
.ifPresent(OverlayManagerService.this::updateTargetPackagesLocked);
return true;
} catch (OperationFailedException e) {
return false;
}
}
} finally {
Binder.restoreCallingIdentity(ident);
}
}
}
4.2.enforceActor
执行对应的方法
private void enforceActor(@NonNull OverlayIdentifier overlay, @NonNull String methodName,
int realUserId) throws SecurityException {
//见4.3
OverlayInfo overlayInfo = mImpl.getOverlayInfo(overlay, realUserId);
if (overlayInfo == null) {
throw new IllegalArgumentException("Unable to retrieve overlay information for "
+ overlay);
}
int callingUid = Binder.getCallingUid();
//4.4
mActorEnforcer.enforceActor(overlayInfo, methodName, callingUid, realUserId);
}
};
4.3.getOverlayInfo
根据包名查找对应的overlay包的信息
OverlayInfo getOverlayInfo
(@NonNull final OverlayIdentifier packageName, final int userId) {
try {
return mSettings.getOverlayInfo(packageName, userId);
} catch (OverlayManagerSettings.BadKeyException e) {
return null;
}
}
4.4.enforceActor
OverlayActorEnforcer.java
void enforceActor(@NonNull OverlayInfo overlayInfo, @NonNull String methodName,
int callingUid, int userId) throws SecurityException {
//补充1
final ActorState actorState = isAllowedActor(methodName, overlayInfo, callingUid, userId);
if (actorState == ActorState.ALLOWED) {
return;
}
//走到这里说明失败了..
throw new SecurityException(errorMessage);
}
>1.isAllowedActor
- 查看overlay包的信息,以及调用者相关的权限检查等
public ActorState isAllowedActor(String methodName, OverlayInfo overlayInfo,
int callingUid, int userId) {
//
switch (callingUid) {
case Process.ROOT_UID:
case Process.SYSTEM_UID:
return ActorState.ALLOWED;
}
final String targetPackageName = overlayInfo.targetPackageName;
final AndroidPackage targetPkgInfo = mPackageManager.getPackageForUser(targetPackageName,
userId);
if (targetPkgInfo == null) {
return ActorState.TARGET_NOT_FOUND;
}
if (targetPkgInfo.isDebuggable()) {
return ActorState.ALLOWED;
}
String[] callingPackageNames = mPackageManager.getPackagesForUid(callingUid);
if (ArrayUtils.isEmpty(callingPackageNames)) {
return ActorState.NO_PACKAGES_FOR_UID;
}
//目标本身总是一个被允许的行动者
if (ArrayUtils.contains(callingPackageNames, targetPackageName)) {
return ActorState.ALLOWED;
}
String targetOverlayableName = overlayInfo.targetOverlayableName;
if (TextUtils.isEmpty(targetOverlayableName)) {
try {
if (mPackageManager.doesTargetDefineOverlayable(targetPackageName, userId)) {
return ActorState.MISSING_TARGET_OVERLAYABLE_NAME;
} else {
// If there's no overlayable defined, fallback to the legacy permission check
try {
mPackageManager.enforcePermission(
android.Manifest.permission.CHANGE_OVERLAY_PACKAGES, methodName);
// If the previous method didn't throw, check passed
return ActorState.ALLOWED;
} catch (SecurityException e) {
return ActorState.MISSING_LEGACY_PERMISSION;
}
}
} catch (IOException e) {
return ActorState.ERROR_READING_OVERLAYABLE;
}
}
OverlayableInfo targetOverlayable;
try {
targetOverlayable = mPackageManager.getOverlayableForTarget(targetPackageName,
targetOverlayableName, userId);
} catch (IOException e) {
return ActorState.UNABLE_TO_GET_TARGET_OVERLAYABLE;
}
if (targetOverlayable == null) {
return ActorState.MISSING_OVERLAYABLE;
}
String actor = targetOverlayable.actor;
if (TextUtils.isEmpty(actor)) {
// If there's no actor defined, fallback to the legacy permission check
try {
mPackageManager.enforcePermission(
android.Manifest.permission.CHANGE_OVERLAY_PACKAGES, methodName);
// If the previous method didn't throw, check passed
return ActorState.ALLOWED;
} catch (SecurityException e) {
return ActorState.MISSING_LEGACY_PERMISSION;
}
}
Map<String, Map<String, String>> namedActors = mPackageManager.getNamedActors();
Pair<String, ActorState> actorUriPair = getPackageNameForActor(actor, namedActors);
ActorState actorUriState = actorUriPair.second;
if (actorUriState != ActorState.ALLOWED) {
return actorUriState;
}
String actorPackageName = actorUriPair.first;
AndroidPackage actorPackage = mPackageManager.getPackageForUser(actorPackageName, userId);
if (actorPackage == null) {
return ActorState.ACTOR_NOT_FOUND;
}
// Currently only pre-installed apps can be actors
if (!actorPackage.isSystem()) {
return ActorState.ACTOR_NOT_PREINSTALLED;
}
if (ArrayUtils.contains(callingPackageNames, actorPackageName)) {
return ActorState.ALLOWED;
}
return ActorState.INVALID_ACTOR;
}
4.5.setEnabledExclusive
Optional<PackageAndUser> setEnabledExclusive(@NonNull final OverlayIdentifier overlay,
boolean withinCategory, final int userId) throws OperationFailedException {
//..
try {
final OverlayInfo enabledInfo = mSettings.getOverlayInfo(overlay, userId);
if (!enabledInfo.isMutable) {
//只能是动态包,静态的是不可变的,不可修改的
throw new OperationFailedException(
"cannot enable immutable overlay packages in runtime");
}
//从要禁用的覆盖列表中删除要启用的覆盖
//查找目标包的所有overlay信息
List<OverlayInfo> allOverlays = getOverlayInfosForTarget(enabledInfo.targetPackageName,
userId);
//先移除目标包的已有的overlay包
allOverlays.remove(enabledInfo);
boolean modified = false;
//循环把所有目标包的overlay包先disable掉
for (int i = 0; i < allOverlays.size(); i++) {
final OverlayInfo disabledInfo = allOverlays.get(i);
final OverlayIdentifier disabledOverlay = disabledInfo.getOverlayIdentifier();
if (!disabledInfo.isMutable) {
// Don't touch immutable overlays.
continue;
}
if (withinCategory && !Objects.equals(disabledInfo.category,
enabledInfo.category)) {
// Don't touch overlays from other categories.
continue;
}
// Disable the overlay.
modified |= mSettings.setEnabled(disabledOverlay, userId, false);
modified |= updateState(disabledInfo, userId, 0);
}
//然后重新设置我们选中的overlay包
modified |= mSettings.setEnabled(overlay, userId, true);
modified |= updateState(enabledInfo, userId, 0);
if (modified) {
return Optional.of(new PackageAndUser(enabledInfo.targetPackageName, userId));
}
return Optional.empty();
}
}
5.WindowManagerPolicyConstants
5.1.overlays
//不同的导航模式的覆盖包名
String NAV_BAR_MODE_3BUTTON_OVERLAY = "com.android.internal.systemui.navbar.threebutton";
String NAV_BAR_MODE_2BUTTON_OVERLAY = "com.android.internal.systemui.navbar.twobutton";
String NAV_BAR_MODE_GESTURAL_OVERLAY = "com.android.internal.systemui.navbar.gestural";
5.2.gestural overlay
简单贴下guestural相关的overlay资源
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.internal.systemui.navbar.gestural"
android:versionCode="1"
android:versionName="1.0">
<overlay android:targetPackage="android"
android:category="com.android.internal.navigation_bar_mode"
android:priority="1"/>
<application android:label="@string/navigation_bar_mode_title" android:hasCode="false"/>
</manifest>
>1.config.xml
重写了mode值为2
<!-- Controls the navigation bar interaction mode:
0: 3 button mode (back, home, overview buttons)
1: 2 button mode (back, home buttons + swipe up for overview)
2: gestures only for back, home and overview -->
<integer name="config_navBarInteractionMode">2</integer>
5.3.cmd
我们可以通过命令直接切换导航模式
>手势导航
adb shell cmd overlay enable com.android.internal.systemui.navbar.gestural
>三键导航
adb shell cmd overlay enable com.android.internal.systemui.navbar.threebutton
6.总结
- 导航模式切换,最终其实就是enable不同的overlay包,具体见2.5,最终实现见小节4
- RRO相关的知识见小节5贴的地址