android launcher客制化——将自己的apk设定为开机启动项(home)

文章目录

  • android launcher客制化——将自己的apk设定为开机启动项(home)
    • 1. Description
    • 2. Analysis
    • 3. solution
    • 4. summary

1. Description

  • download mmi version ->should go into the mmitest interface
  • mmitest,一款设备硬件测试apk,主要检查设备硬件好坏。
  • mmi version,相对普通版本(cu版本),mmi版本需要在一开机就启动硬件测试apk mmitest,并且mmi version 因为主要编译后用来测试设备硬件好坏,相对cu版本,除了必要的service和部分模块,一些不必要的service&& 模块则不必安装。
  • 在之前 mmi version的实现中,mmitest apk会在开机完成后接收开机广播android.intent.action.BOOT_COMPLETED来实现自启动逻辑。
  • 但是实际上启动顺序是在launcher界面启动后才启动mmitest apk,中间会有一段时间的间隔

Expect:

  1. 如果是mmi version,将mmitest apk作为launcher启动
  2. 如果是普通版本(cu版本),保持原样(默认的laucher apk 作为开机启动项,mmitest apk作为一个三方apk并隐藏log)

2. Analysis

launcher的启动流程大致如下所示:

Created with Raphaël 2.2.0 startHomeActivityLocked(int userId, String reason):ActivityManagerService.java resolveActivityInfo(Intent intent, int flags, int userId):ActivityManagerService.java resolveIntent(...):PackageManagerService.java resolveIntentInternal(...):PackageManagerService.java chooseBestActivity(intent, resolvedType, flags, query, userId):PackageManagerService.java findPreferredActivity(...):PackageManagerService.java

启动activity的入口是startHomeActivityLocked方法,但是在众多的 activity中如何选择,则是在chooseBestActivity方法中进行的。该方法:

  1. 首先进行判断,如果launcher apk只有一个就直接将它放回给startHomeActivityLocked方法,让它启动。
  2. 如果有多个launcher apk,从偏好设置中获取用户设置好的默认launcher,该偏好设置文件路径为/data/system/users/0/package-restrictions.xml,root后可以查看。
  3. 如果用户还没有设置偏好的activity,则启动ResolverActivity,该 activity会让用户进行偏好设置。

回到本问题,将mmitest apk客制为laucher主要有4种方法:

  1. 删除系统的launcher apk
    只要将mmitest apk中的androidManifest.xml中的main screen添加标签 &&后,该apk在启动时就会作为launcher进行启动。但是因为系统中还有默认的launcher也有这2个标签,所以在设备开机后,系统会询问用户期望将哪个apk作为launcher,具体逻辑如下。
   private ResolveInfo chooseBestActivity(Intent intent, String resolvedType,           int flags, List query, int userId) {       if (query != null) {           final int N = query.size();           if (N == 1) {               return query.get(0);/*如果List中只有一个,这不需要进行选择*/           } else if (N > 1) {               final boolean debug = ((intent.getFlags() & Intent.FLAG_DEBUG_LOG_RESOLUTION) != 0);               // If there is more than one activity with the same priority,               // then let the user decide between them.               ResolveInfo r0 = query.get(0);               ResolveInfo r1 = query.get(1);               if (DEBUG_INTENT_MATCHING || debug) {                   Slog.v(TAG, r0.activityInfo.name + "=" + r0.priority + " vs "                           + r1.activityInfo.name + "=" + r1.priority);               }               // If the first activity has a higher priority, or a different               // default, then it is always desirable to pick it.               if (r0.priority != r1.priority                       || r0.preferredOrder != r1.preferredOrder                       || r0.isDefault != r1.isDefault) {                   return query.get(0);               }               // If we have saved a preference for a preferred activity for               // this Intent, use that.               ResolveInfo ri = findPreferredActivity(intent, resolvedType,/* 此处去查询用户的偏好设置,如果在多个launcher apk的情况下,用户之前已经选定了某个apk 默认一直作为launcher,则直接返回该apk*/                       flags, query, r0.priority, true, false, debug, userId);               if (ri != null) {                   return ri;               }               // If we have an ephemeral app, use it               for (int i = 0; i < N; i++) {                   ri = query.get(i);                   if (ri.activityInfo.applicationInfo.isInstantApp()) {                       final String packageName = ri.activityInfo.packageName;                       final PackageSetting ps = mSettings.mPackages.get(packageName);                       final long packedStatus = getDomainVerificationStatusLPr(ps, userId);                       final int status = (int)(packedStatus >> 32);                       if (status != INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK) {                           return ri;                       }                   }               }               ri = new ResolveInfo(mResolveInfo);               ri.activityInfo = new ActivityInfo(ri.activityInfo);               ri.activityInfo.labelRes = ResolverActivity.getLabelRes(intent.getAction());               // If all of the options come from the same package, show the application's               // label and icon instead of the generic resolver's.               // Some calls like Intent.resolveActivityInfo query the ResolveInfo from here               // and then throw away the ResolveInfo itself, meaning that the caller loses               // the resolvePackageName. Therefore the activityInfo.labelRes above provides               // a fallback for this case; we only set the target package's resources on               // the ResolveInfo, not the ActivityInfo.               final String intentPackage = intent.getPackage();               if (!TextUtils.isEmpty(intentPackage) && allHavePackage(query, intentPackage)) {                   final ApplicationInfo appi = query.get(0).activityInfo.applicationInfo;                   ri.resolvePackageName = intentPackage;                   if (userNeedsBadging(userId)) {                       ri.noResourceId = true;                   } else {                       ri.icon = appi.icon;                   }                   ri.iconResourceId = appi.icon;                   ri.labelRes = appi.labelRes;               }               ri.activityInfo.applicationInfo = new ApplicationInfo(                       ri.activityInfo.applicationInfo);               if (userId != 0) {                   ri.activityInfo.applicationInfo.uid = UserHandle.getUid(userId,                           UserHandle.getAppId(ri.activityInfo.applicationInfo.uid));               }               // Make sure that the resolver is displayable in car mode               if (ri.activityInfo.metaData == null) ri.activityInfo.metaData = new Bundle();               ri.activityInfo.metaData.putBoolean(Intent.METADATA_DOCK_HOME, true);               return ri;/*如果前面的逻辑都没有确定出一个合适的 activity,则会返回com.android.internal.app.ResolverActivity,该activity会显示一个偏好设置界面,提供用户选择*/           }       }       return null;   }

因为需要将mmitest apk设置默认launcher,所以并不希望开机后出现这个选择框。简单的做法就是将系统中lancher apk去掉,这样开机后系统只有mmitest apk具有标签,默认的就会将它作为lancher启动而不出现弹框。
不过这种做法还是不能解决当前的问题,如果是普通版本(cu版本),编译后很可能就没有launcher了,不符合 如果是普通版本(cu版本),保持原样的需求。 当然可以在mkaefile文件中做好判断,即如果当前编译的是cu版本,则将默认的laucher apk加入编译,否则laucher apk不参加编译。
而这种修改虽然可以保持cu版本保留默认的laucher apk但是还是回到了最开始的问题上了,启动后会系统会询问用户期望将哪个apk作为launcher。

  1. 修改intent-filter priority
    参考博客Android framework 使用自定的activity取代默认的Launcher界面描述的,AMS在启动launcher时,会通过resolveActivityInfo方法向PMS查询具有CATEGORY标签的组件,当有多个组件都满足条件的情况下,会依据priority的值的大小来选择,取priority值最大的一个,当有多个组件priority相同的情况,会提示用户进行选择。
    所以在修改mmitest apk的androidManifest.xml标签的同时,对文件PackageManagerService.java的adjustPriority方法修改,如果检测到ApplicationInfo的packageName是mmitest 并且当前版本为mmi version,手动设置mmitest 的main screen activity的intent-filter priority为一个较大的值,否则就设为一个较小的值。此方法验证,未生效
       /**        * Adjusts the priority of the given intent filter according to policy.        * 

*

    *
  • The priority for non privileged applications is capped to '0'
  • *
  • The priority for protected actions on privileged applications is capped to '0'
  • *
  • The priority for unbundled updates to privileged applications is capped to the * priority defined on the system partition
  • *
*

* NOTE: There is one exception. For security reasons, the setup wizard is * allowed to obtain any priority on any action. */ private void adjustPriority( List systemActivities, ActivityIntentInfo intent) { // nothing to do; priority is fine as-is if (intent.getPriority() <= 0) { return; } final ActivityInfo activityInfo = intent.activity.info; final ApplicationInfo applicationInfo = activityInfo.applicationInfo; final boolean privilegedApp = ((applicationInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0); if (!privilegedApp) { // non-privileged applications can never define a priority >0 if (DEBUG_FILTERS) { Slog.i(TAG, "Non-privileged app; cap priority to 0;" + " package: " + applicationInfo.packageName + " activity: " + intent.activity.className + " origPrio: " + intent.getPriority()); } intent.setPriority(0); return; } if (systemActivities == null) { // the system package is not disabled; we're parsing the system partition if (isProtectedAction(intent)) { if (mDeferProtectedFilters) { // We can't deal with these just yet. No component should ever obtain a // >0 priority for a protected actions, with ONE exception -- the setup // wizard. The setup wizard, however, cannot be known until we're able to // query it for the category CATEGORY_SETUP_WIZARD. Which we can't do // until all intent filters have been processed. Chicken, meet egg. // Let the filter temporarily have a high priority and rectify the // priorities after all system packages have been scanned. mProtectedFilters.add(intent); if (DEBUG_FILTERS) { Slog.i(TAG, "Protected action; save for later;" + " package: " + applicationInfo.packageName + " activity: " + intent.activity.className + " origPrio: " + intent.getPriority()); } return; } else { if (DEBUG_FILTERS && mSetupWizardPackage == null) { Slog.i(TAG, "No setup wizard;" + " All protected intents capped to priority 0"); } if (intent.activity.info.packageName.equals(mSetupWizardPackage)) { if (DEBUG_FILTERS) { Slog.i(TAG, "Found setup wizard;" + " allow priority " + intent.getPriority() + ";" + " package: " + intent.activity.info.packageName + " activity: " + intent.activity.className + " priority: " + intent.getPriority()); } // setup wizard gets whatever it wants return; } if (DEBUG_FILTERS) { Slog.i(TAG, "Protected action; cap priority to 0;" + " package: " + intent.activity.info.packageName + " activity: " + intent.activity.className + " origPrio: " + intent.getPriority()); } intent.setPriority(0); return; } } // privileged apps on the system image get whatever priority they request return; }

  1. 修改偏好设置
    当系统有多个apk具有标签 &&时,系统会主动询问用户希望选择哪个apk作为launcher,即设置用户的偏好。
    如果是mmi version,修改此处逻辑,将弹出弹框逻辑去掉并让系统默认选择指定apk作为launcher,则可以实现本问题的需求。分析偏好设置具体逻辑可参考android设置多个类似APP其中的一个为默认。而针对本问题,可以在chooseBestActivity方法在进行最佳 activity匹配时就将指定的 activity设置进偏好设置中去, 该操作的代码实现接口为addPreferredActivity(IntentFilter filter, int match,ComponentName[] set, ComponentName activity, int userId)

  2. 修改系统默认laucher启动标签
    参考让你自己写的Android的Launcher成为系统中第一个启动的,也是唯一的Launcher做法,创建一个私有的filter选项,让它来作为系统lancher的过滤选项。然后将mmitest apk中的androidManifest.xml中的main screen添加该私有标签,这个方法从framework层修改了launcher的启动标签,而原本的将不会作为系统launcher标签,这种做法对系统修改不可谓不大,但是这个做法同样不适用本问题。

3. solution

具体修改如下:

  1. 修改文件services/core/java/com/android/server/pm/PackageManagerService.java
private void setTargetActivityAsPreferredActivity(Intent intent,List query, int userId){/*添加方法*/ final int N =query.size();   if(SystemProperties.getBoolean("/*版本标识*/", false)){    ComponentName[] set = new ComponentName[N];    ComponentName componentName = null;    int bestMatch = 0;    for(int i=0;i bestMatch) bestMatch = r.match;     if("/*目标包名*/".equals(r.activityInfo.packageName)){      componentName=set[i];     }    }             IntentFilter filter = new IntentFilter();       if (intent.getAction() != null) {     filter.addAction(intent.getAction());     }             Set categories = intent.getCategories();       if (categories != null) {     for (String cat : categories) {                     filter.addCategory(cat);           }    }             filter.addCategory(Intent.CATEGORY_DEFAULT);    if(null!=componentName){     addPreferredActivity(filter, bestMatch,set,componentName,userId);    }   }}   private ResolveInfo chooseBestActivity(Intent intent, String resolvedType,int flags, List query, int userId) {       if (query != null) {           final int N = query.size();           if (N == 1) {               return query.get(0);           } else if (N > 1) {               setTargetActivityAsPreferredActivity(intent,query,userId);/*此处调用新加的方法*/               final boolean debug = ((intent.getFlags() & Intent.FLAG_DEBUG_LOG_RESOLUTION) != 0);               // If there is more than one activity with the same priority,               // then let the user decide between them.               /*此处省略*/   }
  1. 修改目标apk
    因为希望cu版本保持launcher apk作为系统launcher,而mmi版本则mmitest apk作为 launcher,那么在cu版本的mmitest apk中不能具有, 所有需要针对2个版本准备不同的AndroidManifest.xml,具体的可以在编译脚本makefile中配置如下:
ifeq ($(/*版本标识*/),true) ${shell cp ./AndroidManifest_mmi.xml ./AndroidManifest.xml}  else ${shell cp ./AndroidManifest_cu.xml ./AndroidManifest.xml}  endif.PHONY: $(LOCAL_PATH)/AndroidManifest.xml$(LOCAL_PATH)/AndroidManifest.xml:LOCAL_MANIFEST_FILE := ./AndroidManifest.xml

然后准备2份AndroidManifest.xml,即AndroidManifest_mmi.xml和./AndroidManifest_cu.xml。而./AndroidManifest_cu.xml就是apk原本的AndroidManifest.xml,./AndroidManifest_mmi.xml在原本的AndroidManifest.xml基础上加入如下语句:

                           +                +                                              

4. summary

这个问题主要需要去了解下laucher的启动流程,然后在弄清系统是怎么去获取到目标acitivity的。对于有多个相同标签时, 系统又是如何选择最优的activity 的,只要知道了这些,就可以有意识的去替换掉它,让系统启动我们需要的activity。

更多相关文章

  1. 安全新手入坑——HTML标签
  2. Nginx系列教程(六)| 手把手教你搭建 LNMP 架构并部署天空网络电影
  3. Android(安卓)后台静默安装
  4. Android知识体系总结2020之Android部分对话框 & 弹框 & 通知 &
  5. 编译Android版本的libmad
  6. Android(安卓)图片文件夹区别
  7. Android(安卓)NDK r8e __gmsl 错误
  8. 如何编译 Chrome for Android
  9. [转] This Android(安卓)SDK requires Android(安卓)Developer T

随机推荐

  1. Android系统服务概要
  2. android 相对布局属性说明
  3. Android核心分析 之十-------Android(安
  4. 【Android应用】【Shape使用总结】
  5. Android(安卓)RelativeLayout 的属性
  6. Android系统中自带的图标&一些预定义样式
  7. android 笔记 --- Android(安卓)Shadow
  8. Android零碎知识点
  9. LibGDX制作android动态壁纸
  10. android 底座充电压力插拔,有概率没有提示