Merge "Add atom logging for intervention framework"
diff --git a/core/api/current.txt b/core/api/current.txt
index bc9f10f..f550952 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -6969,6 +6969,7 @@
method @Deprecated public void onStart(android.content.Intent, int);
method public int onStartCommand(android.content.Intent, int, int);
method public void onTaskRemoved(android.content.Intent);
+ method public void onTimeout(int);
method public void onTrimMemory(int);
method public boolean onUnbind(android.content.Intent);
method public final void startForeground(int, android.app.Notification);
@@ -12475,6 +12476,7 @@
field @Deprecated public static final int FOREGROUND_SERVICE_TYPE_NONE = 0; // 0x0
field @RequiresPermission(allOf={android.Manifest.permission.FOREGROUND_SERVICE_PHONE_CALL}, anyOf={android.Manifest.permission.MANAGE_OWN_CALLS}, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_PHONE_CALL = 4; // 0x4
field @RequiresPermission(value=android.Manifest.permission.FOREGROUND_SERVICE_REMOTE_MESSAGING, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_REMOTE_MESSAGING = 512; // 0x200
+ field public static final int FOREGROUND_SERVICE_TYPE_SHORT_SERVICE = 2048; // 0x800
field @RequiresPermission(value=android.Manifest.permission.FOREGROUND_SERVICE_SPECIAL_USE, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_SPECIAL_USE = 1073741824; // 0x40000000
field @RequiresPermission(value=android.Manifest.permission.FOREGROUND_SERVICE_SYSTEM_EXEMPTED, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED = 1024; // 0x400
field public int flags;
@@ -13025,10 +13027,12 @@
}
public final class CreateCredentialRequest implements android.os.Parcelable {
- ctor public CreateCredentialRequest(@NonNull String, @NonNull android.os.Bundle);
+ ctor public CreateCredentialRequest(@NonNull String, @NonNull android.os.Bundle, @NonNull android.os.Bundle, boolean);
method public int describeContents();
- method @NonNull public android.os.Bundle getData();
+ method @NonNull public android.os.Bundle getCandidateQueryData();
+ method @NonNull public android.os.Bundle getCredentialData();
method @NonNull public String getType();
+ method public boolean requireSystemProvider();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.credentials.CreateCredentialRequest> CREATOR;
}
@@ -13066,10 +13070,11 @@
}
public final class GetCredentialOption implements android.os.Parcelable {
- ctor public GetCredentialOption(@NonNull String, @NonNull android.os.Bundle);
+ ctor public GetCredentialOption(@NonNull String, @NonNull android.os.Bundle, boolean);
method public int describeContents();
method @NonNull public android.os.Bundle getData();
method @NonNull public String getType();
+ method public boolean requireSystemProvider();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.credentials.GetCredentialOption> CREATOR;
}
@@ -32697,7 +32702,6 @@
method @NonNull @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.QUERY_USERS", "android.permission.INTERACT_ACROSS_USERS"}, conditional=true) public android.content.pm.UserProperties getUserProperties(@NonNull android.os.UserHandle);
method public android.os.Bundle getUserRestrictions();
method @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.INTERACT_ACROSS_USERS"}, conditional=true) public android.os.Bundle getUserRestrictions(android.os.UserHandle);
- method @NonNull @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.INTERACT_ACROSS_USERS"}) public java.util.Set<android.os.UserHandle> getVisibleUsers();
method public boolean hasUserRestriction(String);
method public boolean isDemoUser();
method public static boolean isHeadlessSystemUserMode();
@@ -32711,7 +32715,6 @@
method @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.INTERACT_ACROSS_USERS"}, conditional=true) public boolean isUserRunningOrStopping(android.os.UserHandle);
method public boolean isUserUnlocked();
method @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.INTERACT_ACROSS_USERS"}, conditional=true) public boolean isUserUnlocked(android.os.UserHandle);
- method public boolean isUserVisible();
method @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.MODIFY_QUIET_MODE"}, conditional=true) public boolean requestQuietModeEnabled(boolean, @NonNull android.os.UserHandle);
method public boolean requestQuietModeEnabled(boolean, @NonNull android.os.UserHandle, int);
method @Deprecated public boolean setRestrictionsChallenge(String);
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index c47e3b2..13914c5 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -2998,13 +2998,13 @@
public static final class VirtualDeviceParams.Builder {
ctor public VirtualDeviceParams.Builder();
- method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder addDevicePolicy(int, int);
method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder addVirtualSensorConfig(@NonNull android.companion.virtual.sensor.VirtualSensorConfig);
method @NonNull public android.companion.virtual.VirtualDeviceParams build();
method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setAllowedActivities(@NonNull java.util.Set<android.content.ComponentName>);
method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setAllowedCrossTaskNavigations(@NonNull java.util.Set<android.content.ComponentName>);
method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setBlockedActivities(@NonNull java.util.Set<android.content.ComponentName>);
method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setBlockedCrossTaskNavigations(@NonNull java.util.Set<android.content.ComponentName>);
+ method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setDevicePolicy(int, int);
method @NonNull @RequiresPermission(value=android.Manifest.permission.ADD_ALWAYS_UNLOCKED_DISPLAY, conditional=true) public android.companion.virtual.VirtualDeviceParams.Builder setLockState(int);
method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setName(@NonNull String);
method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setUsersWithMatchingAccounts(@NonNull java.util.Set<android.os.UserHandle>);
@@ -10097,6 +10097,7 @@
method @NonNull public java.util.List<android.os.UserHandle> getAllProfiles();
method @NonNull public java.util.List<android.os.UserHandle> getEnabledProfiles();
method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public android.os.UserHandle getMainUser();
+ method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public android.os.UserHandle getPreviousForegroundUser();
method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}) public android.os.UserHandle getProfileParent(@NonNull android.os.UserHandle);
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public int getRemainingCreatableProfileCount(@NonNull String);
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public int getRemainingCreatableUserCount(@NonNull String);
@@ -10110,6 +10111,7 @@
method @Deprecated @android.os.UserManager.UserRestrictionSource @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.QUERY_USERS}) public int getUserRestrictionSource(String, android.os.UserHandle);
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.QUERY_USERS}) public java.util.List<android.os.UserManager.EnforcingUser> getUserRestrictionSources(String, android.os.UserHandle);
method @RequiresPermission(allOf={android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional=true) public int getUserSwitchability();
+ method @NonNull @RequiresPermission(anyOf={"android.permission.INTERACT_ACROSS_USERS", "android.permission.MANAGE_USERS"}) public java.util.Set<android.os.UserHandle> getVisibleUsers();
method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean hasRestrictedProfiles();
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional=true) public boolean hasUserRestrictionForUser(@NonNull String, @NonNull android.os.UserHandle);
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public boolean isAdminUser();
@@ -10127,6 +10129,7 @@
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS, android.Manifest.permission.GET_ACCOUNTS_PRIVILEGED}) public boolean isUserNameSet();
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public boolean isUserOfType(@NonNull String);
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional=true) public boolean isUserUnlockingOrUnlocked(@NonNull android.os.UserHandle);
+ method @RequiresPermission(anyOf={"android.permission.INTERACT_ACROSS_USERS", "android.permission.MANAGE_USERS"}) public boolean isUserVisible();
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean removeUser(@NonNull android.os.UserHandle);
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public int removeUserWhenPossible(@NonNull android.os.UserHandle, boolean);
method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public void setUserIcon(@NonNull android.graphics.Bitmap) throws android.os.UserManager.UserOperationException;
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 884870b..96ced41 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -502,6 +502,7 @@
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
static volatile Handler sMainThreadHandler; // set once in main()
+ private long mStartSeq; // Only accesssed from the main thread
Bundle mCoreSettings = null;
@@ -6809,6 +6810,14 @@
Application app;
final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskWrites();
final StrictMode.ThreadPolicy writesAllowedPolicy = StrictMode.getThreadPolicy();
+
+ final IActivityManager mgr = ActivityManager.getService();
+ try {
+ mgr.finishAttachApplication(mStartSeq);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+
try {
// If the app is being launched for full backup or restore, bring it up in
// a restricted environment with the base application class.
@@ -7649,6 +7658,8 @@
sCurrentActivityThread = this;
mConfigurationController = new ConfigurationController(this);
mSystemThread = system;
+ mStartSeq = startSeq;
+
if (!system) {
android.ddm.DdmHandleAppName.setAppName("<pre-initialized>",
UserHandle.myUserId());
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 902f172..3edaabd 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -147,6 +147,7 @@
oneway void finishReceiver(in IBinder who, int resultCode, in String resultData, in Bundle map,
boolean abortBroadcast, int flags);
void attachApplication(in IApplicationThread app, long startSeq);
+ void finishAttachApplication(long startSeq);
List<ActivityManager.RunningTaskInfo> getTasks(int maxNum);
@UnsupportedAppUsage
void moveTaskToFront(in IApplicationThread caller, in String callingPackage, int task,
@@ -718,8 +719,8 @@
/**
* Control the app freezer state. Returns true in case of success, false if the operation
- * didn't succeed (for example, when the app freezer isn't supported).
- * Handling the freezer state via this method is reentrant, that is it can be
+ * didn't succeed (for example, when the app freezer isn't supported).
+ * Handling the freezer state via this method is reentrant, that is it can be
* disabled and re-enabled multiple times in parallel. As long as there's a 1:1 disable to
* enable match, the freezer is re-enabled at last enable only.
* @param enable set it to true to enable the app freezer, false to disable it.
diff --git a/core/java/android/app/Service.java b/core/java/android/app/Service.java
index 6d7a161..3a7d483 100644
--- a/core/java/android/app/Service.java
+++ b/core/java/android/app/Service.java
@@ -1128,12 +1128,10 @@
/**
* Callback called on timeout for {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_SHORT_SERVICE}.
+ * See {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_SHORT_SERVICE} for more details.
*
- * TODO Implement it
- * TODO Javadoc
- *
- * @param startId
- * @hide
+ * @param startId the startId passed to {@link #onStartCommand(Intent, int, int)} when
+ * the service started.
*/
public void onTimeout(int startId) {
}
diff --git a/core/java/android/companion/virtual/VirtualDeviceParams.java b/core/java/android/companion/virtual/VirtualDeviceParams.java
index bad26c6..e2b5c5d 100644
--- a/core/java/android/companion/virtual/VirtualDeviceParams.java
+++ b/core/java/android/companion/virtual/VirtualDeviceParams.java
@@ -313,7 +313,7 @@
* Returns the policy specified for this policy type, or {@link #DEVICE_POLICY_DEFAULT} if no
* policy for this type has been explicitly specified.
*
- * @see Builder#addDevicePolicy
+ * @see Builder#setDevicePolicy
*/
public @DevicePolicy int getDevicePolicy(@PolicyType int policyType) {
return mDevicePolicies.get(policyType, DEVICE_POLICY_DEFAULT);
@@ -615,7 +615,7 @@
}
/**
- * Add a policy for this virtual device.
+ * Specifies a policy for this virtual device.
*
* Policies define the system behavior that may be specific for this virtual device. A
* policy can be defined for each {@code PolicyType}, but they are all optional.
@@ -624,7 +624,7 @@
* @param devicePolicy the value of the policy, i.e. how to interpret the device behavior.
*/
@NonNull
- public Builder addDevicePolicy(@PolicyType int policyType, @DevicePolicy int devicePolicy) {
+ public Builder setDevicePolicy(@PolicyType int policyType, @DevicePolicy int devicePolicy) {
mDevicePolicies.put(policyType, devicePolicy);
return this;
}
@@ -632,13 +632,13 @@
/**
* Adds a configuration for a sensor that should be created for this virtual device.
*
- * Device sensors must remain valid for the entire lifetime of the device, hence they are
+ * <p>Device sensors must remain valid for the entire lifetime of the device, hence they are
* created together with the device itself, and removed when the device is removed.
*
- * Requires {@link #DEVICE_POLICY_CUSTOM} to be set for {@link #POLICY_TYPE_SENSORS}.
+ * <p>Requires {@link #DEVICE_POLICY_CUSTOM} to be set for {@link #POLICY_TYPE_SENSORS}.
*
* @see android.companion.virtual.sensor.VirtualSensor
- * @see #addDevicePolicy
+ * @see #setDevicePolicy
*/
@NonNull
public Builder addVirtualSensorConfig(@NonNull VirtualSensorConfig virtualSensorConfig) {
diff --git a/core/java/android/content/pm/ServiceInfo.java b/core/java/android/content/pm/ServiceInfo.java
index 9e6cf62..7ea6733 100644
--- a/core/java/android/content/pm/ServiceInfo.java
+++ b/core/java/android/content/pm/ServiceInfo.java
@@ -366,24 +366,48 @@
public static final int FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED = 1 << 10;
/**
- * Foreground service type corresponding to {@code shortService} in
- * the {@link android.R.attr#foregroundServiceType} attribute.
+ * A foreground service type for "short-lived" services, which corresponds to
+ * {@code shortService} in the {@link android.R.attr#foregroundServiceType} attribute in the
+ * manifest.
*
- * TODO Implement it
+ * <p>Unlike other foreground service types, this type is not associated with a specific use
+ * case, and it will not require any special permissions
+ * (besides {@link Manifest.permission#FOREGROUND_SERVICE}).
*
- * TODO Expand the javadoc
+ * However, this type has the following restrictions.
*
- * This type is not associated with specific use cases unlike other types, but this has
- * unique restrictions.
* <ul>
- * <li>Has a timeout
- * <li>Cannot start other foreground services from this
* <li>
+ * The type has a 1 minute timeout.
+ * A foreground service of this type must be stopped within the timeout by
+ * {@link android.app.Service#stopSelf),
+ * or {@link android.content.Context#stopService).
+ * {@link android.app.Service#stopForeground) will also work, which will demote the
+ * service to a "background" service, which will soon be stopped by the system.
+ *
+ * <p>The system will <em>not</em> automatically stop it.
+ *
+ * <p>If the service isn't stopped within the timeout,
+ * {@link android.app.Service#onTimeout(int)} will be called.
+ * If the service is still not stopped after the callback,
+ * the app will be declared an ANR.
+ *
+ * <li>
+ * A foreground service of this type cannot be made "sticky"
+ * (see {@link android.app.Service#START_STICKY}). That is, if an app is killed
+ * due to a crash or out-of memory while it's running a short foregorund-service,
+ * the system will not restart the service.
+ * <li>
+ * Other foreground services cannot be started from short foreground services.
+ * Unlike other foreground service types, when an app is running in the background
+ * while only having a "short" foreground service, it's not allowed to start
+ * other foreground services, due to the restriction describe here:
+ * <a href="/guide/components/foreground-services#background-start-restrictions>
+ * Restrictions on background starts
+ * </a>
* </ul>
*
- * @see Service#onTimeout
- *
- * @hide
+ * @see android.app.Service#onTimeout(int)
*/
public static final int FOREGROUND_SERVICE_TYPE_SHORT_SERVICE = 1 << 11;
diff --git a/core/java/android/content/res/loader/ResourcesProvider.java b/core/java/android/content/res/loader/ResourcesProvider.java
index 463dcac..a5a1fa689 100644
--- a/core/java/android/content/res/loader/ResourcesProvider.java
+++ b/core/java/android/content/res/loader/ResourcesProvider.java
@@ -18,7 +18,10 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.content.Context;
+import android.content.om.OverlayInfo;
+import android.content.om.OverlayManager;
import android.content.pm.ApplicationInfo;
import android.content.res.ApkAssets;
import android.content.res.AssetFileDescriptor;
@@ -27,11 +30,17 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.content.om.OverlayManagerImpl;
import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.Preconditions;
import java.io.Closeable;
import java.io.File;
+import java.io.FileNotFoundException;
import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Objects;
/**
* Provides methods to load resources data from APKs ({@code .apk}) and resources tables
@@ -63,6 +72,48 @@
}
/**
+ * Creates a ResourcesProvider instance from the specified overlay information.
+ *
+ * <p>In order to enable the registered overlays, an application can create a {@link
+ * ResourcesProvider} instance according to the specified {@link OverlayInfo} instance and put
+ * them into a {@link ResourcesLoader} instance. The application calls {@link
+ * android.content.res.Resources#addLoaders(ResourcesLoader...)} to load the overlays.
+ *
+ * @param overlayInfo is the information about the specified overlay
+ * @return the resources provider instance for the {@code overlayInfo}
+ * @throws IOException when the files can't be loaded.
+ * @see OverlayManager#getOverlayInfosForTarget(String) to get the list of overlay info.
+ * @hide
+ */
+ @SuppressLint("WrongConstant") // TODO(b/238713267): ApkAssets blocks PROPERTY_LOADER
+ @NonNull
+ public static ResourcesProvider loadOverlay(@NonNull OverlayInfo overlayInfo)
+ throws IOException {
+ Objects.requireNonNull(overlayInfo);
+ Preconditions.checkArgument(overlayInfo.isFabricated(), "Not accepted overlay");
+ Preconditions.checkStringNotEmpty(
+ overlayInfo.getTargetOverlayableName(), "Without overlayable name");
+ final String overlayName =
+ OverlayManagerImpl.checkOverlayNameValid(overlayInfo.getOverlayName());
+ final String path =
+ Preconditions.checkStringNotEmpty(
+ overlayInfo.getBaseCodePath(), "Invalid base path");
+
+ final Path frroPath = Path.of(path);
+ if (!Files.isRegularFile(frroPath)) {
+ throw new FileNotFoundException("The frro file not found");
+ }
+ final Path idmapPath = frroPath.getParent().resolve(overlayName + ".idmap");
+ if (!Files.isRegularFile(idmapPath)) {
+ throw new FileNotFoundException("The idmap file not found");
+ }
+
+ return new ResourcesProvider(
+ ApkAssets.loadOverlayFromPath(
+ idmapPath.toString(), 0 /* flags: self targeting overlay */));
+ }
+
+ /**
* Creates a ResourcesProvider from an APK ({@code .apk}) file descriptor.
*
* <p>The file descriptor is duplicated and the original may be closed by the application at any
diff --git a/core/java/android/credentials/CreateCredentialRequest.java b/core/java/android/credentials/CreateCredentialRequest.java
index 22ef230..4589039 100644
--- a/core/java/android/credentials/CreateCredentialRequest.java
+++ b/core/java/android/credentials/CreateCredentialRequest.java
@@ -39,10 +39,17 @@
private final String mType;
/**
- * The request data.
+ * The full credential creation request data.
*/
@NonNull
- private final Bundle mData;
+ private final Bundle mCredentialData;
+
+ /**
+ * The partial request data that will be sent to the provider during the initial creation
+ * candidate query stage.
+ */
+ @NonNull
+ private final Bundle mCandidateQueryData;
/**
* Determines whether or not the request must only be fulfilled by a system provider.
@@ -58,18 +65,39 @@
}
/**
- * Returns the request data.
+ * Returns the full credential creation request data.
+ *
+ * For security reason, a provider will receive the request data in two stages. First it gets
+ * a partial request, {@link #getCandidateQueryData()} that do not contain sensitive user
+ * information; it uses this information to provide credential creation candidates that the
+ * [@code CredentialManager] will show to the user. Next, this full request data will be sent to
+ * a provider only if the user further grants the consent by choosing a candidate from the
+ * provider.
*/
@NonNull
- public Bundle getData() {
- return mData;
+ public Bundle getCredentialData() {
+ return mCredentialData;
+ }
+
+ /**
+ * Returns the partial request data that will be sent to the provider during the initial
+ * creation candidate query stage.
+ *
+ * For security reason, a provider will receive the request data in two stages. First it gets
+ * this partial request that do not contain sensitive user information; it uses this information
+ * to provide credential creation candidates that the [@code CredentialManager] will show to
+ * the user. Next, the full request data, {@link #getCredentialData()}, will be sent to a
+ * provider only if the user further grants the consent by choosing a candidate from the
+ * provider.
+ */
+ @NonNull
+ public Bundle getCandidateQueryData() {
+ return mCandidateQueryData;
}
/**
* Returns true if the request must only be fulfilled by a system provider, and false
* otherwise.
- *
- * @hide
*/
public boolean requireSystemProvider() {
return mRequireSystemProvider;
@@ -78,7 +106,8 @@
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeString8(mType);
- dest.writeBundle(mData);
+ dest.writeBundle(mCredentialData);
+ dest.writeBundle(mCandidateQueryData);
dest.writeBoolean(mRequireSystemProvider);
}
@@ -91,7 +120,8 @@
public String toString() {
return "CreateCredentialRequest {"
+ "type=" + mType
- + ", data=" + mData
+ + ", credentialData=" + mCredentialData
+ + ", candidateQueryData=" + mCandidateQueryData
+ ", requireSystemProvider=" + mRequireSystemProvider
+ "}";
}
@@ -100,44 +130,37 @@
* Constructs a {@link CreateCredentialRequest}.
*
* @param type the requested credential type
- * @param data the request data
- *
- * @throws IllegalArgumentException If type is empty
- */
- public CreateCredentialRequest(@NonNull String type, @NonNull Bundle data) {
- this(type, data, /*requireSystemProvider=*/ false);
- }
-
- /**
- * Constructs a {@link CreateCredentialRequest}.
- *
- * @param type the requested credential type
- * @param data the request data
- * @param requireSystemProvider whether or not the request must only be fulfilled by a system
- * provider
+ * @param credentialData the full credential creation request data
+ * @param candidateQueryData the partial request data that will be sent to the provider
+ * during the initial creation candidate query stage
+ * @param requireSystemProvider whether the request must only be fulfilled by a system provider
*
* @throws IllegalArgumentException If type is empty.
- *
- * @hide
*/
public CreateCredentialRequest(
@NonNull String type,
- @NonNull Bundle data,
+ @NonNull Bundle credentialData,
+ @NonNull Bundle candidateQueryData,
boolean requireSystemProvider) {
mType = Preconditions.checkStringNotEmpty(type, "type must not be empty");
- mData = requireNonNull(data, "data must not be null");
+ mCredentialData = requireNonNull(credentialData, "credentialData must not be null");
+ mCandidateQueryData = requireNonNull(candidateQueryData,
+ "candidateQueryData must not be null");
mRequireSystemProvider = requireSystemProvider;
}
private CreateCredentialRequest(@NonNull Parcel in) {
String type = in.readString8();
- Bundle data = in.readBundle();
+ Bundle credentialData = in.readBundle();
+ Bundle candidateQueryData = in.readBundle();
boolean requireSystemProvider = in.readBoolean();
mType = type;
AnnotationValidations.validate(NonNull.class, null, mType);
- mData = data;
- AnnotationValidations.validate(NonNull.class, null, mData);
+ mCredentialData = credentialData;
+ AnnotationValidations.validate(NonNull.class, null, mCredentialData);
+ mCandidateQueryData = candidateQueryData;
+ AnnotationValidations.validate(NonNull.class, null, mCandidateQueryData);
mRequireSystemProvider = requireSystemProvider;
}
diff --git a/core/java/android/credentials/GetCredentialOption.java b/core/java/android/credentials/GetCredentialOption.java
index a0d3c0b..ed93dae 100644
--- a/core/java/android/credentials/GetCredentialOption.java
+++ b/core/java/android/credentials/GetCredentialOption.java
@@ -67,8 +67,6 @@
/**
* Returns true if the request must only be fulfilled by a system provider, and false
* otherwise.
- *
- * @hide
*/
public boolean requireSystemProvider() {
return mRequireSystemProvider;
@@ -100,24 +98,10 @@
*
* @param type the requested credential type
* @param data the request data
- *
- * @throws IllegalArgumentException If type is empty
- */
- public GetCredentialOption(@NonNull String type, @NonNull Bundle data) {
- this(type, data, /*requireSystemProvider=*/ false);
- }
-
- /**
- * Constructs a {@link GetCredentialOption}.
- *
- * @param type the requested credential type
- * @param data the request data
* @param requireSystemProvider whether or not the request must only be fulfilled by a system
* provider
*
* @throws IllegalArgumentException If type is empty.
- *
- * @hide
*/
public GetCredentialOption(
@NonNull String type,
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index eae7ce0..d31540a 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -59,6 +59,7 @@
ParcelFileDescriptor getUserIcon(int userId);
UserInfo getPrimaryUser();
int getMainUserId();
+ int getPreviousFullUserToEnterForeground();
List<UserInfo> getUsers(boolean excludePartial, boolean excludeDying, boolean excludePreCreated);
List<UserInfo> getProfiles(int userId, boolean enabledOnly);
int[] getProfileIds(int userId, boolean enabledOnly);
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 954d1fc..dd02e02 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -2976,8 +2976,15 @@
* </ol>
*
* @return whether the user is visible at the moment, as defined above.
+ *
+ * @hide
*/
+ @SystemApi
@UserHandleAware
+ @RequiresPermission(anyOf = {
+ "android.permission.INTERACT_ACROSS_USERS",
+ "android.permission.MANAGE_USERS"
+ })
public boolean isUserVisible() {
try {
return mService.isUserVisible(mUserId);
@@ -2990,9 +2997,14 @@
* Gets the visible users (as defined by {@link #isUserVisible()}.
*
* @return visible users at the moment.
+ *
+ * @hide
*/
- @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
- Manifest.permission.INTERACT_ACROSS_USERS})
+ @SystemApi
+ @RequiresPermission(anyOf = {
+ "android.permission.INTERACT_ACROSS_USERS",
+ "android.permission.MANAGE_USERS"
+ })
public @NonNull Set<UserHandle> getVisibleUsers() {
ArraySet<UserHandle> result = new ArraySet<>();
try {
@@ -4300,6 +4312,43 @@
}
/**
+ * Returns the user who was last in the foreground, not including the current user and not
+ * including profiles.
+ *
+ * <p>Returns {@code null} if there is no previous user, for example if there
+ * is only one full user (i.e. only one user which is not a profile) on the device.
+ *
+ * <p>This method may be used for example to find the user to switch back to if the
+ * current user is removed, or if creating a new user is aborted.
+ *
+ * <p>Note that reboots do not interrupt this calculation; the previous user need not have
+ * used the device since it rebooted.
+ *
+ * <p>Note also that on devices that support multiple users on multiple displays, it is possible
+ * that the returned user will be visible on a secondary display, as the foreground user is the
+ * one associated with the main display.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.CREATE_USERS,
+ android.Manifest.permission.QUERY_USERS
+ })
+ public @Nullable UserHandle getPreviousForegroundUser() {
+ try {
+ final int previousUser = mService.getPreviousFullUserToEnterForeground();
+ if (previousUser == UserHandle.USER_NULL) {
+ return null;
+ }
+ return UserHandle.of(previousUser);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Checks whether it's possible to add more users.
*
* @return true if more users can be added, false if limit has been reached.
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 608cbda..4277d01 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -140,6 +140,13 @@
public static final String SETTINGS_ADB_METRICS_WRITER = "settings_adb_metrics_writer";
/**
+ * Flag to show stylus-specific preferences in Connected Devices
+ * @hide
+ */
+ public static final String SETTINGS_SHOW_STYLUS_PREFERENCES =
+ "settings_show_stylus_preferences";
+
+ /**
* Flag to enable/disable biometrics enrollment v2
* @hide
*/
@@ -181,10 +188,12 @@
DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_TRACKPAD_GESTURE, "false");
DEFAULT_FLAGS.put(SETTINGS_ENABLE_SPA, "false");
DEFAULT_FLAGS.put(SETTINGS_ADB_METRICS_WRITER, "false");
+ DEFAULT_FLAGS.put(SETTINGS_SHOW_STYLUS_PREFERENCES, "false");
DEFAULT_FLAGS.put(SETTINGS_BIOMETRICS2_ENROLLMENT, "false");
}
private static final Set<String> PERSISTENT_FLAGS;
+
static {
PERSISTENT_FLAGS = new HashSet<>();
PERSISTENT_FLAGS.add(SETTINGS_ALLOW_INTENT_REDIRECTION_FOR_CLONE_PROFILE);
diff --git a/core/proto/android/server/activitymanagerservice.proto b/core/proto/android/server/activitymanagerservice.proto
index 4650000..7393c6f 100644
--- a/core/proto/android/server/activitymanagerservice.proto
+++ b/core/proto/android/server/activitymanagerservice.proto
@@ -977,12 +977,11 @@
optional int32 profile = 2;
}
repeated UserProfile user_profile_group_ids = 4;
- repeated int32 visible_users_array = 5;
// current_user contains the id of the current user, while current_profiles contains the ids of
// both the current user and its profiles (if any)
- optional int32 current_user = 6;
- repeated int32 current_profiles = 7;
+ optional int32 current_user = 5;
+ repeated int32 current_profiles = 6;
}
// sync with com.android.server.am.AppTimeTracker.java
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 6460007..eb70344 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -1705,8 +1705,7 @@
-->
<flag name="systemExempted" value="0x400" />
<!-- "Short service" foreground service type. See
- TODO: Change it to a real link
- {@code android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_SHORT_SERVICE}.
+ {@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_SHORT_SERVICE}.
for more details.
-->
<flag name="shortService" value="0x800" />
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index c0a4fdf..88cfed9 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -589,6 +589,7 @@
"ProfileData.cpp",
"ProfileDataContainer.cpp",
"Readback.cpp",
+ "Tonemapper.cpp",
"TreeInfo.cpp",
"WebViewFunctorManager.cpp",
"protos/graphicsstats.proto",
diff --git a/libs/hwui/Readback.cpp b/libs/hwui/Readback.cpp
index 02c2e67..8dcd6db 100644
--- a/libs/hwui/Readback.cpp
+++ b/libs/hwui/Readback.cpp
@@ -16,16 +16,6 @@
#include "Readback.h"
-#include <sync/sync.h>
-#include <system/window.h>
-
-#include <gui/TraceUtils.h>
-#include "DeferredLayerUpdater.h"
-#include "Properties.h"
-#include "hwui/Bitmap.h"
-#include "pipeline/skia/LayerDrawable.h"
-#include "renderthread/EglManager.h"
-#include "renderthread/VulkanManager.h"
#include <SkBitmap.h>
#include <SkBlendMode.h>
#include <SkCanvas.h>
@@ -38,6 +28,19 @@
#include <SkRefCnt.h>
#include <SkSamplingOptions.h>
#include <SkSurface.h>
+#include <gui/TraceUtils.h>
+#include <private/android/AHardwareBufferHelpers.h>
+#include <shaders/shaders.h>
+#include <sync/sync.h>
+#include <system/window.h>
+
+#include "DeferredLayerUpdater.h"
+#include "Properties.h"
+#include "Tonemapper.h"
+#include "hwui/Bitmap.h"
+#include "pipeline/skia/LayerDrawable.h"
+#include "renderthread/EglManager.h"
+#include "renderthread/VulkanManager.h"
#include "utils/Color.h"
#include "utils/MathUtils.h"
#include "utils/NdkUtils.h"
@@ -91,8 +94,18 @@
}
}
- sk_sp<SkColorSpace> colorSpace = DataSpaceToColorSpace(
- static_cast<android_dataspace>(ANativeWindow_getBuffersDataSpace(window)));
+ int32_t dataspace = ANativeWindow_getBuffersDataSpace(window);
+
+ // If the application is not updating the Surface themselves, e.g., another
+ // process is producing buffers for the application to display, then
+ // ANativeWindow_getBuffersDataSpace will return an unknown answer, so grab
+ // the dataspace from buffer metadata instead, if it exists.
+ if (dataspace == 0) {
+ dataspace = AHardwareBuffer_getDataSpace(sourceBuffer.get());
+ }
+
+ sk_sp<SkColorSpace> colorSpace =
+ DataSpaceToColorSpace(static_cast<android_dataspace>(dataspace));
sk_sp<SkImage> image =
SkImage::MakeFromAHardwareBuffer(sourceBuffer.get(), kPremul_SkAlphaType, colorSpace);
@@ -227,6 +240,10 @@
const bool hasBufferCrop = cropRect.left < cropRect.right && cropRect.top < cropRect.bottom;
auto constraint =
hasBufferCrop ? SkCanvas::kStrict_SrcRectConstraint : SkCanvas::kFast_SrcRectConstraint;
+
+ static constexpr float kMaxLuminanceNits = 4000.f;
+ tonemapPaint(image->imageInfo(), canvas->imageInfo(), kMaxLuminanceNits, paint);
+
canvas->drawImageRect(image, imageSrcRect, imageDstRect, sampling, &paint, constraint);
canvas->restore();
diff --git a/libs/hwui/SkiaInterpolator.cpp b/libs/hwui/SkiaInterpolator.cpp
index 0695dd1..153c3b6 100644
--- a/libs/hwui/SkiaInterpolator.cpp
+++ b/libs/hwui/SkiaInterpolator.cpp
@@ -17,6 +17,8 @@
#include "SkiaInterpolator.h"
#include "include/core/SkMath.h"
+#include "include/core/SkScalar.h"
+#include "include/core/SkTypes.h"
#include "include/private/SkFixed.h"
#include "include/private/SkMalloc.h"
#include "include/private/SkTo.h"
diff --git a/libs/hwui/Tonemapper.cpp b/libs/hwui/Tonemapper.cpp
new file mode 100644
index 0000000..a7e76b6
--- /dev/null
+++ b/libs/hwui/Tonemapper.cpp
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2022 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.
+ */
+
+#include "Tonemapper.h"
+
+#include <SkRuntimeEffect.h>
+#include <log/log.h>
+#include <shaders/shaders.h>
+
+#include "utils/Color.h"
+
+namespace android::uirenderer {
+
+namespace {
+
+class ColorFilterRuntimeEffectBuilder : public SkRuntimeEffectBuilder {
+public:
+ explicit ColorFilterRuntimeEffectBuilder(sk_sp<SkRuntimeEffect> effect)
+ : SkRuntimeEffectBuilder(std::move(effect)) {}
+
+ sk_sp<SkColorFilter> makeColorFilter() {
+ return this->effect()->makeColorFilter(this->uniforms());
+ }
+};
+
+static sk_sp<SkColorFilter> createLinearEffectColorFilter(const shaders::LinearEffect& linearEffect,
+ float maxDisplayLuminance,
+ float currentDisplayLuminanceNits,
+ float maxLuminance) {
+ auto shaderString = SkString(shaders::buildLinearEffectSkSL(linearEffect));
+ auto [runtimeEffect, error] = SkRuntimeEffect::MakeForColorFilter(std::move(shaderString));
+ if (!runtimeEffect) {
+ LOG_ALWAYS_FATAL("LinearColorFilter construction error: %s", error.c_str());
+ }
+
+ ColorFilterRuntimeEffectBuilder effectBuilder(std::move(runtimeEffect));
+
+ const auto uniforms =
+ shaders::buildLinearEffectUniforms(linearEffect, android::mat4(), maxDisplayLuminance,
+ currentDisplayLuminanceNits, maxLuminance);
+
+ for (const auto& uniform : uniforms) {
+ effectBuilder.uniform(uniform.name.c_str()).set(uniform.value.data(), uniform.value.size());
+ }
+
+ return effectBuilder.makeColorFilter();
+}
+
+static bool extractTransfer(ui::Dataspace dataspace) {
+ return dataspace & HAL_DATASPACE_TRANSFER_MASK;
+}
+
+static bool isHdrDataspace(ui::Dataspace dataspace) {
+ const auto transfer = extractTransfer(dataspace);
+
+ return transfer == HAL_DATASPACE_TRANSFER_ST2084 || transfer == HAL_DATASPACE_TRANSFER_HLG;
+}
+
+static ui::Dataspace getDataspace(const SkImageInfo& image) {
+ return static_cast<ui::Dataspace>(
+ ColorSpaceToADataSpace(image.colorSpace(), image.colorType()));
+}
+
+} // namespace
+
+// Given a source and destination image info, and the max content luminance, generate a tonemaping
+// shader and tag it on the supplied paint.
+void tonemapPaint(const SkImageInfo& source, const SkImageInfo& destination, float maxLuminanceNits,
+ SkPaint& paint) {
+ const auto sourceDataspace = getDataspace(source);
+ const auto destinationDataspace = getDataspace(destination);
+
+ if (extractTransfer(sourceDataspace) != extractTransfer(destinationDataspace) &&
+ (isHdrDataspace(sourceDataspace) || isHdrDataspace(destinationDataspace))) {
+ const auto effect = shaders::LinearEffect{
+ .inputDataspace = sourceDataspace,
+ .outputDataspace = destinationDataspace,
+ .undoPremultipliedAlpha = source.alphaType() == kPremul_SkAlphaType,
+ .fakeInputDataspace = destinationDataspace,
+ .type = shaders::LinearEffect::SkSLType::ColorFilter};
+ constexpr float kMaxDisplayBrightnessNits = 1000.f;
+ constexpr float kCurrentDisplayBrightnessNits = 500.f;
+ sk_sp<SkColorFilter> colorFilter = createLinearEffectColorFilter(
+ effect, kMaxDisplayBrightnessNits, kCurrentDisplayBrightnessNits, maxLuminanceNits);
+
+ if (paint.getColorFilter()) {
+ paint.setColorFilter(SkColorFilters::Compose(paint.refColorFilter(), colorFilter));
+ } else {
+ paint.setColorFilter(colorFilter);
+ }
+ }
+}
+
+} // namespace android::uirenderer
diff --git a/libs/hwui/Tonemapper.h b/libs/hwui/Tonemapper.h
new file mode 100644
index 0000000..c0d5325
--- /dev/null
+++ b/libs/hwui/Tonemapper.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2022 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.
+ */
+
+#pragma once
+
+#include <SkCanvas.h>
+
+namespace android::uirenderer {
+
+// Given a source and destination image info, and the max content luminance, generate a tonemaping
+// shader and tag it on the supplied paint.
+void tonemapPaint(const SkImageInfo& source, const SkImageInfo& destination, float maxLuminanceNits,
+ SkPaint& paint);
+
+} // namespace android::uirenderer
diff --git a/libs/hwui/pipeline/skia/LayerDrawable.cpp b/libs/hwui/pipeline/skia/LayerDrawable.cpp
index 3ba5409..99f54c1 100644
--- a/libs/hwui/pipeline/skia/LayerDrawable.cpp
+++ b/libs/hwui/pipeline/skia/LayerDrawable.cpp
@@ -25,6 +25,7 @@
#include "SkColorFilter.h"
#include "SkRuntimeEffect.h"
#include "SkSurface.h"
+#include "Tonemapper.h"
#include "gl/GrGLTypes.h"
#include "math/mat4.h"
#include "system/graphics-base-v1.0.h"
@@ -76,37 +77,6 @@
isIntegerAligned(dstDevRect.y()));
}
-static sk_sp<SkShader> createLinearEffectShader(sk_sp<SkShader> shader,
- const shaders::LinearEffect& linearEffect,
- float maxDisplayLuminance,
- float currentDisplayLuminanceNits,
- float maxLuminance) {
- auto shaderString = SkString(shaders::buildLinearEffectSkSL(linearEffect));
- auto [runtimeEffect, error] = SkRuntimeEffect::MakeForShader(std::move(shaderString));
- if (!runtimeEffect) {
- LOG_ALWAYS_FATAL("LinearColorFilter construction error: %s", error.c_str());
- }
-
- SkRuntimeShaderBuilder effectBuilder(std::move(runtimeEffect));
-
- effectBuilder.child("child") = std::move(shader);
-
- const auto uniforms = shaders::buildLinearEffectUniforms(
- linearEffect, mat4(), maxDisplayLuminance, currentDisplayLuminanceNits, maxLuminance);
-
- for (const auto& uniform : uniforms) {
- effectBuilder.uniform(uniform.name.c_str()).set(uniform.value.data(), uniform.value.size());
- }
-
- return effectBuilder.makeShader();
-}
-
-static bool isHdrDataspace(ui::Dataspace dataspace) {
- const auto transfer = dataspace & HAL_DATASPACE_TRANSFER_MASK;
-
- return transfer == HAL_DATASPACE_TRANSFER_ST2084 || transfer == HAL_DATASPACE_TRANSFER_HLG;
-}
-
static void adjustCropForYUV(uint32_t format, int bufferWidth, int bufferHeight, SkRect* cropRect) {
// Chroma channels of YUV420 images are subsampled we may need to shrink the crop region by
// a whole texel on each side. Since skia still adds its own 0.5 inset, we apply an
@@ -215,31 +185,10 @@
sampling = SkSamplingOptions(SkFilterMode::kLinear);
}
- const auto sourceDataspace = static_cast<ui::Dataspace>(
- ColorSpaceToADataSpace(layerImage->colorSpace(), layerImage->colorType()));
- const SkImageInfo& imageInfo = canvas->imageInfo();
- const auto destinationDataspace = static_cast<ui::Dataspace>(
- ColorSpaceToADataSpace(imageInfo.colorSpace(), imageInfo.colorType()));
-
- if (isHdrDataspace(sourceDataspace) || isHdrDataspace(destinationDataspace)) {
- const auto effect = shaders::LinearEffect{
- .inputDataspace = sourceDataspace,
- .outputDataspace = destinationDataspace,
- .undoPremultipliedAlpha = layerImage->alphaType() == kPremul_SkAlphaType,
- .fakeInputDataspace = destinationDataspace};
- auto shader = layerImage->makeShader(sampling,
- SkMatrix::RectToRect(skiaSrcRect, skiaDestRect));
- constexpr float kMaxDisplayBrightess = 1000.f;
- constexpr float kCurrentDisplayBrightness = 500.f;
- shader = createLinearEffectShader(std::move(shader), effect, kMaxDisplayBrightess,
- kCurrentDisplayBrightness,
- layer->getMaxLuminanceNits());
- paint.setShader(shader);
- canvas->drawRect(skiaDestRect, paint);
- } else {
- canvas->drawImageRect(layerImage.get(), skiaSrcRect, skiaDestRect, sampling, &paint,
- constraint);
- }
+ tonemapPaint(layerImage->imageInfo(), canvas->imageInfo(), layer->getMaxLuminanceNits(),
+ paint);
+ canvas->drawImageRect(layerImage.get(), skiaSrcRect, skiaDestRect, sampling, &paint,
+ constraint);
canvas->restore();
// restore the original matrix
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
index 0cc1194..e3ed3d9 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
@@ -59,7 +59,7 @@
) {
val requestInfo: RequestInfo
private val providerEnabledList: List<ProviderData>
- private val providerDisabledList: List<DisabledProviderData>
+ private val providerDisabledList: List<DisabledProviderData>?
// TODO: require non-null.
val resultReceiver: ResultReceiver?
@@ -143,7 +143,7 @@
providerEnabledList as List<CreateCredentialProviderData>, requestDisplayInfo, context)
val providerDisabledList = CreateFlowUtils.toDisabledProviderList(
// Handle runtime cast error
- providerDisabledList as List<DisabledProviderData>, context)
+ providerDisabledList, context)
var defaultProvider: EnabledProviderInfo? = null
var remoteEntry: RemoteInfo? = null
providerEnabledList.forEach{providerInfo -> providerInfo.createOptions =
@@ -209,7 +209,7 @@
)
}
- private fun testDisabledProviderList(): List<DisabledProviderData> {
+ private fun testDisabledProviderList(): List<DisabledProviderData>? {
return listOf(
DisabledProviderData("com.lastpass.lpandroid"),
DisabledProviderData("com.google.android.youtube")
@@ -458,12 +458,15 @@
" \"residentKey\": \"required\",\n" +
" \"requireResidentKey\": true\n" +
" }}")
- val data = request.data
+ val credentialData = request.data
return RequestInfo.newCreateRequestInfo(
Binder(),
CreateCredentialRequest(
TYPE_PUBLIC_KEY_CREDENTIAL,
- data
+ credentialData,
+ // TODO: populate with actual data
+ /*candidateQueryData=*/ Bundle(),
+ /*requireSystemProvider=*/ false
),
/*isFirstUsage=*/false,
"tribank"
@@ -476,7 +479,10 @@
Binder(),
CreateCredentialRequest(
TYPE_PASSWORD_CREDENTIAL,
- data
+ data,
+ // TODO: populate with actual data
+ /*candidateQueryData=*/ Bundle(),
+ /*requireSystemProvider=*/ false
),
/*isFirstUsage=*/false,
"tribank"
@@ -489,7 +495,9 @@
Binder(),
CreateCredentialRequest(
"other-sign-ins",
- data
+ data,
+ /*candidateQueryData=*/ Bundle(),
+ /*requireSystemProvider=*/ false
),
/*isFirstUsage=*/false,
"tribank"
@@ -501,7 +509,8 @@
Binder(),
GetCredentialRequest.Builder()
.addGetCredentialOption(
- GetCredentialOption(TYPE_PUBLIC_KEY_CREDENTIAL, Bundle())
+ GetCredentialOption(
+ TYPE_PUBLIC_KEY_CREDENTIAL, Bundle(), /*requireSystemProvider=*/ false)
)
.build(),
/*isFirstUsage=*/false,
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
index b96f686..357c55d 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
@@ -209,12 +209,12 @@
}
fun toDisabledProviderList(
- providerDataList: List<DisabledProviderData>,
+ providerDataList: List<DisabledProviderData>?,
context: Context,
- ): List<com.android.credentialmanager.createflow.DisabledProviderInfo> {
+ ): List<com.android.credentialmanager.createflow.DisabledProviderInfo>? {
// TODO: get from the actual service info
val packageManager = context.packageManager
- return providerDataList.map {
+ return providerDataList?.map {
val pkgInfo = packageManager
.getPackageInfo(it.providerFlattenedComponentName,
PackageManager.PackageInfoFlags.of(0))
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreateCredentialRequest.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreateCredentialRequest.kt
index 7e7dbde..008e1b6 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreateCredentialRequest.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreateCredentialRequest.kt
@@ -38,14 +38,18 @@
return try {
when (from.type) {
Credential.TYPE_PASSWORD_CREDENTIAL ->
- CreatePasswordRequest.createFrom(from.data)
+ CreatePasswordRequest.createFrom(from.credentialData)
PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL ->
- CreatePublicKeyCredentialBaseRequest.createFrom(from.data)
+ CreatePublicKeyCredentialBaseRequest.createFrom(from.credentialData)
else ->
- CreateCredentialRequest(from.type, from.data, from.requireSystemProvider())
+ CreateCredentialRequest(
+ from.type, from.credentialData, from.requireSystemProvider()
+ )
}
} catch (e: FrameworkClassParsingException) {
- CreateCredentialRequest(from.type, from.data, from.requireSystemProvider())
+ CreateCredentialRequest(
+ from.type, from.credentialData, from.requireSystemProvider()
+ )
}
}
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaLogger.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaLogger.kt
index 00a0362..6ecb7fa 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaLogger.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaLogger.kt
@@ -16,6 +16,7 @@
package com.android.settingslib.spa.framework.common
+import android.os.Bundle
import android.util.Log
// Defines the category of the log, for quick filter
@@ -38,10 +39,13 @@
// Entry related events.
ENTRY_CLICK,
- ENTRY_SWITCH_ON,
- ENTRY_SWITCH_OFF,
+ ENTRY_SWITCH,
}
+internal const val LOG_DATA_DISPLAY_NAME = "name"
+internal const val LOG_DATA_SESSION_NAME = "session"
+internal const val LOG_DATA_SWITCH_STATUS = "switch"
+
/**
* The interface of logger in Spa
*/
@@ -54,7 +58,7 @@
id: String,
event: LogEvent,
category: LogCategory = LogCategory.DEFAULT,
- details: String? = null
+ extraData: Bundle = Bundle.EMPTY
) {
}
}
@@ -64,8 +68,8 @@
Log.d("SpaMsg-$category", "[$tag] $msg")
}
- override fun event(id: String, event: LogEvent, category: LogCategory, details: String?) {
- val extraMsg = if (details == null) "" else " ($details)"
- Log.d("SpaEvent-$category", "[$id] $event$extraMsg")
+ override fun event(id: String, event: LogEvent, category: LogCategory, extraData: Bundle) {
+ val extraMsg = extraData.toString().removeRange(0, 6)
+ Log.d("SpaEvent-$category", "[$id] $event $extraMsg")
}
}
\ No newline at end of file
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/EntryLogger.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/EntryLogger.kt
index 8d0a35c..1c88187 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/EntryLogger.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/EntryLogger.kt
@@ -16,17 +16,22 @@
package com.android.settingslib.spa.framework.util
+import android.os.Bundle
import androidx.compose.runtime.Composable
+import androidx.core.os.bundleOf
+import com.android.settingslib.spa.framework.common.LOG_DATA_SWITCH_STATUS
import com.android.settingslib.spa.framework.common.LocalEntryDataProvider
import com.android.settingslib.spa.framework.common.LogCategory
import com.android.settingslib.spa.framework.common.LogEvent
import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
@Composable
-fun logEntryEvent(): (event: LogEvent) -> Unit {
- val entryId = LocalEntryDataProvider.current.entryId ?: return {}
- return {
- SpaEnvironmentFactory.instance.logger.event(entryId, it, category = LogCategory.VIEW)
+fun logEntryEvent(): (event: LogEvent, extraData: Bundle) -> Unit {
+ val entryId = LocalEntryDataProvider.current.entryId ?: return { _, _ -> }
+ return { event, extraData ->
+ SpaEnvironmentFactory.instance.logger.event(
+ entryId, event, category = LogCategory.VIEW, extraData = extraData
+ )
}
}
@@ -35,7 +40,7 @@
if (onClick == null) return null
val logEvent = logEntryEvent()
return {
- logEvent(LogEvent.ENTRY_CLICK)
+ logEvent(LogEvent.ENTRY_CLICK, Bundle.EMPTY)
onClick()
}
}
@@ -45,8 +50,7 @@
if (onSwitch == null) return null
val logEvent = logEntryEvent()
return {
- val event = if (it) LogEvent.ENTRY_SWITCH_ON else LogEvent.ENTRY_SWITCH_OFF
- logEvent(event)
+ logEvent(LogEvent.ENTRY_SWITCH, bundleOf(LOG_DATA_SWITCH_STATUS to it))
onSwitch(it)
}
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Flows.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Flows.kt
index d801840..97e3ac2 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Flows.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Flows.kt
@@ -27,6 +27,13 @@
import kotlinx.coroutines.flow.map
/**
+ * Returns a [Flow] whose values are a list which containing the results of applying the given
+ * [transform] function to each element in the original flow's list.
+ */
+inline fun <T, R> Flow<List<T>>.mapItem(crossinline transform: (T) -> R): Flow<List<R>> =
+ map { list -> list.map(transform) }
+
+/**
* Returns a [Flow] whose values are a list which containing the results of asynchronously applying
* the given [transform] function to each element in the original flow's list.
*/
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/PageLogger.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/PageLogger.kt
index b9e4b78..a881254 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/PageLogger.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/PageLogger.kt
@@ -21,8 +21,11 @@
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalLifecycleOwner
+import androidx.core.os.bundleOf
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
+import com.android.settingslib.spa.framework.common.LOG_DATA_DISPLAY_NAME
+import com.android.settingslib.spa.framework.common.LOG_DATA_SESSION_NAME
import com.android.settingslib.spa.framework.common.LogCategory
import com.android.settingslib.spa.framework.common.LogEvent
import com.android.settingslib.spa.framework.common.SettingsPageProvider
@@ -37,21 +40,21 @@
val navController = LocalNavController.current
DisposableEffect(lifecycleOwner) {
val observer = LifecycleEventObserver { _, event ->
- val spaLogger = SpaEnvironmentFactory.instance.logger
+ val logPageEvent: (event: LogEvent) -> Unit = {
+ SpaEnvironmentFactory.instance.logger.event(
+ id = page.id,
+ event = it,
+ category = LogCategory.FRAMEWORK,
+ extraData = bundleOf(
+ LOG_DATA_DISPLAY_NAME to page.displayName,
+ LOG_DATA_SESSION_NAME to navController.sessionSourceName,
+ )
+ )
+ }
if (event == Lifecycle.Event.ON_START) {
- spaLogger.event(
- page.id,
- LogEvent.PAGE_ENTER,
- category = LogCategory.FRAMEWORK,
- details = navController.sessionSourceName ?: page.displayName,
- )
+ logPageEvent(LogEvent.PAGE_ENTER)
} else if (event == Lifecycle.Event.ON_STOP) {
- spaLogger.event(
- page.id,
- LogEvent.PAGE_LEAVE,
- category = LogCategory.FRAMEWORK,
- details = navController.sessionSourceName ?: page.displayName,
- )
+ logPageEvent(LogEvent.PAGE_LEAVE)
}
}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SettingsSliceDataRepositoryTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SettingsSliceDataRepositoryTest.kt
index 90e25f9..1bdba29 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SettingsSliceDataRepositoryTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SettingsSliceDataRepositoryTest.kt
@@ -18,12 +18,12 @@
import android.content.Context
import android.net.Uri
+import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import androidx.lifecycle.Observer
import androidx.slice.Slice
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settingslib.spa.framework.common.createSettingsPage
-import com.android.settingslib.spa.testutils.InstantTaskExecutorRule
import com.android.settingslib.spa.tests.testutils.SpaEnvironmentForTest
import com.android.settingslib.spa.tests.testutils.SppHome
import com.android.settingslib.spa.tests.testutils.SppLayer2
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/SpaEnvironmentForTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/SpaEnvironmentForTest.kt
index 6385954..f38bd08 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/SpaEnvironmentForTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/SpaEnvironmentForTest.kt
@@ -51,7 +51,7 @@
messageCount[key] = (messageCount[key] ?: 0) + 1
}
- override fun event(id: String, event: LogEvent, category: LogCategory, details: String?) {
+ override fun event(id: String, event: LogEvent, category: LogCategory, extraData: Bundle) {
val key = EventCountKey(id, event, category)
eventCount[key] = (eventCount[key] ?: 0) + 1
}
diff --git a/packages/SettingsLib/Spa/testutils/Android.bp b/packages/SettingsLib/Spa/testutils/Android.bp
index 48df569..de87dde 100644
--- a/packages/SettingsLib/Spa/testutils/Android.bp
+++ b/packages/SettingsLib/Spa/testutils/Android.bp
@@ -24,7 +24,7 @@
srcs: ["src/**/*.kt"],
static_libs: [
- "androidx.arch.core_core-runtime",
+ "androidx.arch.core_core-testing",
"androidx.compose.ui_ui-test-junit4",
"androidx.compose.ui_ui-test-manifest",
"mockito",
diff --git a/packages/SettingsLib/Spa/testutils/build.gradle b/packages/SettingsLib/Spa/testutils/build.gradle
index be8df43..81e54c1 100644
--- a/packages/SettingsLib/Spa/testutils/build.gradle
+++ b/packages/SettingsLib/Spa/testutils/build.gradle
@@ -47,7 +47,7 @@
}
dependencies {
- api "androidx.arch.core:core-runtime:2.1.0"
+ api "androidx.arch.core:core-testing:2.1.0"
api "androidx.compose.ui:ui-test-junit4:$jetpack_compose_version"
api "com.google.truth:truth:1.1.3"
api "org.mockito:mockito-core:2.21.0"
diff --git a/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/InstantTaskExecutorRule.kt b/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/InstantTaskExecutorRule.kt
deleted file mode 100644
index 43c18d4..0000000
--- a/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/InstantTaskExecutorRule.kt
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright (C) 2022 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.settingslib.spa.testutils
-
-import androidx.arch.core.executor.ArchTaskExecutor
-import androidx.arch.core.executor.TaskExecutor
-import org.junit.rules.TestWatcher
-import org.junit.runner.Description
-
-/**
- * Test rule that makes ArchTaskExecutor main thread assertions pass. There is one such assert
- * in LifecycleRegistry.
-
- * This is a copy of androidx/arch/core/executor/testing/InstantTaskExecutorRule which should be
- * replaced once the dependency issue is solved.
- */
-class InstantTaskExecutorRule : TestWatcher() {
- override fun starting(description: Description) {
- super.starting(description)
- ArchTaskExecutor.getInstance().setDelegate(
- object : TaskExecutor() {
- override fun executeOnDiskIO(runnable: Runnable) {
- runnable.run()
- }
-
- override fun postToMainThread(runnable: Runnable) {
- runnable.run()
- }
-
- override fun isMainThread(): Boolean {
- return true
- }
- }
- )
- }
-
- override fun finished(description: Description) {
- super.finished(description)
- ArchTaskExecutor.getInstance().setDelegate(null)
- }
-}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListModel.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListModel.kt
index a7122d0..6999908 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListModel.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListModel.kt
@@ -33,7 +33,8 @@
*
* @return the [AppRecord] list which will be displayed.
*/
- fun filter(userIdFlow: Flow<Int>, option: Int, recordListFlow: Flow<List<T>>): Flow<List<T>>
+ fun filter(userIdFlow: Flow<Int>, option: Int, recordListFlow: Flow<List<T>>): Flow<List<T>> =
+ recordListFlow
/**
* This function is called when the App List's loading is finished and displayed to the user.
@@ -67,5 +68,5 @@
* @return null if no summary should be displayed.
*/
@Composable
- fun getSummary(option: Int, record: T): State<String>?
+ fun getSummary(option: Int, record: T): State<String>? = null
}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt
index 65c547a..b9c875b 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt
@@ -21,7 +21,7 @@
import androidx.compose.runtime.Composable
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settingslib.spa.framework.compose.stateOf
-import com.android.settingslib.spa.framework.util.asyncMapItem
+import com.android.settingslib.spa.framework.util.mapItem
import com.android.settingslib.spa.testutils.waitUntil
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -116,16 +116,7 @@
var onFirstLoadedCalled = false
override fun transform(userIdFlow: Flow<Int>, appListFlow: Flow<List<ApplicationInfo>>) =
- appListFlow.asyncMapItem { TestAppRecord(it) }
-
- @Composable
- override fun getSummary(option: Int, record: TestAppRecord) = null
-
- override fun filter(
- userIdFlow: Flow<Int>,
- option: Int,
- recordListFlow: Flow<List<TestAppRecord>>,
- ) = recordListFlow
+ appListFlow.mapItem(::TestAppRecord)
override suspend fun onFirstLoaded(recordList: List<TestAppRecord>) {
onFirstLoadedCalled = true
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/TestAppListModel.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/TestAppListModel.kt
index d556487..ada4016 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/TestAppListModel.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/TestAppListModel.kt
@@ -17,8 +17,7 @@
package com.android.settingslib.spaprivileged.tests.testutils
import android.content.pm.ApplicationInfo
-import androidx.compose.runtime.Composable
-import com.android.settingslib.spa.framework.util.asyncMapItem
+import com.android.settingslib.spa.framework.util.mapItem
import com.android.settingslib.spaprivileged.model.app.AppListModel
import com.android.settingslib.spaprivileged.model.app.AppRecord
import kotlinx.coroutines.flow.Flow
@@ -35,16 +34,7 @@
override fun getSpinnerOptions() = options
override fun transform(userIdFlow: Flow<Int>, appListFlow: Flow<List<ApplicationInfo>>) =
- appListFlow.asyncMapItem { TestAppRecord(it) }
-
- @Composable
- override fun getSummary(option: Int, record: TestAppRecord) = null
-
- override fun filter(
- userIdFlow: Flow<Int>,
- option: Int,
- recordListFlow: Flow<List<TestAppRecord>>,
- ) = recordListFlow
+ appListFlow.mapItem(::TestAppRecord)
override fun getGroupTitle(option: Int, record: TestAppRecord) =
if (enableGrouping) record.group else null
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/data/content/FakeKeyguardQuickAffordanceProviderClient.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/data/content/FakeKeyguardQuickAffordanceProviderClient.kt
new file mode 100644
index 0000000..f490c54
--- /dev/null
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/data/content/FakeKeyguardQuickAffordanceProviderClient.kt
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2022 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.shared.quickaffordance.data.content
+
+import android.graphics.drawable.BitmapDrawable
+import android.graphics.drawable.Drawable
+import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.combine
+
+class FakeKeyguardQuickAffordanceProviderClient(
+ slots: List<KeyguardQuickAffordanceProviderClient.Slot> =
+ listOf(
+ KeyguardQuickAffordanceProviderClient.Slot(
+ id = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
+ capacity = 1,
+ ),
+ KeyguardQuickAffordanceProviderClient.Slot(
+ id = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
+ capacity = 1,
+ ),
+ ),
+ affordances: List<KeyguardQuickAffordanceProviderClient.Affordance> =
+ listOf(
+ KeyguardQuickAffordanceProviderClient.Affordance(
+ id = AFFORDANCE_1,
+ name = AFFORDANCE_1,
+ iconResourceId = 0,
+ ),
+ KeyguardQuickAffordanceProviderClient.Affordance(
+ id = AFFORDANCE_2,
+ name = AFFORDANCE_2,
+ iconResourceId = 0,
+ ),
+ KeyguardQuickAffordanceProviderClient.Affordance(
+ id = AFFORDANCE_3,
+ name = AFFORDANCE_3,
+ iconResourceId = 0,
+ ),
+ ),
+ flags: List<KeyguardQuickAffordanceProviderClient.Flag> =
+ listOf(
+ KeyguardQuickAffordanceProviderClient.Flag(
+ name = KeyguardQuickAffordanceProviderContract.FlagsTable.FLAG_NAME_FEATURE_ENABLED,
+ value = true,
+ )
+ ),
+) : KeyguardQuickAffordanceProviderClient {
+
+ private val slots = MutableStateFlow(slots)
+ private val affordances = MutableStateFlow(affordances)
+ private val flags = MutableStateFlow(flags)
+
+ private val selections = MutableStateFlow<Map<String, List<String>>>(emptyMap())
+
+ override suspend fun insertSelection(slotId: String, affordanceId: String) {
+ val slotCapacity =
+ querySlots().find { it.id == slotId }?.capacity
+ ?: error("Slot with ID \"$slotId\" not found!")
+ val affordances = selections.value.getOrDefault(slotId, mutableListOf()).toMutableList()
+ while (affordances.size + 1 > slotCapacity) {
+ affordances.removeAt(0)
+ }
+ affordances.remove(affordanceId)
+ affordances.add(affordanceId)
+ selections.value = selections.value.toMutableMap().apply { this[slotId] = affordances }
+ }
+
+ override suspend fun querySlots(): List<KeyguardQuickAffordanceProviderClient.Slot> {
+ return slots.value
+ }
+
+ override suspend fun queryFlags(): List<KeyguardQuickAffordanceProviderClient.Flag> {
+ return flags.value
+ }
+
+ override fun observeSlots(): Flow<List<KeyguardQuickAffordanceProviderClient.Slot>> {
+ return slots.asStateFlow()
+ }
+
+ override fun observeFlags(): Flow<List<KeyguardQuickAffordanceProviderClient.Flag>> {
+ return flags.asStateFlow()
+ }
+
+ override suspend fun queryAffordances():
+ List<KeyguardQuickAffordanceProviderClient.Affordance> {
+ return affordances.value
+ }
+
+ override fun observeAffordances():
+ Flow<List<KeyguardQuickAffordanceProviderClient.Affordance>> {
+ return affordances.asStateFlow()
+ }
+
+ override suspend fun querySelections(): List<KeyguardQuickAffordanceProviderClient.Selection> {
+ return toSelectionList(selections.value, affordances.value)
+ }
+
+ override fun observeSelections(): Flow<List<KeyguardQuickAffordanceProviderClient.Selection>> {
+ return combine(selections, affordances) { selections, affordances ->
+ toSelectionList(selections, affordances)
+ }
+ }
+
+ override suspend fun deleteSelection(slotId: String, affordanceId: String) {
+ val affordances = selections.value.getOrDefault(slotId, mutableListOf()).toMutableList()
+ affordances.remove(affordanceId)
+
+ selections.value = selections.value.toMutableMap().apply { this[slotId] = affordances }
+ }
+
+ override suspend fun deleteAllSelections(slotId: String) {
+ selections.value = selections.value.toMutableMap().apply { this[slotId] = emptyList() }
+ }
+
+ override suspend fun getAffordanceIcon(iconResourceId: Int, tintColor: Int): Drawable {
+ return BitmapDrawable()
+ }
+
+ fun setFlag(
+ name: String,
+ value: Boolean,
+ ) {
+ flags.value =
+ flags.value.toMutableList().apply {
+ removeIf { it.name == name }
+ add(KeyguardQuickAffordanceProviderClient.Flag(name = name, value = value))
+ }
+ }
+
+ fun setSlotCapacity(slotId: String, capacity: Int) {
+ slots.value =
+ slots.value.toMutableList().apply {
+ val index = indexOfFirst { it.id == slotId }
+ check(index != -1) { "Slot with ID \"$slotId\" doesn't exist!" }
+ set(
+ index,
+ KeyguardQuickAffordanceProviderClient.Slot(id = slotId, capacity = capacity)
+ )
+ }
+ }
+
+ fun addAffordance(affordance: KeyguardQuickAffordanceProviderClient.Affordance): Int {
+ affordances.value = affordances.value + listOf(affordance)
+ return affordances.value.size - 1
+ }
+
+ private fun toSelectionList(
+ selections: Map<String, List<String>>,
+ affordances: List<KeyguardQuickAffordanceProviderClient.Affordance>,
+ ): List<KeyguardQuickAffordanceProviderClient.Selection> {
+ return selections
+ .map { (slotId, affordanceIds) ->
+ affordanceIds.map { affordanceId ->
+ val affordanceName =
+ affordances.find { it.id == affordanceId }?.name
+ ?: error("No affordance with ID of \"$affordanceId\"!")
+ KeyguardQuickAffordanceProviderClient.Selection(
+ slotId = slotId,
+ affordanceId = affordanceId,
+ affordanceName = affordanceName,
+ )
+ }
+ }
+ .flatten()
+ }
+
+ companion object {
+ const val AFFORDANCE_1 = "affordance_1"
+ const val AFFORDANCE_2 = "affordance_2"
+ const val AFFORDANCE_3 = "affordance_3"
+ }
+}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/data/content/KeyguardQuickAffordanceProviderClient.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/data/content/KeyguardQuickAffordanceProviderClient.kt
new file mode 100644
index 0000000..3213b2e
--- /dev/null
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/data/content/KeyguardQuickAffordanceProviderClient.kt
@@ -0,0 +1,479 @@
+/*
+ * Copyright (C) 2022 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.shared.quickaffordance.data.content
+
+import android.annotation.SuppressLint
+import android.content.ContentValues
+import android.content.Context
+import android.database.ContentObserver
+import android.graphics.Color
+import android.graphics.drawable.Drawable
+import android.net.Uri
+import androidx.annotation.DrawableRes
+import com.android.systemui.shared.quickaffordance.data.content.KeyguardQuickAffordanceProviderContract as Contract
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.withContext
+
+/** Client for using a content provider implementing the [Contract]. */
+interface KeyguardQuickAffordanceProviderClient {
+
+ /**
+ * Selects an affordance with the given ID for a slot on the lock screen with the given ID.
+ *
+ * Note that the maximum number of selected affordances on this slot is automatically enforced.
+ * Selecting a slot that is already full (e.g. already has a number of selected affordances at
+ * its maximum capacity) will automatically remove the oldest selected affordance before adding
+ * the one passed in this call. Additionally, selecting an affordance that's already one of the
+ * selected affordances on the slot will move the selected affordance to the newest location in
+ * the slot.
+ */
+ suspend fun insertSelection(
+ slotId: String,
+ affordanceId: String,
+ )
+
+ /** Returns all available slots supported by the device. */
+ suspend fun querySlots(): List<Slot>
+
+ /** Returns the list of flags. */
+ suspend fun queryFlags(): List<Flag>
+
+ /**
+ * Returns [Flow] for observing the collection of slots.
+ *
+ * @see [querySlots]
+ */
+ fun observeSlots(): Flow<List<Slot>>
+
+ /**
+ * Returns [Flow] for observing the collection of flags.
+ *
+ * @see [queryFlags]
+ */
+ fun observeFlags(): Flow<List<Flag>>
+
+ /**
+ * Returns all available affordances supported by the device, regardless of current slot
+ * placement.
+ */
+ suspend fun queryAffordances(): List<Affordance>
+
+ /**
+ * Returns [Flow] for observing the collection of affordances.
+ *
+ * @see [queryAffordances]
+ */
+ fun observeAffordances(): Flow<List<Affordance>>
+
+ /** Returns the current slot-affordance selections. */
+ suspend fun querySelections(): List<Selection>
+
+ /**
+ * Returns [Flow] for observing the collection of selections.
+ *
+ * @see [querySelections]
+ */
+ fun observeSelections(): Flow<List<Selection>>
+
+ /** Unselects an affordance with the given ID from the slot with the given ID. */
+ suspend fun deleteSelection(
+ slotId: String,
+ affordanceId: String,
+ )
+
+ /** Unselects all affordances from the slot with the given ID. */
+ suspend fun deleteAllSelections(
+ slotId: String,
+ )
+
+ /** Returns a [Drawable] with the given ID, loaded from the system UI package. */
+ suspend fun getAffordanceIcon(
+ @DrawableRes iconResourceId: Int,
+ tintColor: Int = Color.WHITE,
+ ): Drawable
+
+ /** Models a slot. A position that quick affordances can be positioned in. */
+ data class Slot(
+ /** Unique ID of the slot. */
+ val id: String,
+ /**
+ * The maximum number of quick affordances that are allowed to be positioned in this slot.
+ */
+ val capacity: Int,
+ )
+
+ /**
+ * Models a quick affordance. An action that can be selected by the user to appear in one or
+ * more slots on the lock screen.
+ */
+ data class Affordance(
+ /** Unique ID of the quick affordance. */
+ val id: String,
+ /** User-facing label for this affordance. */
+ val name: String,
+ /**
+ * Resource ID for the user-facing icon for this affordance. This resource is hosted by the
+ * System UI process so it must be used with
+ * `PackageManager.getResourcesForApplication(String)`.
+ */
+ val iconResourceId: Int,
+ /**
+ * Whether the affordance is enabled. Disabled affordances should be shown on the picker but
+ * should be rendered as "disabled". When tapped, the enablement properties should be used
+ * to populate UI that would explain to the user what to do in order to re-enable this
+ * affordance.
+ */
+ val isEnabled: Boolean = true,
+ /**
+ * If the affordance is disabled, this is a set of instruction messages to be shown to the
+ * user when the disabled affordance is selected. The instructions should help the user
+ * figure out what to do in order to re-neable this affordance.
+ */
+ val enablementInstructions: List<String>? = null,
+ /**
+ * If the affordance is disabled, this is a label for a button shown together with the set
+ * of instruction messages when the disabled affordance is selected. The button should help
+ * send the user to a flow that would help them achieve the instructions and re-enable this
+ * affordance.
+ *
+ * If `null`, the button should not be shown.
+ */
+ val enablementActionText: String? = null,
+ /**
+ * If the affordance is disabled, this is a "component name" of the format
+ * `packageName/action` to be used as an `Intent` for `startActivity` when the action button
+ * (shown together with the set of instruction messages when the disabled affordance is
+ * selected) is clicked by the user. The button should help send the user to a flow that
+ * would help them achieve the instructions and re-enable this affordance.
+ *
+ * If `null`, the button should not be shown.
+ */
+ val enablementActionComponentName: String? = null,
+ )
+
+ /** Models a selection of a quick affordance on a slot. */
+ data class Selection(
+ /** The unique ID of the slot. */
+ val slotId: String,
+ /** The unique ID of the quick affordance. */
+ val affordanceId: String,
+ /** The user-visible label for the quick affordance. */
+ val affordanceName: String,
+ )
+
+ /** Models a System UI flag. */
+ data class Flag(
+ /** The name of the flag. */
+ val name: String,
+ /** The value of the flag. */
+ val value: Boolean,
+ )
+}
+
+class KeyguardQuickAffordanceProviderClientImpl(
+ private val context: Context,
+ private val backgroundDispatcher: CoroutineDispatcher,
+) : KeyguardQuickAffordanceProviderClient {
+
+ override suspend fun insertSelection(
+ slotId: String,
+ affordanceId: String,
+ ) {
+ withContext(backgroundDispatcher) {
+ context.contentResolver.insert(
+ Contract.SelectionTable.URI,
+ ContentValues().apply {
+ put(Contract.SelectionTable.Columns.SLOT_ID, slotId)
+ put(Contract.SelectionTable.Columns.AFFORDANCE_ID, affordanceId)
+ }
+ )
+ }
+ }
+
+ override suspend fun querySlots(): List<KeyguardQuickAffordanceProviderClient.Slot> {
+ return withContext(backgroundDispatcher) {
+ context.contentResolver
+ .query(
+ Contract.SlotTable.URI,
+ null,
+ null,
+ null,
+ null,
+ )
+ ?.use { cursor ->
+ buildList {
+ val idColumnIndex = cursor.getColumnIndex(Contract.SlotTable.Columns.ID)
+ val capacityColumnIndex =
+ cursor.getColumnIndex(Contract.SlotTable.Columns.CAPACITY)
+ if (idColumnIndex == -1 || capacityColumnIndex == -1) {
+ return@buildList
+ }
+
+ while (cursor.moveToNext()) {
+ add(
+ KeyguardQuickAffordanceProviderClient.Slot(
+ id = cursor.getString(idColumnIndex),
+ capacity = cursor.getInt(capacityColumnIndex),
+ )
+ )
+ }
+ }
+ }
+ }
+ ?: emptyList()
+ }
+
+ override suspend fun queryFlags(): List<KeyguardQuickAffordanceProviderClient.Flag> {
+ return withContext(backgroundDispatcher) {
+ context.contentResolver
+ .query(
+ Contract.FlagsTable.URI,
+ null,
+ null,
+ null,
+ null,
+ )
+ ?.use { cursor ->
+ buildList {
+ val nameColumnIndex =
+ cursor.getColumnIndex(Contract.FlagsTable.Columns.NAME)
+ val valueColumnIndex =
+ cursor.getColumnIndex(Contract.FlagsTable.Columns.VALUE)
+ if (nameColumnIndex == -1 || valueColumnIndex == -1) {
+ return@buildList
+ }
+
+ while (cursor.moveToNext()) {
+ add(
+ KeyguardQuickAffordanceProviderClient.Flag(
+ name = cursor.getString(nameColumnIndex),
+ value = cursor.getInt(valueColumnIndex) == 1,
+ )
+ )
+ }
+ }
+ }
+ }
+ ?: emptyList()
+ }
+
+ override fun observeSlots(): Flow<List<KeyguardQuickAffordanceProviderClient.Slot>> {
+ return observeUri(Contract.SlotTable.URI).map { querySlots() }
+ }
+
+ override fun observeFlags(): Flow<List<KeyguardQuickAffordanceProviderClient.Flag>> {
+ return observeUri(Contract.FlagsTable.URI).map { queryFlags() }
+ }
+
+ override suspend fun queryAffordances():
+ List<KeyguardQuickAffordanceProviderClient.Affordance> {
+ return withContext(backgroundDispatcher) {
+ context.contentResolver
+ .query(
+ Contract.AffordanceTable.URI,
+ null,
+ null,
+ null,
+ null,
+ )
+ ?.use { cursor ->
+ buildList {
+ val idColumnIndex =
+ cursor.getColumnIndex(Contract.AffordanceTable.Columns.ID)
+ val nameColumnIndex =
+ cursor.getColumnIndex(Contract.AffordanceTable.Columns.NAME)
+ val iconColumnIndex =
+ cursor.getColumnIndex(Contract.AffordanceTable.Columns.ICON)
+ val isEnabledColumnIndex =
+ cursor.getColumnIndex(Contract.AffordanceTable.Columns.IS_ENABLED)
+ val enablementInstructionsColumnIndex =
+ cursor.getColumnIndex(
+ Contract.AffordanceTable.Columns.ENABLEMENT_INSTRUCTIONS
+ )
+ val enablementActionTextColumnIndex =
+ cursor.getColumnIndex(
+ Contract.AffordanceTable.Columns.ENABLEMENT_ACTION_TEXT
+ )
+ val enablementComponentNameColumnIndex =
+ cursor.getColumnIndex(
+ Contract.AffordanceTable.Columns.ENABLEMENT_COMPONENT_NAME
+ )
+ if (
+ idColumnIndex == -1 ||
+ nameColumnIndex == -1 ||
+ iconColumnIndex == -1 ||
+ isEnabledColumnIndex == -1 ||
+ enablementInstructionsColumnIndex == -1 ||
+ enablementActionTextColumnIndex == -1 ||
+ enablementComponentNameColumnIndex == -1
+ ) {
+ return@buildList
+ }
+
+ while (cursor.moveToNext()) {
+ add(
+ KeyguardQuickAffordanceProviderClient.Affordance(
+ id = cursor.getString(idColumnIndex),
+ name = cursor.getString(nameColumnIndex),
+ iconResourceId = cursor.getInt(iconColumnIndex),
+ isEnabled = cursor.getInt(isEnabledColumnIndex) == 1,
+ enablementInstructions =
+ cursor
+ .getString(enablementInstructionsColumnIndex)
+ ?.split(
+ Contract.AffordanceTable
+ .ENABLEMENT_INSTRUCTIONS_DELIMITER
+ ),
+ enablementActionText =
+ cursor.getString(enablementActionTextColumnIndex),
+ enablementActionComponentName =
+ cursor.getString(enablementComponentNameColumnIndex),
+ )
+ )
+ }
+ }
+ }
+ }
+ ?: emptyList()
+ }
+
+ override fun observeAffordances():
+ Flow<List<KeyguardQuickAffordanceProviderClient.Affordance>> {
+ return observeUri(Contract.AffordanceTable.URI).map { queryAffordances() }
+ }
+
+ override suspend fun querySelections(): List<KeyguardQuickAffordanceProviderClient.Selection> {
+ return withContext(backgroundDispatcher) {
+ context.contentResolver
+ .query(
+ Contract.SelectionTable.URI,
+ null,
+ null,
+ null,
+ null,
+ )
+ ?.use { cursor ->
+ buildList {
+ val slotIdColumnIndex =
+ cursor.getColumnIndex(Contract.SelectionTable.Columns.SLOT_ID)
+ val affordanceIdColumnIndex =
+ cursor.getColumnIndex(Contract.SelectionTable.Columns.AFFORDANCE_ID)
+ val affordanceNameColumnIndex =
+ cursor.getColumnIndex(Contract.SelectionTable.Columns.AFFORDANCE_NAME)
+ if (
+ slotIdColumnIndex == -1 ||
+ affordanceIdColumnIndex == -1 ||
+ affordanceNameColumnIndex == -1
+ ) {
+ return@buildList
+ }
+
+ while (cursor.moveToNext()) {
+ add(
+ KeyguardQuickAffordanceProviderClient.Selection(
+ slotId = cursor.getString(slotIdColumnIndex),
+ affordanceId = cursor.getString(affordanceIdColumnIndex),
+ affordanceName = cursor.getString(affordanceNameColumnIndex),
+ )
+ )
+ }
+ }
+ }
+ }
+ ?: emptyList()
+ }
+
+ override fun observeSelections(): Flow<List<KeyguardQuickAffordanceProviderClient.Selection>> {
+ return observeUri(Contract.SelectionTable.URI).map { querySelections() }
+ }
+
+ override suspend fun deleteSelection(
+ slotId: String,
+ affordanceId: String,
+ ) {
+ withContext(backgroundDispatcher) {
+ context.contentResolver.delete(
+ Contract.SelectionTable.URI,
+ "${Contract.SelectionTable.Columns.SLOT_ID} = ? AND" +
+ " ${Contract.SelectionTable.Columns.AFFORDANCE_ID} = ?",
+ arrayOf(
+ slotId,
+ affordanceId,
+ ),
+ )
+ }
+ }
+
+ override suspend fun deleteAllSelections(
+ slotId: String,
+ ) {
+ withContext(backgroundDispatcher) {
+ context.contentResolver.delete(
+ Contract.SelectionTable.URI,
+ Contract.SelectionTable.Columns.SLOT_ID,
+ arrayOf(
+ slotId,
+ ),
+ )
+ }
+ }
+
+ @SuppressLint("UseCompatLoadingForDrawables")
+ override suspend fun getAffordanceIcon(
+ @DrawableRes iconResourceId: Int,
+ tintColor: Int,
+ ): Drawable {
+ return withContext(backgroundDispatcher) {
+ context.packageManager
+ .getResourcesForApplication(SYSTEM_UI_PACKAGE_NAME)
+ .getDrawable(iconResourceId, context.theme)
+ .apply { setTint(tintColor) }
+ }
+ }
+
+ private fun observeUri(
+ uri: Uri,
+ ): Flow<Unit> {
+ return callbackFlow {
+ val observer =
+ object : ContentObserver(null) {
+ override fun onChange(selfChange: Boolean) {
+ trySend(Unit)
+ }
+ }
+
+ context.contentResolver.registerContentObserver(
+ uri,
+ /* notifyForDescendants= */ true,
+ observer,
+ )
+
+ awaitClose { context.contentResolver.unregisterContentObserver(observer) }
+ }
+ .onStart { emit(Unit) }
+ }
+
+ companion object {
+ private const val SYSTEM_UI_PACKAGE_NAME = "com.android.systemui"
+ }
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/keyguard/data/content/KeyguardQuickAffordanceProviderContract.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/data/content/KeyguardQuickAffordanceProviderContract.kt
similarity index 98%
rename from packages/SystemUI/shared/src/com/android/systemui/shared/keyguard/data/content/KeyguardQuickAffordanceProviderContract.kt
rename to packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/data/content/KeyguardQuickAffordanceProviderContract.kt
index 98d8d3e..17be74b 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/keyguard/data/content/KeyguardQuickAffordanceProviderContract.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/data/content/KeyguardQuickAffordanceProviderContract.kt
@@ -15,7 +15,7 @@
*
*/
-package com.android.systemui.shared.keyguard.data.content
+package com.android.systemui.shared.quickaffordance.data.content
import android.content.ContentResolver
import android.net.Uri
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/keyguard/shared/model/KeyguardQuickAffordanceSlots.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/shared/model/KeyguardQuickAffordanceSlots.kt
similarity index 100%
rename from packages/SystemUI/shared/src/com/android/systemui/shared/keyguard/shared/model/KeyguardQuickAffordanceSlots.kt
rename to packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/shared/model/KeyguardQuickAffordanceSlots.kt
diff --git a/packages/SystemUI/src/com/android/systemui/backup/BackupHelper.kt b/packages/SystemUI/src/com/android/systemui/backup/BackupHelper.kt
index 5616a00..621b99d 100644
--- a/packages/SystemUI/src/com/android/systemui/backup/BackupHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/backup/BackupHelper.kt
@@ -29,13 +29,15 @@
import android.util.Log
import com.android.systemui.controls.controller.AuxiliaryPersistenceWrapper
import com.android.systemui.controls.controller.ControlsFavoritePersistenceWrapper
+import com.android.systemui.keyguard.domain.backup.KeyguardQuickAffordanceBackupHelper
import com.android.systemui.people.widget.PeopleBackupHelper
/**
* Helper for backing up elements in SystemUI
*
- * This helper is invoked by BackupManager whenever a backup or restore is required in SystemUI.
- * The helper can be used to back up any element that is stored in [Context.getFilesDir].
+ * This helper is invoked by BackupManager whenever a backup or restore is required in SystemUI. The
+ * helper can be used to back up any element that is stored in [Context.getFilesDir] or
+ * [Context.getSharedPreferences].
*
* After restoring is done, a [ACTION_RESTORE_FINISHED] intent will be send to SystemUI user 0,
* indicating that restoring is finished for a given user.
@@ -47,9 +49,11 @@
internal const val CONTROLS = ControlsFavoritePersistenceWrapper.FILE_NAME
private const val NO_OVERWRITE_FILES_BACKUP_KEY = "systemui.files_no_overwrite"
private const val PEOPLE_TILES_BACKUP_KEY = "systemui.people.shared_preferences"
+ private const val KEYGUARD_QUICK_AFFORDANCES_BACKUP_KEY =
+ "systemui.keyguard.quickaffordance.shared_preferences"
val controlsDataLock = Any()
const val ACTION_RESTORE_FINISHED = "com.android.systemui.backup.RESTORE_FINISHED"
- private const val PERMISSION_SELF = "com.android.systemui.permission.SELF"
+ const val PERMISSION_SELF = "com.android.systemui.permission.SELF"
}
override fun onCreate(userHandle: UserHandle, operationType: Int) {
@@ -67,17 +71,27 @@
}
val keys = PeopleBackupHelper.getFilesToBackup()
- addHelper(PEOPLE_TILES_BACKUP_KEY, PeopleBackupHelper(
- this, userHandle, keys.toTypedArray()))
+ addHelper(
+ PEOPLE_TILES_BACKUP_KEY,
+ PeopleBackupHelper(this, userHandle, keys.toTypedArray())
+ )
+ addHelper(
+ KEYGUARD_QUICK_AFFORDANCES_BACKUP_KEY,
+ KeyguardQuickAffordanceBackupHelper(
+ context = this,
+ userId = userHandle.identifier,
+ ),
+ )
}
override fun onRestoreFinished() {
super.onRestoreFinished()
- val intent = Intent(ACTION_RESTORE_FINISHED).apply {
- `package` = packageName
- putExtra(Intent.EXTRA_USER_ID, userId)
- flags = Intent.FLAG_RECEIVER_REGISTERED_ONLY
- }
+ val intent =
+ Intent(ACTION_RESTORE_FINISHED).apply {
+ `package` = packageName
+ putExtra(Intent.EXTRA_USER_ID, userId)
+ flags = Intent.FLAG_RECEIVER_REGISTERED_ONLY
+ }
sendBroadcastAsUser(intent, UserHandle.SYSTEM, PERMISSION_SELF)
}
@@ -90,7 +104,9 @@
* @property lock a lock to hold while backing up and restoring the files.
* @property context the context of the [BackupAgent]
* @property fileNamesAndPostProcess a map from the filenames to back up and the post processing
+ * ```
* actions to take
+ * ```
*/
private class NoOverwriteFileBackupHelper(
val lock: Any,
@@ -115,23 +131,23 @@
data: BackupDataOutput?,
newState: ParcelFileDescriptor?
) {
- synchronized(lock) {
- super.performBackup(oldState, data, newState)
- }
+ synchronized(lock) { super.performBackup(oldState, data, newState) }
}
}
}
+
private fun getPPControlsFile(context: Context): () -> Unit {
return {
val filesDir = context.filesDir
val file = Environment.buildPath(filesDir, BackupHelper.CONTROLS)
if (file.exists()) {
- val dest = Environment.buildPath(filesDir,
- AuxiliaryPersistenceWrapper.AUXILIARY_FILE_NAME)
+ val dest =
+ Environment.buildPath(filesDir, AuxiliaryPersistenceWrapper.AUXILIARY_FILE_NAME)
file.copyTo(dest)
val jobScheduler = context.getSystemService(JobScheduler::class.java)
jobScheduler?.schedule(
- AuxiliaryPersistenceWrapper.DeletionJobService.getJobForContext(context))
+ AuxiliaryPersistenceWrapper.DeletionJobService.getJobForContext(context)
+ )
}
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt b/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt
index 537cbc5..a0a892d 100644
--- a/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt
+++ b/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt
@@ -64,8 +64,9 @@
* from SystemUI. That way the number of calls to [BroadcastReceiver.onReceive] can be reduced for
* a given broadcast.
*
- * Use only for IntentFilters with actions and optionally categories. It does not support,
- * permissions, schemes, data types, data authorities or priority different than 0.
+ * Use only for IntentFilters with actions and optionally categories. It does not support schemes,
+ * data types, data authorities or priority different than 0.
+ *
* Cannot be used for getting sticky broadcasts (either as return of registering or as re-delivery).
* Broadcast handling may be asynchronous *without* calling goAsync(), as it's running within sysui
* and doesn't need to worry about being killed.
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index aa6c619..2d558ad 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -346,6 +346,12 @@
// TODO(b/256873975): Tracking Bug
@JvmField @Keep val WM_BUBBLE_BAR = unreleasedFlag(1111, "wm_bubble_bar")
+ // TODO(b/260271148): Tracking bug
+ @Keep
+ @JvmField
+ val WM_DESKTOP_WINDOWING_2 =
+ sysPropBooleanFlag(1112, "persist.wm.debug.desktop_mode_2", default = false)
+
// 1200 - predictive back
@Keep
@JvmField
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProvider.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProvider.kt
index 29febb6..4ae37c5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProvider.kt
@@ -29,7 +29,7 @@
import com.android.systemui.SystemUIAppComponentFactoryBase
import com.android.systemui.SystemUIAppComponentFactoryBase.ContextAvailableCallback
import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
-import com.android.systemui.shared.keyguard.data.content.KeyguardQuickAffordanceProviderContract as Contract
+import com.android.systemui.shared.quickaffordance.data.content.KeyguardQuickAffordanceProviderContract as Contract
import javax.inject.Inject
import kotlinx.coroutines.runBlocking
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfig.kt
index 3c09aab..dbc376e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfig.kt
@@ -26,14 +26,17 @@
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import dagger.Lazy
+import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf
-import javax.inject.Inject
@SysUISingleton
-class CameraQuickAffordanceConfig @Inject constructor(
- @Application private val context: Context,
- private val cameraGestureHelper: CameraGestureHelper,
+class CameraQuickAffordanceConfig
+@Inject
+constructor(
+ @Application private val context: Context,
+ private val cameraGestureHelper: Lazy<CameraGestureHelper>,
) : KeyguardQuickAffordanceConfig {
override val key: String
@@ -46,17 +49,23 @@
get() = com.android.internal.R.drawable.perm_group_camera
override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState>
- get() = flowOf(
- KeyguardQuickAffordanceConfig.LockScreenState.Visible(
- icon = Icon.Resource(
+ get() =
+ flowOf(
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(
+ icon =
+ Icon.Resource(
com.android.internal.R.drawable.perm_group_camera,
ContentDescription.Resource(R.string.accessibility_camera_button)
- )
+ )
+ )
)
- )
- override fun onTriggered(expandable: Expandable?): KeyguardQuickAffordanceConfig.OnTriggeredResult {
- cameraGestureHelper.launchCamera(StatusBarManager.CAMERA_LAUNCH_SOURCE_QUICK_AFFORDANCE)
+ override fun onTriggered(
+ expandable: Expandable?
+ ): KeyguardQuickAffordanceConfig.OnTriggeredResult {
+ cameraGestureHelper
+ .get()
+ .launchCamera(StatusBarManager.CAMERA_LAUNCH_SOURCE_QUICK_AFFORDANCE)
return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt
index 4477310..98b1a73 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt
@@ -21,7 +21,7 @@
import com.android.systemui.animation.Expandable
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
-import com.android.systemui.shared.keyguard.data.content.KeyguardQuickAffordanceProviderContract as Contract
+import com.android.systemui.shared.quickaffordance.data.content.KeyguardQuickAffordanceProviderContract as Contract
import kotlinx.coroutines.flow.Flow
/** Defines interface that can act as data source for a single quick affordance model. */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManager.kt
index b29cf45..4f37e5f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManager.kt
@@ -18,9 +18,11 @@
package com.android.systemui.keyguard.data.quickaffordance
import android.content.Context
+import android.content.IntentFilter
import android.content.SharedPreferences
-import androidx.annotation.VisibleForTesting
import com.android.systemui.R
+import com.android.systemui.backup.BackupHelper
+import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
@@ -28,14 +30,18 @@
import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.onStart
/**
* Manages and provides access to the current "selections" of keyguard quick affordances, answering
* the question "which affordances should the keyguard show?".
*/
+@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
class KeyguardQuickAffordanceSelectionManager
@Inject
@@ -43,15 +49,10 @@
@Application context: Context,
private val userFileManager: UserFileManager,
private val userTracker: UserTracker,
+ broadcastDispatcher: BroadcastDispatcher,
) {
- private val sharedPrefs: SharedPreferences
- get() =
- userFileManager.getSharedPreferences(
- FILE_NAME,
- Context.MODE_PRIVATE,
- userTracker.userId,
- )
+ private var sharedPrefs: SharedPreferences = instantiateSharedPrefs()
private val userId: Flow<Int> = conflatedCallbackFlow {
val callback =
@@ -78,21 +79,54 @@
}
}
+ /**
+ * Emits an event each time a Backup & Restore restoration job is completed. Does not emit an
+ * initial value.
+ */
+ private val backupRestorationEvents: Flow<Unit> =
+ broadcastDispatcher.broadcastFlow(
+ filter = IntentFilter(BackupHelper.ACTION_RESTORE_FINISHED),
+ flags = Context.RECEIVER_NOT_EXPORTED,
+ permission = BackupHelper.PERMISSION_SELF,
+ )
+
/** IDs of affordances to show, indexed by slot ID, and sorted in descending priority order. */
val selections: Flow<Map<String, List<String>>> =
- userId.flatMapLatest {
- conflatedCallbackFlow {
- val listener =
- SharedPreferences.OnSharedPreferenceChangeListener { _, _ ->
- trySend(getSelections())
- }
-
- sharedPrefs.registerOnSharedPreferenceChangeListener(listener)
- send(getSelections())
-
- awaitClose { sharedPrefs.unregisterOnSharedPreferenceChangeListener(listener) }
+ combine(
+ userId,
+ backupRestorationEvents.onStart {
+ // We emit an initial event to make sure that the combine emits at least once,
+ // even
+ // if we never get a Backup & Restore restoration event (which is the most
+ // common
+ // case anyway as restoration really only happens on initial device setup).
+ emit(Unit)
+ }
+ ) { _, _ ->
}
- }
+ .flatMapLatest {
+ conflatedCallbackFlow {
+ // We want to instantiate a new SharedPreferences instance each time either the
+ // user
+ // ID changes or we have a backup & restore restoration event. The reason is
+ // that
+ // our sharedPrefs instance needs to be replaced with a new one as it depends on
+ // the
+ // user ID and when the B&R job completes, the backing file is replaced but the
+ // existing instance still has a stale in-memory cache.
+ sharedPrefs = instantiateSharedPrefs()
+
+ val listener =
+ SharedPreferences.OnSharedPreferenceChangeListener { _, _ ->
+ trySend(getSelections())
+ }
+
+ sharedPrefs.registerOnSharedPreferenceChangeListener(listener)
+ send(getSelections())
+
+ awaitClose { sharedPrefs.unregisterOnSharedPreferenceChangeListener(listener) }
+ }
+ }
/**
* Returns a snapshot of the IDs of affordances to show, indexed by slot ID, and sorted in
@@ -144,9 +178,17 @@
sharedPrefs.edit().putString(key, value).apply()
}
+ private fun instantiateSharedPrefs(): SharedPreferences {
+ return userFileManager.getSharedPreferences(
+ FILE_NAME,
+ Context.MODE_PRIVATE,
+ userTracker.userId,
+ )
+ }
+
companion object {
private const val TAG = "KeyguardQuickAffordanceSelectionManager"
- @VisibleForTesting const val FILE_NAME = "quick_affordance_selections"
+ const val FILE_NAME = "quick_affordance_selections"
private const val KEY_PREFIX_SLOT = "slot_"
private const val SLOT_AFFORDANCES_DELIMITER = ":"
private const val AFFORDANCE_DELIMITER = ","
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/backup/KeyguardQuickAffordanceBackupHelper.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/backup/KeyguardQuickAffordanceBackupHelper.kt
new file mode 100644
index 0000000..0e865ce
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/backup/KeyguardQuickAffordanceBackupHelper.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2022 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.keyguard.domain.backup
+
+import android.app.backup.SharedPreferencesBackupHelper
+import android.content.Context
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceSelectionManager
+import com.android.systemui.settings.UserFileManagerImpl
+
+/** Handles backup & restore for keyguard quick affordances. */
+class KeyguardQuickAffordanceBackupHelper(
+ context: Context,
+ userId: Int,
+) :
+ SharedPreferencesBackupHelper(
+ context,
+ if (UserFileManagerImpl.isPrimaryUser(userId)) {
+ KeyguardQuickAffordanceSelectionManager.FILE_NAME
+ } else {
+ UserFileManagerImpl.secondaryUserFile(
+ context = context,
+ fileName = KeyguardQuickAffordanceSelectionManager.FILE_NAME,
+ directoryName = UserFileManagerImpl.SHARED_PREFS,
+ userId = userId,
+ )
+ .also { UserFileManagerImpl.ensureParentDirExists(it) }
+ .toString()
+ }
+ )
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
index 2d94d76..ee7154f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
@@ -34,8 +34,8 @@
import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.settings.UserTracker
-import com.android.systemui.shared.keyguard.data.content.KeyguardQuickAffordanceProviderContract
import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
+import com.android.systemui.shared.quickaffordance.data.content.KeyguardQuickAffordanceProviderContract
import com.android.systemui.statusbar.policy.KeyguardStateController
import dagger.Lazy
import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserFileManagerImpl.kt b/packages/SystemUI/src/com/android/systemui/settings/UserFileManagerImpl.kt
index d450afa..bfba6df 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/UserFileManagerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/UserFileManagerImpl.kt
@@ -35,12 +35,14 @@
import javax.inject.Inject
/**
- * Implementation for retrieving file paths for file storage of system and secondary users.
- * Files lie in {File Directory}/UserFileManager/{User Id} for secondary user.
- * For system user, we use the conventional {File Directory}
+ * Implementation for retrieving file paths for file storage of system and secondary users. Files
+ * lie in {File Directory}/UserFileManager/{User Id} for secondary user. For system user, we use the
+ * conventional {File Directory}
*/
@SysUISingleton
-class UserFileManagerImpl @Inject constructor(
+class UserFileManagerImpl
+@Inject
+constructor(
// Context of system process and system user.
private val context: Context,
val userManager: UserManager,
@@ -49,80 +51,114 @@
) : UserFileManager, CoreStartable {
companion object {
private const val FILES = "files"
- @VisibleForTesting internal const val SHARED_PREFS = "shared_prefs"
+ const val SHARED_PREFS = "shared_prefs"
@VisibleForTesting internal const val ID = "UserFileManager"
- }
- private val broadcastReceiver = object : BroadcastReceiver() {
+ /** Returns `true` if the given user ID is that for the primary/system user. */
+ fun isPrimaryUser(userId: Int): Boolean {
+ return UserHandle(userId).isSystem
+ }
+
/**
- * Listen to Intent.ACTION_USER_REMOVED to clear user data.
+ * Returns a [File] pointing to the correct path for a secondary user ID.
+ *
+ * Note that there is no check for the type of user. This should only be called for
+ * secondary users, never for the system user. For that, make sure to call [isPrimaryUser].
+ *
+ * Note also that there is no guarantee that the parent directory structure for the file
+ * exists on disk. For that, call [ensureParentDirExists].
+ *
+ * @param context The context
+ * @param fileName The name of the file
+ * @param directoryName The name of the directory that would contain the file
+ * @param userId The ID of the user to build a file path for
*/
- override fun onReceive(context: Context, intent: Intent) {
- if (intent.action == Intent.ACTION_USER_REMOVED) {
- clearDeletedUserData()
+ fun secondaryUserFile(
+ context: Context,
+ fileName: String,
+ directoryName: String,
+ userId: Int,
+ ): File {
+ return Environment.buildPath(
+ context.filesDir,
+ ID,
+ userId.toString(),
+ directoryName,
+ fileName,
+ )
+ }
+
+ /**
+ * Checks to see if parent dir of the file exists. If it does not, we create the parent dirs
+ * recursively.
+ */
+ fun ensureParentDirExists(file: File) {
+ val parent = file.parentFile
+ if (!parent.exists()) {
+ if (!parent.mkdirs()) {
+ Log.e(ID, "Could not create parent directory for file: ${file.absolutePath}")
+ }
}
}
}
- /**
- * Poll for user-specific directories to delete upon start up.
- */
+ private val broadcastReceiver =
+ object : BroadcastReceiver() {
+ /** Listen to Intent.ACTION_USER_REMOVED to clear user data. */
+ override fun onReceive(context: Context, intent: Intent) {
+ if (intent.action == Intent.ACTION_USER_REMOVED) {
+ clearDeletedUserData()
+ }
+ }
+ }
+
+ /** Poll for user-specific directories to delete upon start up. */
override fun start() {
clearDeletedUserData()
- val filter = IntentFilter().apply {
- addAction(Intent.ACTION_USER_REMOVED)
- }
+ val filter = IntentFilter().apply { addAction(Intent.ACTION_USER_REMOVED) }
broadcastDispatcher.registerReceiver(broadcastReceiver, filter, backgroundExecutor)
}
- /**
- * Return the file based on current user.
- */
+ /** Return the file based on current user. */
override fun getFile(fileName: String, userId: Int): File {
- return if (UserHandle(userId).isSystem) {
- Environment.buildPath(
- context.filesDir,
- fileName
- )
+ return if (isPrimaryUser(userId)) {
+ Environment.buildPath(context.filesDir, fileName)
} else {
- val secondaryFile = Environment.buildPath(
- context.filesDir,
- ID,
- userId.toString(),
- FILES,
- fileName
- )
+ val secondaryFile =
+ secondaryUserFile(
+ context = context,
+ userId = userId,
+ directoryName = FILES,
+ fileName = fileName,
+ )
ensureParentDirExists(secondaryFile)
secondaryFile
}
}
- /**
- * Get shared preferences from user.
- */
+ /** Get shared preferences from user. */
override fun getSharedPreferences(
fileName: String,
@Context.PreferencesMode mode: Int,
userId: Int
): SharedPreferences {
- if (UserHandle(userId).isSystem) {
+ if (isPrimaryUser(userId)) {
return context.getSharedPreferences(fileName, mode)
}
- val secondaryUserDir = Environment.buildPath(
- context.filesDir,
- ID,
- userId.toString(),
- SHARED_PREFS,
- fileName
- )
+
+ val secondaryUserDir =
+ secondaryUserFile(
+ context = context,
+ fileName = fileName,
+ directoryName = SHARED_PREFS,
+ userId = userId,
+ )
ensureParentDirExists(secondaryUserDir)
return context.getSharedPreferences(secondaryUserDir, mode)
}
- /**
- * Remove dirs for deleted users.
- */
+ /** Remove dirs for deleted users. */
@VisibleForTesting
internal fun clearDeletedUserData() {
backgroundExecutor.execute {
@@ -133,10 +169,11 @@
dirsToDelete.forEach { dir ->
try {
- val dirToDelete = Environment.buildPath(
- file,
- dir,
- )
+ val dirToDelete =
+ Environment.buildPath(
+ file,
+ dir,
+ )
dirToDelete.deleteRecursively()
} catch (e: Exception) {
Log.e(ID, "Deletion failed.", e)
@@ -144,18 +181,4 @@
}
}
}
-
- /**
- * Checks to see if parent dir of the file exists. If it does not, we create the parent dirs
- * recursively.
- */
- @VisibleForTesting
- internal fun ensureParentDirExists(file: File) {
- val parent = file.parentFile
- if (!parent.exists()) {
- if (!parent.mkdirs()) {
- Log.e(ID, "Could not create parent directory for file: ${file.absolutePath}")
- }
- }
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProviderTest.kt
index cedde58..32c5b3f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProviderTest.kt
@@ -36,8 +36,8 @@
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
-import com.android.systemui.shared.keyguard.data.content.KeyguardQuickAffordanceProviderContract as Contract
import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
+import com.android.systemui.shared.quickaffordance.data.content.KeyguardQuickAffordanceProviderContract as Contract
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.FakeSharedPreferences
import com.android.systemui.util.mockito.mock
@@ -89,6 +89,7 @@
.thenReturn(FakeSharedPreferences())
},
userTracker = userTracker,
+ broadcastDispatcher = fakeBroadcastDispatcher,
)
val quickAffordanceRepository =
KeyguardQuickAffordanceRepository(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt
index 623becf..7205f30 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt
@@ -37,25 +37,29 @@
@Mock private lateinit var cameraGestureHelper: CameraGestureHelper
@Mock private lateinit var context: Context
+
private lateinit var underTest: CameraQuickAffordanceConfig
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- underTest = CameraQuickAffordanceConfig(
+
+ underTest =
+ CameraQuickAffordanceConfig(
context,
- cameraGestureHelper,
- )
+ ) {
+ cameraGestureHelper
+ }
}
@Test
fun `affordance triggered -- camera launch called`() {
- //when
+ // When
val result = underTest.onTriggered(null)
- //then
+ // Then
verify(cameraGestureHelper)
- .launchCamera(StatusBarManager.CAMERA_LAUNCH_SOURCE_QUICK_AFFORDANCE)
+ .launchCamera(StatusBarManager.CAMERA_LAUNCH_SOURCE_QUICK_AFFORDANCE)
assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled, result)
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt
index 8ef921e..552b8cb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt
@@ -89,6 +89,7 @@
.thenReturn(FakeSharedPreferences())
},
userTracker = FakeUserTracker(),
+ broadcastDispatcher = fakeBroadcastDispatcher,
)
settings = FakeSettings()
settings.putInt(Settings.Secure.LOCKSCREEN_SHOW_CONTROLS, 0)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManagerTest.kt
index d8ee9f1..6a2376b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManagerTest.kt
@@ -17,6 +17,7 @@
package com.android.systemui.keyguard.data.quickaffordance
+import android.content.Intent
import android.content.SharedPreferences
import android.content.pm.UserInfo
import androidx.test.filters.SmallTest
@@ -27,10 +28,15 @@
import com.android.systemui.util.FakeSharedPreferences
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.runTest
+import kotlinx.coroutines.test.setMain
+import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -38,8 +44,12 @@
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.anyString
import org.mockito.Mock
+import org.mockito.Mockito.atLeastOnce
+import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(JUnit4::class)
class KeyguardQuickAffordanceSelectionManagerTest : SysuiTestCase() {
@@ -60,15 +70,23 @@
sharedPrefs.getOrPut(userId) { FakeSharedPreferences() }
}
userTracker = FakeUserTracker()
+ val dispatcher = UnconfinedTestDispatcher()
+ Dispatchers.setMain(dispatcher)
underTest =
KeyguardQuickAffordanceSelectionManager(
context = context,
userFileManager = userFileManager,
userTracker = userTracker,
+ broadcastDispatcher = fakeBroadcastDispatcher,
)
}
+ @After
+ fun tearDown() {
+ Dispatchers.resetMain()
+ }
+
@Test
fun setSelections() = runTest {
overrideResource(R.array.config_keyguardQuickAffordanceDefaults, arrayOf<String>())
@@ -318,6 +336,22 @@
job.cancel()
}
+ @Test
+ fun `responds to backup and restore by reloading the selections from disk`() = runTest {
+ overrideResource(R.array.config_keyguardQuickAffordanceDefaults, arrayOf<String>())
+ val affordanceIdsBySlotId = mutableListOf<Map<String, List<String>>>()
+ val job =
+ launch(UnconfinedTestDispatcher()) {
+ underTest.selections.toList(affordanceIdsBySlotId)
+ }
+ clearInvocations(userFileManager)
+
+ fakeBroadcastDispatcher.registeredReceivers.firstOrNull()?.onReceive(context, Intent())
+
+ verify(userFileManager, atLeastOnce()).getSharedPreferences(anyString(), anyInt(), anyInt())
+ job.cancel()
+ }
+
private fun assertSelections(
observed: Map<String, List<String>>?,
expected: Map<String, List<String>>,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt
index 5c75417..652fae9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt
@@ -76,6 +76,7 @@
.thenReturn(FakeSharedPreferences())
},
userTracker = FakeUserTracker(),
+ broadcastDispatcher = fakeBroadcastDispatcher,
)
underTest =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
index c2650ec..ba7c40b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
@@ -252,6 +252,7 @@
.thenReturn(FakeSharedPreferences())
},
userTracker = userTracker,
+ broadcastDispatcher = fakeBroadcastDispatcher,
)
val quickAffordanceRepository =
KeyguardQuickAffordanceRepository(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
index b790306..8d0c4ef 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
@@ -113,6 +113,7 @@
.thenReturn(FakeSharedPreferences())
},
userTracker = userTracker,
+ broadcastDispatcher = fakeBroadcastDispatcher,
)
val quickAffordanceRepository =
KeyguardQuickAffordanceRepository(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
index 8b166bd..32849cd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
@@ -136,6 +136,7 @@
.thenReturn(FakeSharedPreferences())
},
userTracker = userTracker,
+ broadcastDispatcher = fakeBroadcastDispatcher,
)
val quickAffordanceRepository =
KeyguardQuickAffordanceRepository(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/UserFileManagerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/UserFileManagerImplTest.kt
index 6d9b01e..020a866 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/settings/UserFileManagerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/settings/UserFileManagerImplTest.kt
@@ -50,24 +50,20 @@
lateinit var userFileManager: UserFileManagerImpl
lateinit var backgroundExecutor: FakeExecutor
- @Mock
- lateinit var userManager: UserManager
- @Mock
- lateinit var broadcastDispatcher: BroadcastDispatcher
+ @Mock lateinit var userManager: UserManager
+ @Mock lateinit var broadcastDispatcher: BroadcastDispatcher
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
backgroundExecutor = FakeExecutor(FakeSystemClock())
- userFileManager = UserFileManagerImpl(context, userManager,
- broadcastDispatcher, backgroundExecutor)
+ userFileManager =
+ UserFileManagerImpl(context, userManager, broadcastDispatcher, backgroundExecutor)
}
@After
fun end() {
- val dir = Environment.buildPath(
- context.filesDir,
- UserFileManagerImpl.ID)
+ val dir = Environment.buildPath(context.filesDir, UserFileManagerImpl.ID)
dir.deleteRecursively()
}
@@ -82,13 +78,14 @@
@Test
fun testGetSharedPreferences() {
val secondarySharedPref = userFileManager.getSharedPreferences(TEST_FILE_NAME, 0, 11)
- val secondaryUserDir = Environment.buildPath(
- context.filesDir,
- UserFileManagerImpl.ID,
- "11",
- UserFileManagerImpl.SHARED_PREFS,
- TEST_FILE_NAME
- )
+ val secondaryUserDir =
+ Environment.buildPath(
+ context.filesDir,
+ UserFileManagerImpl.ID,
+ "11",
+ UserFileManagerImpl.SHARED_PREFS,
+ TEST_FILE_NAME
+ )
assertThat(secondarySharedPref).isNotNull()
assertThat(secondaryUserDir.exists())
@@ -101,32 +98,35 @@
val userFileManager = spy(userFileManager)
userFileManager.start()
verify(userFileManager).clearDeletedUserData()
- verify(broadcastDispatcher).registerReceiver(any(BroadcastReceiver::class.java),
- any(IntentFilter::class.java),
- any(Executor::class.java), isNull(), eq(Context.RECEIVER_EXPORTED), isNull())
+ verify(broadcastDispatcher)
+ .registerReceiver(
+ any(BroadcastReceiver::class.java),
+ any(IntentFilter::class.java),
+ any(Executor::class.java),
+ isNull(),
+ eq(Context.RECEIVER_EXPORTED),
+ isNull()
+ )
}
@Test
fun testClearDeletedUserData() {
- val dir = Environment.buildPath(
- context.filesDir,
- UserFileManagerImpl.ID,
- "11",
- "files"
- )
+ val dir = Environment.buildPath(context.filesDir, UserFileManagerImpl.ID, "11", "files")
dir.mkdirs()
- val file = Environment.buildPath(
- context.filesDir,
- UserFileManagerImpl.ID,
- "11",
- "files",
- TEST_FILE_NAME
- )
- val secondaryUserDir = Environment.buildPath(
- context.filesDir,
- UserFileManagerImpl.ID,
- "11",
- )
+ val file =
+ Environment.buildPath(
+ context.filesDir,
+ UserFileManagerImpl.ID,
+ "11",
+ "files",
+ TEST_FILE_NAME
+ )
+ val secondaryUserDir =
+ Environment.buildPath(
+ context.filesDir,
+ UserFileManagerImpl.ID,
+ "11",
+ )
file.createNewFile()
assertThat(secondaryUserDir.exists()).isTrue()
assertThat(file.exists()).isTrue()
@@ -139,15 +139,16 @@
@Test
fun testEnsureParentDirExists() {
- val file = Environment.buildPath(
- context.filesDir,
- UserFileManagerImpl.ID,
- "11",
- "files",
- TEST_FILE_NAME
- )
+ val file =
+ Environment.buildPath(
+ context.filesDir,
+ UserFileManagerImpl.ID,
+ "11",
+ "files",
+ TEST_FILE_NAME
+ )
assertThat(file.parentFile.exists()).isFalse()
- userFileManager.ensureParentDirExists(file)
+ UserFileManagerImpl.ensureParentDirExists(file)
assertThat(file.parentFile.exists()).isTrue()
}
}
diff --git a/services/core/java/com/android/server/BinaryTransparencyService.java b/services/core/java/com/android/server/BinaryTransparencyService.java
index 59024e7..6cd7ce8 100644
--- a/services/core/java/com/android/server/BinaryTransparencyService.java
+++ b/services/core/java/com/android/server/BinaryTransparencyService.java
@@ -310,27 +310,27 @@
Bundle packageMeasurement = measurePackage(packageInfo);
results.add(packageMeasurement);
- if (record) {
+ if (record && (mba_status == MBA_STATUS_UPDATED_PRELOAD)) {
// compute digests of signing info
String[] signerDigestHexStrings = computePackageSignerSha256Digests(
packageInfo.signingInfo);
// now we should have all the bits for the atom
- /* TODO: Uncomment and test after merging new atom definition.
+ byte[] cDigest = packageMeasurement.getByteArray(BUNDLE_CONTENT_DIGEST);
FrameworkStatsLog.write(FrameworkStatsLog.MOBILE_BUNDLED_APP_INFO_GATHERED,
packageInfo.packageName,
packageInfo.getLongVersionCode(),
- HexEncoding.encodeToString(packageMeasurement.getByteArray(
- BUNDLE_CONTENT_DIGEST), false),
+ (cDigest != null) ? HexEncoding.encodeToString(
+ packageMeasurement.getByteArray(BUNDLE_CONTENT_DIGEST),
+ false) : null,
packageMeasurement.getInt(BUNDLE_CONTENT_DIGEST_ALGORITHM),
signerDigestHexStrings, // signer_cert_digest
- mba_status, // mba_status
+ mba_status, // mba_status
null, // initiator
null, // initiator_signer_digest
null, // installer
null // originator
);
- */
}
}
if (DEBUG) {
@@ -377,12 +377,13 @@
}
// we should now have all the info needed for the atom
- /* TODO: Uncomment and test after merging new atom definition.
+ byte[] cDigest = packageMeasurement.getByteArray(BUNDLE_CONTENT_DIGEST);
FrameworkStatsLog.write(FrameworkStatsLog.MOBILE_BUNDLED_APP_INFO_GATHERED,
packageInfo.packageName,
packageInfo.getLongVersionCode(),
- HexEncoding.encodeToString(packageMeasurement.getByteArray(
- BUNDLE_CONTENT_DIGEST), false),
+ (cDigest != null) ? HexEncoding.encodeToString(
+ packageMeasurement.getByteArray(BUNDLE_CONTENT_DIGEST),
+ false) : null,
packageMeasurement.getInt(BUNDLE_CONTENT_DIGEST_ALGORITHM),
signerDigestHexStrings,
MBA_STATUS_NEW_INSTALL, // mba_status
@@ -391,7 +392,6 @@
installer,
originator
);
- */
}
}
if (DEBUG) {
diff --git a/services/core/java/com/android/server/DockObserver.java b/services/core/java/com/android/server/DockObserver.java
index 540ed4c..3487613 100644
--- a/services/core/java/com/android/server/DockObserver.java
+++ b/services/core/java/com/android/server/DockObserver.java
@@ -19,6 +19,7 @@
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
+import android.database.ContentObserver;
import android.media.AudioManager;
import android.media.Ringtone;
import android.media.RingtoneManager;
@@ -73,6 +74,7 @@
private final boolean mAllowTheaterModeWakeFromDock;
private final List<ExtconStateConfig> mExtconStateConfigs;
+ private DeviceProvisionedObserver mDeviceProvisionedObserver;
static final class ExtconStateProvider {
private final Map<String, String> mState;
@@ -110,7 +112,7 @@
Slog.w(TAG, "No state file found at: " + stateFilePath);
return new ExtconStateProvider(new HashMap<>());
} catch (Exception e) {
- Slog.e(TAG, "" , e);
+ Slog.e(TAG, "", e);
return new ExtconStateProvider(new HashMap<>());
}
}
@@ -136,7 +138,7 @@
private static List<ExtconStateConfig> loadExtconStateConfigs(Context context) {
String[] rows = context.getResources().getStringArray(
- com.android.internal.R.array.config_dockExtconStateMapping);
+ com.android.internal.R.array.config_dockExtconStateMapping);
try {
ArrayList<ExtconStateConfig> configs = new ArrayList<>();
for (String row : rows) {
@@ -167,6 +169,7 @@
com.android.internal.R.bool.config_allowTheaterModeWakeFromDock);
mKeepDreamingWhenUndocking = context.getResources().getBoolean(
com.android.internal.R.bool.config_keepDreamingWhenUndocking);
+ mDeviceProvisionedObserver = new DeviceProvisionedObserver(mHandler);
mExtconStateConfigs = loadExtconStateConfigs(context);
@@ -199,15 +202,19 @@
if (phase == PHASE_ACTIVITY_MANAGER_READY) {
synchronized (mLock) {
mSystemReady = true;
-
- // don't bother broadcasting undocked here
- if (mReportedDockState != Intent.EXTRA_DOCK_STATE_UNDOCKED) {
- updateLocked();
- }
+ mDeviceProvisionedObserver.onSystemReady();
+ updateIfDockedLocked();
}
}
}
+ private void updateIfDockedLocked() {
+ // don't bother broadcasting undocked here
+ if (mReportedDockState != Intent.EXTRA_DOCK_STATE_UNDOCKED) {
+ updateLocked();
+ }
+ }
+
private void setActualDockStateLocked(int newState) {
mActualDockState = newState;
if (!mUpdatesStopped) {
@@ -252,8 +259,7 @@
// Skip the dock intent if not yet provisioned.
final ContentResolver cr = getContext().getContentResolver();
- if (Settings.Global.getInt(cr,
- Settings.Global.DEVICE_PROVISIONED, 0) == 0) {
+ if (!mDeviceProvisionedObserver.isDeviceProvisioned()) {
Slog.i(TAG, "Device not provisioned, skipping dock broadcast");
return;
}
@@ -419,4 +425,48 @@
}
}
}
+
+ private final class DeviceProvisionedObserver extends ContentObserver {
+ private boolean mRegistered;
+
+ public DeviceProvisionedObserver(Handler handler) {
+ super(handler);
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ synchronized (mLock) {
+ updateRegistration();
+ if (isDeviceProvisioned()) {
+ // Send the dock broadcast if device is docked after provisioning.
+ updateIfDockedLocked();
+ }
+ }
+ }
+
+ void onSystemReady() {
+ updateRegistration();
+ }
+
+ private void updateRegistration() {
+ boolean register = !isDeviceProvisioned();
+ if (register == mRegistered) {
+ return;
+ }
+ final ContentResolver resolver = getContext().getContentResolver();
+ if (register) {
+ resolver.registerContentObserver(
+ Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONED),
+ false, this);
+ } else {
+ resolver.unregisterContentObserver(this);
+ }
+ mRegistered = register;
+ }
+
+ boolean isDeviceProvisioned() {
+ return Settings.Global.getInt(getContext().getContentResolver(),
+ Settings.Global.DEVICE_PROVISIONED, 0) != 0;
+ }
+ }
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 35b46c1..50be458 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -87,6 +87,7 @@
import static android.os.Process.isSdkSandboxUid;
import static android.os.Process.isThreadInProcess;
import static android.os.Process.killProcess;
+import static android.os.Process.killProcessGroup;
import static android.os.Process.killProcessQuiet;
import static android.os.Process.myPid;
import static android.os.Process.myUid;
@@ -952,13 +953,6 @@
}
return false;
}
-
- boolean doRemoveIfNoThreadInternal(int pid, ProcessRecord app) {
- if (app == null || app.getThread() != null) {
- return false;
- }
- return doRemoveInternal(pid, app);
- }
}
private final PendingStartActivityUids mPendingStartActivityUids;
@@ -990,7 +984,7 @@
* method.
*/
@GuardedBy("this")
- void removePidLocked(int pid, ProcessRecord app) {
+ boolean removePidLocked(int pid, ProcessRecord app) {
final boolean removed;
synchronized (mPidsSelfLocked) {
removed = mPidsSelfLocked.doRemoveInternal(pid, app);
@@ -1001,26 +995,6 @@
}
mAtmInternal.onProcessUnMapped(pid);
}
- }
-
- /**
- * Removes the process record from the map if it doesn't have a thread.
- * <p>NOTE: Callers should avoid acquiring the mPidsSelfLocked lock before calling this
- * method.
- */
- @GuardedBy("this")
- private boolean removePidIfNoThreadLocked(ProcessRecord app) {
- final boolean removed;
- final int pid = app.getPid();
- synchronized (mPidsSelfLocked) {
- removed = mPidsSelfLocked.doRemoveIfNoThreadInternal(pid, app);
- }
- if (removed) {
- synchronized (sActiveProcessInfoSelfLocked) {
- sActiveProcessInfoSelfLocked.remove(pid);
- }
- mAtmInternal.onProcessUnMapped(pid);
- }
return removed;
}
@@ -2364,7 +2338,7 @@
mAppErrors = null;
mPackageWatchdog = null;
mAppOpsService = mInjector.getAppOpsService(null /* file */, null /* handler */);
- mBatteryStatsService = null;
+ mBatteryStatsService = mInjector.getBatteryStatsService();
mHandler = new MainHandler(handlerThread.getLooper());
mHandlerThread = handlerThread;
mConstants = new ActivityManagerConstants(mContext, this, mHandler);
@@ -2379,7 +2353,7 @@
mIntentFirewall = null;
mProcessStats = new ProcessStatsService(this, mContext.getCacheDir());
mCpHelper = new ContentProviderHelper(this, false);
- mServices = null;
+ mServices = mInjector.getActiveServices(this);
mSystemThread = null;
mUiHandler = injector.getUiHandler(null /* service */);
mUidObserverController = new UidObserverController(mUiHandler);
@@ -4771,7 +4745,7 @@
@GuardedBy("this")
void handleProcessStartOrKillTimeoutLocked(ProcessRecord app, boolean isKillTimeout) {
final int pid = app.getPid();
- boolean gone = isKillTimeout || removePidIfNoThreadLocked(app);
+ boolean gone = isKillTimeout || removePidLocked(pid, app);
if (gone) {
if (isKillTimeout) {
@@ -4852,7 +4826,7 @@
}
@GuardedBy("this")
- private boolean attachApplicationLocked(@NonNull IApplicationThread thread,
+ private void attachApplicationLocked(@NonNull IApplicationThread thread,
int pid, int callingUid, long startSeq) {
// Find the application record that is being attached... either via
@@ -4917,7 +4891,7 @@
// Ignore exceptions.
}
}
- return false;
+ return;
}
// If this application record is still attached to a previous
@@ -4942,7 +4916,7 @@
mProcessList.startProcessLocked(app,
new HostingRecord(HostingRecord.HOSTING_TYPE_LINK_FAIL, processName),
ZYGOTE_POLICY_FLAG_EMPTY);
- return false;
+ return;
}
EventLogTags.writeAmProcBound(app.userId, pid, app.processName);
@@ -4965,8 +4939,6 @@
app.setUnlocked(StorageManager.isUserKeyUnlocked(app.userId));
}
- mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app);
-
boolean normalMode = mProcessesReady || isAllowedWhileBooting(app.info);
List<ProviderInfo> providers = normalMode
? mCpHelper.generateApplicationProvidersLocked(app)
@@ -5132,7 +5104,7 @@
app.killLocked("error during bind", ApplicationExitInfo.REASON_INITIALIZATION_FAILURE,
true);
handleAppDiedLocked(app, pid, false, true, false /* fromBinderDied */);
- return false;
+ return;
}
// Remove this record from the list of starting applications.
@@ -5140,91 +5112,6 @@
if (DEBUG_PROCESSES && mProcessesOnHold.contains(app)) Slog.v(TAG_PROCESSES,
"Attach application locked removing on hold: " + app);
mProcessesOnHold.remove(app);
-
- boolean badApp = false;
- boolean didSomething = false;
-
- // See if the top visible activity is waiting to run in this process...
- if (normalMode) {
- try {
- didSomething = mAtmInternal.attachApplication(app.getWindowProcessController());
- } catch (Exception e) {
- Slog.wtf(TAG, "Exception thrown launching activities in " + app, e);
- badApp = true;
- }
- }
-
- // Find any services that should be running in this process...
- if (!badApp) {
- try {
- didSomething |= mServices.attachApplicationLocked(app, processName);
- checkTime(startTime, "attachApplicationLocked: after mServices.attachApplicationLocked");
- } catch (Exception e) {
- Slog.wtf(TAG, "Exception thrown starting services in " + app, e);
- badApp = true;
- }
- }
-
- // Check if a next-broadcast receiver is in this process...
- if (!badApp) {
- try {
- for (BroadcastQueue queue : mBroadcastQueues) {
- didSomething |= queue.onApplicationAttachedLocked(app);
- }
- checkTime(startTime, "attachApplicationLocked: after dispatching broadcasts");
- } catch (Exception e) {
- // If the app died trying to launch the receiver we declare it 'bad'
- Slog.wtf(TAG, "Exception thrown dispatching broadcasts in " + app, e);
- badApp = true;
- }
- }
-
- // Check whether the next backup agent is in this process...
- if (!badApp && backupTarget != null && backupTarget.app == app) {
- if (DEBUG_BACKUP) Slog.v(TAG_BACKUP,
- "New app is backup target, launching agent for " + app);
- notifyPackageUse(backupTarget.appInfo.packageName,
- PackageManager.NOTIFY_PACKAGE_USE_BACKUP);
- try {
- thread.scheduleCreateBackupAgent(backupTarget.appInfo,
- backupTarget.backupMode, backupTarget.userId,
- backupTarget.backupDestination);
- } catch (Exception e) {
- Slog.wtf(TAG, "Exception thrown creating backup agent in " + app, e);
- badApp = true;
- }
- }
-
- if (badApp) {
- app.killLocked("error during init", ApplicationExitInfo.REASON_INITIALIZATION_FAILURE,
- true);
- handleAppDiedLocked(app, pid, false, true, false /* fromBinderDied */);
- return false;
- }
-
- if (!didSomething) {
- updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_PROCESS_BEGIN);
- checkTime(startTime, "attachApplicationLocked: after updateOomAdjLocked");
- }
-
-
- final HostingRecord hostingRecord = app.getHostingRecord();
- String shortAction = getShortAction(hostingRecord.getAction());
- FrameworkStatsLog.write(
- FrameworkStatsLog.PROCESS_START_TIME,
- app.info.uid,
- pid,
- app.info.packageName,
- FrameworkStatsLog.PROCESS_START_TIME__TYPE__COLD,
- app.getStartElapsedTime(),
- (int) (bindApplicationTimeMillis - app.getStartUptime()),
- (int) (SystemClock.uptimeMillis() - app.getStartUptime()),
- hostingRecord.getType(),
- hostingRecord.getName(),
- shortAction,
- HostingRecord.getHostingTypeIdStatsd(hostingRecord.getType()),
- HostingRecord.getTriggerTypeForStatsd(hostingRecord.getTriggerType()));
- return true;
}
@Override
@@ -5241,6 +5128,143 @@
}
}
+ private void finishAttachApplicationInner(long startSeq, int uid, int pid) {
+ final long startTime = SystemClock.uptimeMillis();
+ // Find the application record that is being attached... either via
+ // the pid if we are running in multiple processes, or just pull the
+ // next app record if we are emulating process with anonymous threads.
+ final ProcessRecord app;
+ synchronized (mPidsSelfLocked) {
+ app = mPidsSelfLocked.get(pid);
+ }
+
+ if (app != null && app.getStartUid() == uid && app.getStartSeq() == startSeq) {
+ mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app);
+ } else {
+ Slog.wtf(TAG, "Mismatched or missing ProcessRecord: " + app + ". Pid: " + pid
+ + ". Uid: " + uid);
+ killProcess(pid);
+ killProcessGroup(uid, pid);
+ mProcessList.noteAppKill(pid, uid,
+ ApplicationExitInfo.REASON_INITIALIZATION_FAILURE,
+ ApplicationExitInfo.SUBREASON_UNKNOWN,
+ "wrong startSeq");
+ app.killLocked("unexpected process record",
+ ApplicationExitInfo.REASON_OTHER, true);
+ return;
+ }
+
+ synchronized (this) {
+ final boolean normalMode = mProcessesReady || isAllowedWhileBooting(app.info);
+ final String processName = app.processName;
+ boolean badApp = false;
+ boolean didSomething = false;
+
+ // See if the top visible activity is waiting to run in this process...
+ if (normalMode) {
+ try {
+ didSomething = mAtmInternal.attachApplication(app.getWindowProcessController());
+ } catch (Exception e) {
+ Slog.wtf(TAG, "Exception thrown launching activities in " + app, e);
+ badApp = true;
+ }
+ }
+
+ // Find any services that should be running in this process...
+ if (!badApp) {
+ try {
+ didSomething |= mServices.attachApplicationLocked(app, processName);
+ checkTime(startTime, "finishAttachApplicationInner: "
+ + "after mServices.attachApplicationLocked");
+ } catch (Exception e) {
+ Slog.wtf(TAG, "Exception thrown starting services in " + app, e);
+ badApp = true;
+ }
+ }
+
+ // Check if a next-broadcast receiver is in this process...
+ if (!badApp) {
+ try {
+ for (BroadcastQueue queue : mBroadcastQueues) {
+ didSomething |= queue.onApplicationAttachedLocked(app);
+ }
+ checkTime(startTime, "finishAttachApplicationInner: "
+ + "after dispatching broadcasts");
+ } catch (Exception e) {
+ // If the app died trying to launch the receiver we declare it 'bad'
+ Slog.wtf(TAG, "Exception thrown dispatching broadcasts in " + app, e);
+ badApp = true;
+ }
+ }
+
+ // Check whether the next backup agent is in this process...
+ final BackupRecord backupTarget = mBackupTargets.get(app.userId);
+ if (!badApp && backupTarget != null && backupTarget.app == app) {
+ if (DEBUG_BACKUP) {
+ Slog.v(TAG_BACKUP,
+ "New app is backup target, launching agent for " + app);
+ }
+
+ notifyPackageUse(backupTarget.appInfo.packageName,
+ PackageManager.NOTIFY_PACKAGE_USE_BACKUP);
+ try {
+ app.getThread().scheduleCreateBackupAgent(backupTarget.appInfo,
+ backupTarget.backupMode, backupTarget.userId,
+ backupTarget.backupDestination);
+ } catch (Exception e) {
+ Slog.wtf(TAG, "Exception thrown creating backup agent in " + app, e);
+ badApp = true;
+ }
+ }
+
+ if (badApp) {
+ app.killLocked("error during init",
+ ApplicationExitInfo.REASON_INITIALIZATION_FAILURE, true);
+ handleAppDiedLocked(app, pid, false, true, false /* fromBinderDied */);
+ return;
+ }
+
+ if (!didSomething) {
+ updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_PROCESS_BEGIN);
+ checkTime(startTime, "finishAttachApplicationInner: after updateOomAdjLocked");
+ }
+
+ final HostingRecord hostingRecord = app.getHostingRecord();
+ final String shortAction = getShortAction(hostingRecord.getAction());
+ FrameworkStatsLog.write(
+ FrameworkStatsLog.PROCESS_START_TIME,
+ app.info.uid,
+ pid,
+ app.info.packageName,
+ FrameworkStatsLog.PROCESS_START_TIME__TYPE__COLD,
+ app.getStartElapsedTime(),
+ (int) (app.getBindApplicationTime() - app.getStartUptime()),
+ (int) (SystemClock.uptimeMillis() - app.getStartUptime()),
+ hostingRecord.getType(),
+ hostingRecord.getName(),
+ shortAction,
+ HostingRecord.getHostingTypeIdStatsd(hostingRecord.getType()),
+ HostingRecord.getTriggerTypeForStatsd(hostingRecord.getTriggerType()));
+ }
+ }
+
+ @Override
+ public final void finishAttachApplication(long startSeq) {
+ final int pid = Binder.getCallingPid();
+ final int uid = Binder.getCallingUid();
+
+ if (pid == MY_PID && uid == SYSTEM_UID) {
+ return;
+ }
+
+ final long origId = Binder.clearCallingIdentity();
+ try {
+ finishAttachApplicationInner(startSeq, uid, pid);
+ } finally {
+ Binder.restoreCallingIdentity(origId);
+ }
+ }
+
/**
* @return The last part of the string of an intent's action.
*/
@@ -18805,6 +18829,21 @@
return new ProcessList();
}
+ /**
+ * Returns the {@link BatteryStatsService} instance
+ */
+ public BatteryStatsService getBatteryStatsService() {
+ return new BatteryStatsService(mContext, SystemServiceManager.ensureSystemDir(),
+ BackgroundThread.get().getHandler());
+ }
+
+ /**
+ * Returns the {@link ActiveServices} instance
+ */
+ public ActiveServices getActiveServices(ActivityManagerService service) {
+ return new ActiveServices(service);
+ }
+
private boolean ensureHasNetworkManagementInternal() {
if (mNmi == null) {
mNmi = LocalServices.getService(NetworkManagementInternal.class);
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index ecea96e..937bbc9c 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -2508,7 +2508,7 @@
}
@GuardedBy("mService")
- private String isProcStartValidLocked(ProcessRecord app, long expectedStartSeq) {
+ String isProcStartValidLocked(ProcessRecord app, long expectedStartSeq) {
StringBuilder sb = null;
if (app.isKilledByAm()) {
if (sb == null) sb = new StringBuilder();
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index 0a8c640..4706c268 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -200,6 +200,11 @@
private volatile long mStartElapsedTime;
/**
+ * When the process was sent the bindApplication request
+ */
+ private volatile long mBindApplicationTime;
+
+ /**
* This will be same as {@link #uid} usually except for some apps used during factory testing.
*/
private volatile int mStartUid;
@@ -739,6 +744,10 @@
return mStartElapsedTime;
}
+ long getBindApplicationTime() {
+ return mBindApplicationTime;
+ }
+
int getStartUid() {
return mStartUid;
}
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 4d86140..2ea49b3 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -100,7 +100,6 @@
import android.util.IntArray;
import android.util.Pair;
import android.util.SparseArray;
-import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
import android.util.proto.ProtoOutputStream;
import android.view.Display;
@@ -177,7 +176,8 @@
static final int START_USER_SWITCH_FG_MSG = 120;
static final int COMPLETE_USER_SWITCH_MSG = 130;
static final int USER_COMPLETED_EVENT_MSG = 140;
- static final int USER_VISIBILITY_CHANGED_MSG = 150;
+
+ private static final int NO_ARG2 = 0;
// Message constant to clear {@link UserJourneySession} from {@link mUserIdToUserJourneyMap} if
// the user journey, defined in the UserLifecycleJourneyReported atom for statsd, is not
@@ -437,20 +437,6 @@
/** @see #getLastUserUnlockingUptime */
private volatile long mLastUserUnlockingUptime = 0;
- // TODO(b/244333150) remove this array and let UserVisibilityMediator call the listeners
- // directly, as that class should be responsible for all user visibility logic (for example,
- // when the foreground user is switched out, its profiles also become invisible)
- /**
- * List of visible users (as defined by {@link UserManager#isUserVisible()}).
- *
- * <p>It's only used to call {@link UserManagerInternal} when the visibility is changed upon
- * the user starting or stopping.
- *
- * <p>Note: only the key is used, not the value.
- */
- @GuardedBy("mLock")
- private final SparseBooleanArray mVisibleUsers = new SparseBooleanArray();
-
private final UserLifecycleListener mUserLifecycleListener = new UserLifecycleListener() {
@Override
public void onUserCreated(UserInfo user, Object token) {
@@ -1092,24 +1078,11 @@
// instead.
userManagerInternal.unassignUserFromDisplayOnStop(userId);
- final boolean visibilityChanged;
- boolean visibleBefore;
- synchronized (mLock) {
- visibleBefore = mVisibleUsers.get(userId);
- if (visibleBefore) {
- deleteVisibleUserLocked(userId);
- visibilityChanged = true;
- } else {
- visibilityChanged = false;
- }
- }
-
updateStartedUserArrayLU();
final boolean allowDelayedLockingCopied = allowDelayedLocking;
Runnable finishUserStoppingAsync = () ->
- mHandler.post(() -> finishUserStopping(userId, uss, allowDelayedLockingCopied,
- visibilityChanged));
+ mHandler.post(() -> finishUserStopping(userId, uss, allowDelayedLockingCopied));
if (mInjector.getUserManager().isPreCreated(userId)) {
finishUserStoppingAsync.run();
@@ -1146,22 +1119,8 @@
}
}
- private void addVisibleUserLocked(@UserIdInt int userId) {
- if (DEBUG_MU) {
- Slogf.d(TAG, "adding %d to mVisibleUsers", userId);
- }
- mVisibleUsers.put(userId, true);
- }
-
- private void deleteVisibleUserLocked(@UserIdInt int userId) {
- if (DEBUG_MU) {
- Slogf.d(TAG, "deleting %d from mVisibleUsers", userId);
- }
- mVisibleUsers.delete(userId);
- }
-
private void finishUserStopping(final int userId, final UserState uss,
- final boolean allowDelayedLocking, final boolean visibilityChanged) {
+ final boolean allowDelayedLocking) {
EventLog.writeEvent(EventLogTags.UC_FINISH_USER_STOPPING, userId);
synchronized (mLock) {
if (uss.state != UserState.STATE_STOPPING) {
@@ -1179,9 +1138,6 @@
BatteryStats.HistoryItem.EVENT_USER_RUNNING_FINISH,
Integer.toString(userId), userId);
mInjector.getSystemServiceManager().onUserStopping(userId);
- if (visibilityChanged) {
- mInjector.onUserVisibilityChanged(userId, /* visible= */ false);
- }
Runnable finishUserStoppedAsync = () ->
mHandler.post(() -> finishUserStopped(uss, allowDelayedLocking));
@@ -1655,25 +1611,13 @@
userInfo.profileGroupId, foreground, displayId);
t.traceEnd();
- boolean visible;
- switch (result) {
- case UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE:
- visible = true;
- break;
- case UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE:
- visible = false;
- break;
- default:
- Slogf.wtf(TAG, "Wrong result from assignUserToDisplayOnStart(): %d", result);
- // Fall through
- case UserManagerInternal.USER_ASSIGNMENT_RESULT_FAILURE:
- Slogf.e(TAG, "%s user(%d) / display (%d) assignment failed: %s",
- (foreground ? "fg" : "bg"), userId, displayId,
- UserManagerInternal.userAssignmentResultToString(result));
- return false;
+ if (result == UserManagerInternal.USER_ASSIGNMENT_RESULT_FAILURE) {
+ Slogf.e(TAG, "%s user(%d) / display (%d) assignment failed: %s",
+ (foreground ? "fg" : "bg"), userId, displayId,
+ UserManagerInternal.userAssignmentResultToString(result));
+ return false;
}
-
// TODO(b/239982558): might need something similar for bg users on secondary display
if (foreground && isUserSwitchUiEnabled()) {
t.traceBegin("startFreezingScreen");
@@ -1724,15 +1668,7 @@
// Make sure the old user is no longer considering the display to be on.
mInjector.reportGlobalUsageEvent(UsageEvents.Event.SCREEN_NON_INTERACTIVE);
boolean userSwitchUiEnabled;
- // TODO(b/244333150): temporary state until the callback logic is moved to
- // UserVisibilityManager
- int previousCurrentUserId; boolean notifyPreviousCurrentUserId;
synchronized (mLock) {
- previousCurrentUserId = mCurrentUserId;
- notifyPreviousCurrentUserId = mVisibleUsers.get(previousCurrentUserId);
- if (notifyPreviousCurrentUserId) {
- deleteVisibleUserLocked(previousCurrentUserId);
- }
mCurrentUserId = userId;
mTargetUserId = UserHandle.USER_NULL; // reset, mCurrentUserId has caught up
userSwitchUiEnabled = mUserSwitchUiEnabled;
@@ -1753,10 +1689,6 @@
mInjector.getWindowManager().lockNow(null);
}
}
- if (notifyPreviousCurrentUserId) {
- mHandler.sendMessage(mHandler.obtainMessage(USER_VISIBILITY_CHANGED_MSG,
- previousCurrentUserId, 0));
- }
} else {
final Integer currentUserIdInt = mCurrentUserId;
@@ -1768,12 +1700,6 @@
}
t.traceEnd();
- if (visible) {
- synchronized (mLock) {
- addVisibleUserLocked(userId);
- }
- }
-
// Make sure user is in the started state. If it is currently
// stopping, we need to knock that off.
if (uss.state == UserState.STATE_STOPPING) {
@@ -1810,20 +1736,10 @@
// Booting up a new user, need to tell system services about it.
// Note that this is on the same handler as scheduling of broadcasts,
// which is important because it needs to go first.
- mHandler.sendMessage(mHandler.obtainMessage(USER_START_MSG, userId,
- visible ? 1 : 0));
+ mHandler.sendMessage(mHandler.obtainMessage(USER_START_MSG, userId, NO_ARG2));
t.traceEnd();
}
- if (visible) {
- // User was already running and became visible (for example, when switching to a
- // user that was started in the background before), so it's necessary to explicitly
- // notify the services (while when the user starts from BOOTING, USER_START_MSG
- // takes care of that.
- mHandler.sendMessage(
- mHandler.obtainMessage(USER_VISIBILITY_CHANGED_MSG, userId, 1));
- }
-
t.traceBegin("sendMessages");
if (foreground) {
mHandler.sendMessage(mHandler.obtainMessage(USER_CURRENT_MSG, userId, oldUserId));
@@ -2248,11 +2164,6 @@
uss.switching = false;
stopGuestOrEphemeralUserIfBackground(oldUserId);
stopUserOnSwitchIfEnforced(oldUserId);
- if (oldUserId == UserHandle.USER_SYSTEM) {
- // System user is never stopped, but its visibility is changed (as it is brought to the
- // background)
- updateSystemUserVisibility(t, /* visible= */ false);
- }
t.traceEnd(); // end continueUserSwitch
}
@@ -2614,27 +2525,10 @@
// Don't need to call on HSUM because it will be called when the system user is
// restarted on background
mInjector.onUserStarting(UserHandle.USER_SYSTEM);
- mInjector.onUserVisibilityChanged(UserHandle.USER_SYSTEM, /* visible= */ true);
+ mInjector.onSystemUserVisibilityChanged(/* visible= */ true);
}
}
- private void updateSystemUserVisibility(TimingsTraceAndSlog t, boolean visible) {
- t.traceBegin("update-system-userVisibility-" + visible);
- if (DEBUG_MU) {
- Slogf.d(TAG, "updateSystemUserVisibility(): visible=%b", visible);
- }
- int userId = UserHandle.USER_SYSTEM;
- synchronized (mLock) {
- if (visible) {
- addVisibleUserLocked(userId);
- } else {
- deleteVisibleUserLocked(userId);
- }
- }
- mInjector.onUserVisibilityChanged(userId, visible);
- t.traceEnd();
- }
-
/**
* Refreshes the internal caches related to user profiles.
*
@@ -3032,9 +2926,6 @@
proto.end(uToken);
}
}
- for (int i = 0; i < mVisibleUsers.size(); i++) {
- proto.write(UserControllerProto.VISIBLE_USERS_ARRAY, mVisibleUsers.keyAt(i));
- }
proto.write(UserControllerProto.CURRENT_USER, mCurrentUserId);
for (int i = 0; i < mCurrentProfileIds.length; i++) {
proto.write(UserControllerProto.CURRENT_PROFILES, mCurrentProfileIds[i]);
@@ -3094,7 +2985,6 @@
pw.println(" mSwitchingToSystemUserMessage: " + mSwitchingToSystemUserMessage);
}
pw.println(" mLastUserUnlockingUptime: " + mLastUserUnlockingUptime);
- pw.println(" mVisibleUsers: " + mVisibleUsers);
}
}
@@ -3212,10 +3102,6 @@
case COMPLETE_USER_SWITCH_MSG:
completeUserSwitch(msg.arg1);
break;
- case USER_VISIBILITY_CHANGED_MSG:
- mInjector.onUserVisibilityChanged(/* userId= */ msg.arg1,
- /* visible= */ msg.arg2 == 1);
- break;
}
return false;
}
@@ -3750,8 +3636,8 @@
getSystemServiceManager().onUserStarting(TimingsTraceAndSlog.newAsyncLog(), userId);
}
- void onUserVisibilityChanged(@UserIdInt int userId, boolean visible) {
- getUserManagerInternal().onUserVisibilityChanged(userId, visible);
+ void onSystemUserVisibilityChanged(boolean visible) {
+ getUserManagerInternal().onSystemUserVisibilityChanged(visible);
}
}
}
diff --git a/services/core/java/com/android/server/pm/UserManagerInternal.java b/services/core/java/com/android/server/pm/UserManagerInternal.java
index df132a9..0f920c6 100644
--- a/services/core/java/com/android/server/pm/UserManagerInternal.java
+++ b/services/core/java/com/android/server/pm/UserManagerInternal.java
@@ -435,8 +435,10 @@
/** Removes a {@link UserVisibilityListener}. */
public abstract void removeUserVisibilityListener(UserVisibilityListener listener);
- /** TODO(b/244333150): temporary method until UserVisibilityMediator handles that logic */
- public abstract void onUserVisibilityChanged(@UserIdInt int userId, boolean visible);
+ // TODO(b/242195409): remove this method if not needed anymore
+ /** Notify {@link UserVisibilityListener listeners} that the visibility of the
+ * {@link android.os.UserHandle#USER_SYSTEM} changed. */
+ public abstract void onSystemUserVisibilityChanged(boolean visible);
/** Return the integer types of the given user IDs. Only used for reporting metrics to statsd.
*/
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index b5c1206..3234e87 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -97,7 +97,6 @@
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.AtomicFile;
-import android.util.EventLog;
import android.util.IndentingPrintWriter;
import android.util.IntArray;
import android.util.Slog;
@@ -126,7 +125,6 @@
import com.android.server.LocalServices;
import com.android.server.LockGuard;
import com.android.server.SystemService;
-import com.android.server.am.EventLogTags;
import com.android.server.am.UserState;
import com.android.server.pm.UserManagerInternal.UserLifecycleListener;
import com.android.server.pm.UserManagerInternal.UserRestrictionsListener;
@@ -191,6 +189,7 @@
private static final String ATTR_CREATION_TIME = "created";
private static final String ATTR_LAST_LOGGED_IN_TIME = "lastLoggedIn";
private static final String ATTR_LAST_LOGGED_IN_FINGERPRINT = "lastLoggedInFingerprint";
+ private static final String ATTR_LAST_ENTERED_FOREGROUND_TIME = "lastEnteredForeground";
private static final String ATTR_SERIAL_NO = "serialNumber";
private static final String ATTR_NEXT_SERIAL_NO = "nextSerialNumber";
private static final String ATTR_PARTIAL = "partial";
@@ -341,6 +340,9 @@
/** Elapsed realtime since boot when the user was unlocked. */
long unlockRealtime;
+ /** Wall clock time in millis when the user last entered the foreground. */
+ long mLastEnteredForegroundTimeMillis;
+
private long mLastRequestQuietModeEnabledMillis;
/**
@@ -508,10 +510,6 @@
@GuardedBy("mUserLifecycleListeners")
private final ArrayList<UserLifecycleListener> mUserLifecycleListeners = new ArrayList<>();
- // TODO(b/244333150): temporary array, should belong to UserVisibilityMediator
- @GuardedBy("mUserVisibilityListeners")
- private final ArrayList<UserVisibilityListener> mUserVisibilityListeners = new ArrayList<>();
-
private final LockPatternUtils mLockPatternUtils;
private final String ACTION_DISABLE_QUIET_MODE_AFTER_UNLOCK =
@@ -680,6 +678,10 @@
final UserData user = mUms.getUserDataLU(targetUser.getUserIdentifier());
if (user != null) {
user.startRealtime = SystemClock.elapsedRealtime();
+ if (targetUser.getUserIdentifier() == UserHandle.USER_SYSTEM
+ && targetUser.isFull()) {
+ mUms.setLastEnteredForegroundTimeToNow(user);
+ }
}
}
}
@@ -695,6 +697,16 @@
}
@Override
+ public void onUserSwitching(@NonNull TargetUser from, @NonNull TargetUser to) {
+ synchronized (mUms.mUsersLock) {
+ final UserData user = mUms.getUserDataLU(to.getUserIdentifier());
+ if (user != null) {
+ mUms.setLastEnteredForegroundTimeToNow(user);
+ }
+ }
+ }
+
+ @Override
public void onUserStopping(@NonNull TargetUser targetUser) {
synchronized (mUms.mUsersLock) {
final UserData user = mUms.getUserDataLU(targetUser.getUserIdentifier());
@@ -920,6 +932,30 @@
return UserHandle.USER_NULL;
}
+ @Override
+ public int getPreviousFullUserToEnterForeground() {
+ checkQueryOrCreateUsersPermission("get previous user");
+ int previousUser = UserHandle.USER_NULL;
+ long latestEnteredTime = 0;
+ final int currentUser = getCurrentUserId();
+ synchronized (mUsersLock) {
+ final int userSize = mUsers.size();
+ for (int i = 0; i < userSize; i++) {
+ final UserData userData = mUsers.valueAt(i);
+ final int userId = userData.info.id;
+ if (userId != currentUser && userData.info.isFull() && !userData.info.partial
+ && !mRemovingUserIds.get(userId)) {
+ final long userEnteredTime = userData.mLastEnteredForegroundTimeMillis;
+ if (userEnteredTime > latestEnteredTime) {
+ latestEnteredTime = userEnteredTime;
+ previousUser = userId;
+ }
+ }
+ }
+ }
+ return previousUser;
+ }
+
public @NonNull List<UserInfo> getUsers(boolean excludeDying) {
return getUsers(/*excludePartial= */ true, excludeDying, /* excludePreCreated= */
true);
@@ -3973,6 +4009,8 @@
serializer.attribute(null, ATTR_LAST_LOGGED_IN_FINGERPRINT,
userInfo.lastLoggedInFingerprint);
}
+ serializer.attributeLong(
+ null, ATTR_LAST_ENTERED_FOREGROUND_TIME, userData.mLastEnteredForegroundTimeMillis);
if (userInfo.iconPath != null) {
serializer.attribute(null, ATTR_ICON_PATH, userInfo.iconPath);
}
@@ -4146,6 +4184,7 @@
long lastLoggedInTime = 0L;
long lastRequestQuietModeEnabledTimestamp = 0L;
String lastLoggedInFingerprint = null;
+ long lastEnteredForegroundTime = 0L;
int profileGroupId = UserInfo.NO_PROFILE_GROUP_ID;
int profileBadge = 0;
int restrictedProfileParentId = UserInfo.NO_PROFILE_GROUP_ID;
@@ -4191,6 +4230,8 @@
lastLoggedInTime = parser.getAttributeLong(null, ATTR_LAST_LOGGED_IN_TIME, 0);
lastLoggedInFingerprint = parser.getAttributeValue(null,
ATTR_LAST_LOGGED_IN_FINGERPRINT);
+ lastEnteredForegroundTime =
+ parser.getAttributeLong(null, ATTR_LAST_ENTERED_FOREGROUND_TIME, 0L);
profileGroupId = parser.getAttributeInt(null, ATTR_PROFILE_GROUP_ID,
UserInfo.NO_PROFILE_GROUP_ID);
profileBadge = parser.getAttributeInt(null, ATTR_PROFILE_BADGE, 0);
@@ -4285,6 +4326,7 @@
userData.seedAccountOptions = seedAccountOptions;
userData.userProperties = userProperties;
userData.setLastRequestQuietModeEnabledMillis(lastRequestQuietModeEnabledTimestamp);
+ userData.mLastEnteredForegroundTimeMillis = lastEnteredForegroundTime;
if (ignorePrepareStorageErrors) {
userData.setIgnorePrepareStorageErrors();
}
@@ -6204,6 +6246,11 @@
|| someUserHasSeedAccountNoChecks(accountName, accountType));
}
+ private void setLastEnteredForegroundTimeToNow(@NonNull UserData userData) {
+ userData.mLastEnteredForegroundTimeMillis = System.currentTimeMillis();
+ scheduleWriteUser(userData);
+ }
+
@Override
public void onShellCommand(FileDescriptor in, FileDescriptor out,
FileDescriptor err, String[] args, ShellCallback callback,
@@ -6330,9 +6377,6 @@
synchronized (mUserLifecycleListeners) {
pw.println(" user lifecycle events: " + mUserLifecycleListeners.size());
}
- synchronized (mUserVisibilityListeners) {
- pw.println(" user visibility events: " + mUserVisibilityListeners.size());
- }
// Dump UserTypes
pw.println();
@@ -6428,6 +6472,9 @@
pw.print(" Unlock time: ");
dumpTimeAgo(pw, tempStringBuilder, nowRealtime, userData.unlockRealtime);
+ pw.print(" Last entered foreground: ");
+ dumpTimeAgo(pw, tempStringBuilder, now, userData.mLastEnteredForegroundTimeMillis);
+
pw.print(" Has profile owner: ");
pw.println(mIsUserManaged.get(userId));
pw.println(" Restrictions:");
@@ -6905,31 +6952,17 @@
@Override
public void addUserVisibilityListener(UserVisibilityListener listener) {
- synchronized (mUserVisibilityListeners) {
- mUserVisibilityListeners.add(listener);
- }
+ mUserVisibilityMediator.addListener(listener);
}
@Override
public void removeUserVisibilityListener(UserVisibilityListener listener) {
- synchronized (mUserVisibilityListeners) {
- mUserVisibilityListeners.remove(listener);
- }
+ mUserVisibilityMediator.removeListener(listener);
}
@Override
- public void onUserVisibilityChanged(@UserIdInt int userId, boolean visible) {
- EventLog.writeEvent(EventLogTags.UM_USER_VISIBILITY_CHANGED, userId, visible ? 1 : 0);
- mHandler.post(() -> {
- UserVisibilityListener[] listeners;
- synchronized (mUserVisibilityListeners) {
- listeners = new UserVisibilityListener[mUserVisibilityListeners.size()];
- mUserVisibilityListeners.toArray(listeners);
- }
- for (UserVisibilityListener listener : listeners) {
- listener.onUserVisibilityChanged(userId, visible);
- }
- });
+ public void onSystemUserVisibilityChanged(boolean visible) {
+ mUserVisibilityMediator.onSystemUserVisibilityChanged(visible);
}
@Override
diff --git a/services/core/java/com/android/server/pm/UserVisibilityMediator.java b/services/core/java/com/android/server/pm/UserVisibilityMediator.java
index 878855a..9b9ca10 100644
--- a/services/core/java/com/android/server/pm/UserVisibilityMediator.java
+++ b/services/core/java/com/android/server/pm/UserVisibilityMediator.java
@@ -32,6 +32,7 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.util.Dumpable;
+import android.util.EventLog;
import android.util.IndentingPrintWriter;
import android.util.IntArray;
import android.util.SparseIntArray;
@@ -40,6 +41,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;
+import com.android.server.am.EventLogTags;
import com.android.server.pm.UserManagerInternal.UserAssignmentResult;
import com.android.server.pm.UserManagerInternal.UserVisibilityListener;
import com.android.server.utils.Slogf;
@@ -68,6 +70,7 @@
public final class UserVisibilityMediator implements Dumpable {
private static final boolean DBG = false; // DO NOT SUBMIT WITH TRUE
+ private static final boolean VERBOSE = false; // DO NOT SUBMIT WITH TRUE
private static final String TAG = UserVisibilityMediator.class.getSimpleName();
@@ -381,8 +384,8 @@
public boolean isUserVisible(@UserIdInt int userId) {
// First check current foreground user and their profiles (on main display)
if (isCurrentUserOrRunningProfileOfCurrentUser(userId)) {
- if (DBG) {
- Slogf.d(TAG, "isUserVisible(%d): true to current user or profile", userId);
+ if (VERBOSE) {
+ Slogf.v(TAG, "isUserVisible(%d): true to current user or profile", userId);
}
return true;
}
@@ -517,6 +520,14 @@
}
}
+ // TODO(b/242195409): remove this method if not needed anymore
+ /**
+ * Nofify all listeners that the system user visibility changed.
+ */
+ void onSystemUserVisibilityChanged(boolean visible) {
+ dispatchVisibilityChanged(mListeners, USER_SYSTEM, visible);
+ }
+
/**
* Nofify all listeners about the visibility changes from before / after a change of state.
*/
@@ -534,7 +545,7 @@
Slogf.d(TAG,
"dispatchVisibilityChanged(): visibleUsersBefore=%s, visibleUsersAfter=%s, "
+ "%d listeners (%s)", visibleUsersBefore, visibleUsersAfter, listeners.size(),
- mListeners);
+ listeners);
}
for (int i = 0; i < visibleUsersBefore.size(); i++) {
int userId = visibleUsersBefore.get(i);
@@ -552,13 +563,14 @@
private void dispatchVisibilityChanged(CopyOnWriteArrayList<UserVisibilityListener> listeners,
@UserIdInt int userId, boolean visible) {
+ EventLog.writeEvent(EventLogTags.UM_USER_VISIBILITY_CHANGED, userId, visible ? 1 : 0);
if (DBG) {
Slogf.d(TAG, "dispatchVisibilityChanged(%d -> %b): sending to %d listeners",
userId, visible, listeners.size());
}
for (int i = 0; i < mListeners.size(); i++) {
UserVisibilityListener listener = mListeners.get(i);
- if (DBG) {
+ if (VERBOSE) {
Slogf.v(TAG, "dispatchVisibilityChanged(%d -> %b): sending to %s",
userId, visible, listener);
}
diff --git a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
index d7c5e93..719f72c 100644
--- a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
+++ b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
@@ -399,8 +399,11 @@
* @return The intercepting intent if needed.
*/
private Intent interceptWithConfirmCredentialsIfNeeded(ActivityInfo aInfo, int userId) {
+ if (!mService.mAmInternal.shouldConfirmCredentials(userId)) {
+ return null;
+ }
if ((aInfo.flags & ActivityInfo.FLAG_SHOW_WHEN_LOCKED) != 0
- || !mService.mAmInternal.shouldConfirmCredentials(userId)) {
+ && (mUserManager.isUserUnlocked(userId) || aInfo.directBootAware)) {
return null;
}
final IntentSender target = createIntentSenderForOriginalIntent(mCallingUid,
diff --git a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
index 332a75e..8854453 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
@@ -25,7 +25,6 @@
import android.credentials.ui.CreateCredentialProviderData;
import android.credentials.ui.Entry;
import android.credentials.ui.ProviderPendingIntentResponse;
-import android.os.Bundle;
import android.service.credentials.BeginCreateCredentialRequest;
import android.service.credentials.BeginCreateCredentialResponse;
import android.service.credentials.CreateCredentialRequest;
@@ -68,12 +67,11 @@
createRequestSession.mClientRequest,
createRequestSession.mClientCallingPackage);
if (providerCreateRequest != null) {
- // TODO : Replace with proper splitting of request
BeginCreateCredentialRequest providerBeginCreateRequest =
new BeginCreateCredentialRequest(
providerCreateRequest.getCallingPackage(),
providerCreateRequest.getType(),
- new Bundle());
+ createRequestSession.mClientRequest.getCandidateQueryData());
return new ProviderCreateSession(context, providerInfo, createRequestSession, userId,
remoteCredentialService, providerBeginCreateRequest, providerCreateRequest);
}
@@ -88,7 +86,7 @@
String capability = clientRequest.getType();
if (providerCapabilities.contains(capability)) {
return new CreateCredentialRequest(clientCallingPackage, capability,
- clientRequest.getData());
+ clientRequest.getCredentialData());
}
Log.i(TAG, "Unable to create provider request - capabilities do not match");
return null;
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java b/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java
new file mode 100644
index 0000000..ea14ffb
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java
@@ -0,0 +1,282 @@
+/*
+ * Copyright (C) 2022 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.am;
+
+import static android.os.Process.myPid;
+import static android.os.Process.myUid;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+import android.app.ActivityManagerInternal;
+import android.app.IApplicationThread;
+import android.app.usage.UsageStatsManagerInternal;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManagerInternal;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.SystemClock;
+import android.util.Log;
+
+import androidx.test.filters.MediumTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.server.DropBoxManagerInternal;
+import com.android.server.LocalServices;
+import com.android.server.am.ActivityManagerService.Injector;
+import com.android.server.appop.AppOpsService;
+import com.android.server.wm.ActivityTaskManagerInternal;
+import com.android.server.wm.ActivityTaskManagerService;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.File;
+import java.util.Arrays;
+
+
+/**
+ * Tests to verify process starts are completed or timeout correctly
+ */
+@MediumTest
+@SuppressWarnings("GuardedBy")
+public class AsyncProcessStartTest {
+ private static final String TAG = "AsyncProcessStartTest";
+
+ private static final String PACKAGE = "com.foo";
+
+ @Rule
+ public final ApplicationExitInfoTest.ServiceThreadRule
+ mServiceThreadRule = new ApplicationExitInfoTest.ServiceThreadRule();
+
+ private Context mContext;
+ private HandlerThread mHandlerThread;
+
+ @Mock
+ private AppOpsService mAppOpsService;
+ @Mock
+ private DropBoxManagerInternal mDropBoxManagerInt;
+ @Mock
+ private PackageManagerInternal mPackageManagerInt;
+ @Mock
+ private UsageStatsManagerInternal mUsageStatsManagerInt;
+ @Mock
+ private ActivityManagerInternal mActivityManagerInt;
+ @Mock
+ private ActivityTaskManagerInternal mActivityTaskManagerInt;
+ @Mock
+ private BatteryStatsService mBatteryStatsService;
+
+ private ActivityManagerService mRealAms;
+ private ActivityManagerService mAms;
+
+ private ProcessList mRealProcessList = new ProcessList();
+ private ProcessList mProcessList;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+
+ mHandlerThread = new HandlerThread(TAG);
+ mHandlerThread.start();
+
+ LocalServices.removeServiceForTest(DropBoxManagerInternal.class);
+ LocalServices.addService(DropBoxManagerInternal.class, mDropBoxManagerInt);
+
+ LocalServices.removeServiceForTest(PackageManagerInternal.class);
+ LocalServices.addService(PackageManagerInternal.class, mPackageManagerInt);
+
+ LocalServices.removeServiceForTest(ActivityManagerInternal.class);
+ LocalServices.addService(ActivityManagerInternal.class, mActivityManagerInt);
+
+ LocalServices.removeServiceForTest(ActivityTaskManagerInternal.class);
+ LocalServices.addService(ActivityTaskManagerInternal.class, mActivityTaskManagerInt);
+
+ doReturn(new ComponentName("", "")).when(mPackageManagerInt).getSystemUiServiceComponent();
+ doReturn(true).when(mActivityTaskManagerInt).attachApplication(any());
+ doNothing().when(mActivityTaskManagerInt).onProcessMapped(anyInt(), any());
+
+ mRealAms = new ActivityManagerService(
+ new TestInjector(mContext), mServiceThreadRule.getThread());
+ mRealAms.mActivityTaskManager = new ActivityTaskManagerService(mContext);
+ mRealAms.mActivityTaskManager.initialize(null, null, mContext.getMainLooper());
+ mRealAms.mAtmInternal = mActivityTaskManagerInt;
+ mRealAms.mPackageManagerInt = mPackageManagerInt;
+ mRealAms.mUsageStatsService = mUsageStatsManagerInt;
+ mRealAms.mProcessesReady = true;
+ mAms = spy(mRealAms);
+ mRealProcessList.mService = mAms;
+ mProcessList = spy(mRealProcessList);
+
+ doAnswer((invocation) -> {
+ Log.v(TAG, "Intercepting isProcStartValidLocked() for "
+ + Arrays.toString(invocation.getArguments()));
+ return null;
+ }).when(mProcessList).isProcStartValidLocked(any(), anyLong());
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ mHandlerThread.quit();
+ }
+
+ private class TestInjector extends Injector {
+ TestInjector(Context context) {
+ super(context);
+ }
+
+ @Override
+ public AppOpsService getAppOpsService(File file, Handler handler) {
+ return mAppOpsService;
+ }
+
+ @Override
+ public Handler getUiHandler(ActivityManagerService service) {
+ return mHandlerThread.getThreadHandler();
+ }
+
+ @Override
+ public ProcessList getProcessList(ActivityManagerService service) {
+ return mRealProcessList;
+ }
+
+ @Override
+ public BatteryStatsService getBatteryStatsService() {
+ return mBatteryStatsService;
+ }
+ }
+
+ private ProcessRecord makeActiveProcessRecord(String packageName, boolean wedge)
+ throws Exception {
+ final ApplicationInfo ai = makeApplicationInfo(packageName);
+ return makeActiveProcessRecord(ai, wedge);
+ }
+
+ private ProcessRecord makeActiveProcessRecord(ApplicationInfo ai, boolean wedge)
+ throws Exception {
+ final IApplicationThread thread = mock(IApplicationThread.class);
+ final IBinder threadBinder = new Binder();
+ doReturn(threadBinder).when(thread).asBinder();
+ doAnswer((invocation) -> {
+ Log.v(TAG, "Intercepting bindApplication() for "
+ + Arrays.toString(invocation.getArguments()));
+ if (!wedge) {
+ mRealAms.finishAttachApplication(0);
+ }
+ return null;
+ }).when(thread).bindApplication(
+ any(), any(),
+ any(), any(),
+ any(), any(),
+ any(), any(),
+ any(),
+ any(), anyInt(),
+ anyBoolean(), anyBoolean(),
+ anyBoolean(), anyBoolean(), any(),
+ any(), any(), any(),
+ any(), any(),
+ any(), any(),
+ any(),
+ anyLong(), anyLong());
+
+ final ProcessRecord r = spy(new ProcessRecord(mAms, ai, ai.processName, ai.uid));
+ r.setPid(myPid());
+ r.setStartUid(myUid());
+ r.setHostingRecord(new HostingRecord(HostingRecord.HOSTING_TYPE_BROADCAST));
+ r.makeActive(thread, mAms.mProcessStats);
+ doNothing().when(r).killLocked(any(), any(), anyInt(), anyInt(), anyBoolean(),
+ anyBoolean());
+
+ return r;
+ }
+
+ static ApplicationInfo makeApplicationInfo(String packageName) {
+ final ApplicationInfo ai = new ApplicationInfo();
+ ai.packageName = packageName;
+ ai.processName = packageName;
+ ai.uid = myUid();
+ return ai;
+ }
+
+ /**
+ * Verify that we don't kill a normal process
+ */
+ @Test
+ public void testNormal() throws Exception {
+ ProcessRecord app = startProcessAndWait(false);
+
+ verify(app, never()).killLocked(any(), anyInt(), anyBoolean());
+ }
+
+ /**
+ * Verify that we kill a wedged process after the process start timeout
+ */
+ @Test
+ public void testWedged() throws Exception {
+ ProcessRecord app = startProcessAndWait(true);
+
+ verify(app).killLocked(any(), anyInt(), anyBoolean());
+ }
+
+ private ProcessRecord startProcessAndWait(boolean wedge) throws Exception {
+ final ProcessRecord app = makeActiveProcessRecord(PACKAGE, wedge);
+ final ApplicationInfo appInfo = makeApplicationInfo(PACKAGE);
+
+ mProcessList.handleProcessStartedLocked(app, app.getPid(), /* usingWrapper */ false,
+ /* expectedStartSeq */ 0, /* procAttached */ false);
+
+ app.getThread().bindApplication(PACKAGE, appInfo,
+ null, null,
+ null,
+ null,
+ null, null,
+ null,
+ null, 0,
+ false, false,
+ true, false,
+ null,
+ null, null,
+ null,
+ null, null, null,
+ null, null,
+ 0, 0);
+
+ // Sleep until timeout should have triggered
+ SystemClock.sleep(ActivityManagerService.PROC_START_TIMEOUT + 1000);
+
+ return app;
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/AsyncUserVisibilityListener.java b/services/tests/mockingservicestests/src/com/android/server/pm/AsyncUserVisibilityListener.java
index a97491d..ddfbf16 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/AsyncUserVisibilityListener.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/AsyncUserVisibilityListener.java
@@ -30,7 +30,6 @@
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
-
/**
* {@link UserVisibilityListener} implementation that expects callback events to be asynchronously
* received.
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java
index c203831..4487d13 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java
@@ -329,6 +329,15 @@
listener.verify();
}
+ @Test
+ public final void testOnSystemUserVisibilityChanged() throws Exception {
+ AsyncUserVisibilityListener listener = addListenerForEvents(onVisible(USER_SYSTEM));
+
+ mMediator.onSystemUserVisibilityChanged(/* visible= */ true);
+
+ listener.verify();
+ }
+
/**
* Starts a user in foreground on the default display, asserting it was properly started.
*
diff --git a/services/tests/servicestests/src/com/android/server/DockObserverTest.java b/services/tests/servicestests/src/com/android/server/DockObserverTest.java
index c325778..ee09074 100644
--- a/services/tests/servicestests/src/com/android/server/DockObserverTest.java
+++ b/services/tests/servicestests/src/com/android/server/DockObserverTest.java
@@ -20,6 +20,7 @@
import android.content.Intent;
import android.os.Looper;
+import android.provider.Settings;
import android.testing.AndroidTestingRunner;
import android.testing.TestableContext;
import android.testing.TestableLooper;
@@ -74,6 +75,11 @@
.isEqualTo(Intent.EXTRA_DOCK_STATE_UNDOCKED);
}
+ void setDeviceProvisioned(boolean provisioned) {
+ Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.DEVICE_PROVISIONED,
+ provisioned ? 1 : 0);
+ }
+
@Before
public void setUp() {
if (Looper.myLooper() == null) {
@@ -131,4 +137,25 @@
assertDockEventIntentWithExtraThenUndock(observer, "DOCK=1\nKEY5=5",
Intent.EXTRA_DOCK_STATE_HE_DESK);
}
+
+ @Test
+ public void testDockIntentBroadcast_deviceNotProvisioned()
+ throws ExecutionException, InterruptedException {
+ DockObserver observer = new DockObserver(mInterceptingContext);
+ // Set the device as not provisioned.
+ setDeviceProvisioned(false);
+ observer.onBootPhase(SystemService.PHASE_ACTIVITY_MANAGER_READY);
+
+ BroadcastInterceptingContext.FutureIntent futureIntent =
+ updateExtconDockState(observer, "DOCK=1");
+ TestableLooper.get(this).processAllMessages();
+ // Verify no broadcast was sent as device was not provisioned.
+ futureIntent.assertNotReceived();
+
+ // Ensure we send the broadcast when the device is provisioned.
+ setDeviceProvisioned(true);
+ TestableLooper.get(this).processAllMessages();
+ assertThat(futureIntent.get().getIntExtra(Intent.EXTRA_DOCK_STATE, -1))
+ .isEqualTo(Intent.EXTRA_DOCK_STATE_DESK);
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
index a49214f..e8b8253 100644
--- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -38,9 +38,6 @@
import static com.android.server.am.UserController.USER_CURRENT_MSG;
import static com.android.server.am.UserController.USER_START_MSG;
import static com.android.server.am.UserController.USER_SWITCH_TIMEOUT_MSG;
-import static com.android.server.am.UserController.USER_VISIBILITY_CHANGED_MSG;
-import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE;
-import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE;
import static com.google.android.collect.Lists.newArrayList;
import static com.google.android.collect.Sets.newHashSet;
@@ -102,7 +99,6 @@
import com.android.server.SystemService;
import com.android.server.am.UserState.KeyEvictedCallback;
import com.android.server.pm.UserManagerInternal;
-import com.android.server.pm.UserManagerInternal.UserAssignmentResult;
import com.android.server.pm.UserManagerService;
import com.android.server.wm.WindowManagerService;
@@ -162,18 +158,12 @@
REPORT_USER_SWITCH_MSG,
USER_SWITCH_TIMEOUT_MSG,
USER_START_MSG,
- USER_VISIBILITY_CHANGED_MSG,
USER_CURRENT_MSG);
- private static final Set<Integer> START_INVISIBLE_BACKGROUND_USER_MESSAGE_CODES = newHashSet(
+ private static final Set<Integer> START_BACKGROUND_USER_MESSAGE_CODES = newHashSet(
USER_START_MSG,
REPORT_LOCKED_BOOT_COMPLETE_MSG);
- private static final Set<Integer> START_VISIBLE_BACKGROUND_USER_MESSAGE_CODES = newHashSet(
- USER_START_MSG,
- USER_VISIBILITY_CHANGED_MSG,
- REPORT_LOCKED_BOOT_COMPLETE_MSG);
-
@Before
public void setUp() throws Exception {
runWithDexmakerShareClassLoader(() -> {
@@ -225,14 +215,12 @@
@Test
public void testStartUser_background() {
- mockAssignUserToMainDisplay(TEST_USER_ID, /* foreground= */ false,
- USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE);
boolean started = mUserController.startUser(TEST_USER_ID, /* foreground= */ false);
assertWithMessage("startUser(%s, foreground=false)", TEST_USER_ID).that(started).isTrue();
verify(mInjector.getWindowManager(), never()).startFreezingScreen(anyInt(), anyInt());
verify(mInjector.getWindowManager(), never()).setSwitchingUser(anyBoolean());
verify(mInjector, never()).clearAllLockedTasks(anyString());
- startBackgroundUserAssertions(/*visible= */ false);
+ startBackgroundUserAssertions();
verifyUserAssignedToDisplay(TEST_USER_ID, Display.DEFAULT_DISPLAY);
}
@@ -267,7 +255,7 @@
verify(mInjector.getWindowManager(), never()).startFreezingScreen(anyInt(), anyInt());
verify(mInjector.getWindowManager(), never()).setSwitchingUser(anyBoolean());
verify(mInjector, never()).clearAllLockedTasks(anyString());
- startBackgroundUserAssertions(/*visible= */ true);
+ startBackgroundUserAssertions();
}
@Test
@@ -293,8 +281,6 @@
@Test
public void testStartPreCreatedUser_background() throws Exception {
- mockAssignUserToMainDisplay(TEST_PRE_CREATED_USER_ID, /* foreground= */ false,
- USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE);
assertTrue(mUserController.startUser(TEST_PRE_CREATED_USER_ID, /* foreground= */ false));
// Make sure no intents have been fired for pre-created users.
assertTrue(mInjector.mSentIntents.isEmpty());
@@ -322,10 +308,8 @@
assertEquals("Unexpected message sent", expectedMessageCodes, actualCodes);
}
- private void startBackgroundUserAssertions(boolean visible) {
- startUserAssertions(START_BACKGROUND_USER_ACTIONS,
- visible ? START_VISIBLE_BACKGROUND_USER_MESSAGE_CODES
- : START_INVISIBLE_BACKGROUND_USER_MESSAGE_CODES);
+ private void startBackgroundUserAssertions() {
+ startUserAssertions(START_BACKGROUND_USER_ACTIONS, START_BACKGROUND_USER_MESSAGE_CODES);
}
private void startForegroundUserAssertions() {
@@ -433,7 +417,7 @@
verify(mInjector, times(0)).dismissKeyguard(any(), anyString());
verify(mInjector.getWindowManager(), times(1)).stopFreezingScreen();
continueUserSwitchAssertions(TEST_USER_ID, false);
- verifySystemUserVisibilityChangedNotified(/* visible= */ false);
+ verifySystemUserVisibilityChangesNeverNotified();
}
@Test
@@ -454,7 +438,7 @@
verify(mInjector, times(1)).dismissKeyguard(any(), anyString());
verify(mInjector.getWindowManager(), times(1)).stopFreezingScreen();
continueUserSwitchAssertions(TEST_USER_ID, false);
- verifySystemUserVisibilityChangedNotified(/* visible= */ false);
+ verifySystemUserVisibilityChangesNeverNotified();
}
@Test
@@ -561,7 +545,7 @@
assertFalse(mUserController.canStartMoreUsers());
assertEquals(Arrays.asList(new Integer[] {0, TEST_USER_ID1, TEST_USER_ID2}),
mUserController.getRunningUsersLU());
- verifySystemUserVisibilityChangedNotified(/* visible= */ false);
+ verifySystemUserVisibilityChangesNeverNotified();
}
/**
@@ -709,24 +693,19 @@
@Test
public void testStartProfile() throws Exception {
- mockAssignUserToMainDisplay(TEST_PRE_CREATED_USER_ID, /* foreground= */ false,
- USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE);
setUpAndStartProfileInBackground(TEST_USER_ID1);
- startBackgroundUserAssertions(/*visible= */ true);
+ startBackgroundUserAssertions();
verifyUserAssignedToDisplay(TEST_USER_ID1, Display.DEFAULT_DISPLAY);
}
@Test
public void testStartProfile_whenUsersOnSecondaryDisplaysIsEnabled() throws Exception {
- mockAssignUserToMainDisplay(TEST_USER_ID1, /* foreground= */ false,
- USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE);
-
mockIsUsersOnSecondaryDisplaysEnabled(true);
setUpAndStartProfileInBackground(TEST_USER_ID1);
- startBackgroundUserAssertions(/*visible= */ true);
+ startBackgroundUserAssertions();
verifyUserAssignedToDisplay(TEST_USER_ID1, Display.DEFAULT_DISPLAY);
}
@@ -983,13 +962,6 @@
when(mInjector.isUsersOnSecondaryDisplaysEnabled()).thenReturn(value);
}
- private void mockAssignUserToMainDisplay(@UserIdInt int userId, boolean foreground,
- @UserAssignmentResult int result) {
- when(mInjector.mUserManagerInternalMock.assignUserToDisplayOnStart(eq(userId),
- /* profileGroupId= */ anyInt(), eq(foreground), eq(Display.DEFAULT_DISPLAY)))
- .thenReturn(result);
- }
-
private void verifyUserAssignedToDisplay(@UserIdInt int userId, int displayId) {
verify(mInjector.getUserManagerInternal()).assignUserToDisplayOnStart(eq(userId), anyInt(),
anyBoolean(), eq(displayId));
@@ -1008,8 +980,8 @@
verify(mInjector.getUserManagerInternal(), never()).unassignUserFromDisplayOnStop(userId);
}
- private void verifySystemUserVisibilityChangedNotified(boolean visible) {
- verify(mInjector).onUserVisibilityChanged(UserHandle.USER_SYSTEM, visible);
+ private void verifySystemUserVisibilityChangesNeverNotified() {
+ verify(mInjector, never()).onSystemUserVisibilityChanged(anyBoolean());
}
// Should be public to allow mocking
@@ -1154,8 +1126,8 @@
}
@Override
- void onUserVisibilityChanged(@UserIdInt int userId, boolean visible) {
- Log.i(TAG, "onUserVisibilityChanged(" + userId + ", " + visible + ")");
+ void onSystemUserVisibilityChanged(boolean visible) {
+ Log.i(TAG, "onSystemUserVisibilityChanged(" + visible + ")");
}
}
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
index afaee04..3e8a070 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -317,7 +317,7 @@
VirtualDeviceParams params = new VirtualDeviceParams
.Builder()
.setBlockedActivities(getBlockedActivities())
- .addDevicePolicy(POLICY_TYPE_SENSORS, DEVICE_POLICY_CUSTOM)
+ .setDevicePolicy(POLICY_TYPE_SENSORS, DEVICE_POLICY_CUSTOM)
.build();
mDeviceImpl = new VirtualDeviceImpl(mContext,
mAssociationInfo, new Binder(), /* ownerUid */ 0, VIRTUAL_DEVICE_ID,
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceParamsTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceParamsTest.java
index a226ebc..aefe4b6 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceParamsTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceParamsTest.java
@@ -46,7 +46,7 @@
VirtualDeviceParams originalParams = new VirtualDeviceParams.Builder()
.setLockState(VirtualDeviceParams.LOCK_STATE_ALWAYS_UNLOCKED)
.setUsersWithMatchingAccounts(Set.of(UserHandle.of(123), UserHandle.of(456)))
- .addDevicePolicy(POLICY_TYPE_SENSORS, DEVICE_POLICY_CUSTOM)
+ .setDevicePolicy(POLICY_TYPE_SENSORS, DEVICE_POLICY_CUSTOM)
.addVirtualSensorConfig(
new VirtualSensorConfig.Builder(TYPE_ACCELEROMETER, SENSOR_NAME)
.setVendor(SENSOR_VENDOR)
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryJobServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryJobServiceTest.java
index af10b9d..d758e71 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryJobServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryJobServiceTest.java
@@ -52,9 +52,9 @@
@RunWith(AndroidTestingRunner.class)
public class NotificationHistoryJobServiceTest extends UiServiceTestCase {
private NotificationHistoryJobService mJobService;
- private JobParameters mJobParams = new JobParameters(null,
- NotificationHistoryJobService.BASE_JOB_ID, null, null, null,
- 0, false, false, null, null, null);
+
+ @Mock
+ private JobParameters mJobParams;
@Captor
ArgumentCaptor<JobInfo> mJobInfoCaptor;
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ReviewNotificationPermissionsJobServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ReviewNotificationPermissionsJobServiceTest.java
index 3a6c0eb..a83eb00 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ReviewNotificationPermissionsJobServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ReviewNotificationPermissionsJobServiceTest.java
@@ -44,9 +44,9 @@
@RunWith(AndroidTestingRunner.class)
public class ReviewNotificationPermissionsJobServiceTest extends UiServiceTestCase {
private ReviewNotificationPermissionsJobService mJobService;
- private JobParameters mJobParams = new JobParameters(null,
- ReviewNotificationPermissionsJobService.JOB_ID, null, null, null,
- 0, false, false, null, null, null);
+
+ @Mock
+ private JobParameters mJobParams;
@Captor
ArgumentCaptor<JobInfo> mJobInfoCaptor;
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java
index 1575336..8a15c30 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java
@@ -28,6 +28,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.nullable;
@@ -275,11 +276,60 @@
// THEN calling intercept returns true
mInterceptor.intercept(null, null, mAInfo, null, null, null, 0, 0, null);
- // THEN the returned intent is the quiet mode intent
+ // THEN the returned intent is the confirm credentials intent
assertTrue(CONFIRM_CREDENTIALS_INTENT.filterEquals(mInterceptor.mIntent));
}
@Test
+ public void testLockedManagedProfileShowWhenLocked() {
+ Intent originalIntent = new Intent();
+ // GIVEN that the user is locked but its storage is unlocked and the activity has
+ // showWhenLocked flag
+ when(mAmInternal.shouldConfirmCredentials(TEST_USER_ID)).thenReturn(true);
+ when(mUserManager.isUserUnlocked(eq(TEST_USER_ID))).thenReturn(true);
+ mAInfo.flags |= ActivityInfo.FLAG_SHOW_WHEN_LOCKED;
+
+ // THEN calling intercept returns true
+ mInterceptor.intercept(originalIntent, null, mAInfo, null, null, null, 0, 0, null);
+
+ // THEN the returned intent is original intent
+ assertSame(originalIntent, mInterceptor.mIntent);
+ }
+
+ @Test
+ public void testLockedManagedProfileShowWhenLockedEncryptedStorage() {
+ // GIVEN that the user storage is locked, activity has showWhenLocked flag but no
+ // directBootAware flag
+ when(mAmInternal.shouldConfirmCredentials(TEST_USER_ID)).thenReturn(true);
+ when(mUserManager.isUserUnlocked(eq(TEST_USER_ID))).thenReturn(false);
+ mAInfo.flags |= ActivityInfo.FLAG_SHOW_WHEN_LOCKED;
+ mAInfo.directBootAware = false;
+
+ // THEN calling intercept returns true
+ mInterceptor.intercept(null, null, mAInfo, null, null, null, 0, 0, null);
+
+ // THEN the returned intent is the confirm credentials intent
+ assertTrue(CONFIRM_CREDENTIALS_INTENT.filterEquals(mInterceptor.mIntent));
+ }
+
+ @Test
+ public void testLockedManagedProfileShowWhenLockedEncryptedStorageDirectBootAware() {
+ Intent originalIntent = new Intent();
+ // GIVEN that the user storage is locked, activity has showWhenLocked flag and
+ // directBootAware flag
+ when(mAmInternal.shouldConfirmCredentials(TEST_USER_ID)).thenReturn(true);
+ when(mUserManager.isUserUnlocked(eq(TEST_USER_ID))).thenReturn(false);
+ mAInfo.flags |= ActivityInfo.FLAG_SHOW_WHEN_LOCKED;
+ mAInfo.directBootAware = true;
+
+ // THEN calling intercept returns true
+ mInterceptor.intercept(originalIntent, null, mAInfo, null, null, null, 0, 0, null);
+
+ // THEN the returned intent is original intent
+ assertSame(originalIntent, mInterceptor.mIntent);
+ }
+
+ @Test
public void testHarmfulAppWarning() throws RemoteException {
// GIVEN the package we're about to launch has a harmful app warning set
when(mPackageManager.getHarmfulAppWarning(TEST_PACKAGE_NAME, TEST_USER_ID))
diff --git a/tools/aapt2/cmd/Optimize.cpp b/tools/aapt2/cmd/Optimize.cpp
index 9c1a2f6..d7a39bf 100644
--- a/tools/aapt2/cmd/Optimize.cpp
+++ b/tools/aapt2/cmd/Optimize.cpp
@@ -154,13 +154,22 @@
return 1;
}
- if (options_.shorten_resource_paths) {
- Obfuscator obfuscator(options_.table_flattener_options.shortened_path_map);
+ Obfuscator obfuscator(options_);
+ if (obfuscator.IsEnabled()) {
if (!obfuscator.Consume(context_, apk->GetResourceTable())) {
context_->GetDiagnostics()->Error(android::DiagMessage()
<< "failed shortening resource paths");
return 1;
}
+
+ if (options_.obfuscation_map_path &&
+ !obfuscator.WriteObfuscationMap(options_.obfuscation_map_path.value())) {
+ context_->GetDiagnostics()->Error(android::DiagMessage()
+ << "failed to write the obfuscation map to file");
+ return 1;
+ }
+
+ // TODO(b/246489170): keep the old option and format until transform to the new one
if (options_.shortened_paths_map_path
&& !WriteShortenedPathsMap(options_.table_flattener_options.shortened_path_map,
options_.shortened_paths_map_path.value())) {
@@ -292,6 +301,7 @@
ArchiveEntry::kAlign, writer);
}
+ // TODO(b/246489170): keep the old option and format until transform to the new one
bool WriteShortenedPathsMap(const std::map<std::string, std::string> &path_map,
const std::string &file_path) {
std::stringstream ss;
diff --git a/tools/aapt2/cmd/Optimize.h b/tools/aapt2/cmd/Optimize.h
index 794a87b..1879f25 100644
--- a/tools/aapt2/cmd/Optimize.h
+++ b/tools/aapt2/cmd/Optimize.h
@@ -58,6 +58,7 @@
bool shorten_resource_paths = false;
// Path to the output map of original resource paths to shortened paths.
+ // TODO(b/246489170): keep the old option and format until transform to the new one
std::optional<std::string> shortened_paths_map_path;
// Whether sparse encoding should be used for O+ resources.
@@ -65,6 +66,9 @@
// Whether sparse encoding should be used for all resources.
bool force_sparse_encoding = false;
+
+ // Path to the output map of original resource paths/names to obfuscated paths/names.
+ std::optional<std::string> obfuscation_map_path;
};
class OptimizeCommand : public Command {
@@ -120,9 +124,13 @@
AddOptionalSwitch("--shorten-resource-paths",
"Shortens the paths of resources inside the APK.",
&options_.shorten_resource_paths);
+ // TODO(b/246489170): keep the old option and format until transform to the new one
AddOptionalFlag("--resource-path-shortening-map",
- "Path to output the map of old resource paths to shortened paths.",
- &options_.shortened_paths_map_path);
+ "[Deprecated]Path to output the map of old resource paths to shortened paths.",
+ &options_.shortened_paths_map_path);
+ AddOptionalFlag("--save-obfuscation-map",
+ "Path to output the map of original paths/names to obfuscated paths/names.",
+ &options_.obfuscation_map_path);
AddOptionalSwitch(
"--deduplicate-entry-values",
"Whether to deduplicate pairs of resource entry and value for simple resources.\n"
diff --git a/tools/aapt2/format/binary/TableFlattener.cpp b/tools/aapt2/format/binary/TableFlattener.cpp
index f192234..8c594ba 100644
--- a/tools/aapt2/format/binary/TableFlattener.cpp
+++ b/tools/aapt2/format/binary/TableFlattener.cpp
@@ -32,6 +32,7 @@
#include "format/binary/ChunkWriter.h"
#include "format/binary/ResEntryWriter.h"
#include "format/binary/ResourceTypeExtensions.h"
+#include "optimize/Obfuscator.h"
#include "trace/TraceBuffer.h"
using namespace android;
@@ -466,9 +467,6 @@
// table.
std::map<ConfigDescription, std::vector<FlatEntry>> config_to_entry_list_map;
- // hardcoded string uses characters which make it an invalid resource name
- const std::string obfuscated_resource_name = "0_resource_name_obfuscated";
-
for (const ResourceTableEntryView& entry : type.entries) {
if (entry.staged_id) {
aliases_.insert(std::make_pair(
@@ -477,30 +475,31 @@
}
uint32_t local_key_index;
- ResourceName resource_name({}, type.named_type, entry.name);
- if (!collapse_key_stringpool_ ||
- name_collapse_exemptions_.find(resource_name) != name_collapse_exemptions_.end()) {
- local_key_index = (uint32_t)key_pool_.MakeRef(entry.name).index();
- } else {
- // resource isn't exempt from collapse, add it as obfuscated value
- if (entry.overlayable_item) {
+ auto onObfuscate = [this, &local_key_index, &entry](Obfuscator::Result obfuscatedResult,
+ const ResourceName& resource_name) {
+ if (obfuscatedResult == Obfuscator::Result::Keep_ExemptionList) {
+ local_key_index = (uint32_t)key_pool_.MakeRef(entry.name).index();
+ } else if (obfuscatedResult == Obfuscator::Result::Keep_Overlayable) {
// if the resource name of the specific entry is obfuscated and this
// entry is in the overlayable list, the overlay can't work on this
// overlayable at runtime because the name has been obfuscated in
// resources.arsc during flatten operation.
const OverlayableItem& item = entry.overlayable_item.value();
context_->GetDiagnostics()->Warn(android::DiagMessage(item.overlayable->source)
- << "The resource name of overlayable entry "
- << resource_name.to_string() << "'"
- << " shouldn't be obfuscated in resources.arsc");
+ << "The resource name of overlayable entry '"
+ << resource_name.to_string()
+ << "' shouldn't be obfuscated in resources.arsc");
local_key_index = (uint32_t)key_pool_.MakeRef(entry.name).index();
} else {
- // TODO(b/228192695): output the entry.name and Resource id to make
- // de-obfuscated possible.
- local_key_index = (uint32_t)key_pool_.MakeRef(obfuscated_resource_name).index();
+ local_key_index =
+ (uint32_t)key_pool_.MakeRef(Obfuscator::kObfuscatedResourceName).index();
}
- }
+ };
+
+ Obfuscator::ObfuscateResourceName(collapse_key_stringpool_, name_collapse_exemptions_,
+ type.named_type, entry, onObfuscate);
+
// Group values by configuration.
for (auto& config_value : entry.values) {
config_to_entry_list_map[config_value->config].push_back(
diff --git a/tools/aapt2/format/binary/TableFlattener.h b/tools/aapt2/format/binary/TableFlattener.h
index 35254ba..60605d2 100644
--- a/tools/aapt2/format/binary/TableFlattener.h
+++ b/tools/aapt2/format/binary/TableFlattener.h
@@ -14,8 +14,13 @@
* limitations under the License.
*/
-#ifndef AAPT_FORMAT_BINARY_TABLEFLATTENER_H
-#define AAPT_FORMAT_BINARY_TABLEFLATTENER_H
+#ifndef TOOLS_AAPT2_FORMAT_BINARY_TABLEFLATTENER_H_
+#define TOOLS_AAPT2_FORMAT_BINARY_TABLEFLATTENER_H_
+
+#include <map>
+#include <set>
+#include <string>
+#include <unordered_map>
#include "Resource.h"
#include "ResourceTable.h"
@@ -71,6 +76,9 @@
//
// This applies only to simple entries (entry->flags & ResTable_entry::FLAG_COMPLEX == 0).
bool deduplicate_entry_values = false;
+
+ // Map from original resource ids to obfuscated names.
+ std::unordered_map<uint32_t, std::string> id_resource_map;
};
class TableFlattener : public IResourceTableConsumer {
@@ -82,12 +90,12 @@
bool Consume(IAaptContext* context, ResourceTable* table) override;
private:
- DISALLOW_COPY_AND_ASSIGN(TableFlattener);
-
TableFlattenerOptions options_;
android::BigBuffer* buffer_;
+
+ DISALLOW_COPY_AND_ASSIGN(TableFlattener);
};
} // namespace aapt
-#endif /* AAPT_FORMAT_BINARY_TABLEFLATTENER_H */
+#endif // TOOLS_AAPT2_FORMAT_BINARY_TABLEFLATTENER_H_
diff --git a/tools/aapt2/format/proto/ProtoSerialize.cpp b/tools/aapt2/format/proto/ProtoSerialize.cpp
index a6d58fd..0e40124 100644
--- a/tools/aapt2/format/proto/ProtoSerialize.cpp
+++ b/tools/aapt2/format/proto/ProtoSerialize.cpp
@@ -18,6 +18,7 @@
#include "ValueVisitor.h"
#include "androidfw/BigBuffer.h"
+#include "optimize/Obfuscator.h"
using android::ConfigDescription;
@@ -366,21 +367,21 @@
}
pb_type->set_name(type.named_type.to_string());
- // hardcoded string uses characters which make it an invalid resource name
- static const char* obfuscated_resource_name = "0_resource_name_obfuscated";
for (const auto& entry : type.entries) {
pb::Entry* pb_entry = pb_type->add_entry();
if (entry.id) {
pb_entry->mutable_entry_id()->set_id(entry.id.value());
}
- ResourceName resource_name({}, type.named_type, entry.name);
- if (options.collapse_key_stringpool &&
- options.name_collapse_exemptions.find(resource_name) ==
- options.name_collapse_exemptions.end()) {
- pb_entry->set_name(obfuscated_resource_name);
- } else {
- pb_entry->set_name(entry.name);
- }
+ auto onObfuscate = [pb_entry, &entry](Obfuscator::Result obfuscatedResult,
+ const ResourceName& resource_name) {
+ pb_entry->set_name(obfuscatedResult == Obfuscator::Result::Obfuscated
+ ? Obfuscator::kObfuscatedResourceName
+ : entry.name);
+ };
+
+ Obfuscator::ObfuscateResourceName(options.collapse_key_stringpool,
+ options.name_collapse_exemptions, type.named_type, entry,
+ onObfuscate);
// Write the Visibility struct.
pb::Visibility* pb_visibility = pb_entry->mutable_visibility();
diff --git a/tools/aapt2/optimize/Obfuscator.cpp b/tools/aapt2/optimize/Obfuscator.cpp
index 1fdd728..cc21093 100644
--- a/tools/aapt2/optimize/Obfuscator.cpp
+++ b/tools/aapt2/optimize/Obfuscator.cpp
@@ -16,6 +16,8 @@
#include "optimize/Obfuscator.h"
+#include <fstream>
+#include <map>
#include <set>
#include <string>
#include <unordered_set>
@@ -32,7 +34,10 @@
namespace aapt {
-Obfuscator::Obfuscator(std::map<std::string, std::string>& path_map_out) : path_map_(path_map_out) {
+Obfuscator::Obfuscator(OptimizeOptions& optimizeOptions)
+ : options_(optimizeOptions.table_flattener_options),
+ shorten_resource_paths_(optimizeOptions.shorten_resource_paths),
+ collapse_key_stringpool_(optimizeOptions.table_flattener_options.collapse_key_stringpool) {
}
std::string ShortenFileName(android::StringPiece file_path, int output_length) {
@@ -77,7 +82,8 @@
}
};
-bool Obfuscator::Consume(IAaptContext* context, ResourceTable* table) {
+static bool HandleShortenFilePaths(ResourceTable* table,
+ std::map<std::string, std::string>& shortened_path_map) {
// used to detect collisions
std::unordered_set<std::string> shortened_paths;
std::set<FileReference*, PathComparator> file_refs;
@@ -109,10 +115,117 @@
shortened_path = GetShortenedPath(shortened_filename, extension, collision_count);
}
shortened_paths.insert(shortened_path);
- path_map_.insert({*file_ref->path, shortened_path});
+ shortened_path_map.insert({*file_ref->path, shortened_path});
file_ref->path = table->string_pool.MakeRef(shortened_path, file_ref->path.GetContext());
}
return true;
}
+void Obfuscator::ObfuscateResourceName(
+ const bool collapse_key_stringpool, const std::set<ResourceName>& name_collapse_exemptions,
+ const ResourceNamedType& type_name, const ResourceTableEntryView& entry,
+ const android::base::function_ref<void(Result obfuscatedResult, const ResourceName&)>
+ onObfuscate) {
+ ResourceName resource_name({}, type_name, entry.name);
+ if (!collapse_key_stringpool ||
+ name_collapse_exemptions.find(resource_name) != name_collapse_exemptions.end()) {
+ onObfuscate(Result::Keep_ExemptionList, resource_name);
+ } else {
+ // resource isn't exempt from collapse, add it as obfuscated value
+ if (entry.overlayable_item) {
+ // if the resource name of the specific entry is obfuscated and this
+ // entry is in the overlayable list, the overlay can't work on this
+ // overlayable at runtime because the name has been obfuscated in
+ // resources.arsc during flatten operation.
+ onObfuscate(Result::Keep_Overlayable, resource_name);
+ } else {
+ onObfuscate(Result::Obfuscated, resource_name);
+ }
+ }
+}
+
+static bool HandleCollapseKeyStringPool(
+ const ResourceTable* table, const bool collapse_key_string_pool,
+ const std::set<ResourceName>& name_collapse_exemptions,
+ std::unordered_map<uint32_t, std::string>& id_resource_map) {
+ if (!collapse_key_string_pool) {
+ return true;
+ }
+
+ int entryResId = 0;
+ auto onObfuscate = [&entryResId, &id_resource_map](const Obfuscator::Result obfuscatedResult,
+ const ResourceName& resource_name) {
+ if (obfuscatedResult == Obfuscator::Result::Obfuscated) {
+ id_resource_map.insert({entryResId, resource_name.entry});
+ }
+ };
+
+ for (auto& package : table->packages) {
+ for (auto& type : package->types) {
+ for (auto& entry : type->entries) {
+ if (!entry->id.has_value() || entry->name.empty()) {
+ continue;
+ }
+ entryResId = entry->id->id;
+ ResourceTableEntryView entry_view{
+ .name = entry->name,
+ .id = entry->id ? entry->id.value().entry_id() : (std::optional<uint16_t>)std::nullopt,
+ .visibility = entry->visibility,
+ .allow_new = entry->allow_new,
+ .overlayable_item = entry->overlayable_item,
+ .staged_id = entry->staged_id};
+
+ Obfuscator::ObfuscateResourceName(collapse_key_string_pool, name_collapse_exemptions,
+ type->named_type, entry_view, onObfuscate);
+ }
+ }
+ }
+
+ return true;
+}
+
+bool Obfuscator::Consume(IAaptContext* context, ResourceTable* table) {
+ HandleCollapseKeyStringPool(table, options_.collapse_key_stringpool,
+ options_.name_collapse_exemptions, options_.id_resource_map);
+ if (shorten_resource_paths_) {
+ return HandleShortenFilePaths(table, options_.shortened_path_map);
+ }
+ return true;
+}
+
+bool Obfuscator::WriteObfuscationMap(const std::string& file_path) const {
+ pb::ResourceMappings resourceMappings;
+ for (const auto& [id, name] : options_.id_resource_map) {
+ auto* collapsedNameMapping = resourceMappings.mutable_collapsed_names()->add_resource_names();
+ collapsedNameMapping->set_id(id);
+ collapsedNameMapping->set_name(name);
+ }
+
+ for (const auto& [original_path, shortened_path] : options_.shortened_path_map) {
+ auto* resource_path = resourceMappings.mutable_shortened_paths()->add_resource_paths();
+ resource_path->set_original_path(original_path);
+ resource_path->set_shortened_path(shortened_path);
+ }
+
+ { // RAII style, output the pb content to file and close fout in destructor
+ std::ofstream fout(file_path, std::ios::out | std::ios::trunc | std::ios::binary);
+ if (!fout.is_open()) {
+ return false;
+ }
+ return resourceMappings.SerializeToOstream(&fout);
+ }
+}
+
+/**
+ * Tell the optimizer whether it's needed to dump information for de-obfuscating.
+ *
+ * There are two conditions need to dump the information for de-obfuscating.
+ * * the option of shortening file paths is enabled.
+ * * the option of collapsing resource names is enabled.
+ * @return true if the information needed for de-obfuscating, otherwise false
+ */
+bool Obfuscator::IsEnabled() const {
+ return shorten_resource_paths_ || collapse_key_stringpool_;
+}
+
} // namespace aapt
diff --git a/tools/aapt2/optimize/Obfuscator.h b/tools/aapt2/optimize/Obfuscator.h
index 1ea32db..5ccf5438 100644
--- a/tools/aapt2/optimize/Obfuscator.h
+++ b/tools/aapt2/optimize/Obfuscator.h
@@ -17,10 +17,15 @@
#ifndef TOOLS_AAPT2_OPTIMIZE_OBFUSCATOR_H_
#define TOOLS_AAPT2_OPTIMIZE_OBFUSCATOR_H_
-#include <map>
+#include <set>
#include <string>
+#include "ResourceMetadata.pb.h"
+#include "ResourceTable.h"
+#include "android-base/function_ref.h"
#include "android-base/macros.h"
+#include "cmd/Optimize.h"
+#include "format/binary/TableFlattener.h"
#include "process/IResourceTableConsumer.h"
namespace aapt {
@@ -30,12 +35,28 @@
// Maps resources in the apk to shortened paths.
class Obfuscator : public IResourceTableConsumer {
public:
- explicit Obfuscator(std::map<std::string, std::string>& path_map_out);
+ explicit Obfuscator(OptimizeOptions& optimizeOptions);
bool Consume(IAaptContext* context, ResourceTable* table) override;
+ bool WriteObfuscationMap(const std::string& file_path) const;
+
+ bool IsEnabled() const;
+
+ enum class Result { Obfuscated, Keep_ExemptionList, Keep_Overlayable };
+
+ // hardcoded string uses characters which make it an invalid resource name
+ static constexpr char kObfuscatedResourceName[] = "0_resource_name_obfuscated";
+
+ static void ObfuscateResourceName(
+ const bool collapse_key_stringpool, const std::set<ResourceName>& name_collapse_exemptions,
+ const ResourceNamedType& type_name, const ResourceTableEntryView& entry,
+ const android::base::function_ref<void(Result, const ResourceName&)> onObfuscate);
+
private:
- std::map<std::string, std::string>& path_map_;
+ TableFlattenerOptions& options_;
+ const bool shorten_resource_paths_;
+ const bool collapse_key_stringpool_;
DISALLOW_COPY_AND_ASSIGN(Obfuscator);
};
diff --git a/tools/aapt2/optimize/Obfuscator_test.cpp b/tools/aapt2/optimize/Obfuscator_test.cpp
index a3339d4..7f57b71 100644
--- a/tools/aapt2/optimize/Obfuscator_test.cpp
+++ b/tools/aapt2/optimize/Obfuscator_test.cpp
@@ -16,14 +16,19 @@
#include "optimize/Obfuscator.h"
+#include <map>
#include <memory>
#include <string>
#include "ResourceTable.h"
+#include "android-base/file.h"
#include "test/Test.h"
using ::aapt::test::GetValue;
+using ::testing::AnyOf;
using ::testing::Eq;
+using ::testing::HasSubstr;
+using ::testing::IsTrue;
using ::testing::Not;
using ::testing::NotNull;
@@ -51,8 +56,9 @@
.AddString("android:string/string", "res/should/still/be/the/same.png")
.Build();
- std::map<std::string, std::string> path_map;
- ASSERT_TRUE(Obfuscator(path_map).Consume(context.get(), table.get()));
+ OptimizeOptions options{.shorten_resource_paths = true};
+ std::map<std::string, std::string>& path_map = options.table_flattener_options.shortened_path_map;
+ ASSERT_TRUE(Obfuscator(options).Consume(context.get(), table.get()));
// Expect that the path map is populated
ASSERT_THAT(path_map.find("res/drawables/xmlfile.xml"), Not(Eq(path_map.end())));
@@ -87,8 +93,9 @@
test::ParseConfigOrDie("mdp-v21"))
.Build();
- std::map<std::string, std::string> path_map;
- ASSERT_TRUE(Obfuscator(path_map).Consume(context.get(), table.get()));
+ OptimizeOptions options{.shorten_resource_paths = true};
+ std::map<std::string, std::string>& path_map = options.table_flattener_options.shortened_path_map;
+ ASSERT_TRUE(Obfuscator(options).Consume(context.get(), table.get()));
// Expect that the path map to not contain the ColorStateList
ASSERT_THAT(path_map.find("res/color/colorlist.xml"), Eq(path_map.end()));
@@ -107,8 +114,9 @@
.AddFileReference("android:color/pngfile", original_png_path)
.Build();
- std::map<std::string, std::string> path_map;
- ASSERT_TRUE(Obfuscator(path_map).Consume(context.get(), table.get()));
+ OptimizeOptions options{.shorten_resource_paths = true};
+ std::map<std::string, std::string>& path_map = options.table_flattener_options.shortened_path_map;
+ ASSERT_TRUE(Obfuscator(options).Consume(context.get(), table.get()));
// Expect that the path map is populated
ASSERT_THAT(path_map.find("res/drawable/xmlfile.xml"), Not(Eq(path_map.end())));
@@ -133,8 +141,10 @@
test::ResourceTableBuilder builder1;
FillTable(builder1, 0, kNumResources);
std::unique_ptr<ResourceTable> table1 = builder1.Build();
- std::map<std::string, std::string> expected_mapping;
- ASSERT_TRUE(Obfuscator(expected_mapping).Consume(context.get(), table1.get()));
+ OptimizeOptions options{.shorten_resource_paths = true};
+ std::map<std::string, std::string>& expected_mapping =
+ options.table_flattener_options.shortened_path_map;
+ ASSERT_TRUE(Obfuscator(options).Consume(context.get(), table1.get()));
// We are trying to ensure lack of non-determinism, it is not simple to prove
// a negative, thus we must try the test a few times so that the test itself
@@ -153,8 +163,10 @@
FillTable(builder2, 0, start_index);
std::unique_ptr<ResourceTable> table2 = builder2.Build();
- std::map<std::string, std::string> actual_mapping;
- ASSERT_TRUE(Obfuscator(actual_mapping).Consume(context.get(), table2.get()));
+ OptimizeOptions actualOptimizerOptions{.shorten_resource_paths = true};
+ TableFlattenerOptions& actual_options = actualOptimizerOptions.table_flattener_options;
+ std::map<std::string, std::string>& actual_mapping = actual_options.shortened_path_map;
+ ASSERT_TRUE(Obfuscator(actualOptimizerOptions).Consume(context.get(), table2.get()));
for (auto& item : actual_mapping) {
ASSERT_THAT(expected_mapping[item.first], Eq(item.second));
@@ -162,4 +174,126 @@
}
}
+TEST(ObfuscatorTest, DumpIdResourceMap) {
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
+
+ OverlayableItem overlayable_item(std::make_shared<Overlayable>("TestName", "overlay://theme"));
+ overlayable_item.policies |= PolicyFlags::PRODUCT_PARTITION;
+ overlayable_item.policies |= PolicyFlags::SYSTEM_PARTITION;
+ overlayable_item.policies |= PolicyFlags::VENDOR_PARTITION;
+
+ std::string original_xml_path = "res/drawable/xmlfile.xml";
+ std::string original_png_path = "res/drawable/pngfile.png";
+
+ std::string name = "com.app.test:string/overlayable";
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .AddFileReference("android:color/xmlfile", original_xml_path)
+ .AddFileReference("android:color/pngfile", original_png_path)
+ .AddValue("com.app.test:color/mycolor", aapt::ResourceId(0x7f020000),
+ aapt::util::make_unique<aapt::BinaryPrimitive>(
+ uint8_t(android::Res_value::TYPE_INT_COLOR_ARGB8), 0xffaabbcc))
+ .AddString("com.app.test:string/mystring", ResourceId(0x7f030000), "hi")
+ .AddString("com.app.test:string/in_exemption", ResourceId(0x7f030001), "Hi")
+ .AddString(name, ResourceId(0x7f030002), "HI")
+ .SetOverlayable(name, overlayable_item)
+ .Build();
+
+ OptimizeOptions options{.shorten_resource_paths = true};
+ TableFlattenerOptions& flattenerOptions = options.table_flattener_options;
+ flattenerOptions.collapse_key_stringpool = true;
+ flattenerOptions.name_collapse_exemptions.insert(
+ ResourceName({}, ResourceType::kString, "in_exemption"));
+ auto& id_resource_map = flattenerOptions.id_resource_map;
+ ASSERT_TRUE(Obfuscator(options).Consume(context.get(), table.get()));
+
+ // Expect that the id resource name map is populated
+ EXPECT_THAT(id_resource_map.at(0x7f020000), Eq("mycolor"));
+ EXPECT_THAT(id_resource_map.at(0x7f030000), Eq("mystring"));
+ EXPECT_THAT(id_resource_map.find(0x7f030001), Eq(id_resource_map.end()));
+ EXPECT_THAT(id_resource_map.find(0x7f030002), Eq(id_resource_map.end()));
+}
+
+TEST(ObfuscatorTest, IsEnabledWithDefaultOption) {
+ OptimizeOptions options;
+ Obfuscator obfuscatorWithDefaultOption(options);
+ ASSERT_THAT(obfuscatorWithDefaultOption.IsEnabled(), Eq(false));
+}
+
+TEST(ObfuscatorTest, IsEnabledWithShortenPathOption) {
+ OptimizeOptions options{.shorten_resource_paths = true};
+ Obfuscator obfuscatorWithShortenPathOption(options);
+ ASSERT_THAT(obfuscatorWithShortenPathOption.IsEnabled(), Eq(true));
+}
+
+TEST(ObfuscatorTest, IsEnabledWithCollapseStringPoolOption) {
+ OptimizeOptions options;
+ options.table_flattener_options.collapse_key_stringpool = true;
+ Obfuscator obfuscatorWithCollapseStringPoolOption(options);
+ ASSERT_THAT(obfuscatorWithCollapseStringPoolOption.IsEnabled(), Eq(true));
+}
+
+TEST(ObfuscatorTest, IsEnabledWithShortenPathAndCollapseStringPoolOption) {
+ OptimizeOptions options{.shorten_resource_paths = true};
+ options.table_flattener_options.collapse_key_stringpool = true;
+ Obfuscator obfuscatorWithCollapseStringPoolOption(options);
+ ASSERT_THAT(obfuscatorWithCollapseStringPoolOption.IsEnabled(), Eq(true));
+}
+
+static std::unique_ptr<ResourceTable> getProtocolBufferTableUnderTest() {
+ std::string original_xml_path = "res/drawable/xmlfile.xml";
+ std::string original_png_path = "res/drawable/pngfile.png";
+
+ return test::ResourceTableBuilder()
+ .AddFileReference("com.app.test:drawable/xmlfile", original_xml_path)
+ .AddFileReference("com.app.test:drawable/pngfile", original_png_path)
+ .AddValue("com.app.test:color/mycolor", aapt::ResourceId(0x7f020000),
+ aapt::util::make_unique<aapt::BinaryPrimitive>(
+ uint8_t(android::Res_value::TYPE_INT_COLOR_ARGB8), 0xffaabbcc))
+ .AddString("com.app.test:string/mystring", ResourceId(0x7f030000), "hello world")
+ .Build();
+}
+
+TEST(ObfuscatorTest, WriteObfuscationMapInProtocolBufferFormat) {
+ OptimizeOptions options{.shorten_resource_paths = true};
+ options.table_flattener_options.collapse_key_stringpool = true;
+ Obfuscator obfuscator(options);
+ ASSERT_TRUE(obfuscator.Consume(test::ContextBuilder().Build().get(),
+ getProtocolBufferTableUnderTest().get()));
+
+ obfuscator.WriteObfuscationMap("obfuscated_map.pb");
+
+ std::string pbOut;
+ android::base::ReadFileToString("obfuscated_map.pb", &pbOut, false /* follow_symlinks */);
+ EXPECT_THAT(pbOut, HasSubstr("drawable/xmlfile.xml"));
+ EXPECT_THAT(pbOut, HasSubstr("drawable/pngfile.png"));
+ EXPECT_THAT(pbOut, HasSubstr("mycolor"));
+ EXPECT_THAT(pbOut, HasSubstr("mystring"));
+ pb::ResourceMappings resourceMappings;
+ EXPECT_THAT(resourceMappings.ParseFromString(pbOut), IsTrue());
+ EXPECT_THAT(resourceMappings.collapsed_names().resource_names_size(), Eq(2));
+ auto& resource_names = resourceMappings.collapsed_names().resource_names();
+ EXPECT_THAT(resource_names.at(0).name(), AnyOf(Eq("mycolor"), Eq("mystring")));
+ EXPECT_THAT(resource_names.at(1).name(), AnyOf(Eq("mycolor"), Eq("mystring")));
+ auto& shortened_paths = resourceMappings.shortened_paths();
+ EXPECT_THAT(shortened_paths.resource_paths_size(), Eq(2));
+ EXPECT_THAT(shortened_paths.resource_paths(0).original_path(),
+ AnyOf(Eq("res/drawable/pngfile.png"), Eq("res/drawable/xmlfile.xml")));
+ EXPECT_THAT(shortened_paths.resource_paths(1).original_path(),
+ AnyOf(Eq("res/drawable/pngfile.png"), Eq("res/drawable/xmlfile.xml")));
+}
+
+TEST(ObfuscatorTest, WriteObfuscatingMapWithNonEnabledOption) {
+ OptimizeOptions options;
+ Obfuscator obfuscator(options);
+ ASSERT_TRUE(obfuscator.Consume(test::ContextBuilder().Build().get(),
+ getProtocolBufferTableUnderTest().get()));
+
+ obfuscator.WriteObfuscationMap("obfuscated_map.pb");
+
+ std::string pbOut;
+ android::base::ReadFileToString("obfuscated_map.pb", &pbOut, false /* follow_symlinks */);
+ ASSERT_THAT(pbOut, Eq(""));
+}
+
} // namespace aapt