背景:最近在封裝供第三應用系統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博客