@[TOC](Android11、12 动态禁用(隐藏)Home键)
概述
在实际开发中,导航栏的定制化开发比较常见,属于 SystemUI的修改常客。禁用Home键需求调用接口后隐藏Home键。
动态禁用Home键功能实现核心类
vendor\mediatek\proprietary\packages\apps\SystemUI\src\com\android\systemui\statusbar\phone\NavigationBarFragment.java
vendor\mediatek\proprietary\packages\apps\SystemUI\src\com\android\systemui\statusbar\phone\NavigationBarView.java
动态禁用Home键功能分析
在11、12的android系统中,导航栏的布局文件是NavigationBarView,而很显然,Home键的显示逻辑也在里面实现,实际上Home键是一个自定义的Button按钮。
home.xml
<com.android.systemui.statusbar.policy.KeyButtonView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:systemui="http://schemas.android.com/apk/res-auto"
android:id="@+id/home"
android:layout_width="@dimen/navigation_key_width"
android:layout_height="match_parent"
android:layout_weight="0"
systemui:keyCode="3"
android:scaleType="center"
android:contentDescription="@string/accessibility_home"
android:paddingStart="@dimen/navigation_key_padding"
android:paddingEnd="@dimen/navigation_key_padding"
/>
接下来看Home键在NavigationBarView中初始化
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
mActiveRegion.setEmpty();
updateButtonLocation(getBackButton(), mBackButtonBounds, true);
updateButtonLocation(getHomeButton(), mHomeButtonBounds, false);
updateButtonLocation(getRecentsButton(), mRecentsButtonBounds, false);
updateButtonLocation(getRotateSuggestionButton(), mRotationButtonBounds, true);
// TODO: Handle button visibility changes
mOverviewProxyService.onActiveNavBarRegionChanges(mActiveRegion);
mRecentsOnboarding.setNavBarHeight(getMeasuredHeight());
}
...
public ButtonDispatcher getHomeButton() {
return mButtonDispatchers.get(R.id.home);
}
绘制图标
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int w = MeasureSpec.getSize(widthMeasureSpec);
int h = MeasureSpec.getSize(heightMeasureSpec);
if (DEBUG) Log.d(TAG, String.format(
"onMeasure: (%dx%d) old: (%dx%d)", w, h, getMeasuredWidth(), getMeasuredHeight()));
final boolean newVertical = w > 0 && h > w
&& !isGesturalMode(mNavBarMode);
if (newVertical != mIsVertical) {
mIsVertical = newVertical;
if (DEBUG) {
Log.d(TAG, String.format("onMeasure: h=%d, w=%d, vert=%s", h, w,
mIsVertical ? "y" : "n"));
}
reorient();
notifyVerticalChangedListener(newVertical);
}
if (isGesturalMode(mNavBarMode)) {
// Update the nav bar background to match the height of the visible nav bar
int height = mIsVertical
? getResources().getDimensionPixelSize(
com.android.internal.R.dimen.navigation_bar_height_landscape)
: getResources().getDimensionPixelSize(
com.android.internal.R.dimen.navigation_bar_height);
int frameHeight = getResources().getDimensionPixelSize(
com.android.internal.R.dimen.navigation_bar_frame_height);
mBarTransitions.setBackgroundFrame(new Rect(0, frameHeight - height, w, h));
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
重点在于reorient()方法中的updateNavButtonIcons()方法,主要的布局显示隐藏都在里面;
public void reorient() {
updateCurrentView();
((NavigationBarFrame) getRootView()).setDeadZone(mDeadZone);
mDeadZone.onConfigurationChanged(mCurrentRotation);
// force the low profile & disabled states into compliance
mBarTransitions.init();
if (DEBUG) {
Log.d(TAG, "reorient(): rot=" + mCurrentRotation);
}
// Resolve layout direction if not resolved since components changing layout direction such
// as changing languages will recreate this view and the direction will be resolved later
if (!isLayoutDirectionResolved()) {
resolveLayoutDirection();
}
updateNavButtonIcons();
getHomeButton().setVertical(mIsVertical);
}
接下来看看updateNavButtonIcons()方法,导航栏的几个按钮就是在这加载了image,并且控制了显示隐藏。当然了,中间有很多判断条件。
public void updateNavButtonIcons() {
// We have to replace or restore the back and home button icons when exiting or entering
// carmode, respectively. Recents are not available in CarMode in nav bar so change
// to recent icon is not required.
final boolean useAltBack =
(mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) != 0;
KeyButtonDrawable backIcon = mBackIcon;
orientBackButton(backIcon);
KeyButtonDrawable homeIcon = mHomeDefaultIcon;
if (!mUseCarModeUi) {
orientHomeButton(homeIcon);
}
getHomeButton().setImageDrawable(homeIcon);
getBackButton().setImageDrawable(backIcon);
updateRecentsIcon();
// Update IME button visibility, a11y and rotate button always overrides the appearance
mContextualButtonGroup.setButtonVisibility(R.id.ime_switcher,
(mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_IME_SHOWN) != 0);
mBarTransitions.reapplyDarkIntensity();
boolean disableHome = isGesturalMode(mNavBarMode)
|| ((mDisabledFlags & View.STATUS_BAR_DISABLE_HOME) != 0);
// Always disable recents when alternate car mode UI is active and for secondary displays.
boolean disableRecent = isRecentsButtonDisabled();
// Disable the home handle if both hone and recents are disabled
boolean disableHomeHandle = disableRecent
&& ((mDisabledFlags & View.STATUS_BAR_DISABLE_HOME) != 0);
boolean disableBack = !useAltBack && (mEdgeBackGestureHandler.isHandlingGestures()
|| ((mDisabledFlags & View.STATUS_BAR_DISABLE_BACK) != 0));
// When screen pinning, don't hide back and home when connected service or back and
// recents buttons when disconnected from launcher service in screen pinning mode,
// as they are used for exiting.
final boolean pinningActive = ActivityManagerWrapper.getInstance().isScreenPinningActive();
if (mOverviewProxyService.isEnabled()) {
// Force disable recents when not in legacy mode
disableRecent |= !QuickStepContract.isLegacyMode(mNavBarMode);
if (pinningActive && !QuickStepContract.isGesturalMode(mNavBarMode)) {
disableBack = disableHome = false;
}
} else if (pinningActive) {
disableBack = disableRecent = false;
}
ViewGroup navButtons = getCurrentView().findViewById(R.id.nav_buttons);
if (navButtons != null) {
LayoutTransition lt = navButtons.getLayoutTransition();
if (lt != null) {
if (!lt.getTransitionListeners().contains(mTransitionListener)) {
lt.addTransitionListener(mTransitionListener);
}
}
}
getBackButton().setVisibility(disableBack ? View.INVISIBLE : View.VISIBLE);
getHomeButton().setVisibility(disableHome ? View.INVISIBLE : View.VISIBLE);
getRecentsButton().setVisibility(disableRecent ? View.INVISIBLE : View.VISIBLE);
getHomeHandle().setVisibility(disableHomeHandle ? View.INVISIBLE : View.VISIBLE);
}
至此,可以清晰的分析到Home键的加载与显示,接下来将给出实现思路和修改方法。
动态禁用Home键功能实现和修改
1、方案思路
(1)通过广播的形式进行动态控制;
(2)在systemui的NavigationBarFragment中注册广播,进行按键控制;
(3)收到动态隐藏的广播后最好通过Handler发送消息,然后收到消息后处理button的setVisibility;
(4)因为有些情况是需要查询是否禁用Home键的,所以我们在收到消息后根据flag置全局值(系统值);
(5)在重启后导航栏NavigationBarView在初始化时需根据全局值判断是否显示按键;
2、代码实现
我们选择发送广播的方式触发控制时机。
//隐藏HOME键
public static final String LEON_HIDE_HOME_BUTTON = "com.leon.mdm_disabled_home";
//显示HOME键
public static final String LEON_SHOW_HOME_BUTTON = "com.leon.mdm_enabled_home";
//禁止启用HOME
public void setHomeKeyDisabled(Context context, boolean disabled) {
if (disabled){
Intent intent = new Intent();
intent.setPackage("com.android.systemui");
intent.setAction(LEON_HIDE_HOME_BUTTON );
context.sendBroadcast(intent);
}else {
Intent intent = new Intent();
intent.setPackage("com.android.systemui");
intent.setAction(LEON_SHOW_HOME_BUTTON );
context.sendBroadcast(intent);
}
}
在NavigationBarFragment中注册接收广播,处理相应逻辑
vendor/mediatek/proprietary/packages/apps/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
//隐藏HOME键
public static final String LEON_HIDE_HOME_BUTTON = "com.leon.mdm_disabled_home";
//显示HOME键
public static final String LEON_SHOW_HOME_BUTTON = "com.leon.mdm_enabled_home";
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mNavigationBarView = (NavigationBarView) view;
...省略代码...
if (savedInstanceState != null) {
mNavigationBarView.getLightTransitionsController().restoreState(savedInstanceState);
}
mNavigationBarView.setNavigationIconHints(mNavigationIconHints);
mNavigationBarView.setWindowVisible(isNavBarWindowVisible());
prepareNavigationBarView();
checkNavBarModes();
IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
filter.addAction(Intent.ACTION_SCREEN_ON);
filter.addAction(Intent.ACTION_USER_SWITCHED);
//[动态隐藏HOME键][huangjiawei][20240614]begin
filter.addAction(LEON_HIDE_HOME_BUTTON );
filter.addAction(LEON_SHOW_HOME_BUTTON );
//[动态隐藏HOME键][huangjiawei][20240614]end
mBroadcastDispatcher.registerReceiverWithHandler(mBroadcastReceiver, filter,
Handler.getMain(), UserHandle.ALL);
notifyNavigationBarScreenOn();
...省略代码...
}
private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
//[动态隐藏HOME键][huangjiawei][20240614]begin
if (LEON_HIDE_HOME_BUTTON.equals(action)){
grgHandler.removeMessages(MSG_HIDE_HOME_BUTTON);
grgHandler.sendEmptyMessage(MSG_HIDE_HOME_BUTTON);
}
if (LEON_SHOW_HOME_BUTTON.equals(action)){
grgHandler.removeMessages(MSG_SHOW_HOME_BUTTON);
grgHandler.sendEmptyMessage(MSG_SHOW_HOME_BUTTON);
}
//[动态隐藏HOME键][huangjiawei][20240614]end
if (Intent.ACTION_SCREEN_OFF.equals(action)
|| Intent.ACTION_SCREEN_ON.equals(action)) {
notifyNavigationBarScreenOn();
mNavigationBarView.onScreenStateChanged(Intent.ACTION_SCREEN_ON.equals(action));
}
if (Intent.ACTION_USER_SWITCHED.equals(action)) {
// The accessibility settings may be different for the new user
updateAccessibilityServicesState(mAccessibilityManager);
}
}
};
可以看到,收到广播后我交给了Handler去处理。
接下来来看看 关键实现
//[动态隐藏HOME键][huangjiawei][20240614]begin
private static final int MSG_HIDE_HOME_BUTTON = 501;
private static final int MSG_SHOW_HOME_BUTTON = 502;
protected final GrgHandler grgHandler = new GrgHandler();
protected class GrgHandler extends Handler {
@Override
public void handleMessage(Message m) {
Context mContext = getContext();
switch (m.what) {
case MSG_HIDE_HOME_BUTTON:
changeNavBarButton(mContext,true);
break;
case MSG_SHOW_HOME_BUTTON:
changeNavBarButton(mContext,false);
break;
}
}
}
public void changeNavBarButton(Context context, boolean disable){
if (mNavigationBarView != null){
mNavigationBarView.getHomeButton().setVisibility(disable ? View.INVISIBLE : View.VISIBLE);
Settings.System.putInt(context.getContentResolver(), "navigationbar_home_button_hide", disable ? 1 : 0);
}
}
//[动态隐藏HOME键][huangjiawei][20240614]end
可以看到,关键是用到了mNavigationBarView 对象,使用它去调度导航栏布局。
可能有疑问了,为啥要设置一个Settings值,这是为了方便初始化的时候根据这个全局值来决定是否显示Home键,也就是重启后,这修改是否还能生效的关键一点。
vendor\mediatek\proprietary\packages\apps\SystemUI\src\com\android\systemui\statusbar\phone\NavigationBarView.java
public void updateNavButtonIcons() {
...省略代码...
ViewGroup navButtons = getCurrentView().findViewById(R.id.nav_buttons);
if (navButtons != null) {
LayoutTransition lt = navButtons.getLayoutTransition();
if (lt != null) {
if (!lt.getTransitionListeners().contains(mTransitionListener)) {
lt.addTransitionListener(mTransitionListener);
}
}
}
//[动态隐藏HOME键][huangjiawei][20240614]begin
if (Settings.System.getInt(getContext().getContentResolver(), "navigationbar_home_button_hide", 0) == 1){
disableHome = true;
}
//[动态隐藏HOME键][huangjiawei][20240614]end
getBackButton().setVisibility(disableBack ? View.INVISIBLE : View.VISIBLE);
getHomeButton().setVisibility(disableHome ? View.INVISIBLE : View.VISIBLE);
getRecentsButton().setVisibility(disableRecent ? View.INVISIBLE : View.VISIBLE);
getHomeHandle().setVisibility(disableHomeHandle ? View.INVISIBLE : View.VISIBLE);
}
当然了,某些情况下,也需要判断是否禁用了Home键,那么根据这个全局值来判断是否禁用了Home键再合适不过了。
//查询是否禁用HOME
@Override
public boolean isHomeKeyDisabled(Context context) {
return Settings.System.getInt(context.getContentResolver(), "navigationbar_home_button_hide", 0) == 1;
}
以上就是动态禁用Home键的方案了,其他键也是一样道理的。欢迎大家探讨学习~