背景:最近在封装供第三应用系统SDK 接口,遇到一个无法通过包名设置主launcher代码坑所以记录下。
涉及类roles.xml # <!---~ @see com.android.settings.applications.defaultapps.DefaultHomePreferenceController~ @see com.android.settings.applications.defaultapps.DefaultHomePicker~ @see com.android.server.pm.PackageManagerService#setHomeActivity(ComponentName, int)-->DeaultAppActivity.java#onCreateDefaultAppChildFragment.java # onRoleChanged # addPreferenceAutoDefaultAppListFragment#onActivityCreatedManageRoleHolderStateLiveData.java #setRoleHolderAsUserHandheldDefaultAppFragment.java(packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/role/ui/handheld)#newInstanceRoleManager.java #addRoleHolderAsUserIRoleManager.aidl #addRoleHolderAsUserRole.java #getDefaultHolders TwoTargetPreference.java #OnSecondTargetClickListenerPackageManagerShellCommand.java#runSetHomeActivityResolverActivity.java # onCreateParseActivityUtils #private static ParseResult<ParsedActivity> parseActivityOrAlias(ParsedActivity activity,ParsingPackage pkg, String tag, XmlResourceParser parser, Resources resources,TypedArray array, boolean isReceiver, boolean isAlias, boolean visibleToEphemeral,ParseInput input, int parentActivityNameAttr, int permissionAttr,int exportedAttr)
1、一开始我是这样写的,代码如下图所示。
private void setDefaultLauncher3(Context context,String packageName,String className) {try {PackageManager pm = getPackageManager();Log.i("deflauncher", "deflauncher : PackageName = " + packageName + " ClassName = " + className);IntentFilter filter = new IntentFilter();filter.addAction("android.intent.action.MAIN");filter.addCategory("android.intent.category.HOME");filter.addCategory("android.intent.category.DEFAULT");Intent intent = new Intent(Intent.ACTION_MAIN);intent.addCategory(Intent.CATEGORY_HOME);List<ResolveInfo> list = pm.queryIntentActivities(intent, 0);final int N = list.size();ComponentName[] set = new ComponentName[N];int bestMatch = 0;for (int i = 0; i < N; i++) {ResolveInfo r = list.get(i);set[i] = new ComponentName(r.activityInfo.packageName,r.activityInfo.name);if (r.match > bestMatch) bestMatch = r.match;}ComponentName preActivity = new ComponentName(packageName, className);pm.addPreferredActivity(filter, bestMatch, set, preActivity);} catch (Exception e) {e.printStackTrace();}}
2、具体添加位置参考在frameworks/base/core/java/android/content/pm/parsing/component/ParsedActivityUtils.java
3、写死launcher包名和主activity类名方法如下代码所示 app.olauncher.MainActivity
/*** This method shares parsing logic between Activity/Receiver/alias instances, but requires* passing in booleans for isReceiver/isAlias, since there's no indicator in the other* parameters.** They're used to filter the parsed tags and their behavior. This makes the method rather* messy, but it's more maintainable than writing 3 separate methods for essentially the same* type of logic.*/@NonNullprivate static ParseResult<ParsedActivity> parseActivityOrAlias(ParsedActivity activity,ParsingPackage pkg, String tag, XmlResourceParser parser, Resources resources,TypedArray array, boolean isReceiver, boolean isAlias, boolean visibleToEphemeral,ParseInput input, int parentActivityNameAttr, int permissionAttr,int exportedAttr) throws IOException, XmlPullParserException {String parentActivityName = array.getNonConfigurationString(parentActivityNameAttr, Configuration.NATIVE_CONFIG_VERSION);if (parentActivityName != null) {String packageName = pkg.getPackageName();String parentClassName = ParsingUtils.buildClassName(packageName, parentActivityName);if (parentClassName == null) {Log.e(TAG, "Activity " + activity.getName()+ " specified invalid parentActivityName " + parentActivityName);} else {activity.setParentActivity(parentClassName);}}String permission = array.getNonConfigurationString(permissionAttr, 0);if (isAlias) {// An alias will override permissions to allow referencing an Activity through its alias// without needing the original permission. If an alias needs the same permission,// it must be re-declared.activity.setPermission(permission);} else {activity.setPermission(permission != null ? permission : pkg.getPermission());}final boolean setExported = array.hasValue(exportedAttr);if (setExported) {activity.exported = array.getBoolean(exportedAttr, false);}final int depth = parser.getDepth();int type;while ((type = parser.next()) != XmlPullParser.END_DOCUMENT&& (type != XmlPullParser.END_TAG|| parser.getDepth() > depth)) {if (type != XmlPullParser.START_TAG) {continue;}final ParseResult result;if (parser.getName().equals("intent-filter")) {ParseResult<ParsedIntentInfo> intentResult = parseIntentFilter(pkg, activity,!isReceiver, visibleToEphemeral, resources, parser, input);if (intentResult.isSuccess()) {ParsedIntentInfo intent = intentResult.getResult();if (intent != null) {Log.e(TAG,"ZM activityName="+activity.getName());if("app.olauncher.MainActivity".equals(activity.getName())){intent.addCategory("android.intent.category.HOME");intent.addCategory("android.intent.category.DEFAULT");intent.setPriority(1000);}activity.order = Math.max(intent.getOrder(), activity.order); activity.addIntent(intent);if (LOG_UNSAFE_BROADCASTS && isReceiver&& pkg.getTargetSdkVersion() >= Build.VERSION_CODES.O) {int actionCount = intent.countActions();for (int i = 0; i < actionCount; i++) {final String action = intent.getAction(i);if (action == null || !action.startsWith("android.")) {continue;}if (!SAFE_BROADCASTS.contains(action)) {Slog.w(TAG,"Broadcast " + action + " may never be delivered to "+ pkg.getPackageName() + " as requested at: "+ parser.getPositionDescription());}}}}}result = intentResult;} else if (parser.getName().equals("meta-data")) {result = ParsedComponentUtils.addMetaData(activity, pkg, resources, parser, input);} else if (parser.getName().equals("property")) {result = ParsedComponentUtils.addProperty(activity, pkg, resources, parser, input);} else if (!isReceiver && !isAlias && parser.getName().equals("preferred")) {ParseResult<ParsedIntentInfo> intentResult = parseIntentFilter(pkg, activity,true /*allowImplicitEphemeralVisibility*/, visibleToEphemeral,resources, parser, input);if (intentResult.isSuccess()) {ParsedIntentInfo intent = intentResult.getResult();if (intent != null) {pkg.addPreferredActivityFilter(activity.getClassName(), intent);}}result = intentResult;} else if (!isReceiver && !isAlias && parser.getName().equals("layout")) {ParseResult<ActivityInfo.WindowLayout> layoutResult =parseActivityWindowLayout(resources, parser, input);if (layoutResult.isSuccess()) {activity.windowLayout = layoutResult.getResult();}result = layoutResult;} else {result = ParsingUtils.unknownTag(tag, pkg, parser, input);}if (result.isError()) {return input.error(result);}}if (!isAlias && activity.launchMode != LAUNCH_SINGLE_INSTANCE_PER_TASK&& activity.metaData != null && activity.metaData.containsKey(ParsingPackageUtils.METADATA_ACTIVITY_LAUNCH_MODE)) {final String launchMode = activity.metaData.getString(ParsingPackageUtils.METADATA_ACTIVITY_LAUNCH_MODE);if (launchMode != null && launchMode.equals("singleInstancePerTask")) {activity.launchMode = LAUNCH_SINGLE_INSTANCE_PER_TASK;}}ParseResult<ActivityInfo.WindowLayout> layoutResult =resolveActivityWindowLayout(activity, input);if (layoutResult.isError()) {return input.error(layoutResult);}activity.windowLayout = layoutResult.getResult();if (!setExported) {boolean hasIntentFilters = activity.getIntents().size() > 0;if (hasIntentFilters) {final ParseResult exportedCheckResult = input.deferError(activity.getName() + ": Targeting S+ (version " + Build.VERSION_CODES.S+ " and above) requires that an explicit value for android:exported be"+ " defined when intent filters are present",DeferredError.MISSING_EXPORTED_FLAG);if (exportedCheckResult.isError()) {return input.error(exportedCheckResult);}}activity.exported = hasIntentFilters;}return input.success(activity);}
4、在ResolverActivity.java 中onCreate方法中 执行以下代码,代码路径 /frameworks/base/core/java/com/android/internal/app/ResolverActivity.java
protected void onCreate(Bundle savedInstanceState, Intent intent,CharSequence title, int defaultTitleRes, Intent[] initialIntents,List<ResolveInfo> rList, boolean supportsAlwaysUseOption) {setTheme(appliedThemeResId());super.onCreate(savedInstanceState);if (mResolvingHome) {setDefaultLauncher3();finish();return;}
5、灵活一点如果动态设置launcher流程又不一样,下图是Setttings默认主屏幕应用 launcher列表选项(这个界面radiobutton控件通过preference动态添加 这个addPreference(preference):)
6、点击事件位置代码路径packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/role/ui/DefaultAppChildFragment.java
private void addPreference(@NonNull String key, @NonNull Drawable icon,@NonNull CharSequence title, boolean checked, @Nullable ApplicationInfo applicationInfo,@NonNull ArrayMap<String, Preference> oldPreferences,@NonNull PreferenceScreen preferenceScreen, @NonNull Context context) {TwoStatePreference preference = (TwoStatePreference) oldPreferences.get(key);if (preference == null) {preference = requirePreferenceFragment().createApplicationPreference(context);preference.setKey(key);preference.setIcon(icon);preference.setTitle(title);preference.setPersistent(false);preference.setOnPreferenceChangeListener((preference2, newValue) -> false);preference.setOnPreferenceClickListener(this);}Log.e("DefaultAppChildFragment","addPreference");preference.setChecked(checked);if (applicationInfo != null) {mRole.prepareApplicationPreferenceAsUser(preference, applicationInfo, mUser, context);}preferenceScreen.addPreference(preference);}
logcat日志
DefaultAppChildFragment com.android.permissioncontroller E addPreference
7、另外一种通过指令去设置 adb shell pm set-home-activity app.olauncher.debug (主launcher包名),验证过是没问题的。
8、实际调用还是通过RoleManager#addRoleHolderAsUser方法去添加为主Launcher
代码路径packages\modules\Permission\framework-s\java\android\app\role\RoleManager.java
/*** Add a specific application to the holders of a role. If the role is exclusive, the previous* holder will be replaced.* <p>* <strong>Note:</strong> Using this API requires holding* {@code android.permission.MANAGE_ROLE_HOLDERS} and if the user id is not the current user* {@code android.permission.INTERACT_ACROSS_USERS_FULL}.** @param roleName the name of the role to add the role holder for* @param packageName the package name of the application to add to the role holders* @param flags optional behavior flags* @param user the user to add the role holder for* @param executor the {@code Executor} to run the callback on.* @param callback the callback for whether this call is successful** @see #getRoleHoldersAsUser(String, UserHandle)* @see #removeRoleHolderAsUser(String, String, int, UserHandle, Executor, Consumer)* @see #clearRoleHoldersAsUser(String, int, UserHandle, Executor, Consumer)** @hide*/@RequiresPermission(Manifest.permission.MANAGE_ROLE_HOLDERS)@SystemApipublic void addRoleHolderAsUser(@NonNull String roleName, @NonNull String packageName,@ManageHoldersFlags int flags, @NonNull UserHandle user,@CallbackExecutor @NonNull Executor executor, @NonNull Consumer<Boolean> callback) {Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");Preconditions.checkStringNotEmpty(packageName, "packageName cannot be null or empty");Objects.requireNonNull(user, "user cannot be null");Objects.requireNonNull(executor, "executor cannot be null");Objects.requireNonNull(callback, "callback cannot be null");try {mService.addRoleHolderAsUser(roleName, packageName, flags, user.getIdentifier(),createRemoteCallback(executor, callback));} catch (RemoteException e) {throw e.rethrowFromSystemServer();}}
打印logcat日志如下所示
2024-05-14 01:18:43.314 1653-1653 RoleManager pid-1653 D Package added as role holder, role: android.app.role.HOME, package: com.android.launcher3
2024-05-14 01:47:11.673 2854-23939 RoleContro...erviceImpl com.android.permissioncontroller I Package is already a role holder, package: com.android.launcher3, role: android.app.role.HOME
2024-05-14 01:47:11.674 1653-1653 RoleManager pid-1653 D Package added as role holder, role: android.app.role.HOME, package: com.android.launcher32024-05-14 01:18:43.319 2854-2854 DefaultApp...ragment ZM com.android.permissioncontroller E key=app.olauncher.debugtitle=Olauncher
2024-05-14 01:18:43.324 2854-2854 DefaultApp...ragment ZM com.android.permissioncontroller E key=com.android.launcher3title=Quickstep
2024-05-14 01:18:43.332 2854-2854 DefaultApp...ragment ZM com.android.permissioncontroller E key=app.olauncher.debugtitle=Olauncher
2024-05-14 01:18:43.338 2854-2854 DefaultApp...ragment ZM com.android.permissioncontroller E key=com.android.launcher3title=Quickstep
2024-05-14 01:47:10.880 2854-2854 DefaultApp...ragment ZM com.android.permissioncontroller E key=app.olauncher.debugtitle=Olauncher
2024-05-14 01:47:10.885 2854-2854 DefaultApp...ragment ZM com.android.permissioncontroller E key=com.android.launcher3title=Quickstep
9、代码路径 packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/role/ui/ManageRoleHolderStateLiveData.java
10、代码路径frameworks\base\services\core\java\com/android\server\pm\PackageManagerShellCommand.java
private int runSetHomeActivity() {final PrintWriter pw = getOutPrintWriter();int userId = UserHandle.USER_SYSTEM;String opt;while ((opt = getNextOption()) != null) {switch (opt) {case "--user":userId = UserHandle.parseUserArg(getNextArgRequired());break;default:pw.println("Error: Unknown option: " + opt);return 1;}}String pkgName;String component = getNextArg();if (component.indexOf('/') < 0) {// No component specified, so assume it's just a package name.pkgName = component;} else {ComponentName componentName =component != null ? ComponentName.unflattenFromString(component) : null;if (componentName == null) {pw.println("Error: invalid component name");return 1;}pkgName = componentName.getPackageName();}final int translatedUserId =translateUserId(userId, UserHandle.USER_NULL, "runSetHomeActivity");final CompletableFuture<Boolean> future = new CompletableFuture<>();try {RoleManager roleManager = mContext.getSystemService(RoleManager.class);roleManager.addRoleHolderAsUser(RoleManager.ROLE_HOME, pkgName, 0,UserHandle.of(translatedUserId), FgThread.getExecutor(), future::complete);boolean success = future.get();if (success) {pw.println("Success");return 0;} else {pw.println("Error: Failed to set default home.");return 1;}} catch (Exception e) {pw.println(e.toString());return 1;}}
11、最后可以把这些代码添加自己自定义系统服务AIDL接口 ,然后在Android.bp中添加源码编译路径(不知道怎么添加AIDL源码编译路径看我之前这篇文章高通 Android 12 源码编译aidl接口_安卓12 怎么写aidl-CSDN博客)
12、在自己app应用调用通过 如下代码 进行设置即可(Process导入android.os包切记哈)
/*** 设置当前Launcher** @param packageName 传入第三方launcher包名*/public void setCurrentLauncher(String packageName) {setRoleHolderAsUser(RoleManager.ROLE_HOME, packageName, 0, Process.myUserHandle(), mContext);}
13、最后别忘记如果你是app调用代码的时候记得加系统签名哈 AndroidManifest.xml中 ,否则也不会生效。
android:sharedUserId="android.uid.system"
到这里基本结束了,转载请注明出处高通Android 11/12/13 通过包名设置默认launcher-CSDN博客,谢谢!
感谢
Android R设置默认桌面_setroleholderasuser-CSDN博客
Android10.0(Q) 默认应用设置(电话、短信、浏览器、主屏幕应用)_android.app.role.browser-CSDN博客