Android 12 任意apk设置默认Launcher

需求:在11.0 12.0的系统产品开发中,对于一些产品的需求,需要将一些无源码app的某个MainActivity作为启动页面的功能实现,由于没有源码,所以需要 利用PMS的安装解析apk的AndroidManifest.xml的时候,在判断是某个Activity的时候,设置Lancher属性来实现某些功能

  1. 无源码apk设置默认启动Launcher的相关属性的核心类


frameworks/base/core/java/android/content/pm/parsing/ParsingPackageUtils.java
frameworks/base/core/java/android/content/pm/parsing/component/ParsedActivityUtils.java
  1. 无源码apk设置默认启动Launcher的相关属性的核心功能实现和分析

在11.0的产品中,在PMS解析的相关功能源码都重构在ParsingPackageUtils.java这里面了,所以在ParsingPackageUtils.java负责解析关于 AndroidManifest.xml中的各种组件,所以就需要在解析的时候,设置对应的Home属性让app变成启动Launcher 接下来看ParsingPackageUtils.java的相关代码

2.1ParsingPackageUtils.java的相关解析代码分析


  public class ParsingPackageUtils {
public ParsingPackageUtils(boolean onlyCoreApps, String[] separateProcesses,
              DisplayMetrics displayMetrics, @NonNull Callback callback) {
          mOnlyCoreApps = onlyCoreApps;
          mSeparateProcesses = separateProcesses;
          mDisplayMetrics = displayMetrics;
          mCallback = callback;
      }

在ParsingPackageUtils的构造方法中,主要是构造callback参数,供解析的时候回调参数使用的,接下来主要看解析的最主要的方法parseBaseApplication

接下来看parseBaseApplication的具体解析方式方法


private ParseResult<ParsingPackage> parseBaseApplication(ParseInput input,
              ParsingPackage pkg, Resources res, XmlResourceParser parser, int flags)
              throws XmlPullParserException, IOException {
          final String pkgName = pkg.getPackageName();
          int targetSdk = pkg.getTargetSdkVersion();
  
          TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestApplication);
          try {
              // TODO(b/135203078): Remove this and force unit tests to mock an empty manifest
              // This case can only happen in unit tests where we sometimes need to create fakes
              // of various package parser data structures.
              if (sa == null) {
                  return input.error("<application> does not contain any attributes");
              }
  
              String name = sa.getNonConfigurationString(R.styleable.AndroidManifestApplication_name,
                      0);
              if (name != null) {
                  String packageName = pkg.getPackageName();
                  String outInfoName = ParsingUtils.buildClassName(packageName, name);
                  if (PackageManager.APP_DETAILS_ACTIVITY_CLASS_NAME.equals(outInfoName)) {
                      return input.error("<application> invalid android:name");
                  } else if (outInfoName == null) {
                      return input.error("Empty class name in package " + packageName);
                  }
  
                  pkg.setClassName(outInfoName);
              }
  
              TypedValue labelValue = sa.peekValue(R.styleable.AndroidManifestApplication_label);
              if (labelValue != null) {
                  pkg.setLabelRes(labelValue.resourceId);
                  if (labelValue.resourceId == 0) {
                      pkg.setNonLocalizedLabel(labelValue.coerceToString());
                  }
              }
  
              parseBaseAppBasicFlags(pkg, sa);
  
              String manageSpaceActivity = nonConfigString(Configuration.NATIVE_CONFIG_VERSION,
                      R.styleable.AndroidManifestApplication_manageSpaceActivity, sa);
              if (manageSpaceActivity != null) {
                  String manageSpaceActivityName = ParsingUtils.buildClassName(pkgName,
                          manageSpaceActivity);
  
                  if (manageSpaceActivityName == null) {
                      return input.error("Empty class name in package " + pkgName);
                  }
  
                  pkg.setManageSpaceActivityName(manageSpaceActivityName);
              }
  
              if (pkg.isAllowBackup()) {
                  // backupAgent, killAfterRestore, fullBackupContent, backupInForeground,
                  // and restoreAnyVersion are only relevant if backup is possible for the
                  // given application.
                  String backupAgent = nonConfigString(Configuration.NATIVE_CONFIG_VERSION,
                          R.styleable.AndroidManifestApplication_backupAgent, sa);
                  if (backupAgent != null) {
                      String backupAgentName = ParsingUtils.buildClassName(pkgName, backupAgent);
                      if (backupAgentName == null) {
                          return input.error("Empty class name in package " + pkgName);
                      }
  
                      if (PackageParser.DEBUG_BACKUP) {
                          Slog.v(TAG, "android:backupAgent = " + backupAgentName
                                  + " from " + pkgName + "+" + backupAgent);
                      }
  
                      pkg.setBackupAgentName(backupAgentName)
                              .setKillAfterRestore(bool(true,
                                      R.styleable.AndroidManifestApplication_killAfterRestore, sa))
                              .setRestoreAnyVersion(bool(false,
                                      R.styleable.AndroidManifestApplication_restoreAnyVersion, sa))
                              .setFullBackupOnly(bool(false,
                                      R.styleable.AndroidManifestApplication_fullBackupOnly, sa))
                              .setBackupInForeground(bool(false,
                                      R.styleable.AndroidManifestApplication_backupInForeground, sa));
                  }
  
                  TypedValue v = sa.peekValue(
                          R.styleable.AndroidManifestApplication_fullBackupContent);
                  int fullBackupContent = 0;
  
                  if (v != null) {
                      fullBackupContent = v.resourceId;
  
                      if (v.resourceId == 0) {
                          if (PackageParser.DEBUG_BACKUP) {
                              Slog.v(TAG, "fullBackupContent specified as boolean=" +
                                      (v.data == 0 ? "false" : "true"));
                          }
                          // "false" => -1, "true" => 0
                          fullBackupContent = v.data == 0 ? -1 : 0;
                      }
  
                      pkg.setFullBackupContent(fullBackupContent);
                  }
                  if (PackageParser.DEBUG_BACKUP) {
                      Slog.v(TAG, "fullBackupContent=" + fullBackupContent + " for " + pkgName);
                  }
              }
  
              if (sa.getBoolean(R.styleable.AndroidManifestApplication_persistent, false)) {
                  // Check if persistence is based on a feature being present
                  final String requiredFeature = sa.getNonResourceString(R.styleable
                          .AndroidManifestApplication_persistentWhenFeatureAvailable);
                  pkg.setPersistent(requiredFeature == null || mCallback.hasFeature(requiredFeature));
              }
 ......
  
              String classLoaderName = pkg.getClassLoaderName();
              if (classLoaderName != null
                      && !ClassLoaderFactory.isValidClassLoaderName(classLoaderName)) {
                  return input.error("Invalid class loader name: " + classLoaderName);
              }
  
              pkg.setGwpAsanMode(sa.getInt(R.styleable.AndroidManifestApplication_gwpAsanMode, -1));
          } finally {
              sa.recycle();
          }
  
          boolean hasActivityOrder = false;
          boolean hasReceiverOrder = false;
          boolean hasServiceOrder = 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;
              String tagName = parser.getName();
              boolean isActivity = false;
              switch (tagName) {
                  case "activity":
                      isActivity = true;
                      // fall-through
                  case "receiver":
                      ParseResult<ParsedActivity> activityResult =
                              ParsedActivityUtils.parseActivityOrReceiver(mSeparateProcesses, pkg,
                                      res, parser, flags, PackageParser.sUseRoundIcon, input);
  
                      if (activityResult.isSuccess()) {
                          ParsedActivity activity = activityResult.getResult();
                          if (isActivity) {
                              hasActivityOrder |= (activity.getOrder() != 0);
                              pkg.addActivity(activity);
                          } else {
                              hasReceiverOrder |= (activity.getOrder() != 0);
                              pkg.addReceiver(activity);
                          }
                      }
  
                      result = activityResult;
                      break;
                  case "service":
                      ParseResult<ParsedService> serviceResult =
                              ParsedServiceUtils.parseService(mSeparateProcesses, pkg, res, parser,
                                      flags, PackageParser.sUseRoundIcon, input);
                      if (serviceResult.isSuccess()) {
                          ParsedService service = serviceResult.getResult();
                          hasServiceOrder |= (service.getOrder() != 0);
                          pkg.addService(service);
                      }
  
                      result = serviceResult;
                      break;
                  case "provider":
                      ParseResult<ParsedProvider> providerResult =
                              ParsedProviderUtils.parseProvider(mSeparateProcesses, pkg, res, parser,
                                      flags, PackageParser.sUseRoundIcon, input);
                      if (providerResult.isSuccess()) {
                          pkg.addProvider(providerResult.getResult());
                      }
  
                      result = providerResult;
                      break;
                  case "activity-alias":
                      activityResult = ParsedActivityUtils.parseActivityAlias(pkg, res,
                              parser, PackageParser.sUseRoundIcon, input);
                      if (activityResult.isSuccess()) {
                          ParsedActivity activity = activityResult.getResult();
                          hasActivityOrder |= (activity.getOrder() != 0);
                          pkg.addActivity(activity);
                      }
  
                      result = activityResult;
                      break;
                  default:
                      result = parseBaseAppChildTag(input, tagName, pkg, res, parser, flags);
                      break;
              }
  
              if (result.isError()) {
                  return input.error(result);
              }
          }
  
          if (TextUtils.isEmpty(pkg.getStaticSharedLibName())) {
              // Add a hidden app detail activity to normal apps which forwards user to App Details
              // page.
              ParseResult<ParsedActivity> a = generateAppDetailsHiddenActivity(input, pkg);
              if (a.isError()) {
                  // Error should be impossible here, as the only failure case as of SDK R is a
                  // string validation error on a constant ":app_details" string passed in by the
                  // parsing code itself. For this reason, this is just a hard failure instead of
                  // deferred.
                  return input.error(a);
              }
  
              pkg.addActivity(a.getResult());
          }
  
          if (hasActivityOrder) {
              pkg.sortActivities();
          }
          if (hasReceiverOrder) {
              pkg.sortReceivers();
          }
          if (hasServiceOrder) {
              pkg.sortServices();
          }
  
          // Must be run after the entire {@link ApplicationInfo} has been fully processed and after
          // every activity info has had a chance to set it from its attributes.
          setMaxAspectRatio(pkg);
          setMinAspectRatio(pkg);
          setSupportsSizeChanges(pkg);
  
          pkg.setHasDomainUrls(hasDomainURLs(pkg));
  
          return input.success(pkg);
      } 

从上述代码中,可以看出在parseBaseApplication中解析xml时可以对tag的值为activity,receiver,service,provider,activity-alias等几种类型的组件进行解析 在activity-alias这种tag,就是对activity的别类的解析,而这里也是今天实现默认启动Launcher的重点部分在解析activity的实现调用的是ParsedActivityUtils.parseActivityOrReceiver 进行解析的,最后代码还是由parseActivityOrAlias来负责解析的,所以接下来看这部分代码

2.2 ParsedActivityUtils相关解析源码分析


@NonNull
      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) 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) {
                          activity.order = Math.max(intent.getOrder(), activity.order);
 
//add core start
                    if ("com.prj.MainActivity".equals(activity.getName()))  {
                        intent.addCategory("android.intent.category.HOME");
                        intent.addCategory("android.intent.category.DEFAULT");
                        intent.setPriority(1000);
                    }
//add core end
 
                          activity.addIntent(intent);
                          if (PackageParser.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 (!PackageParser.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 (!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 = parseLayout(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);
              }
          }
  
          ParseResult<ActivityInfo.WindowLayout> layoutResult = resolveWindowLayout(activity, input);
          if (layoutResult.isError()) {
              return input.error(layoutResult);
          }
          activity.windowLayout = layoutResult.getResult();
  
          if (!setExported) {
              activity.exported = activity.getIntents().size() > 0;
          }
  
          return input.success(activity);
      } 

在上述的parseActivityOrAlias的方法中,在解析intent-filter的属性的时候,判断activity的tag名称是否等于需要添加home属性的activity的值,然后 增加android.intent.category.HOME和android.intent.category.DEFAULT属性 就构成了设置默认启动Launcher的相关属性,就实现了功能.

猜你喜欢

转载自blog.csdn.net/a546036242/article/details/128973285