问题讨论-需求场景
何为手势和物理按键、虚拟导航
Android11 开始支持了手势操作,如大家目前手机基本上都是手势操作形式;早期都是物理按键或者虚拟按键的操作。
手势导航和虚拟导航如何选择
系统层面:设置->系统->手势->手势切换
如何应用程序中让用户自己选择,自由选择设置
强制手势或者底部虚拟导航
定制的很多案子,强制使用手势或者底部虚拟导航:手势导航和虚拟导航二选一,那么如何定制客户选择其一时候有要求虚拟导航或者底部导航,不允许修改的。如何不让用户修改。
参考资料
如何配置导航方式
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
*/
@Readable
public 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 方法, 但是服务的方法本身不是对外释放的,那就反射。
@Override
public 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.java
getAvailabilityStatus 方法,修改如下:
@Override
public 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
}
}