Merge "Revert "Revoke permissions defined in a to-be removed package.""
diff --git a/api/current.txt b/api/current.txt
index e53760a..120b475 100755
--- a/api/current.txt
+++ b/api/current.txt
@@ -11176,12 +11176,17 @@
public static class PackageInstaller.Session implements java.io.Closeable {
method public void abandon();
+ method public void addChildSessionId(int);
method public void close();
method public void commit(android.content.IntentSender);
method public void fsync(java.io.OutputStream) throws java.io.IOException;
+ method public int[] getChildSessionIds();
method public java.lang.String[] getNames() throws java.io.IOException;
+ method public int getParentSessionId();
+ method public boolean isMultiPackage();
method public java.io.InputStream openRead(java.lang.String) throws java.io.IOException;
method public java.io.OutputStream openWrite(java.lang.String, long, long) throws java.io.IOException;
+ method public void removeChildSessionId(int);
method public void removeSplit(java.lang.String) throws java.io.IOException;
method public void setStagingProgress(float);
method public void transfer(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
@@ -11202,20 +11207,24 @@
method public android.graphics.Bitmap getAppIcon();
method public java.lang.CharSequence getAppLabel();
method public java.lang.String getAppPackageName();
+ method public int[] getChildSessionIds();
method public int getInstallLocation();
method public int getInstallReason();
method public java.lang.String getInstallerPackageName();
method public int getMode();
method public int getOriginatingUid();
method public android.net.Uri getOriginatingUri();
+ method public int getParentSessionId();
method public float getProgress();
method public android.net.Uri getReferrerUri();
method public int getSessionId();
method public long getSize();
method public boolean isActive();
+ method public boolean isMultiPackage();
method public boolean isSealed();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.content.pm.PackageInstaller.SessionInfo> CREATOR;
+ field public static final int INVALID_ID = -1; // 0xffffffff
}
public static class PackageInstaller.SessionParams implements android.os.Parcelable {
@@ -11226,6 +11235,7 @@
method public void setAppPackageName(java.lang.String);
method public void setInstallLocation(int);
method public void setInstallReason(int);
+ method public void setMultiPackage();
method public void setOriginatingUid(int);
method public void setOriginatingUri(android.net.Uri);
method public void setReferrerUri(android.net.Uri);
diff --git a/api/system-current.txt b/api/system-current.txt
index b2a47f2..123ca51 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -5920,6 +5920,7 @@
field public static final int RESET_OPTION_DELETE_FIELD_LOADED_TEST_PROFILES = 2; // 0x2
field public static final int RESET_OPTION_DELETE_OPERATIONAL_PROFILES = 1; // 0x1
field public static final int RESET_OPTION_RESET_DEFAULT_SMDP_ADDRESS = 4; // 0x4
+ field public static final int RESULT_CALLER_NOT_ALLOWED = -3; // 0xfffffffd
field public static final int RESULT_EUICC_NOT_FOUND = -2; // 0xfffffffe
field public static final int RESULT_OK = 0; // 0x0
field public static final int RESULT_UNKNOWN_ERROR = -1; // 0xffffffff
diff --git a/core/java/android/content/pm/IPackageInstallerSession.aidl b/core/java/android/content/pm/IPackageInstallerSession.aidl
index 8fddb99..cef21f6 100644
--- a/core/java/android/content/pm/IPackageInstallerSession.aidl
+++ b/core/java/android/content/pm/IPackageInstallerSession.aidl
@@ -38,4 +38,9 @@
void commit(in IntentSender statusReceiver, boolean forTransferred);
void transfer(in String packageName);
void abandon();
+ boolean isMultiPackage();
+ int[] getChildSessionIds();
+ void addChildSessionId(in int sessionId);
+ void removeChildSessionId(in int sessionId);
+ int getParentSessionId();
}
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index e9cfa78..38e1c49 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -365,12 +365,14 @@
*/
public @NonNull Session openSession(int sessionId) throws IOException {
try {
- return new Session(mInstaller.openSession(sessionId));
+ try {
+ return new Session(mInstaller.openSession(sessionId));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
} catch (RuntimeException e) {
ExceptionUtils.maybeUnwrapIOException(e);
throw e;
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
}
}
@@ -769,9 +771,18 @@
* If an APK included in this session is already defined by the existing
* installation (for example, the same split name), the APK in this session
* will replace the existing APK.
+ * <p>
+ * In such a case that multiple packages need to be commited simultaneously,
+ * multiple sessions can be referenced by a single multi-package session.
+ * This session is created with no package name and calling
+ * {@link #setMultiPackage()} with {@code true}. The
+ * individual session IDs can be added with {@link #addChildSessionId(int)}
+ * and commit of the multi-package session will result in all child sessions
+ * being committed atomically.
*/
public static class Session implements Closeable {
- private IPackageInstallerSession mSession;
+ /** {@hide} */
+ protected final IPackageInstallerSession mSession;
/** {@hide} */
public Session(IPackageInstallerSession session) {
@@ -1080,6 +1091,71 @@
throw e.rethrowFromSystemServer();
}
}
+
+ /**
+ * @return {@code true} if this session will commit more than one package when it is
+ * committed.
+ */
+ public boolean isMultiPackage() {
+ try {
+ return mSession.isMultiPackage();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @return the session ID of the multi-package session that this belongs to or
+ * {@link SessionInfo#INVALID_ID} if it does not belong to a multi-package session.
+ */
+ public int getParentSessionId() {
+ try {
+ return mSession.getParentSessionId();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @return the set of session IDs that will be committed atomically when this session is
+ * committed if this is a multi-package session or null if none exist.
+ */
+ @NonNull
+ public int[] getChildSessionIds() {
+ try {
+ return mSession.getChildSessionIds();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Adds a session ID to the set of sessions that will be committed atomically
+ * when this session is committed.
+ *
+ * @param sessionId the session ID to add to this multi-package session.
+ */
+ public void addChildSessionId(int sessionId) {
+ try {
+ mSession.addChildSessionId(sessionId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Removes a session ID from the set of sessions that will be committed
+ * atomically when this session is committed.
+ *
+ * @param sessionId the session ID to remove from this multi-package session.
+ */
+ public void removeChildSessionId(int sessionId) {
+ try {
+ mSession.removeChildSessionId(sessionId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
/**
@@ -1149,6 +1225,8 @@
public String[] grantedRuntimePermissions;
/** {@hide} */
public String installerPackageName;
+ /** {@hide} */
+ public boolean isMultiPackage;
/**
* Construct parameters for a new package install session.
@@ -1178,6 +1256,7 @@
volumeUuid = source.readString();
grantedRuntimePermissions = source.readStringArray();
installerPackageName = source.readString();
+ isMultiPackage = source.readBoolean();
}
/**
@@ -1392,6 +1471,18 @@
this.installerPackageName = installerPackageName;
}
+ /**
+ * Set this session to be the parent of a multi-package install.
+ *
+ * A multi-package install session contains no APKs and only references other install
+ * sessions via ID. When a multi-package session is committed, all of its children
+ * are committed to the system in an atomic manner. If any children fail to install,
+ * all of them do, including the multi-package session.
+ */
+ public void setMultiPackage() {
+ this.isMultiPackage = true;
+ }
+
/** {@hide} */
public void dump(IndentingPrintWriter pw) {
pw.printPair("mode", mode);
@@ -1408,6 +1499,7 @@
pw.printPair("volumeUuid", volumeUuid);
pw.printPair("grantedRuntimePermissions", grantedRuntimePermissions);
pw.printPair("installerPackageName", installerPackageName);
+ pw.printPair("isMultiPackage", isMultiPackage);
pw.println();
}
@@ -1433,6 +1525,7 @@
dest.writeString(volumeUuid);
dest.writeStringArray(grantedRuntimePermissions);
dest.writeString(installerPackageName);
+ dest.writeBoolean(isMultiPackage);
}
public static final Parcelable.Creator<SessionParams>
@@ -1454,6 +1547,12 @@
*/
public static class SessionInfo implements Parcelable {
+ /**
+ * A session ID that does not exist or is invalid.
+ */
+ public static final int INVALID_ID = -1;
+ /** {@hide} */
+ private static final int[] NO_SESSIONS = {};
/** {@hide} */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
public int sessionId;
@@ -1503,6 +1602,12 @@
public String[] grantedRuntimePermissions;
/** {@hide} */
public int installFlags;
+ /** {@hide} */
+ public boolean isMultiPackage;
+ /** {@hide} */
+ public int parentSessionId = INVALID_ID;
+ /** {@hide} */
+ public int[] childSessionIds = NO_SESSIONS;
/** {@hide} */
@UnsupportedAppUsage
@@ -1531,6 +1636,12 @@
referrerUri = source.readParcelable(null);
grantedRuntimePermissions = source.readStringArray();
installFlags = source.readInt();
+ isMultiPackage = source.readBoolean();
+ parentSessionId = source.readInt();
+ childSessionIds = source.createIntArray();
+ if (childSessionIds == null) {
+ childSessionIds = NO_SESSIONS;
+ }
}
/**
@@ -1784,6 +1895,30 @@
return createDetailsIntent();
}
+ /**
+ * Returns true if this session is a multi-package session containing references to other
+ * sessions.
+ */
+ public boolean isMultiPackage() {
+ return isMultiPackage;
+ }
+
+ /**
+ * Returns the parent multi-package session ID if this session belongs to one,
+ * {@link #INVALID_ID} otherwise.
+ */
+ public int getParentSessionId() {
+ return parentSessionId;
+ }
+
+ /**
+ * Returns the set of session IDs that will be committed when this session is commited if
+ * this session is a multi-package session.
+ */
+ public int[] getChildSessionIds() {
+ return childSessionIds;
+ }
+
@Override
public int describeContents() {
return 0;
@@ -1811,6 +1946,9 @@
dest.writeParcelable(referrerUri, flags);
dest.writeStringArray(grantedRuntimePermissions);
dest.writeInt(installFlags);
+ dest.writeBoolean(isMultiPackage);
+ dest.writeInt(parentSessionId);
+ dest.writeIntArray(childSessionIds);
}
public static final Parcelable.Creator<SessionInfo>
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 67b86c0..44b3fda 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -915,6 +915,11 @@
public static final int INSTALL_REASON_USER = 4;
/**
+ * @hide
+ */
+ public static final int INSTALL_UNKNOWN = 0;
+
+ /**
* Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS}
* on success.
*
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 99c48d2..a30e38a 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -12508,6 +12508,17 @@
"privileged_device_identifier_target_q_behavior_enabled";
/**
+ * If set to 1, the device identifier check will be relaxed to the previous READ_PHONE_STATE
+ * permission check for 3P apps.
+ *
+ * STOPSHIP: Remove this once we ship with the new device identifier check enabled.
+ *
+ * @hide
+ */
+ public static final String PRIVILEGED_DEVICE_IDENTIFIER_3P_CHECK_RELAXED =
+ "privileged_device_identifier_3p_check_relaxed";
+
+ /**
* If set to 1, SettingsProvider's restoreAnyVersion="true" attribute will be ignored
* and restoring to lower version of platform API will be skipped.
*
diff --git a/core/java/android/view/inspector/ChildTraverser.java b/core/java/android/view/inspector/ChildTraverser.java
new file mode 100644
index 0000000..b775de5
--- /dev/null
+++ b/core/java/android/view/inspector/ChildTraverser.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2018 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.view.inspector;
+
+import android.annotation.NonNull;
+
+/**
+ * Interface for visiting all the child nodes of an inspectable object.
+ *
+ * Inspectable objects may return a collection of children as an array, an {@link Iterable} or an
+ * {@link java.util.Iterator}. This provides a unified API for traversing across all the children
+ * of an inspectable node.
+ *
+ * This interface is consumed by {@link InspectionHelper#traverseChildren(Object, ChildTraverser)}
+ * and may be implemented as a lambda.
+ *
+ * @see InspectionHelper#traverseChildren(Object, ChildTraverser)
+ * @hide
+ */
+@FunctionalInterface
+public interface ChildTraverser {
+ /**
+ * Visit one child object of a parent inspectable object.
+ *
+ * The iteration interface will filter null values out before passing them to this method, but
+ * some child objects may not be inspectable. It is up to the implementor to determine their
+ * inspectablity and what to do with them.
+ *
+ * @param child A child object, guaranteed not to be null.
+ */
+ void traverseChild(@NonNull Object child);
+}
diff --git a/core/java/android/view/inspector/InspectionHelper.java b/core/java/android/view/inspector/InspectionHelper.java
new file mode 100644
index 0000000..27a9704
--- /dev/null
+++ b/core/java/android/view/inspector/InspectionHelper.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright 2018 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.view.inspector;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+/**
+ * An interface for companion objects used to inspect views.
+ *
+ * Inspection helpers only need to handle the properties, name and traversal of the specific class
+ * they are defined for, not anything from a parent class. At runtime, the inspector instantiates
+ * one instance of each inspection helper, and handles visiting them in the correct inheritance
+ * order for each type it inspects.
+ *
+ * Properties are read from the top of the type tree to the bottom, so that classes that override
+ * a property in their parent class can overwrite it in the reader. In general, properties will
+ * cleanly inherit through their getters, and the inspector runtime will read the properties of a
+ * parent class via the parent's inspection helper, and the child helper will only read properties
+ * added or changed since the parent was defined.
+ *
+ * Only one child traversal is considered for each class. If a descendant class defines a
+ * different child traversal than its parent, only the bottom traversal is used. If a class does
+ * not define its own child traversal, but one of its ancestors does, the bottom-most ancestor's
+ * traversal will be used.
+ *
+ * @param <T> The type of inspectable this helper operates on
+ * @hide
+ */
+public interface InspectionHelper<T> {
+ /**
+ * Map the string names of the properties this helper knows about to integer IDs.
+ *
+ * Each helper is responsible for storing the integer IDs of all its properties. This is the
+ * only method that is allowed to modify the stored IDs.
+ *
+ * Calling {@link #readProperties(T, PropertyReader)} before calling this results in
+ * undefined behavior.
+ *
+ * @param propertyMapper A {@link PropertyMapper} or lambda which maps string names to IDs.
+ */
+ void mapProperties(@NonNull PropertyMapper propertyMapper);
+
+ /**
+ * Read the values of an instance of this helper's type into a {@link PropertyReader}.
+ *
+ * This method needs to return the property IDs stored by
+ * {@link #mapProperties(PropertyMapper)}. Implementations should track if their properties
+ * have been mapped and throw a {@link UninitializedPropertyMapException} if this method is
+ * called before {mapProperties}.
+ *
+ * @param inspectable A object of type {@link T} to read the properties of.
+ * @param propertyReader An object which receives the property IDs and values.
+ */
+ void readProperties(@NonNull T inspectable, @NonNull PropertyReader propertyReader);
+
+ /**
+ * Query if this inspectable type can potentially have child nodes.
+ *
+ * E.g.: any descendant of {@link android.view.ViewGroup} can have child nodes, but a leaf
+ * view like {@link android.widget.ImageView} may not.
+ *
+ * The default implementation always returns false. If an implementing class overrides this, it
+ * should also define {@link #traverseChildren(T, ChildTraverser)}.
+ *
+ * @return True if this inspectable type can potentially have child nodes, false otherwise.
+ */
+ default boolean hasChildTraversal() {
+ return false;
+ }
+
+ /**
+ * Traverse the child nodes of an instance of this helper's type into a {@link ChildTraverser}.
+ *
+ * This provides the ability to traverse over a variety of collection APIs (e.g.: arrays,
+ * {@link Iterable}, or {@link java.util.Iterator}) in a uniform fashion. The traversal must be
+ * in the order defined by this helper's type. If the getter returns null, the helper must
+ * treat it as an empty collection.
+ *
+ * The default implementation throws a {@link NoChildTraversalException}. If
+ * {@link #hasChildTraversal()} returns is overriden to return true, it is expected that the
+ * implementing class will also override this method and provide a traversal.
+ *
+ * @param inspectable An object of type {@link T} to traverse the child nodes of.
+ * @param childTraverser A {@link ChildTraverser} or lamba to receive the children in order.
+ * @throws NoChildTraversalException If there is no defined child traversal
+ */
+ default void traverseChildren(
+ @NonNull T inspectable,
+ @SuppressWarnings("unused") @NonNull ChildTraverser childTraverser) {
+ throw new NoChildTraversalException(inspectable.getClass());
+ }
+
+ /**
+ * Get an optional name to display to developers for inspection nodes of this helper's type.
+ *
+ * The default implementation returns null, which will cause the runtime to use the class's
+ * simple name as defined by {@link Class#getSimpleName()} as the node name.
+ *
+ * If the type of this helper is inflated from XML, this method should be overridden to return
+ * the string used as the tag name for this type in XML.
+ *
+ * @return A string to use as the node name, or null to use the simple class name fallback.
+ */
+ @Nullable
+ default String getNodeName() {
+ return null;
+ }
+
+ /**
+ * Thrown by {@link #readProperties(Object, PropertyReader)} if called before
+ * {@link #mapProperties(PropertyMapper)}.
+ */
+ class UninitializedPropertyMapException extends RuntimeException {
+ public UninitializedPropertyMapException() {
+ super("Unable to read properties of an inspectable before mapping their IDs.");
+ }
+ }
+
+ /**
+ * Thrown by {@link #traverseChildren(Object, ChildTraverser)} if no child traversal exists.
+ */
+ class NoChildTraversalException extends RuntimeException {
+ public NoChildTraversalException(Class cls) {
+ super(String.format(
+ "Class %s does not have a defined child traversal. Cannot traverse children.",
+ cls.getCanonicalName()
+ ));
+ }
+ }
+}
diff --git a/core/java/android/view/inspector/OWNERS b/core/java/android/view/inspector/OWNERS
new file mode 100644
index 0000000..0473f54
--- /dev/null
+++ b/core/java/android/view/inspector/OWNERS
@@ -0,0 +1,3 @@
+alanv@google.com
+ashleyrose@google.com
+aurimas@google.com
\ No newline at end of file
diff --git a/core/java/android/view/inspector/PropertyMapper.java b/core/java/android/view/inspector/PropertyMapper.java
new file mode 100644
index 0000000..35550bd
--- /dev/null
+++ b/core/java/android/view/inspector/PropertyMapper.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright 2018 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.view.inspector;
+
+import android.annotation.NonNull;
+
+/**
+ * An interface for mapping the string names of inspectable properties to integer identifiers.
+ *
+ * This interface is consumed by {@link InspectionHelper#mapProperties(PropertyMapper)}.
+ *
+ * Mapping properties to IDs enables quick comparisons against shadow copies of inspectable
+ * objects without performing a large number of string comparisons.
+ *
+ * @see InspectionHelper#mapProperties(PropertyMapper)
+ * @hide
+ */
+public interface PropertyMapper {
+ /**
+ * Map a string name to an integer ID for a primitive boolean property.
+ *
+ * @param name The name of the property
+ * @return An integer ID for the property
+ * @throws PropertyConflictException If the property name is already mapped as another type.
+ */
+ int mapBoolean(@NonNull String name);
+
+ /**
+ * Map a string name to an integer ID for a primitive byte property.
+ *
+ * @param name The name of the property
+ * @return An integer ID for the property
+ * @throws PropertyConflictException If the property name is already mapped as another type.
+ */
+ int mapByte(@NonNull String name);
+
+ /**
+ * Map a string name to an integer ID for a primitive char property.
+ *
+ * @param name The name of the property
+ * @return An integer ID for the property
+ * @throws PropertyConflictException If the property name is already mapped as another type.
+ */
+ int mapChar(@NonNull String name);
+
+ /**
+ * Map a string name to an integer ID for a primitive double property.
+ *
+ * @param name The name of the property
+ * @return An integer ID for the property
+ * @throws PropertyConflictException If the property name is already mapped as another type.
+ */
+ int mapDouble(@NonNull String name);
+
+ /**
+ * Map a string name to an integer ID for a primitive float property.
+ *
+ * @param name The name of the property
+ * @return An integer ID for the property
+ * @throws PropertyConflictException If the property name is already mapped as another type.
+ */
+ int mapFloat(@NonNull String name);
+
+ /**
+ * Map a string name to an integer ID for a primitive int property.
+ *
+ * @param name The name of the property
+ * @return An integer ID for the property
+ * @throws PropertyConflictException If the property name is already mapped as another type.
+ */
+ int mapInt(@NonNull String name);
+
+ /**
+ * Map a string name to an integer ID for a primitive long property.
+ *
+ * @param name The name of the property
+ * @return An integer ID for the property
+ * @throws PropertyConflictException If the property name is already mapped as another type.
+ */
+ int mapLong(@NonNull String name);
+
+ /**
+ * Map a string name to an integer ID for a primitive short property.
+ *
+ * @param name The name of the property
+ * @return An integer ID for the property
+ * @throws PropertyConflictException If the property name is already mapped as another type.
+ */
+ int mapShort(@NonNull String name);
+
+ /**
+ * Map a string name to an integer ID for an object property.
+ *
+ * @param name The name of the property
+ * @return An integer ID for the property
+ * @throws PropertyConflictException If the property name is already mapped as another type.
+ */
+ int mapObject(@NonNull String name);
+
+ /**
+ * Thrown from a map method if a property name is already mapped as different type.
+ */
+ class PropertyConflictException extends RuntimeException {
+ public PropertyConflictException(
+ @NonNull String name,
+ @NonNull String newPropertyType,
+ @NonNull String existingPropertyType) {
+ super(String.format(
+ "Attempted to map property \"%s\" as type %s, but it is already mapped as %s.",
+ name,
+ newPropertyType,
+ existingPropertyType
+ ));
+ }
+ }
+}
diff --git a/core/java/android/view/inspector/PropertyReader.java b/core/java/android/view/inspector/PropertyReader.java
new file mode 100644
index 0000000..df81c10
--- /dev/null
+++ b/core/java/android/view/inspector/PropertyReader.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright 2018 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.view.inspector;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+/**
+ * An interface for reading the properties of an inspectable object.
+ *
+ * Used as the parameter for {@link InspectionHelper#readProperties(Object, PropertyReader)}.
+ * It has separate methods for all primitive types to avoid autoboxing overhead if a concrete
+ * implementation is able to work with primitives. Implementations should be prepared to accept
+ * {null} as the value of {@link PropertyReader#readObject(int, Object)}.
+ *
+ * @see InspectionHelper#readProperties(Object, PropertyReader)
+ * @hide
+ */
+public interface PropertyReader {
+ /**
+ * Read a primitive boolean property.
+ *
+ * @param id Identifier of the property from a {@link PropertyMapper}
+ * @param value Value of the property
+ * @throws PropertyTypeMismatchException If the property ID is not mapped as a {boolean}
+ */
+ void readBoolean(int id, boolean value);
+
+ /**
+ * Read a primitive byte property.
+ *
+ * @param id Identifier of the property from a {@link PropertyMapper}
+ * @param value Value of the property
+ * @throws PropertyTypeMismatchException If the property ID is not mapped as a {byte}
+ */
+ void readByte(int id, byte value);
+
+ /**
+ * Read a primitive character property.
+ *
+ * @param id Identifier of the property from a {@link PropertyMapper}
+ * @param value Value of the property
+ * @throws PropertyTypeMismatchException If the property ID is not mapped as a {char}
+ */
+ void readChar(int id, char value);
+
+ /**
+ * Read a read a primitive double property.
+ *
+ * @param id Identifier of the property from a {@link PropertyMapper}
+ * @param value Value of the property
+ * @throws PropertyTypeMismatchException If the property ID is not mapped as a {double}
+ */
+ void readDouble(int id, double value);
+
+ /**
+ * Read a primitive float property.
+ *
+ * @param id Identifier of the property from a {@link PropertyMapper}
+ * @param value Value of the property
+ * @throws PropertyTypeMismatchException If the property ID is not mapped as a {float}
+ */
+ void readFloat(int id, float value);
+
+ /**
+ * Read a primitive integer property.
+ *
+ * @param id Identifier of the property from a {@link PropertyMapper}
+ * @param value Value of the property
+ * @throws PropertyTypeMismatchException If the property ID is not mapped as an {int}
+ */
+ void readInt(int id, int value);
+
+ /**
+ * Read a primitive long property.
+ *
+ * @param id Identifier of the property from a {@link PropertyMapper}
+ * @param value Value of the property
+ * @throws PropertyTypeMismatchException If the property ID is not mapped as a {long}
+ */
+ void readLong(int id, long value);
+
+ /**
+ * Read a primitive short property.
+ *
+ * @param id Identifier of the property from a {@link PropertyMapper}
+ * @param value Value of the property
+ * @throws PropertyTypeMismatchException If the property ID is not mapped as a {short}
+ */
+ void readShort(int id, short value);
+
+ /**
+ * Read any object as a property.
+ *
+ * If value is null, the property is marked as empty.
+ *
+ * @param id Identifier of the property from a {@link PropertyMapper}
+ * @param value Value of the property
+ * @throws PropertyTypeMismatchException If the property ID is not mapped as an object
+ */
+ void readObject(int id, @Nullable Object value);
+
+ /**
+ * Thrown if a client calls a typed read method for a property of a different type.
+ */
+ class PropertyTypeMismatchException extends RuntimeException {
+ public PropertyTypeMismatchException(
+ int id,
+ @NonNull String expectedPropertyType,
+ @NonNull String actualPropertyType,
+ @Nullable String propertyName) {
+ super(formatMessage(id, expectedPropertyType, actualPropertyType, propertyName));
+ }
+
+ public PropertyTypeMismatchException(
+ int id,
+ @NonNull String expectedPropertyType,
+ @NonNull String actualPropertyType) {
+ super(formatMessage(id, expectedPropertyType, actualPropertyType, null));
+ }
+
+ private static @NonNull String formatMessage(
+ int id,
+ @NonNull String expectedPropertyType,
+ @NonNull String actualPropertyType,
+ @Nullable String propertyName) {
+
+ if (propertyName == null) {
+ return String.format(
+ "Attempted to read property with ID 0x%08X as type %s, "
+ + "but the ID is of type %s.",
+ id,
+ expectedPropertyType,
+ actualPropertyType
+ );
+ } else {
+ return String.format(
+ "Attempted to read property \"%s\" with ID 0x%08X as type %s, "
+ + "but the ID is of type %s.",
+ propertyName,
+ id,
+ expectedPropertyType,
+ actualPropertyType
+ );
+ }
+ }
+ }
+}
diff --git a/core/jni/android_hardware_input_InputApplicationHandle.cpp b/core/jni/android_hardware_input_InputApplicationHandle.cpp
index 8ace8da..c66a87c 100644
--- a/core/jni/android_hardware_input_InputApplicationHandle.cpp
+++ b/core/jni/android_hardware_input_InputApplicationHandle.cpp
@@ -17,6 +17,7 @@
#define LOG_TAG "InputApplicationHandle"
#include <nativehelper/JNIHelp.h>
+#include "nativehelper/scoped_utf_chars.h"
#include "jni.h"
#include <android_runtime/AndroidRuntime.h>
#include <utils/threads.h>
@@ -64,9 +65,8 @@
jstring nameObj = jstring(env->GetObjectField(obj,
gInputApplicationHandleClassInfo.name));
if (nameObj) {
- const char* nameStr = env->GetStringUTFChars(nameObj, NULL);
- mInfo->name = nameStr;
- env->ReleaseStringUTFChars(nameObj, nameStr);
+ ScopedUtfChars nameChars(env, nameObj);
+ mInfo->name = nameChars.c_str();
env->DeleteLocalRef(nameObj);
} else {
mInfo->name = "<null>";
diff --git a/core/jni/android_hardware_input_InputWindowHandle.cpp b/core/jni/android_hardware_input_InputWindowHandle.cpp
index f4829ad..f232919b 100644
--- a/core/jni/android_hardware_input_InputWindowHandle.cpp
+++ b/core/jni/android_hardware_input_InputWindowHandle.cpp
@@ -17,6 +17,7 @@
#define LOG_TAG "InputWindowHandle"
#include <nativehelper/JNIHelp.h>
+#include "nativehelper/scoped_utf_chars.h"
#include "jni.h"
#include <android_runtime/AndroidRuntime.h>
#include <utils/threads.h>
@@ -102,9 +103,8 @@
jstring nameObj = jstring(env->GetObjectField(obj,
gInputWindowHandleClassInfo.name));
if (nameObj) {
- const char* nameStr = env->GetStringUTFChars(nameObj, NULL);
- mInfo->name = nameStr;
- env->ReleaseStringUTFChars(nameObj, nameStr);
+ ScopedUtfChars nameChars(env, nameObj);
+ mInfo->name = nameChars.c_str();
env->DeleteLocalRef(nameObj);
} else {
mInfo->name = "<null>";
diff --git a/core/jni/android_view_InputChannel.cpp b/core/jni/android_view_InputChannel.cpp
index 2f17907..1e887b3 100644
--- a/core/jni/android_view_InputChannel.cpp
+++ b/core/jni/android_view_InputChannel.cpp
@@ -17,7 +17,7 @@
#define LOG_TAG "InputChannel-JNI"
#include <nativehelper/JNIHelp.h>
-
+#include "nativehelper/scoped_utf_chars.h"
#include <android_runtime/AndroidRuntime.h>
#include <binder/Parcel.h>
#include <utils/Log.h>
@@ -123,9 +123,8 @@
static jobjectArray android_view_InputChannel_nativeOpenInputChannelPair(JNIEnv* env,
jclass clazz, jstring nameObj) {
- const char* nameChars = env->GetStringUTFChars(nameObj, NULL);
- std::string name = nameChars;
- env->ReleaseStringUTFChars(nameObj, nameChars);
+ ScopedUtfChars nameChars(env, nameObj);
+ std::string name = nameChars.c_str();
sp<InputChannel> serverChannel;
sp<InputChannel> clientChannel;
diff --git a/core/proto/OWNERS b/core/proto/OWNERS
index 2ace1ac..480b1ea 100644
--- a/core/proto/OWNERS
+++ b/core/proto/OWNERS
@@ -11,6 +11,9 @@
yaochen@google.com
yro@google.com
+# Settings UI
+per-file settings_enums.proto=zhfan@google.com
+
# Frameworks
ogunwale@google.com
jjaggi@google.com
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index 329ae32..d5dc903 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -370,6 +370,7 @@
Settings.Global.PRIVATE_DNS_DEFAULT_MODE,
Settings.Global.PRIVILEGED_DEVICE_IDENTIFIER_CHECK_ENABLED,
Settings.Global.PRIVILEGED_DEVICE_IDENTIFIER_TARGET_Q_BEHAVIOR_ENABLED,
+ Settings.Global.PRIVILEGED_DEVICE_IDENTIFIER_3P_CHECK_RELAXED,
Settings.Global.PROVISIONING_APN_ALARM_DELAY_IN_MS,
Settings.Global.RADIO_BLUETOOTH,
Settings.Global.RADIO_CELL,
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index f0053a4..17d2db71 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -176,7 +176,7 @@
"pipeline/skia/SkiaRecordingCanvas.cpp",
"pipeline/skia/SkiaVulkanPipeline.cpp",
"pipeline/skia/VectorDrawableAtlas.cpp",
- "pipeline/skia/VkFunctorDrawable.cpp",
+ "pipeline/skia/VkInteropFunctorDrawable.cpp",
"renderstate/RenderState.cpp",
"renderthread/CacheManager.cpp",
"renderthread/CanvasContext.cpp",
diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
index 596b8af..45022e7 100644
--- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
+++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
@@ -24,7 +24,7 @@
#include "RenderNode.h"
#include "pipeline/skia/AnimatedDrawables.h"
#include "pipeline/skia/GLFunctorDrawable.h"
-#include "pipeline/skia/VkFunctorDrawable.h"
+#include "pipeline/skia/VkInteropFunctorDrawable.h"
namespace android {
namespace uirenderer {
@@ -124,8 +124,8 @@
uirenderer::GlFunctorLifecycleListener* listener) {
FunctorDrawable* functorDrawable;
if (Properties::getRenderPipelineType() == RenderPipelineType::SkiaVulkan) {
- functorDrawable = mDisplayList->allocateDrawable<VkFunctorDrawable>(functor, listener,
- asSkCanvas());
+ functorDrawable = mDisplayList->allocateDrawable<VkInteropFunctorDrawable>(functor,
+ listener, asSkCanvas());
} else {
functorDrawable = mDisplayList->allocateDrawable<GLFunctorDrawable>(functor, listener,
asSkCanvas());
diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
index 2ca110f..7fc41ac 100644
--- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
@@ -22,7 +22,7 @@
#include "SkiaProfileRenderer.h"
#include "renderstate/RenderState.h"
#include "renderthread/Frame.h"
-#include "VkFunctorDrawable.h"
+#include "VkInteropFunctorDrawable.h"
#include <SkSurface.h>
#include <SkTypes.h>
@@ -144,7 +144,7 @@
}
void SkiaVulkanPipeline::invokeFunctor(const RenderThread& thread, Functor* functor) {
- VkFunctorDrawable::vkInvokeFunctor(functor);
+ VkInteropFunctorDrawable::vkInvokeFunctor(functor);
}
sk_sp<Bitmap> SkiaVulkanPipeline::allocateHardwareBitmap(renderthread::RenderThread& renderThread,
diff --git a/libs/hwui/pipeline/skia/VkFunctorDrawable.cpp b/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp
similarity index 96%
rename from libs/hwui/pipeline/skia/VkFunctorDrawable.cpp
rename to libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp
index 6486ddb..2a6a9f5 100644
--- a/libs/hwui/pipeline/skia/VkFunctorDrawable.cpp
+++ b/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-#include "VkFunctorDrawable.h"
+#include "VkInteropFunctorDrawable.h"
#include <private/hwui/DrawGlInfo.h>
#include "renderthread/EglManager.h"
@@ -64,7 +64,7 @@
}
};
-void VkFunctorDrawable::vkInvokeFunctor(Functor* functor) {
+void VkInteropFunctorDrawable::vkInvokeFunctor(Functor* functor) {
ScopedDrawRequest _drawRequest{};
sGLDrawThread->queue().runSync([&]() {
EGLDisplay display = sEglManager.eglDisplay();
@@ -78,7 +78,7 @@
#define FENCE_TIMEOUT 2000000000
-void VkFunctorDrawable::onDraw(SkCanvas* canvas) {
+void VkInteropFunctorDrawable::onDraw(SkCanvas* canvas) {
ATRACE_CALL();
if (canvas->getGrContext() == nullptr) {
@@ -202,7 +202,7 @@
canvas->restore();
}
-VkFunctorDrawable::~VkFunctorDrawable() {
+VkInteropFunctorDrawable::~VkInteropFunctorDrawable() {
if (mListener.get() != nullptr) {
ScopedDrawRequest _drawRequest{};
sGLDrawThread->queue().runSync([&]() {
@@ -211,7 +211,7 @@
}
}
-void VkFunctorDrawable::syncFunctor() const {
+void VkInteropFunctorDrawable::syncFunctor() const {
ScopedDrawRequest _drawRequest{};
sGLDrawThread->queue().runSync([&]() {
(*mFunctor)(DrawGlInfo::kModeSync, nullptr);
diff --git a/libs/hwui/pipeline/skia/VkFunctorDrawable.h b/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.h
similarity index 86%
rename from libs/hwui/pipeline/skia/VkFunctorDrawable.h
rename to libs/hwui/pipeline/skia/VkInteropFunctorDrawable.h
index e37f6fb..3269cfb 100644
--- a/libs/hwui/pipeline/skia/VkFunctorDrawable.h
+++ b/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.h
@@ -30,11 +30,12 @@
* This drawable wraps a Vulkan functor enabling it to be recorded into a list
* of Skia drawing commands.
*/
-class VkFunctorDrawable : public FunctorDrawable {
+class VkInteropFunctorDrawable : public FunctorDrawable {
public:
- VkFunctorDrawable(Functor* functor, GlFunctorLifecycleListener* listener, SkCanvas* canvas)
+ VkInteropFunctorDrawable(Functor* functor, GlFunctorLifecycleListener* listener,
+ SkCanvas* canvas)
: FunctorDrawable(functor, listener, canvas) {}
- virtual ~VkFunctorDrawable();
+ virtual ~VkInteropFunctorDrawable();
void syncFunctor() const override;
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 7d09c00..6f5d657 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2131,7 +2131,7 @@
<string name="app_info">App info</string>
<!-- Action label for switching to a browser for an instant app [CHAR LIMIT=20] -->
- <string name="go_to_web">Go to web</string>
+ <string name="go_to_web">Go to browser</string>
<!-- Quick settings tile for toggling mobile data [CHAR LIMIT=20] -->
<string name="mobile_data">Mobile data</string>
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
new file mode 100644
index 0000000..a8c1ac6
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2018 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 com.android.systemui.statusbar.phone;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.support.test.filters.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+public class KeyguardClockPositionAlgorithmTest extends SysuiTestCase {
+
+ private static final int SCREEN_HEIGHT = 2000;
+ private static final int EMPTY_MARGIN = 0;
+ private static final int EMPTY_HEIGHT = 0;
+ private static final boolean SECURE_LOCKED = false;
+ private static final boolean PULSING_NO = false;
+ private static final float ZERO_DRAG = 0.f;
+ private static final float OPAQUE = 1.f;
+ private static final float TRANSPARENT = 0.f;
+
+ private KeyguardClockPositionAlgorithm mClockPositionAlgorithm;
+ private KeyguardClockPositionAlgorithm.Result mClockPosition;
+ private int mNotificationStackHeight;
+ private float mPanelExpansion;
+ private int mKeyguardStatusHeight;
+ private float mDark;
+
+ @Before
+ public void setUp() {
+ mClockPositionAlgorithm = new KeyguardClockPositionAlgorithm();
+ mClockPosition = new KeyguardClockPositionAlgorithm.Result();
+ }
+
+ @Test
+ public void clockPositionMiddleOfScreenOnAOD() {
+ // GIVEN on AOD and both stack scroll and clock have 0 height
+ givenAOD();
+ mNotificationStackHeight = EMPTY_HEIGHT;
+ mKeyguardStatusHeight = EMPTY_HEIGHT;
+ // WHEN the clock position algorithm is run
+ positionClock();
+ // THEN the clock Y position is the middle of the screen (SCREEN_HEIGHT / 2).
+ assertThat(mClockPosition.clockY).isEqualTo(1000);
+ // AND the clock is opaque and positioned on the left.
+ assertThat(mClockPosition.clockX).isEqualTo(0);
+ assertThat(mClockPosition.clockAlpha).isEqualTo(OPAQUE);
+ }
+
+ @Test
+ public void clockPositionAdjustsForKeyguardStatusOnAOD() {
+ // GIVEN on AOD with a clock of height 100
+ givenAOD();
+ mNotificationStackHeight = EMPTY_HEIGHT;
+ mKeyguardStatusHeight = 100;
+ // WHEN the clock position algorithm is run
+ positionClock();
+ // THEN the clock Y position adjusts for the clock height (SCREEN_HEIGHT / 2 - 100).
+ assertThat(mClockPosition.clockY).isEqualTo(900);
+ // AND the clock is opaque and positioned on the left.
+ assertThat(mClockPosition.clockX).isEqualTo(0);
+ assertThat(mClockPosition.clockAlpha).isEqualTo(OPAQUE);
+ }
+
+ @Test
+ public void clockPositionLargeClockOnAOD() {
+ // GIVEN on AOD with a full screen clock
+ givenAOD();
+ mNotificationStackHeight = EMPTY_HEIGHT;
+ mKeyguardStatusHeight = SCREEN_HEIGHT;
+ // WHEN the clock position algorithm is run
+ positionClock();
+ // THEN the clock Y position overflows the parent.
+ assertThat(mClockPosition.clockY).isEqualTo(-1000);
+ // AND the clock is opaque and positioned on the left.
+ assertThat(mClockPosition.clockX).isEqualTo(0);
+ assertThat(mClockPosition.clockAlpha).isEqualTo(OPAQUE);
+ }
+
+ @Test
+ public void clockPositionMiddleOfScreenOnLockScreen() {
+ // GIVEN on lock screen with stack scroll and clock of 0 height
+ givenLockScreen();
+ mNotificationStackHeight = EMPTY_HEIGHT;
+ mKeyguardStatusHeight = EMPTY_HEIGHT;
+ // WHEN the clock position algorithm is run
+ positionClock();
+ // THEN the clock Y position is the middle of the screen (SCREEN_HEIGHT / 2).
+ assertThat(mClockPosition.clockY).isEqualTo(1000);
+ // AND the clock is opaque and positioned on the left.
+ assertThat(mClockPosition.clockX).isEqualTo(0);
+ assertThat(mClockPosition.clockAlpha).isEqualTo(OPAQUE);
+ }
+
+ @Test
+ public void clockPositionWithStackScrollExpandOnLockScreen() {
+ // GIVEN on lock screen with stack scroll of height 500
+ givenLockScreen();
+ mNotificationStackHeight = 500;
+ mKeyguardStatusHeight = EMPTY_HEIGHT;
+ // WHEN the clock position algorithm is run
+ positionClock();
+ // THEN the clock Y position adjusts for stack scroll height ( (SCREEN_HEIGHT - 500 ) / 2).
+ assertThat(mClockPosition.clockY).isEqualTo(750);
+ // AND the clock is opaque and positioned on the left.
+ assertThat(mClockPosition.clockX).isEqualTo(0);
+ assertThat(mClockPosition.clockAlpha).isEqualTo(OPAQUE);
+ }
+
+ @Test
+ public void clockPositionWithPartialDragOnLockScreen() {
+ // GIVEN dragging up on lock screen
+ givenLockScreen();
+ mNotificationStackHeight = EMPTY_HEIGHT;
+ mKeyguardStatusHeight = EMPTY_HEIGHT;
+ mPanelExpansion = 0.5f;
+ // WHEN the clock position algorithm is run
+ positionClock();
+ // THEN the clock Y position adjusts with drag gesture.
+ assertThat(mClockPosition.clockY).isLessThan(1000);
+ // AND the clock is positioned on the left and not fully opaque.
+ assertThat(mClockPosition.clockX).isEqualTo(0);
+ assertThat(mClockPosition.clockAlpha).isLessThan(OPAQUE);
+ }
+
+ @Test
+ public void clockPositionWithFullDragOnLockScreen() {
+ // GIVEN the lock screen is dragged up
+ givenLockScreen();
+ mNotificationStackHeight = EMPTY_HEIGHT;
+ mKeyguardStatusHeight = EMPTY_HEIGHT;
+ mPanelExpansion = 0.f;
+ // WHEN the clock position algorithm is run
+ positionClock();
+ // THEN the clock is transparent.
+ assertThat(mClockPosition.clockAlpha).isEqualTo(TRANSPARENT);
+ }
+
+ @Test
+ public void largeClockOnLockScreenIsTransparent() {
+ // GIVEN on lock screen with a full screen clock
+ givenLockScreen();
+ mNotificationStackHeight = EMPTY_HEIGHT;
+ mKeyguardStatusHeight = SCREEN_HEIGHT;
+ // WHEN the clock position algorithm is run
+ positionClock();
+ // THEN the clock is transparent
+ assertThat(mClockPosition.clockAlpha).isEqualTo(TRANSPARENT);
+ }
+
+ private void givenAOD() {
+ mPanelExpansion = 1.f;
+ mDark = 1.f;
+ }
+
+ private void givenLockScreen() {
+ mPanelExpansion = 1.f;
+ mDark = 0.f;
+ }
+
+ private void positionClock() {
+ mClockPositionAlgorithm.setup(EMPTY_MARGIN, SCREEN_HEIGHT, mNotificationStackHeight,
+ mPanelExpansion, SCREEN_HEIGHT, mKeyguardStatusHeight, mDark, SECURE_LOCKED,
+ PULSING_NO, ZERO_DRAG);
+ mClockPositionAlgorithm.run(mClockPosition);
+ }
+}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 6ccd040..1a5b86c 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -107,7 +107,9 @@
import java.util.Objects;
import java.util.Random;
-public class PackageInstallerService extends IPackageInstaller.Stub {
+/** The service responsible for installing packages. */
+public class PackageInstallerService extends IPackageInstaller.Stub implements
+ PackageSessionProvider {
private static final String TAG = "PackageInstaller";
private static final boolean LOGD = false;
@@ -296,6 +298,7 @@
in.setInput(fis, StandardCharsets.UTF_8.name());
int type;
+ PackageInstallerSession currentSession = null;
while ((type = in.next()) != END_DOCUMENT) {
if (type == START_TAG) {
final String tag = in.getName();
@@ -303,8 +306,10 @@
final PackageInstallerSession session;
try {
session = PackageInstallerSession.readFromXml(in, mInternalCallback,
- mContext, mPm, mInstallThread.getLooper(), mSessionsDir);
+ mContext, mPm, mInstallThread.getLooper(), mSessionsDir, this);
+ currentSession = session;
} catch (Exception e) {
+ currentSession = null;
Slog.e(TAG, "Could not read session", e);
continue;
}
@@ -329,6 +334,10 @@
addHistoricalSessionLocked(session);
}
mAllocatedSessions.put(session.sessionId, true);
+ } else if (currentSession != null
+ && PackageInstallerSession.TAG_CHILD_SESSION.equals(tag)) {
+ currentSession.addChildSessionIdInternal(
+ PackageInstallerSession.readChildSessionIdFromXml(in));
}
}
}
@@ -436,70 +445,72 @@
}
}
- // Only system components can circumvent runtime permissions when installing.
- if ((params.installFlags & PackageManager.INSTALL_GRANT_RUNTIME_PERMISSIONS) != 0
- && mContext.checkCallingOrSelfPermission(Manifest.permission
- .INSTALL_GRANT_RUNTIME_PERMISSIONS) == PackageManager.PERMISSION_DENIED) {
- throw new SecurityException("You need the "
- + "android.permission.INSTALL_GRANT_RUNTIME_PERMISSIONS permission "
- + "to use the PackageManager.INSTALL_GRANT_RUNTIME_PERMISSIONS flag");
- }
-
- if ((params.installFlags & PackageManager.INSTALL_FORWARD_LOCK) != 0
- || (params.installFlags & PackageManager.INSTALL_EXTERNAL) != 0) {
- throw new IllegalArgumentException(
- "New installs into ASEC containers no longer supported");
- }
-
- // Defensively resize giant app icons
- if (params.appIcon != null) {
- final ActivityManager am = (ActivityManager) mContext.getSystemService(
- Context.ACTIVITY_SERVICE);
- final int iconSize = am.getLauncherLargeIconSize();
- if ((params.appIcon.getWidth() > iconSize * 2)
- || (params.appIcon.getHeight() > iconSize * 2)) {
- params.appIcon = Bitmap.createScaledBitmap(params.appIcon, iconSize, iconSize,
- true);
- }
- }
-
- switch (params.mode) {
- case SessionParams.MODE_FULL_INSTALL:
- case SessionParams.MODE_INHERIT_EXISTING:
- break;
- default:
- throw new IllegalArgumentException("Invalid install mode: " + params.mode);
- }
-
- // If caller requested explicit location, sanity check it, otherwise
- // resolve the best internal or adopted location.
- if ((params.installFlags & PackageManager.INSTALL_INTERNAL) != 0) {
- if (!PackageHelper.fitsOnInternal(mContext, params)) {
- throw new IOException("No suitable internal storage available");
+ if (!params.isMultiPackage) {
+ // Only system components can circumvent runtime permissions when installing.
+ if ((params.installFlags & PackageManager.INSTALL_GRANT_RUNTIME_PERMISSIONS) != 0
+ && mContext.checkCallingOrSelfPermission(Manifest.permission
+ .INSTALL_GRANT_RUNTIME_PERMISSIONS) == PackageManager.PERMISSION_DENIED) {
+ throw new SecurityException("You need the "
+ + "android.permission.INSTALL_GRANT_RUNTIME_PERMISSIONS permission "
+ + "to use the PackageManager.INSTALL_GRANT_RUNTIME_PERMISSIONS flag");
}
- } else if ((params.installFlags & PackageManager.INSTALL_EXTERNAL) != 0) {
- if (!PackageHelper.fitsOnExternal(mContext, params)) {
- throw new IOException("No suitable external storage available");
+ if ((params.installFlags & PackageManager.INSTALL_FORWARD_LOCK) != 0
+ || (params.installFlags & PackageManager.INSTALL_EXTERNAL) != 0) {
+ throw new IllegalArgumentException(
+ "New installs into ASEC containers no longer supported");
}
- } else if ((params.installFlags & PackageManager.INSTALL_FORCE_VOLUME_UUID) != 0) {
- // For now, installs to adopted media are treated as internal from
- // an install flag point-of-view.
- params.setInstallFlagsInternal();
+ // Defensively resize giant app icons
+ if (params.appIcon != null) {
+ final ActivityManager am = (ActivityManager) mContext.getSystemService(
+ Context.ACTIVITY_SERVICE);
+ final int iconSize = am.getLauncherLargeIconSize();
+ if ((params.appIcon.getWidth() > iconSize * 2)
+ || (params.appIcon.getHeight() > iconSize * 2)) {
+ params.appIcon = Bitmap.createScaledBitmap(params.appIcon, iconSize, iconSize,
+ true);
+ }
+ }
- } else {
- // For now, installs to adopted media are treated as internal from
- // an install flag point-of-view.
- params.setInstallFlagsInternal();
+ switch (params.mode) {
+ case SessionParams.MODE_FULL_INSTALL:
+ case SessionParams.MODE_INHERIT_EXISTING:
+ break;
+ default:
+ throw new IllegalArgumentException("Invalid install mode: " + params.mode);
+ }
- // Resolve best location for install, based on combination of
- // requested install flags, delta size, and manifest settings.
- final long ident = Binder.clearCallingIdentity();
- try {
- params.volumeUuid = PackageHelper.resolveInstallVolume(mContext, params);
- } finally {
- Binder.restoreCallingIdentity(ident);
+ // If caller requested explicit location, sanity check it, otherwise
+ // resolve the best internal or adopted location.
+ if ((params.installFlags & PackageManager.INSTALL_INTERNAL) != 0) {
+ if (!PackageHelper.fitsOnInternal(mContext, params)) {
+ throw new IOException("No suitable internal storage available");
+ }
+
+ } else if ((params.installFlags & PackageManager.INSTALL_EXTERNAL) != 0) {
+ if (!PackageHelper.fitsOnExternal(mContext, params)) {
+ throw new IOException("No suitable external storage available");
+ }
+
+ } else if ((params.installFlags & PackageManager.INSTALL_FORCE_VOLUME_UUID) != 0) {
+ // For now, installs to adopted media are treated as internal from
+ // an install flag point-of-view.
+ params.setInstallFlagsInternal();
+
+ } else {
+ // For now, installs to adopted media are treated as internal from
+ // an install flag point-of-view.
+ params.setInstallFlagsInternal();
+
+ // Resolve best location for install, based on combination of
+ // requested install flags, delta size, and manifest settings.
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ params.volumeUuid = PackageHelper.resolveInstallVolume(mContext, params);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
}
}
@@ -525,17 +536,19 @@
// We're staging to exactly one location
File stageDir = null;
String stageCid = null;
- if ((params.installFlags & PackageManager.INSTALL_INTERNAL) != 0) {
- final boolean isInstant =
- (params.installFlags & PackageManager.INSTALL_INSTANT_APP) != 0;
- stageDir = buildStageDir(params.volumeUuid, sessionId, isInstant);
- } else {
- stageCid = buildExternalStageCid(sessionId);
+ if (!params.isMultiPackage) {
+ if ((params.installFlags & PackageManager.INSTALL_INTERNAL) != 0) {
+ final boolean isInstant =
+ (params.installFlags & PackageManager.INSTALL_INSTANT_APP) != 0;
+ stageDir = buildStageDir(params.volumeUuid, sessionId, isInstant);
+ } else {
+ stageCid = buildExternalStageCid(sessionId);
+ }
}
-
- session = new PackageInstallerSession(mInternalCallback, mContext, mPm,
- mInstallThread.getLooper(), sessionId, userId, installerPackageName, callingUid,
- params, createdMillis, stageDir, stageCid, false, false);
+ session = new PackageInstallerSession(mInternalCallback, mContext, mPm, this,
+ mInstallThread.getLooper(), sessionId, userId, installerPackageName,
+ callingUid, params, createdMillis, stageDir, stageCid, false, false, null,
+ SessionInfo.INVALID_ID);
synchronized (mSessions) {
mSessions.put(sessionId, session);
@@ -678,7 +691,7 @@
synchronized (mSessions) {
for (int i = 0; i < mSessions.size(); i++) {
final PackageInstallerSession session = mSessions.valueAt(i);
- if (session.userId == userId) {
+ if (session.userId == userId && !session.hasParentSessionId()) {
result.add(session.generateInfo(false));
}
}
@@ -699,7 +712,7 @@
SessionInfo info = session.generateInfo(false);
if (Objects.equals(info.getInstallerPackageName(), installerPackageName)
- && session.userId == userId) {
+ && session.userId == userId && !session.hasParentSessionId()) {
result.add(info);
}
}
@@ -781,6 +794,13 @@
mCallbacks.unregister(callback);
}
+ @Override
+ public PackageInstallerSession getSession(int sessionId) {
+ synchronized (mSessions) {
+ return mSessions.get(sessionId);
+ }
+ }
+
private static int getSessionCount(SparseArray<PackageInstallerSession> sessions,
int installerUid) {
int count = 0;
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 6e45013..26f6e96 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -47,6 +47,8 @@
import android.app.admin.DeviceAdminInfo;
import android.app.admin.DevicePolicyManagerInternal;
import android.content.Context;
+import android.content.IIntentReceiver;
+import android.content.IIntentSender;
import android.content.Intent;
import android.content.IntentSender;
import android.content.pm.ApplicationInfo;
@@ -69,6 +71,7 @@
import android.os.FileBridge;
import android.os.FileUtils;
import android.os.Handler;
+import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.ParcelFileDescriptor;
@@ -90,6 +93,7 @@
import android.util.ExceptionUtils;
import android.util.MathUtils;
import android.util.Slog;
+import android.util.SparseIntArray;
import android.util.apk.ApkSignatureVerifier;
import com.android.internal.annotations.GuardedBy;
@@ -130,6 +134,7 @@
/** XML constants used for persisting a session */
static final String TAG_SESSION = "session";
+ static final String TAG_CHILD_SESSION = "childSession";
private static final String TAG_GRANTED_RUNTIME_PERMISSION = "granted-runtime-permission";
private static final String ATTR_SESSION_ID = "sessionId";
private static final String ATTR_USER_ID = "userId";
@@ -140,6 +145,8 @@
private static final String ATTR_SESSION_STAGE_CID = "sessionStageCid";
private static final String ATTR_PREPARED = "prepared";
private static final String ATTR_SEALED = "sealed";
+ private static final String ATTR_MULTI_PACKAGE = "multiPackage";
+ private static final String ATTR_PARENT_SESSION_ID = "parentSessionId";
private static final String ATTR_MODE = "mode";
private static final String ATTR_INSTALL_FLAGS = "installFlags";
private static final String ATTR_INSTALL_LOCATION = "installLocation";
@@ -157,6 +164,7 @@
private static final String ATTR_INSTALL_REASON = "installRason";
private static final String PROPERTY_NAME_INHERIT_NATIVE = "pi.inherit_native_on_dont_kill";
+ private static final int[] EMPTY_CHILD_SESSION_ARRAY = {};
// TODO: enforce INSTALL_ALLOW_TEST
// TODO: enforce INSTALL_ALLOW_DOWNGRADE
@@ -165,6 +173,7 @@
private final Context mContext;
private final PackageManagerService mPm;
private final Handler mHandler;
+ private final PackageSessionProvider mSessionProvider;
final int sessionId;
final int userId;
@@ -236,6 +245,10 @@
private long mVersionCode;
@GuardedBy("mLock")
private PackageParser.SigningDetails mSigningDetails;
+ @GuardedBy("mLock")
+ private SparseIntArray mChildSessionIds = new SparseIntArray();
+ @GuardedBy("mLock")
+ private int mParentSessionId;
/**
* Path to the validated base APK for this session, which may point at an
@@ -372,12 +385,16 @@
}
public PackageInstallerSession(PackageInstallerService.InternalCallback callback,
- Context context, PackageManagerService pm, Looper looper, int sessionId, int userId,
+ Context context, PackageManagerService pm,
+ PackageSessionProvider sessionProvider, Looper looper,
+ int sessionId, int userId,
String installerPackageName, int installerUid, SessionParams params, long createdMillis,
- File stageDir, String stageCid, boolean prepared, boolean sealed) {
+ File stageDir, String stageCid, boolean prepared, boolean sealed,
+ @Nullable int[] childSessionIds, int parentSessionId) {
mCallback = callback;
mContext = context;
mPm = pm;
+ mSessionProvider = sessionProvider;
mHandler = new Handler(looper, mHandlerCallback);
this.sessionId = sessionId;
@@ -389,8 +406,14 @@
this.createdMillis = createdMillis;
this.stageDir = stageDir;
this.stageCid = stageCid;
+ if (childSessionIds != null) {
+ for (int childSessionId : childSessionIds) {
+ mChildSessionIds.put(childSessionId, 0);
+ }
+ }
+ this.mParentSessionId = parentSessionId;
- if ((stageDir == null) == (stageCid == null)) {
+ if (!params.isMultiPackage && (stageDir == null) == (stageCid == null)) {
throw new IllegalArgumentException(
"Exactly one of stageDir or stageCid stage must be set");
}
@@ -439,6 +462,12 @@
info.referrerUri = params.referrerUri;
info.grantedRuntimePermissions = params.grantedRuntimePermissions;
info.installFlags = params.installFlags;
+ info.isMultiPackage = params.isMultiPackage;
+ info.parentSessionId = mParentSessionId;
+ info.childSessionIds = mChildSessionIds.copyKeys();
+ if (info.childSessionIds == null) {
+ info.childSessionIds = EMPTY_CHILD_SESSION_ARRAY;
+ }
}
return info;
}
@@ -762,6 +791,92 @@
@Override
public void commit(@NonNull IntentSender statusReceiver, boolean forTransfer) {
+ if (!markAsCommitted(statusReceiver, forTransfer /* enforce */)) {
+ return;
+ }
+ if (isMultiPackage()) {
+
+ final SparseIntArray remainingSessions = mChildSessionIds.clone();
+ final ChildStatusIntentReceiver localIntentReceiver =
+ new ChildStatusIntentReceiver(remainingSessions, statusReceiver);
+ for (int childSessionId : getChildSessionIds()) {
+ mSessionProvider.getSession(childSessionId)
+ .markAsCommitted(localIntentReceiver.getIntentSender(), forTransfer);
+ }
+ }
+ mHandler.obtainMessage(MSG_COMMIT).sendToTarget();
+ }
+
+ private class ChildStatusIntentReceiver {
+ private final SparseIntArray mChildSessionsRemaining;
+ private final IntentSender mStatusReceiver;
+ private final IIntentSender.Stub mLocalSender = new IIntentSender.Stub() {
+ @Override
+ public void send(int code, Intent intent, String resolvedType, IBinder whitelistToken,
+ IIntentReceiver finishedReceiver, String requiredPermission, Bundle options) {
+ statusUpdate(intent);
+ }
+ };
+
+ private ChildStatusIntentReceiver(SparseIntArray remainingSessions,
+ IntentSender statusReceiver) {
+ this.mChildSessionsRemaining = remainingSessions;
+ this.mStatusReceiver = statusReceiver;
+ }
+
+ public IntentSender getIntentSender() {
+ return new IntentSender((IIntentSender) mLocalSender);
+ }
+
+ public void statusUpdate(Intent intent) {
+ mHandler.post(() -> {
+ if (mChildSessionsRemaining.size() == 0) {
+ return;
+ }
+ final int sessionId = intent.getIntExtra(
+ PackageInstaller.EXTRA_SESSION_ID, 0);
+ final int status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS,
+ PackageInstaller.STATUS_FAILURE);
+ final int sessionIndex = mChildSessionsRemaining.indexOfKey(sessionId);
+ if (PackageInstaller.STATUS_SUCCESS == status) {
+ mChildSessionsRemaining.removeAt(sessionIndex);
+ if (mChildSessionsRemaining.size() == 0) {
+ try {
+ intent.putExtra(PackageInstaller.EXTRA_SESSION_ID,
+ PackageInstallerSession.this.sessionId);
+ mStatusReceiver.sendIntent(mContext, 0, intent, null, null);
+ } catch (IntentSender.SendIntentException ignore) {
+ }
+ }
+ } else if (PackageInstaller.STATUS_PENDING_USER_ACTION == status) {
+ try {
+ mStatusReceiver.sendIntent(mContext, 0, intent, null, null);
+ } catch (IntentSender.SendIntentException ignore) {
+ }
+ } else {
+ intent.putExtra(PackageInstaller.EXTRA_SESSION_ID,
+ PackageInstallerSession.this.sessionId);
+ mChildSessionsRemaining.clear(); // we're done. Don't send any more.
+ try {
+ mStatusReceiver.sendIntent(mContext, 0, intent, null, null);
+ } catch (IntentSender.SendIntentException ignore) {
+ }
+ }
+ });
+ }
+ }
+
+
+ /**
+ * Do everything but actually commit the session. If this was not already called, the session
+ * will be sealed and marked as committed. The caller of this method is responsible for
+ * subsequently submitting this session for processing.
+ *
+ * This method may be called multiple times to update the status receiver validate caller
+ * permissions.
+ */
+ public boolean markAsCommitted(
+ @NonNull IntentSender statusReceiver, boolean forTransfer) {
Preconditions.checkNotNull(statusReceiver);
final boolean wasSealed;
@@ -786,6 +901,12 @@
}
}
+ // After validations and updating the observer, we can skip re-sealing, etc. because we
+ // have already marked ourselves as committed.
+ if (mCommitted) {
+ return true;
+ }
+
wasSealed = mSealed;
if (!mSealed) {
try {
@@ -796,7 +917,7 @@
// Do now throw an exception here to stay compatible with O and older
destroyInternal();
dispatchSessionFinished(e.error, ExceptionUtils.getCompleteMessage(e), null);
- return;
+ return false;
}
}
@@ -809,7 +930,6 @@
mActiveCount.incrementAndGet();
mCommitted = true;
- mHandler.obtainMessage(MSG_COMMIT).sendToTarget();
}
if (!wasSealed) {
@@ -818,6 +938,7 @@
// the session lock, since otherwise it's a lock inversion.
mCallback.onSessionSealedBlocking(this);
}
+ return true;
}
/**
@@ -833,32 +954,37 @@
assertNoWriteFileTransfersOpenLocked();
assertPreparedAndNotDestroyedLocked("sealing of session");
- final PackageInfo pkgInfo = mPm.getPackageInfo(
- params.appPackageName, PackageManager.GET_SIGNATURES
- | PackageManager.MATCH_STATIC_SHARED_LIBRARIES /*flags*/, userId);
-
- resolveStageDirLocked();
-
mSealed = true;
- try {
- if ((params.installFlags & PackageManager.INSTALL_APEX) != 0) {
- validateApexInstallLocked(pkgInfo);
- } else {
- // Verify that stage looks sane with respect to existing application.
- // This currently only ensures packageName, versionCode, and certificate
- // consistency.
- validateApkInstallLocked(pkgInfo);
- }
- } catch (PackageManagerException e) {
- throw e;
- } catch (Throwable e) {
- // Convert all exceptions into package manager exceptions as only those are handled
- // in the code above
- throw new PackageManagerException(e);
- }
// Read transfers from the original owner stay open, but as the session's data
// cannot be modified anymore, there is no leak of information.
+ if (!params.isMultiPackage) {
+ final PackageInfo pkgInfo = mPm.getPackageInfo(
+ params.appPackageName, PackageManager.GET_SIGNATURES
+ | PackageManager.MATCH_STATIC_SHARED_LIBRARIES /*flags*/, userId);
+
+ resolveStageDirLocked();
+
+ // Verify that stage looks sane with respect to existing application.
+ // This currently only ensures packageName, versionCode, and certificate
+ // consistency.
+ try {
+ if ((params.installFlags & PackageManager.INSTALL_APEX) != 0) {
+ validateApexInstallLocked(pkgInfo);
+ } else {
+ // Verify that stage looks sane with respect to existing application.
+ // This currently only ensures packageName, versionCode, and certificate
+ // consistency.
+ validateApkInstallLocked(pkgInfo);
+ }
+ } catch (PackageManagerException e) {
+ throw e;
+ } catch (Throwable e) {
+ // Convert all exceptions into package manager exceptions as only those are handled
+ // in the code above
+ throw new PackageManagerException(e);
+ }
+ }
}
@Override
@@ -916,25 +1042,52 @@
@GuardedBy("mLock")
private void commitLocked()
throws PackageManagerException {
- if (mRelinquished) {
- Slog.d(TAG, "Ignoring commit after previous commit relinquished control");
+ final PackageManagerService.ActiveInstallSession committingSession =
+ makeSessionActiveLocked();
+ if (committingSession == null) {
return;
}
- if (mDestroyed) {
- throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR, "Session destroyed");
- }
- if (!mSealed) {
- throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR, "Session not sealed");
- }
-
- Preconditions.checkNotNull(mPackageName);
- Preconditions.checkNotNull(mSigningDetails);
- Preconditions.checkNotNull(mResolvedBaseFile);
-
- if ((params.installFlags & PackageManager.INSTALL_APEX) != 0) {
- commitApexLocked();
+ if (isMultiPackage()) {
+ final int[] childSessionIds = getChildSessionIds();
+ List<PackageManagerService.ActiveInstallSession> childSessions =
+ new ArrayList<>(childSessionIds.length);
+ boolean success = true;
+ PackageManagerException failure = null;
+ for (int childSessionId : getChildSessionIds()) {
+ final PackageInstallerSession session = mSessionProvider.getSession(childSessionId);
+ try {
+ final PackageManagerService.ActiveInstallSession activeSession =
+ session.makeSessionActiveLocked();
+ if (activeSession != null) {
+ if ((activeSession.getSessionParams().installFlags
+ & PackageManager.INSTALL_APEX) != 0) {
+ // TODO(b/118865310): Add exception to this case for staged installs
+ throw new PackageManagerException(
+ PackageManager.INSTALL_FAILED_INTERNAL_ERROR,
+ "Atomic install is not supported for APEX packages.");
+ }
+ childSessions.add(activeSession);
+ }
+ } catch (PackageManagerException e) {
+ failure = e;
+ success = false;
+ }
+ }
+ if (!success) {
+ try {
+ mRemoteObserver.onPackageInstalled(
+ null, failure.error, failure.getLocalizedMessage(), null);
+ } catch (RemoteException ignored) {
+ }
+ return;
+ }
+ mPm.installStage(childSessions);
} else {
- commitApkLocked();
+ if ((params.installFlags & PackageManager.INSTALL_APEX) != 0) {
+ commitApexLocked();
+ } else {
+ mPm.installStage(committingSession);
+ }
}
}
@@ -943,7 +1096,7 @@
try {
IApexService apex = IApexService.Stub.asInterface(
ServiceManager.getService("apexservice"));
- apex.installPackage(mResolvedBaseFile.toString());
+ apex.stagePackage(mResolvedBaseFile.toString());
} catch (Throwable e) {
// Convert all exceptions into package manager exceptions as only those are handled
// in the code above
@@ -954,81 +1107,105 @@
}
}
+ /**
+ * Stages this session for install and returns a
+ * {@link PackageManagerService.ActiveInstallSession} representing this new staged state or null
+ * in case permissions need to be requested before install can proceed.
+ */
@GuardedBy("mLock")
- private void commitApkLocked() throws PackageManagerException {
- if (needToAskForPermissionsLocked()) {
- // User needs to confirm installation; give installer an intent they can use to involve
- // user.
- final Intent intent = new Intent(PackageInstaller.ACTION_CONFIRM_INSTALL);
- intent.setPackage(mPm.getPackageInstallerPackageName());
- intent.putExtra(PackageInstaller.EXTRA_SESSION_ID, sessionId);
- try {
- mRemoteObserver.onUserActionRequired(intent);
- } catch (RemoteException ignored) {
- }
-
- // Commit was keeping session marked as active until now; release
- // that extra refcount so session appears idle.
- closeInternal(false);
- return;
+ private PackageManagerService.ActiveInstallSession makeSessionActiveLocked()
+ throws PackageManagerException {
+ if (mRelinquished) {
+ throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
+ "Session relinquished");
+ }
+ if (mDestroyed) {
+ throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR, "Session destroyed");
+ }
+ if (!mSealed) {
+ throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR, "Session not sealed");
}
- // Inherit any packages and native libraries from existing install that
- // haven't been overridden.
- if (params.mode == SessionParams.MODE_INHERIT_EXISTING) {
- try {
- final List<File> fromFiles = mResolvedInheritedFiles;
- final File toDir = resolveStageDirLocked();
+ if (!params.isMultiPackage) {
+ Preconditions.checkNotNull(mPackageName);
+ Preconditions.checkNotNull(mSigningDetails);
+ Preconditions.checkNotNull(mResolvedBaseFile);
- if (LOGD) Slog.d(TAG, "Inherited files: " + mResolvedInheritedFiles);
- if (!mResolvedInheritedFiles.isEmpty() && mInheritedFilesBase == null) {
- throw new IllegalStateException("mInheritedFilesBase == null");
+ if (needToAskForPermissionsLocked()) {
+ // User needs to confirm installation;
+ // give installer an intent they can use to involve
+ // user.
+ final Intent intent = new Intent(PackageInstaller.ACTION_CONFIRM_INSTALL);
+ intent.setPackage(mPm.getPackageInstallerPackageName());
+ intent.putExtra(PackageInstaller.EXTRA_SESSION_ID, sessionId);
+ try {
+ mRemoteObserver.onUserActionRequired(intent);
+ } catch (RemoteException ignored) {
}
- if (isLinkPossible(fromFiles, toDir)) {
- if (!mResolvedInstructionSets.isEmpty()) {
- final File oatDir = new File(toDir, "oat");
- createOatDirs(mResolvedInstructionSets, oatDir);
+ // Commit was keeping session marked as active until now; release
+ // that extra refcount so session appears idle.
+ closeInternal(false);
+ return null;
+ }
+
+ // Inherit any packages and native libraries from existing install that
+ // haven't been overridden.
+ if (params.mode == SessionParams.MODE_INHERIT_EXISTING) {
+ try {
+ final List<File> fromFiles = mResolvedInheritedFiles;
+ final File toDir = resolveStageDirLocked();
+
+ if (LOGD) Slog.d(TAG, "Inherited files: " + mResolvedInheritedFiles);
+ if (!mResolvedInheritedFiles.isEmpty() && mInheritedFilesBase == null) {
+ throw new IllegalStateException("mInheritedFilesBase == null");
}
- // pre-create lib dirs for linking if necessary
- if (!mResolvedNativeLibPaths.isEmpty()) {
- for (String libPath : mResolvedNativeLibPaths) {
- // "/lib/arm64" -> ["lib", "arm64"]
- final int splitIndex = libPath.lastIndexOf('/');
- if (splitIndex < 0 || splitIndex >= libPath.length() - 1) {
- Slog.e(TAG, "Skipping native library creation for linking due to "
- + "invalid path: " + libPath);
- continue;
- }
- final String libDirPath = libPath.substring(1, splitIndex);
- final File libDir = new File(toDir, libDirPath);
- if (!libDir.exists()) {
- NativeLibraryHelper.createNativeLibrarySubdir(libDir);
- }
- final String archDirPath = libPath.substring(splitIndex + 1);
- NativeLibraryHelper.createNativeLibrarySubdir(
- new File(libDir, archDirPath));
+
+ if (isLinkPossible(fromFiles, toDir)) {
+ if (!mResolvedInstructionSets.isEmpty()) {
+ final File oatDir = new File(toDir, "oat");
+ createOatDirs(mResolvedInstructionSets, oatDir);
}
+ // pre-create lib dirs for linking if necessary
+ if (!mResolvedNativeLibPaths.isEmpty()) {
+ for (String libPath : mResolvedNativeLibPaths) {
+ // "/lib/arm64" -> ["lib", "arm64"]
+ final int splitIndex = libPath.lastIndexOf('/');
+ if (splitIndex < 0 || splitIndex >= libPath.length() - 1) {
+ Slog.e(TAG,
+ "Skipping native library creation for linking due to "
+ + "invalid path: " + libPath);
+ continue;
+ }
+ final String libDirPath = libPath.substring(1, splitIndex);
+ final File libDir = new File(toDir, libDirPath);
+ if (!libDir.exists()) {
+ NativeLibraryHelper.createNativeLibrarySubdir(libDir);
+ }
+ final String archDirPath = libPath.substring(splitIndex + 1);
+ NativeLibraryHelper.createNativeLibrarySubdir(
+ new File(libDir, archDirPath));
+ }
+ }
+ linkFiles(fromFiles, toDir, mInheritedFilesBase);
+ } else {
+ // TODO: this should delegate to DCS so the system process
+ // avoids holding open FDs into containers.
+ copyFiles(fromFiles, toDir);
}
- linkFiles(fromFiles, toDir, mInheritedFilesBase);
- } else {
- // TODO: this should delegate to DCS so the system process
- // avoids holding open FDs into containers.
- copyFiles(fromFiles, toDir);
+ } catch (IOException e) {
+ throw new PackageManagerException(INSTALL_FAILED_INSUFFICIENT_STORAGE,
+ "Failed to inherit existing install", e);
}
- } catch (IOException e) {
- throw new PackageManagerException(INSTALL_FAILED_INSUFFICIENT_STORAGE,
- "Failed to inherit existing install", e);
}
+
+ // TODO: surface more granular state from dexopt
+ mInternalProgress = 0.5f;
+ computeProgressLocked(true);
+
+ // Unpack native libraries
+ extractNativeLibraries(mResolvedStageDir, params.abiOverride, mayInheritNativeLibs());
}
-
- // TODO: surface more granular state from dexopt
- mInternalProgress = 0.5f;
- computeProgressLocked(true);
-
- // Unpack native libraries
- extractNativeLibraries(mResolvedStageDir, params.abiOverride, mayInheritNativeLibs());
-
// We've reached point of no return; call into PMS to install the stage.
// Regardless of success or failure we always destroy session.
final IPackageInstallObserver2 localObserver = new IPackageInstallObserver2.Stub() {
@@ -1053,8 +1230,11 @@
}
mRelinquished = true;
- mPm.installStage(mPackageName, stageDir, localObserver, params,
- mInstallerPackageName, mInstallerUid, user, mSigningDetails);
+ final PackageManagerService.ActiveInstallSession activeInstallSession =
+ new PackageManagerService.ActiveInstallSession(mPackageName, stageDir,
+ localObserver, params, mInstallerPackageName, mInstallerUid, user,
+ mSigningDetails);
+ return activeInstallSession;
}
private static void maybeRenameFile(File from, File to) throws PackageManagerException {
@@ -1556,6 +1736,14 @@
}
}
+ /**
+ * Adds a child session ID without any safety / sanity checks. This should only be used to
+ * build a session from XML or similar.
+ */
+ void addChildSessionIdInternal(int sessionId) {
+ mChildSessionIds.put(sessionId, 0);
+ }
+
public void open() throws IOException {
if (mActiveCount.getAndIncrement() == 0) {
mCallback.onSessionActiveChanged(this, true);
@@ -1567,6 +1755,8 @@
if (!mPrepared) {
if (stageDir != null) {
prepareStageDir(stageDir);
+ } else if (params.isMultiPackage) {
+ // it's all ok
} else {
throw new IllegalArgumentException("stageDir must be set");
}
@@ -1615,6 +1805,81 @@
dispatchSessionFinished(INSTALL_FAILED_ABORTED, "Session was abandoned", null);
}
+ @Override
+ public boolean isMultiPackage() {
+ return params.isMultiPackage;
+ }
+
+ @Override
+ public int[] getChildSessionIds() {
+ final int[] childSessionIds = mChildSessionIds.copyKeys();
+ if (childSessionIds != null) {
+ return childSessionIds;
+ }
+ return EMPTY_CHILD_SESSION_ARRAY;
+ }
+
+ @Override
+ public void addChildSessionId(int sessionId) {
+ final PackageInstallerSession session = mSessionProvider.getSession(sessionId);
+ if (session == null) {
+ throw new RemoteException("Unable to add child.",
+ new PackageManagerException("Child session " + sessionId + " does not exist"),
+ false, true).rethrowAsRuntimeException();
+ }
+ synchronized (mLock) {
+ final int indexOfSession = mChildSessionIds.indexOfKey(sessionId);
+ if (indexOfSession >= 0) {
+ return;
+ }
+ session.setParentSessionId(this.sessionId);
+ addChildSessionIdInternal(sessionId);
+ }
+ }
+
+ @Override
+ public void removeChildSessionId(int sessionId) {
+ final PackageInstallerSession session = mSessionProvider.getSession(sessionId);
+ synchronized (mLock) {
+ final int indexOfSession = mChildSessionIds.indexOfKey(sessionId);
+ if (session != null) {
+ session.setParentSessionId(SessionInfo.INVALID_ID);
+ }
+ if (indexOfSession < 0) {
+ // not added in the first place; no-op
+ return;
+ }
+ mChildSessionIds.removeAt(indexOfSession);
+ }
+ }
+
+ /**
+ * Sets the parent session ID if not already set.
+ * If {@link SessionInfo#INVALID_ID} is passed, it will be unset.
+ */
+ void setParentSessionId(int parentSessionId) {
+ synchronized (mLock) {
+ if (parentSessionId != SessionInfo.INVALID_ID
+ && mParentSessionId != SessionInfo.INVALID_ID) {
+ throw new RemoteException("Unable to set parent session.",
+ new PackageManagerException(
+ "The parent of " + sessionId + " is" + " already set to "
+ + mParentSessionId), false,
+ true).rethrowAsRuntimeException();
+ }
+ this.mParentSessionId = parentSessionId;
+ }
+ }
+
+ boolean hasParentSessionId() {
+ return mParentSessionId != SessionInfo.INVALID_ID;
+ }
+
+ @Override
+ public int getParentSessionId() {
+ return mParentSessionId;
+ }
+
private void dispatchSessionFinished(int returnCode, String msg, Bundle extras) {
final IPackageInstallObserver2 observer;
final String packageName;
@@ -1704,6 +1969,7 @@
pw.printPair("mBridges", mBridges.size());
pw.printPair("mFinalStatus", mFinalStatus);
pw.printPair("mFinalMessage", mFinalMessage);
+ pw.printPair("params.isMultiPackage", params.isMultiPackage);
pw.println();
pw.decreaseIndent();
@@ -1754,6 +2020,10 @@
writeBooleanAttribute(out, ATTR_PREPARED, isPrepared());
writeBooleanAttribute(out, ATTR_SEALED, isSealed());
+ writeBooleanAttribute(out, ATTR_MULTI_PACKAGE, params.isMultiPackage);
+ // TODO(patb,109941548): avoid writing to xml and instead infer / validate this after
+ // we've read all sessions.
+ writeIntAttribute(out, ATTR_PARENT_SESSION_ID, mParentSessionId);
writeIntAttribute(out, ATTR_MODE, params.mode);
writeIntAttribute(out, ATTR_INSTALL_FLAGS, params.installFlags);
writeIntAttribute(out, ATTR_INSTALL_LOCATION, params.installLocation);
@@ -1788,6 +2058,12 @@
params.appIconLastModified = appIconFile.lastModified();
}
+ final int[] childSessionIds = getChildSessionIds();
+ for (int childSessionId : childSessionIds) {
+ out.startTag(null, TAG_CHILD_SESSION);
+ writeIntAttribute(out, ATTR_SESSION_ID, childSessionId);
+ out.endTag(null, TAG_CHILD_SESSION);
+ }
}
out.endTag(null, TAG_SESSION);
@@ -1832,11 +2108,15 @@
* @param installerThread Thread to be used for callbacks of this session
* @param sessionsDir The directory the sessions are stored in
*
+ * @param sessionProvider
* @return The newly created session
*/
+ // TODO(patb,109941548): modify readFromXml to consume to the next tag session tag so we
+ // can have a complete session for the constructor
public static PackageInstallerSession readFromXml(@NonNull XmlPullParser in,
@NonNull PackageInstallerService.InternalCallback callback, @NonNull Context context,
- @NonNull PackageManagerService pm, Looper installerThread, @NonNull File sessionsDir)
+ @NonNull PackageManagerService pm, Looper installerThread, @NonNull File sessionsDir,
+ @NonNull PackageSessionProvider sessionProvider)
throws IOException, XmlPullParserException {
final int sessionId = readIntAttribute(in, ATTR_SESSION_ID);
final int userId = readIntAttribute(in, ATTR_USER_ID);
@@ -1849,9 +2129,12 @@
final String stageCid = readStringAttribute(in, ATTR_SESSION_STAGE_CID);
final boolean prepared = readBooleanAttribute(in, ATTR_PREPARED, true);
final boolean sealed = readBooleanAttribute(in, ATTR_SEALED);
+ final int parentSessionId = readIntAttribute(in, ATTR_PARENT_SESSION_ID,
+ SessionInfo.INVALID_ID);
final SessionParams params = new SessionParams(
SessionParams.MODE_INVALID);
+ params.isMultiPackage = readBooleanAttribute(in, ATTR_MULTI_PACKAGE, false);
params.mode = readIntAttribute(in, ATTR_MODE);
params.installFlags = readIntAttribute(in, ATTR_INSTALL_FLAGS);
params.installLocation = readIntAttribute(in, ATTR_INSTALL_LOCATION);
@@ -1874,9 +2157,16 @@
params.appIcon = BitmapFactory.decodeFile(appIconFile.getAbsolutePath());
params.appIconLastModified = appIconFile.lastModified();
}
-
- return new PackageInstallerSession(callback, context, pm,
+ return new PackageInstallerSession(callback, context, pm, sessionProvider,
installerThread, sessionId, userId, installerPackageName, installerUid,
- params, createdMillis, stageDir, stageCid, prepared, sealed);
+ params, createdMillis, stageDir, stageCid, prepared, sealed,
+ EMPTY_CHILD_SESSION_ARRAY, parentSessionId);
+ }
+
+ /**
+ * Reads the session ID from a child session tag stored in the provided {@link XmlPullParser}
+ */
+ static int readChildSessionIdFromXml(@NonNull XmlPullParser in) {
+ return readIntAttribute(in, ATTR_SESSION_ID, SessionInfo.INVALID_ID);
}
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index ed3e857..af316d3 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -60,6 +60,7 @@
import static android.content.pm.PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE;
import static android.content.pm.PackageManager.INSTALL_FAILED_VERSION_DOWNGRADE;
import static android.content.pm.PackageManager.INSTALL_INTERNAL;
+import static android.content.pm.PackageManager.INSTALL_SUCCEEDED;
import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS;
import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK;
import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK;
@@ -12357,28 +12358,15 @@
return installReason;
}
- void installStage(String packageName, File stagedDir,
- IPackageInstallObserver2 observer, PackageInstaller.SessionParams sessionParams,
- String installerPackageName, int installerUid, UserHandle user,
- PackageParser.SigningDetails signingDetails) {
+ void installStage(ActiveInstallSession activeInstallSession) {
if (DEBUG_INSTANT) {
- if ((sessionParams.installFlags & PackageManager.INSTALL_INSTANT_APP) != 0) {
- Slog.d(TAG, "Ephemeral install of " + packageName);
+ if ((activeInstallSession.getSessionParams().installFlags
+ & PackageManager.INSTALL_INSTANT_APP) != 0) {
+ Slog.d(TAG, "Ephemeral install of " + activeInstallSession.getPackageName());
}
}
- final VerificationInfo verificationInfo = new VerificationInfo(
- sessionParams.originatingUri, sessionParams.referrerUri,
- sessionParams.originatingUid, installerUid);
-
- final OriginInfo origin = OriginInfo.fromStagedFile(stagedDir);
-
final Message msg = mHandler.obtainMessage(INIT_COPY);
- final int installReason = fixUpInstallReason(installerPackageName, installerUid,
- sessionParams.installReason);
- final InstallParams params = new InstallParams(origin, null, observer,
- sessionParams.installFlags, installerPackageName, sessionParams.volumeUuid,
- verificationInfo, user, sessionParams.abiOverride,
- sessionParams.grantedRuntimePermissions, signingDetails, installReason);
+ final InstallParams params = new InstallParams(activeInstallSession);
params.setTraceMethod("installStage").setTraceCookie(System.identityHashCode(params));
msg.obj = params;
@@ -12390,6 +12378,22 @@
mHandler.sendMessage(msg);
}
+ void installStage(List<ActiveInstallSession> children)
+ throws PackageManagerException {
+ final Message msg = mHandler.obtainMessage(INIT_COPY);
+ final MultiPackageInstallParams params =
+ new MultiPackageInstallParams(UserHandle.ALL, children);
+ params.setTraceMethod("installStageMultiPackage")
+ .setTraceCookie(System.identityHashCode(params));
+ msg.obj = params;
+
+ Trace.asyncTraceBegin(TRACE_TAG_PACKAGE_MANAGER, "installStageMultiPackage",
+ System.identityHashCode(msg.obj));
+ Trace.asyncTraceBegin(TRACE_TAG_PACKAGE_MANAGER, "queueInstall",
+ System.identityHashCode(msg.obj));
+ mHandler.sendMessage(msg);
+ }
+
private void sendPackageAddedForUser(String packageName, PackageSetting pkgSetting,
int userId) {
final boolean isSystem = isSystemApp(pkgSetting) || isUpdatedSystemApp(pkgSetting);
@@ -13567,88 +13571,113 @@
}
private void processPendingInstall(final InstallArgs args, final int currentStatus) {
- // Queue up an async operation since the package installation may take a little while.
- mHandler.post(new Runnable() {
- public void run() {
- mHandler.removeCallbacks(this);
- // Result object to be returned
- PackageInstalledInfo res = new PackageInstalledInfo();
- res.setReturnCode(currentStatus);
- res.uid = -1;
- res.pkg = null;
- res.removedInfo = null;
- if (res.returnCode == PackageManager.INSTALL_SUCCEEDED) {
- args.doPreInstall(res.returnCode);
- synchronized (mInstallLock) {
- installPackageTracedLI(args, res);
- }
- args.doPostInstall(res.returnCode, res.uid);
+ if (args.mMultiPackageInstallParams != null) {
+ args.mMultiPackageInstallParams.tryProcessInstallRequest(args, currentStatus);
+ } else {
+ PackageInstalledInfo res = createPackageInstalledInfo(currentStatus);
+ processInstallRequestsAsync(
+ res.returnCode == PackageManager.INSTALL_SUCCEEDED,
+ Collections.singletonList(new InstallRequest(args, res)));
+ }
+ }
+
+ // Queue up an async operation since the package installation may take a little while.
+ private void processInstallRequestsAsync(boolean success,
+ List<InstallRequest> installRequests) {
+ mHandler.post(() -> {
+ if (success) {
+ for (InstallRequest request : installRequests) {
+ request.args.doPreInstall(request.installResult.returnCode);
}
-
- // A restore should be performed at this point if (a) the install
- // succeeded, (b) the operation is not an update, and (c) the new
- // package has not opted out of backup participation.
- final boolean update = res.removedInfo != null
- && res.removedInfo.removedPackage != null;
- final int flags = (res.pkg == null) ? 0 : res.pkg.applicationInfo.flags;
- boolean doRestore = !update
- && ((flags & ApplicationInfo.FLAG_ALLOW_BACKUP) != 0);
-
- // Set up the post-install work request bookkeeping. This will be used
- // and cleaned up by the post-install event handling regardless of whether
- // there's a restore pass performed. Token values are >= 1.
- int token;
- if (mNextInstallToken < 0) mNextInstallToken = 1;
- token = mNextInstallToken++;
-
- PostInstallData data = new PostInstallData(args, res);
- mRunningInstalls.put(token, data);
- if (DEBUG_INSTALL) Log.v(TAG, "+ starting restore round-trip " + token);
-
- if (res.returnCode == PackageManager.INSTALL_SUCCEEDED && doRestore) {
- // Pass responsibility to the Backup Manager. It will perform a
- // restore if appropriate, then pass responsibility back to the
- // Package Manager to run the post-install observer callbacks
- // and broadcasts.
- IBackupManager bm = IBackupManager.Stub.asInterface(
- ServiceManager.getService(Context.BACKUP_SERVICE));
- if (bm != null) {
- if (DEBUG_INSTALL) Log.v(TAG, "token " + token
- + " to BM for possible restore");
- Trace.asyncTraceBegin(TRACE_TAG_PACKAGE_MANAGER, "restore", token);
- try {
- // TODO: http://b/22388012
- if (bm.isBackupServiceActive(UserHandle.USER_SYSTEM)) {
- bm.restoreAtInstall(res.pkg.applicationInfo.packageName, token);
- } else {
- doRestore = false;
- }
- } catch (RemoteException e) {
- // can't happen; the backup manager is local
- } catch (Exception e) {
- Slog.e(TAG, "Exception trying to enqueue restore", e);
- doRestore = false;
- }
- } else {
- Slog.e(TAG, "Backup Manager not found!");
- doRestore = false;
- }
+ synchronized (mInstallLock) {
+ installPackagesTracedLI(installRequests);
}
-
- if (!doRestore) {
- // No restore possible, or the Backup Manager was mysteriously not
- // available -- just fire the post-install work request directly.
- if (DEBUG_INSTALL) Log.v(TAG, "No restore - queue post-install for " + token);
-
- Trace.asyncTraceBegin(TRACE_TAG_PACKAGE_MANAGER, "postInstall", token);
-
- Message msg = mHandler.obtainMessage(POST_INSTALL, token, 0);
- mHandler.sendMessage(msg);
+ for (InstallRequest request : installRequests) {
+ request.args.doPostInstall(
+ request.installResult.returnCode, request.installResult.uid);
}
}
+ for (InstallRequest request : installRequests) {
+ resolvePackageInstalledInfo(request.args,
+ request.installResult);
+ }
});
}
+ private PackageInstalledInfo createPackageInstalledInfo(
+ int currentStatus) {
+ PackageInstalledInfo res = new PackageInstalledInfo();
+ res.setReturnCode(currentStatus);
+ res.uid = -1;
+ res.pkg = null;
+ res.removedInfo = null;
+ return res;
+ }
+
+ private void resolvePackageInstalledInfo(InstallArgs args, PackageInstalledInfo res) {
+ // A restore should be performed at this point if (a) the install
+ // succeeded, (b) the operation is not an update, and (c) the new
+ // package has not opted out of backup participation.
+ final boolean update = res.removedInfo != null
+ && res.removedInfo.removedPackage != null;
+ final int flags = (res.pkg == null) ? 0 : res.pkg.applicationInfo.flags;
+ boolean doRestore = !update
+ && ((flags & ApplicationInfo.FLAG_ALLOW_BACKUP) != 0);
+
+ // Set up the post-install work request bookkeeping. This will be used
+ // and cleaned up by the post-install event handling regardless of whether
+ // there's a restore pass performed. Token values are >= 1.
+ int token;
+ if (mNextInstallToken < 0) mNextInstallToken = 1;
+ token = mNextInstallToken++;
+
+ PostInstallData data = new PostInstallData(args, res);
+ mRunningInstalls.put(token, data);
+ if (DEBUG_INSTALL) Log.v(TAG, "+ starting restore round-trip " + token);
+
+ if (res.returnCode == PackageManager.INSTALL_SUCCEEDED && doRestore) {
+ // Pass responsibility to the Backup Manager. It will perform a
+ // restore if appropriate, then pass responsibility back to the
+ // Package Manager to run the post-install observer callbacks
+ // and broadcasts.
+ IBackupManager bm = IBackupManager.Stub.asInterface(
+ ServiceManager.getService(Context.BACKUP_SERVICE));
+ if (bm != null) {
+ if (DEBUG_INSTALL) {
+ Log.v(TAG, "token " + token + " to BM for possible restore");
+ }
+ Trace.asyncTraceBegin(TRACE_TAG_PACKAGE_MANAGER, "restore", token);
+ try {
+ // TODO: http://b/22388012
+ if (bm.isBackupServiceActive(UserHandle.USER_SYSTEM)) {
+ bm.restoreAtInstall(res.pkg.applicationInfo.packageName, token);
+ } else {
+ doRestore = false;
+ }
+ } catch (RemoteException e) {
+ // can't happen; the backup manager is local
+ } catch (Exception e) {
+ Slog.e(TAG, "Exception trying to enqueue restore", e);
+ doRestore = false;
+ }
+ } else {
+ Slog.e(TAG, "Backup Manager not found!");
+ doRestore = false;
+ }
+ }
+
+ if (!doRestore) {
+ // No restore possible, or the Backup Manager was mysteriously not
+ // available -- just fire the post-install work request directly.
+ if (DEBUG_INSTALL) Log.v(TAG, "No restore - queue post-install for " + token);
+
+ Trace.asyncTraceBegin(TRACE_TAG_PACKAGE_MANAGER, "postInstall", token);
+
+ Message msg = mHandler.obtainMessage(POST_INSTALL, token, 0);
+ mHandler.sendMessage(msg);
+ }
+ }
+
/**
* Callback from PackageSettings whenever an app is first transitioned out of the
* 'stopped' state. Normally we just issue the broadcast, but we can't do that if
@@ -13836,7 +13865,83 @@
}
}
+ /**
+ * Container for a multi-package install which refers to all install sessions and args being
+ * committed together.
+ */
+ class MultiPackageInstallParams extends HandlerParams {
+
+ private int mRet = INSTALL_SUCCEEDED;
+ @NonNull
+ private final ArrayList<InstallParams> mChildParams;
+ @NonNull
+ private final Map<InstallArgs, Integer> mVerifiedState;
+
+ MultiPackageInstallParams(
+ @NonNull UserHandle user,
+ @NonNull List<ActiveInstallSession> activeInstallSessions)
+ throws PackageManagerException {
+ super(user);
+ if (activeInstallSessions.size() == 0) {
+ throw new PackageManagerException("No child sessions found!");
+ }
+ mChildParams = new ArrayList<>(activeInstallSessions.size());
+ for (int i = 0; i < activeInstallSessions.size(); i++) {
+ final InstallParams childParams = new InstallParams(activeInstallSessions.get(i));
+ childParams.mParentInstallParams = this;
+ this.mChildParams.add(childParams);
+ }
+ this.mVerifiedState = new ArrayMap<>(mChildParams.size());
+ }
+
+ @Override
+ void handleStartCopy() {
+ for (InstallParams params : mChildParams) {
+ params.handleStartCopy();
+ if (params.mRet != INSTALL_SUCCEEDED) {
+ mRet = params.mRet;
+ break;
+ }
+ }
+ }
+
+ @Override
+ void handleReturnCode() {
+ for (InstallParams params : mChildParams) {
+ params.handleReturnCode();
+ if (params.mRet != INSTALL_SUCCEEDED) {
+ mRet = params.mRet;
+ break;
+ }
+ }
+ }
+
+ void tryProcessInstallRequest(InstallArgs args, int currentStatus) {
+ mVerifiedState.put(args, currentStatus);
+ boolean success = true;
+ if (mVerifiedState.size() != mChildParams.size()) {
+ return;
+ }
+ for (Integer status : mVerifiedState.values()) {
+ if (status == PackageManager.INSTALL_UNKNOWN) {
+ return;
+ } else if (status != PackageManager.INSTALL_SUCCEEDED) {
+ success = false;
+ break;
+ }
+ }
+ final List<InstallRequest> installRequests = new ArrayList<>(mVerifiedState.size());
+ for (Map.Entry<InstallArgs, Integer> entry : mVerifiedState.entrySet()) {
+ installRequests.add(new InstallRequest(entry.getKey(),
+ createPackageInstalledInfo(entry.getValue())));
+ }
+ processInstallRequestsAsync(success, installRequests);
+ }
+ }
+
class InstallParams extends HandlerParams {
+ // TODO: see if we can collapse this into ActiveInstallSession
+
final OriginInfo origin;
final MoveInfo move;
final IPackageInstallObserver2 observer;
@@ -13844,17 +13949,20 @@
final String installerPackageName;
final String volumeUuid;
private InstallArgs mArgs;
- private int mRet;
+ int mRet;
final String packageAbiOverride;
final String[] grantedRuntimePermissions;
final VerificationInfo verificationInfo;
final PackageParser.SigningDetails signingDetails;
final int installReason;
+ @Nullable
+ MultiPackageInstallParams mParentInstallParams;
InstallParams(OriginInfo origin, MoveInfo move, IPackageInstallObserver2 observer,
int installFlags, String installerPackageName, String volumeUuid,
VerificationInfo verificationInfo, UserHandle user, String packageAbiOverride,
- String[] grantedPermissions, PackageParser.SigningDetails signingDetails, int installReason) {
+ String[] grantedPermissions, PackageParser.SigningDetails signingDetails,
+ int installReason) {
super(user);
this.origin = origin;
this.move = move;
@@ -13869,6 +13977,34 @@
this.installReason = installReason;
}
+ InstallParams(ActiveInstallSession activeInstallSession) {
+ super(activeInstallSession.getUser());
+ if (DEBUG_INSTANT) {
+ if ((activeInstallSession.getSessionParams().installFlags
+ & PackageManager.INSTALL_INSTANT_APP) != 0) {
+ Slog.d(TAG, "Ephemeral install of " + activeInstallSession.getPackageName());
+ }
+ }
+ verificationInfo = new VerificationInfo(
+ activeInstallSession.getSessionParams().originatingUri,
+ activeInstallSession.getSessionParams().referrerUri,
+ activeInstallSession.getSessionParams().originatingUid,
+ activeInstallSession.getInstallerUid());
+ origin = OriginInfo.fromStagedFile(activeInstallSession.getStagedDir());
+ move = null;
+ installReason = fixUpInstallReason(activeInstallSession.getInstallerPackageName(),
+ activeInstallSession.getInstallerUid(),
+ activeInstallSession.getSessionParams().installReason);
+ observer = activeInstallSession.getObserver();
+ installFlags = activeInstallSession.getSessionParams().installFlags;
+ installerPackageName = activeInstallSession.getInstallerPackageName();
+ volumeUuid = activeInstallSession.getSessionParams().volumeUuid;
+ packageAbiOverride = activeInstallSession.getSessionParams().abiOverride;
+ grantedRuntimePermissions =
+ activeInstallSession.getSessionParams().grantedRuntimePermissions;
+ signingDetails = activeInstallSession.getSigningDetails();
+ }
+
@Override
public String toString() {
return "InstallParams{" + Integer.toHexString(System.identityHashCode(this))
@@ -14290,6 +14426,7 @@
final int traceCookie;
final PackageParser.SigningDetails signingDetails;
final int installReason;
+ @Nullable final MultiPackageInstallParams mMultiPackageInstallParams;
// The list of instruction sets supported by this app. This is currently
// only used during the rmdex() phase to clean up resources. We can get rid of this
@@ -14300,8 +14437,9 @@
int installFlags, String installerPackageName, String volumeUuid,
UserHandle user, String[] instructionSets,
String abiOverride, String[] installGrantPermissions,
- String traceMethod, int traceCookie, PackageParser.SigningDetails signingDetails,
- int installReason) {
+ String traceMethod, int traceCookie, SigningDetails signingDetails,
+ int installReason,
+ MultiPackageInstallParams multiPackageInstallParams) {
this.origin = origin;
this.move = move;
this.installFlags = installFlags;
@@ -14316,6 +14454,7 @@
this.traceCookie = traceCookie;
this.signingDetails = signingDetails;
this.installReason = installReason;
+ this.mMultiPackageInstallParams = multiPackageInstallParams;
}
abstract int copyApk();
@@ -14411,7 +14550,7 @@
params.getUser(), null /*instructionSets*/, params.packageAbiOverride,
params.grantedRuntimePermissions,
params.traceMethod, params.traceCookie, params.signingDetails,
- params.installReason);
+ params.installReason, params.mParentInstallParams);
if (isFwdLocked()) {
throw new IllegalArgumentException("Forward locking only supported in ASEC");
}
@@ -14421,7 +14560,7 @@
FileInstallArgs(String codePath, String resourcePath, String[] instructionSets) {
super(OriginInfo.fromNothing(), null, null, 0, null, null, null, instructionSets,
null, null, null, 0, PackageParser.SigningDetails.UNKNOWN,
- PackageManager.INSTALL_REASON_UNKNOWN);
+ PackageManager.INSTALL_REASON_UNKNOWN, null /* parent */);
this.codeFile = (codePath != null) ? new File(codePath) : null;
this.resourceFile = (resourcePath != null) ? new File(resourcePath) : null;
}
@@ -14613,7 +14752,7 @@
params.getUser(), null /* instruction sets */, params.packageAbiOverride,
params.grantedRuntimePermissions,
params.traceMethod, params.traceCookie, params.signingDetails,
- params.installReason);
+ params.installReason, params.mParentInstallParams);
}
int copyApk() {
@@ -15018,10 +15157,10 @@
}
@GuardedBy({"mInstallLock", "mPackages"})
- private void installPackageTracedLI(InstallArgs args, PackageInstalledInfo installResult) {
+ private void installPackagesTracedLI(List<InstallRequest> requests) {
try {
- Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "installPackage");
- installPackagesLI(Collections.singletonList(new InstallRequest(args, installResult)));
+ Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "installPackages");
+ installPackagesLI(requests);
} finally {
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
@@ -15361,7 +15500,7 @@
request.installResult.setError(
PackageManager.INSTALL_FAILED_DUPLICATE_PACKAGE,
"Duplicate package " + result.pkgSetting.pkg.packageName
- + " in atomic install request.");
+ + " in multi-package install request.");
return;
}
}
@@ -23433,6 +23572,62 @@
return mProtectedPackages.isPackageStateProtected(userId, packageName);
}
+
+ static class ActiveInstallSession {
+ private final String mPackageName;
+ private final File mStagedDir;
+ private final IPackageInstallObserver2 mObserver;
+ private final PackageInstaller.SessionParams mSessionParams;
+ private final String mInstallerPackageName;
+ private final int mInstallerUid;
+ private final UserHandle mUser;
+ private final SigningDetails mSigningDetails;
+
+ ActiveInstallSession(String packageName, File stagedDir, IPackageInstallObserver2 observer,
+ PackageInstaller.SessionParams sessionParams, String installerPackageName,
+ int installerUid, UserHandle user, SigningDetails signingDetails) {
+ mPackageName = packageName;
+ mStagedDir = stagedDir;
+ mObserver = observer;
+ mSessionParams = sessionParams;
+ mInstallerPackageName = installerPackageName;
+ mInstallerUid = installerUid;
+ mUser = user;
+ mSigningDetails = signingDetails;
+ }
+
+ public String getPackageName() {
+ return mPackageName;
+ }
+
+ public File getStagedDir() {
+ return mStagedDir;
+ }
+
+ public IPackageInstallObserver2 getObserver() {
+ return mObserver;
+ }
+
+ public PackageInstaller.SessionParams getSessionParams() {
+ return mSessionParams;
+ }
+
+ public String getInstallerPackageName() {
+ return mInstallerPackageName;
+ }
+
+ public int getInstallerUid() {
+ return mInstallerUid;
+ }
+
+ public UserHandle getUser() {
+ return mUser;
+ }
+
+ public SigningDetails getSigningDetails() {
+ return mSigningDetails;
+ }
+ }
}
interface PackageSender {
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 38bd172..31711e5 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -173,6 +173,8 @@
return runSetInstallLocation();
case "get-install-location":
return runGetInstallLocation();
+ case "install-add-session":
+ return runInstallAddSession();
case "move-package":
return runMovePackage();
case "move-primary-storage":
@@ -983,6 +985,23 @@
return doWriteSplit(sessionId, path, sizeBytes, splitName, true /*logSuccess*/);
}
+ private int runInstallAddSession() throws RemoteException {
+ final PrintWriter pw = getOutPrintWriter();
+ final int parentSessionId = Integer.parseInt(getNextArg());
+
+ List<Integer> otherSessionIds = new ArrayList<>();
+ String opt;
+ while ((opt = getNextArg()) != null) {
+ otherSessionIds.add(Integer.parseInt(opt));
+ }
+ if (otherSessionIds.size() == 0) {
+ pw.println("Error: At least two sessions are required.");
+ return 1;
+ }
+ return doInstallAddSession(parentSessionId, ArrayUtils.convertToIntArray(otherSessionIds),
+ true /*logSuccess*/);
+ }
+
private int runInstallRemove() throws RemoteException {
final PrintWriter pw = getOutPrintWriter();
@@ -2268,6 +2287,9 @@
case "--apex":
sessionParams.installFlags |= PackageManager.INSTALL_APEX;
break;
+ case "--multi-package":
+ sessionParams.setMultiPackage();
+ break;
default:
throw new IllegalArgumentException("Unknown option " + opt);
}
@@ -2500,6 +2522,30 @@
}
}
+ private int doInstallAddSession(int parentId, int[] sessionIds, boolean logSuccess)
+ throws RemoteException {
+ final PrintWriter pw = getOutPrintWriter();
+ PackageInstaller.Session session = null;
+ try {
+ session = new PackageInstaller.Session(
+ mInterface.getPackageInstaller().openSession(parentId));
+ if (!session.isMultiPackage()) {
+ getErrPrintWriter().println(
+ "Error: parent session ID is not a multi-package session");
+ return 1;
+ }
+ for (int i = 0; i < sessionIds.length; i++) {
+ session.addChildSessionId(sessionIds[i]);
+ }
+ if (logSuccess) {
+ pw.println("Success");
+ }
+ return 0;
+ } finally {
+ IoUtils.closeQuietly(session);
+ }
+ }
+
private int doRemoveSplit(int sessionId, String splitName, boolean logSuccess)
throws RemoteException {
final PrintWriter pw = getOutPrintWriter();
@@ -2521,24 +2567,26 @@
}
}
- private int doCommitSession(int sessionId, boolean logSuccess) throws RemoteException {
+ private int doCommitSession(int sessionId, boolean logSuccess)
+ throws RemoteException {
+
final PrintWriter pw = getOutPrintWriter();
PackageInstaller.Session session = null;
try {
session = new PackageInstaller.Session(
mInterface.getPackageInstaller().openSession(sessionId));
-
- // Sanity check that all .dm files match an apk.
- // (The installer does not support standalone .dm files and will not process them.)
- try {
- DexMetadataHelper.validateDexPaths(session.getNames());
- } catch (IllegalStateException | IOException e) {
- pw.println("Warning [Could not validate the dex paths: " + e.getMessage() + "]");
+ if (!session.isMultiPackage()) {
+ // Sanity check that all .dm files match an apk.
+ // (The installer does not support standalone .dm files and will not process them.)
+ try {
+ DexMetadataHelper.validateDexPaths(session.getNames());
+ } catch (IllegalStateException | IOException e) {
+ pw.println(
+ "Warning [Could not validate the dex paths: " + e.getMessage() + "]");
+ }
}
-
final LocalIntentReceiver receiver = new LocalIntentReceiver();
session.commit(receiver.getIntentSender());
-
final Intent result = receiver.getResult();
final int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
PackageInstaller.STATUS_FAILURE);
@@ -2809,6 +2857,7 @@
pw.println(" [--referrer URI] [--abi ABI_NAME] [--force-sdk]");
pw.println(" [--preload] [--instantapp] [--full] [--dont-kill]");
pw.println(" [--force-uuid internal|UUID] [--pkg PACKAGE] [-S BYTES]");
+ pw.println(" [--multi-package]");
pw.println(" Like \"install\", but starts an install session. Use \"install-write\"");
pw.println(" to push data into the session, and \"install-commit\" to finish.");
pw.println("");
@@ -2817,6 +2866,9 @@
pw.println(" will be read from stdin. Options are:");
pw.println(" -S: size in bytes of package, required for stdin");
pw.println("");
+ pw.println(" install-add-session MULTI_PACKAGE_SESSION_ID CHILD_SESSION_IDs");
+ pw.println(" Add one or more session IDs to a multi-package session.");
+ pw.println("");
pw.println(" install-commit SESSION_ID");
pw.println(" Commit the given active install session, installing the app.");
pw.println("");
diff --git a/services/core/java/com/android/server/pm/PackageSessionProvider.java b/services/core/java/com/android/server/pm/PackageSessionProvider.java
new file mode 100644
index 0000000..af11e77
--- /dev/null
+++ b/services/core/java/com/android/server/pm/PackageSessionProvider.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2018 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 com.android.server.pm;
+
+/** Provides access to individual sessions managed by the install service */
+public interface PackageSessionProvider {
+
+ /**
+ * Get the sessions for the provided session IDs. Null will be returned for session IDs that
+ * do not exist.
+ */
+ PackageInstallerSession getSession(int sessionId);
+
+}
diff --git a/services/core/java/com/android/server/pm/permission/OWNERS b/services/core/java/com/android/server/pm/permission/OWNERS
index ffc4731..88b97ea 100644
--- a/services/core/java/com/android/server/pm/permission/OWNERS
+++ b/services/core/java/com/android/server/pm/permission/OWNERS
@@ -1,8 +1,9 @@
per-file DefaultPermissionGrantPolicy.java = bpoiesz@google.com
-per-file DefaultPermissionGrantPolicy.java = fkupolov@google.com
per-file DefaultPermissionGrantPolicy.java = hackbod@android.com
per-file DefaultPermissionGrantPolicy.java = jsharkey@android.com
per-file DefaultPermissionGrantPolicy.java = svetoslavganov@google.com
per-file DefaultPermissionGrantPolicy.java = toddke@google.com
per-file DefaultPermissionGrantPolicy.java = yamasani@google.com
per-file DefaultPermissionGrantPolicy.java = patb@google.com
+per-file DefaultPermissionGrantPolicy.java = eugenesusla@google.com
+per-file DefaultPermissionGrantPolicy.java = moltmann@google.com
diff --git a/startop/view_compiler/Android.bp b/startop/view_compiler/Android.bp
index 3681529..c39688c 100644
--- a/startop/view_compiler/Android.bp
+++ b/startop/view_compiler/Android.bp
@@ -16,10 +16,15 @@
cc_defaults {
name: "viewcompiler_defaults",
+ header_libs: [
+ "libbase_headers",
+ ],
shared_libs: [
+ "libbase",
"libdexfile",
"slicer",
],
+ cppflags: ["-std=c++17"],
}
cc_library_host_static {
@@ -30,9 +35,6 @@
"java_lang_builder.cc",
"util.cc",
],
- static_libs: [
- "libbase",
- ],
}
cc_binary_host {
@@ -42,7 +44,6 @@
"main.cc",
],
static_libs: [
- "libbase",
"libtinyxml2",
"libgflags",
"libviewcompiler",
@@ -59,4 +60,5 @@
static_libs: [
"libviewcompiler",
],
+ test_suites: ["general-tests"],
}
diff --git a/startop/view_compiler/TEST_MAPPING b/startop/view_compiler/TEST_MAPPING
deleted file mode 100644
index cc4b17a..0000000
--- a/startop/view_compiler/TEST_MAPPING
+++ /dev/null
@@ -1,7 +0,0 @@
-{
- "presubmit": [
- {
- "name": "view-compiler-tests"
- }
- ]
-}
diff --git a/startop/view_compiler/dex_builder.cc b/startop/view_compiler/dex_builder.cc
index 7a9f41f..13e7f73 100644
--- a/startop/view_compiler/dex_builder.cc
+++ b/startop/view_compiler/dex_builder.cc
@@ -22,14 +22,16 @@
#include <fstream>
#include <memory>
+#define DCHECK_NOT_NULL(p) DCHECK((p) != nullptr)
+
namespace startop {
namespace dex {
using std::shared_ptr;
using std::string;
-using art::Instruction;
using ::dex::kAccPublic;
+using Op = Instruction::Op;
const TypeDescriptor TypeDescriptor::Int() { return TypeDescriptor{"I"}; };
const TypeDescriptor TypeDescriptor::Void() { return TypeDescriptor{"V"}; };
@@ -43,6 +45,20 @@
} // namespace
+std::ostream& operator<<(std::ostream& out, const Instruction::Op& opcode) {
+ switch (opcode) {
+ case Instruction::Op::kReturn:
+ out << "kReturn";
+ return out;
+ case Instruction::Op::kMove:
+ out << "kMove";
+ return out;
+ case Instruction::Op::kInvokeVirtual:
+ out << "kInvokeVirtual";
+ return out;
+ }
+}
+
void* TrackingAllocator::Allocate(size_t size) {
std::unique_ptr<uint8_t[]> buffer = std::make_unique<uint8_t[]>(size);
void* raw_buffer = buffer.get();
@@ -56,7 +72,7 @@
//
// package dextest;
// public class DexTest {
-// public static int foo() { return 5; }
+// public static int foo(String s) { return s.length(); }
// }
void WriteTestDexFile(const string& filename) {
DexBuilder dex_file;
@@ -64,11 +80,17 @@
ClassBuilder cbuilder{dex_file.MakeClass("dextest.DexTest")};
cbuilder.set_source_file("dextest.java");
- MethodBuilder method{cbuilder.CreateMethod("foo", Prototype{TypeDescriptor::Int()})};
+ TypeDescriptor string_type = TypeDescriptor::FromClassname("java.lang.String");
- MethodBuilder::Register r = method.MakeRegister();
- method.BuildConst4(r, 5);
- method.BuildReturn(r);
+ MethodBuilder method{cbuilder.CreateMethod("foo", Prototype{TypeDescriptor::Int(), string_type})};
+
+ Value result = method.MakeRegister();
+
+ MethodDeclData string_length =
+ dex_file.GetOrDeclareMethod(string_type, "length", Prototype{TypeDescriptor::Int()});
+
+ method.AddInstruction(Instruction::InvokeVirtual(string_length.id, result, Value::Parameter(0)));
+ method.BuildReturn(result);
method.Encode();
@@ -78,6 +100,10 @@
out_file.write(image.ptr<const char>(), image.size());
}
+TypeDescriptor TypeDescriptor::FromClassname(const std::string& name) {
+ return TypeDescriptor{art::DotToDescriptor(name.c_str())};
+}
+
DexBuilder::DexBuilder() : dex_file_{std::make_shared<ir::DexFile>()} {
dex_file_->magic = slicer::MemView{kDexFileMagic, sizeof(kDexFileMagic)};
}
@@ -119,10 +145,9 @@
class_def->type = type_def;
class_def->super_class = GetOrAddType(art::DotToDescriptor("java.lang.Object"));
class_def->access_flags = kAccPublic;
- return ClassBuilder{this, class_def};
+ return ClassBuilder{this, name, class_def};
}
-// TODO(eholk): we probably want GetOrAddString() also
ir::Type* DexBuilder::GetOrAddType(const std::string& descriptor) {
if (types_by_descriptor_.find(descriptor) != types_by_descriptor_.end()) {
return types_by_descriptor_[descriptor];
@@ -158,16 +183,11 @@
return shorty;
}
-ClassBuilder::ClassBuilder(DexBuilder* parent, ir::Class* class_def)
- : parent_(parent), class_(class_def) {}
+ClassBuilder::ClassBuilder(DexBuilder* parent, const std::string& name, ir::Class* class_def)
+ : parent_(parent), type_descriptor_{TypeDescriptor::FromClassname(name)}, class_(class_def) {}
MethodBuilder ClassBuilder::CreateMethod(const std::string& name, Prototype prototype) {
- ir::String* dex_name{parent_->GetOrAddString(name)};
-
- auto* decl = parent_->Alloc<ir::MethodDecl>();
- decl->name = dex_name;
- decl->parent = class_->type;
- decl->prototype = prototype.Encode(parent_);
+ ir::MethodDecl* decl = parent_->GetOrDeclareMethod(type_descriptor_, name, prototype).decl;
return MethodBuilder{parent_, class_, decl};
}
@@ -187,8 +207,13 @@
method->access_flags = kAccPublic | ::dex::kAccStatic;
auto* code = dex_->Alloc<ir::Code>();
- code->registers = num_registers_;
- // TODO: support ins and outs
+ DCHECK_NOT_NULL(decl_->prototype);
+ size_t const num_args =
+ decl_->prototype->param_types != nullptr ? decl_->prototype->param_types->types.size() : 0;
+ code->registers = num_registers_ + num_args;
+ code->ins_count = num_args;
+ code->outs_count = decl_->prototype->return_type == dex_->GetOrAddType("V") ? 0 : 1;
+ EncodeInstructions();
code->instructions = slicer::ArrayView<const ::dex::u2>(buffer_.data(), buffer_.size());
method->code = code;
@@ -197,17 +222,135 @@
return method;
}
-MethodBuilder::Register MethodBuilder::MakeRegister() { return num_registers_++; }
+Value MethodBuilder::MakeRegister() { return Value::Local(num_registers_++); }
-void MethodBuilder::BuildReturn() { buffer_.push_back(Instruction::RETURN_VOID); }
+void MethodBuilder::AddInstruction(Instruction instruction) {
+ instructions_.push_back(instruction);
+}
-void MethodBuilder::BuildReturn(Register src) { buffer_.push_back(Instruction::RETURN | src << 8); }
+void MethodBuilder::BuildReturn() { AddInstruction(Instruction::OpNoArgs(Op::kReturn)); }
-void MethodBuilder::BuildConst4(Register target, int value) {
+void MethodBuilder::BuildReturn(Value src) {
+ AddInstruction(Instruction::OpWithArgs(Op::kReturn, /*destination=*/{}, src));
+}
+
+void MethodBuilder::BuildConst4(Value target, int value) {
DCHECK_LT(value, 16);
- // TODO: support more registers
- DCHECK_LT(target, 16);
- buffer_.push_back(Instruction::CONST_4 | (value << 12) | (target << 8));
+ AddInstruction(Instruction::OpWithArgs(Op::kMove, target, Value::Immediate(value)));
+}
+
+void MethodBuilder::EncodeInstructions() {
+ buffer_.clear();
+ for (const auto& instruction : instructions_) {
+ EncodeInstruction(instruction);
+ }
+}
+
+void MethodBuilder::EncodeInstruction(const Instruction& instruction) {
+ switch (instruction.opcode()) {
+ case Instruction::Op::kReturn:
+ return EncodeReturn(instruction);
+ case Instruction::Op::kMove:
+ return EncodeMove(instruction);
+ case Instruction::Op::kInvokeVirtual:
+ return EncodeInvokeVirtual(instruction);
+ }
+}
+
+void MethodBuilder::EncodeReturn(const Instruction& instruction) {
+ DCHECK_EQ(Instruction::Op::kReturn, instruction.opcode());
+ DCHECK(!instruction.dest().has_value());
+ if (instruction.args().size() == 0) {
+ buffer_.push_back(art::Instruction::RETURN_VOID);
+ } else {
+ DCHECK(instruction.args().size() == 1);
+ size_t source = RegisterValue(instruction.args()[0]);
+ buffer_.push_back(art::Instruction::RETURN | source << 8);
+ }
+}
+
+void MethodBuilder::EncodeMove(const Instruction& instruction) {
+ DCHECK_EQ(Instruction::Op::kMove, instruction.opcode());
+ DCHECK(instruction.dest().has_value());
+ DCHECK(instruction.dest()->is_register() || instruction.dest()->is_parameter());
+ DCHECK_EQ(1, instruction.args().size());
+
+ const Value& source = instruction.args()[0];
+
+ if (source.is_immediate()) {
+ // TODO: support more registers
+ DCHECK_LT(RegisterValue(*instruction.dest()), 16);
+ DCHECK_LT(source.value(), 16);
+ buffer_.push_back(art::Instruction::CONST_4 | (source.value() << 12) |
+ (RegisterValue(*instruction.dest()) << 8));
+ } else {
+ UNIMPLEMENTED(FATAL);
+ }
+}
+
+void MethodBuilder::EncodeInvokeVirtual(const Instruction& instruction) {
+ DCHECK_EQ(Instruction::Op::kInvokeVirtual, instruction.opcode());
+
+ // TODO: support more than one argument (i.e. the this argument) and change this to DCHECK_GE
+ DCHECK_EQ(1, instruction.args().size());
+
+ const Value& this_arg = instruction.args()[0];
+
+ size_t real_reg = RegisterValue(this_arg) & 0xf;
+ buffer_.push_back(1 << 12 | art::Instruction::INVOKE_VIRTUAL);
+ buffer_.push_back(instruction.method_id());
+ buffer_.push_back(real_reg);
+
+ if (instruction.dest().has_value()) {
+ real_reg = RegisterValue(*instruction.dest());
+ buffer_.push_back(real_reg << 8 | art::Instruction::MOVE_RESULT);
+ }
+}
+
+size_t MethodBuilder::RegisterValue(Value value) const {
+ if (value.is_register()) {
+ return value.value();
+ } else if (value.is_parameter()) {
+ return value.value() + num_registers_;
+ }
+ DCHECK(false && "Must be either a parameter or a register");
+ return 0;
+}
+
+const MethodDeclData& DexBuilder::GetOrDeclareMethod(TypeDescriptor type, const std::string& name,
+ Prototype prototype) {
+ MethodDeclData& entry = method_id_map_[{type, name, prototype}];
+
+ if (entry.decl == nullptr) {
+ // This method has not already been declared, so declare it.
+ ir::MethodDecl* decl = dex_file_->Alloc<ir::MethodDecl>();
+ // The method id is the last added method.
+ size_t id = dex_file_->methods.size() - 1;
+
+ ir::String* dex_name{GetOrAddString(name)};
+ decl->name = dex_name;
+ decl->parent = GetOrAddType(type.descriptor());
+ decl->prototype = GetOrEncodeProto(prototype);
+
+ // update the index -> ir node map (see tools/dexter/slicer/dex_ir_builder.cc)
+ auto new_index = dex_file_->methods_indexes.AllocateIndex();
+ auto& ir_node = dex_file_->methods_map[new_index];
+ SLICER_CHECK(ir_node == nullptr);
+ ir_node = decl;
+ decl->orig_index = new_index;
+
+ entry = {id, decl};
+ }
+
+ return entry;
+}
+
+ir::Proto* DexBuilder::GetOrEncodeProto(Prototype prototype) {
+ ir::Proto*& ir_proto = proto_map_[prototype];
+ if (ir_proto == nullptr) {
+ ir_proto = prototype.Encode(this);
+ }
+ return ir_proto;
}
} // namespace dex
diff --git a/startop/view_compiler/dex_builder.h b/startop/view_compiler/dex_builder.h
index d280abc..e46655e 100644
--- a/startop/view_compiler/dex_builder.h
+++ b/startop/view_compiler/dex_builder.h
@@ -17,7 +17,9 @@
#define DEX_BUILDER_H_
#include <map>
+#include <optional>
#include <string>
+#include <unordered_map>
#include <vector>
#include "slicer/dex_ir.h"
@@ -45,7 +47,7 @@
virtual void Free(void* ptr);
private:
- std::map<void*, std::unique_ptr<uint8_t[]>> allocations_;
+ std::unordered_map<void*, std::unique_ptr<uint8_t[]>> allocations_;
};
// Represents a DEX type descriptor.
@@ -57,11 +59,17 @@
static const TypeDescriptor Int();
static const TypeDescriptor Void();
+ // Creates a type descriptor from a fully-qualified class name. For example, it turns the class
+ // name java.lang.Object into the descriptor Ljava/lang/Object.
+ static TypeDescriptor FromClassname(const std::string& name);
+
// Return the full descriptor, such as I or Ljava/lang/Object
const std::string& descriptor() const { return descriptor_; }
// Return the shorty descriptor, such as I or L
std::string short_descriptor() const { return descriptor().substr(0, 1); }
+ bool operator<(const TypeDescriptor& rhs) const { return descriptor_ < rhs.descriptor_; }
+
private:
TypeDescriptor(std::string descriptor) : descriptor_{descriptor} {}
@@ -82,11 +90,98 @@
// Get the shorty descriptor, such as VII for (Int, Int) -> Void
std::string Shorty() const;
+ bool operator<(const Prototype& rhs) const {
+ return std::make_tuple(return_type_, param_types_) <
+ std::make_tuple(rhs.return_type_, rhs.param_types_);
+ }
+
private:
const TypeDescriptor return_type_;
const std::vector<TypeDescriptor> param_types_;
};
+// Represents a DEX register or constant. We separate regular registers and parameters
+// because we will not know the real parameter id until after all instructions
+// have been generated.
+class Value {
+ public:
+ static constexpr Value Local(size_t id) { return Value{id, Kind::kLocalRegister}; }
+ static constexpr Value Parameter(size_t id) { return Value{id, Kind::kParameter}; }
+ static constexpr Value Immediate(size_t value) { return Value{value, Kind::kImmediate}; }
+
+ bool is_register() const { return kind_ == Kind::kLocalRegister; }
+ bool is_parameter() const { return kind_ == Kind::kParameter; }
+ bool is_immediate() const { return kind_ == Kind::kImmediate; }
+
+ size_t value() const { return value_; }
+
+ private:
+ enum class Kind { kLocalRegister, kParameter, kImmediate };
+
+ const size_t value_;
+ const Kind kind_;
+
+ constexpr Value(size_t value, Kind kind) : value_{value}, kind_{kind} {}
+};
+
+// A virtual instruction. We convert these to real instructions in MethodBuilder::Encode.
+// Virtual instructions are needed to keep track of information that is not known until all of the
+// code is generated. This information includes things like how many local registers are created and
+// branch target locations.
+class Instruction {
+ public:
+ // The operation performed by this instruction. These are virtual instructions that do not
+ // correspond exactly to DEX instructions.
+ enum class Op { kReturn, kMove, kInvokeVirtual };
+
+ ////////////////////////
+ // Named Constructors //
+ ////////////////////////
+
+ // For instructions with no return value and no arguments.
+ static inline Instruction OpNoArgs(Op opcode) {
+ return Instruction{opcode, /*method_id*/ 0, /*dest*/ {}};
+ }
+ // For most instructions, which take some number of arguments and have an optional return value.
+ template <typename... T>
+ static inline Instruction OpWithArgs(Op opcode, std::optional<const Value> dest, T... args) {
+ return Instruction{opcode, /*method_id*/ 0, dest, args...};
+ }
+ // For method calls.
+ template <typename... T>
+ static inline Instruction InvokeVirtual(size_t method_id, std::optional<const Value> dest,
+ Value this_arg, T... args) {
+ return Instruction{Op::kInvokeVirtual, method_id, dest, this_arg, args...};
+ }
+
+ ///////////////
+ // Accessors //
+ ///////////////
+
+ Op opcode() const { return opcode_; }
+ size_t method_id() const { return method_id_; }
+ const std::optional<const Value>& dest() const { return dest_; }
+ const std::vector<const Value>& args() const { return args_; }
+
+ private:
+ inline Instruction(Op opcode, size_t method_id, std::optional<const Value> dest)
+ : opcode_{opcode}, method_id_{method_id}, dest_{dest}, args_{} {}
+
+ template <typename... T>
+ inline constexpr Instruction(Op opcode, size_t method_id, std::optional<const Value> dest,
+ T... args)
+ : opcode_{opcode}, method_id_{method_id}, dest_{dest}, args_{args...} {}
+
+ const Op opcode_;
+ // The index of the method to invoke, for kInvokeVirtual and similar opcodes.
+ const size_t method_id_{0};
+ const std::optional<const Value> dest_;
+ const std::vector<const Value> args_;
+};
+
+// Needed for CHECK_EQ, DCHECK_EQ, etc.
+std::ostream& operator<<(std::ostream& out, const Instruction::Op& opcode);
+
// Tools to help build methods and their bodies.
class MethodBuilder {
public:
@@ -95,42 +190,53 @@
// Encode the method into DEX format.
ir::EncodedMethod* Encode();
- // Registers are just represented by their number.
- using Register = size_t;
-
// Create a new register to be used to storing values. Note that these are not SSA registers, like
// might be expected in similar code generators. This does no liveness tracking or anything, so
// it's up to the caller to reuse registers as appropriate.
- Register MakeRegister();
+ Value MakeRegister();
/////////////////////////////////
// Instruction builder methods //
/////////////////////////////////
+ void AddInstruction(Instruction instruction);
+
// return-void
void BuildReturn();
- void BuildReturn(Register src);
+ void BuildReturn(Value src);
// const/4
- void BuildConst4(Register target, int value);
+ void BuildConst4(Value target, int value);
// TODO: add builders for more instructions
private:
+ void EncodeInstructions();
+ void EncodeInstruction(const Instruction& instruction);
+ void EncodeReturn(const Instruction& instruction);
+ void EncodeMove(const Instruction& instruction);
+ void EncodeInvokeVirtual(const Instruction& instruction);
+
+ // Converts a register or parameter to its DEX register number.
+ size_t RegisterValue(Value value) const;
+
DexBuilder* dex_;
ir::Class* class_;
ir::MethodDecl* decl_;
- // A buffer to hold instructions we are generating.
+ // A list of the instructions we will eventually encode.
+ std::vector<Instruction> instructions_;
+
+ // A buffer to hold instructions that have been encoded.
std::vector<::dex::u2> buffer_;
// How many registers we've allocated
- size_t num_registers_;
+ size_t num_registers_{0};
};
// A helper to build class definitions.
class ClassBuilder {
public:
- ClassBuilder(DexBuilder* parent, ir::Class* class_def);
+ ClassBuilder(DexBuilder* parent, const std::string& name, ir::Class* class_def);
void set_source_file(const std::string& source);
@@ -139,8 +245,15 @@
MethodBuilder CreateMethod(const std::string& name, Prototype prototype);
private:
- DexBuilder* parent_;
- ir::Class* class_;
+ DexBuilder* const parent_;
+ const TypeDescriptor type_descriptor_;
+ ir::Class* const class_;
+};
+
+// Keeps track of information needed to manipulate or call a method.
+struct MethodDeclData {
+ size_t id;
+ ir::MethodDecl* decl;
};
// Builds Dex files from scratch.
@@ -163,10 +276,19 @@
ClassBuilder MakeClass(const std::string& name);
// Add a type for the given descriptor, or return the existing one if it already exists.
- // See the TypeDescriptor class for help generating these.
+ // See the TypeDescriptor class for help generating these. GetOrAddType can be used to declare
+ // imported classes.
ir::Type* GetOrAddType(const std::string& descriptor);
+ // Returns the method id for the method, creating it if it has not been created yet.
+ const MethodDeclData& GetOrDeclareMethod(TypeDescriptor type, const std::string& name,
+ Prototype prototype);
+
private:
+ // Looks up the ir::Proto* corresponding to this given prototype, or creates one if it does not
+ // exist.
+ ir::Proto* GetOrEncodeProto(Prototype prototype);
+
std::shared_ptr<ir::DexFile> dex_file_;
// allocator_ is needed to be able to encode the image.
@@ -177,10 +299,29 @@
std::vector<std::unique_ptr<uint8_t[]>> string_data_;
// Keep track of what types we've defined so we can look them up later.
- std::map<std::string, ir::Type*> types_by_descriptor_;
+ std::unordered_map<std::string, ir::Type*> types_by_descriptor_;
+
+ struct MethodDescriptor {
+ TypeDescriptor type;
+ std::string name;
+ Prototype prototype;
+
+ inline bool operator<(const MethodDescriptor& rhs) const {
+ return std::make_tuple(type, name, prototype) <
+ std::make_tuple(rhs.type, rhs.name, rhs.prototype);
+ }
+ };
+
+ // Maps method declarations to their method index. This is needed to encode references to them.
+ // When we go to actually write the DEX file, slicer will re-assign these after correctly sorting
+ // the methods list.
+ std::map<MethodDescriptor, MethodDeclData> method_id_map_;
// Keep track of what strings we've defined so we can look them up later.
- std::map<std::string, ir::String*> strings_;
+ std::unordered_map<std::string, ir::String*> strings_;
+
+ // Keep track of already-encoded protos.
+ std::map<Prototype, ir::Proto*> proto_map_;
};
} // namespace dex
diff --git a/startop/view_compiler/dex_builder_test.cc b/startop/view_compiler/dex_builder_test.cc
index 0d8b854..61c86b4 100644
--- a/startop/view_compiler/dex_builder_test.cc
+++ b/startop/view_compiler/dex_builder_test.cc
@@ -40,6 +40,12 @@
return loaded_dex_file != nullptr;
}
+// Write out and verify a DEX file that corresponds to:
+//
+// package dextest;
+// public class DexTest {
+// public static void foo() {}
+// }
TEST(DexBuilderTest, VerifyDexWithClassMethod) {
DexBuilder dex_file;
@@ -67,6 +73,12 @@
EXPECT_FALSE(EncodeAndVerify(&dex_file));
}
+// Write out and verify a DEX file that corresponds to:
+//
+// package dextest;
+// public class DexTest {
+// public static int foo() { return 5; }
+// }
TEST(DexBuilderTest, VerifyDexReturn5) {
DexBuilder dex_file;
@@ -80,3 +92,51 @@
EXPECT_TRUE(EncodeAndVerify(&dex_file));
}
+
+// Write out and verify a DEX file that corresponds to:
+//
+// package dextest;
+// public class DexTest {
+// public static int foo(int x) { return x; }
+// }
+TEST(DexBuilderTest, VerifyDexReturnIntParam) {
+ DexBuilder dex_file;
+
+ auto cbuilder{dex_file.MakeClass("dextest.DexTest")};
+
+ auto method{
+ cbuilder.CreateMethod("foo", Prototype{TypeDescriptor::Int(), TypeDescriptor::Int()})};
+ method.BuildReturn(Value::Parameter(0));
+ method.Encode();
+
+ EXPECT_TRUE(EncodeAndVerify(&dex_file));
+}
+
+// Write out and verify a DEX file that corresponds to:
+//
+// package dextest;
+// public class DexTest {
+// public static int foo(String s) { return s.length(); }
+// }
+TEST(DexBuilderTest, VerifyDexCallStringLength) {
+ DexBuilder dex_file;
+
+ auto cbuilder{dex_file.MakeClass("dextest.DexTest")};
+
+ MethodBuilder method{cbuilder.CreateMethod(
+ "foo", Prototype{TypeDescriptor::Int(), TypeDescriptor::FromClassname("java.lang.String")})};
+
+ Value result = method.MakeRegister();
+
+ MethodDeclData string_length =
+ dex_file.GetOrDeclareMethod(TypeDescriptor::FromClassname("java.lang.String"),
+ "length",
+ Prototype{TypeDescriptor::Int()});
+
+ method.AddInstruction(Instruction::InvokeVirtual(string_length.id, result, Value::Parameter(0)));
+ method.BuildReturn(result);
+
+ method.Encode();
+
+ EXPECT_TRUE(EncodeAndVerify(&dex_file));
+}
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 8a77f14..1091b58 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -1285,9 +1285,10 @@
* Returns the unique device ID, for example, the IMEI for GSM and the MEID
* or ESN for CDMA phones. Return null if device ID is not available.
*
- * <p>Requires Permission: READ_PRIVILEGED_PHONE_STATE, or for the calling package to be the
- * device or profile owner and have the READ_PHONE_STATE permission. The profile owner is an app
- * that owns a managed profile on the device; for more details see <a
+ * <p>Requires Permission: READ_PRIVILEGED_PHONE_STATE, for the calling app to be the device or
+ * profile owner and have the READ_PHONE_STATE permission, or that the calling app has carrier
+ * privileges (see {@link #hasCarrierPrivileges}). The profile owner is an app that owns a
+ * managed profile on the device; for more details see <a
* href="https://developer.android.com/work/managed-profiles">Work profiles</a>. Profile owner
* access is deprecated and will be removed in a future release.
*
@@ -1295,7 +1296,7 @@
* MEID for CDMA.
*/
@Deprecated
- @SuppressAutoDoc // No support for device / profile owner.
+ @SuppressAutoDoc // No support for device / profile owner or carrier privileges (b/72967236).
@RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
public String getDeviceId() {
try {
@@ -1314,9 +1315,10 @@
* Returns the unique device ID of a subscription, for example, the IMEI for
* GSM and the MEID for CDMA phones. Return null if device ID is not available.
*
- * <p>Requires Permission: READ_PRIVILEGED_PHONE_STATE, or for the calling package to be the
- * device or profile owner and have the READ_PHONE_STATE permission. The profile owner is an app
- * that owns a managed profile on the device; for more details see <a
+ * <p>Requires Permission: READ_PRIVILEGED_PHONE_STATE, for the calling app to be the device or
+ * profile owner and have the READ_PHONE_STATE permission, or that the calling app has carrier
+ * privileges (see {@link #hasCarrierPrivileges}). The profile owner is an app that owns a
+ * managed profile on the device; for more details see <a
* href="https://developer.android.com/work/managed-profiles">Work profiles</a>. Profile owner
* access is deprecated and will be removed in a future release.
*
@@ -1326,7 +1328,7 @@
* MEID for CDMA.
*/
@Deprecated
- @SuppressAutoDoc // No support for device / profile owner.
+ @SuppressAutoDoc // No support for device / profile owner or carrier privileges (b/72967236).
@RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
public String getDeviceId(int slotIndex) {
// FIXME this assumes phoneId == slotIndex
@@ -1346,13 +1348,14 @@
* Returns the IMEI (International Mobile Equipment Identity). Return null if IMEI is not
* available.
*
- * <p>Requires Permission: READ_PRIVILEGED_PHONE_STATE, or for the calling package to be the
- * device or profile owner and have the READ_PHONE_STATE permission. The profile owner is an app
- * that owns a managed profile on the device; for more details see <a
+ * <p>Requires Permission: READ_PRIVILEGED_PHONE_STATE, for the calling app to be the device or
+ * profile owner and have the READ_PHONE_STATE permission, or that the calling app has carrier
+ * privileges (see {@link #hasCarrierPrivileges}). The profile owner is an app that owns a
+ * managed profile on the device; for more details see <a
* href="https://developer.android.com/work/managed-profiles">Work profiles</a>. Profile owner
* access is deprecated and will be removed in a future release.
*/
- @SuppressAutoDoc // No support for device / profile owner.
+ @SuppressAutoDoc // No support for device / profile owner or carrier privileges (b/72967236).
@RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
public String getImei() {
return getImei(getSlotIndex());
@@ -1362,15 +1365,16 @@
* Returns the IMEI (International Mobile Equipment Identity). Return null if IMEI is not
* available.
*
- * <p>Requires Permission: READ_PRIVILEGED_PHONE_STATE, or for the calling package to be the
- * device or profile owner and have the READ_PHONE_STATE permission. The profile owner is an app
- * that owns a managed profile on the device; for more details see <a
+ * <p>Requires Permission: READ_PRIVILEGED_PHONE_STATE, for the calling app to be the device or
+ * profile owner and have the READ_PHONE_STATE permission, or that the calling app has carrier
+ * privileges (see {@link #hasCarrierPrivileges}). The profile owner is an app that owns a
+ * managed profile on the device; for more details see <a
* href="https://developer.android.com/work/managed-profiles">Work profiles</a>. Profile owner
* access is deprecated and will be removed in a future release.
*
* @param slotIndex of which IMEI is returned
*/
- @SuppressAutoDoc // No support for device / profile owner.
+ @SuppressAutoDoc // No support for device / profile owner or carrier privileges (b/72967236).
@RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
public String getImei(int slotIndex) {
ITelephony telephony = getITelephony();
@@ -1415,13 +1419,14 @@
/**
* Returns the MEID (Mobile Equipment Identifier). Return null if MEID is not available.
*
- * <p>Requires Permission: READ_PRIVILEGED_PHONE_STATE, or for the calling package to be the
- * device or profile owner and have the READ_PHONE_STATE permission. The profile owner is an app
- * that owns a managed profile on the device; for more details see <a
+ * <p>Requires Permission: READ_PRIVILEGED_PHONE_STATE, for the calling app to be the device or
+ * profile owner and have the READ_PHONE_STATE permission, or that the calling app has carrier
+ * privileges (see {@link #hasCarrierPrivileges}). The profile owner is an app that owns a
+ * managed profile on the device; for more details see <a
* href="https://developer.android.com/work/managed-profiles">Work profiles</a>. Profile owner
* access is deprecated and will be removed in a future release.
*/
- @SuppressAutoDoc // No support for device / profile owner.
+ @SuppressAutoDoc // No support for device / profile owner or carrier privileges (b/72967236).
@RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
public String getMeid() {
return getMeid(getSlotIndex());
@@ -1430,15 +1435,16 @@
/**
* Returns the MEID (Mobile Equipment Identifier). Return null if MEID is not available.
*
- * <p>Requires Permission: READ_PRIVILEGED_PHONE_STATE, or for the calling package to be the
- * device or profile owner and have the READ_PHONE_STATE permission. The profile owner is an app
- * that owns a managed profile on the device; for more details see <a
+ * <p>Requires Permission: READ_PRIVILEGED_PHONE_STATE, for the calling app to be the device or
+ * profile owner and have the READ_PHONE_STATE permission, or that the calling app has carrier
+ * privileges (see {@link #hasCarrierPrivileges}). The profile owner is an app that owns a
+ * managed profile on the device; for more details see <a
* href="https://developer.android.com/work/managed-profiles">Work profiles</a>. Profile owner
* access is deprecated and will be removed in a future release.
*
* @param slotIndex of which MEID is returned
*/
- @SuppressAutoDoc // No support for device / profile owner.
+ @SuppressAutoDoc // No support for device / profile owner or carrier privileges (b/72967236).
@RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
public String getMeid(int slotIndex) {
ITelephony telephony = getITelephony();
@@ -2936,7 +2942,7 @@
* href="https://developer.android.com/work/managed-profiles">Work profiles</a>. Profile owner
* access is deprecated and will be removed in a future release.
*/
- @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
+ @SuppressAutoDoc // No support for device / profile owner or carrier privileges (b/72967236).
@RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
public String getSimSerialNumber() {
return getSimSerialNumber(getSubId());
@@ -3098,7 +3104,7 @@
* href="https://developer.android.com/work/managed-profiles">Work profiles</a>. Profile owner
* access is deprecated and will be removed in a future release.
*/
- @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
+ @SuppressAutoDoc // No support for device / profile owner or carrier privileges (b/72967236).
@RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
public String getSubscriberId() {
return getSubscriberId(getSubId());
diff --git a/telephony/java/android/telephony/euicc/EuiccCardManager.java b/telephony/java/android/telephony/euicc/EuiccCardManager.java
index 1141177..3b1ef3f 100644
--- a/telephony/java/android/telephony/euicc/EuiccCardManager.java
+++ b/telephony/java/android/telephony/euicc/EuiccCardManager.java
@@ -15,6 +15,7 @@
*/
package android.telephony.euicc;
+import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
import android.annotation.Nullable;
import android.annotation.SystemApi;
@@ -50,7 +51,6 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-import android.annotation.CallbackExecutor;
import java.util.concurrent.Executor;
/**
@@ -119,6 +119,9 @@
/** Result code when the eUICC card with the given card Id is not found. */
public static final int RESULT_EUICC_NOT_FOUND = -2;
+ /** Result code indicating the caller is not the active LPA. */
+ public static final int RESULT_CALLER_NOT_ALLOWED = -3;
+
/**
* Callback to receive the result of an eUICC card API.
*
@@ -152,7 +155,7 @@
* Requests all the profiles on eUicc.
*
* @param cardId The Id of the eUICC.
- * @param executor The executor through which the callback should be invode.
+ * @param executor The executor through which the callback should be invoke.
* @param callback The callback to get the result code and all the profiles.
*/
public void requestAllProfiles(String cardId, @CallbackExecutor Executor executor,
@@ -176,7 +179,7 @@
*
* @param cardId The Id of the eUICC.
* @param iccid The iccid of the profile.
- * @param executor The executor through which the callback should be invode.
+ * @param executor The executor through which the callback should be invoke.
* @param callback The callback to get the result code and profile.
*/
public void requestProfile(String cardId, String iccid, @CallbackExecutor Executor executor,
@@ -201,7 +204,7 @@
* @param cardId The Id of the eUICC.
* @param iccid The iccid of the profile.
* @param refresh Whether sending the REFRESH command to modem.
- * @param executor The executor through which the callback should be invode.
+ * @param executor The executor through which the callback should be invoke.
* @param callback The callback to get the result code.
*/
public void disableProfile(String cardId, String iccid, boolean refresh,
@@ -227,7 +230,7 @@
* @param cardId The Id of the eUICC.
* @param iccid The iccid of the profile to switch to.
* @param refresh Whether sending the REFRESH command to modem.
- * @param executor The executor through which the callback should be invode.
+ * @param executor The executor through which the callback should be invoke.
* @param callback The callback to get the result code and the EuiccProfileInfo enabled.
*/
public void switchToProfile(String cardId, String iccid, boolean refresh,
@@ -252,7 +255,7 @@
* @param cardId The Id of the eUICC.
* @param iccid The iccid of the profile.
* @param nickname The nickname of the profile.
- * @param executor The executor through which the callback should be invode.
+ * @param executor The executor through which the callback should be invoke.
* @param callback The callback to get the result code.
*/
public void setNickname(String cardId, String iccid, String nickname,
@@ -276,7 +279,7 @@
*
* @param cardId The Id of the eUICC.
* @param iccid The iccid of the profile.
- * @param executor The executor through which the callback should be invode.
+ * @param executor The executor through which the callback should be invoke.
* @param callback The callback to get the result code.
*/
public void deleteProfile(String cardId, String iccid, @CallbackExecutor Executor executor,
@@ -301,7 +304,7 @@
* @param cardId The Id of the eUICC.
* @param options Bits of the options of resetting which parts of the eUICC memory. See
* EuiccCard for details.
- * @param executor The executor through which the callback should be invode.
+ * @param executor The executor through which the callback should be invoke.
* @param callback The callback to get the result code.
*/
public void resetMemory(String cardId, @ResetOption int options,
@@ -324,7 +327,7 @@
* Requests the default SM-DP+ address from eUICC.
*
* @param cardId The Id of the eUICC.
- * @param executor The executor through which the callback should be invode.
+ * @param executor The executor through which the callback should be invoke.
* @param callback The callback to get the result code and the default SM-DP+ address.
*/
public void requestDefaultSmdpAddress(String cardId, @CallbackExecutor Executor executor,
@@ -347,7 +350,7 @@
* Requests the SM-DS address from eUICC.
*
* @param cardId The Id of the eUICC.
- * @param executor The executor through which the callback should be invode.
+ * @param executor The executor through which the callback should be invoke.
* @param callback The callback to get the result code and the SM-DS address.
*/
public void requestSmdsAddress(String cardId, @CallbackExecutor Executor executor,
@@ -371,7 +374,7 @@
*
* @param cardId The Id of the eUICC.
* @param defaultSmdpAddress The default SM-DP+ address to set.
- * @param executor The executor through which the callback should be invode.
+ * @param executor The executor through which the callback should be invoke.
* @param callback The callback to get the result code.
*/
public void setDefaultSmdpAddress(String cardId, String defaultSmdpAddress,
@@ -395,7 +398,7 @@
* Requests Rules Authorisation Table.
*
* @param cardId The Id of the eUICC.
- * @param executor The executor through which the callback should be invode.
+ * @param executor The executor through which the callback should be invoke.
* @param callback the callback to get the result code and the rule authorisation table.
*/
public void requestRulesAuthTable(String cardId, @CallbackExecutor Executor executor,
@@ -418,7 +421,7 @@
* Requests the eUICC challenge for new profile downloading.
*
* @param cardId The Id of the eUICC.
- * @param executor The executor through which the callback should be invode.
+ * @param executor The executor through which the callback should be invoke.
* @param callback the callback to get the result code and the challenge.
*/
public void requestEuiccChallenge(String cardId, @CallbackExecutor Executor executor,
@@ -441,7 +444,7 @@
* Requests the eUICC info1 defined in GSMA RSP v2.0+ for new profile downloading.
*
* @param cardId The Id of the eUICC.
- * @param executor The executor through which the callback should be invode.
+ * @param executor The executor through which the callback should be invoke.
* @param callback the callback to get the result code and the info1.
*/
public void requestEuiccInfo1(String cardId, @CallbackExecutor Executor executor,
@@ -464,7 +467,7 @@
* Gets the eUICC info2 defined in GSMA RSP v2.0+ for new profile downloading.
*
* @param cardId The Id of the eUICC.
- * @param executor The executor through which the callback should be invode.
+ * @param executor The executor through which the callback should be invoke.
* @param callback the callback to get the result code and the info2.
*/
public void requestEuiccInfo2(String cardId, @CallbackExecutor Executor executor,
@@ -497,7 +500,7 @@
* GSMA RSP v2.0+.
* @param serverCertificate ASN.1 data in byte array indicating SM-DP+ Certificate returned by
* SM-DP+ server.
- * @param executor The executor through which the callback should be invode.
+ * @param executor The executor through which the callback should be invoke.
* @param callback the callback to get the result code and a byte array which represents a
* {@code AuthenticateServerResponse} defined in GSMA RSP v2.0+.
*/
@@ -537,7 +540,7 @@
* SM-DP+ server.
* @param smdpCertificate ASN.1 data in byte array indicating the SM-DP+ Certificate returned
* by SM-DP+ server.
- * @param executor The executor through which the callback should be invode.
+ * @param executor The executor through which the callback should be invoke.
* @param callback the callback to get the result code and a byte array which represents a
* {@code PrepareDownloadResponse} defined in GSMA RSP v2.0+
*/
@@ -569,7 +572,7 @@
*
* @param cardId The Id of the eUICC.
* @param boundProfilePackage the Bound Profile Package data returned by SM-DP+ server.
- * @param executor The executor through which the callback should be invode.
+ * @param executor The executor through which the callback should be invoke.
* @param callback the callback to get the result code and a byte array which represents a
* {@code LoadBoundProfilePackageResponse} defined in GSMA RSP v2.0+.
*/
@@ -598,7 +601,7 @@
* @param cardId The Id of the eUICC.
* @param transactionId the transaction ID returned by SM-DP+ server.
* @param reason the cancel reason.
- * @param executor The executor through which the callback should be invode.
+ * @param executor The executor through which the callback should be invoke.
* @param callback the callback to get the result code and an byte[] which represents a
* {@code CancelSessionResponse} defined in GSMA RSP v2.0+.
*/
@@ -627,7 +630,7 @@
*
* @param cardId The Id of the eUICC.
* @param events bits of the event types ({@link EuiccNotification.Event}) to list.
- * @param executor The executor through which the callback should be invode.
+ * @param executor The executor through which the callback should be invoke.
* @param callback the callback to get the result code and the list of notifications.
*/
public void listNotifications(String cardId, @EuiccNotification.Event int events,
@@ -651,7 +654,7 @@
*
* @param cardId The Id of the eUICC.
* @param events bits of the event types ({@link EuiccNotification.Event}) to list.
- * @param executor The executor through which the callback should be invode.
+ * @param executor The executor through which the callback should be invoke.
* @param callback the callback to get the result code and the list of notifications.
*/
public void retrieveNotificationList(String cardId, @EuiccNotification.Event int events,
@@ -675,7 +678,7 @@
*
* @param cardId The Id of the eUICC.
* @param seqNumber the sequence number of the notification.
- * @param executor The executor through which the callback should be invode.
+ * @param executor The executor through which the callback should be invoke.
* @param callback the callback to get the result code and the notification.
*/
public void retrieveNotification(String cardId, int seqNumber,
@@ -699,7 +702,7 @@
*
* @param cardId The Id of the eUICC.
* @param seqNumber the sequence number of the notification.
- * @param executor The executor through which the callback should be invode.
+ * @param executor The executor through which the callback should be invoke.
* @param callback the callback to get the result code.
*/
public void removeNotificationFromList(String cardId, int seqNumber,
diff --git a/telephony/java/com/android/internal/telephony/TelephonyPermissions.java b/telephony/java/com/android/internal/telephony/TelephonyPermissions.java
index eb6be65..553e3fb 100644
--- a/telephony/java/com/android/internal/telephony/TelephonyPermissions.java
+++ b/telephony/java/com/android/internal/telephony/TelephonyPermissions.java
@@ -188,6 +188,13 @@
if (checkReadDeviceIdentifiers(context, pid, uid, callingPackage)) {
return true;
}
+ // Calling packages with carrier privileges will also have access to device identifiers, but
+ // this may be removed in a future release.
+ if (SubscriptionManager.isValidSubscriptionId(subId) && getCarrierPrivilegeStatus(
+ TELEPHONY_SUPPLIER, subId, uid)
+ == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) {
+ return true;
+ }
// else the calling package is not authorized to access the device identifiers; call
// a central method to report the failure based on the target SDK and if the calling package
// has the READ_PHONE_STATE permission or carrier privileges that were previously required
@@ -279,44 +286,51 @@
int uid, String callingPackage, String message) {
Log.wtf(LOG_TAG,
"reportAccessDeniedToReadIdentifiers:" + callingPackage + ":" + message);
- // if the device identifier check is relaxed then revert to the READ_PHONE_STATE permission
- // check that was previously required to access device identifiers.
- boolean relaxDeviceIdentifierCheck = Settings.Global.getInt(context.getContentResolver(),
- Settings.Global.PRIVILEGED_DEVICE_IDENTIFIER_CHECK_ENABLED, 0) == 0;
- if (relaxDeviceIdentifierCheck) {
- return checkReadPhoneState(context, subId, pid, uid, callingPackage, message);
- } else {
+ // If the device identifier check is enabled then enforce the new access requirements for
+ // both 1P and 3P apps.
+ boolean enableDeviceIdentifierCheck = Settings.Global.getInt(context.getContentResolver(),
+ Settings.Global.PRIVILEGED_DEVICE_IDENTIFIER_CHECK_ENABLED, 0) == 1;
+ // Check if the application is a 3P app; if so then a separate setting is required to relax
+ // the check to begin flagging problems with 3P apps early.
+ boolean relax3PDeviceIdentifierCheck = Settings.Global.getInt(context.getContentResolver(),
+ Settings.Global.PRIVILEGED_DEVICE_IDENTIFIER_3P_CHECK_RELAXED, 0) == 1;
+ boolean is3PApp = true;
+ ApplicationInfo callingPackageInfo = null;
+ try {
+ callingPackageInfo = context.getPackageManager().getApplicationInfo(callingPackage, 0);
+ if (callingPackageInfo.isSystemApp()) {
+ is3PApp = false;
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ // If the application info for the calling package could not be found then assume the
+ // calling app is a 3P app to detect any issues with the check
+ }
+ if (enableDeviceIdentifierCheck || (is3PApp && !relax3PDeviceIdentifierCheck)) {
boolean targetQBehaviorDisabled = Settings.Global.getInt(context.getContentResolver(),
Settings.Global.PRIVILEGED_DEVICE_IDENTIFIER_TARGET_Q_BEHAVIOR_ENABLED, 0) == 0;
if (callingPackage != null) {
- try {
- // if the target SDK is pre-Q or the target Q behavior is disabled then check if
- // the calling package would have previously had access to device identifiers.
- ApplicationInfo callingPackageInfo =
- context.getPackageManager().getApplicationInfo(
- callingPackage, 0);
- if (callingPackageInfo != null && (
- callingPackageInfo.targetSdkVersion < Build.VERSION_CODES.Q
- || targetQBehaviorDisabled)) {
- if (context.checkPermission(
- android.Manifest.permission.READ_PHONE_STATE,
- pid,
- uid) == PackageManager.PERMISSION_GRANTED) {
- return false;
- }
- if (SubscriptionManager.isValidSubscriptionId(subId)
- && getCarrierPrivilegeStatus(TELEPHONY_SUPPLIER, subId, uid)
- == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) {
- return false;
- }
+ // if the target SDK is pre-Q or the target Q behavior is disabled then check if
+ // the calling package would have previously had access to device identifiers.
+ if (callingPackageInfo != null && (
+ callingPackageInfo.targetSdkVersion < Build.VERSION_CODES.Q
+ || targetQBehaviorDisabled)) {
+ if (context.checkPermission(
+ android.Manifest.permission.READ_PHONE_STATE,
+ pid,
+ uid) == PackageManager.PERMISSION_GRANTED) {
+ return false;
}
- } catch (PackageManager.NameNotFoundException e) {
- // If the application info for the calling package could not be found then
- // default to throwing the SecurityException.
+ if (SubscriptionManager.isValidSubscriptionId(subId)
+ && getCarrierPrivilegeStatus(TELEPHONY_SUPPLIER, subId, uid)
+ == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) {
+ return false;
+ }
}
}
throw new SecurityException(message + ": The user " + uid
+ " does not meet the requirements to access device identifiers.");
+ } else {
+ return checkReadPhoneState(context, subId, pid, uid, callingPackage, message);
}
}