diff --git a/core/java/android/content/pm/parsing/ParsingPackageImpl.java b/core/java/android/content/pm/parsing/ParsingPackageImpl.java index f046d383ff7dd..65027d439dea9 100644 --- a/core/java/android/content/pm/parsing/ParsingPackageImpl.java +++ b/core/java/android/content/pm/parsing/ParsingPackageImpl.java @@ -84,16 +84,16 @@ public class ParsingPackageImpl implements ParsingPackage, Parcelable { private static final String TAG = "PackageImpl"; - protected static ForBoolean sForBoolean = Parcelling.Cache.getOrCreate(ForBoolean.class); - protected static ForInternedString sForString = Parcelling.Cache.getOrCreate( + public static ForBoolean sForBoolean = Parcelling.Cache.getOrCreate(ForBoolean.class); + public static ForInternedString sForString = Parcelling.Cache.getOrCreate( ForInternedString.class); - protected static ForInternedStringArray sForStringArray = Parcelling.Cache.getOrCreate( + public static ForInternedStringArray sForStringArray = Parcelling.Cache.getOrCreate( ForInternedStringArray.class); - protected static ForInternedStringList sForStringList = Parcelling.Cache.getOrCreate( + public static ForInternedStringList sForStringList = Parcelling.Cache.getOrCreate( ForInternedStringList.class); - protected static ForInternedStringValueMap sForStringValueMap = Parcelling.Cache.getOrCreate( + public static ForInternedStringValueMap sForStringValueMap = Parcelling.Cache.getOrCreate( ForInternedStringValueMap.class); - protected static ForInternedStringSet sForStringSet = Parcelling.Cache.getOrCreate( + public static ForInternedStringSet sForStringSet = Parcelling.Cache.getOrCreate( ForInternedStringSet.class); // These are objects because null represents not explicitly set diff --git a/core/java/android/content/pm/parsing/component/ComponentParseUtils.java b/core/java/android/content/pm/parsing/component/ComponentParseUtils.java new file mode 100644 index 0000000000000..c4caedcb87ffa --- /dev/null +++ b/core/java/android/content/pm/parsing/component/ComponentParseUtils.java @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content.pm.parsing.component; + +import android.annotation.AttrRes; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Intent; +import android.content.pm.PackageParser; +import android.content.pm.PackageUserState; +import android.content.pm.parsing.ParsingPackage; +import android.content.pm.parsing.ParsingUtils; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.content.res.XmlResourceParser; +import android.text.TextUtils; + +import android.content.pm.parsing.ParsingPackageUtils; +import android.content.pm.parsing.result.ParseInput; +import android.content.pm.parsing.result.ParseResult; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; + +/** @hide */ +public class ComponentParseUtils { + + private static final String TAG = ParsingPackageUtils.TAG; + + public static boolean isImplicitlyExposedIntent(ParsedIntentInfo intentInfo) { + return intentInfo.hasCategory(Intent.CATEGORY_BROWSABLE) + || intentInfo.hasAction(Intent.ACTION_SEND) + || intentInfo.hasAction(Intent.ACTION_SENDTO) + || intentInfo.hasAction(Intent.ACTION_SEND_MULTIPLE); + } + + static ParseResult parseAllMetaData( + ParsingPackage pkg, Resources res, XmlResourceParser parser, String tag, + Component component, ParseInput input) throws XmlPullParserException, IOException { + 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 ("meta-data".equals(parser.getName())) { + result = ParsedComponentUtils.addMetaData(component, pkg, res, parser, input); + } else { + result = ParsingUtils.unknownTag(tag, pkg, parser, input); + } + + if (result.isError()) { + return input.error(result); + } + } + + return input.success(component); + } + + @NonNull + public static ParseResult buildProcessName(@NonNull String pkg, String defProc, + CharSequence procSeq, int flags, String[] separateProcesses, ParseInput input) { + if ((flags & PackageParser.PARSE_IGNORE_PROCESSES) != 0 && !"system".contentEquals( + procSeq)) { + return input.success(defProc != null ? defProc : pkg); + } + if (separateProcesses != null) { + for (int i = separateProcesses.length - 1; i >= 0; i--) { + String sp = separateProcesses[i]; + if (sp.equals(pkg) || sp.equals(defProc) || sp.contentEquals(procSeq)) { + return input.success(pkg); + } + } + } + if (procSeq == null || procSeq.length() <= 0) { + return input.success(defProc); + } + + ParseResult nameResult = ComponentParseUtils.buildCompoundName(pkg, procSeq, + "process", input); + return input.success(TextUtils.safeIntern(nameResult.getResult())); + } + + @NonNull + public static ParseResult buildTaskAffinityName(String pkg, String defProc, + CharSequence procSeq, ParseInput input) { + if (procSeq == null) { + return input.success(defProc); + } + if (procSeq.length() <= 0) { + return input.success(null); + } + return buildCompoundName(pkg, procSeq, "taskAffinity", input); + } + + public static ParseResult buildCompoundName(String pkg, CharSequence procSeq, + String type, ParseInput input) { + String proc = procSeq.toString(); + char c = proc.charAt(0); + if (pkg != null && c == ':') { + if (proc.length() < 2) { + return input.error("Bad " + type + " name " + proc + " in package " + pkg + + ": must be at least two characters"); + } + String subName = proc.substring(1); + String nameError = PackageParser.validateName(subName, false, false); + if (nameError != null) { + return input.error("Invalid " + type + " name " + proc + " in package " + pkg + + ": " + nameError); + } + return input.success(pkg + proc); + } + String nameError = PackageParser.validateName(proc, true, false); + if (nameError != null && !"system".equals(proc)) { + return input.error("Invalid " + type + " name " + proc + " in package " + pkg + + ": " + nameError); + } + return input.success(proc); + } + + public static int flag(int flag, @AttrRes int attribute, TypedArray typedArray) { + return typedArray.getBoolean(attribute, false) ? flag : 0; + } + + public static int flag(int flag, @AttrRes int attribute, boolean defaultValue, + TypedArray typedArray) { + return typedArray.getBoolean(attribute, defaultValue) ? flag : 0; + } + + /** + * This is not state aware. Avoid and access through PackageInfoUtils in the system server. + */ + @Nullable + public static CharSequence getNonLocalizedLabel( + ParsedComponent component) { + return component.nonLocalizedLabel; + } + + /** + * This is not state aware. Avoid and access through PackageInfoUtils in the system server. + * + * This is a method of the utility class to discourage use. + */ + public static int getIcon(ParsedComponent component) { + return component.icon; + } + + public static boolean isMatch(PackageUserState state, boolean isSystem, + boolean isPackageEnabled, ParsedMainComponent component, int flags) { + return state.isMatch(isSystem, isPackageEnabled, component.isEnabled(), + component.isDirectBootAware(), component.getName(), flags); + } + + public static boolean isEnabled(PackageUserState state, boolean isPackageEnabled, + ParsedMainComponent parsedComponent, int flags) { + return state.isEnabled(isPackageEnabled, parsedComponent.isEnabled(), + parsedComponent.getName(), flags); + } +} diff --git a/core/java/android/content/pm/parsing/component/ParsedActivity.java b/core/java/android/content/pm/parsing/component/ParsedActivity.java new file mode 100644 index 0000000000000..f11f79458b5a7 --- /dev/null +++ b/core/java/android/content/pm/parsing/component/ParsedActivity.java @@ -0,0 +1,448 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content.pm.parsing.component; + +import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZEABLE; +import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE; +import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; +import static android.content.pm.parsing.ParsingPackageImpl.sForString; +import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED; + +import android.annotation.Nullable; +import android.app.ActivityTaskManager; +import android.content.ComponentName; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageParser; +import android.content.pm.parsing.ParsingPackageImpl; +import android.os.Parcel; +import android.os.Parcelable; +import android.text.TextUtils; + +import com.android.internal.util.DataClass; +import com.android.internal.util.Parcelling; +import com.android.internal.util.Parcelling.BuiltIn.ForInternedString; + +/** @hide **/ +public class ParsedActivity extends ParsedMainComponent { + + int theme; + int uiOptions; + + @Nullable + @DataClass.ParcelWith(ForInternedString.class) + private String targetActivity; + + @Nullable + @DataClass.ParcelWith(ForInternedString.class) + private String parentActivityName; + @Nullable + String taskAffinity; + int privateFlags; + @Nullable + @DataClass.ParcelWith(ForInternedString.class) + private String permission; + + int launchMode; + int documentLaunchMode; + int maxRecents; + int configChanges; + int softInputMode; + int persistableMode; + int lockTaskLaunchMode; + + int screenOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; + int resizeMode = ActivityInfo.RESIZE_MODE_RESIZEABLE; + + @Nullable + private Float maxAspectRatio; + + @Nullable + private Float minAspectRatio; + + @Nullable + String requestedVrComponent; + int rotationAnimation = -1; + int colorMode; + + boolean preferMinimalPostProcessing; + + @Nullable + ActivityInfo.WindowLayout windowLayout; + + public ParsedActivity(ParsedActivity other) { + super(other); + this.theme = other.theme; + this.uiOptions = other.uiOptions; + this.targetActivity = other.targetActivity; + this.parentActivityName = other.parentActivityName; + this.taskAffinity = other.taskAffinity; + this.privateFlags = other.privateFlags; + this.permission = other.permission; + this.launchMode = other.launchMode; + this.documentLaunchMode = other.documentLaunchMode; + this.maxRecents = other.maxRecents; + this.configChanges = other.configChanges; + this.softInputMode = other.softInputMode; + this.persistableMode = other.persistableMode; + this.lockTaskLaunchMode = other.lockTaskLaunchMode; + this.screenOrientation = other.screenOrientation; + this.resizeMode = other.resizeMode; + this.maxAspectRatio = other.maxAspectRatio; + this.minAspectRatio = other.minAspectRatio; + this.requestedVrComponent = other.requestedVrComponent; + this.rotationAnimation = other.rotationAnimation; + this.colorMode = other.colorMode; + this.windowLayout = other.windowLayout; + } + + /** + * Generate activity object that forwards user to App Details page automatically. + * This activity should be invisible to user and user should not know or see it. + */ + public static ParsedActivity makeAppDetailsActivity(String packageName, String processName, + int uiOptions, String taskAffinity, boolean hardwareAccelerated) { + ParsedActivity activity = new ParsedActivity(); + activity.setPackageName(packageName); + activity.theme = android.R.style.Theme_NoDisplay; + activity.exported = true; + activity.setName(PackageManager.APP_DETAILS_ACTIVITY_CLASS_NAME); + activity.setProcessName(processName); + activity.uiOptions = uiOptions; + activity.taskAffinity = taskAffinity; + activity.launchMode = ActivityInfo.LAUNCH_MULTIPLE; + activity.documentLaunchMode = ActivityInfo.DOCUMENT_LAUNCH_NONE; + activity.maxRecents = ActivityTaskManager.getDefaultAppRecentsLimitStatic(); + activity.configChanges = PackageParser.getActivityConfigChanges(0, 0); + activity.softInputMode = 0; + activity.persistableMode = ActivityInfo.PERSIST_NEVER; + activity.screenOrientation = SCREEN_ORIENTATION_UNSPECIFIED; + activity.resizeMode = RESIZE_MODE_FORCE_RESIZEABLE; + activity.lockTaskLaunchMode = 0; + activity.setDirectBootAware(false); + activity.rotationAnimation = ROTATION_ANIMATION_UNSPECIFIED; + activity.colorMode = ActivityInfo.COLOR_MODE_DEFAULT; + activity.preferMinimalPostProcessing = ActivityInfo.MINIMAL_POST_PROCESSING_DEFAULT; + if (hardwareAccelerated) { + activity.setFlags(activity.getFlags() | ActivityInfo.FLAG_HARDWARE_ACCELERATED); + } + return activity; + } + + static ParsedActivity makeAlias(String targetActivityName, ParsedActivity target) { + ParsedActivity alias = new ParsedActivity(); + alias.setPackageName(target.getPackageName()); + alias.setTargetActivity(targetActivityName); + alias.configChanges = target.configChanges; + alias.flags = target.flags; + alias.privateFlags = target.privateFlags; + alias.icon = target.icon; + alias.logo = target.logo; + alias.banner = target.banner; + alias.labelRes = target.labelRes; + alias.nonLocalizedLabel = target.nonLocalizedLabel; + alias.launchMode = target.launchMode; + alias.lockTaskLaunchMode = target.lockTaskLaunchMode; + alias.descriptionRes = target.descriptionRes; + alias.screenOrientation = target.screenOrientation; + alias.taskAffinity = target.taskAffinity; + alias.theme = target.theme; + alias.softInputMode = target.softInputMode; + alias.uiOptions = target.uiOptions; + alias.parentActivityName = target.parentActivityName; + alias.maxRecents = target.maxRecents; + alias.windowLayout = target.windowLayout; + alias.resizeMode = target.resizeMode; + alias.maxAspectRatio = target.maxAspectRatio; + alias.minAspectRatio = target.minAspectRatio; + alias.requestedVrComponent = target.requestedVrComponent; + alias.directBootAware = target.directBootAware; + alias.setProcessName(target.getProcessName()); + return alias; + + // Not all attributes from the target ParsedActivity are copied to the alias. + // Careful when adding an attribute and determine whether or not it should be copied. +// alias.enabled = target.enabled; +// alias.exported = target.exported; +// alias.permission = target.permission; +// alias.splitName = target.splitName; +// alias.documentLaunchMode = target.documentLaunchMode; +// alias.persistableMode = target.persistableMode; +// alias.rotationAnimation = target.rotationAnimation; +// alias.colorMode = target.colorMode; +// alias.intents.addAll(target.intents); +// alias.order = target.order; +// alias.metaData = target.metaData; + } + + public void setMaxAspectRatio(int resizeMode, float maxAspectRatio) { + if (resizeMode == ActivityInfo.RESIZE_MODE_RESIZEABLE + || resizeMode == ActivityInfo.RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION) { + // Resizeable activities can be put in any aspect ratio. + return; + } + + if (maxAspectRatio < 1.0f && maxAspectRatio != 0) { + // Ignore any value lesser than 1.0. + return; + } + + this.maxAspectRatio = maxAspectRatio; + } + + public void setMinAspectRatio(int resizeMode, float minAspectRatio) { + if (resizeMode == RESIZE_MODE_RESIZEABLE + || resizeMode == RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION) { + // Resizeable activities can be put in any aspect ratio. + return; + } + + if (minAspectRatio < 1.0f && minAspectRatio != 0) { + // Ignore any value lesser than 1.0. + return; + } + + this.minAspectRatio = minAspectRatio; + } + + public void setFlags(int flags) { + this.flags = flags; + } + + public ParsedActivity setResizeMode(int resizeMode) { + this.resizeMode = resizeMode; + return this; + } + + public ParsedActivity setTargetActivity(String targetActivity) { + this.targetActivity = TextUtils.safeIntern(targetActivity); + return this; + } + + public ParsedActivity setParentActivity(String parentActivity) { + this.parentActivityName = TextUtils.safeIntern(parentActivity); + return this; + } + + public ParsedActivity setPermission(String permission) { + // Empty string must be converted to null + this.permission = TextUtils.isEmpty(permission) ? null : permission.intern(); + return this; + } + + public String toString() { + StringBuilder sb = new StringBuilder(128); + sb.append("Activity{"); + sb.append(Integer.toHexString(System.identityHashCode(this))); + sb.append(' '); + ComponentName.appendShortString(sb, getPackageName(), getName()); + sb.append('}'); + return sb.toString(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeInt(this.theme); + dest.writeInt(this.uiOptions); + sForString.parcel(this.targetActivity, dest, flags); + sForString.parcel(this.parentActivityName, dest, flags); + dest.writeString(this.taskAffinity); + dest.writeInt(this.privateFlags); + sForString.parcel(this.permission, dest, flags); + dest.writeInt(this.launchMode); + dest.writeInt(this.documentLaunchMode); + dest.writeInt(this.maxRecents); + dest.writeInt(this.configChanges); + dest.writeInt(this.softInputMode); + dest.writeInt(this.persistableMode); + dest.writeInt(this.lockTaskLaunchMode); + dest.writeInt(this.screenOrientation); + dest.writeInt(this.resizeMode); + dest.writeValue(this.maxAspectRatio); + dest.writeValue(this.minAspectRatio); + dest.writeString(this.requestedVrComponent); + dest.writeInt(this.rotationAnimation); + dest.writeInt(this.colorMode); + dest.writeBoolean(this.preferMinimalPostProcessing); + dest.writeBundle(this.metaData); + + if (windowLayout != null) { + dest.writeBoolean(true); + dest.writeInt(windowLayout.width); + dest.writeFloat(windowLayout.widthFraction); + dest.writeInt(windowLayout.height); + dest.writeFloat(windowLayout.heightFraction); + dest.writeInt(windowLayout.gravity); + dest.writeInt(windowLayout.minWidth); + dest.writeInt(windowLayout.minHeight); + } else { + dest.writeBoolean(false); + } + } + + public ParsedActivity() { + } + + protected ParsedActivity(Parcel in) { + super(in); + this.theme = in.readInt(); + this.uiOptions = in.readInt(); + this.targetActivity = sForString.unparcel(in); + this.parentActivityName = sForString.unparcel(in); + this.taskAffinity = in.readString(); + this.privateFlags = in.readInt(); + this.permission = sForString.unparcel(in); + this.launchMode = in.readInt(); + this.documentLaunchMode = in.readInt(); + this.maxRecents = in.readInt(); + this.configChanges = in.readInt(); + this.softInputMode = in.readInt(); + this.persistableMode = in.readInt(); + this.lockTaskLaunchMode = in.readInt(); + this.screenOrientation = in.readInt(); + this.resizeMode = in.readInt(); + this.maxAspectRatio = (Float) in.readValue(Float.class.getClassLoader()); + this.minAspectRatio = (Float) in.readValue(Float.class.getClassLoader()); + this.requestedVrComponent = in.readString(); + this.rotationAnimation = in.readInt(); + this.colorMode = in.readInt(); + this.preferMinimalPostProcessing = in.readBoolean(); + this.metaData = in.readBundle(); + if (in.readBoolean()) { + windowLayout = new ActivityInfo.WindowLayout(in); + } + } + + public static final Parcelable.Creator CREATOR = new Creator() { + @Override + public ParsedActivity createFromParcel(Parcel source) { + return new ParsedActivity(source); + } + + @Override + public ParsedActivity[] newArray(int size) { + return new ParsedActivity[size]; + } + }; + + public int getTheme() { + return theme; + } + + public int getUiOptions() { + return uiOptions; + } + + @Nullable + public String getTargetActivity() { + return targetActivity; + } + + @Nullable + public String getParentActivityName() { + return parentActivityName; + } + + @Nullable + public String getTaskAffinity() { + return taskAffinity; + } + + public int getPrivateFlags() { + return privateFlags; + } + + @Nullable + public String getPermission() { + return permission; + } + + public int getLaunchMode() { + return launchMode; + } + + public int getDocumentLaunchMode() { + return documentLaunchMode; + } + + public int getMaxRecents() { + return maxRecents; + } + + public int getConfigChanges() { + return configChanges; + } + + public int getSoftInputMode() { + return softInputMode; + } + + public int getPersistableMode() { + return persistableMode; + } + + public int getLockTaskLaunchMode() { + return lockTaskLaunchMode; + } + + public int getScreenOrientation() { + return screenOrientation; + } + + public int getResizeMode() { + return resizeMode; + } + + @Nullable + public Float getMaxAspectRatio() { + return maxAspectRatio; + } + + @Nullable + public Float getMinAspectRatio() { + return minAspectRatio; + } + + @Nullable + public String getRequestedVrComponent() { + return requestedVrComponent; + } + + public int getRotationAnimation() { + return rotationAnimation; + } + + public int getColorMode() { + return colorMode; + } + + public boolean isPreferMinimalPostProcessing() { + return preferMinimalPostProcessing; + } + + @Nullable + public ActivityInfo.WindowLayout getWindowLayout() { + return windowLayout; + } +} diff --git a/core/java/android/content/pm/parsing/component/ParsedActivityUtils.java b/core/java/android/content/pm/parsing/component/ParsedActivityUtils.java new file mode 100644 index 0000000000000..7d0ed19fae80c --- /dev/null +++ b/core/java/android/content/pm/parsing/component/ParsedActivityUtils.java @@ -0,0 +1,476 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content.pm.parsing.component; + +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; + +import static android.content.pm.parsing.component.ComponentParseUtils.flag; + +import android.annotation.NonNull; +import android.app.ActivityTaskManager; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageParser; + +import android.content.pm.parsing.ParsingPackage; +import android.content.pm.parsing.ParsingUtils; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.content.res.XmlResourceParser; +import android.os.Build; +import android.util.AttributeSet; +import android.util.Log; +import android.util.Slog; +import android.util.TypedValue; +import android.view.Gravity; +import android.view.WindowManager; + +import com.android.internal.R; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.ArrayUtils; +import android.content.pm.parsing.ParsingPackageUtils; +import android.content.pm.parsing.result.ParseInput; +import android.content.pm.parsing.result.ParseResult; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; +import java.util.List; +import java.util.Objects; + +/** @hide */ +public class ParsedActivityUtils { + + private static final String TAG = ParsingPackageUtils.TAG; + + @NonNull + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public static ParseResult parseActivityOrReceiver(String[] separateProcesses, + ParsingPackage pkg, Resources res, XmlResourceParser parser, int flags, + boolean useRoundIcon, ParseInput input) + throws XmlPullParserException, IOException { + final String packageName = pkg.getPackageName(); + final ParsedActivity + activity = new ParsedActivity(); + + boolean receiver = "receiver".equals(parser.getName()); + String tag = "<" + parser.getName() + ">"; + try (TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestActivity)){ + ParseResult result = + ParsedMainComponentUtils.parseMainComponent( + activity, tag, separateProcesses, + pkg, sa, flags, useRoundIcon, input, + R.styleable.AndroidManifestActivity_banner, + R.styleable.AndroidManifestActivity_description, + R.styleable.AndroidManifestActivity_directBootAware, + R.styleable.AndroidManifestActivity_enabled, + R.styleable.AndroidManifestActivity_icon, + R.styleable.AndroidManifestActivity_label, + R.styleable.AndroidManifestActivity_logo, + R.styleable.AndroidManifestActivity_name, + R.styleable.AndroidManifestActivity_process, + R.styleable.AndroidManifestActivity_roundIcon, + R.styleable.AndroidManifestActivity_splitName); + if (result.isError()) { + return result; + } + + if (receiver && pkg.isCantSaveState()) { + // A heavy-weight application can not have receivers in its main process + if (Objects.equals(activity.getProcessName(), packageName)) { + return input.error("Heavy-weight applications can not have receivers " + + "in main process"); + } + } + + // The following section has formatting off to make it easier to read the flags. + // Multi-lining them to fit within the column restriction makes it hard to tell what + // field is assigned where. + // @formatter:off + activity.theme = sa.getResourceId(R.styleable.AndroidManifestActivity_theme, 0); + activity.uiOptions = sa.getInt(R.styleable.AndroidManifestActivity_uiOptions, pkg.getUiOptions()); + + activity.flags |= flag(ActivityInfo.FLAG_ALLOW_TASK_REPARENTING, R.styleable.AndroidManifestActivity_allowTaskReparenting, pkg.isAllowTaskReparenting(), sa) + | flag(ActivityInfo.FLAG_ALWAYS_RETAIN_TASK_STATE, R.styleable.AndroidManifestActivity_alwaysRetainTaskState, sa) + | flag(ActivityInfo.FLAG_CLEAR_TASK_ON_LAUNCH, R.styleable.AndroidManifestActivity_clearTaskOnLaunch, sa) + | flag(ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS, R.styleable.AndroidManifestActivity_excludeFromRecents, sa) + | flag(ActivityInfo.FLAG_FINISH_ON_CLOSE_SYSTEM_DIALOGS, R.styleable.AndroidManifestActivity_finishOnCloseSystemDialogs, sa) + | flag(ActivityInfo.FLAG_FINISH_ON_TASK_LAUNCH, R.styleable.AndroidManifestActivity_finishOnTaskLaunch, sa) + | flag(ActivityInfo.FLAG_IMMERSIVE, R.styleable.AndroidManifestActivity_immersive, sa) + | flag(ActivityInfo.FLAG_MULTIPROCESS, R.styleable.AndroidManifestActivity_multiprocess, sa) + | flag(ActivityInfo.FLAG_NO_HISTORY, R.styleable.AndroidManifestActivity_noHistory, sa) + | flag(ActivityInfo.FLAG_SHOW_FOR_ALL_USERS, R.styleable.AndroidManifestActivity_showForAllUsers, sa) + | flag(ActivityInfo.FLAG_SHOW_FOR_ALL_USERS, R.styleable.AndroidManifestActivity_showOnLockScreen, sa) + | flag(ActivityInfo.FLAG_STATE_NOT_NEEDED, R.styleable.AndroidManifestActivity_stateNotNeeded, sa) + | flag(ActivityInfo.FLAG_SYSTEM_USER_ONLY, R.styleable.AndroidManifestActivity_systemUserOnly, sa); + + if (!receiver) { + activity.flags |= flag(ActivityInfo.FLAG_HARDWARE_ACCELERATED, R.styleable.AndroidManifestActivity_hardwareAccelerated, pkg.isBaseHardwareAccelerated(), sa) + | flag(ActivityInfo.FLAG_ALLOW_EMBEDDED, R.styleable.AndroidManifestActivity_allowEmbedded, sa) + | flag(ActivityInfo.FLAG_ALWAYS_FOCUSABLE, R.styleable.AndroidManifestActivity_alwaysFocusable, sa) + | flag(ActivityInfo.FLAG_AUTO_REMOVE_FROM_RECENTS, R.styleable.AndroidManifestActivity_autoRemoveFromRecents, sa) + | flag(ActivityInfo.FLAG_RELINQUISH_TASK_IDENTITY, R.styleable.AndroidManifestActivity_relinquishTaskIdentity, sa) + | flag(ActivityInfo.FLAG_RESUME_WHILE_PAUSING, R.styleable.AndroidManifestActivity_resumeWhilePausing, sa) + | flag(ActivityInfo.FLAG_SHOW_WHEN_LOCKED, R.styleable.AndroidManifestActivity_showWhenLocked, sa) + | flag(ActivityInfo.FLAG_SUPPORTS_PICTURE_IN_PICTURE, R.styleable.AndroidManifestActivity_supportsPictureInPicture, sa) + | flag(ActivityInfo.FLAG_TURN_SCREEN_ON, R.styleable.AndroidManifestActivity_turnScreenOn, sa); + + activity.privateFlags |= flag(ActivityInfo.FLAG_INHERIT_SHOW_WHEN_LOCKED, R.styleable.AndroidManifestActivity_inheritShowWhenLocked, sa); + + activity.colorMode = sa.getInt(R.styleable.AndroidManifestActivity_colorMode, ActivityInfo.COLOR_MODE_DEFAULT); + activity.preferMinimalPostProcessing = sa.getBoolean(R.styleable.AndroidManifestActivity_preferMinimalPostProcessing, ActivityInfo.MINIMAL_POST_PROCESSING_DEFAULT); + activity.documentLaunchMode = sa.getInt(R.styleable.AndroidManifestActivity_documentLaunchMode, ActivityInfo.DOCUMENT_LAUNCH_NONE); + activity.launchMode = sa.getInt(R.styleable.AndroidManifestActivity_launchMode, ActivityInfo.LAUNCH_MULTIPLE); + activity.lockTaskLaunchMode = sa.getInt(R.styleable.AndroidManifestActivity_lockTaskMode, 0); + activity.maxRecents = sa.getInt(R.styleable.AndroidManifestActivity_maxRecents, ActivityTaskManager.getDefaultAppRecentsLimitStatic()); + activity.persistableMode = sa.getInteger(R.styleable.AndroidManifestActivity_persistableMode, ActivityInfo.PERSIST_ROOT_ONLY); + activity.requestedVrComponent = sa.getString(R.styleable.AndroidManifestActivity_enableVrMode); + activity.rotationAnimation = sa.getInt(R.styleable.AndroidManifestActivity_rotationAnimation, WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED); + activity.softInputMode = sa.getInt(R.styleable.AndroidManifestActivity_windowSoftInputMode, 0); + + activity.configChanges = PackageParser.getActivityConfigChanges( + sa.getInt(R.styleable.AndroidManifestActivity_configChanges, 0), + sa.getInt(R.styleable.AndroidManifestActivity_recreateOnConfigChanges, 0)); + + int screenOrientation = sa.getInt(R.styleable.AndroidManifestActivity_screenOrientation, SCREEN_ORIENTATION_UNSPECIFIED); + int resizeMode = getActivityResizeMode(pkg, sa, screenOrientation); + activity.screenOrientation = screenOrientation; + activity.resizeMode = resizeMode; + + if (sa.hasValue(R.styleable.AndroidManifestActivity_maxAspectRatio) + && sa.getType(R.styleable.AndroidManifestActivity_maxAspectRatio) + == TypedValue.TYPE_FLOAT) { + activity.setMaxAspectRatio(resizeMode, + sa.getFloat(R.styleable.AndroidManifestActivity_maxAspectRatio, + 0 /*default*/)); + } + + if (sa.hasValue(R.styleable.AndroidManifestActivity_minAspectRatio) + && sa.getType(R.styleable.AndroidManifestActivity_minAspectRatio) + == TypedValue.TYPE_FLOAT) { + activity.setMinAspectRatio(resizeMode, + sa.getFloat(R.styleable.AndroidManifestActivity_minAspectRatio, + 0 /*default*/)); + } + } else { + activity.launchMode = ActivityInfo.LAUNCH_MULTIPLE; + activity.configChanges = 0; + activity.flags |= flag(ActivityInfo.FLAG_SINGLE_USER, R.styleable.AndroidManifestActivity_singleUser, sa); + } + // @formatter:on + + String taskAffinity = sa.getNonConfigurationString( + R.styleable.AndroidManifestActivity_taskAffinity, + Configuration.NATIVE_CONFIG_VERSION); + + ParseResult affinityNameResult = ComponentParseUtils.buildTaskAffinityName( + packageName, pkg.getTaskAffinity(), taskAffinity, input); + if (affinityNameResult.isSuccess()) { + activity.taskAffinity = affinityNameResult.getResult(); + } else { + // Backwards-compat, ignore error + affinityNameResult.ignoreError(); + } + + boolean visibleToEphemeral = sa.getBoolean(R.styleable.AndroidManifestActivity_visibleToInstantApps, false); + if (visibleToEphemeral) { + activity.flags |= ActivityInfo.FLAG_VISIBLE_TO_INSTANT_APP; + pkg.setVisibleToInstantApps(true); + } + + return parseActivityOrAlias(activity, pkg, tag, parser, res, sa, receiver, + false /*isAlias*/, visibleToEphemeral, input, + R.styleable.AndroidManifestActivity_parentActivityName, + R.styleable.AndroidManifestActivity_permission, + R.styleable.AndroidManifestActivity_exported + ); + } + } + + @NonNull + public static ParseResult parseActivityAlias(ParsingPackage pkg, Resources res, + XmlResourceParser parser, boolean useRoundIcon, ParseInput input) + throws XmlPullParserException, IOException { + try (TypedArray sa = res.obtainAttributes(parser, + R.styleable.AndroidManifestActivityAlias)) { + String targetActivity = sa.getNonConfigurationString( + R.styleable.AndroidManifestActivityAlias_targetActivity, + Configuration.NATIVE_CONFIG_VERSION); + if (targetActivity == null) { + return input.error(" does not specify android:targetActivity"); + } + + String packageName = pkg.getPackageName(); + targetActivity = ParsingUtils.buildClassName(packageName, targetActivity); + if (targetActivity == null) { + return input.error("Empty class name in package " + packageName); + } + + ParsedActivity target = null; + + List activities = pkg.getActivities(); + final int activitiesSize = ArrayUtils.size(activities); + for (int i = 0; i < activitiesSize; i++) { + ParsedActivity t = activities.get(i); + if (targetActivity.equals(t.getName())) { + target = t; + break; + } + } + + if (target == null) { + return input.error(" target activity " + targetActivity + + " not found in manifest with activities = " + + pkg.getActivities() + + ", parsedActivities = " + activities); + } + + ParsedActivity activity = ParsedActivity.makeAlias(targetActivity, target); + String tag = "<" + parser.getName() + ">"; + + ParseResult result = ParsedMainComponentUtils.parseMainComponent( + activity, tag, null, pkg, sa, 0, useRoundIcon, input, + R.styleable.AndroidManifestActivityAlias_banner, + R.styleable.AndroidManifestActivityAlias_description, + null /*directBootAwareAttr*/, + R.styleable.AndroidManifestActivityAlias_enabled, + R.styleable.AndroidManifestActivityAlias_icon, + R.styleable.AndroidManifestActivityAlias_label, + R.styleable.AndroidManifestActivityAlias_logo, + R.styleable.AndroidManifestActivityAlias_name, + null /*processAttr*/, + R.styleable.AndroidManifestActivityAlias_roundIcon, + null /*splitNameAttr*/); + if (result.isError()) { + return result; + } + + // TODO add visibleToInstantApps attribute to activity alias + final boolean visibleToEphemeral = + ((activity.getFlags() & ActivityInfo.FLAG_VISIBLE_TO_INSTANT_APP) != 0); + + return parseActivityOrAlias(activity, pkg, tag, parser, res, sa, false /*isReceiver*/, true /*isAlias*/, + visibleToEphemeral, input, + R.styleable.AndroidManifestActivityAlias_parentActivityName, + R.styleable.AndroidManifestActivityAlias_permission, + R.styleable.AndroidManifestActivityAlias_exported); + } + } + + /** + * 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. + */ + @NonNull + private static ParseResult 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); + 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 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); + 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 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 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); + } + } + + if (!setExported) { + activity.exported = activity.getIntents().size() > 0; + } + + return input.success(activity); + } + + @NonNull + private static ParseResult parseIntentFilter(ParsingPackage pkg, + ParsedActivity activity, boolean allowImplicitEphemeralVisibility, + boolean visibleToEphemeral, Resources resources, XmlResourceParser parser, + ParseInput input) throws IOException, XmlPullParserException { + ParseResult result = ParsedMainComponentUtils.parseIntentFilter(activity, + pkg, resources, parser, visibleToEphemeral, true /*allowGlobs*/, + true /*allowAutoVerify*/, allowImplicitEphemeralVisibility, + true /*failOnNoActions*/, input); + if (result.isError()) { + return input.error(result); + } + + ParsedIntentInfo intent = result.getResult(); + if (intent != null) { + if (intent.isVisibleToInstantApp()) { + activity.flags |= ActivityInfo.FLAG_VISIBLE_TO_INSTANT_APP; + } + if (intent.isImplicitlyVisibleToInstantApp()) { + activity.flags |= ActivityInfo.FLAG_IMPLICITLY_VISIBLE_TO_INSTANT_APP; + } + } + + return input.success(intent); + } + + private static int getActivityResizeMode(ParsingPackage pkg, TypedArray sa, + int screenOrientation) { + Boolean resizeableActivity = pkg.getResizeableActivity(); + + if (sa.hasValue(R.styleable.AndroidManifestActivity_resizeableActivity) + || resizeableActivity != null) { + // Activity or app explicitly set if it is resizeable or not; + if (sa.getBoolean(R.styleable.AndroidManifestActivity_resizeableActivity, + resizeableActivity != null && resizeableActivity)) { + return ActivityInfo.RESIZE_MODE_RESIZEABLE; + } else { + return ActivityInfo.RESIZE_MODE_UNRESIZEABLE; + } + } + + if (pkg.isResizeableActivityViaSdkVersion()) { + // The activity or app didn't explicitly set the resizing option, however we want to + // make it resize due to the sdk version it is targeting. + return ActivityInfo.RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION; + } + + // resize preference isn't set and target sdk version doesn't support resizing apps by + // default. For the app to be resizeable if it isn't fixed orientation or immersive. + if (ActivityInfo.isFixedOrientationPortrait(screenOrientation)) { + return ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_PORTRAIT_ONLY; + } else if (ActivityInfo.isFixedOrientationLandscape(screenOrientation)) { + return ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_LANDSCAPE_ONLY; + } else if (screenOrientation == ActivityInfo.SCREEN_ORIENTATION_LOCKED) { + return ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_PRESERVE_ORIENTATION; + } else { + return ActivityInfo.RESIZE_MODE_FORCE_RESIZEABLE; + } + } + + @NonNull + private static ParseResult parseLayout(Resources res, + AttributeSet attrs, ParseInput input) { + try (TypedArray sw = res.obtainAttributes(attrs, R.styleable.AndroidManifestLayout)) { + int width = -1; + float widthFraction = -1f; + int height = -1; + float heightFraction = -1f; + final int widthType = sw.getType(R.styleable.AndroidManifestLayout_defaultWidth); + if (widthType == TypedValue.TYPE_FRACTION) { + widthFraction = sw.getFraction(R.styleable.AndroidManifestLayout_defaultWidth, 1, 1, + -1); + } else if (widthType == TypedValue.TYPE_DIMENSION) { + width = sw.getDimensionPixelSize(R.styleable.AndroidManifestLayout_defaultWidth, + -1); + } + final int heightType = sw.getType(R.styleable.AndroidManifestLayout_defaultHeight); + if (heightType == TypedValue.TYPE_FRACTION) { + heightFraction = sw.getFraction(R.styleable.AndroidManifestLayout_defaultHeight, 1, + 1, -1); + } else if (heightType == TypedValue.TYPE_DIMENSION) { + height = sw.getDimensionPixelSize(R.styleable.AndroidManifestLayout_defaultHeight, + -1); + } + int gravity = sw.getInt(R.styleable.AndroidManifestLayout_gravity, Gravity.CENTER); + int minWidth = sw.getDimensionPixelSize(R.styleable.AndroidManifestLayout_minWidth, -1); + int minHeight = sw.getDimensionPixelSize(R.styleable.AndroidManifestLayout_minHeight, + -1); + return input.success(new ActivityInfo.WindowLayout(width, widthFraction, height, + heightFraction, gravity, minWidth, minHeight)); + } + } +} diff --git a/core/java/android/content/pm/parsing/component/ParsedComponent.java b/core/java/android/content/pm/parsing/component/ParsedComponent.java new file mode 100644 index 0000000000000..098d6204b021f --- /dev/null +++ b/core/java/android/content/pm/parsing/component/ParsedComponent.java @@ -0,0 +1,208 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content.pm.parsing.component; + +import static android.content.pm.parsing.ParsingPackageImpl.sForString; + +import android.annotation.CallSuper; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.ComponentName; +import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; +import android.text.TextUtils; + +import com.android.internal.util.CollectionUtils; +import com.android.internal.util.DataClass; +import com.android.internal.util.Parcelling; +import com.android.internal.util.Parcelling.BuiltIn.ForInternedString; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** @hide */ +public abstract class ParsedComponent implements Parcelable { + + private static ParsedIntentInfo.ListParceler sForIntentInfos = Parcelling.Cache.getOrCreate( + ParsedIntentInfo.ListParceler.class); + + @NonNull + @DataClass.ParcelWith(ForInternedString.class) + private String name; + int icon; + int labelRes; + @Nullable + CharSequence nonLocalizedLabel; + int logo; + int banner; + int descriptionRes; + + // TODO(b/135203078): Replace flags with individual booleans, scoped by subclass + int flags; + + @NonNull + @DataClass.ParcelWith(ForInternedString.class) + private String packageName; + + @Nullable + @DataClass.PluralOf("intent") + @DataClass.ParcelWith(ParsedIntentInfo.ListParceler.class) + private List intents; + + private ComponentName componentName; + + @Nullable + protected Bundle metaData; + + ParsedComponent() { + + } + + @SuppressWarnings("IncompleteCopyConstructor") + public ParsedComponent(ParsedComponent other) { + this.metaData = other.metaData; + this.name = other.name; + this.icon = other.getIcon(); + this.labelRes = other.getLabelRes(); + this.nonLocalizedLabel = other.getNonLocalizedLabel(); + this.logo = other.getLogo(); + this.banner = other.getBanner(); + + this.descriptionRes = other.getDescriptionRes(); + + this.flags = other.getFlags(); + + this.setPackageName(other.packageName); + this.intents = new ArrayList<>(other.getIntents()); + } + + public void addIntent(ParsedIntentInfo intent) { + this.intents = CollectionUtils.add(this.intents, intent); + } + + @NonNull + public List getIntents() { + return intents != null ? intents : Collections.emptyList(); + } + + public ParsedComponent setName(String name) { + this.name = TextUtils.safeIntern(name); + return this; + } + + @CallSuper + public void setPackageName(@NonNull String packageName) { + this.packageName = TextUtils.safeIntern(packageName); + //noinspection ConstantConditions + this.componentName = null; + + // Note: this method does not edit name (which can point to a class), because this package + // name change is not changing the package in code, but the identifier used by the system. + } + + @NonNull + public ComponentName getComponentName() { + if (componentName == null) { + componentName = new ComponentName(getPackageName(), getName()); + } + return componentName; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + sForString.parcel(this.name, dest, flags); + dest.writeInt(this.getIcon()); + dest.writeInt(this.getLabelRes()); + dest.writeCharSequence(this.getNonLocalizedLabel()); + dest.writeInt(this.getLogo()); + dest.writeInt(this.getBanner()); + dest.writeInt(this.getDescriptionRes()); + dest.writeInt(this.getFlags()); + sForString.parcel(this.packageName, dest, flags); + sForIntentInfos.parcel(this.getIntents(), dest, flags); + dest.writeBundle(this.metaData); + } + + protected ParsedComponent(Parcel in) { + // We use the boot classloader for all classes that we load. + final ClassLoader boot = Object.class.getClassLoader(); + //noinspection ConstantConditions + this.name = sForString.unparcel(in); + this.icon = in.readInt(); + this.labelRes = in.readInt(); + this.nonLocalizedLabel = in.readCharSequence(); + this.logo = in.readInt(); + this.banner = in.readInt(); + this.descriptionRes = in.readInt(); + this.flags = in.readInt(); + //noinspection ConstantConditions + this.packageName = sForString.unparcel(in); + this.intents = sForIntentInfos.unparcel(in); + this.metaData = in.readBundle(boot); + } + + @NonNull + public String getName() { + return name; + } + + public int getIcon() { + return icon; + } + + public int getLabelRes() { + return labelRes; + } + + @Nullable + public CharSequence getNonLocalizedLabel() { + return nonLocalizedLabel; + } + + public int getLogo() { + return logo; + } + + public int getBanner() { + return banner; + } + + public int getDescriptionRes() { + return descriptionRes; + } + + public int getFlags() { + return flags; + } + + @NonNull + public String getPackageName() { + return packageName; + } + + @Nullable + public Bundle getMetaData() { + return metaData; + } +} diff --git a/core/java/android/content/pm/parsing/component/ParsedComponentUtils.java b/core/java/android/content/pm/parsing/component/ParsedComponentUtils.java new file mode 100644 index 0000000000000..b37b617570533 --- /dev/null +++ b/core/java/android/content/pm/parsing/component/ParsedComponentUtils.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content.pm.parsing.component; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.pm.PackageManager; +import android.content.pm.parsing.ParsingPackage; +import android.content.pm.parsing.ParsingUtils; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.content.res.XmlResourceParser; +import android.os.Bundle; +import android.text.TextUtils; +import android.util.TypedValue; + +import com.android.internal.annotations.VisibleForTesting; +import android.content.pm.parsing.ParsingPackageUtils; +import android.content.pm.parsing.result.ParseInput; +import android.content.pm.parsing.result.ParseResult; + +/** @hide */ +class ParsedComponentUtils { + + private static final String TAG = ParsingPackageUtils.TAG; + + @NonNull + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + static ParseResult parseComponent( + Component component, String tag, ParsingPackage pkg, TypedArray array, + boolean useRoundIcon, ParseInput input, int bannerAttr, + @Nullable Integer descriptionAttr, int iconAttr, int labelAttr, int logoAttr, + int nameAttr, int roundIconAttr) { + String name = array.getNonConfigurationString(nameAttr, 0); + if (TextUtils.isEmpty(name)) { + return input.error(tag + " does not specify android:name"); + } + + String packageName = pkg.getPackageName(); + String className = ParsingUtils.buildClassName(packageName, name); + if (PackageManager.APP_DETAILS_ACTIVITY_CLASS_NAME.equals(className)) { + return input.error(tag + " invalid android:name"); + } + + //noinspection ConstantConditions; null check done above with isEmpty + component.setName(className); + component.setPackageName(packageName); + + if (useRoundIcon) { + component.icon = array.getResourceId(roundIconAttr, 0); + } + + if (component.icon == 0) { + component.icon = array.getResourceId(iconAttr, 0); + } + + component.logo = array.getResourceId(logoAttr, 0); + component.banner = array.getResourceId(bannerAttr, 0); + + if (descriptionAttr != null) { + component.descriptionRes = array.getResourceId(descriptionAttr, 0); + } + + TypedValue v = array.peekValue(labelAttr); + if (v != null) { + component.labelRes = v.resourceId; + if (v.resourceId == 0) { + component.nonLocalizedLabel = v.coerceToString(); + } + } + + return input.success(component); + } + + static ParseResult addMetaData(ParsedComponent component, ParsingPackage pkg, + Resources resources, XmlResourceParser parser, ParseInput input) { + ParseResult result = ParsingPackageUtils.parseMetaData(pkg, resources, + parser, component.metaData, input); + if (result.isError()) { + return input.error(result); + } + Bundle bundle = result.getResult(); + component.metaData = bundle; + return input.success(bundle); + } +} diff --git a/core/java/android/content/pm/parsing/component/ParsedFeature.java b/core/java/android/content/pm/parsing/component/ParsedFeature.java new file mode 100644 index 0000000000000..b8a9098cfa0e1 --- /dev/null +++ b/core/java/android/content/pm/parsing/component/ParsedFeature.java @@ -0,0 +1,229 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content.pm.parsing.component; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.StringRes; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.ArraySet; + +import com.android.internal.util.DataClass; + +import java.util.ArrayList; +import java.util.List; + +/** + * A {@link android.R.styleable#AndroidManifestFeature <feature>} tag parsed from the + * manifest. + * + * @hide + */ +@DataClass(genAidl = false) +public class ParsedFeature implements Parcelable { + /** Maximum length of featureId */ + public static final int MAX_FEATURE_ID_LEN = 50; + + /** Maximum amount of features per package */ + private static final int MAX_NUM_FEATURES = 1000; + + /** Id of the feature */ + public final @NonNull String id; + + /** User visible label fo the feature */ + public final @StringRes int label; + + /** Ids of previously declared features this feature inherits from */ + public final @NonNull List inheritFrom; + + /** + * @return Is this set of features a valid combination for a single package? + */ + public static boolean isCombinationValid(@Nullable List features) { + if (features == null) { + return true; + } + + ArraySet featureIds = new ArraySet<>(features.size()); + ArraySet inheritFromFeatureIds = new ArraySet<>(); + + int numFeatures = features.size(); + if (numFeatures > MAX_NUM_FEATURES) { + return false; + } + + for (int featureNum = 0; featureNum < numFeatures; featureNum++) { + boolean wasAdded = featureIds.add(features.get(featureNum).id); + if (!wasAdded) { + // feature id is not unique + return false; + } + } + + for (int featureNum = 0; featureNum < numFeatures; featureNum++) { + ParsedFeature feature = features.get(featureNum); + + int numInheritFrom = feature.inheritFrom.size(); + for (int inheritFromNum = 0; inheritFromNum < numInheritFrom; inheritFromNum++) { + String inheritFrom = feature.inheritFrom.get(inheritFromNum); + + if (featureIds.contains(inheritFrom)) { + // Cannot inherit from a feature that is still defined + return false; + } + + boolean wasAdded = inheritFromFeatureIds.add(inheritFrom); + if (!wasAdded) { + // inheritFrom is not unique + return false; + } + } + } + + return true; + } + + + + // Code below generated by codegen v1.0.14. + // + // DO NOT MODIFY! + // CHECKSTYLE:OFF Generated code + // + // To regenerate run: + // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/parsing/component/ParsedFeature.java + // + // To exclude the generated code from IntelliJ auto-formatting enable (one-time): + // Settings > Editor > Code Style > Formatter Control + //@formatter:off + + + @android.annotation.IntDef(prefix = "MAX_", value = { + MAX_FEATURE_ID_LEN, + MAX_NUM_FEATURES + }) + @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) + @DataClass.Generated.Member + public @interface Max {} + + @DataClass.Generated.Member + public static String maxToString(@Max int value) { + switch (value) { + case MAX_FEATURE_ID_LEN: + return "MAX_FEATURE_ID_LEN"; + case MAX_NUM_FEATURES: + return "MAX_NUM_FEATURES"; + default: return Integer.toHexString(value); + } + } + + /** + * Creates a new ParsedFeature. + * + * @param id + * Id of the feature + * @param label + * User visible label fo the feature + * @param inheritFrom + * Ids of previously declared features this feature inherits from + */ + @DataClass.Generated.Member + public ParsedFeature( + @NonNull String id, + @StringRes int label, + @NonNull List inheritFrom) { + this.id = id; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, id); + this.label = label; + com.android.internal.util.AnnotationValidations.validate( + StringRes.class, null, label); + this.inheritFrom = inheritFrom; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, inheritFrom); + + // onConstructed(); // You can define this method to get a callback + } + + @Override + @DataClass.Generated.Member + public void writeToParcel(@NonNull Parcel dest, int flags) { + // You can override field parcelling by defining methods like: + // void parcelFieldName(Parcel dest, int flags) { ... } + + dest.writeString(id); + dest.writeInt(label); + dest.writeStringList(inheritFrom); + } + + @Override + @DataClass.Generated.Member + public int describeContents() { return 0; } + + /** @hide */ + @SuppressWarnings({"unchecked", "RedundantCast"}) + @DataClass.Generated.Member + protected ParsedFeature(@NonNull Parcel in) { + // You can override field unparcelling by defining methods like: + // static FieldType unparcelFieldName(Parcel in) { ... } + + String _id = in.readString(); + int _label = in.readInt(); + List _inheritFrom = new ArrayList<>(); + in.readStringList(_inheritFrom); + + this.id = _id; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, id); + this.label = _label; + com.android.internal.util.AnnotationValidations.validate( + StringRes.class, null, label); + this.inheritFrom = _inheritFrom; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, inheritFrom); + + // onConstructed(); // You can define this method to get a callback + } + + @DataClass.Generated.Member + public static final @NonNull Parcelable.Creator CREATOR + = new Parcelable.Creator() { + @Override + public ParsedFeature[] newArray(int size) { + return new ParsedFeature[size]; + } + + @Override + public ParsedFeature createFromParcel(@NonNull Parcel in) { + return new ParsedFeature(in); + } + }; + + @DataClass.Generated( + time = 1581379861853L, + codegenVersion = "1.0.14", + sourceFile = "frameworks/base/core/java/android/content/pm/parsing/component/ParsedFeature.java", + inputSignatures = "public static final int MAX_FEATURE_ID_LEN\nprivate static final int MAX_NUM_FEATURES\npublic final @android.annotation.NonNull java.lang.String id\npublic final @android.annotation.StringRes int label\npublic final @android.annotation.NonNull java.util.List inheritFrom\npublic static boolean isCombinationValid(java.util.List)\nclass ParsedFeature extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genAidl=false)") + @Deprecated + private void __metadata() {} + + + //@formatter:on + // End of generated code + +} diff --git a/core/java/android/content/pm/parsing/component/ParsedFeatureUtils.java b/core/java/android/content/pm/parsing/component/ParsedFeatureUtils.java new file mode 100644 index 0000000000000..fb5280175d1a4 --- /dev/null +++ b/core/java/android/content/pm/parsing/component/ParsedFeatureUtils.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content.pm.parsing.component; + +import android.annotation.NonNull; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.content.res.XmlResourceParser; + +import com.android.internal.R; +import android.content.pm.parsing.result.ParseInput; +import android.content.pm.parsing.result.ParseResult; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** @hide */ +public class ParsedFeatureUtils { + + @NonNull + public static ParseResult parseFeature(Resources res, XmlResourceParser parser, + ParseInput input) throws IOException, XmlPullParserException { + String featureId; + int label; + List inheritFrom = null; + + TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestFeature); + if (sa == null) { + return input.error(" could not be parsed"); + } + + try { + featureId = sa.getNonConfigurationString(R.styleable.AndroidManifestFeature_featureId, + 0); + if (featureId == null) { + return input.error(" does not specify android:featureId"); + } + if (featureId.length() > ParsedFeature.MAX_FEATURE_ID_LEN) { + return input.error(" is too long. Max length is " + + ParsedFeature.MAX_FEATURE_ID_LEN); + } + + label = sa.getResourceId(R.styleable.AndroidManifestFeature_label, 0); + if (label == Resources.ID_NULL) { + return input.error(" does not specify android:label"); + } + } finally { + sa.recycle(); + } + + int type; + final int innerDepth = parser.getDepth(); + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT + && (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) { + if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { + continue; + } + + String tagName = parser.getName(); + if (tagName.equals("inherit-from")) { + sa = res.obtainAttributes(parser, R.styleable.AndroidManifestFeatureInheritFrom); + if (sa == null) { + return input.error(" could not be parsed"); + } + + try { + String inheritFromId = sa.getNonConfigurationString( + R.styleable.AndroidManifestFeatureInheritFrom_featureId,0); + + if (inheritFrom == null) { + inheritFrom = new ArrayList<>(); + } + inheritFrom.add(inheritFromId); + } finally { + sa.recycle(); + } + } else { + return input.error("Bad element under : " + tagName); + } + } + + if (inheritFrom == null) { + inheritFrom = Collections.emptyList(); + } else { + ((ArrayList) inheritFrom).trimToSize(); + } + + return input.success(new ParsedFeature(featureId, label, inheritFrom)); + } +} diff --git a/core/java/android/content/pm/parsing/component/ParsedInstrumentation.java b/core/java/android/content/pm/parsing/component/ParsedInstrumentation.java new file mode 100644 index 0000000000000..396a145311f24 --- /dev/null +++ b/core/java/android/content/pm/parsing/component/ParsedInstrumentation.java @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content.pm.parsing.component; + +import static android.content.pm.parsing.ParsingPackageImpl.sForString; + +import android.annotation.Nullable; +import android.content.ComponentName; +import android.os.Parcel; +import android.os.Parcelable; +import android.text.TextUtils; + +import com.android.internal.util.DataClass; +import com.android.internal.util.Parcelling; +import com.android.internal.util.Parcelling.BuiltIn.ForInternedString; + +/** @hide */ +public class ParsedInstrumentation extends ParsedComponent { + + @Nullable + @DataClass.ParcelWith(ForInternedString.class) + private String targetPackage; + @Nullable + @DataClass.ParcelWith(ForInternedString.class) + private String targetProcesses; + boolean handleProfiling; + boolean functionalTest; + + public ParsedInstrumentation() { + } + + public void setTargetPackage(@Nullable String targetPackage) { + this.targetPackage = TextUtils.safeIntern(targetPackage); + } + + public void setTargetProcesses(@Nullable String targetProcesses) { + this.targetProcesses = TextUtils.safeIntern(targetProcesses); + } + + public String toString() { + StringBuilder sb = new StringBuilder(128); + sb.append("Instrumentation{"); + sb.append(Integer.toHexString(System.identityHashCode(this))); + sb.append(' '); + ComponentName.appendShortString(sb, getPackageName(), getName()); + sb.append('}'); + return sb.toString(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + sForString.parcel(this.targetPackage, dest, flags); + sForString.parcel(this.targetProcesses, dest, flags); + dest.writeBoolean(this.handleProfiling); + dest.writeBoolean(this.functionalTest); + } + + protected ParsedInstrumentation(Parcel in) { + super(in); + this.targetPackage = sForString.unparcel(in); + this.targetProcesses = sForString.unparcel(in); + this.handleProfiling = in.readByte() != 0; + this.functionalTest = in.readByte() != 0; + } + + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + @Override + public ParsedInstrumentation createFromParcel(Parcel source) { + return new ParsedInstrumentation(source); + } + + @Override + public ParsedInstrumentation[] newArray(int size) { + return new ParsedInstrumentation[size]; + } + }; + + @Nullable + public String getTargetPackage() { + return targetPackage; + } + + @Nullable + public String getTargetProcesses() { + return targetProcesses; + } + + public boolean isHandleProfiling() { + return handleProfiling; + } + + public boolean isFunctionalTest() { + return functionalTest; + } +} diff --git a/core/java/android/content/pm/parsing/component/ParsedInstrumentationUtils.java b/core/java/android/content/pm/parsing/component/ParsedInstrumentationUtils.java new file mode 100644 index 0000000000000..022d00d3105d8 --- /dev/null +++ b/core/java/android/content/pm/parsing/component/ParsedInstrumentationUtils.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content.pm.parsing.component; + +import android.annotation.NonNull; +import android.content.pm.parsing.ParsingPackage; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.content.res.XmlResourceParser; + +import com.android.internal.R; +import android.content.pm.parsing.result.ParseInput; +import android.content.pm.parsing.result.ParseResult; + +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; + +/** @hide */ +public class ParsedInstrumentationUtils { + + @NonNull + public static ParseResult parseInstrumentation(ParsingPackage pkg, + Resources res, XmlResourceParser parser, boolean useRoundIcon, + ParseInput input) throws IOException, XmlPullParserException { + ParsedInstrumentation + instrumentation = new ParsedInstrumentation(); + String tag = "<" + parser.getName() + ">"; + + try (TypedArray sa = res.obtainAttributes(parser, + R.styleable.AndroidManifestInstrumentation)) { + ParseResult result = ParsedComponentUtils.parseComponent( + instrumentation, tag, pkg, sa, useRoundIcon, input, + R.styleable.AndroidManifestInstrumentation_banner, + null /*descriptionAttr*/, + R.styleable.AndroidManifestInstrumentation_icon, + R.styleable.AndroidManifestInstrumentation_label, + R.styleable.AndroidManifestInstrumentation_logo, + R.styleable.AndroidManifestInstrumentation_name, + R.styleable.AndroidManifestInstrumentation_roundIcon); + if (result.isError()) { + return result; + } + + // @formatter:off + // Note: don't allow this value to be a reference to a resource + // that may change. + instrumentation.setTargetPackage(sa.getNonResourceString(R.styleable.AndroidManifestInstrumentation_targetPackage)); + instrumentation.setTargetProcesses(sa.getNonResourceString(R.styleable.AndroidManifestInstrumentation_targetProcesses)); + instrumentation.handleProfiling = sa.getBoolean(R.styleable.AndroidManifestInstrumentation_handleProfiling, false); + instrumentation.functionalTest = sa.getBoolean(R.styleable.AndroidManifestInstrumentation_functionalTest, false); + // @formatter:on + } + + return ComponentParseUtils.parseAllMetaData(pkg, res, parser, tag, instrumentation, + input); + } +} diff --git a/core/java/android/content/pm/parsing/component/ParsedIntentInfo.java b/core/java/android/content/pm/parsing/component/ParsedIntentInfo.java new file mode 100644 index 0000000000000..0ba92cc4fef78 --- /dev/null +++ b/core/java/android/content/pm/parsing/component/ParsedIntentInfo.java @@ -0,0 +1,200 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content.pm.parsing.component; + +import android.annotation.Nullable; +import android.content.IntentFilter; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.Pair; + +import com.android.internal.util.DataClass; +import com.android.internal.util.Parcelling; + +import java.util.ArrayList; +import java.util.List; + +/** @hide **/ +public final class ParsedIntentInfo extends IntentFilter { + + public static final Parceler PARCELER = new Parceler(); + + public static class Parceler implements Parcelling { + + @Override + public void parcel(ParsedIntentInfo item, Parcel dest, int parcelFlags) { + item.writeIntentInfoToParcel(dest, parcelFlags); + } + + @Override + public ParsedIntentInfo unparcel(Parcel source) { + return new ParsedIntentInfo(source); + } + } + + public static class ListParceler implements Parcelling> { + + /** + *

+ * Implementation note: The serialized form for the intent list also contains the name + * of the concrete class that's stored in the list, and assumes that every element of the + * list is of the same type. This is very similar to the original parcelable mechanism. + * We cannot use that directly because IntentInfo extends IntentFilter, which is parcelable + * and is public API. It also declares Parcelable related methods as final which means + * we can't extend them. The approach of using composition instead of inheritance leads to + * a large set of cascading changes in the PackageManagerService, which seem undesirable. + * + *

+ * WARNING: The list of objects returned by this function might need to be fixed up + * to make sure their owner fields are consistent. See {@code fixupOwner}. + */ + @Override + public void parcel(List item, Parcel dest, int parcelFlags) { + if (item == null) { + dest.writeInt(-1); + return; + } + + final int size = item.size(); + dest.writeInt(size); + + for (int index = 0; index < size; index++) { + PARCELER.parcel(item.get(index), dest, parcelFlags); + } + } + + @Override + public List unparcel(Parcel source) { + int size = source.readInt(); + if (size == -1) { + return null; + } + + if (size == 0) { + return new ArrayList<>(0); + } + + final ArrayList intentsList = new ArrayList<>(size); + for (int i = 0; i < size; ++i) { + intentsList.add(PARCELER.unparcel(source)); + } + + return intentsList; + } + } + + public static class StringPairListParceler implements Parcelling>> { + + @Override + public void parcel(List> item, Parcel dest, + int parcelFlags) { + if (item == null) { + dest.writeInt(-1); + return; + } + + final int size = item.size(); + dest.writeInt(size); + + for (int index = 0; index < size; index++) { + Pair pair = item.get(index); + dest.writeString(pair.first); + PARCELER.parcel(pair.second, dest, parcelFlags); + } + } + + @Override + public List> unparcel(Parcel source) { + int size = source.readInt(); + if (size == -1) { + return null; + } + + if (size == 0) { + return new ArrayList<>(0); + } + + final List> list = new ArrayList<>(size); + for (int i = 0; i < size; ++i) { + list.add(Pair.create(source.readString(), PARCELER.unparcel(source))); + } + + return list; + } + } + + boolean hasDefault; + int labelRes; + @Nullable + CharSequence nonLocalizedLabel; + int icon; + + public ParsedIntentInfo() { + } + + public ParsedIntentInfo(Parcel in) { + super(in); + hasDefault = in.readBoolean(); + labelRes = in.readInt(); + nonLocalizedLabel = in.readCharSequence(); + icon = in.readInt(); + } + + public void writeIntentInfoToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeBoolean(hasDefault); + dest.writeInt(labelRes); + dest.writeCharSequence(nonLocalizedLabel); + dest.writeInt(icon); + } + + public String toString() { + return "ProviderIntentInfo{" + + Integer.toHexString(System.identityHashCode(this)) + + '}'; + } + + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + @Override + public ParsedIntentInfo createFromParcel(Parcel source) { + return new ParsedIntentInfo(source); + } + + @Override + public ParsedIntentInfo[] newArray(int size) { + return new ParsedIntentInfo[size]; + } + }; + + public boolean isHasDefault() { + return hasDefault; + } + + public int getLabelRes() { + return labelRes; + } + + @Nullable + public CharSequence getNonLocalizedLabel() { + return nonLocalizedLabel; + } + + public int getIcon() { + return icon; + } +} diff --git a/core/java/android/content/pm/parsing/component/ParsedIntentInfoUtils.java b/core/java/android/content/pm/parsing/component/ParsedIntentInfoUtils.java new file mode 100644 index 0000000000000..34475fca9a9a5 --- /dev/null +++ b/core/java/android/content/pm/parsing/component/ParsedIntentInfoUtils.java @@ -0,0 +1,242 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content.pm.parsing.component; + +import android.annotation.NonNull; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.PackageParser; +import android.content.pm.parsing.ParsingPackage; +import android.content.pm.parsing.ParsingPackageUtils; +import android.content.pm.parsing.ParsingUtils; +import android.content.pm.parsing.result.ParseInput; +import android.content.pm.parsing.result.ParseResult; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.content.res.XmlResourceParser; +import android.os.PatternMatcher; +import android.text.TextUtils; +import android.util.Slog; +import android.util.TypedValue; + +import com.android.internal.R; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; +import java.util.Iterator; + +/** @hide */ +public class ParsedIntentInfoUtils { + + private static final String TAG = ParsingPackageUtils.TAG; + + @NonNull + public static ParseResult parseIntentInfo(String className, + ParsingPackage pkg, Resources res, XmlResourceParser parser, boolean allowGlobs, + boolean allowAutoVerify, ParseInput input) + throws XmlPullParserException, IOException { + ParsedIntentInfo intentInfo = new ParsedIntentInfo(); + try (TypedArray sa = res.obtainAttributes(parser, + R.styleable.AndroidManifestIntentFilter)) { + + intentInfo.setPriority(sa.getInt(R.styleable.AndroidManifestIntentFilter_priority, 0)); + intentInfo.setOrder(sa.getInt(R.styleable.AndroidManifestIntentFilter_order, 0)); + + TypedValue v = sa.peekValue(R.styleable.AndroidManifestIntentFilter_label); + if (v != null) { + intentInfo.labelRes = v.resourceId; + if (v.resourceId == 0) { + intentInfo.nonLocalizedLabel = v.coerceToString(); + } + } + + if (PackageParser.sUseRoundIcon) { + intentInfo.icon = sa.getResourceId( + R.styleable.AndroidManifestIntentFilter_roundIcon, 0); + } + + if (intentInfo.icon == 0) { + intentInfo.icon = sa.getResourceId(R.styleable.AndroidManifestIntentFilter_icon, 0); + } + + if (allowAutoVerify) { + intentInfo.setAutoVerify(sa.getBoolean( + R.styleable.AndroidManifestIntentFilter_autoVerify, + 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 nodeName = parser.getName(); + switch (nodeName) { + case "action": { + String value = parser.getAttributeValue(PackageParser.ANDROID_RESOURCES, + "name"); + if (TextUtils.isEmpty(value)) { + result = input.error("No value supplied for "); + } else { + intentInfo.addAction(value); + result = input.success(null); + } + break; + } + case "category": { + String value = parser.getAttributeValue(PackageParser.ANDROID_RESOURCES, + "name"); + if (TextUtils.isEmpty(value)) { + result = input.error("No value supplied for "); + } else { + intentInfo.addCategory(value); + result = input.success(null); + } + break; + } + case "data": + result = parseData(intentInfo, res, parser, allowGlobs, input); + break; + default: + result = ParsingUtils.unknownTag("", pkg, parser, input); + break; + } + + if (result.isError()) { + return input.error(result); + } + } + + intentInfo.hasDefault = intentInfo.hasCategory(Intent.CATEGORY_DEFAULT); + + if (PackageParser.DEBUG_PARSER) { + final StringBuilder cats = new StringBuilder("Intent d="); + cats.append(intentInfo.isHasDefault()); + cats.append(", cat="); + + final Iterator it = intentInfo.categoriesIterator(); + if (it != null) { + while (it.hasNext()) { + cats.append(' '); + cats.append(it.next()); + } + } + Slog.d(TAG, cats.toString()); + } + + return input.success(intentInfo); + } + + @NonNull + private static ParseResult parseData( + ParsedIntentInfo intentInfo, + Resources resources, XmlResourceParser parser, boolean allowGlobs, ParseInput input) { + try (TypedArray sa = resources.obtainAttributes(parser, + R.styleable.AndroidManifestData)) { + + String str = sa.getNonConfigurationString( + R.styleable.AndroidManifestData_mimeType, 0); + if (str != null) { + try { + intentInfo.addDataType(str); + } catch (IntentFilter.MalformedMimeTypeException e) { + return input.error(e.toString()); + } + } + + str = sa.getNonConfigurationString( + R.styleable.AndroidManifestData_scheme, 0); + if (str != null) { + intentInfo.addDataScheme(str); + } + + str = sa.getNonConfigurationString( + R.styleable.AndroidManifestData_ssp, 0); + if (str != null) { + intentInfo.addDataSchemeSpecificPart(str, + PatternMatcher.PATTERN_LITERAL); + } + + str = sa.getNonConfigurationString( + R.styleable.AndroidManifestData_sspPrefix, 0); + if (str != null) { + intentInfo.addDataSchemeSpecificPart(str, + PatternMatcher.PATTERN_PREFIX); + } + + str = sa.getNonConfigurationString( + R.styleable.AndroidManifestData_sspPattern, 0); + if (str != null) { + if (!allowGlobs) { + return input.error( + "sspPattern not allowed here; ssp must be literal"); + } + intentInfo.addDataSchemeSpecificPart(str, + PatternMatcher.PATTERN_SIMPLE_GLOB); + } + + String host = sa.getNonConfigurationString( + R.styleable.AndroidManifestData_host, 0); + String port = sa.getNonConfigurationString( + R.styleable.AndroidManifestData_port, 0); + if (host != null) { + intentInfo.addDataAuthority(host, port); + } + + str = sa.getNonConfigurationString( + R.styleable.AndroidManifestData_path, 0); + if (str != null) { + intentInfo.addDataPath(str, PatternMatcher.PATTERN_LITERAL); + } + + str = sa.getNonConfigurationString( + R.styleable.AndroidManifestData_pathPrefix, 0); + if (str != null) { + intentInfo.addDataPath(str, PatternMatcher.PATTERN_PREFIX); + } + + str = sa.getNonConfigurationString( + R.styleable.AndroidManifestData_pathPattern, 0); + if (str != null) { + if (!allowGlobs) { + return input.error( + "pathPattern not allowed here; path must be literal"); + } + intentInfo.addDataPath(str, PatternMatcher.PATTERN_SIMPLE_GLOB); + } + + str = sa.getNonConfigurationString( + R.styleable.AndroidManifestData_pathAdvancedPattern, 0); + if (str != null) { + if (!allowGlobs) { + return input.error( + "pathAdvancedPattern not allowed here; path must be literal"); + } + intentInfo.addDataPath(str, PatternMatcher.PATTERN_ADVANCED_GLOB); + } + + return input.success(null); + } + } +} diff --git a/core/java/android/content/pm/parsing/component/ParsedMainComponent.java b/core/java/android/content/pm/parsing/component/ParsedMainComponent.java new file mode 100644 index 0000000000000..59e9a84e3ceb7 --- /dev/null +++ b/core/java/android/content/pm/parsing/component/ParsedMainComponent.java @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content.pm.parsing.component; + +import static android.content.pm.parsing.ParsingPackageImpl.sForString; + +import android.annotation.Nullable; +import android.os.Parcel; +import android.os.Parcelable; +import android.text.TextUtils; + +import com.android.internal.util.DataClass; +import com.android.internal.util.Parcelling; +import com.android.internal.util.Parcelling.BuiltIn.ForInternedString; + +/** @hide */ +public class ParsedMainComponent extends ParsedComponent { + + @Nullable + @DataClass.ParcelWith(ForInternedString.class) + private String processName; + boolean directBootAware; + boolean enabled = true; + boolean exported; + int order; + + @Nullable + String splitName; + + public ParsedMainComponent() { + } + + public ParsedMainComponent(ParsedMainComponent other) { + super(other); + this.processName = other.processName; + this.directBootAware = other.directBootAware; + this.enabled = other.enabled; + this.exported = other.exported; + this.order = other.order; + this.splitName = other.splitName; + } + + public ParsedMainComponent setProcessName(String processName) { + this.processName = TextUtils.safeIntern(processName); + return this; + } + + public ParsedMainComponent setEnabled(boolean enabled) { + this.enabled = enabled; + return this; + } + + /** + * A main component's name is a class name. This makes code slightly more readable. + */ + public String getClassName() { + return getName(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + sForString.parcel(this.processName, dest, flags); + dest.writeBoolean(this.directBootAware); + dest.writeBoolean(this.enabled); + dest.writeBoolean(this.exported); + dest.writeInt(this.order); + dest.writeString(this.splitName); + } + + protected ParsedMainComponent(Parcel in) { + super(in); + this.processName = sForString.unparcel(in); + this.directBootAware = in.readBoolean(); + this.enabled = in.readBoolean(); + this.exported = in.readBoolean(); + this.order = in.readInt(); + this.splitName = in.readString(); + } + + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + @Override + public ParsedMainComponent createFromParcel(Parcel source) { + return new ParsedMainComponent(source); + } + + @Override + public ParsedMainComponent[] newArray(int size) { + return new ParsedMainComponent[size]; + } + }; + + @Nullable + public String getProcessName() { + return processName; + } + + public boolean isDirectBootAware() { + return directBootAware; + } + + public boolean isEnabled() { + return enabled; + } + + public boolean isExported() { + return exported; + } + + public int getOrder() { + return order; + } + + @Nullable + public String getSplitName() { + return splitName; + } + + public ParsedMainComponent setDirectBootAware(boolean value) { + directBootAware = value; + return this; + } + + public ParsedMainComponent setExported(boolean value) { + exported = value; + return this; + } + + public ParsedMainComponent setSplitName(@Nullable String value) { + splitName = value; + return this; + } +} diff --git a/core/java/android/content/pm/parsing/component/ParsedMainComponentUtils.java b/core/java/android/content/pm/parsing/component/ParsedMainComponentUtils.java new file mode 100644 index 0000000000000..6188f8933ab2c --- /dev/null +++ b/core/java/android/content/pm/parsing/component/ParsedMainComponentUtils.java @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content.pm.parsing.component; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.IntentFilter; +import android.content.pm.parsing.ParsingPackage; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.content.res.XmlResourceParser; +import android.os.Build; +import android.util.Slog; + +import com.android.internal.annotations.VisibleForTesting; +import android.content.pm.parsing.ParsingPackageUtils; +import android.content.pm.parsing.result.ParseInput; +import android.content.pm.parsing.result.ParseResult; + +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; + +/** @hide */ +class ParsedMainComponentUtils { + + private static final String TAG = ParsingPackageUtils.TAG; + + @NonNull + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + static ParseResult parseMainComponent( + Component component, String tag, String[] separateProcesses, ParsingPackage pkg, + TypedArray array, int flags, boolean useRoundIcon, ParseInput input, + int bannerAttr, int descriptionAttr, @Nullable Integer directBootAwareAttr, + @Nullable Integer enabledAttr, int iconAttr, int labelAttr, int logoAttr, int nameAttr, + @Nullable Integer processAttr, int roundIconAttr, @Nullable Integer splitNameAttr) { + ParseResult result = ParsedComponentUtils.parseComponent(component, tag, pkg, + array, useRoundIcon, input, bannerAttr, descriptionAttr, iconAttr, labelAttr, + logoAttr, nameAttr, roundIconAttr); + if (result.isError()) { + return result; + } + + if (directBootAwareAttr != null) { + component.directBootAware = array.getBoolean(directBootAwareAttr, false); + if (component.isDirectBootAware()) { + pkg.setPartiallyDirectBootAware(true); + } + } + + if (enabledAttr != null) { + component.enabled = array.getBoolean(enabledAttr, true); + } + + if (processAttr != null) { + CharSequence processName; + if (pkg.getTargetSdkVersion() >= Build.VERSION_CODES.FROYO) { + processName = array.getNonConfigurationString(processAttr, + Configuration.NATIVE_CONFIG_VERSION); + } else { + // Some older apps have been seen to use a resource reference + // here that on older builds was ignored (with a warning). We + // need to continue to do this for them so they don't break. + processName = array.getNonResourceString(processAttr); + } + + // Backwards-compat, ignore error + ParseResult processNameResult = ComponentParseUtils.buildProcessName( + pkg.getPackageName(), pkg.getProcessName(), processName, flags, + separateProcesses, input); + if (processNameResult.isSuccess()) { + component.setProcessName(processNameResult.getResult()); + } else { + // Backwards-compat, ignore error + processNameResult.ignoreError(); + } + } + + if (splitNameAttr != null) { + component.splitName = array.getNonConfigurationString(splitNameAttr, 0); + } + + return input.success(component); + } + + static ParseResult parseIntentFilter( + ParsedMainComponent mainComponent, + ParsingPackage pkg, Resources resources, XmlResourceParser parser, + boolean visibleToEphemeral, boolean allowGlobs, boolean allowAutoVerify, + boolean allowImplicitEphemeralVisibility, boolean failOnNoActions, + ParseInput input) throws IOException, XmlPullParserException { + ParseResult intentResult = ParsedIntentInfoUtils.parseIntentInfo( + mainComponent.getName(), pkg, resources, parser, allowGlobs, + allowAutoVerify, input); + if (intentResult.isError()) { + return input.error(intentResult); + } + + ParsedIntentInfo intent = intentResult.getResult(); + int actionCount = intent.countActions(); + if (actionCount == 0 && failOnNoActions) { + Slog.w(TAG, "No actions in " + parser.getName() + " at " + pkg.getBaseCodePath() + " " + + parser.getPositionDescription()); + // Backward-compat, do not actually fail + return input.success(null); + } + + int intentVisibility; + if (visibleToEphemeral) { + intentVisibility = IntentFilter.VISIBILITY_EXPLICIT; + } else if (allowImplicitEphemeralVisibility + && ComponentParseUtils.isImplicitlyExposedIntent(intent)){ + intentVisibility = IntentFilter.VISIBILITY_IMPLICIT; + } else { + intentVisibility = IntentFilter.VISIBILITY_NONE; + } + intent.setVisibilityToInstantApp(intentVisibility); + + return input.success(intentResult.getResult()); + } + +} diff --git a/core/java/android/content/pm/parsing/component/ParsedPermission.java b/core/java/android/content/pm/parsing/component/ParsedPermission.java new file mode 100644 index 0000000000000..6c36ecb76846b --- /dev/null +++ b/core/java/android/content/pm/parsing/component/ParsedPermission.java @@ -0,0 +1,194 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content.pm.parsing.component; + +import static android.content.pm.parsing.ParsingPackageImpl.sForString; + +import android.annotation.Nullable; +import android.content.pm.PermissionInfo; +import android.os.Parcel; +import android.os.Parcelable; +import android.text.TextUtils; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.DataClass; +import com.android.internal.util.Parcelling; +import com.android.internal.util.Parcelling.BuiltIn.ForInternedString; + +/** @hide */ +public class ParsedPermission extends ParsedComponent { + + @Nullable + String backgroundPermission; + @Nullable + @DataClass.ParcelWith(ForInternedString.class) + private String group; + int requestRes; + int protectionLevel; + boolean tree; + @Nullable + private ParsedPermissionGroup parsedPermissionGroup; + + @VisibleForTesting + public ParsedPermission() { + } + + public ParsedPermission(ParsedPermission other) { + super(other); + this.backgroundPermission = other.backgroundPermission; + this.group = other.group; + this.requestRes = other.requestRes; + this.protectionLevel = other.protectionLevel; + this.tree = other.tree; + this.parsedPermissionGroup = other.parsedPermissionGroup; + } + + public ParsedPermission(ParsedPermission other, PermissionInfo pendingPermissionInfo, + String packageName, String name) { + this(other); + + this.flags = pendingPermissionInfo.flags; + this.descriptionRes = pendingPermissionInfo.descriptionRes; + + this.backgroundPermission = pendingPermissionInfo.backgroundPermission; + this.group = pendingPermissionInfo.group; + this.requestRes = pendingPermissionInfo.requestRes; + this.protectionLevel = pendingPermissionInfo.protectionLevel; + + setName(name); + setPackageName(packageName); + } + + public ParsedPermission setGroup(String group) { + this.group = TextUtils.safeIntern(group); + return this; + } + + public ParsedPermission setFlags(int flags) { + this.flags = flags; + return this; + } + + public boolean isRuntime() { + return getProtection() == PermissionInfo.PROTECTION_DANGEROUS; + } + + public boolean isAppOp() { + return (protectionLevel & PermissionInfo.PROTECTION_FLAG_APPOP) != 0; + } + + @PermissionInfo.Protection + public int getProtection() { + return protectionLevel & PermissionInfo.PROTECTION_MASK_BASE; + } + + public int getProtectionFlags() { + return protectionLevel & ~PermissionInfo.PROTECTION_MASK_BASE; + } + + public int calculateFootprint() { + int size = getName().length(); + if (getNonLocalizedLabel() != null) { + size += getNonLocalizedLabel().length(); + } + return size; + } + + public String toString() { + return "Permission{" + + Integer.toHexString(System.identityHashCode(this)) + + " " + getName() + "}"; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeString(this.backgroundPermission); + sForString.parcel(this.group, dest, flags); + dest.writeInt(this.requestRes); + dest.writeInt(this.protectionLevel); + dest.writeBoolean(this.tree); + dest.writeParcelable(this.parsedPermissionGroup, flags); + } + + protected ParsedPermission(Parcel in) { + super(in); + // We use the boot classloader for all classes that we load. + final ClassLoader boot = Object.class.getClassLoader(); + this.backgroundPermission = in.readString(); + this.group = sForString.unparcel(in); + this.requestRes = in.readInt(); + this.protectionLevel = in.readInt(); + this.tree = in.readBoolean(); + this.parsedPermissionGroup = in.readParcelable(boot); + } + + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + @Override + public ParsedPermission createFromParcel(Parcel source) { + return new ParsedPermission(source); + } + + @Override + public ParsedPermission[] newArray(int size) { + return new ParsedPermission[size]; + } + }; + + @Nullable + public String getBackgroundPermission() { + return backgroundPermission; + } + + @Nullable + public String getGroup() { + return group; + } + + public int getRequestRes() { + return requestRes; + } + + public int getProtectionLevel() { + return protectionLevel; + } + + public boolean isTree() { + return tree; + } + + @Nullable + public ParsedPermissionGroup getParsedPermissionGroup() { + return parsedPermissionGroup; + } + + public ParsedPermission setProtectionLevel(int value) { + protectionLevel = value; + return this; + } + + public ParsedPermission setParsedPermissionGroup(@Nullable ParsedPermissionGroup value) { + parsedPermissionGroup = value; + return this; + } +} diff --git a/core/java/android/content/pm/parsing/component/ParsedPermissionGroup.java b/core/java/android/content/pm/parsing/component/ParsedPermissionGroup.java new file mode 100644 index 0000000000000..741c00cbb7231 --- /dev/null +++ b/core/java/android/content/pm/parsing/component/ParsedPermissionGroup.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content.pm.parsing.component; + +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.internal.util.DataClass; + +/** @hide */ +public class ParsedPermissionGroup extends ParsedComponent { + + int requestDetailResourceId; + int backgroundRequestResourceId; + int backgroundRequestDetailResourceId; + int requestRes; + int priority; + + public void setPriority(int priority) { + this.priority = priority; + } + + public String toString() { + return "PermissionGroup{" + + Integer.toHexString(System.identityHashCode(this)) + + " " + getName() + "}"; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeInt(this.requestDetailResourceId); + dest.writeInt(this.backgroundRequestResourceId); + dest.writeInt(this.backgroundRequestDetailResourceId); + dest.writeInt(this.requestRes); + dest.writeInt(this.priority); + } + + public ParsedPermissionGroup() { + } + + protected ParsedPermissionGroup(Parcel in) { + super(in); + this.requestDetailResourceId = in.readInt(); + this.backgroundRequestResourceId = in.readInt(); + this.backgroundRequestDetailResourceId = in.readInt(); + this.requestRes = in.readInt(); + this.priority = in.readInt(); + } + + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + @Override + public ParsedPermissionGroup createFromParcel(Parcel source) { + return new ParsedPermissionGroup(source); + } + + @Override + public ParsedPermissionGroup[] newArray(int size) { + return new ParsedPermissionGroup[size]; + } + }; + + public int getRequestDetailResourceId() { + return requestDetailResourceId; + } + + public int getBackgroundRequestResourceId() { + return backgroundRequestResourceId; + } + + public int getBackgroundRequestDetailResourceId() { + return backgroundRequestDetailResourceId; + } + + public int getRequestRes() { + return requestRes; + } + + public int getPriority() { + return priority; + } +} diff --git a/core/java/android/content/pm/parsing/component/ParsedPermissionUtils.java b/core/java/android/content/pm/parsing/component/ParsedPermissionUtils.java new file mode 100644 index 0000000000000..7b38bee7e3a64 --- /dev/null +++ b/core/java/android/content/pm/parsing/component/ParsedPermissionUtils.java @@ -0,0 +1,205 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content.pm.parsing.component; + +import android.annotation.NonNull; +import android.content.pm.PermissionInfo; +import android.content.pm.parsing.ParsingPackage; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.content.res.XmlResourceParser; +import android.util.Slog; + +import com.android.internal.R; +import android.content.pm.parsing.ParsingPackageUtils; +import android.content.pm.parsing.result.ParseInput; +import android.content.pm.parsing.result.ParseResult; + +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; + +/** @hide */ +public class ParsedPermissionUtils { + + private static final String TAG = ParsingPackageUtils.TAG; + + @NonNull + public static ParseResult parsePermission(ParsingPackage pkg, Resources res, + XmlResourceParser parser, boolean useRoundIcon, ParseInput input) + throws IOException, XmlPullParserException { + String packageName = pkg.getPackageName(); + ParsedPermission + permission = new ParsedPermission(); + String tag = "<" + parser.getName() + ">"; + final ParseResult result; + + try (TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestPermission)) { + + result = ParsedComponentUtils.parseComponent( + permission, tag, pkg, sa, useRoundIcon, input, + R.styleable.AndroidManifestPermission_banner, + R.styleable.AndroidManifestPermission_description, + R.styleable.AndroidManifestPermission_icon, + R.styleable.AndroidManifestPermission_label, + R.styleable.AndroidManifestPermission_logo, + R.styleable.AndroidManifestPermission_name, + R.styleable.AndroidManifestPermission_roundIcon); + if (result.isError()) { + return result; + } + + if (sa.hasValue( + R.styleable.AndroidManifestPermission_backgroundPermission)) { + if ("android".equals(packageName)) { + permission.backgroundPermission = sa.getNonResourceString( + R.styleable + .AndroidManifestPermission_backgroundPermission); + } else { + Slog.w(TAG, packageName + " defines a background permission. Only the " + + "'android' package can do that."); + } + } + + // Note: don't allow this value to be a reference to a resource + // that may change. + permission.setGroup(sa.getNonResourceString( + R.styleable.AndroidManifestPermission_permissionGroup)); + + permission.requestRes = sa.getResourceId( + R.styleable.AndroidManifestPermission_request, 0); + + permission.protectionLevel = sa.getInt( + R.styleable.AndroidManifestPermission_protectionLevel, + PermissionInfo.PROTECTION_NORMAL); + + permission.flags = sa.getInt( + R.styleable.AndroidManifestPermission_permissionFlags, 0); + + // For now only platform runtime permissions can be restricted + if (!permission.isRuntime() || !"android".equals(permission.getPackageName())) { + permission.flags &= ~PermissionInfo.FLAG_HARD_RESTRICTED; + permission.flags &= ~PermissionInfo.FLAG_SOFT_RESTRICTED; + } else { + // The platform does not get to specify conflicting permissions + if ((permission.flags & PermissionInfo.FLAG_HARD_RESTRICTED) != 0 + && (permission.flags & PermissionInfo.FLAG_SOFT_RESTRICTED) != 0) { + throw new IllegalStateException("Permission cannot be both soft and hard" + + " restricted: " + permission.getName()); + } + } + } + + // TODO(b/135203078): This is impossible because of default value in above getInt + if (permission.protectionLevel == -1) { + return input.error(" does not specify protectionLevel"); + } + + permission.protectionLevel = PermissionInfo.fixProtectionLevel(permission.protectionLevel); + + if (permission.getProtectionFlags() != 0) { + if ((permission.protectionLevel & PermissionInfo.PROTECTION_FLAG_INSTANT) == 0 + && (permission.protectionLevel & PermissionInfo.PROTECTION_FLAG_RUNTIME_ONLY) + == 0 + && (permission.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE) != + PermissionInfo.PROTECTION_SIGNATURE) { + return input.error(" protectionLevel specifies a non-instant flag " + + "but is not based on signature type"); + } + } + + return ComponentParseUtils.parseAllMetaData(pkg, res, parser, tag, permission, input); + } + + @NonNull + public static ParseResult parsePermissionTree(ParsingPackage pkg, Resources res, + XmlResourceParser parser, boolean useRoundIcon, ParseInput input) + throws IOException, XmlPullParserException { + ParsedPermission permission = new ParsedPermission(); + String tag = "<" + parser.getName() + ">"; + final ParseResult result; + + try (TypedArray sa = res.obtainAttributes(parser, + R.styleable.AndroidManifestPermissionTree)) { + + result = ParsedComponentUtils.parseComponent( + permission, tag, pkg, sa, useRoundIcon, input, + R.styleable.AndroidManifestPermissionTree_banner, + null /*descriptionAttr*/, + R.styleable.AndroidManifestPermissionTree_icon, + R.styleable.AndroidManifestPermissionTree_label, + R.styleable.AndroidManifestPermissionTree_logo, + R.styleable.AndroidManifestPermissionTree_name, + R.styleable.AndroidManifestPermissionTree_roundIcon); + if (result.isError()) { + return result; + } + } + + int index = permission.getName().indexOf('.'); + if (index > 0) { + index = permission.getName().indexOf('.', index + 1); + } + if (index < 0) { + return input.error(" name has less than three segments: " + + permission.getName()); + } + + permission.protectionLevel = PermissionInfo.PROTECTION_NORMAL; + permission.tree = true; + + return ComponentParseUtils.parseAllMetaData(pkg, res, parser, tag, permission, + input); + } + + @NonNull + public static ParseResult parsePermissionGroup(ParsingPackage pkg, + Resources res, XmlResourceParser parser, boolean useRoundIcon, ParseInput input) + throws IOException, XmlPullParserException { + ParsedPermissionGroup + permissionGroup = new ParsedPermissionGroup(); + String tag = "<" + parser.getName() + ">"; + + try (TypedArray sa = res.obtainAttributes(parser, + R.styleable.AndroidManifestPermissionGroup)) { + ParseResult result = ParsedComponentUtils.parseComponent( + permissionGroup, tag, pkg, sa, useRoundIcon, input, + R.styleable.AndroidManifestPermissionGroup_banner, + R.styleable.AndroidManifestPermissionGroup_description, + R.styleable.AndroidManifestPermissionGroup_icon, + R.styleable.AndroidManifestPermissionGroup_label, + R.styleable.AndroidManifestPermissionGroup_logo, + R.styleable.AndroidManifestPermissionGroup_name, + R.styleable.AndroidManifestPermissionGroup_roundIcon); + if (result.isError()) { + return result; + } + + // @formatter:off + permissionGroup.requestDetailResourceId = sa.getResourceId(R.styleable.AndroidManifestPermissionGroup_requestDetail, 0); + permissionGroup.backgroundRequestResourceId = sa.getResourceId(R.styleable.AndroidManifestPermissionGroup_backgroundRequest, 0); + permissionGroup.backgroundRequestDetailResourceId = sa.getResourceId(R.styleable.AndroidManifestPermissionGroup_backgroundRequestDetail, 0); + permissionGroup.requestRes = sa.getResourceId(R.styleable.AndroidManifestPermissionGroup_request, 0); + permissionGroup.flags = sa.getInt(R.styleable.AndroidManifestPermissionGroup_permissionGroupFlags,0); + permissionGroup.priority = sa.getInt(R.styleable.AndroidManifestPermissionGroup_priority, 0); + // @formatter:on + } + + return ComponentParseUtils.parseAllMetaData(pkg, res, parser, tag, permissionGroup, + input); + } +} diff --git a/core/java/android/content/pm/parsing/component/ParsedProcess.java b/core/java/android/content/pm/parsing/component/ParsedProcess.java new file mode 100644 index 0000000000000..da7bf984aa7f0 --- /dev/null +++ b/core/java/android/content/pm/parsing/component/ParsedProcess.java @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content.pm.parsing.component; + +import static java.util.Collections.emptySet; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.ArraySet; + +import com.android.internal.util.CollectionUtils; +import com.android.internal.util.DataClass; +import com.android.internal.util.Parcelling; + +import java.util.Set; + +/** @hide */ +@DataClass(genGetters = true, genSetters = false, genParcelable = true, genAidl = false, + genBuilder = false) +public class ParsedProcess implements Parcelable { + + @NonNull + protected String name; + @NonNull + @DataClass.ParcelWith(Parcelling.BuiltIn.ForInternedStringSet.class) + protected Set deniedPermissions = emptySet(); + + public ParsedProcess() { + } + + public ParsedProcess(@NonNull ParsedProcess other) { + name = other.name; + deniedPermissions = new ArraySet<>(other.deniedPermissions); + } + + public void addStateFrom(@NonNull ParsedProcess other) { + deniedPermissions = CollectionUtils.addAll(deniedPermissions, other.deniedPermissions); + } + + + + // Code below generated by codegen v1.0.14. + // + // DO NOT MODIFY! + // CHECKSTYLE:OFF Generated code + // + // To regenerate run: + // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/parsing/component/ParsedProcess.java + // + // To exclude the generated code from IntelliJ auto-formatting enable (one-time): + // Settings > Editor > Code Style > Formatter Control + //@formatter:off + + + @DataClass.Generated.Member + public ParsedProcess( + @NonNull String name, + @NonNull Set deniedPermissions) { + this.name = name; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, name); + this.deniedPermissions = deniedPermissions; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, deniedPermissions); + + // onConstructed(); // You can define this method to get a callback + } + + @DataClass.Generated.Member + public @NonNull String getName() { + return name; + } + + @DataClass.Generated.Member + public @NonNull Set getDeniedPermissions() { + return deniedPermissions; + } + + @DataClass.Generated.Member + static Parcelling> sParcellingForDeniedPermissions = + Parcelling.Cache.get( + Parcelling.BuiltIn.ForInternedStringSet.class); + static { + if (sParcellingForDeniedPermissions == null) { + sParcellingForDeniedPermissions = Parcelling.Cache.put( + new Parcelling.BuiltIn.ForInternedStringSet()); + } + } + + @Override + @DataClass.Generated.Member + public void writeToParcel(@NonNull Parcel dest, int flags) { + // You can override field parcelling by defining methods like: + // void parcelFieldName(Parcel dest, int flags) { ... } + + dest.writeString(name); + sParcellingForDeniedPermissions.parcel(deniedPermissions, dest, flags); + } + + @Override + @DataClass.Generated.Member + public int describeContents() { return 0; } + + /** @hide */ + @SuppressWarnings({"unchecked", "RedundantCast"}) + @DataClass.Generated.Member + protected ParsedProcess(@NonNull Parcel in) { + // You can override field unparcelling by defining methods like: + // static FieldType unparcelFieldName(Parcel in) { ... } + + String _name = in.readString(); + Set _deniedPermissions = sParcellingForDeniedPermissions.unparcel(in); + + this.name = _name; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, name); + this.deniedPermissions = _deniedPermissions; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, deniedPermissions); + + // onConstructed(); // You can define this method to get a callback + } + + @DataClass.Generated.Member + public static final @NonNull Parcelable.Creator CREATOR + = new Parcelable.Creator() { + @Override + public ParsedProcess[] newArray(int size) { + return new ParsedProcess[size]; + } + + @Override + public ParsedProcess createFromParcel(@NonNull Parcel in) { + return new ParsedProcess(in); + } + }; + + @DataClass.Generated( + time = 1581452315946L, + codegenVersion = "1.0.14", + sourceFile = "frameworks/base/core/java/android/content/pm/parsing/component/ParsedProcess.java", + inputSignatures = "protected @android.annotation.NonNull java.lang.String name\nprotected @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedStringSet.class) java.util.Set deniedPermissions\npublic void addStateFrom(android.content.pm.parsing.component.ParsedProcess)\nclass ParsedProcess extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=false, genParcelable=true, genAidl=false, genBuilder=false)") + @Deprecated + private void __metadata() {} + + + //@formatter:on + // End of generated code + +} diff --git a/core/java/android/content/pm/parsing/component/ParsedProcessUtils.java b/core/java/android/content/pm/parsing/component/ParsedProcessUtils.java new file mode 100644 index 0000000000000..2973e497d65ab --- /dev/null +++ b/core/java/android/content/pm/parsing/component/ParsedProcessUtils.java @@ -0,0 +1,214 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content.pm.parsing.component; + +import android.annotation.NonNull; +import android.content.pm.parsing.ParsingPackage; +import android.content.pm.parsing.ParsingUtils; +import android.content.pm.parsing.result.ParseInput; +import android.content.pm.parsing.result.ParseResult; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.content.res.XmlResourceParser; +import android.util.ArrayMap; +import android.util.ArraySet; + +import com.android.internal.R; +import com.android.internal.util.CollectionUtils; +import com.android.internal.util.XmlUtils; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; +import java.util.Set; + +/** @hide */ +public class ParsedProcessUtils { + + private static final String TAG = ParsingUtils.TAG; + + @NonNull + private static ParseResult> parseDenyPermission(Set perms, + Resources res, XmlResourceParser parser, ParseInput input) + throws IOException, XmlPullParserException { + try (TypedArray sa = res.obtainAttributes(parser, + R.styleable.AndroidManifestDenyPermission)) { + String perm = sa.getNonConfigurationString( + R.styleable.AndroidManifestDenyPermission_name, 0); + if (perm != null && perm.equals(android.Manifest.permission.INTERNET)) { + perms = CollectionUtils.add(perms, perm); + } + } + XmlUtils.skipCurrentTag(parser); + return input.success(perms); + } + + @NonNull + private static ParseResult> parseAllowPermission(Set perms, Resources res, + XmlResourceParser parser, ParseInput input) + throws IOException, XmlPullParserException { + try (TypedArray sa = res.obtainAttributes(parser, + R.styleable.AndroidManifestAllowPermission)) { + String perm = sa.getNonConfigurationString( + R.styleable.AndroidManifestAllowPermission_name, 0); + if (perm != null && perm.equals(android.Manifest.permission.INTERNET)) { + perms = CollectionUtils.remove(perms, perm); + } + } + XmlUtils.skipCurrentTag(parser); + return input.success(perms); + } + + @NonNull + private static ParseResult parseProcess(Set perms, String[] separateProcesses, + ParsingPackage parsingPackage, Resources res, XmlResourceParser parser, int flags, + ParseInput input) throws IOException, XmlPullParserException { + ParsedProcess proc = new ParsedProcess(); + try (TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestProcess)) { + if (perms != null) { + proc.deniedPermissions = new ArraySet<>(perms); + } + + proc.name = sa.getNonConfigurationString( + R.styleable.AndroidManifestProcess_process, 0); + ParseResult processNameResult = ComponentParseUtils.buildProcessName( + parsingPackage.getPackageName(), null, proc.name, flags, separateProcesses, + input); + if (processNameResult.isError()) { + return input.error(processNameResult); + } + + proc.name = processNameResult.getResult(); + + if (proc.name == null || proc.name.length() <= 0) { + return input.error(" does not specify android:process"); + } + + processNameResult = ComponentParseUtils.buildProcessName(parsingPackage.getPackageName(), + parsingPackage.getPackageName(), proc.name, + flags, separateProcesses, input); + if (processNameResult.isError()) { + return input.error(processNameResult); + } + + proc.name = processNameResult.getResult(); + } + + int type; + final int innerDepth = parser.getDepth(); + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT + && (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) { + if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { + continue; + } + + ParseResult result; + + String tagName = parser.getName(); + switch (tagName) { + case "deny-permission": + ParseResult> denyResult = parseDenyPermission( + proc.deniedPermissions, res, parser, input); + result = denyResult; + if (denyResult.isSuccess()) { + proc.deniedPermissions = denyResult.getResult(); + } + break; + case "allow-permission": + ParseResult> allowResult = parseAllowPermission( + proc.deniedPermissions, res, parser, input); + result = allowResult; + if (allowResult.isSuccess()) { + proc.deniedPermissions = allowResult.getResult(); + } + break; + default: + result = ParsingUtils.unknownTag("", parsingPackage, parser, input); + break; + } + + if (result.isError()) { + return input.error(result); + } + } + + return input.success(proc); + } + + @NonNull + public static ParseResult> parseProcesses( + String[] separateProcesses, ParsingPackage parsingPackage, Resources res, + XmlResourceParser parser, int flags, ParseInput input) + throws IOException, XmlPullParserException { + Set deniedPerms = null; + ArrayMap processes = new ArrayMap<>(); + + int type; + final int innerDepth = parser.getDepth(); + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT + && (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) { + if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { + continue; + } + + ParseResult result; + + String tagName = parser.getName(); + switch (tagName) { + case "deny-permission": + ParseResult> denyResult = parseDenyPermission(deniedPerms, res, + parser, input); + result = denyResult; + if (denyResult.isSuccess()) { + deniedPerms = denyResult.getResult(); + } + break; + case "allow-permission": + ParseResult> allowResult = parseAllowPermission(deniedPerms, res, + parser, input); + result = allowResult; + if (allowResult.isSuccess()) { + deniedPerms = allowResult.getResult(); + } + break; + case "process": + ParseResult processResult = parseProcess(deniedPerms, + separateProcesses, parsingPackage, res, parser, flags, input); + result = processResult; + if (processResult.isSuccess()) { + ParsedProcess process = processResult.getResult(); + if (processes.put(process.name, process) != null) { + result = input.error( + " specified existing name '" + process.name + "'"); + } + } + break; + default: + result = ParsingUtils.unknownTag("", parsingPackage, parser, input); + break; + } + + if (result.isError()) { + return input.error(result); + } + + } + + return input.success(processes); + } +} diff --git a/core/java/android/content/pm/parsing/component/ParsedProvider.java b/core/java/android/content/pm/parsing/component/ParsedProvider.java new file mode 100644 index 0000000000000..d2c531dbe5d92 --- /dev/null +++ b/core/java/android/content/pm/parsing/component/ParsedProvider.java @@ -0,0 +1,195 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content.pm.parsing.component; + +import static android.content.pm.parsing.ParsingPackageImpl.sForString; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.ComponentName; +import android.content.pm.PathPermission; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.PatternMatcher; +import android.text.TextUtils; + +import com.android.internal.util.DataClass; +import com.android.internal.util.Parcelling; +import com.android.internal.util.Parcelling.BuiltIn.ForInternedString; + +/** @hide **/ +public class ParsedProvider extends ParsedMainComponent { + + @NonNull + @DataClass.ParcelWith(ForInternedString.class) + private String authority; + boolean syncable; + @Nullable + @DataClass.ParcelWith(ForInternedString.class) + private String readPermission; + @Nullable + @DataClass.ParcelWith(ForInternedString.class) + private String writePermission; + boolean grantUriPermissions; + boolean forceUriPermissions; + boolean multiProcess; + int initOrder; + @Nullable + PatternMatcher[] uriPermissionPatterns; + @Nullable + PathPermission[] pathPermissions; + + public ParsedProvider(ParsedProvider other) { + super(other); + + this.authority = other.authority; + this.syncable = other.syncable; + this.readPermission = other.readPermission; + this.writePermission = other.writePermission; + this.grantUriPermissions = other.grantUriPermissions; + this.forceUriPermissions = other.forceUriPermissions; + this.multiProcess = other.multiProcess; + this.initOrder = other.initOrder; + this.uriPermissionPatterns = other.uriPermissionPatterns; + this.pathPermissions = other.pathPermissions; + } + + public void setAuthority(String authority) { + this.authority = TextUtils.safeIntern(authority); + } + + public void setSyncable(boolean syncable) { + this.syncable = syncable; + } + + public void setReadPermission(String readPermission) { + // Empty string must be converted to null + this.readPermission = TextUtils.isEmpty(readPermission) + ? null : readPermission.intern(); + } + + public void setWritePermission(String writePermission) { + // Empty string must be converted to null + this.writePermission = TextUtils.isEmpty(writePermission) + ? null : writePermission.intern(); + } + + public String toString() { + StringBuilder sb = new StringBuilder(128); + sb.append("Provider{"); + sb.append(Integer.toHexString(System.identityHashCode(this))); + sb.append(' '); + ComponentName.appendShortString(sb, getPackageName(), getName()); + sb.append('}'); + return sb.toString(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + sForString.parcel(this.authority, dest, flags); + dest.writeBoolean(this.syncable); + sForString.parcel(this.readPermission, dest, flags); + sForString.parcel(this.writePermission, dest, flags); + dest.writeBoolean(this.grantUriPermissions); + dest.writeBoolean(this.forceUriPermissions); + dest.writeBoolean(this.multiProcess); + dest.writeInt(this.initOrder); + dest.writeTypedArray(this.uriPermissionPatterns, flags); + dest.writeTypedArray(this.pathPermissions, flags); + } + + public ParsedProvider() { + } + + protected ParsedProvider(Parcel in) { + super(in); + //noinspection ConstantConditions + this.authority = sForString.unparcel(in); + this.syncable = in.readBoolean(); + this.readPermission = sForString.unparcel(in); + this.writePermission = sForString.unparcel(in); + this.grantUriPermissions = in.readBoolean(); + this.forceUriPermissions = in.readBoolean(); + this.multiProcess = in.readBoolean(); + this.initOrder = in.readInt(); + this.uriPermissionPatterns = in.createTypedArray(PatternMatcher.CREATOR); + this.pathPermissions = in.createTypedArray(PathPermission.CREATOR); + } + + public static final Parcelable.Creator CREATOR = new Creator() { + @Override + public ParsedProvider createFromParcel(Parcel source) { + return new ParsedProvider(source); + } + + @Override + public ParsedProvider[] newArray(int size) { + return new ParsedProvider[size]; + } + }; + + @NonNull + public String getAuthority() { + return authority; + } + + public boolean isSyncable() { + return syncable; + } + + @Nullable + public String getReadPermission() { + return readPermission; + } + + @Nullable + public String getWritePermission() { + return writePermission; + } + + public boolean isGrantUriPermissions() { + return grantUriPermissions; + } + + public boolean isForceUriPermissions() { + return forceUriPermissions; + } + + public boolean isMultiProcess() { + return multiProcess; + } + + public int getInitOrder() { + return initOrder; + } + + @Nullable + public PatternMatcher[] getUriPermissionPatterns() { + return uriPermissionPatterns; + } + + @Nullable + public PathPermission[] getPathPermissions() { + return pathPermissions; + } +} diff --git a/core/java/android/content/pm/parsing/component/ParsedProviderUtils.java b/core/java/android/content/pm/parsing/component/ParsedProviderUtils.java new file mode 100644 index 0000000000000..858f45749fc2d --- /dev/null +++ b/core/java/android/content/pm/parsing/component/ParsedProviderUtils.java @@ -0,0 +1,346 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content.pm.parsing.component; + +import static android.content.pm.parsing.component.ComponentParseUtils.flag; + +import android.annotation.NonNull; +import android.content.pm.PackageParser; +import android.content.pm.PathPermission; +import android.content.pm.ProviderInfo; +import android.content.pm.parsing.ParsingPackage; +import android.content.pm.parsing.ParsingPackageUtils; +import android.content.pm.parsing.ParsingUtils; +import android.content.pm.parsing.result.ParseInput; +import android.content.pm.parsing.result.ParseResult; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.content.res.XmlResourceParser; +import android.os.Build; +import android.os.PatternMatcher; +import android.util.Slog; + +import com.android.internal.R; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; +import java.util.Objects; + +/** @hide */ +public class ParsedProviderUtils { + + private static final String TAG = ParsingPackageUtils.TAG; + + @NonNull + public static ParseResult parseProvider(String[] separateProcesses, + ParsingPackage pkg, Resources res, XmlResourceParser parser, int flags, + boolean useRoundIcon, ParseInput input) + throws IOException, XmlPullParserException { + String authority; + boolean visibleToEphemeral; + + final int targetSdkVersion = pkg.getTargetSdkVersion(); + final String packageName = pkg.getPackageName(); + final ParsedProvider provider = new ParsedProvider(); + final String tag = parser.getName(); + + try (TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestProvider)) { + ParseResult result = + ParsedMainComponentUtils.parseMainComponent(provider, tag, separateProcesses, + pkg, sa, flags, useRoundIcon, input, + R.styleable.AndroidManifestProvider_banner, + R.styleable.AndroidManifestProvider_description, + R.styleable.AndroidManifestProvider_directBootAware, + R.styleable.AndroidManifestProvider_enabled, + R.styleable.AndroidManifestProvider_icon, + R.styleable.AndroidManifestProvider_label, + R.styleable.AndroidManifestProvider_logo, + R.styleable.AndroidManifestProvider_name, + R.styleable.AndroidManifestProvider_process, + R.styleable.AndroidManifestProvider_roundIcon, + R.styleable.AndroidManifestProvider_splitName); + if (result.isError()) { + return result; + } + + authority = sa.getNonConfigurationString(R.styleable.AndroidManifestProvider_authorities, 0); + + // For compatibility, applications targeting API level 16 or lower + // should have their content providers exported by default, unless they + // specify otherwise. + provider.exported = sa.getBoolean(R.styleable.AndroidManifestProvider_exported, + targetSdkVersion < Build.VERSION_CODES.JELLY_BEAN_MR1); + + provider.syncable = sa.getBoolean(R.styleable.AndroidManifestProvider_syncable, false); + + String permission = sa.getNonConfigurationString( + R.styleable.AndroidManifestProvider_permission, 0); + String readPermission = sa.getNonConfigurationString( + R.styleable.AndroidManifestProvider_readPermission, 0); + if (readPermission == null) { + readPermission = permission; + } + if (readPermission == null) { + provider.setReadPermission(pkg.getPermission()); + } else { + provider.setReadPermission(readPermission); + } + String writePermission = sa.getNonConfigurationString( + R.styleable.AndroidManifestProvider_writePermission, 0); + if (writePermission == null) { + writePermission = permission; + } + if (writePermission == null) { + provider.setWritePermission(pkg.getPermission()); + } else { + provider.setWritePermission(writePermission); + } + + provider.grantUriPermissions = sa.getBoolean(R.styleable.AndroidManifestProvider_grantUriPermissions, false); + provider.forceUriPermissions = sa.getBoolean(R.styleable.AndroidManifestProvider_forceUriPermissions, false); + provider.multiProcess = sa.getBoolean(R.styleable.AndroidManifestProvider_multiprocess, false); + provider.initOrder = sa.getInt(R.styleable.AndroidManifestProvider_initOrder, 0); + + provider.flags |= flag(ProviderInfo.FLAG_SINGLE_USER, R.styleable.AndroidManifestProvider_singleUser, sa); + + visibleToEphemeral = sa.getBoolean(R.styleable.AndroidManifestProvider_visibleToInstantApps, false); + if (visibleToEphemeral) { + provider.flags |= ProviderInfo.FLAG_VISIBLE_TO_INSTANT_APP; + pkg.setVisibleToInstantApps(true); + } + } + + if (pkg.isCantSaveState()) { + // A heavy-weight application can not have providers in its main process + if (Objects.equals(provider.getProcessName(), packageName)) { + return input.error("Heavy-weight applications can not have providers" + + " in main process"); + } + } + + if (authority == null) { + return input.error(" does not include authorities attribute"); + } + if (authority.length() <= 0) { + return input.error(" has empty authorities attribute"); + } + provider.setAuthority(authority); + + return parseProviderTags(pkg, tag, res, parser, visibleToEphemeral, provider, input); + } + + @NonNull + private static ParseResult parseProviderTags(ParsingPackage pkg, String tag, + Resources res, XmlResourceParser parser, boolean visibleToEphemeral, + ParsedProvider provider, ParseInput input) + throws XmlPullParserException, IOException { + 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; + } + + String name = parser.getName(); + final ParseResult result; + switch (name) { + case "intent-filter": + ParseResult intentResult = ParsedMainComponentUtils + .parseIntentFilter(provider, pkg, res, parser, visibleToEphemeral, + true /*allowGlobs*/, false /*allowAutoVerify*/, + false /*allowImplicitEphemeralVisibility*/, + false /*failOnNoActions*/, input); + result = intentResult; + if (intentResult.isSuccess()) { + ParsedIntentInfo intent = intentResult.getResult(); + provider.order = Math.max(intent.getOrder(), provider.order); + provider.addIntent(intent); + } + break; + case "meta-data": + result = ParsedComponentUtils.addMetaData(provider, pkg, res, parser, input); + break; + case "grant-uri-permission": { + result = parseGrantUriPermission(provider, pkg, res, parser, input); + break; + } + case "path-permission": { + result = parsePathPermission(provider, pkg, res, parser, input); + break; + } + default: + result = ParsingUtils.unknownTag(tag, pkg, parser, input); + break; + } + + if (result.isError()) { + return input.error(result); + } + } + + return input.success(provider); + } + + @NonNull + private static ParseResult parseGrantUriPermission(ParsedProvider provider, + ParsingPackage pkg, Resources resources, XmlResourceParser parser, ParseInput input) { + try (TypedArray sa = resources.obtainAttributes(parser, + R.styleable.AndroidManifestGrantUriPermission)) { + String name = parser.getName(); + // Pattern has priority over prefix over literal path + PatternMatcher pa = null; + String str = sa.getNonConfigurationString( + R.styleable.AndroidManifestGrantUriPermission_pathPattern, 0); + if (str != null) { + pa = new PatternMatcher(str, PatternMatcher.PATTERN_SIMPLE_GLOB); + } else { + str = sa.getNonConfigurationString( + R.styleable.AndroidManifestGrantUriPermission_pathPrefix, 0); + if (str != null) { + pa = new PatternMatcher(str, PatternMatcher.PATTERN_PREFIX); + } else { + str = sa.getNonConfigurationString( + R.styleable.AndroidManifestGrantUriPermission_path, 0); + if (str != null) { + pa = new PatternMatcher(str, PatternMatcher.PATTERN_LITERAL); + } + } + } + + if (pa != null) { + if (provider.uriPermissionPatterns == null) { + provider.uriPermissionPatterns = new PatternMatcher[1]; + provider.uriPermissionPatterns[0] = pa; + } else { + final int N = provider.uriPermissionPatterns.length; + PatternMatcher[] newp = new PatternMatcher[N + 1]; + System.arraycopy(provider.uriPermissionPatterns, 0, newp, 0, N); + newp[N] = pa; + provider.uriPermissionPatterns = newp; + } + provider.grantUriPermissions = true; + } else { + if (PackageParser.RIGID_PARSER) { + return input.error("No path, pathPrefix, or pathPattern for "); + } + + Slog.w(TAG, "Unknown element under : " + name + " at " + + pkg.getBaseCodePath() + " " + parser.getPositionDescription()); + } + + return input.success(provider); + } + } + + @NonNull + private static ParseResult parsePathPermission(ParsedProvider provider, + ParsingPackage pkg, Resources resources, XmlResourceParser parser, ParseInput input) { + try (TypedArray sa = resources.obtainAttributes(parser, + R.styleable.AndroidManifestPathPermission)) { + String name = parser.getName(); + + String permission = sa.getNonConfigurationString( + R.styleable.AndroidManifestPathPermission_permission, 0); + String readPermission = sa.getNonConfigurationString( + R.styleable.AndroidManifestPathPermission_readPermission, 0); + if (readPermission == null) { + readPermission = permission; + } + String writePermission = sa.getNonConfigurationString( + R.styleable.AndroidManifestPathPermission_writePermission, 0); + if (writePermission == null) { + writePermission = permission; + } + + boolean havePerm = false; + if (readPermission != null) { + readPermission = readPermission.intern(); + havePerm = true; + } + if (writePermission != null) { + writePermission = writePermission.intern(); + havePerm = true; + } + + if (!havePerm) { + if (PackageParser.RIGID_PARSER) { + return input.error( + "No readPermission or writePermission for "); + } + Slog.w(TAG, "No readPermission or writePermission for : " + + name + " at " + pkg.getBaseCodePath() + " " + parser.getPositionDescription()); + return input.success(provider); + } + + // Advanced has priority over simply over prefix over literal + PathPermission pa = null; + String path = sa.getNonConfigurationString(R.styleable.AndroidManifestPathPermission_pathAdvancedPattern, 0); + if (path != null) { + pa = new PathPermission(path, PatternMatcher.PATTERN_ADVANCED_GLOB, readPermission, + writePermission); + } else { + path = sa.getNonConfigurationString(R.styleable.AndroidManifestPathPermission_pathPattern, 0); + if (path != null) { + pa = new PathPermission(path, PatternMatcher.PATTERN_SIMPLE_GLOB, + readPermission, writePermission); + } else { + path = sa.getNonConfigurationString( + R.styleable.AndroidManifestPathPermission_pathPrefix, 0); + if (path != null) { + pa = new PathPermission(path, PatternMatcher.PATTERN_PREFIX, readPermission, + writePermission); + } else { + path = sa.getNonConfigurationString(R.styleable.AndroidManifestPathPermission_path, 0); + if (path != null) { + pa = new PathPermission(path, PatternMatcher.PATTERN_LITERAL, + readPermission, writePermission); + } + } + } + } + + if (pa != null) { + if (provider.pathPermissions == null) { + provider.pathPermissions = new PathPermission[1]; + provider.pathPermissions[0] = pa; + } else { + final int N = provider.pathPermissions.length; + PathPermission[] newp = new PathPermission[N + 1]; + System.arraycopy(provider.pathPermissions, 0, newp, 0, N); + newp[N] = pa; + provider.pathPermissions = newp; + } + } else { + if (PackageParser.RIGID_PARSER) { + return input.error( + "No path, pathPrefix, or pathPattern for "); + } + + Slog.w(TAG, "No path, pathPrefix, or pathPattern for : " + + name + " at " + pkg.getBaseCodePath() + + " " + + parser.getPositionDescription()); + } + + return input.success(provider); + } + } +} diff --git a/core/java/android/content/pm/parsing/component/ParsedService.java b/core/java/android/content/pm/parsing/component/ParsedService.java new file mode 100644 index 0000000000000..591eef74453aa --- /dev/null +++ b/core/java/android/content/pm/parsing/component/ParsedService.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content.pm.parsing.component; + +import static android.content.pm.parsing.ParsingPackageImpl.sForString; + +import android.annotation.Nullable; +import android.content.ComponentName; +import android.os.Parcel; +import android.os.Parcelable; +import android.text.TextUtils; + +import com.android.internal.util.DataClass; +import com.android.internal.util.Parcelling; +import com.android.internal.util.Parcelling.BuiltIn.ForInternedString; + +/** @hide **/ +public class ParsedService extends ParsedMainComponent { + + int foregroundServiceType; + @Nullable + @DataClass.ParcelWith(ForInternedString.class) + private String permission; + + public ParsedService(ParsedService other) { + super(other); + this.foregroundServiceType = other.foregroundServiceType; + this.permission = other.permission; + } + + public ParsedMainComponent setPermission(String permission) { + // Empty string must be converted to null + this.permission = TextUtils.isEmpty(permission) ? null : permission.intern(); + return this; + } + + public String toString() { + StringBuilder sb = new StringBuilder(128); + sb.append("Service{"); + sb.append(Integer.toHexString(System.identityHashCode(this))); + sb.append(' '); + ComponentName.appendShortString(sb, getPackageName(), getName()); + sb.append('}'); + return sb.toString(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeInt(this.foregroundServiceType); + sForString.parcel(this.permission, dest, flags); + } + + public ParsedService() { + } + + protected ParsedService(Parcel in) { + super(in); + this.foregroundServiceType = in.readInt(); + this.permission = sForString.unparcel(in); + } + + public static final Parcelable.Creator CREATOR = new Creator() { + @Override + public ParsedService createFromParcel(Parcel source) { + return new ParsedService(source); + } + + @Override + public ParsedService[] newArray(int size) { + return new ParsedService[size]; + } + }; + + public int getForegroundServiceType() { + return foregroundServiceType; + } + + @Nullable + public String getPermission() { + return permission; + } +} diff --git a/core/java/android/content/pm/parsing/component/ParsedServiceUtils.java b/core/java/android/content/pm/parsing/component/ParsedServiceUtils.java new file mode 100644 index 0000000000000..08f0bbc30c6b2 --- /dev/null +++ b/core/java/android/content/pm/parsing/component/ParsedServiceUtils.java @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content.pm.parsing.component; + +import static android.content.pm.parsing.component.ComponentParseUtils.flag; + +import android.annotation.NonNull; +import android.content.pm.ActivityInfo; +import android.content.pm.ServiceInfo; +import android.content.pm.parsing.ParsingPackage; +import android.content.pm.parsing.ParsingPackageUtils; +import android.content.pm.parsing.ParsingUtils; +import android.content.pm.parsing.result.ParseInput; +import android.content.pm.parsing.result.ParseResult; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.content.res.XmlResourceParser; + +import com.android.internal.R; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; +import java.util.Objects; + +/** @hide */ +public class ParsedServiceUtils { + + private static final String TAG = ParsingPackageUtils.TAG; + + @NonNull + public static ParseResult parseService(String[] separateProcesses, + ParsingPackage pkg, Resources res, XmlResourceParser parser, int flags, + boolean useRoundIcon, ParseInput input) + throws XmlPullParserException, IOException { + boolean visibleToEphemeral; + boolean setExported; + + final String packageName = pkg.getPackageName(); + final ParsedService service = new ParsedService(); + String tag = parser.getName(); + + try (TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestService)) { + ParseResult result = ParsedMainComponentUtils.parseMainComponent( + service, tag, separateProcesses, pkg, sa, flags, useRoundIcon, input, + R.styleable.AndroidManifestService_banner, + R.styleable.AndroidManifestService_description, + R.styleable.AndroidManifestService_directBootAware, + R.styleable.AndroidManifestService_enabled, + R.styleable.AndroidManifestService_icon, + R.styleable.AndroidManifestService_label, + R.styleable.AndroidManifestService_logo, + R.styleable.AndroidManifestService_name, + R.styleable.AndroidManifestService_process, + R.styleable.AndroidManifestService_roundIcon, + R.styleable.AndroidManifestService_splitName + ); + + if (result.isError()) { + return result; + } + + setExported = sa.hasValue(R.styleable.AndroidManifestService_exported); + if (setExported) { + service.exported = sa.getBoolean(R.styleable.AndroidManifestService_exported, + false); + } + + String permission = sa.getNonConfigurationString( + R.styleable.AndroidManifestService_permission, 0); + service.setPermission(permission != null ? permission : pkg.getPermission()); + + service.foregroundServiceType = sa.getInt( + R.styleable.AndroidManifestService_foregroundServiceType, + ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE); + + service.flags |= flag(ServiceInfo.FLAG_STOP_WITH_TASK, + R.styleable.AndroidManifestService_stopWithTask, sa) + | flag(ServiceInfo.FLAG_ISOLATED_PROCESS, + R.styleable.AndroidManifestService_isolatedProcess, sa) + | flag(ServiceInfo.FLAG_EXTERNAL_SERVICE, + R.styleable.AndroidManifestService_externalService, sa) + | flag(ServiceInfo.FLAG_USE_APP_ZYGOTE, + R.styleable.AndroidManifestService_useAppZygote, sa) + | flag(ServiceInfo.FLAG_SINGLE_USER, + R.styleable.AndroidManifestService_singleUser, sa); + + visibleToEphemeral = sa.getBoolean( + R.styleable.AndroidManifestService_visibleToInstantApps, false); + if (visibleToEphemeral) { + service.flags |= ActivityInfo.FLAG_VISIBLE_TO_INSTANT_APP; + pkg.setVisibleToInstantApps(true); + } + } + + if (pkg.isCantSaveState()) { + // A heavy-weight application can not have services in its main process + // We can do direct compare because we intern all strings. + if (Objects.equals(service.getProcessName(), packageName)) { + return input.error("Heavy-weight applications can not have services " + + "in main process"); + } + } + 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 parseResult; + switch (parser.getName()) { + case "intent-filter": + ParseResult intentResult = ParsedMainComponentUtils + .parseIntentFilter(service, pkg, res, parser, visibleToEphemeral, + true /*allowGlobs*/, false /*allowAutoVerify*/, + false /*allowImplicitEphemeralVisibility*/, + false /*failOnNoActions*/, input); + parseResult = intentResult; + if (intentResult.isSuccess()) { + ParsedIntentInfo intent = intentResult.getResult(); + service.order = Math.max(intent.getOrder(), service.order); + service.addIntent(intent); + } + break; + case "meta-data": + parseResult = ParsedComponentUtils.addMetaData(service, pkg, res, parser, input); + break; + default: + parseResult = ParsingUtils.unknownTag(tag, pkg, parser, input); + break; + } + + if (parseResult.isError()) { + return input.error(parseResult); + } + } + + if (!setExported) { + service.exported = service.getIntents().size() > 0; + } + + return input.success(service); + } +}