仿豌豆荚Smart锁屏,MIUI7直跳辅助功能设置

本文介绍了如何分析并实现类似豌豆荚在MIUI7中直跳到辅助功能设置的功能。通过调试和查看源码,发现小米系统可能对Android源码有所修改,最终找到了直接进入辅助功能设置界面的方法,涉及关键参数的设置和跳转逻辑分析。
摘要由CSDN通过智能技术生成

很早之前就想开始自己的博客之路,但是早于自己懒癌犯了,所以迟迟没有下笔。最近这段时间学习了不少新的东西,也发现了一些有意思的事情,所以觉得应该写下来纪念一下。

豌豆荚锁屏最近的新版本有一个很有意思的东西,第一次安装的时候要求设置,对不同的手机,弹出的设置界面不一样。它有一个静默安装的功能,跟现在很多实现非root静默安装的实现方法一样,就是利用系统的辅助功能,帮助用户进行点击,这个需要用户手动开启它的辅助功能。

在普通的手机里面安装的时候,它的开启辅助功能方法与普通的app并没有什么不同的地方,最多就是跳到辅助功能列表里面,让用户自己实现辅助功能的开启工作。但是在MIUI7的小米手机上,它在进行设置的时候可以直接跳到它相应的程序的辅助功能设置里面去,而非辅助功能列表里面。这让我们亲爱的PM们很是羡慕,PM一句话,这个功能我们也必须要有,于是这个任务就交给了我这个苦逼的程序员来实现。

现在的Android的市场我们都知道,系统被改得面目全非,我们也不能说小米与豌豆荚没有合作,让它获得了什么特权,从而实现了这么一个功能。但是我们不能放弃,问题一步一步来解决,首先我们要看的就是它是怎么跳过去的,于是我们开始了一系列的分析。

我们先来分析它的跳转过程,看它具体是从哪跳到哪的。这一步我们使用 adb进行调试,adb有一个非常有用的命令dumpsys activity可以查看我们的任务栈,如果只想要关于activity栈相关的信息,我们也可以使用dumpsys activity activities来进行。从输出的内容中我们可以看到一些任务栈的相关信息,比如说最近我们处于哪个Activity,我们是从哪跳过来的,intent的相关信息等等。

从我们对它跳转的分析中,我们看到它跳入的是com.android.settint/.Setting这个Activity。但是相应的如果我们通过对Intent传入Setting.Accessibility这个参数来进行跳转的话,我们跳入的其实是com.android.settings./SubSetting这个Activity,而我们从SubSetting这个辅助功能列表页进入我们相应程序的辅助功能设置界面的时候,它进入的Activity是AccessibilitySettinsActivity,如果我们想从外部启动这个Activity是不可能的,因为这个Activity的exported选项是false,也就是说,我们不可能从其它的程序直接对它进行启动。那直接启动这条路是不可能的,接下来怎么办呢?而且从我们对豌豆荚的跳转过程的分析来讲,它其实也不是跳入的AccessibilitySettinsActivity而是进入的com.android.settint/.Setting这个Activity,我们都知道其实设置界面如果是以Google官方的实现的话,其实是就是一个Activity,里面有不同的Framgent来实现生意人功能。那么问题来了,怎么在Intent里面放Extra可以使我们跳入Settting里面相应的Fragment里面去?

分析好了它的跳转,我们便要想办法实现它的跳转。于是,我们就开始了查资料之旅,StackOverflow上面有个比较有用的回答,它讲了我们可以通过传入相应的Fragment的名字和它所需要的参数来达到直接跳转到相应的Fragment的目的。那么我们怎么能知道Settings这个Activity可以处理哪些参数,以及如果能够处理参数,我们应该怎样传入参数呢?这个Google了很久,也没有找到合适的办法,那么只剩下最后一个办法了,看源码呗。源码直接拷贝下来是不可能的,不说源码那么大,而且版本还那么多,所以找到了一个可以在网上直接看源码的地方GC,因为这里的源码最高的版本就到5.1,所以就直接看了5.1的源码。

首先我们都知道,跳入的是Settings里面,于是我去看了Settings的源码,发现它里面全是相关设置Activity的定义,以下是截取的部份,于是我们便想到了,其它它的具体实现是在SettingsActivity里面的

public static class BluetoothSettingsActivity extends SettingsActivity { /* empty */ }
public static class WirelessSettingsActivity extends SettingsActivity { /* empty */ }
public static class SimSettingsActivity extends SettingsActivity { /* empty */ }
public static class TetherSettingsActivity extends SettingsActivity { /* empty */ }
public static class VpnSettingsActivity extends SettingsActivity { /* empty */ }
public static class DateTimeSettingsActivity extends SettingsActivity { /* empty */ }
public static class StorageSettingsActivity extends SettingsActivity { /* empty */ }
public static class WifiSettingsActivity extends SettingsActivity { /* empty */ }

于是我们打开SettingsActivity,这个Activity有点大,里面比较重要的代码片断有:

public static final String EXTRA_SHOW_FRAGMENT = ":settings:show_fragment";
public static final String EXTRA_SHOW_FRAGMENT_ARGUMENTS = ":settings:show_fragment_args";

这个告诉我们它是可以处理Fragment的跳转问题的,那么它可以处理哪些Fragment呢,在它里面定义了一个数组:

 private static final String[] ENTRY_FRAGMENTS = {
            WirelessSettings.class.getName(),
            WifiSettings.class.getName(),
            ...
            AccessibilitySettings.class.getName(),
            com.android.settings.accessibility.ToggleDaltonizerPreferenceFragment.class.getName(),
            ...
 }

它在进入Fragment之前是要进行一个验证的,看传入的Fragment的名字在不在这个列表里面。如果在这里面,就直接跳入那个Fragment。于是思路便来了,我们看到了里面有一个AccessibilitySettings.class.getName(),我们可以先跳到这个辅助功能设置列表里面,再传入一定的参数跳入我们需要跳入的辅助功能的设置界面,多么简单而美好的事情啊。于是我怀着激动的心情打开了AccessibilitySettings的定义,但是悲剧来了,看完了它的源码,根本没有找到可以处理EXTRA_SHOW_FRAGMENT和EXTRA_SHOW_FRAGMENT_ARGUMENTS这样参数的代码。

我们先来理一下AccessibilitySettings所做的事情,AccessibilitySettings首先拿到所有的我们注册了的辅助功能,查询它们是否开启,再把它们都加入到一个list里面,点击这个list的某一项,我们可以进入下一个Fragment,ToggleAccessibilityServicePreferenceFragment,这个Fragment的界面就是我们最终要去的地方,也就是实现我们改变辅助功能设置的界面,而这个Fragment却是我们直接跳不进去的,那怎么办呢?

到这里,我们似乎陷入了一个困境。经过漫长时间的思考,后来想着,如果在系统的源码里面直接可以跳到设置自己的辅助功能选项的界面的话,以现在国内市场上面的情况,所有的有辅助功能选项的软件都直接进入自己的辅助功能设置界面,而不再进入辅助功能列表界面了。我们都知道小米的系统是对Android本身的系统有过很大程度上的自定义,那么,我们是不是可以是说,它对这些的处理跟源码里面不一样呢,于是我对传入Setting里面的Extra进行了各种各样的试验,想通过传入一定的参数实现跳转到AccessibilitySettings,再跳转到ToggleAccessibilityServicePreferenceFragment里面去的连跳的动作,但是实验了很久,还是跳不进去啊。正当我准备抓狂的时候,突然想到,我们能不能不进入AccessibilitySetting,而直接进入ToggleAccessibilityServicePreferenceFragment,虽然从官方的源码里面我们看出如果要直接进入这个Fragment是不可能的,但是小米嘛,我们总不能以常理来衡量的,所以我便试着以这个参数传入了Extra,果然小米就是不一样,我们直接便进入了相应的设置界面,既然界面已经进去了,接下来就是我们应该传入什么样的参数,使得它显示正确的问题了。

我们分析AccessibilitySettings里面对传入ToggleAccessibilityServicePreferenceFragment参数的一些设置

447 for (int i = 0, count = installedServices.size(); i < count; ++i) {
448   AccessibilityServiceInfo info = installedServices.get(i);
449
450   PreferenceScreen preference = getPreferenceManager().createPreferenceScreen(
451      getActivity());
452   String title = info.getResolveInfo().loadLabel(getPackageManager()).toString();
453
454   ServiceInfo serviceInfo = info.getResolveInfo().serviceInfo;
455   ComponentName componentName = new ComponentName(serviceInfo.packageName,
456     serviceInfo.name);
457
458   preference.setKey(componentName.flattenToString());
459
460   preference.setTitle(title);
461   final boolean serviceEnabled = accessibilityEnabled
462     && enabledServices.contains(componentName);
463   String serviceEnabledString;
464   if (serviceEnabled) {
465      serviceEnabledString = getString(R.string.accessibility_feature_state_on);
466   } else {
467      serviceEnabledString = getString(R.string.accessibility_feature_state_off);
468   }
469
470   // Disable all accessibility services that are not permitted.
471   String packageName = serviceInfo.packageName;
472   boolean serviceAllowed =
473     permittedServices == null || permittedServices.contains(packageName);
474       preference.setEnabled(serviceAllowed || serviceEnabled);
475
476   String summaryString;
477   if (serviceAllowed) {
478     summaryString = serviceEnabledString;
479   } else  {
480     summaryString = getString(R.string.accessibility_feature_or_input_method_not_allowed);
481   }
482   preference.setSummary(summaryString);
483
484   preference.setOrder(i);
485   preference.setFragment(ToggleAccessibilityServicePreferenceFragment.class.getName());
486   preference.setPersistent(true);
487
488   Bundle extras = preference.getExtras();
489   extras.putString(EXTRA_PREFERENCE_KEY, preference.getKey());
490   extras.putBoolean(EXTRA_CHECKED, serviceEnabled);
491   extras.putString(EXTRA_TITLE, title);
492
493   String description = info.loadDescription(getPackageManager());
494   if (TextUtils.isEmpty(description)) {
495      description = getString(R.string.accessibility_service_default_description);
496   }
497   extras.putString(EXTRA_SUMMARY, description);
498
499   String settingsClassName = info.getSettingsActivityName();
500     if (!TextUtils.isEmpty(settingsClassName)) {
501        extras.putString(EXTRA_SETTINGS_TITLE,
502        getString(R.string.accessibility_menu_item_settings));
503        extras.putString(EXTRA_SETTINGS_COMPONENT_NAME,
504        new ComponentName(info.getResolveInfo().serviceInfo.packageName,
505          settingsClassName).flattenToString());
506    }
507
508   extras.putParcelable(EXTRA_COMPONENT_NAME, componentName);
509
510   mServicesCategory.addPreference(preference);
511 }

在源码的485行,我们可以知道,这个跳转最终进入的是我们需要进入的ToggleAccessibilityServicePreferenceFragment,而传入它的参数我们可以知道,就是488行的Bundle里面的内容。那么它里面究竟放了些什么参数呢?首先是EXTRA_PREFERENCE_KEY,它的参数是preference.getKey(),而从前面458行代码我们可以知道,这个key其实就是ComponentName,flattenToString之后的字符串;第二个参数,EXTRA_CHECKED,表示的是现在辅助功能是否为开户的状态,这个参数我们用一些方法是可以拿到的;第三个参数,EXTRA_TITLE,这个就好说了,就是452行我们得到的PackageName,第四个参数EXTRA_SUMMARY,这个就是我们在配置辅助功能时候需要配置一个xml文件,里面的的描述字符串,这个我们自己可以拿得到;后面的两个参数EXTRA_SETTINGS_TITLE,EXTRA_SETTINGS_COMPONENT_NAME是在getSettingsActivityName不为空的情况下才需要设置的,这里我没有往里深究,我想应该是在设置辅助功能如果还有其它的一些设置项的话,就需要传入这些个参数,我们所需要的一些辅助功能设置就只有开启和关闭两个选项,没有需要设置其它的功能,所以这两个参数可以不传;最后一个参数EXTRA_COMPONENT_NAME,这个没有什么好说的吧,我们自己的ComponentName构造出来传入就可以直接拿来用了。

网上找的判断自己程序辅助功能是否已经开启的方法:

    /**
     * 判断辅助开关
     *
     * @param context
     * @return
     */
    private static boolean isAccessibilitySettingsOn(Context context) {
        int accessibilityEnabled = 0;
        try {
            accessibilityEnabled = Settings.Secure.getInt(context.getContentResolver(),
                    android.provider.Settings.Secure.ACCESSIBILITY_ENABLED);
        } catch (Settings.SettingNotFoundException e) {
            Log.i("AccessibilitySettingsOn", e.getMessage());
        }
        if (accessibilityEnabled == 1) {
            String services = Settings.Secure.getString(context.getContentResolver(),
                    Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
            if (services != null) {
                return services.toLowerCase().contains(context.getPackageName().toLowerCase());
            }
        }
        return false;
    }

那么,最后我们可以直接跳到最终的辅助功能设置界面的代码就应该是下面这个样子的:

Intent intent = new Intent(Intent.ACTION_MAIN);
ComponentName componentName = new ComponentName("com.android.settings",   "com.android.settings.Settings");
intent.setComponent(componentName);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);

Bundle args = new Bundle();
ComponentName extraComponent = new ComponentName(packageName, serviceClassName);
args.putString(EXTRA_PREFERENCE_KEY, extraComponent.flattenToString());
args.putBoolean(EXTRA_CHECKED, isAccessibilitySettingsOn(mContext));
args.putString(EXTRA_TITLE, mContext.getString(R.string.appName));
args.putString(EXTRA_SUMMARY, mContext.getString(R.string.servicesDescription));
args.putParcelable(EXTRA_COMPONENT_NAME, extraComponent);

Bundle extra = new Bundle();
extra.putString(PreferenceActivity.EXTRA_SHOW_FRAGMENT, "com.android.settings.accessibility.ToggleAccessibilityServicePreferenceFragment");
extra.putParcelable(PreferenceActivity.EXTRA_SHOW_FRAGMENT_ARGUMENTS, args);

intent.putExtras(extra);

context.startActivity(intent);

我不知道豌豆荚是如何对这个功能进行实现的。如果也是这样的实现方式,那不得不佩服他们的程序员,能够发现问题的程序员才是好的程序员。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值