android launcher客制化——将自己的apk设定为开机启动项(home)
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:
- 如果是mmi version,将mmitest apk作为launcher启动
- 如果是普通版本(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方法中进行的。该方法:
- 首先进行判断,如果launcher apk只有一个就直接将它放回给startHomeActivityLocked方法,让它启动。
- 如果有多个launcher apk,从偏好设置中获取用户设置好的默认launcher,该偏好设置文件路径为
/data/system/users/0/package-restrictions.xml
,root后可以查看。 - 如果用户还没有设置偏好的activity,则启动ResolverActivity,该 activity会让用户进行偏好设置。
回到本问题,将mmitest apk客制为laucher主要有4种方法:
- 删除系统的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。
- 修改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; }
-
修改偏好设置
当系统有多个apk具有标签
&&
时,系统会主动询问用户希望选择哪个apk作为launcher,即设置用户的偏好。
如果是mmi version,修改此处逻辑,将弹出弹框逻辑去掉并让系统默认选择指定apk作为launcher,则可以实现本问题的需求。分析偏好设置具体逻辑可参考android设置多个类似APP其中的一个为默认。而针对本问题,可以在chooseBestActivity方法在进行最佳 activity匹配时就将指定的 activity设置进偏好设置中去, 该操作的代码实现接口为addPreferredActivity(IntentFilter filter, int match,ComponentName[] set, ComponentName activity, int userId)
-
修改系统默认laucher启动标签
参考让你自己写的Android的Launcher成为系统中第一个启动的,也是唯一的Launcher做法,创建一个私有的filter选项,让它来作为系统lancher的过滤选项。然后将mmitest apk中的androidManifest.xml中的main screen添加该私有标签,这个方法从framework层修改了launcher的启动标签,而原本的
将不会作为系统launcher标签,这种做法对系统修改不可谓不大,但是这个做法同样不适用本问题。
3. solution
具体修改如下:
- 修改文件
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. /*此处省略*/ }
- 修改目标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。
更多相关文章
- 安全新手入坑——HTML标签
- Nginx系列教程(六)| 手把手教你搭建 LNMP 架构并部署天空网络电影
- Android(安卓)后台静默安装
- Android知识体系总结2020之Android部分对话框 & 弹框 & 通知 &
- 编译Android版本的libmad
- Android(安卓)图片文件夹区别
- Android(安卓)NDK r8e __gmsl 错误
- 如何编译 Chrome for Android
- [转] This Android(安卓)SDK requires Android(安卓)Developer T