From 022e707f9094cb7b9ac46ca7ab1cb643ab2f42f8 Mon Sep 17 00:00:00 2001 From: Winson Date: Tue, 28 Jan 2020 10:52:58 -0800 Subject: [PATCH] Split ParsedComponents This creates individual files for each Parsed_ object. Each also has a corresponding _Utils class for holding the parsing logic for each object. This was done to keep the data class as simple as possible. This commit does not migrate existing usages of ComponentParseUtils subclasses, so there will be duplicates of everything. A follow up change will migrate all the parsing logic to use these new classes. Bug: 135203078 Test: none, all testing for Parsed_ is to-be-merged and/or TBD Change-Id: I7bea3b1742bc5d945d1ad287f406488d3bf46476 --- .../pm/parsing/ParsingPackageImpl.java | 12 +- .../component/ComponentParseUtils.java | 178 +++++++ .../pm/parsing/component/ParsedActivity.java | 448 +++++++++++++++++ .../component/ParsedActivityUtils.java | 476 ++++++++++++++++++ .../pm/parsing/component/ParsedComponent.java | 208 ++++++++ .../component/ParsedComponentUtils.java | 100 ++++ .../pm/parsing/component/ParsedFeature.java | 229 +++++++++ .../parsing/component/ParsedFeatureUtils.java | 109 ++++ .../component/ParsedInstrumentation.java | 116 +++++ .../component/ParsedInstrumentationUtils.java | 72 +++ .../parsing/component/ParsedIntentInfo.java | 200 ++++++++ .../component/ParsedIntentInfoUtils.java | 242 +++++++++ .../component/ParsedMainComponent.java | 153 ++++++ .../component/ParsedMainComponentUtils.java | 137 +++++ .../parsing/component/ParsedPermission.java | 194 +++++++ .../component/ParsedPermissionGroup.java | 102 ++++ .../component/ParsedPermissionUtils.java | 205 ++++++++ .../pm/parsing/component/ParsedProcess.java | 166 ++++++ .../parsing/component/ParsedProcessUtils.java | 214 ++++++++ .../pm/parsing/component/ParsedProvider.java | 195 +++++++ .../component/ParsedProviderUtils.java | 346 +++++++++++++ .../pm/parsing/component/ParsedService.java | 102 ++++ .../parsing/component/ParsedServiceUtils.java | 162 ++++++ 23 files changed, 4360 insertions(+), 6 deletions(-) create mode 100644 core/java/android/content/pm/parsing/component/ComponentParseUtils.java create mode 100644 core/java/android/content/pm/parsing/component/ParsedActivity.java create mode 100644 core/java/android/content/pm/parsing/component/ParsedActivityUtils.java create mode 100644 core/java/android/content/pm/parsing/component/ParsedComponent.java create mode 100644 core/java/android/content/pm/parsing/component/ParsedComponentUtils.java create mode 100644 core/java/android/content/pm/parsing/component/ParsedFeature.java create mode 100644 core/java/android/content/pm/parsing/component/ParsedFeatureUtils.java create mode 100644 core/java/android/content/pm/parsing/component/ParsedInstrumentation.java create mode 100644 core/java/android/content/pm/parsing/component/ParsedInstrumentationUtils.java create mode 100644 core/java/android/content/pm/parsing/component/ParsedIntentInfo.java create mode 100644 core/java/android/content/pm/parsing/component/ParsedIntentInfoUtils.java create mode 100644 core/java/android/content/pm/parsing/component/ParsedMainComponent.java create mode 100644 core/java/android/content/pm/parsing/component/ParsedMainComponentUtils.java create mode 100644 core/java/android/content/pm/parsing/component/ParsedPermission.java create mode 100644 core/java/android/content/pm/parsing/component/ParsedPermissionGroup.java create mode 100644 core/java/android/content/pm/parsing/component/ParsedPermissionUtils.java create mode 100644 core/java/android/content/pm/parsing/component/ParsedProcess.java create mode 100644 core/java/android/content/pm/parsing/component/ParsedProcessUtils.java create mode 100644 core/java/android/content/pm/parsing/component/ParsedProvider.java create mode 100644 core/java/android/content/pm/parsing/component/ParsedProviderUtils.java create mode 100644 core/java/android/content/pm/parsing/component/ParsedService.java create mode 100644 core/java/android/content/pm/parsing/component/ParsedServiceUtils.java 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); + } +}