Merge "Prefer ScopedUtfChars where possible"
diff --git a/api/current.txt b/api/current.txt
index 12495df..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);
@@ -28704,9 +28714,11 @@
field public java.lang.String SSID;
field public java.util.BitSet allowedAuthAlgorithms;
field public java.util.BitSet allowedGroupCiphers;
+ field public java.util.BitSet allowedGroupMgmtCiphers;
field public java.util.BitSet allowedKeyManagement;
field public java.util.BitSet allowedPairwiseCiphers;
field public java.util.BitSet allowedProtocols;
+ field public java.util.BitSet allowedSuiteBCiphers;
field public android.net.wifi.WifiEnterpriseConfig enterpriseConfig;
field public boolean hiddenSSID;
field public boolean isHomeProviderNetwork;
@@ -28730,6 +28742,7 @@
public static class WifiConfiguration.GroupCipher {
field public static final int CCMP = 3; // 0x3
+ field public static final int GCMP_256 = 5; // 0x5
field public static final int TKIP = 2; // 0x2
field public static final deprecated int WEP104 = 1; // 0x1
field public static final deprecated int WEP40 = 0; // 0x0
@@ -28737,9 +28750,18 @@
field public static final java.lang.String varName = "group";
}
+ public static class WifiConfiguration.GroupMgmtCipher {
+ field public static final int BIP_CMAC_256 = 0; // 0x0
+ field public static final int BIP_GMAC_128 = 1; // 0x1
+ field public static final int BIP_GMAC_256 = 2; // 0x2
+ }
+
public static class WifiConfiguration.KeyMgmt {
field public static final int IEEE8021X = 3; // 0x3
field public static final int NONE = 0; // 0x0
+ field public static final int OWE = 9; // 0x9
+ field public static final int SAE = 8; // 0x8
+ field public static final int SUITE_B_192 = 10; // 0xa
field public static final int WPA_EAP = 2; // 0x2
field public static final int WPA_PSK = 1; // 0x1
field public static final java.lang.String[] strings;
@@ -28748,6 +28770,7 @@
public static class WifiConfiguration.PairwiseCipher {
field public static final int CCMP = 2; // 0x2
+ field public static final int GCMP_256 = 3; // 0x3
field public static final int NONE = 0; // 0x0
field public static final deprecated int TKIP = 1; // 0x1
field public static final java.lang.String[] strings;
diff --git a/api/system-current.txt b/api/system-current.txt
index f2625b4..123ca51 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -3641,10 +3641,13 @@
method public int getWifiApState();
method public boolean isDeviceToApRttSupported();
method public boolean isDeviceToDeviceRttSupported();
+ method public boolean isOweSupported();
method public boolean isPortableHotspotSupported();
method public boolean isWifiApEnabled();
method public boolean isWifiScannerSupported();
method public void registerNetworkRequestMatchCallback(android.net.wifi.WifiManager.NetworkRequestMatchCallback, android.os.Handler);
+ method public boolean isWpa3SaeSupported();
+ method public boolean isWpa3SuiteBSupported();
method public boolean setWifiApConfiguration(android.net.wifi.WifiConfiguration);
method public boolean startScan(android.os.WorkSource);
method public void unregisterNetworkRequestMatchCallback(android.net.wifi.WifiManager.NetworkRequestMatchCallback);
@@ -5917,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 689f975..a30e38a 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -11499,6 +11499,24 @@
public static final String NETWORK_WATCHLIST_ENABLED = "network_watchlist_enabled";
/**
+ * Whether or not show hidden launcher icon apps feature is enabled.
+ * Type: int (0 for false, 1 for true)
+ * Default: 0
+ * @hide
+ */
+ public static final String SHOW_HIDDEN_LAUNCHER_ICON_APPS_ENABLED =
+ "show_hidden_icon_apps_enabled";
+
+ /**
+ * Whether or not show new app installed notification is enabled.
+ * Type: int (0 for false, 1 for true)
+ * Default: 0
+ * @hide
+ */
+ public static final String SHOW_NEW_APP_INSTALLED_NOTIFICATION_ENABLED =
+ "show_new_app_installed_notification_enabled";
+
+ /**
* Flag to keep background restricted profiles running after exiting. If disabled,
* the restricted profile can be put into stopped state as soon as the user leaves it.
* Type: int (0 for false, 1 for true)
@@ -12490,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/WindowInsets.java b/core/java/android/view/WindowInsets.java
index 2d60d28..4a7e783 100644
--- a/core/java/android/view/WindowInsets.java
+++ b/core/java/android/view/WindowInsets.java
@@ -384,6 +384,7 @@
displayCutoutCopyConstructorArgument(this));
}
+ // TODO(b/119190588): replace @code with @link below
/**
* Returns a copy of this WindowInsets with selected system window insets replaced
* with new values.
@@ -398,7 +399,7 @@
* @param right New right inset in pixels
* @param bottom New bottom inset in pixels
* @return A modified copy of this WindowInsets
- * @deprecated use {@link Builder#Builder(WindowInsets)} with
+ * @deprecated use {@code Builder#Builder(WindowInsets)} with
* {@link Builder#setSystemWindowInsets(Insets)} instead.
*/
@Deprecated
@@ -414,6 +415,7 @@
return new Builder(this).setSystemWindowInsets(Insets.of(left, top, right, bottom)).build();
}
+ // TODO(b/119190588): replace @code with @link below
/**
* Returns a copy of this WindowInsets with selected system window insets replaced
* with new values.
@@ -426,7 +428,7 @@
* @param systemWindowInsets New system window insets. Each field is the inset in pixels
* for that edge
* @return A modified copy of this WindowInsets
- * @deprecated use {@link Builder#Builder(WindowInsets)} with
+ * @deprecated use {@code Builder#Builder(WindowInsets)} with
* {@link Builder#setSystemWindowInsets(Insets)} instead.
*/
@Deprecated
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/graphics/Paint.cpp b/core/jni/android/graphics/Paint.cpp
index d391de7..a8b0640 100644
--- a/core/jni/android/graphics/Paint.cpp
+++ b/core/jni/android/graphics/Paint.cpp
@@ -28,6 +28,7 @@
#include "SkBlurDrawLooper.h"
#include "SkColorFilter.h"
+#include "SkFontTypes.h"
#include "SkMaskFilter.h"
#include "SkPath.h"
#include "SkPathEffect.h"
@@ -684,13 +685,13 @@
}
static jint getHinting(jlong paintHandle) {
- return reinterpret_cast<Paint*>(paintHandle)->getHinting()
- == Paint::kNo_Hinting ? 0 : 1;
+ return (SkFontHinting)reinterpret_cast<Paint*>(paintHandle)->getHinting()
+ == kNo_SkFontHinting ? 0 : 1;
}
static void setHinting(jlong paintHandle, jint mode) {
reinterpret_cast<Paint*>(paintHandle)->setHinting(
- mode == 0 ? Paint::kNo_Hinting : Paint::kNormal_Hinting);
+ mode == 0 ? kNo_SkFontHinting : kNormal_SkFontHinting);
}
static void setAntiAlias(jlong paintHandle, jboolean aa) {
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/proto/android/providers/settings/global.proto b/core/proto/android/providers/settings/global.proto
index 3072977..69ebb59 100644
--- a/core/proto/android/providers/settings/global.proto
+++ b/core/proto/android/providers/settings/global.proto
@@ -725,8 +725,10 @@
optional SettingProto set_install_location = 103 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto shortcut_manager_constants = 104;
optional SettingProto show_first_crash_dialog = 105 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ optional SettingProto show_hidden_launcher_icon_apps_enabled = 141 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto show_restart_in_crash_dialog = 106 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto show_mute_in_crash_dialog = 107 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ optional SettingProto show_new_app_installed_notification_enabled = 142 [ (android.privacy).dest = DEST_AUTOMATIC ];
message SmartSelection {
option (android.msg_privacy).dest = DEST_EXPLICIT;
@@ -966,5 +968,5 @@
// Please insert fields in alphabetical order and group them into messages
// if possible (to avoid reaching the method limit).
- // Next tag = 141;
+ // Next tag = 143;
}
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index 4980210..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,
@@ -393,7 +394,9 @@
Settings.Global.SETTINGS_USE_PSD_API,
Settings.Global.SHORTCUT_MANAGER_CONSTANTS,
Settings.Global.SHOW_FIRST_CRASH_DIALOG,
+ Settings.Global.SHOW_HIDDEN_LAUNCHER_ICON_APPS_ENABLED,
Settings.Global.SHOW_MUTE_IN_CRASH_DIALOG,
+ Settings.Global.SHOW_NEW_APP_INSTALLED_NOTIFICATION_ENABLED,
Settings.Global.SHOW_NOTIFICATION_CHANNEL_WARNINGS,
Settings.Global.SHOW_RESTART_IN_CRASH_DIALOG,
Settings.Global.SHOW_TEMPERATURE_WARNING,
diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp
index 288ba32..9e69488 100644
--- a/libs/androidfw/AssetManager2.cpp
+++ b/libs/androidfw/AssetManager2.cpp
@@ -162,6 +162,13 @@
LOG(INFO) << base::StringPrintf("PG (%02x): ",
package_group.dynamic_ref_table.mAssignedPackageId)
<< list;
+
+ for (size_t i = 0; i < 256; i++) {
+ if (package_group.dynamic_ref_table.mLookupTable[i] != 0) {
+ LOG(INFO) << base::StringPrintf(" e[0x%02x] -> 0x%02x", (uint8_t) i,
+ package_group.dynamic_ref_table.mLookupTable[i]);
+ }
+ }
}
}
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
index dc4a0a7..388548b 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -6998,18 +6998,28 @@
}
status_t DynamicRefTable::lookupResourceValue(Res_value* value) const {
- uint8_t resolvedType;
+ uint8_t resolvedType = Res_value::TYPE_REFERENCE;
+ switch (value->dataType) {
+ case Res_value::TYPE_ATTRIBUTE:
+ resolvedType = Res_value::TYPE_ATTRIBUTE;
+ // fallthrough
+ case Res_value::TYPE_REFERENCE:
+ // Only resolve non-dynamic references and attributes if the package is loaded as a
+ // library or if a shared library is attempting to retrieve its own resource
+ if (!(mAppAsLib || (Res_GETPACKAGE(value->data) + 1) == 0)) {
+ return NO_ERROR;
+ }
- if (value->dataType == Res_value::TYPE_ATTRIBUTE
- || value->dataType == Res_value::TYPE_DYNAMIC_ATTRIBUTE) {
- resolvedType = Res_value::TYPE_ATTRIBUTE;
-
- } else if (value->dataType == Res_value::TYPE_REFERENCE
- || value->dataType == Res_value::TYPE_DYNAMIC_REFERENCE) {
- resolvedType = Res_value::TYPE_REFERENCE;
-
- } else {
- return NO_ERROR;
+ // If the package is loaded as shared library, the resource reference
+ // also need to be fixed.
+ break;
+ case Res_value::TYPE_DYNAMIC_ATTRIBUTE:
+ resolvedType = Res_value::TYPE_ATTRIBUTE;
+ // fallthrough
+ case Res_value::TYPE_DYNAMIC_REFERENCE:
+ break;
+ default:
+ return NO_ERROR;
}
status_t err = lookupResourceId(&value->data);
diff --git a/libs/androidfw/tests/DynamicRefTable_test.cpp b/libs/androidfw/tests/DynamicRefTable_test.cpp
index df44e34..5acc46a 100644
--- a/libs/androidfw/tests/DynamicRefTable_test.cpp
+++ b/libs/androidfw/tests/DynamicRefTable_test.cpp
@@ -40,6 +40,26 @@
EXPECT_EQ(value2.data, 0x02010000);
};
+TEST(DynamicRefTableTest, LookupSharedLibSelfAttributes) {
+ // Shared library
+ DynamicRefTable shared_table(0x03, /* appAsLib */ false);
+ shared_table.addMapping(0x00, 0x03);
+ Res_value value;
+ value.dataType = Res_value::TYPE_ATTRIBUTE;
+ value.data = 0x00010000;
+ ASSERT_EQ(shared_table.lookupResourceValue(&value), NO_ERROR);
+ EXPECT_EQ(value.data, 0x03010000);
+
+ // App loaded as a shared library
+ DynamicRefTable shared_app_table(0x04, /* appAsLib */ true);
+ shared_app_table.addMapping(0x7f, 0x04);
+ Res_value value2;
+ value2.dataType = Res_value::TYPE_ATTRIBUTE;
+ value2.data = 0x7f010000;
+ ASSERT_EQ(shared_app_table.lookupResourceValue(&value2), NO_ERROR);
+ EXPECT_EQ(value2.data, 0x04010000);
+};
+
TEST(DynamicRefTableTest, LookupDynamicReferences) {
// Shared library
DynamicRefTable shared_table(0x2, /* appAsLib */ false);
@@ -51,24 +71,46 @@
ASSERT_EQ(shared_table.lookupResourceValue(&value), NO_ERROR);
EXPECT_EQ(value.data, 0x05010000);
- // App loaded as a shared library
- DynamicRefTable shared_app_table(0x2, /* appAsLib */ true);
- shared_app_table.addMapping(0x03, 0x05);
- shared_app_table.addMapping(0x7f, 0x2);
- Res_value value2;
- value2.dataType = Res_value::TYPE_DYNAMIC_REFERENCE;
- value2.data = 0x03010000;
- ASSERT_EQ(shared_app_table.lookupResourceValue(&value2), NO_ERROR);
- EXPECT_EQ(value2.data, 0x05010000);
-
// Regular application
DynamicRefTable app_table(0x7f, /* appAsLib */ false);
app_table.addMapping(0x03, 0x05);
Res_value value3;
- value3.dataType = Res_value::TYPE_REFERENCE;
+ value3.dataType = Res_value::TYPE_DYNAMIC_REFERENCE;
value3.data = 0x03010000;
ASSERT_EQ(app_table.lookupResourceValue(&value3), NO_ERROR);
EXPECT_EQ(value3.data, 0x05010000);
};
+TEST(DynamicRefTableTest, LookupDynamicAttributes) {
+// App loaded as a shared library
+ DynamicRefTable shared_app_table(0x2, /* appAsLib */ true);
+ shared_app_table.addMapping(0x03, 0x05);
+ shared_app_table.addMapping(0x7f, 0x2);
+ Res_value value2;
+ value2.dataType = Res_value::TYPE_DYNAMIC_ATTRIBUTE;
+ value2.data = 0x03010000;
+ ASSERT_EQ(shared_app_table.lookupResourceValue(&value2), NO_ERROR);
+ EXPECT_EQ(value2.data, 0x05010000);
+}
+
+TEST(DynamicRefTableTest, DoNotLookupNonDynamicReferences) {
+ // Regular application
+ DynamicRefTable app_table(0x7f, /* appAsLib */ false);
+ Res_value value;
+ value.dataType = Res_value::TYPE_REFERENCE;
+ value.data = 0x03010000;
+ ASSERT_EQ(app_table.lookupResourceValue(&value), NO_ERROR);
+ EXPECT_EQ(value.data, 0x03010000);
+};
+
+TEST(DynamicRefTableTest, DoNotLookupNonDynamicAttributes) {
+ // App with custom package id
+ DynamicRefTable custom_app_table(0x8f, /* appAsLib */ false);
+ Res_value value2;
+ value2.dataType = Res_value::TYPE_ATTRIBUTE;
+ value2.data = 0x03010000;
+ ASSERT_EQ(custom_app_table.lookupResourceValue(&value2), NO_ERROR);
+ EXPECT_EQ(value2.data, 0x03010000);
+};
+
} // namespace android
\ No newline at end of file
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/hwui/MinikinSkia.cpp b/libs/hwui/hwui/MinikinSkia.cpp
index 2ca40b9..05dc340 100644
--- a/libs/hwui/hwui/MinikinSkia.cpp
+++ b/libs/hwui/hwui/MinikinSkia.cpp
@@ -139,7 +139,7 @@
uint32_t MinikinFontSkia::packPaintFlags(const SkPaint* paint) {
uint32_t flags = paint->getFlags();
- SkPaint::Hinting hinting = paint->getHinting();
+ SkFontHinting hinting = (SkFontHinting)paint->getHinting();
// select only flags that might affect text layout
flags &= (SkPaint::kAntiAlias_Flag | SkPaint::kFakeBoldText_Flag | SkPaint::kLinearText_Flag |
SkPaint::kSubpixelText_Flag | SkPaint::kEmbeddedBitmapText_Flag |
@@ -150,7 +150,7 @@
void MinikinFontSkia::unpackPaintFlags(SkPaint* paint, uint32_t paintFlags) {
paint->setFlags(paintFlags & SkPaint::kAllFlags);
- paint->setHinting(static_cast<SkPaint::Hinting>(paintFlags >> 16));
+ paint->setHinting(static_cast<SkFontHinting>(paintFlags >> 16));
}
void MinikinFontSkia::populateSkPaint(SkPaint* paint, const MinikinFont* font,
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/PackageInstaller/src/com/android/packageinstaller/PackageInstalledReceiver.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstalledReceiver.java
index 1eb423e..74c7b58 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstalledReceiver.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstalledReceiver.java
@@ -20,6 +20,7 @@
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
+import android.provider.Settings;
import android.util.Log;
/**
@@ -29,11 +30,11 @@
private static final String TAG = PackageInstalledReceiver.class.getSimpleName();
private static final boolean DEBUG = false;
- private static final boolean APP_INSTALLED_NOTIFICATION_ENABLED = false;
@Override
public void onReceive(Context context, Intent intent) {
- if (!APP_INSTALLED_NOTIFICATION_ENABLED) {
+ if (Settings.Global.getInt(context.getContentResolver(),
+ Settings.Global.SHOW_NEW_APP_INSTALLED_NOTIFICATION_ENABLED, 0) == 0) {
return;
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index 2d43762..b46c288 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -1120,6 +1120,9 @@
dumpSetting(s, p,
Settings.Global.SHOW_FIRST_CRASH_DIALOG,
GlobalSettingsProto.SHOW_FIRST_CRASH_DIALOG);
+ dumpSetting(s, p,
+ Settings.Global.SHOW_HIDDEN_LAUNCHER_ICON_APPS_ENABLED,
+ GlobalSettingsProto.SHOW_HIDDEN_LAUNCHER_ICON_APPS_ENABLED);
// Settings.Global.SHOW_PROCESSES intentionally excluded since it's deprecated.
dumpSetting(s, p,
Settings.Global.SHOW_RESTART_IN_CRASH_DIALOG,
@@ -1127,6 +1130,9 @@
dumpSetting(s, p,
Settings.Global.SHOW_MUTE_IN_CRASH_DIALOG,
GlobalSettingsProto.SHOW_MUTE_IN_CRASH_DIALOG);
+ dumpSetting(s, p,
+ Settings.Global.SHOW_NEW_APP_INSTALLED_NOTIFICATION_ENABLED,
+ GlobalSettingsProto.SHOW_NEW_APP_INSTALLED_NOTIFICATION_ENABLED);
final long smartSelectToken = p.start(GlobalSettingsProto.SMART_SELECTION);
dumpSetting(s, p,
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/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
index bb3bfc1..e3584cf 100644
--- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
@@ -679,6 +679,7 @@
public boolean isDismissGesture(MotionEvent ev) {
float translation = getTranslation(mCurrView);
return ev.getActionMasked() == MotionEvent.ACTION_UP
+ && !mFalsingManager.isUnlockingDisabled()
&& !isFalseGesture(ev) && (swipedFastEnough() || swipedFarEnough())
&& mCallback.canChildBeDismissedInDirection(mCurrView, translation > 0);
}
diff --git a/packages/SystemUI/src/com/android/systemui/analytics/DataCollector.java b/packages/SystemUI/src/com/android/systemui/analytics/DataCollector.java
index 69e347c9..4010c43 100644
--- a/packages/SystemUI/src/com/android/systemui/analytics/DataCollector.java
+++ b/packages/SystemUI/src/com/android/systemui/analytics/DataCollector.java
@@ -16,6 +16,9 @@
package com.android.systemui.analytics;
+import static com.android.systemui.statusbar.phone.nano.TouchAnalyticsProto.Session;
+import static com.android.systemui.statusbar.phone.nano.TouchAnalyticsProto.Session.PhoneEvent;
+
import android.content.Context;
import android.database.ContentObserver;
import android.hardware.Sensor;
@@ -36,9 +39,6 @@
import java.io.FileOutputStream;
import java.io.IOException;
-import static com.android.systemui.statusbar.phone.nano.TouchAnalyticsProto.Session;
-import static com.android.systemui.statusbar.phone.nano.TouchAnalyticsProto.Session.PhoneEvent;
-
/**
* Tracks touch, sensor and phone events when the lockscreen is on. If the phone is unlocked
* the data containing these events is saved to a file. This data is collected
@@ -53,6 +53,8 @@
private static final String COLLECT_BAD_TOUCHES = "data_collector_collect_bad_touches";
private static final String ALLOW_REJECTED_TOUCH_REPORTS =
"data_collector_allow_rejected_touch_reports";
+ private static final String DISABLE_UNLOCKING_FOR_FALSING_COLLECTION =
+ "data_collector_disable_unlocking";
private static final long TIMEOUT_MILLIS = 11000; // 11 seconds.
public static final boolean DEBUG = false;
@@ -65,11 +67,11 @@
private SensorLoggerSession mCurrentSession = null;
private boolean mEnableCollector = false;
- private boolean mTimeoutActive = false;
private boolean mCollectBadTouches = false;
private boolean mCornerSwiping = false;
private boolean mTrackingStarted = false;
private boolean mAllowReportRejectedTouch = false;
+ private boolean mDisableUnlocking = false;
private static DataCollector sInstance = null;
@@ -98,6 +100,11 @@
mSettingsObserver,
UserHandle.USER_ALL);
+ mContext.getContentResolver().registerContentObserver(
+ Settings.Secure.getUriFor(DISABLE_UNLOCKING_FOR_FALSING_COLLECTION), false,
+ mSettingsObserver,
+ UserHandle.USER_ALL);
+
updateConfiguration();
}
@@ -118,6 +125,9 @@
mAllowReportRejectedTouch = Build.IS_DEBUGGABLE && 0 != Settings.Secure.getInt(
mContext.getContentResolver(),
ALLOW_REJECTED_TOUCH_REPORTS, 0);
+ mDisableUnlocking = mEnableCollector && Build.IS_DEBUGGABLE && 0 != Settings.Secure.getInt(
+ mContext.getContentResolver(),
+ DISABLE_UNLOCKING_FOR_FALSING_COLLECTION, 0);
}
private boolean sessionEntrypoint() {
@@ -144,7 +154,7 @@
SensorLoggerSession session = mCurrentSession;
mCurrentSession = null;
- if (mEnableCollector) {
+ if (mEnableCollector || mDisableUnlocking) {
session.end(System.currentTimeMillis(), result);
queueSession(session);
}
@@ -183,11 +193,11 @@
byte[] b = Session.toByteArray(currentSession.toProto());
String dir = mContext.getFilesDir().getAbsolutePath();
if (currentSession.getResult() != Session.SUCCESS) {
- if (!mCollectBadTouches) {
+ if (!mDisableUnlocking && !mCollectBadTouches) {
return;
}
dir += "/bad_touches";
- } else {
+ } else if (!mDisableUnlocking) {
dir += "/good_touches";
}
@@ -208,19 +218,6 @@
public synchronized void onSensorChanged(SensorEvent event) {
if (isEnabled() && mCurrentSession != null) {
mCurrentSession.addSensorEvent(event, System.nanoTime());
- enforceTimeout();
- }
- }
-
- private void enforceTimeout() {
- if (mTimeoutActive) {
- if (System.currentTimeMillis() - mCurrentSession.getStartTimestampMillis()
- > TIMEOUT_MILLIS) {
- onSessionEnd(Session.UNKNOWN);
- if (DEBUG) {
- Log.i(TAG, "Analytics timed out.");
- }
- }
}
}
@@ -233,9 +230,12 @@
* rejected touch report.
*/
public boolean isEnabled() {
- return mEnableCollector || mAllowReportRejectedTouch;
+ return mEnableCollector || mAllowReportRejectedTouch || mDisableUnlocking;
}
+ public boolean isUnlockingDisabled() {
+ return mDisableUnlocking;
+ }
/**
* @return true if the full data set for data gathering should be collected - including
* extensive sensor data, which is is not normally included with rejected touch reports.
@@ -450,7 +450,6 @@
}
mCurrentSession.addMotionEvent(event);
mCurrentSession.setTouchArea(width, height);
- enforceTimeout();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManager.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManager.java
index 3d578c3..2c61da3 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManager.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManager.java
@@ -201,6 +201,9 @@
return mHumanInteractionClassifier.isEnabled() || mDataCollector.isEnabled();
}
+ public boolean isUnlockingDisabled() {
+ return mDataCollector.isUnlockingDisabled();
+ }
/**
* @return true if the classifier determined that this is not a human interacting with the phone
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/DragDownHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/DragDownHelper.java
index 8526afd..8a86826 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/DragDownHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/DragDownHelper.java
@@ -128,7 +128,8 @@
}
return true;
case MotionEvent.ACTION_UP:
- if (!isFalseTouch() && mDragDownCallback.onDraggedDown(mStartingChild,
+ if (!mFalsingManager.isUnlockingDisabled() && !isFalseTouch()
+ && mDragDownCallback.onDraggedDown(mStartingChild,
(int) (y - mInitialTouchY))) {
if (mStartingChild == null) {
cancelExpansion();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index a00eac4..960d221 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -51,6 +51,7 @@
import com.android.systemui.Dependency;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
+import com.android.systemui.statusbar.StatusBarStateController.StateListener;
import com.android.systemui.statusbar.phone.KeyguardIndicationTextView;
import com.android.systemui.statusbar.phone.LockIcon;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
@@ -66,7 +67,7 @@
/**
* Controls the indications and error messages shown on the Keyguard
*/
-public class KeyguardIndicationController {
+public class KeyguardIndicationController implements StateListener {
private static final String TAG = "KeyguardIndication";
private static final boolean DEBUG_CHARGING_SPEED = false;
@@ -154,6 +155,19 @@
mContext.registerReceiverAsUser(mTickReceiver, UserHandle.SYSTEM,
new IntentFilter(Intent.ACTION_TIME_TICK), null,
Dependency.get(Dependency.TIME_TICK_HANDLER));
+
+ Dependency.get(StatusBarStateController.class).addListener(this);
+ }
+
+ /**
+ * Used by {@link com.android.systemui.statusbar.phone.StatusBar} to give the indication
+ * controller a chance to unregister itself as a receiver.
+ *
+ * //TODO: This can probably be converted to a fragment and not have to be manually recreated
+ */
+ public void destroy() {
+ mContext.unregisterReceiver(mTickReceiver);
+ Dependency.get(StatusBarStateController.class).removeListener(this);
}
/**
@@ -518,6 +532,16 @@
updateAlphas();
}
+ @Override
+ public void onStateChanged(int newState) {
+ // don't care
+ }
+
+ @Override
+ public void onDozingChanged(boolean isDozing) {
+ setDozing(isDozing);
+ }
+
protected class BaseKeyguardCallback extends KeyguardUpdateMonitorCallback {
public static final int HIDE_DELAY_MS = 5000;
private int mLastSuccessiveErrorMessage = -1;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
index b5fbde1..5dfd5d0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
@@ -29,9 +29,11 @@
import com.android.internal.statusbar.NotificationVisibility;
import com.android.systemui.Dependency;
import com.android.systemui.UiOffloadThread;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.NotificationListener;
+import com.android.systemui.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.StatusBarStateController.StateListener;
import com.android.systemui.statusbar.notification.NotificationData;
+import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
import java.util.ArrayList;
@@ -42,7 +44,7 @@
* Handles notification logging, in particular, logging which notifications are visible and which
* are not.
*/
-public class NotificationLogger {
+public class NotificationLogger implements StateListener {
private static final String TAG = "NotificationLogger";
/** The minimum delay in ms between reports of notification visibility. */
@@ -63,7 +65,7 @@
protected IStatusBarService mBarService;
private long mLastVisibilityReportUptimeMs;
private NotificationListContainer mListContainer;
- private Object mDozingLock = new Object();
+ private final Object mDozingLock = new Object();
private boolean mDozing;
protected final OnChildLocationsChangedListener mNotificationLocationsChangedListener =
@@ -146,6 +148,8 @@
public NotificationLogger() {
mBarService = IStatusBarService.Stub.asInterface(
ServiceManager.getService(Context.STATUS_BAR_SERVICE));
+ // Not expected to be destroyed, don't need to unsubscribe
+ Dependency.get(StatusBarStateController.class).addListener(this);
}
public void setUpWithContainer(NotificationListContainer listContainer) {
@@ -175,7 +179,7 @@
mNotificationLocationsChangedListener.onChildLocationsChanged();
}
- public void setDozing(boolean dozing) {
+ private void setDozing(boolean dozing) {
synchronized (mDozingLock) {
mDozing = dozing;
}
@@ -258,6 +262,16 @@
return mVisibilityReporter;
}
+ @Override
+ public void onStateChanged(int newState) {
+ // don't care about state change
+ }
+
+ @Override
+ public void onDozingChanged(boolean isDozing) {
+ setDozing(isDozing);
+ }
+
/**
* A listener that is notified when some child locations might have changed.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
index a0597dc..94b2cde 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
@@ -20,13 +20,17 @@
import android.os.Handler;
import android.util.Log;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.Dependency;
import com.android.systemui.doze.DozeHost;
import com.android.systemui.doze.DozeLog;
+import com.android.systemui.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.StatusBarStateController.StateListener;
/**
* Controller which handles all the doze animations of the scrims.
*/
-public class DozeScrimController {
+public class DozeScrimController implements StateListener {
private static final String TAG = "DozeScrimController";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
@@ -83,8 +87,11 @@
public DozeScrimController(DozeParameters dozeParameters) {
mDozeParameters = dozeParameters;
+ //Never expected to be destroyed
+ Dependency.get(StatusBarStateController.class).addListener(this);
}
+ @VisibleForTesting
public void setDozing(boolean dozing) {
if (mDozing == dozing) return;
mDozing = dozing;
@@ -181,4 +188,14 @@
public ScrimController.Callback getScrimCallback() {
return mScrimCallback;
}
+
+ @Override
+ public void onStateChanged(int newState) {
+ // don't care
+ }
+
+ @Override
+ public void onDozingChanged(boolean isDozing) {
+ setDozing(isDozing);
+ }
}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardDismissUtil.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardDismissUtil.java
index 462201c..b3d0bf8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardDismissUtil.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardDismissUtil.java
@@ -35,7 +35,7 @@
}
/**
- * Executes an action that requres the screen to be unlocked.
+ * Executes an action that requires the screen to be unlocked.
*
* <p>Must be called after {@link #setDismissHandler}.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
index c08366a..6b12dd9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
@@ -16,22 +16,24 @@
package com.android.systemui.statusbar.phone;
-import android.annotation.NonNull;
import android.app.Notification;
import android.os.SystemClock;
import android.service.notification.StatusBarNotification;
-import androidx.annotation.Nullable;
import android.util.Log;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.Dependency;
import com.android.systemui.statusbar.AlertingNotificationManager;
import com.android.systemui.statusbar.AmbientPulseManager;
import com.android.systemui.statusbar.AmbientPulseManager.OnAmbientChangedListener;
import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.StatusBarStateController.StateListener;
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-import com.android.systemui.statusbar.notification.NotificationData;
import com.android.systemui.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.StatusBarStateController.StateListener;
+import com.android.systemui.statusbar.notification.NotificationData;
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
@@ -48,7 +50,7 @@
* A class to handle notifications and their corresponding groups.
*/
public class NotificationGroupManager implements OnHeadsUpChangedListener,
- OnAmbientChangedListener {
+ OnAmbientChangedListener, StateListener {
private static final String TAG = "NotificationGroupManager";
private static final long ALERT_TRANSFER_TIMEOUT = 300;
@@ -62,10 +64,8 @@
private boolean mIsUpdatingUnchangedGroup;
private HashMap<String, NotificationData.Entry> mPendingNotifications;
- private final StateListener mStateListener = this::setStatusBarState;
-
public NotificationGroupManager() {
- Dependency.get(StatusBarStateController.class).addListener(mStateListener);
+ Dependency.get(StatusBarStateController.class).addListener(this);
}
public void setOnGroupChangeListener(OnGroupChangeListener listener) {
@@ -185,6 +185,7 @@
* specific alert state logic based off when the state changes.
* @param isDozing if the device is dozing.
*/
+ @VisibleForTesting
public void setDozing(boolean isDozing) {
if (mIsDozing != isDozing) {
for (NotificationGroup group : mGroupMap.values()) {
@@ -732,6 +733,16 @@
mPendingNotifications = pendingNotifications;
}
+ @Override
+ public void onStateChanged(int newState) {
+ setStatusBarState(newState);
+ }
+
+ @Override
+ public void onDozingChanged(boolean isDozing) {
+ setDozing(isDozing);
+ }
+
public static class NotificationGroup {
public final HashMap<String, NotificationData.Entry> children = new HashMap<>();
public NotificationData.Entry summary;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index 31facb7..f4c2e27 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -582,7 +582,7 @@
int stackScrollerPadding;
if (mBarState != StatusBarState.KEYGUARD) {
stackScrollerPadding = (mQs != null ? mQs.getHeader().getHeight() : 0) + mQsPeekHeight
- + mQsNotificationTopPadding;
+ + mQsNotificationTopPadding;
} else {
int totalHeight = getHeight();
int bottomPadding = Math.max(mIndicationBottomPadding, mAmbientIndicationBottomPadding);
@@ -754,7 +754,7 @@
mQsExpandImmediate = true;
mNotificationStackScroller.setShouldShowShelfOnly(true);
}
- if (isFullyCollapsed()){
+ if (isFullyCollapsed()) {
expand(true /* animate */);
} else {
flingSettings(0 /* velocity */, FLING_EXPAND);
@@ -921,7 +921,7 @@
}
private boolean flingExpandsQs(float vel) {
- if (isFalseTouch()) {
+ if (mFalsingManager.isUnlockingDisabled() || isFalseTouch()) {
return false;
}
if (Math.abs(vel) < mFlingAnimationUtils.getMinVelocityPxPerSecond()) {
@@ -1046,11 +1046,11 @@
final boolean stylusButtonClickDrag = action == MotionEvent.ACTION_DOWN
&& (event.isButtonPressed(MotionEvent.BUTTON_STYLUS_PRIMARY)
- || event.isButtonPressed(MotionEvent.BUTTON_STYLUS_SECONDARY));
+ || event.isButtonPressed(MotionEvent.BUTTON_STYLUS_SECONDARY));
final boolean mouseButtonClickDrag = action == MotionEvent.ACTION_DOWN
&& (event.isButtonPressed(MotionEvent.BUTTON_SECONDARY)
- || event.isButtonPressed(MotionEvent.BUTTON_TERTIARY));
+ || event.isButtonPressed(MotionEvent.BUTTON_TERTIARY));
return twoFingerDrag || stylusButtonClickDrag || mouseButtonClickDrag;
}
@@ -1321,12 +1321,12 @@
private final ValueAnimator.AnimatorUpdateListener mStatusBarAnimateAlphaListener =
new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- mKeyguardStatusBarAnimateAlpha = (float) animation.getAnimatedValue();
- updateHeaderKeyguardAlpha();
- }
- };
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ mKeyguardStatusBarAnimateAlpha = (float) animation.getAnimatedValue();
+ updateHeaderKeyguardAlpha();
+ }
+ };
private void animateKeyguardStatusBarIn(long duration) {
mKeyguardStatusBar.setVisibility(View.VISIBLE);
@@ -1382,7 +1382,7 @@
if (keyguardFadingAway) {
mKeyguardStatusView.animate()
.setStartDelay(mKeyguardMonitor.getKeyguardFadingAwayDelay())
- .setDuration(mKeyguardMonitor.getKeyguardFadingAwayDuration()/2)
+ .setDuration(mKeyguardMonitor.getKeyguardFadingAwayDuration() / 2)
.start();
}
} else if (mBarState == StatusBarState.SHADE_LOCKED
@@ -1425,8 +1425,8 @@
updateEmptyShadeView();
mQsNavbarScrim.setVisibility(mBarState == StatusBarState.SHADE && mQsExpanded
&& !mStackScrollerOverscrolling && mQsScrimEnabled
- ? View.VISIBLE
- : View.INVISIBLE);
+ ? View.VISIBLE
+ : View.INVISIBLE);
if (mKeyguardUserSwitcher != null && mQsExpanded && !mStackScrollerOverscrolling) {
mKeyguardUserSwitcher.hideIfNotSimple(true /* animate */);
}
@@ -1459,7 +1459,8 @@
setAccessibilityPaneTitle(determineAccessibilityPaneTitle());
}
- if (mQsFullyExpanded && mFalsingManager.shouldEnforceBouncer()) {
+ if (!mFalsingManager.isUnlockingDisabled() && mQsFullyExpanded
+ && mFalsingManager.shouldEnforceBouncer()) {
mStatusBar.executeRunnableDismissingKeyguard(null, null /* cancelAction */,
false /* dismissShade */, true /* afterKeyguardGone */, false /* deferred */);
}
@@ -2130,8 +2131,7 @@
}
}, null, true /* dismissShade */, false /* afterKeyguardGone */,
true /* deferred */);
- }
- else {
+ } else {
mKeyguardBottomArea.launchLeftAffordance();
}
} else {
@@ -2588,7 +2588,7 @@
x = Math.min(rightMost, Math.max(leftMost, x));
setVerticalPanelTranslation(x -
(mNotificationStackScroller.getLeft() + mNotificationStackScroller.getWidth() / 2));
- }
+ }
private void resetVerticalPanelPosition() {
setVerticalPanelTranslation(0f);
@@ -2716,8 +2716,8 @@
String packageToLaunch = (resolveInfo == null || resolveInfo.activityInfo == null)
? null : resolveInfo.activityInfo.packageName;
return packageToLaunch != null &&
- (keyguardIsShowing || !isForegroundApp(packageToLaunch)) &&
- !mAffordanceHelper.isSwipingInProgress();
+ (keyguardIsShowing || !isForegroundApp(packageToLaunch))
+ && !mAffordanceHelper.isSwipingInProgress();
}
/**
@@ -2884,13 +2884,14 @@
}
public void setStatusAccessibilityImportance(int mode) {
- mKeyguardStatusView.setImportantForAccessibility(mode);
+ mKeyguardStatusView.setImportantForAccessibility(mode);
}
/**
* TODO: this should be removed.
* It's not correct to pass this view forward because other classes will end up adding
* children to it. Theme will be out of sync.
+ *
* @return bottom area view
*/
public KeyguardBottomAreaView getKeyguardBottomAreaView() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
index f29b7ca..021b430 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
@@ -670,6 +670,10 @@
* @return whether a fling should expands the panel; contracts otherwise
*/
protected boolean flingExpands(float vel, float vectorVel, float x, float y) {
+ if (mFalsingManager.isUnlockingDisabled()) {
+ return true;
+ }
+
if (isFalseTouch(x, y)) {
return true;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 2337857..6279d50 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -586,7 +586,6 @@
mNotificationLogger = Dependency.get(NotificationLogger.class);
mRemoteInputManager = Dependency.get(NotificationRemoteInputManager.class);
mNotificationListener = Dependency.get(NotificationListener.class);
- mGroupManager = Dependency.get(NotificationGroupManager.class);
mNetworkController = Dependency.get(NetworkController.class);
mUserSwitcherController = Dependency.get(UserSwitcherController.class);
mScreenLifecycle = Dependency.get(ScreenLifecycle.class);
@@ -1096,6 +1095,9 @@
@Override
public void onThemeChanged() {
// Recreate Indication controller because internal references changed
+ if (mKeyguardIndicationController != null) {
+ mKeyguardIndicationController.destroy();
+ }
mKeyguardIndicationController =
SystemUIFactory.getInstance().createKeyguardIndicationController(mContext,
mStatusBarWindow.findViewById(R.id.keyguard_indication_area),
@@ -1104,7 +1106,6 @@
mKeyguardIndicationController
.setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
mKeyguardIndicationController.setVisible(mState == StatusBarState.KEYGUARD);
- mKeyguardIndicationController.setDozing(mDozing);
if (mStatusBarKeyguardViewManager != null) {
mStatusBarKeyguardViewManager.onThemeChanged();
}
@@ -3247,12 +3248,8 @@
boolean animate = (!mDozing && mDozeServiceHost.shouldAnimateWakeup())
|| (mDozing && mDozeServiceHost.shouldAnimateScreenOff() && sleepingFromKeyguard);
- mDozeScrimController.setDozing(mDozing);
- mKeyguardIndicationController.setDozing(mDozing);
mNotificationPanel.setDozing(mDozing, animate, mWakeUpTouchLocation,
mDozeServiceHost.wasPassivelyInterrupted());
- mNotificationLogger.setDozing(mDozing);
- mGroupManager.setDozing(mDozing);
updateQsExpansionEnabled();
Trace.endSection();
}
@@ -3442,13 +3439,6 @@
updateQsExpansionEnabled();
mKeyguardViewMediator.setAodShowing(mDozing);
- //TODO: make these folks listeners of StatusBarStateController.onDozingChanged
- mStatusBarWindowController.setDozing(mDozing);
- mStatusBarKeyguardViewManager.setDozing(mDozing);
- if (mAmbientIndicationContainer instanceof DozeReceiver) {
- ((DozeReceiver) mAmbientIndicationContainer).setDozing(mDozing);
- }
-
mEntryManager.updateNotifications();
updateDozingState();
updateScrimController();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index df99a9c..484fe11 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -59,7 +59,8 @@
* which is in turn, reported to this class by the current
* {@link com.android.keyguard.KeyguardViewBase}.
*/
-public class StatusBarKeyguardViewManager implements RemoteInputController.Callback {
+public class StatusBarKeyguardViewManager implements RemoteInputController.Callback,
+ StatusBarStateController.StateListener {
// When hiding the Keyguard with timing supplied from WindowManager, better be early than late.
private static final long HIDE_TIMING_CORRECTION_MS = - 16 * 3;
@@ -150,6 +151,7 @@
mLockPatternUtils = lockPatternUtils;
mStatusBarWindowController = Dependency.get(StatusBarWindowController.class);
KeyguardUpdateMonitor.getInstance(context).registerCallback(mUpdateMonitorCallback);
+ Dependency.get(StatusBarStateController.class).addListener(this);
}
public void registerStatusBar(StatusBar statusBar,
@@ -334,7 +336,7 @@
updateStates();
}
- public void setDozing(boolean dozing) {
+ private void setDozing(boolean dozing) {
if (mDozing != dozing) {
mDozing = dozing;
if (dozing || mBouncer.needsFullscreenBouncer() || mOccluded) {
@@ -781,6 +783,16 @@
}
}
+ @Override
+ public void onStateChanged(int newState) {
+ // Nothing
+ }
+
+ @Override
+ public void onDozingChanged(boolean isDozing) {
+ setDozing(isDozing);
+ }
+
private static class DismissWithActionRequest {
final OnDismissAction dismissAction;
final Runnable cancelAction;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java
index 0d37b55..11de941 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java
@@ -76,7 +76,6 @@
private final State mCurrentState = new State();
private OtherwisedCollapsedListener mListener;
- private final StateListener mStateListener = this::setStatusBarState;
private final SysuiColorExtractor mColorExtractor = Dependency.get(SysuiColorExtractor.class);
public StatusBarWindowController(Context context) {
@@ -564,6 +563,18 @@
}
}
+ private final StateListener mStateListener = new StateListener() {
+ @Override
+ public void onStateChanged(int newState) {
+ setStatusBarState(newState);
+ }
+
+ @Override
+ public void onDozingChanged(boolean isDozing) {
+ setDozing(isDozing);
+ }
+ };
+
/**
* Custom listener to pipe data back to plugins about whether or not the status bar would be
* collapsed if not for the plugin.
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/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index 6203340..275f3dc 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -77,7 +77,6 @@
*/
public class LauncherAppsService extends SystemService {
- private static final boolean SHOW_HIDDEN_APP_ENABLED = false;
private final LauncherAppsImpl mLauncherAppsImpl;
public LauncherAppsService(Context context) {
@@ -310,7 +309,8 @@
.addCategory(Intent.CATEGORY_LAUNCHER)
.setPackage(packageName),
user);
- if (!SHOW_HIDDEN_APP_ENABLED) {
+ if (Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.SHOW_HIDDEN_LAUNCHER_ICON_APPS_ENABLED, 0) == 0) {
return launcherActivities;
}
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 0e37bca..c90f083 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;
@@ -12358,28 +12359,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;
@@ -12391,6 +12379,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);
@@ -13568,88 +13572,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
@@ -13837,7 +13866,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;
@@ -13845,17 +13950,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;
@@ -13870,6 +13978,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))
@@ -14291,6 +14427,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
@@ -14301,8 +14438,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;
@@ -14317,6 +14455,7 @@
this.traceCookie = traceCookie;
this.signingDetails = signingDetails;
this.installReason = installReason;
+ this.mMultiPackageInstallParams = multiPackageInstallParams;
}
abstract int copyApk();
@@ -14412,7 +14551,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");
}
@@ -14422,7 +14561,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;
}
@@ -14614,7 +14753,7 @@
params.getUser(), null /* instruction sets */, params.packageAbiOverride,
params.grantedRuntimePermissions,
params.traceMethod, params.traceCookie, params.signingDetails,
- params.installReason);
+ params.installReason, params.mParentInstallParams);
}
int copyApk() {
@@ -15019,10 +15158,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);
}
@@ -15362,7 +15501,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;
}
}
@@ -23434,6 +23573,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/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java
index 4018eef..32f389a 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java
@@ -31,9 +31,11 @@
import android.app.Notification;
import android.app.NotificationChannel;
+import android.app.NotificationManager;
import android.app.NotificationManager.Policy;
import android.media.AudioAttributes;
import android.service.notification.StatusBarNotification;
+import android.service.notification.ZenModeConfig;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -43,7 +45,6 @@
import com.android.server.UiServiceTestCase;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -75,7 +76,6 @@
return new NotificationRecord(mContext, sbn, c);
}
- @Ignore
@Test
public void testIsMessage() {
NotificationRecord r = getNotificationRecord();
@@ -87,7 +87,6 @@
assertFalse(mZenModeFiltering.isMessage(r));
}
- @Ignore
@Test
public void testIsAlarm() {
NotificationChannel c = mock(NotificationChannel.class);
@@ -102,7 +101,6 @@
assertTrue(mZenModeFiltering.isAlarm(r));
}
- @Ignore
@Test
public void testIsAlarm_wrongCategory() {
NotificationRecord r = getNotificationRecord();
@@ -110,7 +108,6 @@
assertFalse(mZenModeFiltering.isAlarm(r));
}
- @Ignore
@Test
public void testIsAlarm_wrongUsage() {
NotificationChannel c = mock(NotificationChannel.class);
@@ -121,7 +118,6 @@
assertFalse(mZenModeFiltering.isAlarm(r));
}
- @Ignore
@Test
public void testSuppressDNDInfo_yes_VisEffectsAllowed() {
NotificationRecord r = getNotificationRecord();
@@ -133,7 +129,6 @@
assertTrue(mZenModeFiltering.shouldIntercept(ZEN_MODE_IMPORTANT_INTERRUPTIONS, policy, r));
}
- @Ignore
@Test
public void testSuppressDNDInfo_yes_WrongId() {
NotificationRecord r = getNotificationRecord();
@@ -144,7 +139,6 @@
assertTrue(mZenModeFiltering.shouldIntercept(ZEN_MODE_IMPORTANT_INTERRUPTIONS, policy, r));
}
- @Ignore
@Test
public void testSuppressDNDInfo_yes_WrongPackage() {
NotificationRecord r = getNotificationRecord();
@@ -155,7 +149,6 @@
assertTrue(mZenModeFiltering.shouldIntercept(ZEN_MODE_IMPORTANT_INTERRUPTIONS, policy, r));
}
- @Ignore
@Test
public void testSuppressDNDInfo_no() {
NotificationRecord r = getNotificationRecord();
@@ -168,7 +161,6 @@
assertFalse(mZenModeFiltering.shouldIntercept(ZEN_MODE_NO_INTERRUPTIONS, policy, r));
}
- @Ignore
@Test
public void testSuppressAnything_yes_ZenModeOff() {
NotificationRecord r = getNotificationRecord();
@@ -178,7 +170,6 @@
assertFalse(mZenModeFiltering.shouldIntercept(ZEN_MODE_OFF, policy, r));
}
- @Ignore
@Test
public void testSuppressAnything_bypass_ZenModeOn() {
NotificationRecord r = getNotificationRecord();
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);
}
}
diff --git a/wifi/java/android/net/wifi/ScanResult.java b/wifi/java/android/net/wifi/ScanResult.java
index 3a4e88b..9b9247d 100644
--- a/wifi/java/android/net/wifi/ScanResult.java
+++ b/wifi/java/android/net/wifi/ScanResult.java
@@ -86,9 +86,9 @@
public static final int PROTOCOL_WPA = 1;
/**
* @hide
- * Security protocol type: WPA version 2, also called RSN.
+ * Security protocol type: RSN, for WPA version 2, and version 3.
*/
- public static final int PROTOCOL_WPA2 = 2;
+ public static final int PROTOCOL_RSN = 2;
/**
* @hide
* Security protocol type:
@@ -138,7 +138,21 @@
* Used for Hotspot 2.0.
*/
public static final int KEY_MGMT_OSEN = 7;
-
+ /**
+ * @hide
+ * Security key management scheme: SAE.
+ */
+ public static final int KEY_MGMT_SAE = 8;
+ /**
+ * @hide
+ * Security key management scheme: OWE.
+ */
+ public static final int KEY_MGMT_OWE = 9;
+ /**
+ * @hide
+ * Security key management scheme: SUITE_B_192.
+ */
+ public static final int KEY_MGMT_EAP_SUITE_B_192 = 10;
/**
* @hide
* No cipher suite.
@@ -159,6 +173,11 @@
* Cipher suite: CCMP
*/
public static final int CIPHER_CCMP = 3;
+ /**
+ * @hide
+ * Cipher suite: GCMP
+ */
+ public static final int CIPHER_GCMP_256 = 4;
/**
* The detected signal level in dBm, also known as the RSSI.
diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java
index 46dc823..8fc9b97 100644
--- a/wifi/java/android/net/wifi/WifiConfiguration.java
+++ b/wifi/java/android/net/wifi/WifiConfiguration.java
@@ -130,10 +130,26 @@
*/
public static final int FT_EAP = 7;
+ /**
+ * Simultaneous Authentication of Equals
+ */
+ public static final int SAE = 8;
+
+ /**
+ * Opportunististic Wireless Encryption
+ */
+ public static final int OWE = 9;
+
+ /**
+ * SUITE_B_192 192 bit level
+ */
+ public static final int SUITE_B_192 = 10;
+
public static final String varName = "key_mgmt";
public static final String[] strings = { "NONE", "WPA_PSK", "WPA_EAP",
- "IEEE8021X", "WPA2_PSK", "OSEN", "FT_PSK", "FT_EAP" };
+ "IEEE8021X", "WPA2_PSK", "OSEN", "FT_PSK", "FT_EAP",
+ "SAE", "OWE", "SUITE_B_192"};
}
/**
@@ -147,7 +163,7 @@
* is discouraged. WPA-2 (RSN) should be used instead. */
@Deprecated
public static final int WPA = 0;
- /** WPA2/IEEE 802.11i */
+ /** RSN WPA2/WPA3/IEEE 802.11i */
public static final int RSN = 1;
/** HS2.0 r2 OSEN
* @hide
@@ -195,10 +211,14 @@
public static final int TKIP = 1;
/** AES in Counter mode with CBC-MAC [RFC 3610, IEEE 802.11i/D7.0] */
public static final int CCMP = 2;
+ /**
+ * AES in Galois/Counter Mode
+ */
+ public static final int GCMP_256 = 3;
public static final String varName = "pairwise";
- public static final String[] strings = { "NONE", "TKIP", "CCMP" };
+ public static final String[] strings = { "NONE", "TKIP", "CCMP", "GCMP_256" };
}
/**
@@ -208,6 +228,7 @@
* TKIP = Temporal Key Integrity Protocol [IEEE 802.11i/D7.0]
* WEP104 = WEP (Wired Equivalent Privacy) with 104-bit key
* WEP40 = WEP (Wired Equivalent Privacy) with 40-bit key (original 802.11)
+ * GCMP_256 = AES in Galois/Counter Mode
* </pre>
*/
public static class GroupCipher {
@@ -231,12 +252,64 @@
* @hide
*/
public static final int GTK_NOT_USED = 4;
+ /**
+ * AES in Galois/Counter Mode
+ */
+ public static final int GCMP_256 = 5;
public static final String varName = "group";
public static final String[] strings =
{ /* deprecated */ "WEP40", /* deprecated */ "WEP104",
- "TKIP", "CCMP", "GTK_NOT_USED" };
+ "TKIP", "CCMP", "GTK_NOT_USED", "GCMP_256" };
+ }
+
+ /**
+ * Recognized group management ciphers.
+ * <pre>
+ * BIP_CMAC_256 = Cipher-based Message Authentication Code 256 bits
+ * BIP_GMAC_128 = Galois Message Authentication Code 128 bits
+ * BIP_GMAC_256 = Galois Message Authentication Code 256 bits
+ * </pre>
+ */
+ public static class GroupMgmtCipher {
+ private GroupMgmtCipher() { }
+
+ /** CMAC-256 = Cipher-based Message Authentication Code */
+ public static final int BIP_CMAC_256 = 0;
+
+ /** GMAC-128 = Galois Message Authentication Code */
+ public static final int BIP_GMAC_128 = 1;
+
+ /** GMAC-256 = Galois Message Authentication Code */
+ public static final int BIP_GMAC_256 = 2;
+
+ private static final String varName = "groupMgmt";
+
+ private static final String[] strings = { "BIP_CMAC_256",
+ "BIP_GMAC_128", "BIP_GMAC_256"};
+ }
+
+ /**
+ * Recognized suiteB ciphers.
+ * <pre>
+ * ECDHE_ECDSA
+ * ECDHE_RSA
+ * </pre>
+ * @hide
+ */
+ public static class SuiteBCipher {
+ private SuiteBCipher() { }
+
+ /** Diffie-Hellman with Elliptic Curve_ECDSA signature */
+ public static final int ECDHE_ECDSA = 0;
+
+ /** Diffie-Hellman with_RSA signature */
+ public static final int ECDHE_RSA = 1;
+
+ private static final String varName = "SuiteB";
+
+ private static final String[] strings = { "ECDHE_ECDSA", "ECDHE_RSA" };
}
/** Possible status of a network configuration. */
@@ -414,6 +487,17 @@
*/
public BitSet allowedGroupCiphers;
/**
+ * The set of group management ciphers supported by this configuration.
+ * See {@link GroupMgmtCipher} for descriptions of the values.
+ */
+ public BitSet allowedGroupMgmtCiphers;
+ /**
+ * The set of SuiteB ciphers supported by this configuration.
+ * To be used for WPA3-Enterprise mode.
+ * See {@link SuiteBCipher} for descriptions of the values.
+ */
+ public BitSet allowedSuiteBCiphers;
+ /**
* The enterprise configuration details specifying the EAP method,
* certificates and other settings associated with the EAP.
*/
@@ -738,7 +822,8 @@
public boolean isOpenNetwork() {
final int cardinality = allowedKeyManagement.cardinality();
final boolean hasNoKeyMgmt = cardinality == 0
- || (cardinality == 1 && allowedKeyManagement.get(KeyMgmt.NONE));
+ || (cardinality == 1 && (allowedKeyManagement.get(KeyMgmt.NONE)
+ || allowedKeyManagement.get(KeyMgmt.OWE)));
boolean hasNoWepKeys = true;
if (wepKeys != null) {
@@ -1543,6 +1628,8 @@
allowedAuthAlgorithms = new BitSet();
allowedPairwiseCiphers = new BitSet();
allowedGroupCiphers = new BitSet();
+ allowedGroupMgmtCiphers = new BitSet();
+ allowedSuiteBCiphers = new BitSet();
wepKeys = new String[4];
for (int i = 0; i < wepKeys.length; i++) {
wepKeys[i] = null;
@@ -1596,7 +1683,8 @@
@UnsupportedAppUsage
public boolean isEnterprise() {
return (allowedKeyManagement.get(KeyMgmt.WPA_EAP)
- || allowedKeyManagement.get(KeyMgmt.IEEE8021X))
+ || allowedKeyManagement.get(KeyMgmt.IEEE8021X)
+ || allowedKeyManagement.get(KeyMgmt.SUITE_B_192))
&& enterpriseConfig != null
&& enterpriseConfig.getEapMethod() != WifiEnterpriseConfig.Eap.NONE;
}
@@ -1614,6 +1702,7 @@
append(" BSSID: ").append(this.BSSID).append(" FQDN: ").append(this.FQDN)
.append(" PRIO: ").append(this.priority)
.append(" HIDDEN: ").append(this.hiddenSSID)
+ .append(" PMF: ").append(this.requirePMF)
.append('\n');
@@ -1726,10 +1815,35 @@
}
}
}
- sbuf.append('\n').append(" PSK: ");
+ sbuf.append('\n');
+ sbuf.append(" GroupMgmtCiphers:");
+ for (int gmc = 0; gmc < this.allowedGroupMgmtCiphers.size(); gmc++) {
+ if (this.allowedGroupMgmtCiphers.get(gmc)) {
+ sbuf.append(" ");
+ if (gmc < GroupMgmtCipher.strings.length) {
+ sbuf.append(GroupMgmtCipher.strings[gmc]);
+ } else {
+ sbuf.append("??");
+ }
+ }
+ }
+ sbuf.append('\n');
+ sbuf.append(" SuiteBCiphers:");
+ for (int sbc = 0; sbc < this.allowedSuiteBCiphers.size(); sbc++) {
+ if (this.allowedSuiteBCiphers.get(sbc)) {
+ sbuf.append(" ");
+ if (sbc < SuiteBCipher.strings.length) {
+ sbuf.append(SuiteBCipher.strings[sbc]);
+ } else {
+ sbuf.append("??");
+ }
+ }
+ }
+ sbuf.append('\n').append(" PSK/SAE: ");
if (this.preSharedKey != null) {
sbuf.append('*');
}
+
sbuf.append("\nEnterprise config:\n");
sbuf.append(enterpriseConfig);
@@ -1892,6 +2006,12 @@
return KeyMgmt.WPA_EAP;
} else if (allowedKeyManagement.get(KeyMgmt.IEEE8021X)) {
return KeyMgmt.IEEE8021X;
+ } else if (allowedKeyManagement.get(KeyMgmt.SAE)) {
+ return KeyMgmt.SAE;
+ } else if (allowedKeyManagement.get(KeyMgmt.OWE)) {
+ return KeyMgmt.OWE;
+ } else if (allowedKeyManagement.get(KeyMgmt.SUITE_B_192)) {
+ return KeyMgmt.SUITE_B_192;
}
return KeyMgmt.NONE;
}
@@ -1923,6 +2043,12 @@
key = SSID + KeyMgmt.strings[KeyMgmt.WPA_EAP];
} else if (wepKeys[0] != null) {
key = SSID + "WEP";
+ } else if (allowedKeyManagement.get(KeyMgmt.OWE)) {
+ key = SSID + KeyMgmt.strings[KeyMgmt.OWE];
+ } else if (allowedKeyManagement.get(KeyMgmt.SAE)) {
+ key = SSID + KeyMgmt.strings[KeyMgmt.SAE];
+ } else if (allowedKeyManagement.get(KeyMgmt.SUITE_B_192)) {
+ key = SSID + KeyMgmt.strings[KeyMgmt.SUITE_B_192];
} else {
key = SSID + KeyMgmt.strings[KeyMgmt.NONE];
}
@@ -2091,6 +2217,8 @@
allowedAuthAlgorithms = (BitSet) source.allowedAuthAlgorithms.clone();
allowedPairwiseCiphers = (BitSet) source.allowedPairwiseCiphers.clone();
allowedGroupCiphers = (BitSet) source.allowedGroupCiphers.clone();
+ allowedGroupMgmtCiphers = (BitSet) source.allowedGroupMgmtCiphers.clone();
+ allowedSuiteBCiphers = (BitSet) source.allowedSuiteBCiphers.clone();
enterpriseConfig = new WifiEnterpriseConfig(source.enterpriseConfig);
defaultGwMacAddress = source.defaultGwMacAddress;
@@ -2133,6 +2261,7 @@
recentFailure.setAssociationStatus(source.recentFailure.getAssociationStatus());
mRandomizedMacAddress = source.mRandomizedMacAddress;
macRandomizationSetting = source.macRandomizationSetting;
+ requirePMF = source.requirePMF;
}
}
@@ -2168,6 +2297,8 @@
writeBitSet(dest, allowedAuthAlgorithms);
writeBitSet(dest, allowedPairwiseCiphers);
writeBitSet(dest, allowedGroupCiphers);
+ writeBitSet(dest, allowedGroupMgmtCiphers);
+ writeBitSet(dest, allowedSuiteBCiphers);
dest.writeParcelable(enterpriseConfig, flags);
@@ -2236,6 +2367,8 @@
config.allowedAuthAlgorithms = readBitSet(in);
config.allowedPairwiseCiphers = readBitSet(in);
config.allowedGroupCiphers = readBitSet(in);
+ config.allowedGroupMgmtCiphers = readBitSet(in);
+ config.allowedSuiteBCiphers = readBitSet(in);
config.enterpriseConfig = in.readParcelable(null);
config.setIpConfiguration(in.readParcelable(null));
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index a99cab5..954b51f 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -1855,7 +1855,12 @@
public static final int WIFI_FEATURE_SCAN_RAND = 0x2000000; // Random MAC & Probe seq
/** @hide */
public static final int WIFI_FEATURE_TX_POWER_LIMIT = 0x4000000; // Set Tx power limit
-
+ /** @hide */
+ public static final int WIFI_FEATURE_WPA3_SAE = 0x8000000; // WPA3-Personal SAE
+ /** @hide */
+ public static final int WIFI_FEATURE_WPA3_SUITE_B = 0x10000000; // WPA3-Enterprise Suite-B
+ /** @hide */
+ public static final int WIFI_FEATURE_OWE = 0x20000000; // Enhanced Open
private int getSupportedFeatures() {
try {
@@ -4248,4 +4253,31 @@
private void updateVerboseLoggingEnabledFromService() {
mVerboseLoggingEnabled = getVerboseLoggingLevel() > 0;
}
+
+ /**
+ * @return true if this device supports WPA3-Personal SAE
+ * @hide
+ */
+ @SystemApi
+ public boolean isWpa3SaeSupported() {
+ return isFeatureSupported(WIFI_FEATURE_WPA3_SAE);
+ }
+
+ /**
+ * @return true if this device supports WPA3-Enterprise Suite-B-192
+ * @hide
+ */
+ @SystemApi
+ public boolean isWpa3SuiteBSupported() {
+ return isFeatureSupported(WIFI_FEATURE_WPA3_SUITE_B);
+ }
+
+ /**
+ * @return true if this device supports Wi-Fi Enhanced Open (OWE)
+ * @hide
+ */
+ @SystemApi
+ public boolean isOweSupported() {
+ return isFeatureSupported(WIFI_FEATURE_OWE);
+ }
}
diff --git a/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java b/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java
index 3b9f93e..5f3e1b2 100644
--- a/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java
@@ -155,7 +155,10 @@
@Test
public void testIsOpenNetwork_NotOpen_HasAuthType() {
for (int keyMgmt = 0; keyMgmt < WifiConfiguration.KeyMgmt.strings.length; keyMgmt++) {
- if (keyMgmt == WifiConfiguration.KeyMgmt.NONE) continue;
+ if (keyMgmt == WifiConfiguration.KeyMgmt.NONE
+ || keyMgmt == WifiConfiguration.KeyMgmt.OWE) {
+ continue;
+ }
WifiConfiguration config = new WifiConfiguration();
config.allowedKeyManagement.clear();
config.allowedKeyManagement.set(keyMgmt);