问题讨论-需求场景
何为手势和物理按键、虚拟导航
Android11 开始支持了手势操作,如大家目前手机基本上都是手势操作形式;早期都是物理按键或者虚拟按键的操作。
手势导航和虚拟导航如何选择
系统层面:设置->系统->手势->手势切换
如何应用程序中让用户自己选择,自由选择设置
强制手势或者底部虚拟导航
定制的很多案子,强制使用手势或者底部虚拟导航:手势导航和虚拟导航二选一,那么如何定制客户选择其一时候有要求虚拟导航或者底部导航,不允许修改的。如何不让用户修改。
参考资料
Android导航方式切换
SystemUI导航栏
如何配置导航方式
mtk 、RK 配置都在同一个路径下,修改文件
\frameworks\base\core\res\res\values\config.xml <!-- 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>
应用层如何设置导航方式
源码分析:
/packages/apps/Settings/src/com/android/settings/gestures/SystemNavigationGestureSettings.java
导航栏切换逻辑在 SystemNavigationGestureSettings.java 文件中
在线源码SystemNavigationGestureSettings.java 源码
获取当前导航方式和设置导航方式 核心代码如下
@VisibleForTesting
202 static String getCurrentSystemNavigationMode(Context context) {
203 if (SystemNavigationPreferenceController.isGestureNavigationEnabled(context)) {
204 return KEY_SYSTEM_NAV_GESTURAL;
205 } else if (SystemNavigationPreferenceController.is2ButtonNavigationEnabled(context)) {
206 return KEY_SYSTEM_NAV_2BUTTONS;
207 } else {
208 return KEY_SYSTEM_NAV_3BUTTONS;
209 }
210 }
211
212 @VisibleForTesting
213 static void setCurrentSystemNavigationMode(IOverlayManager overlayManager, String key) {
214 String overlayPackage = NAV_BAR_MODE_GESTURAL_OVERLAY;
215 switch (key) {
216 case KEY_SYSTEM_NAV_GESTURAL:
217 overlayPackage = NAV_BAR_MODE_GESTURAL_OVERLAY;
218 break;
219 case KEY_SYSTEM_NAV_2BUTTONS:
220 overlayPackage = NAV_BAR_MODE_2BUTTON_OVERLAY;
221 break;
222 case KEY_SYSTEM_NAV_3BUTTONS:
223 overlayPackage = NAV_BAR_MODE_3BUTTON_OVERLAY;
224 break;
225 }
226
227 try {
228 overlayManager.setEnabledExclusiveInCategory(overlayPackage, USER_CURRENT);
229 } catch (RemoteException e) {
230 throw e.rethrowFromSystemServer();
231 }
232 }
获取当前导航方式
Settings数据库中:
adb shell settings get Secure navigation_mode
位置:
frameworks/base/core/java/android/provider/Settings.java/*** Navigation bar mode.* 0 = 3 button* 1 = 2 button* 2 = fully gestural* @hide*/@Readablepublic static final String NAVIGATION_MODE ="navigation_mode";
所以直接通过系统标准API 获取Setting 相关属性值来获取当前导航方式
public static Mode getNavigationMode(Context context) {try {ContentResolver resolver = context.getApplicationContext().getContentResolver();int mode = Settings.Secure.getInt(resolver, "navigation_mode");if (mode == 0) {return Mode.THREEBUTTON;} else if (mode == 2) {return Mode.GESTURAL;}} catch (Exception e) {e.printStackTrace();}return null;}
设置导航栏
回归到 SystemNavigationGestureSettings.java 类的 setCurrentSystemNavigationMode 方法,最终调用
overlayManager.setEnabledExclusiveInCategory(overlayPackage, USER_CURRENT);
思路:拿到 overlayManager 然后调用 setEnabledExclusiveInCategory 方法即可。
SystemNavigationGestureSettings 类中,overlayManager 获取: 不就是获取OVERLAY_SERVICE 的服务吗
mOverlayManager = IOverlayManager.Stub.asInterface(ServiceManager.getService(Context.OVERLAY_SERVICE));
在应用中获取思路
Object mIOverlayManager = context.getSystemService("overlay")
然后调用 setEnabledExclusiveInCategory 方法, 但是服务的方法本身不是对外释放的,那就反射。
@Overridepublic boolean setEnabledExclusiveInCategory(@Nullable String packageName,final int userIdArg) {if (packageName == null) {return false;}try {traceBegin(TRACE_TAG_RRO, "OMS#setEnabledExclusiveInCategory " + packageName);final OverlayIdentifier overlay = new OverlayIdentifier(packageName);final int realUserId = handleIncomingUser(userIdArg,"setEnabledExclusiveInCategory");enforceActor(overlay, "setEnabledExclusiveInCategory", realUserId);final long ident = Binder.clearCallingIdentity();try {synchronized (mLock) {try {mImpl.setEnabledExclusive(overlay,true /* withinCategory */, realUserId).ifPresent(OverlayManagerService.this::updateTargetPackagesLocked);return true;} catch (OperationFailedException e) {return false;}}} finally {Binder.restoreCallingIdentity(ident);}} finally {traceEnd(TRACE_TAG_RRO);}}
规避客户切换导航栏
在默认了系统导航栏 上面已经给出了用户可以切换导航栏的。 很多产品在默认导航栏后不允许客户切换导航方式的。 那么就需要在系统设置里面规避,隐藏切换方式。
涉及到的源码修改:
/packages/apps/Settings/src/com/android/settings/gestures/SystemNavigationPreferenceController.javagetAvailabilityStatus 方法,修改如下:@Overridepublic int getAvailabilityStatus() {// return isGestureAvailable(mContext) ? UNSUPPORTED_ON_DEVICE : UNSUPPORTED_ON_DEVICE;return isGestureAvailable(mContext) ? AVAILABLE : UNSUPPORTED_ON_DEVICE;}
如果允许设置里面修改,用默认的:return isGestureAvailable(mContext) ? AVAILABLE : UNSUPPORTED_ON_DEVICE;如果不允许修改,那么 返回如下:// return isGestureAvailable(mContext) ? UNSUPPORTED_ON_DEVICE : UNSUPPORTED_ON_DEVICE;
其它扩展知识
导航栏高度:navigation_bar_height
frameworks/base/core/res/res/values/dimens.xml <!-- Height of the bottom navigation / system bar. --><dimen name="navigation_bar_height">48dp</dimen><!-- Height of the bottom navigation bar in portrait; often the same as @dimen/navigation_bar_height --><dimen name="navigation_bar_height_landscape">48dp</dimen>
涉及到部分图示和工具类
设置和获取手导航
import android.annotation.SuppressLint;
import android.content.ContentResolver;
import android.content.Context;
import android.os.UserHandle;
import android.provider.Settings;import java.lang.reflect.Constructor;
import java.lang.reflect.Method;public class NavigationHelper {/*** 设置导航模式** @param context* @param mode GESTURAL:手势 TWOBUTTON:二按钮 THREEBUTTON:三按钮*/public static void setNavigationMode(Context context, Mode mode) {try {// Object mIOverlayManager = context.getSystemService("overlay");@SuppressLint("WrongConstant") Object mIOverlayManager = context.getSystemService(Context.OVERLAY_SERVICE);Method setEnabledExclusiveInCategory = mIOverlayManager.getClass().getMethod("setEnabledExclusiveInCategory", String.class, UserHandle.class);setEnabledExclusiveInCategory.setAccessible(true);String overlayPackage = "com.android.internal.systemui.navbar.gestural";if (mode == Mode.TWOBUTTON) {overlayPackage = "com.android.internal.systemui.navbar.twobutton";} else if (mode == Mode.THREEBUTTON) {overlayPackage = "com.android.internal.systemui.navbar.threebutton";}UserHandle userHandle = (UserHandle) newInstance(UserHandle.class, new Class[]{int.class}, 0);setEnabledExclusiveInCategory.invoke(mIOverlayManager, overlayPackage, userHandle);} catch (Throwable e) {e.printStackTrace();}}/*** 获取导航模式* @param context* @return*/public static Mode getNavigationMode(Context context) {try {ContentResolver resolver = context.getApplicationContext().getContentResolver();int mode = Settings.Secure.getInt(resolver, "navigation_mode");if (mode == 0) {return Mode.THREEBUTTON;} else if (mode == 2) {return Mode.GESTURAL;}} catch (Exception e) {e.printStackTrace();}return null;}/*** 开启经典导航模式** @param enable*/public static void setClassicNavigationMode(boolean enable) {try {Class c = Class.forName("android.os.SystemProperties");Method method = c.getMethod("set", String.class, String.class);method.setAccessible(true);method.invoke(null, "persist.sys.classic.navigation.mode", String.valueOf(enable));} catch (Throwable e) {e.printStackTrace();}}private static Object newInstance(Class clazz, Class[] argsType, Object... args) {Object instance = null;try {Constructor constructor = clazz.getConstructor(argsType);constructor.setAccessible(true);instance = constructor.newInstance(args);} catch (Throwable e) {e.printStackTrace();}return instance;}public enum Mode {GESTURAL,TWOBUTTON,THREEBUTTON}}