安卓源码学习之【导航方式切换分析及实战】

分析

系统启动后通过过滤 SettingsActivity ,然后点击 设置-系统-手势-系统导航,进入系统导航设置界面,通过日志可以看出,

SettingsActivity com.android.settings D Switching to fragment com.android.settings.gestures.SystemNavigationGestureSettings

当前界面就是:com.android.settings.gestures.SystemNavigationGestureSettings

后续需要自定义导航方式切换,代码就从这里复制过来。

SystemNavigationGestureSettings关键代码:

        @VisibleForTesting
        static final String KEY_SYSTEM_NAV_3BUTTONS = "system_nav_3buttons";
        @VisibleForTesting
        static final String KEY_SYSTEM_NAV_2BUTTONS = "system_nav_2buttons";
        @VisibleForTesting
        static final String KEY_SYSTEM_NAV_GESTURAL = "system_nav_gestural";

        \...

            @VisibleForTesting
        static String getCurrentSystemNavigationMode(Context context) {
            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;
            }
        }

        @VisibleForTesting
        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 {
                overlayManager.setEnabledExclusiveInCategory(overlayPackage, USER_CURRENT);
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        }
        \...

getCurrentSystemNavigationMode:获取当前导航方式

setCurrentSystemNavigationMode:设置导航方式

在获取当前导航方式时,通过 SystemNavigationGestureSettings同级目录下 的SystemNavigationPreferenceController中的两个方法去判断导航方式:

       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);
        }

设置导航方式主要通过获取 IOverlayManager设置:

    mOverlayManager = IOverlayManager.Stub.asInterface(
                    ServiceManager.getService(Context.OVERLAY_SERVICE));

实战(自定义导航方式)

继续使用上篇文章的 OOBE 项目,新建一个 SystemNavActivity,当进入欢迎界面后点击进入该界面

布局如下:

在这里插入图片描述

点击该界面的 OK 按钮,则结束开机向导流程,需要将 WelcomeActivity 中的结束开机向导流程代码拷贝过来,选择导航方式调用setCurrentSystemNavigationMode() 代码即可,所以SystemNavActivity 完整代码如下:

    public class SystemNavActivity extends AppCompatActivity {
        private final String TAG = "SystemNavActivity";

        static final String KEY_SYSTEM_NAV_3BUTTONS = "system_nav_3buttons";
        static final String KEY_SYSTEM_NAV_2BUTTONS = "system_nav_2buttons";
        static final String KEY_SYSTEM_NAV_GESTURAL = "system_nav_gestural";

        private IOverlayManager mOverlayManager;


        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_navigation);

            mOverlayManager = IOverlayManager.Stub.asInterface(
                    ServiceManager.getService(Context.OVERLAY_SERVICE));

            ((RadioGroup) findViewById(R.id.rg_navigation)).setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
                @Override
                public void onCheckedChanged(RadioGroup radioGroup, int checkedId) {
                    if (checkedId == R.id.rb_ges) {
                        setCurrentSystemNavigationMode(KEY_SYSTEM_NAV_GESTURAL);
                    } else if (checkedId == R.id.rb_two) {
                        setCurrentSystemNavigationMode(KEY_SYSTEM_NAV_2BUTTONS);
                    } else {
                        setCurrentSystemNavigationMode(KEY_SYSTEM_NAV_3BUTTONS);
                    }
                }
            });

            findViewById(R.id.btn_ok).setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    finishSetup();
                }
            });
        }

        String getCurrentSystemNavigationMode(Context context) {
            if (isGestureNavigationEnabled(context)) {
                return KEY_SYSTEM_NAV_GESTURAL;
            } else if (is2ButtonNavigationEnabled(context)) {
                return KEY_SYSTEM_NAV_2BUTTONS;
            } else {
                return KEY_SYSTEM_NAV_3BUTTONS;
            }
        }

        void setCurrentSystemNavigationMode(String key) {
            if (key.equals(getCurrentSystemNavigationMode(this))) {
                return;
            }
            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 {
                mOverlayManager.setEnabledExclusiveInCategory(overlayPackage, USER_CURRENT);
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        }

        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);
        }

        private void finishSetup() {
            setProvisioningState();
            disableSelfAndFinish();
        }

        private void setProvisioningState() {
            Log.i(TAG, "Setting provisioning state");
            // Add a persistent setting to allow other apps to know the device has been provisioned.
            Settings.Global.putInt(getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 1);
            Settings.Secure.putInt(getContentResolver(), Settings.Secure.USER_SETUP_COMPLETE, 1);
        }

        private void disableSelfAndFinish() {
            // remove this activity from the package manager.
            PackageManager pm = getPackageManager();
            ComponentName name = new ComponentName(this, WelcomeActivity.class);
            Log.i(TAG, "Disabling itself (" + name + ")");
            pm.setComponentEnabledSetting(name, PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
                    PackageManager.DONT_KILL_APP);
            // terminate the activity.
            finish();
        }
    }

接下来无脑导包就行了,注意,个别的系统级别的变量仍会爆红,但我们在上篇导入了 framework.jar在编译时不会报错,所以不必理会。

为了防止 SystemNavActivity finish() 后回到 WelcomeActivity ,需要在WelcomeActivity onCreate方法中添加如下判断,防止逻辑跳转错误:

    if (Settings.Secure.getInt(getContentResolver(),Settings.Secure.USER_SETUP_COMPLETE,0) == 1) {
            finish();
    }

代码作用就是判断开机向导是否结束,结束了,就自然不会进到该界面了,因为我们常见的开机向导功能都包含上一步操作,用户可能还会返回上一个界面进行操作,所以不能简单的 finish()

到这里界面功能就完成了!

因为设置导航方式,涉及到了跨用户通信,这里需要在清单文件中添加如下系统级别权限:

    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL"/>

并且这个权限只能由系统级别的应用才能调用!所以仿照着 Settings 的清单文件,还需在清单文件头部添加以下代码:

    coreApp="true"
    android:sharedUserId="android.uid.system"

至此,自定义开机向导设置导航应用配置完成!接下来就是放到源码中编译验证即可。

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值