Merge "Make MyPackageMonitor#onPackageDataCleared multi-user aware" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 2047168..7317ecd 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -310,6 +310,8 @@
aconfig_declarations {
name: "android.os.flags-aconfig",
package: "android.os",
+ exportable: true,
+ container: "system",
srcs: ["core/java/android/os/*.aconfig"],
}
@@ -326,6 +328,13 @@
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
+java_aconfig_library {
+ name: "android.os.flags-aconfig-java-export",
+ aconfig_declarations: "android.os.flags-aconfig",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+ mode: "exported",
+}
+
// VirtualDeviceManager
cc_aconfig_library {
name: "android.companion.virtualdevice.flags-aconfig-cc",
@@ -483,6 +492,13 @@
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
+java_aconfig_library {
+ name: "android.content.res.flags-aconfig-java-host",
+ aconfig_declarations: "android.content.res.flags-aconfig",
+ host_supported: true,
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
// Media BetterTogether
aconfig_declarations {
name: "com.android.media.flags.bettertogether-aconfig",
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index a28dc49..6fb6b0d 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1366,7 +1366,7 @@
}
@FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") public class IntentFactory {
- method @NonNull public static android.content.Intent createCancelUiIntent(@NonNull android.os.IBinder, boolean, @NonNull String);
+ method @NonNull public static android.content.Intent createCancelUiIntent(@NonNull android.content.Context, @NonNull android.os.IBinder, boolean, @NonNull String);
method @NonNull public static android.content.Intent createCredentialSelectorIntent(@NonNull android.content.Context, @NonNull android.credentials.selection.RequestInfo, @NonNull java.util.ArrayList<android.credentials.selection.ProviderData>, @NonNull java.util.ArrayList<android.credentials.selection.DisabledProviderData>, @NonNull android.os.ResultReceiver);
}
@@ -1547,10 +1547,6 @@
method public boolean isAllowBackgroundAuthentication();
}
- public abstract static class BiometricPrompt.AuthenticationCallback {
- method @FlaggedApi("android.hardware.biometrics.face_background_authentication") public void onAuthenticationAcquired(int);
- }
-
public static class BiometricPrompt.Builder {
method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.TEST_BIOMETRIC, "android.permission.USE_BIOMETRIC_INTERNAL"}) public android.hardware.biometrics.BiometricPrompt.Builder setAllowBackgroundAuthentication(boolean);
method @FlaggedApi("android.multiuser.enable_biometrics_to_unlock_private_space") @NonNull @RequiresPermission(anyOf={android.Manifest.permission.TEST_BIOMETRIC, "android.permission.USE_BIOMETRIC_INTERNAL"}) public android.hardware.biometrics.BiometricPrompt.Builder setAllowBackgroundAuthentication(boolean, boolean);
@@ -1569,7 +1565,6 @@
}
public class SensorProperties {
- ctor @FlaggedApi("android.hardware.biometrics.face_background_authentication") public SensorProperties(int, int, @NonNull java.util.List<android.hardware.biometrics.SensorProperties.ComponentInfo>);
method @NonNull public java.util.List<android.hardware.biometrics.SensorProperties.ComponentInfo> getComponentInfo();
method public int getSensorId();
method public int getSensorStrength();
@@ -1714,18 +1709,6 @@
}
-package android.hardware.face {
-
- @FlaggedApi("android.hardware.biometrics.face_background_authentication") public class FaceManager {
- method @FlaggedApi("android.hardware.biometrics.face_background_authentication") @NonNull @RequiresPermission(android.Manifest.permission.TEST_BIOMETRIC) public android.hardware.biometrics.BiometricTestSession createTestSession(int);
- method @FlaggedApi("android.hardware.biometrics.face_background_authentication") @NonNull public java.util.List<android.hardware.face.FaceSensorProperties> getSensorProperties();
- }
-
- @FlaggedApi("android.hardware.biometrics.face_background_authentication") public class FaceSensorProperties extends android.hardware.biometrics.SensorProperties {
- }
-
-}
-
package android.hardware.fingerprint {
@Deprecated public class FingerprintManager {
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 41151c0..1d39186 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -40,6 +40,7 @@
import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
import static com.android.internal.os.SafeZipPathValidatorCallback.VALIDATE_ZIP_PATH_FOR_PATH_TRAVERSAL;
import static com.android.sdksandbox.flags.Flags.sandboxActivitySdkBasedContext;
+import static com.android.window.flags.Flags.activityWindowInfoFlag;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -63,6 +64,7 @@
import android.app.servertransaction.ActivityRelaunchItem;
import android.app.servertransaction.ActivityResultItem;
import android.app.servertransaction.ClientTransaction;
+import android.app.servertransaction.ClientTransactionListenerController;
import android.app.servertransaction.DestroyActivityItem;
import android.app.servertransaction.PauseActivityItem;
import android.app.servertransaction.PendingTransactionActions;
@@ -606,6 +608,8 @@
Configuration overrideConfig;
@NonNull
private ActivityWindowInfo mActivityWindowInfo;
+ @Nullable
+ private ActivityWindowInfo mLastReportedActivityWindowInfo;
// Used for consolidating configs before sending on to Activity.
private final Configuration tmpConfig = new Configuration();
@@ -4180,6 +4184,9 @@
pendingActions.setRestoreInstanceState(true);
pendingActions.setCallOnPostCreate(true);
}
+
+ // Trigger ActivityWindowInfo callback if first launch or change from relaunch.
+ handleActivityWindowInfoChanged(r);
} else {
// If there was an error, for any reason, tell the activity manager to stop us.
ActivityClient.getInstance().finishActivity(r.token, Activity.RESULT_CANCELED,
@@ -4558,7 +4565,7 @@
private void schedulePauseWithUserLeavingHint(ActivityClientRecord r) {
final ClientTransaction transaction = ClientTransaction.obtain(mAppThread);
final PauseActivityItem pauseActivityItem = PauseActivityItem.obtain(r.token,
- r.activity.isFinishing(), /* userLeaving */ true, r.activity.mConfigChangeFlags,
+ r.activity.isFinishing(), /* userLeaving */ true,
/* dontReport */ false, /* autoEnteringPip */ false);
transaction.addTransactionItem(pauseActivityItem);
executeTransaction(transaction);
@@ -5432,13 +5439,12 @@
@Override
public void handlePauseActivity(ActivityClientRecord r, boolean finished, boolean userLeaving,
- int configChanges, boolean autoEnteringPip, PendingTransactionActions pendingActions,
+ boolean autoEnteringPip, PendingTransactionActions pendingActions,
String reason) {
if (userLeaving) {
performUserLeavingActivity(r);
}
- r.activity.mConfigChangeFlags |= configChanges;
if (autoEnteringPip) {
// Set mIsInPictureInPictureMode earlier in case of auto-enter-pip, see also
// {@link Activity#enterPictureInPictureMode(PictureInPictureParams)}.
@@ -5687,9 +5693,8 @@
}
@Override
- public void handleStopActivity(ActivityClientRecord r, int configChanges,
+ public void handleStopActivity(ActivityClientRecord r,
PendingTransactionActions pendingActions, boolean finalStateRequest, String reason) {
- r.activity.mConfigChangeFlags |= configChanges;
final StopInfo stopInfo = new StopInfo();
performStopActivityInner(r, stopInfo, true /* saveState */, finalStateRequest,
@@ -5859,11 +5864,10 @@
/** Core implementation of activity destroy call. */
void performDestroyActivity(ActivityClientRecord r, boolean finishing,
- int configChanges, boolean getNonConfigInstance, String reason) {
+ boolean getNonConfigInstance, String reason) {
Class<? extends Activity> activityClass;
if (localLOGV) Slog.v(TAG, "Performing finish of " + r);
activityClass = r.activity.getClass();
- r.activity.mConfigChangeFlags |= configChanges;
if (finishing) {
r.activity.mFinished = true;
}
@@ -5928,9 +5932,9 @@
}
@Override
- public void handleDestroyActivity(ActivityClientRecord r, boolean finishing, int configChanges,
+ public void handleDestroyActivity(ActivityClientRecord r, boolean finishing,
boolean getNonConfigInstance, String reason) {
- performDestroyActivity(r, finishing, configChanges, getNonConfigInstance, reason);
+ performDestroyActivity(r, finishing, getNonConfigInstance, reason);
cleanUpPendingRemoveWindows(r, finishing);
WindowManager wm = r.activity.getWindowManager();
View v = r.activity.mDecor;
@@ -6130,7 +6134,7 @@
r.activity.mChangingConfigurations = true;
- handleRelaunchActivityInner(r, configChanges, tmp.pendingResults, tmp.pendingIntents,
+ handleRelaunchActivityInner(r, tmp.pendingResults, tmp.pendingIntents,
pendingActions, tmp.startsNotResumed, tmp.overrideConfig, tmp.mActivityWindowInfo,
"handleRelaunchActivity");
}
@@ -6199,7 +6203,7 @@
executeTransaction(transaction);
}
- private void handleRelaunchActivityInner(@NonNull ActivityClientRecord r, int configChanges,
+ private void handleRelaunchActivityInner(@NonNull ActivityClientRecord r,
@Nullable List<ResultInfo> pendingResults,
@Nullable List<ReferrerIntent> pendingIntents,
@NonNull PendingTransactionActions pendingActions, boolean startsNotResumed,
@@ -6215,7 +6219,7 @@
callActivityOnStop(r, true /* saveState */, reason);
}
- handleDestroyActivity(r, false, configChanges, true, reason);
+ handleDestroyActivity(r, false /* finishing */, true /* getNonConfigInstance */, reason);
r.activity = null;
r.window = null;
@@ -6740,7 +6744,7 @@
// Perform updates.
r.overrideConfig = overrideConfig;
r.mActivityWindowInfo = activityWindowInfo;
- // TODO(b/287582673): notify on ActivityWindowInfo change
+
final ViewRootImpl viewRoot = r.activity.mDecor != null
? r.activity.mDecor.getViewRootImpl() : null;
@@ -6763,6 +6767,22 @@
viewRoot.updateConfiguration(displayId);
}
mSomeActivitiesChanged = true;
+
+ // Trigger ActivityWindowInfo callback if changed.
+ handleActivityWindowInfoChanged(r);
+ }
+
+ private void handleActivityWindowInfoChanged(@NonNull ActivityClientRecord r) {
+ if (!activityWindowInfoFlag()) {
+ return;
+ }
+ if (r.mActivityWindowInfo == null
+ || r.mActivityWindowInfo.equals(r.mLastReportedActivityWindowInfo)) {
+ return;
+ }
+ r.mLastReportedActivityWindowInfo = r.mActivityWindowInfo;
+ ClientTransactionListenerController.getInstance().onActivityWindowInfoChanged(r.token,
+ r.mActivityWindowInfo);
}
final void handleProfilerControl(boolean start, ProfilerInfo profilerInfo, int profileType) {
diff --git a/core/java/android/app/ClientTransactionHandler.java b/core/java/android/app/ClientTransactionHandler.java
index b5b3669..01153c9 100644
--- a/core/java/android/app/ClientTransactionHandler.java
+++ b/core/java/android/app/ClientTransactionHandler.java
@@ -114,11 +114,11 @@
/** Destroy the activity. */
public abstract void handleDestroyActivity(@NonNull ActivityClientRecord r, boolean finishing,
- int configChanges, boolean getNonConfigInstance, String reason);
+ boolean getNonConfigInstance, String reason);
/** Pause the activity. */
public abstract void handlePauseActivity(@NonNull ActivityClientRecord r, boolean finished,
- boolean userLeaving, int configChanges, boolean autoEnteringPip,
+ boolean userLeaving, boolean autoEnteringPip,
PendingTransactionActions pendingActions, String reason);
/**
@@ -146,14 +146,13 @@
/**
* Stop the activity.
* @param r Target activity record.
- * @param configChanges Activity configuration changes.
* @param pendingActions Pending actions to be used on this or later stages of activity
* transaction.
* @param finalStateRequest Flag indicating if this call is handling final lifecycle state
* request for a transaction.
* @param reason Reason for performing this operation.
*/
- public abstract void handleStopActivity(@NonNull ActivityClientRecord r, int configChanges,
+ public abstract void handleStopActivity(@NonNull ActivityClientRecord r,
PendingTransactionActions pendingActions, boolean finalStateRequest, String reason);
/** Report that activity was stopped to server. */
diff --git a/core/java/android/app/LocalActivityManager.java b/core/java/android/app/LocalActivityManager.java
index 1b19ecd..095cfc5 100644
--- a/core/java/android/app/LocalActivityManager.java
+++ b/core/java/android/app/LocalActivityManager.java
@@ -413,7 +413,7 @@
if (localLOGV) Log.v(TAG, r.id + ": destroying");
final ActivityClientRecord clientRecord = mActivityThread.getActivityClient(r);
if (clientRecord != null) {
- mActivityThread.performDestroyActivity(clientRecord, finish, 0 /* configChanges */,
+ mActivityThread.performDestroyActivity(clientRecord, finish,
false /* getNonConfigInstance */, "LocalActivityManager::performDestroy");
}
r.activity = null;
@@ -684,7 +684,7 @@
if (localLOGV) Log.v(TAG, r.id + ": no corresponding record");
continue;
}
- mActivityThread.performDestroyActivity(clientRecord, finishing, 0 /* configChanges */,
+ mActivityThread.performDestroyActivity(clientRecord, finishing,
false /* getNonConfigInstance */, "LocalActivityManager::dispatchDestroy");
}
mActivities.clear();
diff --git a/core/java/android/app/LocaleConfig.java b/core/java/android/app/LocaleConfig.java
index b2be27f..4a06f7d 100644
--- a/core/java/android/app/LocaleConfig.java
+++ b/core/java/android/app/LocaleConfig.java
@@ -248,7 +248,8 @@
}
/**
- * Returns the default locale if specified, otherwise null
+ * Returns the locale the strings in values/strings.xml (the default strings in the directory
+ * with no locale qualifier) are in if specified, otherwise null
*
* @return The default Locale or null
*/
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index d49a254..283e21a 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -2052,10 +2052,12 @@
/** Notification senders to prioritize for calls. One of:
* PRIORITY_SENDERS_ANY, PRIORITY_SENDERS_CONTACTS, PRIORITY_SENDERS_STARRED */
+ @PrioritySenders
public final int priorityCallSenders;
/** Notification senders to prioritize for messages. One of:
* PRIORITY_SENDERS_ANY, PRIORITY_SENDERS_CONTACTS, PRIORITY_SENDERS_STARRED */
+ @PrioritySenders
public final int priorityMessageSenders;
/**
@@ -2063,6 +2065,7 @@
* {@link #CONVERSATION_SENDERS_NONE}, {@link #CONVERSATION_SENDERS_IMPORTANT},
* {@link #CONVERSATION_SENDERS_ANYONE}.
*/
+ @ConversationSenders
public final int priorityConversationSenders;
/**
@@ -2630,16 +2633,19 @@
}
/** @hide **/
+ @PrioritySenders
public int allowCallsFrom() {
return priorityCallSenders;
}
/** @hide **/
+ @PrioritySenders
public int allowMessagesFrom() {
return priorityMessageSenders;
}
/** @hide **/
+ @ConversationSenders
public int allowConversationsFrom() {
return priorityConversationSenders;
}
diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java
index 6255260..8b84f06 100644
--- a/core/java/android/app/ResourcesManager.java
+++ b/core/java/android/app/ResourcesManager.java
@@ -124,6 +124,32 @@
*/
private LocaleConfig mLocaleConfig = new LocaleConfig(LocaleList.getEmptyLocaleList());
+ private final ArrayMap<String, SharedLibraryAssets> mSharedLibAssetsMap =
+ new ArrayMap<>();
+
+ /**
+ * The internal function to register the resources paths of a package (e.g. a shared library).
+ * This will collect the package resources' paths from its ApplicationInfo and add them to all
+ * existing and future contexts while the application is running.
+ */
+ public void registerResourcePaths(@NonNull String uniqueId, @NonNull ApplicationInfo appInfo) {
+ SharedLibraryAssets sharedLibAssets = new SharedLibraryAssets(appInfo.sourceDir,
+ appInfo.splitSourceDirs, appInfo.sharedLibraryFiles,
+ appInfo.resourceDirs, appInfo.overlayPaths);
+
+ synchronized (mLock) {
+ if (mSharedLibAssetsMap.containsKey(uniqueId)) {
+ Slog.v(TAG, "Package resources' paths for uniqueId: " + uniqueId
+ + " has already been registered, this is a no-op.");
+ return;
+ }
+ mSharedLibAssetsMap.put(uniqueId, sharedLibAssets);
+ appendLibAssetsLocked(sharedLibAssets.getAllAssetPaths());
+ Slog.v(TAG, "The following resources' paths have been added: "
+ + Arrays.toString(sharedLibAssets.getAllAssetPaths()));
+ }
+ }
+
private static class ApkKey {
public final String path;
public final boolean sharedLib;
@@ -278,6 +304,21 @@
public ResourcesManager() {
}
+ /**
+ * Inject a customized ResourcesManager instance for testing, return the old ResourcesManager
+ * instance.
+ */
+ @UnsupportedAppUsage
+ @VisibleForTesting
+ public static ResourcesManager setInstance(ResourcesManager resourcesManager) {
+ synchronized (ResourcesManager.class) {
+ ResourcesManager oldResourceManager = sResourcesManager;
+ sResourcesManager = resourcesManager;
+ return oldResourceManager;
+ }
+
+ }
+
@UnsupportedAppUsage
public static ResourcesManager getInstance() {
synchronized (ResourcesManager.class) {
@@ -1480,6 +1521,56 @@
}
}
+ private void appendLibAssetsLocked(String[] libAssets) {
+ synchronized (mLock) {
+ // Record which ResourcesImpl need updating
+ // (and what ResourcesKey they should update to).
+ final ArrayMap<ResourcesImpl, ResourcesKey> updatedResourceKeys = new ArrayMap<>();
+
+ final int implCount = mResourceImpls.size();
+ for (int i = 0; i < implCount; i++) {
+ final ResourcesKey key = mResourceImpls.keyAt(i);
+ final WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.valueAt(i);
+ final ResourcesImpl impl = weakImplRef != null ? weakImplRef.get() : null;
+ if (impl == null) {
+ Slog.w(TAG, "Found a ResourcesImpl which is null, skip it and continue to "
+ + "append shared library assets for next ResourcesImpl.");
+ continue;
+ }
+
+ var newDirs = new ArrayList<String>();
+ var dirsSet = new ArraySet<String>();
+ if (key.mLibDirs != null) {
+ final int dirsLength = key.mLibDirs.length;
+ for (int k = 0; k < dirsLength; k++) {
+ newDirs.add(key.mLibDirs[k]);
+ dirsSet.add(key.mLibDirs[k]);
+ }
+ }
+ final int assetsLength = libAssets.length;
+ for (int j = 0; j < assetsLength; j++) {
+ if (dirsSet.add(libAssets[j])) {
+ newDirs.add(libAssets[j]);
+ }
+ }
+ String[] newLibAssets = newDirs.toArray(new String[0]);
+ if (!Arrays.equals(newLibAssets, key.mLibDirs)) {
+ updatedResourceKeys.put(impl, new ResourcesKey(
+ key.mResDir,
+ key.mSplitResDirs,
+ key.mOverlayPaths,
+ newLibAssets,
+ key.mDisplayId,
+ key.mOverrideConfiguration,
+ key.mCompatInfo,
+ key.mLoaders));
+ }
+ }
+
+ redirectResourcesToNewImplLocked(updatedResourceKeys);
+ }
+ }
+
private void applyNewResourceDirsLocked(@Nullable final String[] oldSourceDirs,
@NonNull final ApplicationInfo appInfo) {
try {
@@ -1689,4 +1780,50 @@
}
}
}
+
+ public static class SharedLibraryAssets{
+ private final String[] mAssetPaths;
+
+ SharedLibraryAssets(String sourceDir, String[] splitSourceDirs, String[] sharedLibraryFiles,
+ String[] resourceDirs, String[] overlayPaths) {
+ mAssetPaths = collectAssetPaths(sourceDir, splitSourceDirs, sharedLibraryFiles,
+ resourceDirs, overlayPaths);
+ }
+
+ private @NonNull String[] collectAssetPaths(String sourceDir, String[] splitSourceDirs,
+ String[] sharedLibraryFiles, String[] resourceDirs, String[] overlayPaths) {
+ final String[][] inputLists = {
+ splitSourceDirs, sharedLibraryFiles, resourceDirs, overlayPaths
+ };
+
+ final ArraySet<String> assetPathSet = new ArraySet<>();
+ final List<String> assetPathList = new ArrayList<>();
+ if (sourceDir != null) {
+ assetPathSet.add(sourceDir);
+ assetPathList.add(sourceDir);
+ }
+
+ for (int i = 0; i < inputLists.length; i++) {
+ if (inputLists[i] != null) {
+ for (int j = 0; j < inputLists[i].length; j++) {
+ if (assetPathSet.add(inputLists[i][j])) {
+ assetPathList.add(inputLists[i][j]);
+ }
+ }
+ }
+ }
+ return assetPathList.toArray(new String[0]);
+ }
+
+ /**
+ * @return all the asset paths of this collected in this class.
+ */
+ public @NonNull String[] getAllAssetPaths() {
+ return mAssetPaths;
+ }
+ }
+
+ public @NonNull ArrayMap<String, SharedLibraryAssets> getSharedLibAssetsMap() {
+ return new ArrayMap<>(mSharedLibAssetsMap);
+ }
}
diff --git a/core/java/android/app/admin/AccountTypePolicyKey.java b/core/java/android/app/admin/AccountTypePolicyKey.java
index d81eb20..51f3137 100644
--- a/core/java/android/app/admin/AccountTypePolicyKey.java
+++ b/core/java/android/app/admin/AccountTypePolicyKey.java
@@ -19,12 +19,12 @@
import static android.app.admin.PolicyUpdateReceiver.EXTRA_ACCOUNT_TYPE;
import static android.app.admin.PolicyUpdateReceiver.EXTRA_POLICY_BUNDLE_KEY;
import static android.app.admin.PolicyUpdateReceiver.EXTRA_POLICY_KEY;
-import static android.app.admin.flags.Flags.devicePolicySizeTrackingEnabled;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.TestApi;
+import android.app.admin.flags.Flags;
import android.os.Bundle;
import android.os.Parcel;
@@ -54,7 +54,7 @@
@TestApi
public AccountTypePolicyKey(@NonNull String key, @NonNull String accountType) {
super(key);
- if (devicePolicySizeTrackingEnabled()) {
+ if (Flags.devicePolicySizeTrackingInternalEnabled()) {
PolicySizeVerifier.enforceMaxStringLength(accountType, "accountType");
}
mAccountType = Objects.requireNonNull((accountType));
diff --git a/core/java/android/app/admin/BundlePolicyValue.java b/core/java/android/app/admin/BundlePolicyValue.java
index cc5e75f..4f70604 100644
--- a/core/java/android/app/admin/BundlePolicyValue.java
+++ b/core/java/android/app/admin/BundlePolicyValue.java
@@ -16,10 +16,9 @@
package android.app.admin;
-import static android.app.admin.flags.Flags.devicePolicySizeTrackingEnabled;
-
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.admin.flags.Flags;
import android.os.Bundle;
import android.os.Parcel;
@@ -32,7 +31,7 @@
public BundlePolicyValue(Bundle value) {
super(value);
- if (devicePolicySizeTrackingEnabled()) {
+ if (Flags.devicePolicySizeTrackingInternalEnabled()) {
PolicySizeVerifier.enforceMaxParcelableFieldsLength(value);
}
}
diff --git a/core/java/android/app/admin/ComponentNamePolicyValue.java b/core/java/android/app/admin/ComponentNamePolicyValue.java
index 4d36195..a957dbf 100644
--- a/core/java/android/app/admin/ComponentNamePolicyValue.java
+++ b/core/java/android/app/admin/ComponentNamePolicyValue.java
@@ -16,10 +16,9 @@
package android.app.admin;
-import static android.app.admin.flags.Flags.devicePolicySizeTrackingEnabled;
-
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.admin.flags.Flags;
import android.content.ComponentName;
import android.os.Parcel;
@@ -32,7 +31,7 @@
public ComponentNamePolicyValue(@NonNull ComponentName value) {
super(value);
- if (devicePolicySizeTrackingEnabled()) {
+ if (Flags.devicePolicySizeTrackingInternalEnabled()) {
PolicySizeVerifier.enforceMaxComponentNameLength(value);
}
}
diff --git a/core/java/android/app/admin/IntentFilterPolicyKey.java b/core/java/android/app/admin/IntentFilterPolicyKey.java
index de7ff9f..63c3a4cb 100644
--- a/core/java/android/app/admin/IntentFilterPolicyKey.java
+++ b/core/java/android/app/admin/IntentFilterPolicyKey.java
@@ -19,12 +19,12 @@
import static android.app.admin.PolicyUpdateReceiver.EXTRA_INTENT_FILTER;
import static android.app.admin.PolicyUpdateReceiver.EXTRA_POLICY_BUNDLE_KEY;
import static android.app.admin.PolicyUpdateReceiver.EXTRA_POLICY_KEY;
-import static android.app.admin.flags.Flags.devicePolicySizeTrackingEnabled;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.TestApi;
+import android.app.admin.flags.Flags;
import android.content.IntentFilter;
import android.os.Bundle;
import android.os.Parcel;
@@ -60,7 +60,7 @@
@TestApi
public IntentFilterPolicyKey(@NonNull String identifier, @NonNull IntentFilter filter) {
super(identifier);
- if (devicePolicySizeTrackingEnabled()) {
+ if (Flags.devicePolicySizeTrackingInternalEnabled()) {
PolicySizeVerifier.enforceMaxParcelableFieldsLength(filter);
}
mFilter = Objects.requireNonNull(filter);
diff --git a/core/java/android/app/admin/LockTaskPolicy.java b/core/java/android/app/admin/LockTaskPolicy.java
index 9d6ce24..a36ea05 100644
--- a/core/java/android/app/admin/LockTaskPolicy.java
+++ b/core/java/android/app/admin/LockTaskPolicy.java
@@ -16,11 +16,10 @@
package android.app.admin;
-import static android.app.admin.flags.Flags.devicePolicySizeTrackingEnabled;
-
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
+import android.app.admin.flags.Flags;
import android.os.Parcel;
import android.os.Parcelable;
@@ -136,7 +135,7 @@
}
private void setPackagesInternal(Set<String> packages) {
- if (devicePolicySizeTrackingEnabled()) {
+ if (Flags.devicePolicySizeTrackingInternalEnabled()) {
for (String p : packages) {
PolicySizeVerifier.enforceMaxPackageNameLength(p);
}
diff --git a/core/java/android/app/admin/PackagePermissionPolicyKey.java b/core/java/android/app/admin/PackagePermissionPolicyKey.java
index 2241fdd..389585f 100644
--- a/core/java/android/app/admin/PackagePermissionPolicyKey.java
+++ b/core/java/android/app/admin/PackagePermissionPolicyKey.java
@@ -20,12 +20,12 @@
import static android.app.admin.PolicyUpdateReceiver.EXTRA_PERMISSION_NAME;
import static android.app.admin.PolicyUpdateReceiver.EXTRA_POLICY_BUNDLE_KEY;
import static android.app.admin.PolicyUpdateReceiver.EXTRA_POLICY_KEY;
-import static android.app.admin.flags.Flags.devicePolicySizeTrackingEnabled;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.TestApi;
+import android.app.admin.flags.Flags;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
@@ -59,7 +59,7 @@
public PackagePermissionPolicyKey(@NonNull String identifier, @NonNull String packageName,
@NonNull String permissionName) {
super(identifier);
- if (devicePolicySizeTrackingEnabled()) {
+ if (Flags.devicePolicySizeTrackingInternalEnabled()) {
PolicySizeVerifier.enforceMaxPackageNameLength(packageName);
PolicySizeVerifier.enforceMaxStringLength(permissionName, "permissionName");
}
diff --git a/core/java/android/app/admin/PackagePolicyKey.java b/core/java/android/app/admin/PackagePolicyKey.java
index 2ea17a1..68dc797 100644
--- a/core/java/android/app/admin/PackagePolicyKey.java
+++ b/core/java/android/app/admin/PackagePolicyKey.java
@@ -19,12 +19,12 @@
import static android.app.admin.PolicyUpdateReceiver.EXTRA_PACKAGE_NAME;
import static android.app.admin.PolicyUpdateReceiver.EXTRA_POLICY_BUNDLE_KEY;
import static android.app.admin.PolicyUpdateReceiver.EXTRA_POLICY_KEY;
-import static android.app.admin.flags.Flags.devicePolicySizeTrackingEnabled;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.TestApi;
+import android.app.admin.flags.Flags;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
@@ -55,7 +55,7 @@
@TestApi
public PackagePolicyKey(@NonNull String key, @NonNull String packageName) {
super(key);
- if (devicePolicySizeTrackingEnabled()) {
+ if (Flags.devicePolicySizeTrackingInternalEnabled()) {
PolicySizeVerifier.enforceMaxPackageNameLength(packageName);
}
mPackageName = Objects.requireNonNull((packageName));
diff --git a/core/java/android/app/admin/StringPolicyValue.java b/core/java/android/app/admin/StringPolicyValue.java
index f4d4adc..8995c0f 100644
--- a/core/java/android/app/admin/StringPolicyValue.java
+++ b/core/java/android/app/admin/StringPolicyValue.java
@@ -16,10 +16,9 @@
package android.app.admin;
-import static android.app.admin.flags.Flags.devicePolicySizeTrackingEnabled;
-
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.admin.flags.Flags;
import android.os.Parcel;
import java.util.Objects;
@@ -31,7 +30,7 @@
public StringPolicyValue(@NonNull String value) {
super(value);
- if (devicePolicySizeTrackingEnabled()) {
+ if (Flags.devicePolicySizeTrackingInternalEnabled()) {
PolicySizeVerifier.enforceMaxStringLength(value, "policyValue");
}
}
diff --git a/core/java/android/app/admin/StringSetPolicyValue.java b/core/java/android/app/admin/StringSetPolicyValue.java
index 82fe761..f37dfee 100644
--- a/core/java/android/app/admin/StringSetPolicyValue.java
+++ b/core/java/android/app/admin/StringSetPolicyValue.java
@@ -16,10 +16,9 @@
package android.app.admin;
-import static android.app.admin.flags.Flags.devicePolicySizeTrackingEnabled;
-
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.admin.flags.Flags;
import android.os.Parcel;
import java.util.HashSet;
@@ -33,7 +32,7 @@
public StringSetPolicyValue(@NonNull Set<String> value) {
super(value);
- if (devicePolicySizeTrackingEnabled()) {
+ if (Flags.devicePolicySizeTrackingInternalEnabled()) {
for (String str : value) {
PolicySizeVerifier.enforceMaxStringLength(str, "policyValue");
}
diff --git a/core/java/android/app/admin/UserRestrictionPolicyKey.java b/core/java/android/app/admin/UserRestrictionPolicyKey.java
index d69a5f0..ee90ccd 100644
--- a/core/java/android/app/admin/UserRestrictionPolicyKey.java
+++ b/core/java/android/app/admin/UserRestrictionPolicyKey.java
@@ -17,11 +17,11 @@
package android.app.admin;
import static android.app.admin.PolicyUpdateReceiver.EXTRA_POLICY_KEY;
-import static android.app.admin.flags.Flags.devicePolicySizeTrackingEnabled;
import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.annotation.TestApi;
+import android.app.admin.flags.Flags;
import android.os.Bundle;
import android.os.Parcel;
@@ -45,7 +45,7 @@
@TestApi
public UserRestrictionPolicyKey(@NonNull String identifier, @NonNull String restriction) {
super(identifier);
- if (devicePolicySizeTrackingEnabled()) {
+ if (Flags.devicePolicySizeTrackingInternalEnabled()) {
PolicySizeVerifier.enforceMaxStringLength(restriction, "restriction");
}
mRestriction = Objects.requireNonNull(restriction);
diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig
index e1a6913..1927019 100644
--- a/core/java/android/app/admin/flags/flags.aconfig
+++ b/core/java/android/app/admin/flags/flags.aconfig
@@ -10,7 +10,14 @@
flag {
name: "device_policy_size_tracking_enabled"
namespace: "enterprise"
- description: "Add feature to track the total policy size and have a max threshold."
+ description: "Add feature to track the total policy size and have a max threshold - public API changes"
+ bug: "281543351"
+}
+
+flag {
+ name: "device_policy_size_tracking_internal_enabled"
+ namespace: "enterprise"
+ description: "Add feature to track the total policy size and have a max threshold - internal changes"
bug: "281543351"
}
diff --git a/core/java/android/app/servertransaction/ClientTransactionListenerController.java b/core/java/android/app/servertransaction/ClientTransactionListenerController.java
index 1a8136e..7383d07 100644
--- a/core/java/android/app/servertransaction/ClientTransactionListenerController.java
+++ b/core/java/android/app/servertransaction/ClientTransactionListenerController.java
@@ -16,16 +16,24 @@
package android.app.servertransaction;
+import static com.android.window.flags.Flags.activityWindowInfoFlag;
import static com.android.window.flags.Flags.bundleClientTransactionFlag;
import static java.util.Objects.requireNonNull;
import android.annotation.NonNull;
+import android.app.Activity;
import android.app.ActivityThread;
import android.hardware.display.DisplayManagerGlobal;
+import android.os.IBinder;
+import android.util.ArraySet;
+import android.window.ActivityWindowInfo;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import java.util.function.BiConsumer;
+
/**
* Singleton controller to manage listeners to individual {@link ClientTransaction}.
*
@@ -35,8 +43,14 @@
private static ClientTransactionListenerController sController;
+ private final Object mLock = new Object();
private final DisplayManagerGlobal mDisplayManager;
+ /** Listeners registered via {@link #registerActivityWindowInfoChangedListener(BiConsumer)}. */
+ @GuardedBy("mLock")
+ private final ArraySet<BiConsumer<IBinder, ActivityWindowInfo>>
+ mActivityWindowInfoChangedListeners = new ArraySet<>();
+
/** Gets the singleton controller. */
@NonNull
public static ClientTransactionListenerController getInstance() {
@@ -62,6 +76,57 @@
}
/**
+ * Registers to listen on activity {@link ActivityWindowInfo} change.
+ * The listener will be invoked with two parameters: {@link Activity#getActivityToken()} and
+ * {@link ActivityWindowInfo}.
+ */
+ public void registerActivityWindowInfoChangedListener(
+ @NonNull BiConsumer<IBinder, ActivityWindowInfo> listener) {
+ if (!activityWindowInfoFlag()) {
+ return;
+ }
+ synchronized (mLock) {
+ mActivityWindowInfoChangedListeners.add(listener);
+ }
+ }
+
+ /**
+ * Unregisters the listener that was previously registered via
+ * {@link #registerActivityWindowInfoChangedListener(BiConsumer)}
+ */
+ public void unregisterActivityWindowInfoChangedListener(
+ @NonNull BiConsumer<IBinder, ActivityWindowInfo> listener) {
+ if (!activityWindowInfoFlag()) {
+ return;
+ }
+ synchronized (mLock) {
+ mActivityWindowInfoChangedListeners.remove(listener);
+ }
+ }
+
+ /**
+ * Called when receives a {@link ClientTransaction} that is updating an activity's
+ * {@link ActivityWindowInfo}.
+ */
+ public void onActivityWindowInfoChanged(@NonNull IBinder activityToken,
+ @NonNull ActivityWindowInfo activityWindowInfo) {
+ if (!activityWindowInfoFlag()) {
+ return;
+ }
+ final Object[] activityWindowInfoChangedListeners;
+ synchronized (mLock) {
+ if (mActivityWindowInfoChangedListeners.isEmpty()) {
+ return;
+ }
+ activityWindowInfoChangedListeners = mActivityWindowInfoChangedListeners.toArray();
+ }
+ for (Object activityWindowInfoChangedListener : activityWindowInfoChangedListeners) {
+ ((BiConsumer<IBinder, ActivityWindowInfo>) activityWindowInfoChangedListener)
+ .accept(activityToken, activityWindowInfo);
+ }
+ }
+
+ /**
* Called when receives a {@link ClientTransaction} that is updating display-related
* window configuration.
*/
diff --git a/core/java/android/app/servertransaction/DestroyActivityItem.java b/core/java/android/app/servertransaction/DestroyActivityItem.java
index f9cf075..b0213d7 100644
--- a/core/java/android/app/servertransaction/DestroyActivityItem.java
+++ b/core/java/android/app/servertransaction/DestroyActivityItem.java
@@ -33,7 +33,6 @@
public class DestroyActivityItem extends ActivityLifecycleItem {
private boolean mFinished;
- private int mConfigChanges;
@Override
public void preExecute(@NonNull ClientTransactionHandler client) {
@@ -44,7 +43,7 @@
public void execute(@NonNull ClientTransactionHandler client, @NonNull ActivityClientRecord r,
@NonNull PendingTransactionActions pendingActions) {
Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityDestroy");
- client.handleDestroyActivity(r, mFinished, mConfigChanges,
+ client.handleDestroyActivity(r, mFinished,
false /* getNonConfigInstance */, "DestroyActivityItem");
Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
}
@@ -67,15 +66,13 @@
/** Obtain an instance initialized with provided params. */
@NonNull
- public static DestroyActivityItem obtain(@NonNull IBinder activityToken, boolean finished,
- int configChanges) {
+ public static DestroyActivityItem obtain(@NonNull IBinder activityToken, boolean finished) {
DestroyActivityItem instance = ObjectPool.obtain(DestroyActivityItem.class);
if (instance == null) {
instance = new DestroyActivityItem();
}
instance.setActivityToken(activityToken);
instance.mFinished = finished;
- instance.mConfigChanges = configChanges;
return instance;
}
@@ -84,7 +81,6 @@
public void recycle() {
super.recycle();
mFinished = false;
- mConfigChanges = 0;
ObjectPool.recycle(this);
}
@@ -95,14 +91,12 @@
public void writeToParcel(@NonNull Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeBoolean(mFinished);
- dest.writeInt(mConfigChanges);
}
/** Read from Parcel. */
private DestroyActivityItem(@NonNull Parcel in) {
super(in);
mFinished = in.readBoolean();
- mConfigChanges = in.readInt();
}
public static final @NonNull Creator<DestroyActivityItem> CREATOR = new Creator<>() {
@@ -124,7 +118,7 @@
return false;
}
final DestroyActivityItem other = (DestroyActivityItem) o;
- return mFinished == other.mFinished && mConfigChanges == other.mConfigChanges;
+ return mFinished == other.mFinished;
}
@Override
@@ -132,14 +126,12 @@
int result = 17;
result = 31 * result + super.hashCode();
result = 31 * result + (mFinished ? 1 : 0);
- result = 31 * result + mConfigChanges;
return result;
}
@Override
public String toString() {
return "DestroyActivityItem{" + super.toString()
- + ",finished=" + mFinished
- + ",mConfigChanges=" + mConfigChanges + "}";
+ + ",finished=" + mFinished + "}";
}
}
diff --git a/core/java/android/app/servertransaction/PauseActivityItem.java b/core/java/android/app/servertransaction/PauseActivityItem.java
index 8f1e90b..d230284 100644
--- a/core/java/android/app/servertransaction/PauseActivityItem.java
+++ b/core/java/android/app/servertransaction/PauseActivityItem.java
@@ -37,7 +37,6 @@
private boolean mFinished;
private boolean mUserLeaving;
- private int mConfigChanges;
private boolean mDontReport;
private boolean mAutoEnteringPip;
@@ -45,7 +44,7 @@
public void execute(@NonNull ClientTransactionHandler client, @NonNull ActivityClientRecord r,
@NonNull PendingTransactionActions pendingActions) {
Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityPause");
- client.handlePauseActivity(r, mFinished, mUserLeaving, mConfigChanges, mAutoEnteringPip,
+ client.handlePauseActivity(r, mFinished, mUserLeaving, mAutoEnteringPip,
pendingActions, "PAUSE_ACTIVITY_ITEM");
Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
}
@@ -72,7 +71,7 @@
/** Obtain an instance initialized with provided params. */
@NonNull
public static PauseActivityItem obtain(@NonNull IBinder activityToken, boolean finished,
- boolean userLeaving, int configChanges, boolean dontReport, boolean autoEnteringPip) {
+ boolean userLeaving, boolean dontReport, boolean autoEnteringPip) {
PauseActivityItem instance = ObjectPool.obtain(PauseActivityItem.class);
if (instance == null) {
instance = new PauseActivityItem();
@@ -80,7 +79,6 @@
instance.setActivityToken(activityToken);
instance.mFinished = finished;
instance.mUserLeaving = userLeaving;
- instance.mConfigChanges = configChanges;
instance.mDontReport = dontReport;
instance.mAutoEnteringPip = autoEnteringPip;
@@ -91,7 +89,7 @@
@NonNull
public static PauseActivityItem obtain(@NonNull IBinder activityToken) {
return obtain(activityToken, false /* finished */, false /* userLeaving */,
- 0 /* configChanges */, true /* dontReport */, false /* autoEnteringPip*/);
+ true /* dontReport */, false /* autoEnteringPip*/);
}
@Override
@@ -99,7 +97,6 @@
super.recycle();
mFinished = false;
mUserLeaving = false;
- mConfigChanges = 0;
mDontReport = false;
mAutoEnteringPip = false;
ObjectPool.recycle(this);
@@ -113,7 +110,6 @@
super.writeToParcel(dest, flags);
dest.writeBoolean(mFinished);
dest.writeBoolean(mUserLeaving);
- dest.writeInt(mConfigChanges);
dest.writeBoolean(mDontReport);
dest.writeBoolean(mAutoEnteringPip);
}
@@ -123,7 +119,6 @@
super(in);
mFinished = in.readBoolean();
mUserLeaving = in.readBoolean();
- mConfigChanges = in.readInt();
mDontReport = in.readBoolean();
mAutoEnteringPip = in.readBoolean();
}
@@ -148,7 +143,7 @@
}
final PauseActivityItem other = (PauseActivityItem) o;
return mFinished == other.mFinished && mUserLeaving == other.mUserLeaving
- && mConfigChanges == other.mConfigChanges && mDontReport == other.mDontReport
+ && mDontReport == other.mDontReport
&& mAutoEnteringPip == other.mAutoEnteringPip;
}
@@ -158,7 +153,6 @@
result = 31 * result + super.hashCode();
result = 31 * result + (mFinished ? 1 : 0);
result = 31 * result + (mUserLeaving ? 1 : 0);
- result = 31 * result + mConfigChanges;
result = 31 * result + (mDontReport ? 1 : 0);
result = 31 * result + (mAutoEnteringPip ? 1 : 0);
return result;
@@ -169,7 +163,6 @@
return "PauseActivityItem{" + super.toString()
+ ",finished=" + mFinished
+ ",userLeaving=" + mUserLeaving
- + ",configChanges=" + mConfigChanges
+ ",dontReport=" + mDontReport
+ ",autoEnteringPip=" + mAutoEnteringPip + "}";
}
diff --git a/core/java/android/app/servertransaction/StopActivityItem.java b/core/java/android/app/servertransaction/StopActivityItem.java
index b8ce52d..def7b3f 100644
--- a/core/java/android/app/servertransaction/StopActivityItem.java
+++ b/core/java/android/app/servertransaction/StopActivityItem.java
@@ -19,7 +19,6 @@
import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.app.ActivityThread.ActivityClientRecord;
import android.app.ClientTransactionHandler;
import android.os.IBinder;
@@ -34,13 +33,11 @@
private static final String TAG = "StopActivityItem";
- private int mConfigChanges;
-
@Override
public void execute(@NonNull ClientTransactionHandler client, @NonNull ActivityClientRecord r,
@NonNull PendingTransactionActions pendingActions) {
Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityStop");
- client.handleStopActivity(r, mConfigChanges, pendingActions,
+ client.handleStopActivity(r, pendingActions,
true /* finalStateRequest */, "STOP_ACTIVITY_ITEM");
Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
}
@@ -63,16 +60,14 @@
/**
* Obtain an instance initialized with provided params.
* @param activityToken the activity that stops.
- * @param configChanges Configuration pieces that changed.
*/
@NonNull
- public static StopActivityItem obtain(@NonNull IBinder activityToken, int configChanges) {
+ public static StopActivityItem obtain(@NonNull IBinder activityToken) {
StopActivityItem instance = ObjectPool.obtain(StopActivityItem.class);
if (instance == null) {
instance = new StopActivityItem();
}
instance.setActivityToken(activityToken);
- instance.mConfigChanges = configChanges;
return instance;
}
@@ -80,23 +75,14 @@
@Override
public void recycle() {
super.recycle();
- mConfigChanges = 0;
ObjectPool.recycle(this);
}
// Parcelable implementation
- /** Write to Parcel. */
- @Override
- public void writeToParcel(@NonNull Parcel dest, int flags) {
- super.writeToParcel(dest, flags);
- dest.writeInt(mConfigChanges);
- }
-
/** Read from Parcel. */
private StopActivityItem(@NonNull Parcel in) {
super(in);
- mConfigChanges = in.readInt();
}
public static final @NonNull Creator<StopActivityItem> CREATOR = new Creator<>() {
@@ -110,28 +96,7 @@
};
@Override
- public boolean equals(@Nullable Object o) {
- if (this == o) {
- return true;
- }
- if (!super.equals(o)) {
- return false;
- }
- final StopActivityItem other = (StopActivityItem) o;
- return mConfigChanges == other.mConfigChanges;
- }
-
- @Override
- public int hashCode() {
- int result = 17;
- result = 31 * result + super.hashCode();
- result = 31 * result + mConfigChanges;
- return result;
- }
-
- @Override
public String toString() {
- return "StopActivityItem{" + super.toString()
- + ",configChanges=" + mConfigChanges + "}";
+ return "StopActivityItem{" + super.toString() + "}";
}
}
diff --git a/core/java/android/app/servertransaction/TransactionExecutor.java b/core/java/android/app/servertransaction/TransactionExecutor.java
index fa73c99..c837191 100644
--- a/core/java/android/app/servertransaction/TransactionExecutor.java
+++ b/core/java/android/app/servertransaction/TransactionExecutor.java
@@ -334,18 +334,18 @@
break;
case ON_PAUSE:
mTransactionHandler.handlePauseActivity(r, false /* finished */,
- false /* userLeaving */, 0 /* configChanges */,
+ false /* userLeaving */,
false /* autoEnteringPip */, mPendingActions,
"LIFECYCLER_PAUSE_ACTIVITY");
break;
case ON_STOP:
- mTransactionHandler.handleStopActivity(r, 0 /* configChanges */,
+ mTransactionHandler.handleStopActivity(r,
mPendingActions, false /* finalStateRequest */,
"LIFECYCLER_STOP_ACTIVITY");
break;
case ON_DESTROY:
mTransactionHandler.handleDestroyActivity(r, false /* finishing */,
- 0 /* configChanges */, false /* getNonConfigInstance */,
+ false /* getNonConfigInstance */,
"performLifecycleSequence. cycling to:" + path.get(size - 1));
break;
case ON_RESTART:
diff --git a/core/java/android/app/servertransaction/TransactionExecutorHelper.java b/core/java/android/app/servertransaction/TransactionExecutorHelper.java
index 475c6fb..710261a 100644
--- a/core/java/android/app/servertransaction/TransactionExecutorHelper.java
+++ b/core/java/android/app/servertransaction/TransactionExecutorHelper.java
@@ -200,7 +200,7 @@
lifecycleItem = PauseActivityItem.obtain(r.token);
break;
case ON_STOP:
- lifecycleItem = StopActivityItem.obtain(r.token, 0 /* configChanges */);
+ lifecycleItem = StopActivityItem.obtain(r.token);
break;
default:
lifecycleItem = ResumeActivityItem.obtain(r.token, false /* isForward */,
diff --git a/core/java/android/content/pm/UserInfo.java b/core/java/android/content/pm/UserInfo.java
index 3e9f260..8a3a3ad 100644
--- a/core/java/android/content/pm/UserInfo.java
+++ b/core/java/android/content/pm/UserInfo.java
@@ -112,6 +112,12 @@
/**
* Indicates that this user is disabled.
*
+ * <p> This is currently used to indicate that a Managed Profile, when created via
+ * DevicePolicyManager, has not yet been provisioned; once the DPC provisions it, a DPM call
+ * will manually set it to enabled.
+ *
+ * <p>Users that are slated for deletion are also generally set to disabled.
+ *
* <p>Note: If an ephemeral user is disabled, it shouldn't be later re-enabled. Ephemeral users
* are disabled as their removal is in progress to indicate that they shouldn't be re-entered.
*/
@@ -398,6 +404,7 @@
return UserManager.isUserTypePrivateProfile(userType);
}
+ /** See {@link #FLAG_DISABLED}*/
@UnsupportedAppUsage
public boolean isEnabled() {
return (flags & FLAG_DISABLED) != FLAG_DISABLED;
diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig
index 5e9d8f0..610057b 100644
--- a/core/java/android/content/pm/flags.aconfig
+++ b/core/java/android/content/pm/flags.aconfig
@@ -208,6 +208,14 @@
}
flag {
+ name: "restrict_nonpreloads_system_shareduids"
+ namespace: "package_manager_service"
+ description: "Feature flag to restrict apps from joining system shared uids"
+ bug: "308573169"
+ is_fixed_read_only: true
+}
+
+flag {
name: "min_target_sdk_24"
namespace: "responsible_apis"
description: "Feature flag to bump min target sdk to 24"
diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java
index d259e97..273e40a 100644
--- a/core/java/android/content/res/AssetManager.java
+++ b/core/java/android/content/res/AssetManager.java
@@ -471,6 +471,16 @@
return addAssetPathInternal(path, true /*overlay*/, false /*appAsLib*/);
}
+ /**
+ * @hide
+ */
+ public void addSharedLibraryPaths(@NonNull String[] paths) {
+ final int length = paths.length;
+ for (int i = 0; i < length; i++) {
+ addAssetPathInternal(paths[i], false, true);
+ }
+ }
+
private int addAssetPathInternal(String path, boolean overlay, boolean appAsLib) {
Objects.requireNonNull(path, "path");
synchronized (this) {
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index 7fba3e8..1f5f88f 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -43,6 +43,7 @@
import android.annotation.StyleableRes;
import android.annotation.XmlRes;
import android.app.Application;
+import android.app.ResourcesManager;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.pm.ActivityInfo;
@@ -2854,6 +2855,11 @@
@FlaggedApi(android.content.res.Flags.FLAG_REGISTER_RESOURCE_PATHS)
public static void registerResourcePaths(@NonNull String uniqueId,
@NonNull ApplicationInfo appInfo) {
- throw new UnsupportedOperationException("The implementation has not been done yet.");
+ if (Flags.registerResourcePaths()) {
+ ResourcesManager.getInstance().registerResourcePaths(uniqueId, appInfo);
+ } else {
+ throw new UnsupportedOperationException("Flag " + Flags.FLAG_REGISTER_RESOURCE_PATHS
+ + " is disabled.");
+ }
}
}
diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java
index 079c2c1..8d045aa 100644
--- a/core/java/android/content/res/ResourcesImpl.java
+++ b/core/java/android/content/res/ResourcesImpl.java
@@ -29,6 +29,7 @@
import android.annotation.StyleableRes;
import android.app.LocaleConfig;
import android.app.ResourcesManager;
+import android.app.ResourcesManager.SharedLibraryAssets;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.pm.ActivityInfo;
import android.content.pm.ActivityInfo.Config;
@@ -47,6 +48,7 @@
import android.os.LocaleList;
import android.os.ParcelFileDescriptor;
import android.os.Trace;
+import android.util.ArrayMap;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
@@ -197,6 +199,14 @@
public ResourcesImpl(@NonNull AssetManager assets, @Nullable DisplayMetrics metrics,
@Nullable Configuration config, @NonNull DisplayAdjustments displayAdjustments) {
mAssets = assets;
+ if (Flags.registerResourcePaths()) {
+ ArrayMap<String, SharedLibraryAssets> sharedLibMap =
+ ResourcesManager.getInstance().getSharedLibAssetsMap();
+ final int size = sharedLibMap.size();
+ for (int i = 0; i < size; i++) {
+ assets.addSharedLibraryPaths(sharedLibMap.valueAt(i).getAllAssetPaths());
+ }
+ }
mMetrics.setToDefaults();
mDisplayAdjustments = displayAdjustments;
mConfiguration.setToDefaults();
diff --git a/core/java/android/credentials/selection/IntentFactory.java b/core/java/android/credentials/selection/IntentFactory.java
index 4b0fa6d..79fba9b 100644
--- a/core/java/android/credentials/selection/IntentFactory.java
+++ b/core/java/android/credentials/selection/IntentFactory.java
@@ -80,17 +80,7 @@
ArrayList<DisabledProviderData> disabledProviderDataList,
@NonNull ResultReceiver resultReceiver) {
Intent intent = new Intent();
- ComponentName componentName =
- ComponentName.unflattenFromString(
- Resources.getSystem()
- .getString(
- com.android.internal.R.string
- .config_credentialManagerDialogComponent));
- ComponentName oemOverrideComponentName = getOemOverrideComponentName(context);
- if (oemOverrideComponentName != null) {
- componentName = oemOverrideComponentName;
- }
- intent.setComponent(componentName);
+ setCredentialSelectorUiComponentName(context, intent);
intent.putParcelableArrayListExtra(
ProviderData.EXTRA_DISABLED_PROVIDER_DATA_LIST, disabledProviderDataList);
intent.putExtra(RequestInfo.EXTRA_REQUEST_INFO, requestInfo);
@@ -100,6 +90,24 @@
return intent;
}
+ private static void setCredentialSelectorUiComponentName(@NonNull Context context,
+ @NonNull Intent intent) {
+ if (configurableSelectorUiEnabled()) {
+ ComponentName componentName = getOemOverrideComponentName(context);
+ if (componentName == null) {
+ componentName = ComponentName.unflattenFromString(Resources.getSystem().getString(
+ com.android.internal.R.string
+ .config_fallbackCredentialManagerDialogComponent));
+ }
+ intent.setComponent(componentName);
+ } else {
+ ComponentName componentName = ComponentName.unflattenFromString(Resources.getSystem()
+ .getString(com.android.internal.R.string
+ .config_fallbackCredentialManagerDialogComponent));
+ intent.setComponent(componentName);
+ }
+ }
+
/**
* Returns null if there is not an enabled and valid oem override component. It means the
* default platform UI component name should be used instead.
@@ -107,44 +115,39 @@
@Nullable
private static ComponentName getOemOverrideComponentName(@NonNull Context context) {
ComponentName result = null;
- if (configurableSelectorUiEnabled()) {
- if (Resources.getSystem().getBoolean(
- com.android.internal.R.bool.config_enableOemCredentialManagerDialogComponent)) {
- String oemComponentString =
- Resources.getSystem()
- .getString(
- com.android.internal.R.string
- .config_oemCredentialManagerDialogComponent);
- if (!TextUtils.isEmpty(oemComponentString)) {
- ComponentName oemComponentName = ComponentName.unflattenFromString(
- oemComponentString);
- if (oemComponentName != null) {
- try {
- ActivityInfo info = context.getPackageManager().getActivityInfo(
- oemComponentName,
- PackageManager.ComponentInfoFlags.of(
- PackageManager.MATCH_SYSTEM_ONLY));
- if (info.enabled && info.exported) {
- Slog.i(TAG,
- "Found enabled oem CredMan UI component."
- + oemComponentString);
- result = oemComponentName;
- } else {
- Slog.i(TAG,
- "Found enabled oem CredMan UI component but it was not "
- + "enabled.");
- }
- } catch (PackageManager.NameNotFoundException e) {
- Slog.i(TAG, "Unable to find oem CredMan UI component: "
- + oemComponentString + ".");
- }
+ String oemComponentString =
+ Resources.getSystem()
+ .getString(
+ com.android.internal.R.string
+ .config_oemCredentialManagerDialogComponent);
+ if (!TextUtils.isEmpty(oemComponentString)) {
+ ComponentName oemComponentName = ComponentName.unflattenFromString(
+ oemComponentString);
+ if (oemComponentName != null) {
+ try {
+ ActivityInfo info = context.getPackageManager().getActivityInfo(
+ oemComponentName,
+ PackageManager.ComponentInfoFlags.of(
+ PackageManager.MATCH_SYSTEM_ONLY));
+ if (info.enabled && info.exported) {
+ Slog.i(TAG,
+ "Found enabled oem CredMan UI component."
+ + oemComponentString);
+ result = oemComponentName;
} else {
- Slog.i(TAG, "Invalid OEM ComponentName format.");
+ Slog.i(TAG,
+ "Found enabled oem CredMan UI component but it was not "
+ + "enabled.");
}
- } else {
- Slog.i(TAG, "Invalid empty OEM component name.");
+ } catch (PackageManager.NameNotFoundException e) {
+ Slog.i(TAG, "Unable to find oem CredMan UI component: "
+ + oemComponentString + ".");
}
+ } else {
+ Slog.i(TAG, "Invalid OEM ComponentName format.");
}
+ } else {
+ Slog.i(TAG, "Invalid empty OEM component name.");
}
return result;
}
@@ -186,16 +189,11 @@
* Creates an Intent that cancels any UI matching the given request token id.
*/
@NonNull
- public static Intent createCancelUiIntent(@NonNull IBinder requestToken,
- boolean shouldShowCancellationUi, @NonNull String appPackageName) {
+ public static Intent createCancelUiIntent(@NonNull Context context,
+ @NonNull IBinder requestToken, boolean shouldShowCancellationUi,
+ @NonNull String appPackageName) {
Intent intent = new Intent();
- ComponentName componentName =
- ComponentName.unflattenFromString(
- Resources.getSystem()
- .getString(
- com.android.internal.R.string
- .config_credentialManagerDialogComponent));
- intent.setComponent(componentName);
+ setCredentialSelectorUiComponentName(context, intent);
intent.putExtra(CancelSelectionRequest.EXTRA_CANCEL_UI_REQUEST,
new CancelSelectionRequest(new RequestToken(requestToken), shouldShowCancellationUi,
appPackageName));
diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java
index d9d4305..0208fed 100644
--- a/core/java/android/hardware/biometrics/BiometricPrompt.java
+++ b/core/java/android/hardware/biometrics/BiometricPrompt.java
@@ -23,7 +23,6 @@
import static android.hardware.biometrics.BiometricManager.Authenticators;
import static android.hardware.biometrics.Flags.FLAG_ADD_KEY_AGREEMENT_CRYPTO_OBJECT;
import static android.hardware.biometrics.Flags.FLAG_CUSTOM_BIOMETRIC_PROMPT;
-import static android.hardware.biometrics.Flags.FLAG_FACE_BACKGROUND_AUTHENTICATION;
import static android.hardware.biometrics.Flags.FLAG_GET_OP_ID_CRYPTO_OBJECT;
import static android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE;
@@ -1128,8 +1127,6 @@
* @hide
*/
@Override
- @TestApi
- @FlaggedApi(FLAG_FACE_BACKGROUND_AUTHENTICATION)
public void onAuthenticationAcquired(int acquireInfo) {}
/**
diff --git a/core/java/android/hardware/biometrics/SensorProperties.java b/core/java/android/hardware/biometrics/SensorProperties.java
index 16f71414..3b9cad4 100644
--- a/core/java/android/hardware/biometrics/SensorProperties.java
+++ b/core/java/android/hardware/biometrics/SensorProperties.java
@@ -16,9 +16,6 @@
package android.hardware.biometrics;
-import static android.hardware.biometrics.Flags.FLAG_FACE_BACKGROUND_AUTHENTICATION;
-
-import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.TestApi;
@@ -144,10 +141,8 @@
/**
* @hide
*/
- @TestApi
- @FlaggedApi(FLAG_FACE_BACKGROUND_AUTHENTICATION)
public SensorProperties(int sensorId, @Strength int sensorStrength,
- @NonNull List<ComponentInfo> componentInfo) {
+ List<ComponentInfo> componentInfo) {
mSensorId = sensorId;
mSensorStrength = sensorStrength;
mComponentInfo = componentInfo;
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index 57b437f..dc8f4b4 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -3521,7 +3521,7 @@
* <p>When the key is present, only a PRIVATE/YUV output of the specified size is guaranteed
* to be supported by the camera HAL in the secure camera mode. Any other format or
* resolutions might not be supported. Use
- * {@link CameraManager#isSessionConfigurationWithParametersSupported }
+ * {@link CameraDevice#isSessionConfigurationSupported }
* API to query if a secure session configuration is supported if the device supports this
* API.</p>
* <p>If this key returns null on a device with SECURE_IMAGE_DATA capability, the application
@@ -5046,18 +5046,18 @@
/**
* <p>The version of the session configuration query
- * {@link android.hardware.camera2.CameraManager#isSessionConfigurationWithParametersSupported }
+ * {@link android.hardware.camera2.CameraDevice.CameraDeviceSetup#isSessionConfigurationSupported }
* API</p>
* <p>The possible values in this key correspond to the values defined in
* android.os.Build.VERSION_CODES. Each version defines a set of feature combinations the
* camera device must reliably report whether they are supported via
- * {@link android.hardware.camera2.CameraManager#isSessionConfigurationWithParametersSupported }
+ * {@link android.hardware.camera2.CameraDevice.CameraDeviceSetup#isSessionConfigurationSupported }
* API. And the version is always less or equal to android.os.Build.VERSION.SDK_INT.</p>
* <p>If set to UPSIDE_DOWN_CAKE, this camera device doesn't support
- * {@link android.hardware.camera2.CameraManager#isSessionConfigurationWithParametersSupported }.
+ * {@link android.hardware.camera2.CameraDevice.CameraDeviceSetup#isSessionConfigurationSupported }.
* Calling the method for this camera ID throws an UnsupportedOperationException.</p>
* <p>If set to VANILLA_ICE_CREAM, the application can call
- * {@link android.hardware.camera2.CameraManager#isSessionConfigurationWithParametersSupported }
+ * {@link android.hardware.camera2.CameraDevice.CameraDeviceSetup#isSessionConfigurationSupported }
* to check if the combinations of below features are supported.</p>
* <ul>
* <li>A subset of LIMITED-level device stream combinations.</li>
@@ -6082,11 +6082,11 @@
/**
* <p>Minimum and maximum padding zoom factors supported by this camera device for
- * {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR } used for the
+ * android.efv.paddingZoomFactor used for the
* {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY }
* extension.</p>
* <p>The minimum and maximum padding zoom factors supported by the device for
- * {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR } used as part of the
+ * android.efv.paddingZoomFactor used as part of the
* {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY }
* extension feature. This extension specific camera characteristic can be queried using
* {@link android.hardware.camera2.CameraExtensionCharacteristics#get }.</p>
diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java
index e24c98e..9fb561b 100644
--- a/core/java/android/hardware/camera2/CameraMetadata.java
+++ b/core/java/android/hardware/camera2/CameraMetadata.java
@@ -911,10 +911,10 @@
* </ul>
* <p>Combinations of logical and physical streams, or physical streams from different
* physical cameras are not guaranteed. However, if the camera device supports
- * {@link CameraManager#isSessionConfigurationWithParametersSupported },
+ * {@link CameraDevice#isSessionConfigurationSupported },
* application must be able to query whether a stream combination involving physical
* streams is supported by calling
- * {@link CameraManager#isSessionConfigurationWithParametersSupported }.</p>
+ * {@link CameraDevice#isSessionConfigurationSupported }.</p>
* <p>Camera application shouldn't assume that there are at most 1 rear camera and 1 front
* camera in the system. For an application that switches between front and back cameras,
* the recommendation is to switch between the first rear camera and the first front
diff --git a/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java b/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java
index 9fbe348..f3b7b91 100644
--- a/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java
+++ b/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java
@@ -260,7 +260,7 @@
* smaller sizes, then the resulting
* {@link android.hardware.camera2.params.SessionConfiguration session configuration} can
* be tested either by calling {@link CameraDevice#createCaptureSession} or
- * {@link CameraManager#isSessionConfigurationWithParametersSupported}.
+ * {@link CameraDeviceSetup#isSessionConfigurationSupported}.
*
* @return non-modifiable ascending list of available sizes.
*/
diff --git a/core/java/android/hardware/face/FaceManager.java b/core/java/android/hardware/face/FaceManager.java
index 1b0a485..066c45f 100644
--- a/core/java/android/hardware/face/FaceManager.java
+++ b/core/java/android/hardware/face/FaceManager.java
@@ -18,7 +18,6 @@
import static android.Manifest.permission.INTERACT_ACROSS_USERS;
import static android.Manifest.permission.MANAGE_BIOMETRIC;
-import static android.Manifest.permission.TEST_BIOMETRIC;
import static android.Manifest.permission.USE_BACKGROUND_FACE_AUTHENTICATION;
import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_LOCKOUT_NONE;
@@ -30,7 +29,6 @@
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.SystemService;
-import android.annotation.TestApi;
import android.content.Context;
import android.content.pm.PackageManager;
import android.hardware.biometrics.BiometricAuthenticator;
@@ -38,7 +36,6 @@
import android.hardware.biometrics.BiometricFaceConstants;
import android.hardware.biometrics.BiometricPrompt;
import android.hardware.biometrics.BiometricStateListener;
-import android.hardware.biometrics.BiometricTestSession;
import android.hardware.biometrics.CryptoObject;
import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
import android.os.Binder;
@@ -72,7 +69,6 @@
*/
@FlaggedApi(FLAG_FACE_BACKGROUND_AUTHENTICATION)
@SystemApi
-@TestApi
@SystemService(Context.FACE_SERVICE)
public class FaceManager implements BiometricAuthenticator, BiometricFaceConstants {
@@ -784,8 +780,6 @@
* @hide
*/
@NonNull
- @TestApi
- @FlaggedApi(FLAG_FACE_BACKGROUND_AUTHENTICATION)
public List<FaceSensorProperties> getSensorProperties() {
final List<FaceSensorProperties> properties = new ArrayList<>();
final List<FaceSensorPropertiesInternal> internalProperties
@@ -1634,23 +1628,4 @@
Slog.w(TAG, "Unknown enrollment acquired message: " + acquireInfo + ", " + vendorCode);
return null;
}
-
- /**
- * Retrieves a test session for FaceManager.
- *
- * @hide
- */
- @TestApi
- @NonNull
- @RequiresPermission(TEST_BIOMETRIC)
- @FlaggedApi(FLAG_FACE_BACKGROUND_AUTHENTICATION)
- public BiometricTestSession createTestSession(int sensorId) {
- try {
- return new BiometricTestSession(mContext, sensorId,
- (context, sensorId1, callback) -> mService
- .createTestSession(sensorId1, callback, context.getOpPackageName()));
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
-}
\ No newline at end of file
+}
diff --git a/core/java/android/hardware/face/FaceSensorProperties.java b/core/java/android/hardware/face/FaceSensorProperties.java
index a1ddb0e..f613127 100644
--- a/core/java/android/hardware/face/FaceSensorProperties.java
+++ b/core/java/android/hardware/face/FaceSensorProperties.java
@@ -16,12 +16,8 @@
package android.hardware.face;
-import static android.hardware.biometrics.Flags.FLAG_FACE_BACKGROUND_AUTHENTICATION;
-
-import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
-import android.annotation.TestApi;
import android.hardware.biometrics.ComponentInfoInternal;
import android.hardware.biometrics.SensorProperties;
@@ -34,8 +30,6 @@
* Container for face sensor properties.
* @hide
*/
-@TestApi
-@FlaggedApi(FLAG_FACE_BACKGROUND_AUTHENTICATION)
public class FaceSensorProperties extends SensorProperties {
/**
* @hide
diff --git a/core/java/android/hardware/face/IFaceService.aidl b/core/java/android/hardware/face/IFaceService.aidl
index 6515312..b98c0cb 100644
--- a/core/java/android/hardware/face/IFaceService.aidl
+++ b/core/java/android/hardware/face/IFaceService.aidl
@@ -39,7 +39,7 @@
interface IFaceService {
// Creates a test session with the specified sensorId
- @EnforcePermission("TEST_BIOMETRIC")
+ @EnforcePermission("USE_BIOMETRIC_INTERNAL")
ITestSession createTestSession(int sensorId, ITestSessionCallback callback, String opPackageName);
// Requests a proto dump of the specified sensor
diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl
index 1f54959..2816f77 100644
--- a/core/java/android/hardware/input/IInputManager.aidl
+++ b/core/java/android/hardware/input/IInputManager.aidl
@@ -27,6 +27,7 @@
import android.hardware.input.IKeyboardBacklightState;
import android.hardware.input.IStickyModifierStateListener;
import android.hardware.input.ITabletModeChangedListener;
+import android.hardware.input.KeyboardLayoutSelectionResult;
import android.hardware.input.TouchCalibration;
import android.os.CombinedVibration;
import android.hardware.input.IInputSensorEventListener;
@@ -120,8 +121,9 @@
String keyboardLayoutDescriptor);
// New Keyboard layout config APIs
- String getKeyboardLayoutForInputDevice(in InputDeviceIdentifier identifier, int userId,
- in InputMethodInfo imeInfo, in InputMethodSubtype imeSubtype);
+ KeyboardLayoutSelectionResult getKeyboardLayoutForInputDevice(
+ in InputDeviceIdentifier identifier, int userId, in InputMethodInfo imeInfo,
+ in InputMethodSubtype imeSubtype);
@EnforcePermission("SET_KEYBOARD_LAYOUT")
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index 744dfae9..a1242fb 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -784,10 +784,10 @@
*
* @hide
*/
- @Nullable
- public String getKeyboardLayoutForInputDevice(@NonNull InputDeviceIdentifier identifier,
- @UserIdInt int userId, @NonNull InputMethodInfo imeInfo,
- @Nullable InputMethodSubtype imeSubtype) {
+ @NonNull
+ public KeyboardLayoutSelectionResult getKeyboardLayoutForInputDevice(
+ @NonNull InputDeviceIdentifier identifier, @UserIdInt int userId,
+ @NonNull InputMethodInfo imeInfo, @Nullable InputMethodSubtype imeSubtype) {
try {
return mIm.getKeyboardLayoutForInputDevice(identifier, userId, imeInfo, imeSubtype);
} catch (RemoteException ex) {
diff --git a/core/java/android/hardware/input/KeyboardLayoutSelectionResult.aidl b/core/java/android/hardware/input/KeyboardLayoutSelectionResult.aidl
new file mode 100644
index 0000000..13be2ff
--- /dev/null
+++ b/core/java/android/hardware/input/KeyboardLayoutSelectionResult.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.input;
+
+parcelable KeyboardLayoutSelectionResult;
diff --git a/core/java/android/hardware/input/KeyboardLayoutSelectionResult.java b/core/java/android/hardware/input/KeyboardLayoutSelectionResult.java
new file mode 100644
index 0000000..5a1c947
--- /dev/null
+++ b/core/java/android/hardware/input/KeyboardLayoutSelectionResult.java
@@ -0,0 +1,260 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.input;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcelable;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodSubtype;
+
+import com.android.internal.util.DataClass;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Provides information about the selected layout and the selection criteria when the caller calls
+ * {@link InputManager#getKeyboardLayoutForInputDevice(InputDeviceIdentifier, int, InputMethodInfo,
+ * InputMethodSubtype)}
+ *
+ * @hide
+ */
+
+@DataClass(genParcelable = true, genToString = true, genEqualsHashCode = true)
+public final class KeyboardLayoutSelectionResult implements Parcelable {
+ @Nullable
+ private final String mLayoutDescriptor;
+
+ /** Unspecified layout selection criteria */
+ public static final int LAYOUT_SELECTION_CRITERIA_UNSPECIFIED = 0;
+
+ /** Manual selection by user */
+ public static final int LAYOUT_SELECTION_CRITERIA_USER = 1;
+
+ /** Auto-detection based on device provided language tag and layout type */
+ public static final int LAYOUT_SELECTION_CRITERIA_DEVICE = 2;
+
+ /** Auto-detection based on IME provided language tag and layout type */
+ public static final int LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD = 3;
+
+ /** Default selection */
+ public static final int LAYOUT_SELECTION_CRITERIA_DEFAULT = 4;
+
+ /** Failed layout selection */
+ public static final KeyboardLayoutSelectionResult FAILED = new KeyboardLayoutSelectionResult(
+ null, LAYOUT_SELECTION_CRITERIA_UNSPECIFIED);
+
+ @LayoutSelectionCriteria
+ private final int mSelectionCriteria;
+
+
+
+ // Code below generated by codegen v1.0.23.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/hardware/input/KeyboardLayoutSelectionResult.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ @IntDef(prefix = "LAYOUT_SELECTION_CRITERIA_", value = {
+ LAYOUT_SELECTION_CRITERIA_UNSPECIFIED,
+ LAYOUT_SELECTION_CRITERIA_USER,
+ LAYOUT_SELECTION_CRITERIA_DEVICE,
+ LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD,
+ LAYOUT_SELECTION_CRITERIA_DEFAULT
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @DataClass.Generated.Member
+ public @interface LayoutSelectionCriteria {}
+
+ @DataClass.Generated.Member
+ public static String layoutSelectionCriteriaToString(@LayoutSelectionCriteria int value) {
+ switch (value) {
+ case LAYOUT_SELECTION_CRITERIA_UNSPECIFIED:
+ return "LAYOUT_SELECTION_CRITERIA_UNSPECIFIED";
+ case LAYOUT_SELECTION_CRITERIA_USER:
+ return "LAYOUT_SELECTION_CRITERIA_USER";
+ case LAYOUT_SELECTION_CRITERIA_DEVICE:
+ return "LAYOUT_SELECTION_CRITERIA_DEVICE";
+ case LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD:
+ return "LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD";
+ case LAYOUT_SELECTION_CRITERIA_DEFAULT:
+ return "LAYOUT_SELECTION_CRITERIA_DEFAULT";
+ default: return Integer.toHexString(value);
+ }
+ }
+
+ @DataClass.Generated.Member
+ public KeyboardLayoutSelectionResult(
+ @Nullable String layoutDescriptor,
+ @LayoutSelectionCriteria int selectionCriteria) {
+ this.mLayoutDescriptor = layoutDescriptor;
+ this.mSelectionCriteria = selectionCriteria;
+
+ if (!(mSelectionCriteria == LAYOUT_SELECTION_CRITERIA_UNSPECIFIED)
+ && !(mSelectionCriteria == LAYOUT_SELECTION_CRITERIA_USER)
+ && !(mSelectionCriteria == LAYOUT_SELECTION_CRITERIA_DEVICE)
+ && !(mSelectionCriteria == LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD)
+ && !(mSelectionCriteria == LAYOUT_SELECTION_CRITERIA_DEFAULT)) {
+ throw new java.lang.IllegalArgumentException(
+ "selectionCriteria was " + mSelectionCriteria + " but must be one of: "
+ + "LAYOUT_SELECTION_CRITERIA_UNSPECIFIED(" + LAYOUT_SELECTION_CRITERIA_UNSPECIFIED + "), "
+ + "LAYOUT_SELECTION_CRITERIA_USER(" + LAYOUT_SELECTION_CRITERIA_USER + "), "
+ + "LAYOUT_SELECTION_CRITERIA_DEVICE(" + LAYOUT_SELECTION_CRITERIA_DEVICE + "), "
+ + "LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD(" + LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD + "), "
+ + "LAYOUT_SELECTION_CRITERIA_DEFAULT(" + LAYOUT_SELECTION_CRITERIA_DEFAULT + ")");
+ }
+
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public @Nullable String getLayoutDescriptor() {
+ return mLayoutDescriptor;
+ }
+
+ @DataClass.Generated.Member
+ public @LayoutSelectionCriteria int getSelectionCriteria() {
+ return mSelectionCriteria;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public String toString() {
+ // You can override field toString logic by defining methods like:
+ // String fieldNameToString() { ... }
+
+ return "KeyboardLayoutSelectionResult { " +
+ "layoutDescriptor = " + mLayoutDescriptor + ", " +
+ "selectionCriteria = " + layoutSelectionCriteriaToString(mSelectionCriteria) +
+ " }";
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public boolean equals(@Nullable Object o) {
+ // You can override field equality logic by defining either of the methods like:
+ // boolean fieldNameEquals(KeyboardLayoutSelectionResult other) { ... }
+ // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ @SuppressWarnings("unchecked")
+ KeyboardLayoutSelectionResult that = (KeyboardLayoutSelectionResult) o;
+ //noinspection PointlessBooleanExpression
+ return true
+ && java.util.Objects.equals(mLayoutDescriptor, that.mLayoutDescriptor)
+ && mSelectionCriteria == that.mSelectionCriteria;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int hashCode() {
+ // You can override field hashCode logic by defining methods like:
+ // int fieldNameHashCode() { ... }
+
+ int _hash = 1;
+ _hash = 31 * _hash + java.util.Objects.hashCode(mLayoutDescriptor);
+ _hash = 31 * _hash + mSelectionCriteria;
+ return _hash;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
+ // You can override field parcelling by defining methods like:
+ // void parcelFieldName(Parcel dest, int flags) { ... }
+
+ byte flg = 0;
+ if (mLayoutDescriptor != null) flg |= 0x1;
+ dest.writeByte(flg);
+ if (mLayoutDescriptor != null) dest.writeString(mLayoutDescriptor);
+ dest.writeInt(mSelectionCriteria);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ /* package-private */ KeyboardLayoutSelectionResult(@NonNull android.os.Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ byte flg = in.readByte();
+ String layoutDescriptor = (flg & 0x1) == 0 ? null : in.readString();
+ int selectionCriteria = in.readInt();
+
+ this.mLayoutDescriptor = layoutDescriptor;
+ this.mSelectionCriteria = selectionCriteria;
+
+ if (!(mSelectionCriteria == LAYOUT_SELECTION_CRITERIA_UNSPECIFIED)
+ && !(mSelectionCriteria == LAYOUT_SELECTION_CRITERIA_USER)
+ && !(mSelectionCriteria == LAYOUT_SELECTION_CRITERIA_DEVICE)
+ && !(mSelectionCriteria == LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD)
+ && !(mSelectionCriteria == LAYOUT_SELECTION_CRITERIA_DEFAULT)) {
+ throw new java.lang.IllegalArgumentException(
+ "selectionCriteria was " + mSelectionCriteria + " but must be one of: "
+ + "LAYOUT_SELECTION_CRITERIA_UNSPECIFIED(" + LAYOUT_SELECTION_CRITERIA_UNSPECIFIED + "), "
+ + "LAYOUT_SELECTION_CRITERIA_USER(" + LAYOUT_SELECTION_CRITERIA_USER + "), "
+ + "LAYOUT_SELECTION_CRITERIA_DEVICE(" + LAYOUT_SELECTION_CRITERIA_DEVICE + "), "
+ + "LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD(" + LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD + "), "
+ + "LAYOUT_SELECTION_CRITERIA_DEFAULT(" + LAYOUT_SELECTION_CRITERIA_DEFAULT + ")");
+ }
+
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<KeyboardLayoutSelectionResult> CREATOR
+ = new Parcelable.Creator<KeyboardLayoutSelectionResult>() {
+ @Override
+ public KeyboardLayoutSelectionResult[] newArray(int size) {
+ return new KeyboardLayoutSelectionResult[size];
+ }
+
+ @Override
+ public KeyboardLayoutSelectionResult createFromParcel(@NonNull android.os.Parcel in) {
+ return new KeyboardLayoutSelectionResult(in);
+ }
+ };
+
+ @DataClass.Generated(
+ time = 1709568115865L,
+ codegenVersion = "1.0.23",
+ sourceFile = "frameworks/base/core/java/android/hardware/input/KeyboardLayoutSelectionResult.java",
+ inputSignatures = "private final @android.annotation.Nullable java.lang.String mLayoutDescriptor\npublic static final int LAYOUT_SELECTION_CRITERIA_UNSPECIFIED\npublic static final int LAYOUT_SELECTION_CRITERIA_USER\npublic static final int LAYOUT_SELECTION_CRITERIA_DEVICE\npublic static final int LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD\npublic static final int LAYOUT_SELECTION_CRITERIA_DEFAULT\npublic static final android.hardware.input.KeyboardLayoutSelectionResult FAILED\nprivate final @android.hardware.input.KeyboardLayoutSelectionResult.LayoutSelectionCriteria int mSelectionCriteria\nclass KeyboardLayoutSelectionResult extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genParcelable=true, genToString=true, genEqualsHashCode=true)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/core/java/android/os/GraphicsEnvironment.java b/core/java/android/os/GraphicsEnvironment.java
index 3149de4..beb9a93 100644
--- a/core/java/android/os/GraphicsEnvironment.java
+++ b/core/java/android/os/GraphicsEnvironment.java
@@ -120,7 +120,6 @@
private ClassLoader mClassLoader;
private String mLibrarySearchPaths;
private String mLibraryPermittedPaths;
- private GameManager mGameManager;
private int mAngleOptInIndex = -1;
private boolean mShouldUseAngle = false;
@@ -134,8 +133,6 @@
final ApplicationInfo appInfoWithMetaData =
getAppInfoWithMetadata(context, pm, packageName);
- mGameManager = context.getSystemService(GameManager.class);
-
Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "setupGpuLayers");
setupGpuLayers(context, coreSettings, pm, packageName, appInfoWithMetaData);
Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS);
@@ -161,9 +158,11 @@
Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS);
Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "notifyGraphicsEnvironmentSetup");
- if (mGameManager != null
- && appInfoWithMetaData.category == ApplicationInfo.CATEGORY_GAME) {
- mGameManager.notifyGraphicsEnvironmentSetup();
+ if (appInfoWithMetaData.category == ApplicationInfo.CATEGORY_GAME) {
+ final GameManager gameManager = context.getSystemService(GameManager.class);
+ if (gameManager != null) {
+ gameManager.notifyGraphicsEnvironmentSetup();
+ }
}
Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS);
}
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index ccb534e..9757a10 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -4708,6 +4708,9 @@
* Sets the user as enabled, if such an user exists.
*
* <p>Note that the default is true, it's only that managed profiles might not be enabled.
+ * (Managed profiles created by DevicePolicyManager will start out disabled, and DPM will later
+ * toggle them to enabled once they are provisioned. This is the primary purpose of the
+ * {@link UserInfo#FLAG_DISABLED} flag.)
* Also ephemeral users can be disabled to indicate that their removal is in progress and they
* shouldn't be re-entered. Therefore ephemeral users should not be re-enabled once disabled.
*
@@ -5259,7 +5262,7 @@
/**
* Returns list of the profiles of userId including userId itself.
- * Note that this returns only enabled.
+ * Note that this returns only {@link UserInfo#isEnabled() enabled} profiles.
* <p>Note that this includes all profile types (not including Restricted profiles).
*
* <p>Requires {@link android.Manifest.permission#MANAGE_USERS} or
diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig
index abfa4e3..d9400ac 100644
--- a/core/java/android/os/flags.aconfig
+++ b/core/java/android/os/flags.aconfig
@@ -1,4 +1,5 @@
package: "android.os"
+container: "system"
flag {
name: "android_os_build_vanilla_ice_cream"
@@ -40,6 +41,7 @@
namespace: "profile_experiences"
description: "Guards a new Private Profile type in UserManager - everything from its setup to config to deletion."
bug: "299069460"
+ is_exported: true
}
flag {
diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java
index fa9f03d..410f510 100644
--- a/core/java/android/permission/PermissionManager.java
+++ b/core/java/android/permission/PermissionManager.java
@@ -1788,6 +1788,9 @@
/**
* Gets the permission states for requested package and persistent device.
+ * <p>
+ * <strong>Note: </strong>Default device permissions are not inherited in this API. Returns the
+ * exact permission states for the requested device.
*
* @param packageName name of the package you are checking against
* @param persistentDeviceId id of the persistent device you are checking against
@@ -2073,5 +2076,29 @@
return new PermissionState[size];
}
};
+
+ /** @hide */
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ PermissionState that = (PermissionState) o;
+ return mGranted == that.mGranted && mFlags == that.mFlags;
+ }
+
+ /** @hide */
+ @Override
+ public int hashCode() {
+ return Objects.hash(mGranted, mFlags);
+ }
+
+ /** @hide */
+ @Override
+ public String toString() {
+ return "PermissionState{"
+ + "mGranted=" + mGranted
+ + ", mFlags=" + mFlags
+ + '}';
+ }
}
}
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index de7008b..dc782d4 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -138,3 +138,11 @@
bug: "325356776"
}
+flag {
+ name: "runtime_permission_appops_mapping_enabled"
+ is_fixed_read_only: true
+ namespace: "permissions"
+ description: "Use runtime permission state to determine appop state"
+ bug: "266164193"
+}
+
diff --git a/core/java/android/provider/CallLog.java b/core/java/android/provider/CallLog.java
index c03dc71..120846c 100644
--- a/core/java/android/provider/CallLog.java
+++ b/core/java/android/provider/CallLog.java
@@ -667,7 +667,8 @@
@FlaggedApi(Flags.FLAG_BUSINESS_CALL_COMPOSER)
public @NonNull AddCallParametersBuilder setAssertedDisplayName(
String assertedDisplayName) {
- if (assertedDisplayName.length() > MAX_NUMBER_OF_CHARACTERS) {
+ if (assertedDisplayName != null
+ && assertedDisplayName.length() > MAX_NUMBER_OF_CHARACTERS) {
throw new IllegalArgumentException("assertedDisplayName exceeds the character"
+ " limit of " + MAX_NUMBER_OF_CHARACTERS + ".");
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 51585af..eea6464 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -11576,6 +11576,15 @@
"extra_low_power_warning_acknowledged";
/**
+ * Whether the emergency thermal alert would be disabled
+ * (0: default) or not (1).
+ *
+ * @hide
+ */
+ public static final String EMERGENCY_THERMAL_ALERT_DISABLED =
+ "emergency_thermal_alert_disabled";
+
+ /**
* 0 (default) Auto battery saver suggestion has not been suppressed. 1) it has been
* suppressed.
* @hide
diff --git a/core/java/android/service/notification/ZenAdapters.java b/core/java/android/service/notification/ZenAdapters.java
new file mode 100644
index 0000000..b249815
--- /dev/null
+++ b/core/java/android/service/notification/ZenAdapters.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.notification;
+
+import android.annotation.NonNull;
+import android.app.Flags;
+import android.app.NotificationManager.Policy;
+
+/**
+ * Converters between different Zen representations.
+ * @hide
+ */
+public class ZenAdapters {
+
+ /** Maps {@link Policy} to {@link ZenPolicy}. */
+ @NonNull
+ public static ZenPolicy notificationPolicyToZenPolicy(@NonNull Policy policy) {
+ ZenPolicy.Builder zenPolicyBuilder = new ZenPolicy.Builder()
+ .allowAlarms(policy.allowAlarms())
+ .allowCalls(
+ policy.allowCalls()
+ ? notificationPolicySendersToZenPolicyPeopleType(
+ policy.allowCallsFrom())
+ : ZenPolicy.PEOPLE_TYPE_NONE)
+ .allowConversations(
+ policy.allowConversations()
+ ? notificationPolicyConversationSendersToZenPolicy(
+ policy.allowConversationsFrom())
+ : ZenPolicy.CONVERSATION_SENDERS_NONE)
+ .allowEvents(policy.allowEvents())
+ .allowMedia(policy.allowMedia())
+ .allowMessages(
+ policy.allowMessages()
+ ? notificationPolicySendersToZenPolicyPeopleType(
+ policy.allowMessagesFrom())
+ : ZenPolicy.PEOPLE_TYPE_NONE)
+ .allowReminders(policy.allowReminders())
+ .allowRepeatCallers(policy.allowRepeatCallers())
+ .allowSystem(policy.allowSystem());
+
+ if (policy.suppressedVisualEffects != Policy.SUPPRESSED_EFFECTS_UNSET) {
+ zenPolicyBuilder.showBadges(policy.showBadges())
+ .showFullScreenIntent(policy.showFullScreenIntents())
+ .showInAmbientDisplay(policy.showAmbient())
+ .showInNotificationList(policy.showInNotificationList())
+ .showLights(policy.showLights())
+ .showPeeking(policy.showPeeking())
+ .showStatusBarIcons(policy.showStatusBarIcons());
+ }
+
+ if (Flags.modesApi()) {
+ zenPolicyBuilder.allowPriorityChannels(policy.allowPriorityChannels());
+ }
+
+ return zenPolicyBuilder.build();
+ }
+
+ /** Maps {@link ZenPolicy.PeopleType} enum to {@link Policy.PrioritySenders}. */
+ @Policy.PrioritySenders
+ public static int zenPolicyPeopleTypeToNotificationPolicySenders(
+ @ZenPolicy.PeopleType int zpPeopleType, @Policy.PrioritySenders int defaultResult) {
+ switch (zpPeopleType) {
+ case ZenPolicy.PEOPLE_TYPE_ANYONE:
+ return Policy.PRIORITY_SENDERS_ANY;
+ case ZenPolicy.PEOPLE_TYPE_CONTACTS:
+ return Policy.PRIORITY_SENDERS_CONTACTS;
+ case ZenPolicy.PEOPLE_TYPE_STARRED:
+ return Policy.PRIORITY_SENDERS_STARRED;
+ default:
+ return defaultResult;
+ }
+ }
+
+ /** Maps {@link Policy.PrioritySenders} enum to {@link ZenPolicy.PeopleType}. */
+ @ZenPolicy.PeopleType
+ public static int notificationPolicySendersToZenPolicyPeopleType(
+ @Policy.PrioritySenders int npPrioritySenders) {
+ switch (npPrioritySenders) {
+ case Policy.PRIORITY_SENDERS_ANY:
+ return ZenPolicy.PEOPLE_TYPE_ANYONE;
+ case Policy.PRIORITY_SENDERS_CONTACTS:
+ return ZenPolicy.PEOPLE_TYPE_CONTACTS;
+ case Policy.PRIORITY_SENDERS_STARRED:
+ default:
+ return ZenPolicy.PEOPLE_TYPE_STARRED;
+ }
+ }
+
+ /** Maps {@link ZenPolicy.ConversationSenders} enum to {@link Policy.ConversationSenders}. */
+ @Policy.ConversationSenders
+ public static int zenPolicyConversationSendersToNotificationPolicy(
+ @ZenPolicy.ConversationSenders int zpConversationSenders,
+ @Policy.ConversationSenders int defaultResult) {
+ switch (zpConversationSenders) {
+ case ZenPolicy.CONVERSATION_SENDERS_ANYONE:
+ return Policy.CONVERSATION_SENDERS_ANYONE;
+ case ZenPolicy.CONVERSATION_SENDERS_IMPORTANT:
+ return Policy.CONVERSATION_SENDERS_IMPORTANT;
+ case ZenPolicy.CONVERSATION_SENDERS_NONE:
+ return Policy.CONVERSATION_SENDERS_NONE;
+ default:
+ return defaultResult;
+ }
+ }
+
+ /** Maps {@link Policy.ConversationSenders} enum to {@link ZenPolicy.ConversationSenders}. */
+ @ZenPolicy.ConversationSenders
+ private static int notificationPolicyConversationSendersToZenPolicy(
+ @Policy.ConversationSenders int npPriorityConversationSenders) {
+ switch (npPriorityConversationSenders) {
+ case Policy.CONVERSATION_SENDERS_ANYONE:
+ return ZenPolicy.CONVERSATION_SENDERS_ANYONE;
+ case Policy.CONVERSATION_SENDERS_IMPORTANT:
+ return ZenPolicy.CONVERSATION_SENDERS_IMPORTANT;
+ case Policy.CONVERSATION_SENDERS_NONE:
+ return ZenPolicy.CONVERSATION_SENDERS_NONE;
+ default: // including Policy.CONVERSATION_SENDERS_UNSET
+ return ZenPolicy.CONVERSATION_SENDERS_UNSET;
+ }
+ }
+}
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index d9ca935..1d6dd1e 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -24,6 +24,9 @@
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_LIGHTS;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_OFF;
+import static android.service.notification.ZenAdapters.notificationPolicySendersToZenPolicyPeopleType;
+import static android.service.notification.ZenAdapters.zenPolicyConversationSendersToNotificationPolicy;
+import static android.service.notification.ZenAdapters.zenPolicyPeopleTypeToNotificationPolicySenders;
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
@@ -1269,11 +1272,11 @@
public ZenPolicy toZenPolicy() {
ZenPolicy.Builder builder = new ZenPolicy.Builder()
.allowCalls(allowCalls
- ? ZenModeConfig.getZenPolicySenders(allowCallsFrom)
+ ? notificationPolicySendersToZenPolicyPeopleType(allowCallsFrom)
: ZenPolicy.PEOPLE_TYPE_NONE)
.allowRepeatCallers(allowRepeatCallers)
.allowMessages(allowMessages
- ? ZenModeConfig.getZenPolicySenders(allowMessagesFrom)
+ ? notificationPolicySendersToZenPolicyPeopleType(allowMessagesFrom)
: ZenPolicy.PEOPLE_TYPE_NONE)
.allowReminders(allowReminders)
.allowEvents(allowEvents)
@@ -1333,14 +1336,14 @@
if (zenPolicy.isCategoryAllowed(ZenPolicy.PRIORITY_CATEGORY_MESSAGES,
isPriorityCategoryEnabled(Policy.PRIORITY_CATEGORY_MESSAGES, defaultPolicy))) {
priorityCategories |= Policy.PRIORITY_CATEGORY_MESSAGES;
- messageSenders = getNotificationPolicySenders(zenPolicy.getPriorityMessageSenders(),
- messageSenders);
+ messageSenders = zenPolicyPeopleTypeToNotificationPolicySenders(
+ zenPolicy.getPriorityMessageSenders(), messageSenders);
}
if (zenPolicy.isCategoryAllowed(ZenPolicy.PRIORITY_CATEGORY_CONVERSATIONS,
isPriorityCategoryEnabled(Policy.PRIORITY_CATEGORY_CONVERSATIONS, defaultPolicy))) {
priorityCategories |= Policy.PRIORITY_CATEGORY_CONVERSATIONS;
- conversationSenders = getConversationSendersWithDefault(
+ conversationSenders = zenPolicyConversationSendersToNotificationPolicy(
zenPolicy.getPriorityConversationSenders(), conversationSenders);
} else {
conversationSenders = CONVERSATION_SENDERS_NONE;
@@ -1349,8 +1352,8 @@
if (zenPolicy.isCategoryAllowed(ZenPolicy.PRIORITY_CATEGORY_CALLS,
isPriorityCategoryEnabled(Policy.PRIORITY_CATEGORY_CALLS, defaultPolicy))) {
priorityCategories |= Policy.PRIORITY_CATEGORY_CALLS;
- callSenders = getNotificationPolicySenders(zenPolicy.getPriorityCallSenders(),
- callSenders);
+ callSenders = zenPolicyPeopleTypeToNotificationPolicySenders(
+ zenPolicy.getPriorityCallSenders(), callSenders);
}
if (zenPolicy.isCategoryAllowed(ZenPolicy.PRIORITY_CATEGORY_REPEAT_CALLERS,
@@ -1449,47 +1452,6 @@
return (policy.suppressedVisualEffects & visualEffect) == 0;
}
- private static int getNotificationPolicySenders(@ZenPolicy.PeopleType int senders,
- int defaultPolicySender) {
- switch (senders) {
- case ZenPolicy.PEOPLE_TYPE_ANYONE:
- return Policy.PRIORITY_SENDERS_ANY;
- case ZenPolicy.PEOPLE_TYPE_CONTACTS:
- return Policy.PRIORITY_SENDERS_CONTACTS;
- case ZenPolicy.PEOPLE_TYPE_STARRED:
- return Policy.PRIORITY_SENDERS_STARRED;
- default:
- return defaultPolicySender;
- }
- }
-
- private static int getConversationSendersWithDefault(@ZenPolicy.ConversationSenders int senders,
- int defaultPolicySender) {
- switch (senders) {
- case ZenPolicy.CONVERSATION_SENDERS_ANYONE:
- case ZenPolicy.CONVERSATION_SENDERS_IMPORTANT:
- case ZenPolicy.CONVERSATION_SENDERS_NONE:
- return senders;
- default:
- return defaultPolicySender;
- }
- }
-
- /**
- * Maps NotificationManager.Policy senders type to ZenPolicy.PeopleType
- */
- public static @ZenPolicy.PeopleType int getZenPolicySenders(int senders) {
- switch (senders) {
- case Policy.PRIORITY_SENDERS_ANY:
- return ZenPolicy.PEOPLE_TYPE_ANYONE;
- case Policy.PRIORITY_SENDERS_CONTACTS:
- return ZenPolicy.PEOPLE_TYPE_CONTACTS;
- case Policy.PRIORITY_SENDERS_STARRED:
- default:
- return ZenPolicy.PEOPLE_TYPE_STARRED;
- }
- }
-
public Policy toNotificationPolicy() {
int priorityCategories = 0;
int priorityCallSenders = Policy.PRIORITY_SENDERS_CONTACTS;
@@ -1524,7 +1486,7 @@
}
priorityCallSenders = sourceToPrioritySenders(allowCallsFrom, priorityCallSenders);
priorityMessageSenders = sourceToPrioritySenders(allowMessagesFrom, priorityMessageSenders);
- priorityConversationSenders = getConversationSendersWithDefault(
+ priorityConversationSenders = zenPolicyConversationSendersToNotificationPolicy(
allowConversationsFrom, priorityConversationSenders);
int state = areChannelsBypassingDnd ? Policy.STATE_CHANNELS_BYPASSING_DND : 0;
@@ -1559,15 +1521,6 @@
}
}
- private static int prioritySendersToSource(int prioritySenders, int def) {
- switch (prioritySenders) {
- case Policy.PRIORITY_SENDERS_CONTACTS: return SOURCE_CONTACT;
- case Policy.PRIORITY_SENDERS_STARRED: return SOURCE_STAR;
- case Policy.PRIORITY_SENDERS_ANY: return SOURCE_ANYONE;
- default: return def;
- }
- }
-
private static int normalizePrioritySenders(int prioritySenders, int def) {
if (!(prioritySenders == Policy.PRIORITY_SENDERS_CONTACTS
|| prioritySenders == Policy.PRIORITY_SENDERS_STARRED
diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java
index 20adc54..306410c 100644
--- a/core/java/android/service/voice/VoiceInteractionService.java
+++ b/core/java/android/service/voice/VoiceInteractionService.java
@@ -519,7 +519,7 @@
@NonNull String keyphrase, @SuppressLint("UseIcu") @NonNull Locale locale,
@NonNull @CallbackExecutor Executor executor,
@NonNull AlwaysOnHotwordDetector.Callback callback) {
- // TODO(b/269080850): Resolve AndroidFrameworkRequiresPermission lint warning
+ // TODO (b/269080850): Resolve AndroidFrameworkRequiresPermission lint warning
Objects.requireNonNull(keyphrase);
Objects.requireNonNull(locale);
@@ -545,10 +545,6 @@
@NonNull SoundTrigger.ModuleProperties moduleProperties,
@NonNull @CallbackExecutor Executor executor,
@NonNull AlwaysOnHotwordDetector.Callback callback) {
- // TODO(b/305787465): Remove the MANAGE_HOTWORD_DETECTION permission enforcement on the
- // {@link #createAlwaysOnHotwordDetectorForTest(String, Locale,
- // SoundTrigger.ModuleProperties, AlwaysOnHotwordDetector.Callback)} and replace with the
- // permission RECEIVE_SANDBOX_TRIGGER_AUDIO when it is fully launched.
Objects.requireNonNull(keyphrase);
Objects.requireNonNull(locale);
@@ -615,11 +611,6 @@
@Nullable PersistableBundle options,
@Nullable SharedMemory sharedMemory,
@SuppressLint("MissingNullability") AlwaysOnHotwordDetector.Callback callback) {
- // TODO(b/305787465): Remove the MANAGE_HOTWORD_DETECTION permission enforcement on the
- // {@link #createAlwaysOnHotwordDetector(String, Locale, PersistableBundle, SharedMemory,
- // AlwaysOnHotwordDetector.Callback)} and replace with the permission
- // RECEIVE_SANDBOX_TRIGGER_AUDIO when it is fully launched.
-
return createAlwaysOnHotwordDetectorInternal(keyphrase, locale,
/* supportHotwordDetectionService= */ true, options, sharedMemory,
/* modulProperties */ null, /* executor= */ null, callback);
@@ -671,11 +662,7 @@
@Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory,
@NonNull @CallbackExecutor Executor executor,
@NonNull AlwaysOnHotwordDetector.Callback callback) {
- // TODO(b/269080850): Resolve AndroidFrameworkRequiresPermission lint warning
- // TODO(b/305787465): Remove the MANAGE_HOTWORD_DETECTION permission enforcement on the
- // {@link #createAlwaysOnHotwordDetector(String, Locale, PersistableBundle, SharedMemory,
- // Executor, AlwaysOnHotwordDetector.Callback)} and replace with the permission
- // RECEIVE_SANDBOX_TRIGGER_AUDIO when it is fully launched.
+ // TODO (b/269080850): Resolve AndroidFrameworkRequiresPermission lint warning
Objects.requireNonNull(keyphrase);
Objects.requireNonNull(locale);
@@ -702,10 +689,6 @@
@NonNull SoundTrigger.ModuleProperties moduleProperties,
@NonNull @CallbackExecutor Executor executor,
@NonNull AlwaysOnHotwordDetector.Callback callback) {
- // TODO(b/305787465): Remove the MANAGE_HOTWORD_DETECTION permission enforcement on the
- // {@link #createAlwaysOnHotwordDetectorForTest(String, Locale, PersistableBundle,
- // SharedMemory, SoundTrigger.ModuleProperties, Executor, AlwaysOnHotwordDetector.Callback)}
- // and replace with the permission RECEIVE_SANDBOX_TRIGGER_AUDIO when it is fully launched.
Objects.requireNonNull(keyphrase);
Objects.requireNonNull(locale);
diff --git a/core/java/android/view/ScrollFeedbackProvider.java b/core/java/android/view/ScrollFeedbackProvider.java
index 8a44d4f..798203f 100644
--- a/core/java/android/view/ScrollFeedbackProvider.java
+++ b/core/java/android/view/ScrollFeedbackProvider.java
@@ -21,8 +21,11 @@
import android.view.flags.Flags;
/**
- * Interface to represent an entity giving consistent feedback for different events surrounding view
- * scroll.
+ * Provides feedback to the user for scroll events on a {@link View}. The type of feedback provided
+ * to the user may depend on the {@link InputDevice} that generated the scroll events.
+ *
+ * <p>An example of the type of feedback that this interface may provide is haptic feedback (that
+ * is, tactile feedback that provide the user physical feedback for their scroll).
*
* <p>The interface provides methods for the client to report different scroll events. The client
* should report all scroll events that they want to be considered for scroll feedback using the
diff --git a/core/java/android/view/inputmethod/ImeTracker.java b/core/java/android/view/inputmethod/ImeTracker.java
index 7f79661..d992feb 100644
--- a/core/java/android/view/inputmethod/ImeTracker.java
+++ b/core/java/android/view/inputmethod/ImeTracker.java
@@ -182,7 +182,6 @@
PHASE_CLIENT_ANIMATION_FINISHED_SHOW,
PHASE_CLIENT_ANIMATION_FINISHED_HIDE,
PHASE_WM_ABORT_SHOW_IME_POST_LAYOUT,
- PHASE_CLIENT_ANIMATION_FINISHED_HIDE,
PHASE_IME_SHOW_WINDOW,
PHASE_IME_HIDE_WINDOW,
PHASE_IME_PRIVILEGED_OPERATIONS,
diff --git a/core/java/android/webkit/WebViewDelegate.java b/core/java/android/webkit/WebViewDelegate.java
index 3fc0a30..8501474 100644
--- a/core/java/android/webkit/WebViewDelegate.java
+++ b/core/java/android/webkit/WebViewDelegate.java
@@ -175,8 +175,16 @@
/**
* Adds the WebView asset path to {@link android.content.res.AssetManager}.
+ * If {@link android.content.res.Flags#FLAG_REGISTER_RESOURCE_PATHS} is enabled, this function
+ * will be a no-op because the asset paths appending work will only be handled by
+ * {@link android.content.res.Resources#registerResourcePaths(String, ApplicationInfo)},
+ * otherwise it behaves the old way.
*/
public void addWebViewAssetPath(Context context) {
+ if (android.content.res.Flags.registerResourcePaths()) {
+ return;
+ }
+
final String[] newAssetPaths =
WebViewFactory.getLoadedPackageInfo().applicationInfo.getAllApkPaths();
final ApplicationInfo appInfo = context.getApplicationInfo();
diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java
index c748a57..01fdd1d 100644
--- a/core/java/android/webkit/WebViewFactory.java
+++ b/core/java/android/webkit/WebViewFactory.java
@@ -30,6 +30,7 @@
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.Signature;
+import android.content.res.Resources;
import android.os.Build;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -544,8 +545,14 @@
Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactory.getChromiumProviderClass()");
try {
sTimestamps.mAddAssetsStart = SystemClock.uptimeMillis();
- for (String newAssetPath : webViewContext.getApplicationInfo().getAllApkPaths()) {
- initialApplication.getAssets().addAssetPathAsSharedLibrary(newAssetPath);
+ if (android.content.res.Flags.registerResourcePaths()) {
+ Resources.registerResourcePaths(webViewContext.getPackageName(),
+ webViewContext.getApplicationInfo());
+ } else {
+ for (String newAssetPath : webViewContext.getApplicationInfo()
+ .getAllApkPaths()) {
+ initialApplication.getAssets().addAssetPathAsSharedLibrary(newAssetPath);
+ }
}
sTimestamps.mAddAssetsEnd = sTimestamps.mGetClassLoaderStart =
SystemClock.uptimeMillis();
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 0e5747d..fe4ac4e 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -1105,6 +1105,7 @@
SetRemoteCollectionItemListAdapterAction(Parcel parcel) {
mViewId = parcel.readInt();
mIntentId = parcel.readInt();
+ mIsReplacedIntoAction = parcel.readBoolean();
mServiceIntent = parcel.readTypedObject(Intent.CREATOR);
mItems = mServiceIntent != null
? null
@@ -1128,6 +1129,7 @@
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(mViewId);
dest.writeInt(mIntentId);
+ dest.writeBoolean(mIsReplacedIntoAction);
dest.writeTypedObject(mServiceIntent, flags);
if (mItems != null) {
mItems.writeToParcel(dest, flags, /* attached= */ true);
@@ -1209,6 +1211,19 @@
}
/**
+ * The maximum size for RemoteViews with converted RemoteCollectionItemsAdapter.
+ * When converting RemoteViewsAdapter to RemoteCollectionItemsAdapter, we want to put size
+ * limits on each unique RemoteCollectionItems in order to not exceed the transaction size limit
+ * for each parcel (typically 1 MB). We leave a certain ratio of the maximum size as a buffer
+ * for missing calculations of certain parameters (e.g. writing a RemoteCollectionItems to the
+ * parcel will write its Id array as well, but that is missing when writing itschild RemoteViews
+ * directly to the parcel as we did in RemoteViewsService)
+ *
+ * @hide
+ */
+ private static final int MAX_SINGLE_PARCEL_SIZE = (int) (1_000_000 * 0.8);
+
+ /**
* @hide
*/
public CompletableFuture<Void> collectAllIntents() {
@@ -1260,17 +1275,47 @@
return mUriToCollectionMapping.get(uri);
}
- CompletableFuture<Void> collectAllIntentsNoComplete(@NonNull RemoteViews inViews) {
- CompletableFuture<Void> collectionFuture = CompletableFuture.completedFuture(null);
+ public @NonNull CompletableFuture<Void> collectAllIntentsNoComplete(
+ @NonNull RemoteViews inViews) {
+ SparseArray<Intent> idToIntentMapping = new SparseArray<>();
+ // Collect the number of uinque Intent (which is equal to the number of new connections
+ // to make) for size allocation and exclude certain collections from being written to
+ // the parcel to better estimate the space left for reallocation.
+ collectAllIntentsInternal(inViews, idToIntentMapping);
+
+ // Calculate the individual size here
+ int numOfIntents = idToIntentMapping.size();
+ if (numOfIntents == 0) {
+ Log.e(LOG_TAG, "Possibly notifying updates for nonexistent view Id");
+ return CompletableFuture.completedFuture(null);
+ }
+
+ Parcel sizeTestParcel = Parcel.obtain();
+ // Write self RemoteViews to the parcel, which includes the actions/bitmaps/collection
+ // cache to see how much space is left for the RemoteCollectionItems that are to be
+ // updated.
+ RemoteViews.this.writeToParcel(sizeTestParcel,
+ /* flags= */ 0,
+ /* intentsToIgnore= */ idToIntentMapping);
+ int remainingSize = MAX_SINGLE_PARCEL_SIZE - sizeTestParcel.dataSize();
+ sizeTestParcel.recycle();
+
+ int individualSize = remainingSize < 0
+ ? 0
+ : remainingSize / numOfIntents;
+
+ return connectAllUniqueIntents(individualSize, idToIntentMapping);
+ }
+
+ private void collectAllIntentsInternal(@NonNull RemoteViews inViews,
+ @NonNull SparseArray<Intent> idToIntentMapping) {
if (inViews.hasSizedRemoteViews()) {
for (RemoteViews remoteViews : inViews.mSizedRemoteViews) {
- collectionFuture = CompletableFuture.allOf(collectionFuture,
- collectAllIntentsNoComplete(remoteViews));
+ collectAllIntentsInternal(remoteViews, idToIntentMapping);
}
} else if (inViews.hasLandscapeAndPortraitLayouts()) {
- collectionFuture = CompletableFuture.allOf(
- collectAllIntentsNoComplete(inViews.mLandscape),
- collectAllIntentsNoComplete(inViews.mPortrait));
+ collectAllIntentsInternal(inViews.mLandscape, idToIntentMapping);
+ collectAllIntentsInternal(inViews.mPortrait, idToIntentMapping);
} else if (inViews.mActions != null) {
for (Action action : inViews.mActions) {
if (action instanceof SetRemoteCollectionItemListAdapterAction rca) {
@@ -1280,13 +1325,16 @@
}
if (rca.mIntentId != -1 && rca.mIsReplacedIntoAction) {
- final String uri = mIdToUriMapping.get(rca.mIntentId);
- collectionFuture = CompletableFuture.allOf(collectionFuture,
- getItemsFutureFromIntentWithTimeout(rca.mServiceIntent)
- .thenAccept(rc -> {
- rc.setHierarchyRootData(getHierarchyRootData());
- mUriToCollectionMapping.put(uri, rc);
- }));
+ rca.mIsReplacedIntoAction = false;
+
+ // Avoid redundant connections for the same intent. Also making sure
+ // that the number of connections we are making is always equal to the
+ // nmuber of unique intents that are being used for the updates.
+ if (idToIntentMapping.contains(rca.mIntentId)) {
+ continue;
+ }
+
+ idToIntentMapping.put(rca.mIntentId, rca.mServiceIntent);
rca.mItems = null;
continue;
}
@@ -1295,7 +1343,7 @@
// intents.
if (rca.mServiceIntent != null) {
final String uri = rca.mServiceIntent.toUri(0);
- int index = mIdToUriMapping.indexOfValue(uri);
+ int index = mIdToUriMapping.indexOfValueByValue(uri);
if (index == -1) {
int newIntentId = mIdToUriMapping.size();
rca.mIntentId = newIntentId;
@@ -1305,41 +1353,50 @@
rca.mItems = null;
continue;
}
- collectionFuture = CompletableFuture.allOf(collectionFuture,
- getItemsFutureFromIntentWithTimeout(rca.mServiceIntent)
- .thenAccept(rc -> {
- rc.setHierarchyRootData(getHierarchyRootData());
- mUriToCollectionMapping.put(uri, rc);
- }));
+
+ idToIntentMapping.put(rca.mIntentId, rca.mServiceIntent);
rca.mItems = null;
} else {
for (RemoteViews views : rca.mItems.mViews) {
- collectionFuture = CompletableFuture.allOf(collectionFuture,
- collectAllIntentsNoComplete(views));
+ collectAllIntentsInternal(views, idToIntentMapping);
}
}
} else if (action instanceof ViewGroupActionAdd vgaa
&& vgaa.mNestedViews != null) {
- collectionFuture = CompletableFuture.allOf(collectionFuture,
- collectAllIntentsNoComplete(vgaa.mNestedViews));
+ collectAllIntentsInternal(vgaa.mNestedViews, idToIntentMapping);
}
}
}
+ }
- return collectionFuture;
+ private @NonNull CompletableFuture<Void> connectAllUniqueIntents(int individualSize,
+ @NonNull SparseArray<Intent> idToIntentMapping) {
+ List<CompletableFuture<Void>> intentFutureList = new ArrayList<>();
+ for (int i = 0; i < idToIntentMapping.size(); i++) {
+ String currentIntentUri = mIdToUriMapping.get(idToIntentMapping.keyAt(i));
+ Intent currentIntent = idToIntentMapping.valueAt(i);
+ intentFutureList.add(getItemsFutureFromIntentWithTimeout(currentIntent,
+ individualSize)
+ .thenAccept(items -> {
+ items.setHierarchyRootData(getHierarchyRootData());
+ mUriToCollectionMapping.put(currentIntentUri, items);
+ }));
+ }
+
+ return CompletableFuture.allOf(intentFutureList.toArray(CompletableFuture[]::new));
}
private static CompletableFuture<RemoteCollectionItems> getItemsFutureFromIntentWithTimeout(
- Intent intent) {
+ Intent intent, int individualSize) {
if (intent == null) {
Log.e(LOG_TAG, "Null intent received when generating adapter future");
return CompletableFuture.completedFuture(new RemoteCollectionItems
- .Builder().build());
+ .Builder().build());
}
final Context context = ActivityThread.currentApplication();
- final CompletableFuture<RemoteCollectionItems> result = new CompletableFuture<>();
+ final CompletableFuture<RemoteCollectionItems> result = new CompletableFuture<>();
context.bindService(intent, Context.BindServiceFlags.of(Context.BIND_AUTO_CREATE),
result.defaultExecutor(), new ServiceConnection() {
@Override
@@ -1348,11 +1405,11 @@
RemoteCollectionItems items;
try {
items = IRemoteViewsFactory.Stub.asInterface(iBinder)
- .getRemoteCollectionItems();
+ .getRemoteCollectionItems(individualSize);
} catch (RemoteException re) {
items = new RemoteCollectionItems.Builder().build();
- Log.e(LOG_TAG, "Error getting collection items from the factory",
- re);
+ Log.e(LOG_TAG, "Error getting collection items from the"
+ + " factory", re);
} finally {
context.unbindService(this);
}
@@ -1371,10 +1428,17 @@
return result;
}
- public void writeToParcel(Parcel out, int flags) {
+ public void writeToParcel(Parcel out, int flags,
+ @Nullable SparseArray<Intent> intentsToIgnore) {
out.writeInt(mIdToUriMapping.size());
for (int i = 0; i < mIdToUriMapping.size(); i++) {
- out.writeInt(mIdToUriMapping.keyAt(i));
+ int currentIntentId = mIdToUriMapping.keyAt(i);
+ if (intentsToIgnore != null && intentsToIgnore.contains(currentIntentId)) {
+ // Skip writing collections that are to be updated in the following steps to
+ // better estimate the RemoteViews size.
+ continue;
+ }
+ out.writeInt(currentIntentId);
String intentUri = mIdToUriMapping.valueAt(i);
out.writeString8(intentUri);
mUriToCollectionMapping.get(intentUri).writeToParcel(out, flags, true);
@@ -6724,7 +6788,13 @@
return 0;
}
+ @Override
public void writeToParcel(Parcel dest, int flags) {
+ writeToParcel(dest, flags, /* intentsToIgnore= */ null);
+ }
+
+ private void writeToParcel(Parcel dest, int flags,
+ @Nullable SparseArray<Intent> intentsToIgnore) {
boolean prevSquashingAllowed = dest.allowSquashing();
if (!hasMultipleLayouts()) {
@@ -6733,7 +6803,7 @@
// is shared by all children.
if (mIsRoot) {
mBitmapCache.writeBitmapsToParcel(dest, flags);
- mCollectionCache.writeToParcel(dest, flags);
+ mCollectionCache.writeToParcel(dest, flags, intentsToIgnore);
}
mApplication.writeToParcel(dest, flags);
if (mIsRoot || mIdealSize == null) {
@@ -6750,7 +6820,7 @@
dest.writeInt(MODE_HAS_SIZED_REMOTEVIEWS);
if (mIsRoot) {
mBitmapCache.writeBitmapsToParcel(dest, flags);
- mCollectionCache.writeToParcel(dest, flags);
+ mCollectionCache.writeToParcel(dest, flags, intentsToIgnore);
}
dest.writeInt(mSizedRemoteViews.size());
for (RemoteViews view : mSizedRemoteViews) {
@@ -6762,7 +6832,7 @@
// is shared by all children.
if (mIsRoot) {
mBitmapCache.writeBitmapsToParcel(dest, flags);
- mCollectionCache.writeToParcel(dest, flags);
+ mCollectionCache.writeToParcel(dest, flags, intentsToIgnore);
}
mLandscape.writeToParcel(dest, flags);
// Both RemoteViews already share the same package and user
diff --git a/core/java/android/widget/RemoteViewsService.java b/core/java/android/widget/RemoteViewsService.java
index a250a86..d4a5bbd 100644
--- a/core/java/android/widget/RemoteViewsService.java
+++ b/core/java/android/widget/RemoteViewsService.java
@@ -19,6 +19,7 @@
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
+import android.os.Parcel;
import com.android.internal.widget.IRemoteViewsFactory;
@@ -43,13 +44,6 @@
private static final Object sLock = new Object();
/**
- * Used for determining the maximum number of entries to retrieve from RemoteViewsFactory
- *
- * @hide
- */
- private static final int MAX_NUM_ENTRY = 10;
-
- /**
* An interface for an adapter between a remote collection view (ListView, GridView, etc) and
* the underlying data for that view. The implementor is responsible for making a RemoteView
* for each item in the data set. This interface is a thin wrapper around {@link Adapter}.
@@ -235,9 +229,10 @@
}
@Override
- public RemoteViews.RemoteCollectionItems getRemoteCollectionItems() {
+ public RemoteViews.RemoteCollectionItems getRemoteCollectionItems(int capSize) {
RemoteViews.RemoteCollectionItems items = new RemoteViews.RemoteCollectionItems
.Builder().build();
+ Parcel capSizeTestParcel = Parcel.obtain();
try {
RemoteViews.RemoteCollectionItems.Builder itemsBuilder =
@@ -245,15 +240,25 @@
mFactory.onDataSetChanged();
itemsBuilder.setHasStableIds(mFactory.hasStableIds());
- final int numOfEntries = Math.min(mFactory.getCount(), MAX_NUM_ENTRY);
+ final int numOfEntries = mFactory.getCount();
+
for (int i = 0; i < numOfEntries; i++) {
- itemsBuilder.addItem(mFactory.getItemId(i), mFactory.getViewAt(i));
+ final long currentItemId = mFactory.getItemId(i);
+ final RemoteViews currentView = mFactory.getViewAt(i);
+ currentView.writeToParcel(capSizeTestParcel, 0);
+ if (capSizeTestParcel.dataSize() > capSize) {
+ break;
+ }
+ itemsBuilder.addItem(currentItemId, currentView);
}
items = itemsBuilder.build();
} catch (Exception ex) {
Thread t = Thread.currentThread();
Thread.getDefaultUncaughtExceptionHandler().uncaughtException(t, ex);
+ } finally {
+ // Recycle the parcel
+ capSizeTestParcel.recycle();
}
return items;
}
diff --git a/core/java/com/android/internal/app/IntentForwarderActivity.java b/core/java/com/android/internal/app/IntentForwarderActivity.java
index 65b5979..c769518 100644
--- a/core/java/com/android/internal/app/IntentForwarderActivity.java
+++ b/core/java/com/android/internal/app/IntentForwarderActivity.java
@@ -172,6 +172,20 @@
newIntent.prepareToLeaveUser(callingUserId);
final CompletableFuture<ResolveInfo> targetResolveInfoFuture =
mInjector.resolveActivityAsUser(newIntent, MATCH_DEFAULT_ONLY, targetUserId);
+
+ if (isPrivateProfile(callingUserId)) {
+ buildAndExecuteForPrivateProfile(intentReceived, className, newIntent, callingUserId,
+ targetUserId);
+ } else {
+ buildAndExecute(targetResolveInfoFuture, intentReceived, className, newIntent,
+ callingUserId,
+ targetUserId, userMessage, managedProfile);
+ }
+ }
+
+ private void buildAndExecute(CompletableFuture<ResolveInfo> targetResolveInfoFuture,
+ Intent intentReceived, String className, Intent newIntent, int callingUserId,
+ int targetUserId, String userMessage, UserInfo managedProfile) {
targetResolveInfoFuture
.thenApplyAsync(targetResolveInfo -> {
if (isResolverActivityResolveInfo(targetResolveInfo)) {
@@ -195,6 +209,23 @@
}, getApplicationContext().getMainExecutor());
}
+ private void buildAndExecuteForPrivateProfile(
+ Intent intentReceived, String className, Intent newIntent, int callingUserId,
+ int targetUserId) {
+ final CompletableFuture<ResolveInfo> targetResolveInfoFuture =
+ mInjector.resolveActivityAsUser(newIntent, MATCH_DEFAULT_ONLY, targetUserId);
+ targetResolveInfoFuture
+ .thenAcceptAsync(targetResolveInfo -> {
+ if (isResolverActivityResolveInfo(targetResolveInfo)) {
+ launchResolverActivityWithCorrectTab(intentReceived, className, newIntent,
+ callingUserId, targetUserId);
+ } else {
+ maybeShowUserConsentMiniResolverPrivate(targetResolveInfo, newIntent,
+ targetUserId);
+ }
+ }, getApplicationContext().getMainExecutor());
+ }
+
private void maybeShowUserConsentMiniResolver(
ResolveInfo target, Intent launchIntent, UserInfo managedProfile) {
if (target == null || isIntentForwarderResolveInfo(target) || !isDeviceProvisioned()) {
@@ -233,24 +264,70 @@
"Showing user consent for redirection into the managed profile for intent [%s] and "
+ " calling package [%s]",
launchIntent, callingPackage));
+ PackageManager packageManagerForTargetUser =
+ createContextAsUser(UserHandle.of(targetUserId), /* flags= */ 0)
+ .getPackageManager();
+ buildMiniResolver(target, launchIntent, targetUserId,
+ getOpenInWorkMessage(launchIntent, target.loadLabel(packageManagerForTargetUser)),
+ packageManagerForTargetUser);
+
+
+ View telephonyInfo = findViewById(R.id.miniresolver_info_section);
+
+ // Additional information section is work telephony specific. Therefore, it is only shown
+ // for telephony related intents, when all sim subscriptions are in the work profile.
+ if ((isDialerIntent(launchIntent) || isTextMessageIntent(launchIntent))
+ && devicePolicyManager.getManagedSubscriptionsPolicy().getPolicyType()
+ == ManagedSubscriptionsPolicy.TYPE_ALL_MANAGED_SUBSCRIPTIONS) {
+ telephonyInfo.setVisibility(View.VISIBLE);
+ ((TextView) findViewById(R.id.miniresolver_info_section_text))
+ .setText(getWorkTelephonyInfoSectionMessage(launchIntent));
+ } else {
+ telephonyInfo.setVisibility(View.GONE);
+ }
+ }
+
+ private void maybeShowUserConsentMiniResolverPrivate(
+ ResolveInfo target, Intent launchIntent, int targetUserId) {
+ if (target == null || isIntentForwarderResolveInfo(target)) {
+ finish();
+ return;
+ }
+
+ String callingPackage = getCallingPackage();
+
+ Log.i("IntentForwarderActivity", String.format(
+ "Showing user consent for redirection into the main profile for intent [%s] and "
+ + " calling package [%s]",
+ launchIntent, callingPackage));
+ PackageManager packageManagerForTargetUser =
+ createContextAsUser(UserHandle.of(targetUserId), /* flags= */ 0)
+ .getPackageManager();
+ buildMiniResolver(target, launchIntent, targetUserId,
+ getString(R.string.miniresolver_open_in_personal,
+ target.loadLabel(packageManagerForTargetUser)),
+ packageManagerForTargetUser);
+
+ View telephonyInfo = findViewById(R.id.miniresolver_info_section);
+ telephonyInfo.setVisibility(View.GONE);
+ }
+
+ private void buildMiniResolver(ResolveInfo target, Intent launchIntent, int targetUserId,
+ String resolverTitle, PackageManager pmForTargetUser) {
int layoutId = R.layout.miniresolver;
setContentView(layoutId);
findViewById(R.id.title_container).setElevation(0);
- PackageManager packageManagerForTargetUser =
- createContextAsUser(UserHandle.of(targetUserId), /* flags= */ 0)
- .getPackageManager();
-
ImageView icon = findViewById(R.id.icon);
icon.setImageDrawable(
- getAppIcon(target, launchIntent, targetUserId, packageManagerForTargetUser));
+ getAppIcon(target, launchIntent, targetUserId, pmForTargetUser));
View buttonContainer = findViewById(R.id.button_bar_container);
buttonContainer.setPadding(0, 0, 0, buttonContainer.getPaddingBottom());
((TextView) findViewById(R.id.open_cross_profile)).setText(
- getOpenInWorkMessage(launchIntent, target.loadLabel(packageManagerForTargetUser)));
+ resolverTitle);
// The mini-resolver's negative button is reused in this flow to cancel the intent
((Button) findViewById(R.id.use_same_profile_browser)).setText(R.string.cancel);
@@ -269,21 +346,6 @@
targetUserId);
finish();
});
-
-
- View telephonyInfo = findViewById(R.id.miniresolver_info_section);
-
- // Additional information section is work telephony specific. Therefore, it is only shown
- // for telephony related intents, when all sim subscriptions are in the work profile.
- if ((isDialerIntent(launchIntent) || isTextMessageIntent(launchIntent))
- && devicePolicyManager.getManagedSubscriptionsPolicy().getPolicyType()
- == ManagedSubscriptionsPolicy.TYPE_ALL_MANAGED_SUBSCRIPTIONS) {
- telephonyInfo.setVisibility(View.VISIBLE);
- ((TextView) findViewById(R.id.miniresolver_info_section_text))
- .setText(getWorkTelephonyInfoSectionMessage(launchIntent));
- } else {
- telephonyInfo.setVisibility(View.GONE);
- }
}
private Drawable getAppIcon(
@@ -548,6 +610,18 @@
}
/**
+ * Returns the private profile for this device or null if there is no private profile.
+ */
+ @Nullable
+ private UserInfo getPrivateProfile() {
+ List<UserInfo> relatedUsers = mInjector.getUserManager().getProfiles(UserHandle.myUserId());
+ for (UserInfo userInfo : relatedUsers) {
+ if (userInfo.isPrivateProfile()) return userInfo;
+ }
+ return null;
+ }
+
+ /**
* Returns the userId of the profile parent or UserHandle.USER_NULL if there is
* no parent.
*/
@@ -577,6 +651,17 @@
return mMetricsLogger;
}
+ private boolean isPrivateProfile(int userId) {
+ UserInfo privateProfile = getPrivateProfile();
+ return privateSpaceFlagsEnabled() && privateProfile != null
+ && privateProfile.id == userId;
+ }
+
+ private boolean privateSpaceFlagsEnabled() {
+ return android.os.Flags.allowPrivateProfile()
+ && android.multiuser.Flags.enablePrivateSpaceIntentRedirection();
+ }
+
@VisibleForTesting
protected Injector createInjector() {
return new InjectorImpl();
diff --git a/core/java/com/android/internal/widget/IRemoteViewsFactory.aidl b/core/java/com/android/internal/widget/IRemoteViewsFactory.aidl
index 918d9c0..1d0e972 100644
--- a/core/java/com/android/internal/widget/IRemoteViewsFactory.aidl
+++ b/core/java/com/android/internal/widget/IRemoteViewsFactory.aidl
@@ -39,6 +39,6 @@
boolean hasStableIds();
@UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
boolean isCreated();
- RemoteViews.RemoteCollectionItems getRemoteCollectionItems();
+ RemoteViews.RemoteCollectionItems getRemoteCollectionItems(int capSize);
}
diff --git a/core/jni/LayoutlibLoader.cpp b/core/jni/LayoutlibLoader.cpp
index d5f17da..071f933 100644
--- a/core/jni/LayoutlibLoader.cpp
+++ b/core/jni/LayoutlibLoader.cpp
@@ -196,14 +196,6 @@
return result;
}
-static vector<string> parseCsv(JNIEnv* env, jstring csvJString) {
- const char* charArray = env->GetStringUTFChars(csvJString, 0);
- string csvString(charArray);
- vector<string> result = parseCsv(csvString);
- env->ReleaseStringUTFChars(csvJString, charArray);
- return result;
-}
-
void LayoutlibLogger(base::LogId, base::LogSeverity severity, const char* tag, const char* file,
unsigned int line, const char* message) {
JNIEnv* env = AndroidRuntime::getJNIEnv();
@@ -395,20 +387,28 @@
jmethodID getPropertyMethod = GetStaticMethodIDOrDie(env, system, "getProperty",
"(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;");
- // Get the names of classes that need to register their native methods
- auto nativesClassesJString =
- (jstring)env->CallStaticObjectMethod(system, getPropertyMethod,
- env->NewStringUTF("core_native_classes"),
- env->NewStringUTF(""));
- vector<string> classesToRegister = parseCsv(env, nativesClassesJString);
+ // Java system properties that contain LayoutLib config. The initial values in the map
+ // are the default values if the property is not specified.
+ std::unordered_map<std::string, std::string> systemProperties =
+ {{"core_native_classes", ""},
+ {"register_properties_during_load", ""},
+ {"icu.data.path", ""},
+ {"use_bridge_for_logging", ""},
+ {"keyboard_paths", ""}};
- jstring registerProperty =
- (jstring)env->CallStaticObjectMethod(system, getPropertyMethod,
- env->NewStringUTF(
- "register_properties_during_load"),
- env->NewStringUTF(""));
- const char* registerPropertyString = env->GetStringUTFChars(registerProperty, 0);
- if (strcmp(registerPropertyString, "true") == 0) {
+ for (auto& [name, defaultValue] : systemProperties) {
+ jstring propertyString =
+ (jstring)env->CallStaticObjectMethod(system, getPropertyMethod,
+ env->NewStringUTF(name.c_str()),
+ env->NewStringUTF(defaultValue.c_str()));
+ const char* propertyChars = env->GetStringUTFChars(propertyString, 0);
+ systemProperties[name] = string(propertyChars);
+ env->ReleaseStringUTFChars(propertyString, propertyChars);
+ }
+ // Get the names of classes that need to register their native methods
+ vector<string> classesToRegister = parseCsv(systemProperties["core_native_classes"]);
+
+ if (systemProperties["register_properties_during_load"] == "true") {
// Set the system properties first as they could be used in the static initialization of
// other classes
if (register_android_os_SystemProperties(env) < 0) {
@@ -423,35 +423,20 @@
env->CallStaticVoidMethod(bridge, setSystemPropertiesMethod);
property_initialize_ro_cpu_abilist();
}
- env->ReleaseStringUTFChars(registerProperty, registerPropertyString);
if (register_jni_procs(gRegJNIMap, classesToRegister, env) < 0) {
return JNI_ERR;
}
- // Set the location of ICU data
- auto stringPath = (jstring)env->CallStaticObjectMethod(system, getPropertyMethod,
- env->NewStringUTF("icu.data.path"),
- env->NewStringUTF(""));
- const char* path = env->GetStringUTFChars(stringPath, 0);
-
- if (strcmp(path, "**n/a**") != 0) {
- bool icuInitialized = init_icu(path);
+ if (!systemProperties["register_properties_during_load"].empty()) {
+ // Set the location of ICU data
+ bool icuInitialized = init_icu(systemProperties["icu.data.path"].c_str());
if (!icuInitialized) {
- fprintf(stderr, "Failed to initialize ICU\n");
return JNI_ERR;
}
- } else {
- fprintf(stderr, "Skip initializing ICU\n");
}
- env->ReleaseStringUTFChars(stringPath, path);
- jstring useJniProperty =
- (jstring)env->CallStaticObjectMethod(system, getPropertyMethod,
- env->NewStringUTF("use_bridge_for_logging"),
- env->NewStringUTF(""));
- const char* useJniString = env->GetStringUTFChars(useJniProperty, 0);
- if (strcmp(useJniString, "true") == 0) {
+ if (systemProperties["use_bridge_for_logging"] == "true") {
layoutLog = FindClassOrDie(env, "com/android/ide/common/rendering/api/ILayoutLog");
layoutLog = MakeGlobalRefOrDie(env, layoutLog);
logMethodId = GetMethodIDOrDie(env, layoutLog, "logAndroidFramework",
@@ -468,23 +453,16 @@
// initialize logging, so ANDROD_LOG_TAGS env variable is respected
android::base::InitLogging(nullptr, android::base::StderrLogger);
}
- env->ReleaseStringUTFChars(useJniProperty, useJniString);
// Use English locale for number format to ensure correct parsing of floats when using strtof
setlocale(LC_NUMERIC, "en_US.UTF-8");
- auto keyboardPathsJString =
- (jstring)env->CallStaticObjectMethod(system, getPropertyMethod,
- env->NewStringUTF("keyboard_paths"),
- env->NewStringUTF(""));
- const char* keyboardPathsString = env->GetStringUTFChars(keyboardPathsJString, 0);
- if (strcmp(keyboardPathsString, "**n/a**") != 0) {
- vector<string> keyboardPaths = parseCsv(env, keyboardPathsJString);
+ if (!systemProperties["keyboard_paths"].empty()) {
+ vector<string> keyboardPaths = parseCsv(systemProperties["keyboard_paths"]);
init_keyboard(env, keyboardPaths);
} else {
fprintf(stderr, "Skip initializing keyboard\n");
}
- env->ReleaseStringUTFChars(keyboardPathsJString, keyboardPathsString);
return JNI_VERSION_1_6;
}
diff --git a/core/proto/android/server/inputmethod/inputmethodmanagerservice.proto b/core/proto/android/server/inputmethod/inputmethodmanagerservice.proto
index 5a18d9e..b75d545 100644
--- a/core/proto/android/server/inputmethod/inputmethodmanagerservice.proto
+++ b/core/proto/android/server/inputmethod/inputmethodmanagerservice.proto
@@ -39,7 +39,7 @@
optional string cur_token = 14;
optional int32 cur_token_display_id = 15;
optional bool system_ready = 16;
- optional int32 last_switch_user_id = 17;
+ reserved 17; // deprecated last_switch_user_id
optional bool have_connection = 18;
optional bool bound_to_method = 19;
optional bool is_interactive = 20;
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 487b5be..ba9751f 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -8621,6 +8621,10 @@
android:permission="android.permission.BIND_JOB_SERVICE">
</service>
+ <service android:name="com.android.system.virtualmachine.SecretkeeperJobService"
+ android:permission="android.permission.BIND_JOB_SERVICE">
+ </service>
+
<service android:name="com.android.server.PruneInstantAppsJobService"
android:permission="android.permission.BIND_JOB_SERVICE" >
</service>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 8edf42a..90f2731 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3348,26 +3348,23 @@
<string name="config_carrierAppInstallDialogComponent" translatable="false"
>com.android.simappdialog/com.android.simappdialog.InstallCarrierAppActivity</string>
- <!-- Name of the default framework dialog that is used to get or save an app credential.
+ <!-- Name of the fallback CredentialManager dialog that is used to get or save an app
+ credential.
- This UI should be always launch-able and is used as a fallback when an oem replacement activity
- (defined at config_oemCredentialManagerDialogComponent) is undefined / not found. -->
- <string name="config_credentialManagerDialogComponent" translatable="false"
+ If empty, no fallback will be used. IMPORTANT: In that case the OEM dialog value specified in
+ config_oemCredentialManagerDialogComponent must always launch-able. Otherwise, the
+ CredentialManager API contract is broken.
+
+ If specified, this UI should be always launch-able. It will be used as a fallback when the OEM
+ dialog value specified in config_oemCredentialManagerDialogComponent) is undefined / not
+ found. -->
+ <string name="config_fallbackCredentialManagerDialogComponent" translatable="false"
>com.android.credentialmanager/com.android.credentialmanager.CredentialSelectorActivity</string>
- <!-- Whether to allow the credential selector activity to be replaced by an activity at
- run-time (restricted to the privileged activity specified by
- config_credentialSelectorActivityName).
-
- When disabled, the fallback activity defined at
- config_credentialManagerDialogComponent will be used instead. -->
- <bool name="config_enableOemCredentialManagerDialogComponent" translatable="false">true</bool>
<!-- Fully qualified activity name providing the credential selector UI, that serves the
- CredentialManager APIs.
+ CredentialManager APIs. Must be a system app component.
- Used only when config_enableOemCredentialManagerDialogComponent is true.
-
- If the activity specified cannot be found or launched, then the fallback activity defined at
- config_credentialManagerDialogComponent will be used instead. -->
+ If empty, or if this activity specified cannot be found or launched, then the fallback activity
+ defined at config_fallbackCredentialManagerDialogComponent will be used instead. -->
<string name="config_oemCredentialManagerDialogComponent" translatable="false"></string>
<!-- Name of the broadcast receiver that is used to receive provider change events -->
diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml
index 660e4c0..6e56fe2 100644
--- a/core/res/res/values/config_telephony.xml
+++ b/core/res/res/values/config_telephony.xml
@@ -334,17 +334,9 @@
<bool name="config_enable_cellular_on_boot_default">true</bool>
<java-symbol type="bool" name="config_enable_cellular_on_boot_default" />
- <!-- The network capabilities that would be forced marked as cellular transport regardless it's
- on cellular or satellite-->
- <string-array name="config_force_cellular_transport_capabilities">
- <!-- Added the following three capabilities for now. For the long term solution, the client
- requests satellite network should really include TRANSPORT_SATELLITE in the network
- request. With the following workaround, the clients can continue request network with
- the following capabilities with TRANSPORT_CELLULAR. The network with one of the
- following capabilities would also be marked as cellular. -->
- <item>ims</item>
- <item>eims</item>
- <item>xcap</item>
- </string-array>
- <java-symbol type="array" name="config_force_cellular_transport_capabilities" />
+ <!-- Defines metrics pull cooldown period. The default cooldown period is 23 hours,
+ some Telephony metrics need to be pulled more frequently -->
+ <integer name="config_metrics_pull_cooldown_millis">82800000</integer>
+ <java-symbol type="integer" name="config_metrics_pull_cooldown_millis" />
+
</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index a180467..a025c8d 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2297,8 +2297,7 @@
<java-symbol type="string" name="config_customVpnAlwaysOnDisconnectedDialogComponent" />
<java-symbol type="string" name="config_platformVpnConfirmDialogComponent" />
<java-symbol type="string" name="config_carrierAppInstallDialogComponent" />
- <java-symbol type="string" name="config_credentialManagerDialogComponent" />
- <java-symbol type="bool" name="config_enableOemCredentialManagerDialogComponent" />
+ <java-symbol type="string" name="config_fallbackCredentialManagerDialogComponent" />
<java-symbol type="string" name="config_oemCredentialManagerDialogComponent" />
<java-symbol type="string" name="config_credentialManagerReceiverComponent" />
<java-symbol type="string" name="config_defaultNetworkScorerPackageName" />
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index e72beee..24031cad 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -101,6 +101,7 @@
"flickerlib-trace_processor_shell",
"mockito-target-extended-minus-junit4",
"TestParameterInjector",
+ "android.content.res.flags-aconfig-java",
],
libs: [
diff --git a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
index d115bf3..927c67c 100644
--- a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
+++ b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
@@ -21,16 +21,22 @@
import static android.content.Intent.ACTION_VIEW;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
+import static com.android.window.flags.Flags.FLAG_ACTIVITY_WINDOW_INFO_FLAG;
+
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import android.annotation.NonNull;
@@ -46,6 +52,7 @@
import android.app.servertransaction.ActivityRelaunchItem;
import android.app.servertransaction.ClientTransaction;
import android.app.servertransaction.ClientTransactionItem;
+import android.app.servertransaction.ClientTransactionListenerController;
import android.app.servertransaction.ConfigurationChangeItem;
import android.app.servertransaction.NewIntentItem;
import android.app.servertransaction.ResumeActivityItem;
@@ -60,7 +67,9 @@
import android.hardware.display.VirtualDisplay;
import android.os.Bundle;
import android.os.IBinder;
+import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.util.DisplayMetrics;
import android.util.MergedConfiguration;
import android.view.Display;
@@ -81,11 +90,14 @@
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
+import java.util.function.BiConsumer;
import java.util.function.Consumer;
/**
@@ -103,11 +115,17 @@
// few sequence numbers the framework used to launch the test activity.
private static final int BASE_SEQ = 10000000;
- @Rule
+ @Rule(order = 0)
public final ActivityTestRule<TestActivity> mActivityTestRule =
new ActivityTestRule<>(TestActivity.class, true /* initialTouchMode */,
false /* launchActivity */);
+ @Rule(order = 1)
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
+
+ @Mock
+ private BiConsumer<IBinder, ActivityWindowInfo> mActivityWindowInfoListener;
+
private WindowTokenClientController mOriginalWindowTokenClientController;
private Configuration mOriginalAppConfig;
@@ -115,6 +133,8 @@
@Before
public void setup() {
+ MockitoAnnotations.initMocks(this);
+
// Keep track of the original controller, so that it can be used to restore in tearDown()
// when there is override in some test cases.
mOriginalWindowTokenClientController = WindowTokenClientController.getInstance();
@@ -129,6 +149,8 @@
mCreatedVirtualDisplays = null;
}
WindowTokenClientController.overrideForTesting(mOriginalWindowTokenClientController);
+ ClientTransactionListenerController.getInstance()
+ .unregisterActivityWindowInfoChangedListener(mActivityWindowInfoListener);
InstrumentationRegistry.getInstrumentation().runOnMainSync(
() -> restoreConfig(ActivityThread.currentActivityThread(), mOriginalAppConfig));
}
@@ -783,6 +805,101 @@
verify(windowTokenClientController).onWindowContextWindowRemoved(clientToken);
}
+ @Test
+ public void testActivityWindowInfoChanged_activityLaunch() {
+ mSetFlagsRule.enableFlags(FLAG_ACTIVITY_WINDOW_INFO_FLAG);
+
+ ClientTransactionListenerController.getInstance().registerActivityWindowInfoChangedListener(
+ mActivityWindowInfoListener);
+
+ final Activity activity = mActivityTestRule.launchActivity(new Intent());
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+ final ActivityClientRecord activityClientRecord = getActivityClientRecord(activity);
+
+ verify(mActivityWindowInfoListener).accept(activityClientRecord.token,
+ activityClientRecord.getActivityWindowInfo());
+ }
+
+ @Test
+ public void testActivityWindowInfoChanged_activityRelaunch() throws RemoteException {
+ mSetFlagsRule.enableFlags(FLAG_ACTIVITY_WINDOW_INFO_FLAG);
+
+ ClientTransactionListenerController.getInstance().registerActivityWindowInfoChangedListener(
+ mActivityWindowInfoListener);
+
+ final Activity activity = mActivityTestRule.launchActivity(new Intent());
+ final IApplicationThread appThread = activity.getActivityThread().getApplicationThread();
+ appThread.scheduleTransaction(newRelaunchResumeTransaction(activity));
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+ final ActivityClientRecord activityClientRecord = getActivityClientRecord(activity);
+
+ // The same ActivityWindowInfo won't trigger duplicated callback.
+ verify(mActivityWindowInfoListener).accept(activityClientRecord.token,
+ activityClientRecord.getActivityWindowInfo());
+
+ final Configuration currentConfig = activity.getResources().getConfiguration();
+ final ActivityWindowInfo activityWindowInfo = new ActivityWindowInfo();
+ activityWindowInfo.set(true /* isEmbedded */, new Rect(0, 0, 1000, 2000),
+ new Rect(0, 0, 1000, 1000));
+ final ActivityRelaunchItem relaunchItem = ActivityRelaunchItem.obtain(
+ activity.getActivityToken(), null, null, 0,
+ new MergedConfiguration(currentConfig, currentConfig),
+ false /* preserveWindow */, activityWindowInfo);
+ final ClientTransaction transaction = newTransaction(activity);
+ transaction.addTransactionItem(relaunchItem);
+ appThread.scheduleTransaction(transaction);
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+
+ verify(mActivityWindowInfoListener).accept(activityClientRecord.token,
+ activityWindowInfo);
+ }
+
+ @Test
+ public void testActivityWindowInfoChanged_activityConfigurationChanged()
+ throws RemoteException {
+ mSetFlagsRule.enableFlags(FLAG_ACTIVITY_WINDOW_INFO_FLAG);
+
+ ClientTransactionListenerController.getInstance().registerActivityWindowInfoChangedListener(
+ mActivityWindowInfoListener);
+
+ final Activity activity = mActivityTestRule.launchActivity(new Intent());
+ final IApplicationThread appThread = activity.getActivityThread().getApplicationThread();
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+
+ clearInvocations(mActivityWindowInfoListener);
+ final Configuration config = new Configuration(activity.getResources().getConfiguration());
+ config.seq++;
+ final Rect taskBounds = new Rect(0, 0, 1000, 2000);
+ final Rect taskFragmentBounds = new Rect(0, 0, 1000, 1000);
+ final ActivityWindowInfo activityWindowInfo = new ActivityWindowInfo();
+ activityWindowInfo.set(true /* isEmbedded */, taskBounds, taskFragmentBounds);
+ final ActivityConfigurationChangeItem activityConfigurationChangeItem =
+ ActivityConfigurationChangeItem.obtain(
+ activity.getActivityToken(), config, activityWindowInfo);
+ final ClientTransaction transaction = newTransaction(activity);
+ transaction.addTransactionItem(activityConfigurationChangeItem);
+ appThread.scheduleTransaction(transaction);
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+
+ verify(mActivityWindowInfoListener).accept(activity.getActivityToken(),
+ activityWindowInfo);
+
+ clearInvocations(mActivityWindowInfoListener);
+ final ActivityWindowInfo activityWindowInfo2 = new ActivityWindowInfo();
+ activityWindowInfo2.set(true /* isEmbedded */, taskBounds, taskFragmentBounds);
+ config.seq++;
+ final ActivityConfigurationChangeItem activityConfigurationChangeItem2 =
+ ActivityConfigurationChangeItem.obtain(
+ activity.getActivityToken(), config, activityWindowInfo2);
+ final ClientTransaction transaction2 = newTransaction(activity);
+ transaction2.addTransactionItem(activityConfigurationChangeItem2);
+ appThread.scheduleTransaction(transaction);
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+
+ // The same ActivityWindowInfo won't trigger duplicated callback.
+ verify(mActivityWindowInfoListener, never()).accept(any(), any());
+ }
+
/**
* Calls {@link ActivityThread#handleActivityConfigurationChanged(ActivityClientRecord,
* Configuration, int, ActivityWindowInfo)} to try to push activity configuration to the
@@ -871,7 +988,7 @@
@NonNull
private static ClientTransaction newStopTransaction(@NonNull Activity activity) {
final StopActivityItem stopStateRequest = StopActivityItem.obtain(
- activity.getActivityToken(), 0 /* configChanges */);
+ activity.getActivityToken());
final ClientTransaction transaction = newTransaction(activity);
transaction.addTransactionItem(stopStateRequest);
diff --git a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionItemTest.java b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionItemTest.java
index 4db5d1b..9907397 100644
--- a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionItemTest.java
+++ b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionItemTest.java
@@ -136,7 +136,7 @@
@Test
public void testDestroyActivityItem_preExecute() {
final DestroyActivityItem item = DestroyActivityItem
- .obtain(mActivityToken, false /* finished */, 123 /* configChanges */);
+ .obtain(mActivityToken, false /* finished */);
item.preExecute(mHandler);
assertEquals(1, mActivitiesToBeDestroyed.size());
@@ -146,7 +146,7 @@
@Test
public void testDestroyActivityItem_postExecute() {
final DestroyActivityItem item = DestroyActivityItem
- .obtain(mActivityToken, false /* finished */, 123 /* configChanges */);
+ .obtain(mActivityToken, false /* finished */);
item.preExecute(mHandler);
item.postExecute(mHandler, mPendingActions);
@@ -156,11 +156,11 @@
@Test
public void testDestroyActivityItem_execute() {
final DestroyActivityItem item = DestroyActivityItem
- .obtain(mActivityToken, false /* finished */, 123 /* configChanges */);
+ .obtain(mActivityToken, false /* finished */);
item.execute(mHandler, mActivityClientRecord, mPendingActions);
verify(mHandler).handleDestroyActivity(eq(mActivityClientRecord), eq(false) /* finishing */,
- eq(123) /* configChanges */, eq(false) /* getNonConfigInstance */, any());
+ eq(false) /* getNonConfigInstance */, any());
}
@Test
diff --git a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java
index 213fd7b..77d31a5 100644
--- a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java
+++ b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java
@@ -22,21 +22,29 @@
import static com.android.window.flags.Flags.FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
+import android.graphics.Rect;
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManagerGlobal;
import android.hardware.display.IDisplayManager;
import android.os.Handler;
+import android.os.IBinder;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
import android.platform.test.flag.junit.SetFlagsRule;
import android.view.DisplayInfo;
+import android.window.ActivityWindowInfo;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.window.flags.Flags;
+
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -44,6 +52,8 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.function.BiConsumer;
+
/**
* Tests for {@link ClientTransactionListenerController}.
*
@@ -62,6 +72,10 @@
private IDisplayManager mIDisplayManager;
@Mock
private DisplayManager.DisplayListener mListener;
+ @Mock
+ private BiConsumer<IBinder, ActivityWindowInfo> mActivityWindowInfoListener;
+ @Mock
+ private IBinder mActivityToken;
private DisplayManagerGlobal mDisplayManager;
private Handler mHandler;
@@ -91,4 +105,24 @@
verify(mListener).onDisplayChanged(123);
}
+
+ @Test
+ public void testActivityWindowInfoChangedListener() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_ACTIVITY_WINDOW_INFO_FLAG);
+
+ mController.registerActivityWindowInfoChangedListener(mActivityWindowInfoListener);
+ final ActivityWindowInfo activityWindowInfo = new ActivityWindowInfo();
+ activityWindowInfo.set(true /* isEmbedded */, new Rect(0, 0, 1000, 2000),
+ new Rect(0, 0, 1000, 1000));
+ mController.onActivityWindowInfoChanged(mActivityToken, activityWindowInfo);
+
+ verify(mActivityWindowInfoListener).accept(mActivityToken, activityWindowInfo);
+
+ clearInvocations(mActivityWindowInfoListener);
+ mController.unregisterActivityWindowInfoChangedListener(mActivityWindowInfoListener);
+
+ mController.onActivityWindowInfoChanged(mActivityToken, activityWindowInfo);
+
+ verify(mActivityWindowInfoListener, never()).accept(any(), any());
+ }
}
diff --git a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
index 31ea675..584fe16 100644
--- a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
@@ -98,7 +98,7 @@
@Test
public void testRecycleDestroyActivityItem() {
- testRecycle(() -> DestroyActivityItem.obtain(mActivityToken, true, 117));
+ testRecycle(() -> DestroyActivityItem.obtain(mActivityToken, true));
}
@Test
@@ -169,7 +169,7 @@
@Test
public void testRecyclePauseActivityItemItem() {
- testRecycle(() -> PauseActivityItem.obtain(mActivityToken, true, true, 5, true, true));
+ testRecycle(() -> PauseActivityItem.obtain(mActivityToken, true, true, true, true));
}
@Test
@@ -185,7 +185,7 @@
@Test
public void testRecycleStopItem() {
- testRecycle(() -> StopActivityItem.obtain(mActivityToken, 4));
+ testRecycle(() -> StopActivityItem.obtain(mActivityToken));
}
@Test
diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
index adb6f2a..935bc75 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
@@ -306,7 +306,7 @@
final IBinder token = mock(IBinder.class);
final ClientTransaction destroyTransaction = ClientTransaction.obtain(null /* client */);
destroyTransaction.addTransactionItem(
- DestroyActivityItem.obtain(token, false /* finished */, 0 /* configChanges */));
+ DestroyActivityItem.obtain(token, false /* finished */));
destroyTransaction.preExecute(mTransactionHandler);
// The activity should be added to to-be-destroyed container.
assertEquals(1, activitiesToBeDestroyed.size());
diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
index 75347bf..d451fe5 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
@@ -155,8 +155,7 @@
@Test
public void testDestroy() {
- DestroyActivityItem item = DestroyActivityItem.obtain(mActivityToken, true /* finished */,
- 135 /* configChanges */);
+ DestroyActivityItem item = DestroyActivityItem.obtain(mActivityToken, true /* finished */);
writeAndPrepareForReading(item);
// Read from parcel and assert
@@ -244,8 +243,7 @@
public void testPause() {
// Write to parcel
PauseActivityItem item = PauseActivityItem.obtain(mActivityToken, true /* finished */,
- true /* userLeaving */, 135 /* configChanges */, true /* dontReport */,
- true /* autoEnteringPip */);
+ true /* userLeaving */, true /* dontReport */, true /* autoEnteringPip */);
writeAndPrepareForReading(item);
// Read from parcel and assert
@@ -272,7 +270,7 @@
@Test
public void testStop() {
// Write to parcel
- StopActivityItem item = StopActivityItem.obtain(mActivityToken, 14 /* configChanges */);
+ StopActivityItem item = StopActivityItem.obtain(mActivityToken);
writeAndPrepareForReading(item);
// Read from parcel and assert
@@ -305,8 +303,7 @@
ActivityConfigurationChangeItem callback2 = ActivityConfigurationChangeItem.obtain(
mActivityToken, config(), new ActivityWindowInfo());
- StopActivityItem lifecycleRequest = StopActivityItem.obtain(mActivityToken,
- 78 /* configChanges */);
+ StopActivityItem lifecycleRequest = StopActivityItem.obtain(mActivityToken);
ClientTransaction transaction = ClientTransaction.obtain(null /* client */);
transaction.addTransactionItem(callback1);
@@ -351,8 +348,7 @@
mSetFlagsRule.disableFlags(FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG);
// Write to parcel
- StopActivityItem lifecycleRequest = StopActivityItem.obtain(mActivityToken,
- 78 /* configChanges */);
+ StopActivityItem lifecycleRequest = StopActivityItem.obtain(mActivityToken);
ClientTransaction transaction = ClientTransaction.obtain(null /* client */);
transaction.addTransactionItem(lifecycleRequest);
diff --git a/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java b/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java
index 4a9cb71..0c1e879 100644
--- a/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java
+++ b/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java
@@ -18,34 +18,52 @@
import android.annotation.NonNull;
import android.app.ResourcesManager;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
import android.os.Binder;
import android.os.LocaleList;
import android.platform.test.annotations.Postsubmit;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.util.ArraySet;
import android.util.DisplayMetrics;
import android.util.TypedValue;
import android.view.Display;
import android.view.DisplayAdjustments;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import junit.framework.TestCase;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
@Postsubmit
+@RunWith(AndroidJUnit4.class)
public class ResourcesManagerTest extends TestCase {
private static final int SECONDARY_DISPLAY_ID = 1;
private static final String APP_ONE_RES_DIR = "app_one.apk";
private static final String APP_ONE_RES_SPLIT_DIR = "app_one_split.apk";
private static final String APP_TWO_RES_DIR = "app_two.apk";
private static final String LIB_RES_DIR = "lib.apk";
+ private static final String TEST_LIB = "com.android.frameworks.coretests.bdr_helper_app1";
private ResourcesManager mResourcesManager;
private Map<Integer, DisplayMetrics> mDisplayMetricsMap;
+ private PackageManager mPackageManager;
- @Override
- protected void setUp() throws Exception {
+ @Before
+ public void setUp() throws Exception {
super.setUp();
mDisplayMetricsMap = new HashMap<>();
@@ -93,8 +111,14 @@
return mDisplayMetricsMap.get(displayId);
}
};
+
+ mPackageManager = InstrumentationRegistry.getContext().getPackageManager();
}
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
+ @Test
@SmallTest
public void testMultipleCallsWithIdenticalParametersCacheReference() {
Resources resources = mResourcesManager.getResources(
@@ -109,6 +133,7 @@
assertSame(resources.getImpl(), newResources.getImpl());
}
+ @Test
@SmallTest
public void testMultipleCallsWithDifferentParametersReturnDifferentReferences() {
Resources resources = mResourcesManager.getResources(
@@ -125,6 +150,7 @@
assertNotSame(resources, newResources);
}
+ @Test
@SmallTest
public void testAddingASplitCreatesANewImpl() {
Resources resources1 = mResourcesManager.getResources(
@@ -142,6 +168,7 @@
assertNotSame(resources1.getImpl(), resources2.getImpl());
}
+ @Test
@SmallTest
public void testUpdateConfigurationUpdatesAllAssetManagers() {
Resources resources1 = mResourcesManager.getResources(
@@ -187,6 +214,7 @@
assertEquals(expectedConfig, resources3.getConfiguration());
}
+ @Test
@SmallTest
public void testTwoActivitiesWithIdenticalParametersShareImpl() {
Binder activity1 = new Binder();
@@ -208,6 +236,7 @@
assertSame(resources1.getImpl(), resources2.getImpl());
}
+ @Test
@SmallTest
public void testThemesGetUpdatedWithNewImpl() {
Binder activity1 = new Binder();
@@ -237,6 +266,7 @@
assertTrue(value.data != 0);
}
+ @Test
@SmallTest
public void testMultipleResourcesForOneActivityGetUpdatedWhenActivityBaseUpdates() {
Binder activity1 = new Binder();
@@ -286,6 +316,7 @@
assertEquals(expectedConfig2, resources2.getConfiguration());
}
+ @Test
@SmallTest
public void testChangingActivityDisplayDoesntOverrideDisplayRequestedByResources() {
Binder activity = new Binder();
@@ -322,4 +353,101 @@
assertEquals(mDisplayMetricsMap.get(Display.DEFAULT_DISPLAY).widthPixels,
defaultDisplayResources.getDisplayMetrics().widthPixels);
}
+
+ @Test
+ @SmallTest
+ @RequiresFlagsEnabled(Flags.FLAG_REGISTER_RESOURCE_PATHS)
+ public void testExistingResourcesAfterResourcePathsRegistration()
+ throws PackageManager.NameNotFoundException {
+ // Inject ResourcesManager instance from this test to the ResourcesManager class so that all
+ // the static method can interact with this test smoothly.
+ ResourcesManager oriResourcesManager = ResourcesManager.getInstance();
+ ResourcesManager.setInstance(mResourcesManager);
+
+ // Create a Resources before register resources' paths for a package.
+ Resources resources = mResourcesManager.getResources(
+ null, APP_ONE_RES_DIR, null, null, null, null, null, null,
+ CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null);
+ assertNotNull(resources);
+ ResourcesImpl oriResImpl = resources.getImpl();
+
+ ApplicationInfo appInfo = mPackageManager.getApplicationInfo(TEST_LIB, 0);
+ Resources.registerResourcePaths(TEST_LIB, appInfo);
+
+ assertNotSame(oriResImpl, resources.getImpl());
+
+ String[] resourcePaths = appInfo.getAllApkPaths();
+ resourcePaths = removeDuplicates(resourcePaths);
+ ApkAssets[] loadedAssets = resources.getAssets().getApkAssets();
+ assertTrue(allResourcePathsLoaded(resourcePaths, loadedAssets));
+
+ // Package resources' paths should be cached in ResourcesManager.
+ assertEquals(Arrays.toString(resourcePaths), Arrays.toString(ResourcesManager.getInstance()
+ .getSharedLibAssetsMap().get(TEST_LIB).getAllAssetPaths()));
+
+ // Revert the ResourcesManager instance back.
+ ResourcesManager.setInstance(oriResourcesManager);
+ }
+
+ @Test
+ @SmallTest
+ @RequiresFlagsEnabled(Flags.FLAG_REGISTER_RESOURCE_PATHS)
+ public void testNewResourcesAfterResourcePathsRegistration()
+ throws PackageManager.NameNotFoundException {
+ // Inject ResourcesManager instance from this test to the ResourcesManager class so that all
+ // the static method can interact with this test smoothly.
+ ResourcesManager oriResourcesManager = ResourcesManager.getInstance();
+ ResourcesManager.setInstance(mResourcesManager);
+
+ ApplicationInfo appInfo = mPackageManager.getApplicationInfo(TEST_LIB, 0);
+ Resources.registerResourcePaths(TEST_LIB, appInfo);
+
+ // Create a Resources after register resources' paths for a package.
+ Resources resources = mResourcesManager.getResources(
+ null, APP_ONE_RES_DIR, null, null, null, null, null, null,
+ CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null);
+ assertNotNull(resources);
+
+ String[] resourcePaths = appInfo.getAllApkPaths();
+ resourcePaths = removeDuplicates(resourcePaths);
+ ApkAssets[] loadedAssets = resources.getAssets().getApkAssets();
+ assertTrue(allResourcePathsLoaded(resourcePaths, loadedAssets));
+
+ // Package resources' paths should be cached in ResourcesManager.
+ assertEquals(Arrays.toString(resourcePaths), Arrays.toString(ResourcesManager.getInstance()
+ .getSharedLibAssetsMap().get(TEST_LIB).getAllAssetPaths()));
+
+ // Revert the ResourcesManager instance back.
+ ResourcesManager.setInstance(oriResourcesManager);
+ }
+
+ private static boolean allResourcePathsLoaded(String[] resourcePaths, ApkAssets[] loadedAsset) {
+ for (int i = 0; i < resourcePaths.length; i++) {
+ if (!resourcePaths[i].endsWith(".apk")) {
+ continue;
+ }
+ boolean found = false;
+ for (int j = 0; j < loadedAsset.length; j++) {
+ if (loadedAsset[j].getAssetPath().equals(resourcePaths[i])) {
+ found = true;
+ }
+ }
+ if (!found) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private static String[] removeDuplicates(String[] paths) {
+ var pathList = new ArrayList<String>();
+ var pathSet = new ArraySet<String>();
+ final int pathsLen = paths.length;
+ for (int i = 0; i < pathsLen; i++) {
+ if (pathSet.add(paths[i])) {
+ pathList.add(paths[i]);
+ }
+ }
+ return pathList.toArray(new String[0]);
+ }
}
diff --git a/core/tests/coretests/src/android/widget/RemoteViewsAdapterTest.java b/core/tests/coretests/src/android/widget/RemoteViewsAdapterTest.java
index 4f722ce..6ab77dc 100644
--- a/core/tests/coretests/src/android/widget/RemoteViewsAdapterTest.java
+++ b/core/tests/coretests/src/android/widget/RemoteViewsAdapterTest.java
@@ -355,7 +355,7 @@
}
@Override
- public RemoteViews.RemoteCollectionItems getRemoteCollectionItems() {
+ public RemoteViews.RemoteCollectionItems getRemoteCollectionItems(int capSize) {
RemoteViews.RemoteCollectionItems.Builder itemsBuilder =
new RemoteViews.RemoteCollectionItems.Builder();
itemsBuilder.setHasStableIds(hasStableIds())
diff --git a/core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java b/core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java
index 58cfc66..43e6227 100644
--- a/core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java
@@ -53,6 +53,7 @@
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
import androidx.test.InstrumentationRegistry;
@@ -93,6 +94,9 @@
private static final String TYPE_PLAIN_TEXT = "text/plain";
private static UserInfo MANAGED_PROFILE_INFO = new UserInfo();
+ private static UserInfo PRIVATE_PROFILE_INFO = new UserInfo(12, "Private", null,
+ UserInfo.FLAG_PROFILE, UserManager.USER_TYPE_PROFILE_PRIVATE);
+ @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
static {
MANAGED_PROFILE_INFO.id = 10;
@@ -131,6 +135,7 @@
@Before
public void setup() {
+
MockitoAnnotations.initMocks(this);
mContext = InstrumentationRegistry.getTargetContext();
sInjector = spy(new TestInjector());
@@ -632,6 +637,54 @@
logMakerCaptor.getValue().getSubtype());
}
+ @Test
+ public void shouldForwardToParent_telephony_privateProfile() throws Exception {
+ mSetFlagsRule.enableFlags(
+ android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+ android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_INTENT_REDIRECTION);
+
+ sComponentName = FORWARD_TO_PARENT_COMPONENT_NAME;
+ when(mIPm.canForwardTo(
+ any(Intent.class), nullable(String.class), anyInt(), anyInt())).thenReturn(true);
+
+ List<UserInfo> profiles = new ArrayList<>();
+ profiles.add(CURRENT_USER_INFO);
+ profiles.add(PRIVATE_PROFILE_INFO);
+ when(mUserManager.getProfiles(anyInt())).thenReturn(profiles);
+ when(mUserManager.getProfileParent(anyInt())).thenReturn(CURRENT_USER_INFO);
+ Intent intent = new Intent(mContext, IntentForwarderWrapperActivity.class);
+ intent.setAction(Intent.ACTION_DIAL);
+ IntentForwarderWrapperActivity activity = mActivityRule.launchActivity(intent);
+ verify(mIPm).canForwardTo(any(), any(), anyInt(), anyInt());
+ assertEquals(activity.getStartActivityIntent().getAction(), intent.getAction());
+ assertEquals(activity.getUserIdActivityLaunchedIn(), CURRENT_USER_INFO.id);
+ }
+
+ @Test
+ public void shouldForwardToParent_mms_privateProfile() throws Exception {
+ mSetFlagsRule.enableFlags(
+ android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+ android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_INTENT_REDIRECTION);
+
+ sComponentName = FORWARD_TO_PARENT_COMPONENT_NAME;
+ when(mIPm.canForwardTo(
+ any(Intent.class), nullable(String.class), anyInt(), anyInt())).thenReturn(true);
+
+ List<UserInfo> profiles = new ArrayList<>();
+ profiles.add(CURRENT_USER_INFO);
+ profiles.add(PRIVATE_PROFILE_INFO);
+ when(mUserManager.getProfiles(anyInt())).thenReturn(profiles);
+ when(mUserManager.getProfileParent(anyInt())).thenReturn(CURRENT_USER_INFO);
+ Intent intent = new Intent(mContext, IntentForwarderWrapperActivity.class);
+ intent.setAction(Intent.ACTION_SEND);
+ intent.setType(TYPE_PLAIN_TEXT);
+ IntentForwarderWrapperActivity activity = mActivityRule.launchActivity(intent);
+ verify(mIPm).canForwardTo(any(), any(), anyInt(), anyInt());
+ assertEquals(activity.getStartActivityIntent().getAction(), intent.getAction());
+ assertEquals(activity.getStartActivityIntent().getType(), intent.getType());
+ assertEquals(activity.getUserIdActivityLaunchedIn(), CURRENT_USER_INFO.id);
+ }
+
private void setupShouldSkipDisclosureTest() throws RemoteException {
sComponentName = FORWARD_TO_PARENT_COMPONENT_NAME;
sActivityName = "MyTestActivity";
@@ -688,6 +741,14 @@
protected MetricsLogger getMetricsLogger() {
return mMetricsLogger;
}
+
+ Intent getStartActivityIntent() {
+ return mStartActivityIntent;
+ }
+
+ int getUserIdActivityLaunchedIn() {
+ return mUserIdActivityLaunchedIn;
+ }
}
public class TestInjector implements IntentForwarderActivity.Injector {
diff --git a/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java b/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java
index 66be05f..ed641e0 100644
--- a/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java
+++ b/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java
@@ -309,17 +309,17 @@
private void pauseActivity(ActivityClientRecord r) {
mThread.handlePauseActivity(r, false /* finished */,
- false /* userLeaving */, 0 /* configChanges */, false /* autoEnteringPip */,
+ false /* userLeaving */, false /* autoEnteringPip */,
null /* pendingActions */, "test");
}
private void stopActivity(ActivityClientRecord r) {
- mThread.handleStopActivity(r, 0 /* configChanges */,
+ mThread.handleStopActivity(r,
new PendingTransactionActions(), false /* finalStateRequest */, "test");
}
private void destroyActivity(ActivityClientRecord r) {
- mThread.handleDestroyActivity(r, true /* finishing */, 0 /* configChanges */,
+ mThread.handleDestroyActivity(r, true /* finishing */,
false /* getNonConfigInstance */, "test");
}
diff --git a/data/etc/Android.bp b/data/etc/Android.bp
index 238a3e1..1410950 100644
--- a/data/etc/Android.bp
+++ b/data/etc/Android.bp
@@ -72,6 +72,12 @@
src: "enhanced-confirmation.xml",
}
+prebuilt_etc {
+ name: "package-shareduid-allowlist.xml",
+ sub_dir: "sysconfig",
+ src: "package-shareduid-allowlist.xml",
+}
+
// Privapp permission whitelist files
prebuilt_etc {
diff --git a/data/etc/CleanSpec.mk b/data/etc/CleanSpec.mk
index 783a7ed..fd38d27 100644
--- a/data/etc/CleanSpec.mk
+++ b/data/etc/CleanSpec.mk
@@ -43,6 +43,8 @@
#$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/core_intermediates)
#$(call add-clean-step, find $(OUT_DIR) -type f -name "IGTalkSession*" -print0 | xargs -0 rm -f)
#$(call add-clean-step, rm -rf $(PRODUCT_OUT)/data/*)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/product/etc/sysconfig/package-shareduid-allowlist.xml)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/product/etc/sysconfig/package-shareduid-allowlist.xml)
$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/product/etc/permissions/com.android.carrierconfig.xml)
$(call add-clean-step, rm -rf $(PRODUCT_OUT)/product/etc/permissions/com.android.carrierconfig.xml)
$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/product/etc/permissions/com.android.emergency.xml)
diff --git a/data/etc/package-shareduid-allowlist.xml b/data/etc/package-shareduid-allowlist.xml
new file mode 100644
index 0000000..2401d4a
--- /dev/null
+++ b/data/etc/package-shareduid-allowlist.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 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.
+ -->
+
+<!--
+This XML defines an allowlist for packages that want to join a particular shared-uid.
+If a non-system package that is signed with platform signature, is trying to join a particular
+shared-uid, and not in this list, the installation will fail.
+
+- The "package" XML attribute refers to the app's package name.
+- The "shareduid" XML attribute refers to the shared uid name.
+
+Example usage
+ 1. <allow-package-shareduid package="com.example.app" shareduid="android.uid.system"/>
+ Indicates that a package - com.example.app, will be able to join android.uid.system.
+ 2. <allow-package-shareduid package="oem.example.app" shareduid="oem.uid.custom"/>
+ Indicates that a package - oem.example.app, will be able to join oem.uid.custom.
+-->
+
+<config>
+ <allow-package-shareduid package="android.test.settings" shareduid="android.uid.system" />
+</config>
diff --git a/data/fonts/font_fallback.xml b/data/fonts/font_fallback.xml
index 15ea15a..53024ab 100644
--- a/data/fonts/font_fallback.xml
+++ b/data/fonts/font_fallback.xml
@@ -792,14 +792,10 @@
</font>
</family>
<family lang="ja">
- <font weight="400" style="normal">
- NotoSerifHentaigana-EL.ttf
+ <font postScriptName="NotoSerifHentaigana-ExtraLight" supportedAxes="wght">
+ NotoSerifHentaigana.ttf
<axis tag="wght" stylevalue="400"/>
</font>
- <font weight="700" style="normal">
- NotoSerifHentaigana-EL.ttf
- <axis tag="wght" stylevalue="700"/>
- </font>
</family>
<family lang="ko">
<font weight="400" style="normal" index="1" postScriptName="NotoSansCJKjp-Regular">
diff --git a/data/fonts/font_fallback_cjkvf.xml b/data/fonts/font_fallback_cjkvf.xml
index c1ca67e..ac1b064 100644
--- a/data/fonts/font_fallback_cjkvf.xml
+++ b/data/fonts/font_fallback_cjkvf.xml
@@ -804,14 +804,10 @@
</font>
</family>
<family lang="ja">
- <font weight="400" style="normal">
- NotoSerifHentaigana-EL.ttf
+ <font postScriptName="NotoSerifHentaigana-ExtraLight" supportedAxes="wght">
+ NotoSerifHentaigana.ttf
<axis tag="wght" stylevalue="400"/>
</font>
- <font weight="700" style="normal">
- NotoSerifHentaigana-EL.ttf
- <axis tag="wght" stylevalue="700"/>
- </font>
</family>
<family lang="ko">
<font weight="400" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin"
diff --git a/data/fonts/fonts.xml b/data/fonts/fonts.xml
index b23f005..d1aa8e9 100644
--- a/data/fonts/fonts.xml
+++ b/data/fonts/fonts.xml
@@ -1433,12 +1433,12 @@
</font>
</family>
<family lang="ja">
- <font weight="400" style="normal">
- NotoSerifHentaigana-EL.ttf
+ <font weight="400" style="normal" postScriptName="NotoSerifHentaigana-ExtraLight">
+ NotoSerifHentaigana.ttf
<axis tag="wght" stylevalue="400"/>
</font>
- <font weight="700" style="normal">
- NotoSerifHentaigana-EL.ttf
+ <font weight="700" style="normal" postScriptName="NotoSerifHentaigana-ExtraLight">
+ NotoSerifHentaigana.ttf
<axis tag="wght" stylevalue="700"/>
</font>
</family>
diff --git a/data/fonts/fonts_cjkvf.xml b/data/fonts/fonts_cjkvf.xml
index 1ab71ae..9545ae7 100644
--- a/data/fonts/fonts_cjkvf.xml
+++ b/data/fonts/fonts_cjkvf.xml
@@ -1532,12 +1532,12 @@
</font>
</family>
<family lang="ja">
- <font weight="400" style="normal">
- NotoSerifHentaigana-EL.ttf
+ <font weight="400" style="normal" postScriptName="NotoSerifHentaigana-ExtraLight">
+ NotoSerifHentaigana.ttf
<axis tag="wght" stylevalue="400"/>
</font>
- <font weight="700" style="normal">
- NotoSerifHentaigana-EL.ttf
+ <font weight="700" style="normal" postScriptName="NotoSerifHentaigana-ExtraLight">
+ NotoSerifHentaigana.ttf
<axis tag="wght" stylevalue="700"/>
</font>
</family>
diff --git a/graphics/java/android/graphics/text/MeasuredText.java b/graphics/java/android/graphics/text/MeasuredText.java
index 6da0719..884268a 100644
--- a/graphics/java/android/graphics/text/MeasuredText.java
+++ b/graphics/java/android/graphics/text/MeasuredText.java
@@ -29,11 +29,13 @@
import com.android.internal.util.Preconditions;
import dalvik.annotation.optimization.CriticalNative;
+import dalvik.annotation.optimization.NeverInline;
import libcore.util.NativeAllocationRegistry;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.Locale;
import java.util.Objects;
/**
@@ -85,6 +87,30 @@
return mChars;
}
+ private void rangeCheck(int start, int end) {
+ if (start < 0 || start > end || end > mChars.length) {
+ throwRangeError(start, end);
+ }
+ }
+
+ @NeverInline
+ private void throwRangeError(int start, int end) {
+ throw new IllegalArgumentException(String.format(Locale.US,
+ "start(%d) end(%d) length(%d) out of bounds", start, end, mChars.length));
+ }
+
+ private void offsetCheck(int offset) {
+ if (offset < 0 || offset >= mChars.length) {
+ throwOffsetError(offset);
+ }
+ }
+
+ @NeverInline
+ private void throwOffsetError(int offset) {
+ throw new IllegalArgumentException(String.format(Locale.US,
+ "offset (%d) length(%d) out of bounds", offset, mChars.length));
+ }
+
/**
* Returns the width of a given range.
*
@@ -93,12 +119,7 @@
*/
public @FloatRange(from = 0.0) @Px float getWidth(
@IntRange(from = 0) int start, @IntRange(from = 0) int end) {
- Preconditions.checkArgument(0 <= start && start <= mChars.length,
- "start(%d) must be 0 <= start <= %d", start, mChars.length);
- Preconditions.checkArgument(0 <= end && end <= mChars.length,
- "end(%d) must be 0 <= end <= %d", end, mChars.length);
- Preconditions.checkArgument(start <= end,
- "start(%d) is larger than end(%d)", start, end);
+ rangeCheck(start, end);
return nGetWidth(mNativePtr, start, end);
}
@@ -120,12 +141,7 @@
*/
public void getBounds(@IntRange(from = 0) int start, @IntRange(from = 0) int end,
@NonNull Rect rect) {
- Preconditions.checkArgument(0 <= start && start <= mChars.length,
- "start(%d) must be 0 <= start <= %d", start, mChars.length);
- Preconditions.checkArgument(0 <= end && end <= mChars.length,
- "end(%d) must be 0 <= end <= %d", end, mChars.length);
- Preconditions.checkArgument(start <= end,
- "start(%d) is larger than end(%d)", start, end);
+ rangeCheck(start, end);
Preconditions.checkNotNull(rect);
nGetBounds(mNativePtr, mChars, start, end, rect);
}
@@ -139,12 +155,7 @@
*/
public void getFontMetricsInt(@IntRange(from = 0) int start, @IntRange(from = 0) int end,
@NonNull Paint.FontMetricsInt outMetrics) {
- Preconditions.checkArgument(0 <= start && start <= mChars.length,
- "start(%d) must be 0 <= start <= %d", start, mChars.length);
- Preconditions.checkArgument(0 <= end && end <= mChars.length,
- "end(%d) must be 0 <= end <= %d", end, mChars.length);
- Preconditions.checkArgument(start <= end,
- "start(%d) is larger than end(%d)", start, end);
+ rangeCheck(start, end);
Objects.requireNonNull(outMetrics);
long packed = nGetExtent(mNativePtr, mChars, start, end);
@@ -160,8 +171,7 @@
* @param offset an offset of the character.
*/
public @FloatRange(from = 0.0f) @Px float getCharWidthAt(@IntRange(from = 0) int offset) {
- Preconditions.checkArgument(0 <= offset && offset < mChars.length,
- "offset(%d) is larger than text length %d" + offset, mChars.length);
+ offsetCheck(offset);
return nGetCharWidthAt(mNativePtr, offset);
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index 038d008..1abda42 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -56,6 +56,7 @@
import android.app.ActivityThread;
import android.app.Application;
import android.app.Instrumentation;
+import android.app.servertransaction.ClientTransactionListenerController;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -103,6 +104,7 @@
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.Executor;
+import java.util.function.BiConsumer;
/**
* Main controller class that manages split states and presentation.
@@ -178,6 +180,20 @@
private final List<ActivityStack> mLastReportedActivityStacks = new ArrayList<>();
+ /** WM Jetpack set callback for {@link EmbeddedActivityWindowInfo}. */
+ @GuardedBy("mLock")
+ @Nullable
+ private Pair<Executor, Consumer<EmbeddedActivityWindowInfo>>
+ mEmbeddedActivityWindowInfoCallback;
+
+ /** Listener registered to {@link ClientTransactionListenerController}. */
+ @GuardedBy("mLock")
+ @Nullable
+ private final BiConsumer<IBinder, ActivityWindowInfo> mActivityWindowInfoListener =
+ Flags.activityWindowInfoFlag()
+ ? this::onActivityWindowInfoChanged
+ : null;
+
private final Handler mHandler;
final Object mLock = new Object();
private final ActivityStartMonitor mActivityStartMonitor;
@@ -2456,6 +2472,13 @@
}
@VisibleForTesting
+ @Nullable
+ ActivityThread.ActivityClientRecord getActivityClientRecord(@NonNull Activity activity) {
+ return ActivityThread.currentActivityThread()
+ .getActivityClient(activity.getActivityToken());
+ }
+
+ @VisibleForTesting
ActivityStartMonitor getActivityStartMonitor() {
return mActivityStartMonitor;
}
@@ -2468,8 +2491,7 @@
@VisibleForTesting
@Nullable
IBinder getTaskFragmentTokenFromActivityClientRecord(@NonNull Activity activity) {
- final ActivityThread.ActivityClientRecord record = ActivityThread.currentActivityThread()
- .getActivityClient(activity.getActivityToken());
+ final ActivityThread.ActivityClientRecord record = getActivityClientRecord(activity);
return record != null ? record.mTaskFragmentToken : null;
}
@@ -2876,17 +2898,102 @@
}
}
+ @Override
+ public void setEmbeddedActivityWindowInfoCallback(@NonNull Executor executor,
+ @NonNull Consumer<EmbeddedActivityWindowInfo> callback) {
+ if (!Flags.activityWindowInfoFlag()) {
+ return;
+ }
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(callback);
+ synchronized (mLock) {
+ if (mEmbeddedActivityWindowInfoCallback == null) {
+ ClientTransactionListenerController.getInstance()
+ .registerActivityWindowInfoChangedListener(getActivityWindowInfoListener());
+ }
+ mEmbeddedActivityWindowInfoCallback = new Pair<>(executor, callback);
+ }
+ }
+
+ @Override
+ public void clearEmbeddedActivityWindowInfoCallback() {
+ if (!Flags.activityWindowInfoFlag()) {
+ return;
+ }
+ synchronized (mLock) {
+ if (mEmbeddedActivityWindowInfoCallback == null) {
+ return;
+ }
+ mEmbeddedActivityWindowInfoCallback = null;
+ ClientTransactionListenerController.getInstance()
+ .unregisterActivityWindowInfoChangedListener(getActivityWindowInfoListener());
+ }
+ }
+
+ @VisibleForTesting
+ @GuardedBy("mLock")
@Nullable
- private static ActivityWindowInfo getActivityWindowInfo(@NonNull Activity activity) {
+ BiConsumer<IBinder, ActivityWindowInfo> getActivityWindowInfoListener() {
+ return mActivityWindowInfoListener;
+ }
+
+ @Nullable
+ @Override
+ public EmbeddedActivityWindowInfo getEmbeddedActivityWindowInfo(@NonNull Activity activity) {
+ if (!Flags.activityWindowInfoFlag()) {
+ return null;
+ }
+ synchronized (mLock) {
+ final ActivityWindowInfo activityWindowInfo = getActivityWindowInfo(activity);
+ return activityWindowInfo != null
+ ? translateActivityWindowInfo(activity, activityWindowInfo)
+ : null;
+ }
+ }
+
+ @VisibleForTesting
+ void onActivityWindowInfoChanged(@NonNull IBinder activityToken,
+ @NonNull ActivityWindowInfo activityWindowInfo) {
+ synchronized (mLock) {
+ if (mEmbeddedActivityWindowInfoCallback == null) {
+ return;
+ }
+ final Executor executor = mEmbeddedActivityWindowInfoCallback.first;
+ final Consumer<EmbeddedActivityWindowInfo> callback =
+ mEmbeddedActivityWindowInfoCallback.second;
+
+ final Activity activity = getActivity(activityToken);
+ if (activity == null) {
+ return;
+ }
+ final EmbeddedActivityWindowInfo info = translateActivityWindowInfo(
+ activity, activityWindowInfo);
+
+ executor.execute(() -> callback.accept(info));
+ }
+ }
+
+ @Nullable
+ private ActivityWindowInfo getActivityWindowInfo(@NonNull Activity activity) {
if (activity.isFinishing()) {
return null;
}
- final ActivityThread.ActivityClientRecord record =
- ActivityThread.currentActivityThread()
- .getActivityClient(activity.getActivityToken());
+ final ActivityThread.ActivityClientRecord record = getActivityClientRecord(activity);
return record != null ? record.getActivityWindowInfo() : null;
}
+ @NonNull
+ private static EmbeddedActivityWindowInfo translateActivityWindowInfo(
+ @NonNull Activity activity, @NonNull ActivityWindowInfo activityWindowInfo) {
+ final boolean isEmbedded = activityWindowInfo.isEmbedded();
+ final Rect activityBounds = new Rect(activity.getResources().getConfiguration()
+ .windowConfiguration.getBounds());
+ final Rect taskBounds = new Rect(activityWindowInfo.getTaskBounds());
+ final Rect activityStackBounds = new Rect(activityWindowInfo.getTaskFragmentBounds());
+ return new EmbeddedActivityWindowInfo(activity, isEmbedded, activityBounds, taskBounds,
+ activityStackBounds);
+ }
+
/**
* If the two rules have the same presentation, and the calculated {@link SplitAttributes}
* matches the {@link SplitAttributes} of {@link SplitContainer}, we can reuse the same
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
index 00f8b59..bdeeb73 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
@@ -72,6 +72,8 @@
import android.annotation.NonNull;
import android.app.Activity;
import android.app.ActivityOptions;
+import android.app.ActivityThread;
+import android.app.servertransaction.ClientTransactionListenerController;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.ActivityInfo;
@@ -83,9 +85,11 @@
import android.os.Handler;
import android.os.IBinder;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.util.ArraySet;
import android.view.WindowInsets;
import android.view.WindowMetrics;
+import android.window.ActivityWindowInfo;
import android.window.TaskFragmentInfo;
import android.window.TaskFragmentOrganizer;
import android.window.TaskFragmentParentInfo;
@@ -99,7 +103,10 @@
import androidx.window.extensions.layout.WindowLayoutComponentImpl;
import androidx.window.extensions.layout.WindowLayoutInfo;
+import com.android.window.flags.Flags;
+
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -110,6 +117,8 @@
import java.util.Collections;
import java.util.List;
import java.util.Set;
+import java.util.concurrent.Executor;
+import java.util.function.BiConsumer;
import java.util.function.Consumer;
/**
@@ -127,6 +136,9 @@
private static final Intent PLACEHOLDER_INTENT = new Intent().setComponent(
new ComponentName("test", "placeholder"));
+ @Rule
+ public final SetFlagsRule mSetFlagRule = new SetFlagsRule();
+
private Activity mActivity;
@Mock
private Resources mActivityResources;
@@ -138,6 +150,13 @@
private Handler mHandler;
@Mock
private WindowLayoutComponentImpl mWindowLayoutComponent;
+ @Mock
+ private ActivityWindowInfo mActivityWindowInfo;
+ @Mock
+ private BiConsumer<IBinder, ActivityWindowInfo> mActivityWindowInfoListener;
+ @Mock
+ private androidx.window.extensions.core.util.function.Consumer<EmbeddedActivityWindowInfo>
+ mEmbeddedActivityWindowInfoCallback;
private SplitController mSplitController;
private SplitPresenter mSplitPresenter;
@@ -1529,6 +1548,73 @@
.getTopNonFinishingActivity(), secondaryActivity);
}
+ @Test
+ public void testIsActivityEmbedded() {
+ mSetFlagRule.enableFlags(Flags.FLAG_ACTIVITY_WINDOW_INFO_FLAG);
+
+ assertFalse(mSplitController.isActivityEmbedded(mActivity));
+
+ doReturn(true).when(mActivityWindowInfo).isEmbedded();
+
+ assertTrue(mSplitController.isActivityEmbedded(mActivity));
+ }
+
+ @Test
+ public void testGetEmbeddedActivityWindowInfo() {
+ mSetFlagRule.enableFlags(Flags.FLAG_ACTIVITY_WINDOW_INFO_FLAG);
+
+ final boolean isEmbedded = true;
+ final Rect activityBounds = mActivity.getResources().getConfiguration().windowConfiguration
+ .getBounds();
+ final Rect taskBounds = new Rect(0, 0, 1000, 2000);
+ final Rect activityStackBounds = new Rect(0, 0, 500, 2000);
+ doReturn(isEmbedded).when(mActivityWindowInfo).isEmbedded();
+ doReturn(taskBounds).when(mActivityWindowInfo).getTaskBounds();
+ doReturn(activityStackBounds).when(mActivityWindowInfo).getTaskFragmentBounds();
+
+ final EmbeddedActivityWindowInfo expected = new EmbeddedActivityWindowInfo(mActivity,
+ isEmbedded, activityBounds, taskBounds, activityStackBounds);
+ assertEquals(expected, mSplitController.getEmbeddedActivityWindowInfo(mActivity));
+ }
+
+ @Test
+ public void testSetEmbeddedActivityWindowInfoCallback() {
+ mSetFlagRule.enableFlags(Flags.FLAG_ACTIVITY_WINDOW_INFO_FLAG);
+
+ final ClientTransactionListenerController controller = ClientTransactionListenerController
+ .getInstance();
+ spyOn(controller);
+ doNothing().when(controller).registerActivityWindowInfoChangedListener(any());
+ doReturn(mActivityWindowInfoListener).when(mSplitController)
+ .getActivityWindowInfoListener();
+ final Executor executor = Runnable::run;
+
+ // Register to ClientTransactionListenerController
+ mSplitController.setEmbeddedActivityWindowInfoCallback(executor,
+ mEmbeddedActivityWindowInfoCallback);
+
+ verify(controller).registerActivityWindowInfoChangedListener(mActivityWindowInfoListener);
+ verify(mEmbeddedActivityWindowInfoCallback, never()).accept(any());
+
+ // Test onActivityWindowInfoChanged triggered.
+ mSplitController.onActivityWindowInfoChanged(mActivity.getActivityToken(),
+ mActivityWindowInfo);
+
+ verify(mEmbeddedActivityWindowInfoCallback).accept(any());
+
+ // Unregister to ClientTransactionListenerController
+ mSplitController.clearEmbeddedActivityWindowInfoCallback();
+
+ verify(controller).unregisterActivityWindowInfoChangedListener(mActivityWindowInfoListener);
+
+ // Test onActivityWindowInfoChanged triggered as no-op after clear callback.
+ clearInvocations(mEmbeddedActivityWindowInfoCallback);
+ mSplitController.onActivityWindowInfoChanged(mActivity.getActivityToken(),
+ mActivityWindowInfo);
+
+ verify(mEmbeddedActivityWindowInfoCallback, never()).accept(any());
+ }
+
/** Creates a mock activity in the organizer process. */
private Activity createMockActivity() {
return createMockActivity(TASK_ID);
@@ -1537,13 +1623,17 @@
/** Creates a mock activity in the organizer process. */
private Activity createMockActivity(int taskId) {
final Activity activity = mock(Activity.class);
+ final ActivityThread.ActivityClientRecord activityClientRecord =
+ mock(ActivityThread.ActivityClientRecord.class);
doReturn(mActivityResources).when(activity).getResources();
final IBinder activityToken = new Binder();
doReturn(activityToken).when(activity).getActivityToken();
doReturn(activity).when(mSplitController).getActivity(activityToken);
+ doReturn(activityClientRecord).when(mSplitController).getActivityClientRecord(activity);
doReturn(taskId).when(activity).getTaskId();
doReturn(new ActivityInfo()).when(activity).getActivityInfo();
doReturn(DEFAULT_DISPLAY).when(activity).getDisplayId();
+ doReturn(mActivityWindowInfo).when(activityClientRecord).getActivityWindowInfo();
return activity;
}
diff --git a/libs/WindowManager/Shell/res/values/config.xml b/libs/WindowManager/Shell/res/values/config.xml
index 8baaf2f..a541c59 100644
--- a/libs/WindowManager/Shell/res/values/config.xml
+++ b/libs/WindowManager/Shell/res/values/config.xml
@@ -145,4 +145,7 @@
<!-- Whether CompatUIController is enabled -->
<bool name="config_enableCompatUIController">true</bool>
+
+ <!-- Whether pointer pilfer is required to start back animation. -->
+ <bool name="config_backAnimationRequiresPointerPilfer">true</bool>
</resources>
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index 74967ef0..7dd3961 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -510,10 +510,6 @@
split select if dragged until the touch input is within the range. -->
<dimen name="desktop_mode_transition_area_width">32dp</dimen>
- <!-- The height of the area at the top of the screen where a freeform task will transition to
- fullscreen if dragged until the top bound of the task is within the area. -->
- <dimen name="desktop_mode_transition_area_height">16dp</dimen>
-
<!-- The width of the area where a desktop task will transition to fullscreen. -->
<dimen name="desktop_mode_fullscreen_from_desktop_width">80dp</dimen>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index 2606fb6..9bd8531 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -64,6 +64,7 @@
import com.android.internal.protolog.common.ProtoLog;
import com.android.internal.util.LatencyTracker;
import com.android.internal.view.AppearanceRegion;
+import com.android.wm.shell.R;
import com.android.wm.shell.animation.FlingAnimationUtils;
import com.android.wm.shell.common.ExternalInterfaceBinder;
import com.android.wm.shell.common.RemoteCallable;
@@ -115,6 +116,7 @@
private boolean mShouldStartOnNextMoveEvent = false;
private boolean mOnBackStartDispatched = false;
private boolean mPointerPilfered = false;
+ private final boolean mRequirePointerPilfer;
private final FlingAnimationUtils mFlingAnimationUtils;
@@ -220,6 +222,8 @@
mActivityTaskManager = activityTaskManager;
mContext = context;
mContentResolver = contentResolver;
+ mRequirePointerPilfer =
+ context.getResources().getBoolean(R.bool.config_backAnimationRequiresPointerPilfer);
mBgHandler = bgHandler;
shellInit.addInitCallback(this::onInit, this);
mAnimationBackground = backAnimationBackground;
@@ -560,7 +564,9 @@
private void tryDispatchOnBackStarted(
IOnBackInvokedCallback callback,
BackMotionEvent backEvent) {
- if (mOnBackStartDispatched || callback == null || !mPointerPilfered) {
+ if (mOnBackStartDispatched
+ || callback == null
+ || (!mPointerPilfered && mRequirePointerPilfer)) {
return;
}
dispatchOnBackStarted(callback, backEvent);
@@ -1006,6 +1012,8 @@
pw.println(prefix + " mBackGestureStarted=" + mBackGestureStarted);
pw.println(prefix + " mPostCommitAnimationInProgress=" + mPostCommitAnimationInProgress);
pw.println(prefix + " mShouldStartOnNextMoveEvent=" + mShouldStartOnNextMoveEvent);
+ pw.println(prefix + " mPointerPilfered=" + mPointerPilfered);
+ pw.println(prefix + " mRequirePointerPilfer=" + mRequirePointerPilfer);
pw.println(prefix + " mCurrentTracker state:");
mCurrentTracker.dump(pw, prefix + " ");
pw.println(prefix + " mQueuedTracker state:");
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
index 7091c4b..fb0ed15 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
@@ -98,6 +98,7 @@
* Based on the coordinates of the current drag event, determine which indicator type we should
* display, including no visible indicator.
*/
+ @NonNull
IndicatorType updateIndicatorType(PointF inputCoordinates, int windowingMode) {
final DisplayLayout layout = mDisplayController.getDisplayLayout(mTaskInfo.displayId);
// If we are in freeform, we don't want a visible indicator in the "freeform" drag zone.
@@ -136,18 +137,18 @@
Region calculateFullscreenRegion(DisplayLayout layout,
@WindowConfiguration.WindowingMode int windowingMode, int captionHeight) {
final Region region = new Region();
- int edgeTransitionHeight = mContext.getResources().getDimensionPixelSize(
- com.android.wm.shell.R.dimen.desktop_mode_transition_area_height);
+ int transitionHeight = windowingMode == WINDOWING_MODE_FREEFORM
+ ? 2 * layout.stableInsets().top
+ : mContext.getResources().getDimensionPixelSize(
+ com.android.wm.shell.R.dimen.desktop_mode_fullscreen_from_desktop_height);
// A thin, short Rect at the top of the screen.
if (windowingMode == WINDOWING_MODE_FREEFORM) {
int fromFreeformWidth = mContext.getResources().getDimensionPixelSize(
com.android.wm.shell.R.dimen.desktop_mode_fullscreen_from_desktop_width);
- int fromFreeformHeight = mContext.getResources().getDimensionPixelSize(
- com.android.wm.shell.R.dimen.desktop_mode_fullscreen_from_desktop_height);
region.union(new Rect((layout.width() / 2) - (fromFreeformWidth / 2),
-captionHeight,
(layout.width() / 2) + (fromFreeformWidth / 2),
- fromFreeformHeight));
+ transitionHeight));
}
// A screen-wide, shorter Rect if the task is in fullscreen or split.
if (windowingMode == WINDOWING_MODE_FULLSCREEN
@@ -155,7 +156,7 @@
region.union(new Rect(0,
-captionHeight,
layout.width(),
- edgeTransitionHeight));
+ transitionHeight));
}
return region;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index d1328ca..654409f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -140,7 +140,7 @@
private val transitionAreaHeight
get() = context.resources.getDimensionPixelSize(
- com.android.wm.shell.R.dimen.desktop_mode_transition_area_height
+ com.android.wm.shell.R.dimen.desktop_mode_fullscreen_from_desktop_height
)
private val transitionAreaWidth
@@ -565,30 +565,7 @@
* @param position the portion of the screen (RIGHT or LEFT) we want to snap the task to.
*/
fun snapToHalfScreen(taskInfo: RunningTaskInfo, position: SnapPosition) {
- val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return
-
- val stableBounds = Rect()
- displayLayout.getStableBounds(stableBounds)
-
- val destinationWidth = stableBounds.width() / 2
- val destinationBounds = when (position) {
- SnapPosition.LEFT -> {
- Rect(
- stableBounds.left,
- stableBounds.top,
- stableBounds.left + destinationWidth,
- stableBounds.bottom
- )
- }
- SnapPosition.RIGHT -> {
- Rect(
- stableBounds.right - destinationWidth,
- stableBounds.top,
- stableBounds.right,
- stableBounds.bottom
- )
- }
- }
+ val destinationBounds = getSnapBounds(taskInfo, position)
if (destinationBounds == taskInfo.configuration.windowConfiguration.bounds) return
@@ -609,8 +586,35 @@
outBounds.set(0, 0, desiredWidth, desiredHeight)
// Center the task in screen bounds
outBounds.offset(
- screenBounds.centerX() - outBounds.centerX(),
- screenBounds.centerY() - outBounds.centerY())
+ screenBounds.centerX() - outBounds.centerX(),
+ screenBounds.centerY() - outBounds.centerY())
+ }
+
+ private fun getSnapBounds(taskInfo: RunningTaskInfo, position: SnapPosition): Rect {
+ val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return Rect()
+
+ val stableBounds = Rect()
+ displayLayout.getStableBounds(stableBounds)
+
+ val destinationWidth = stableBounds.width() / 2
+ return when (position) {
+ SnapPosition.LEFT -> {
+ Rect(
+ stableBounds.left,
+ stableBounds.top,
+ stableBounds.left + destinationWidth,
+ stableBounds.bottom
+ )
+ }
+ SnapPosition.RIGHT -> {
+ Rect(
+ stableBounds.right - destinationWidth,
+ stableBounds.top,
+ stableBounds.right,
+ stableBounds.bottom
+ )
+ }
+ }
}
/**
@@ -646,7 +650,7 @@
?.let { homeTask -> wct.reorder(homeTask.getToken(), true /* onTop */) }
}
- private fun releaseVisualIndicator() {
+ fun releaseVisualIndicator() {
val t = SurfaceControl.Transaction()
visualIndicator?.releaseVisualIndicator(t)
visualIndicator = null
@@ -927,16 +931,13 @@
taskSurface: SurfaceControl,
inputX: Float,
taskTop: Float
- ) {
+ ): DesktopModeVisualIndicator.IndicatorType {
// If the visual indicator does not exist, create it.
- if (visualIndicator == null) {
- visualIndicator = DesktopModeVisualIndicator(
- syncQueue, taskInfo, displayController, context, taskSurface,
- rootTaskDisplayAreaOrganizer)
- }
- // Then, update the indicator type.
- val indicator = visualIndicator ?: return
- indicator.updateIndicatorType(PointF(inputX, taskTop), taskInfo.windowingMode)
+ val indicator = visualIndicator ?: DesktopModeVisualIndicator(
+ syncQueue, taskInfo, displayController, context, taskSurface,
+ rootTaskDisplayAreaOrganizer)
+ if (visualIndicator == null) visualIndicator = indicator
+ return indicator.updateIndicatorType(PointF(inputX, taskTop), taskInfo.windowingMode)
}
/**
@@ -956,20 +957,28 @@
if (taskInfo.configuration.windowConfiguration.windowingMode != WINDOWING_MODE_FREEFORM) {
return
}
- if (taskBounds.top <= transitionAreaHeight) {
- moveToFullscreenWithAnimation(taskInfo, position)
- return
- }
- if (inputCoordinate.x <= transitionAreaWidth) {
- releaseVisualIndicator()
- snapToHalfScreen(taskInfo, SnapPosition.LEFT)
- return
- }
- if (inputCoordinate.x >= (displayController.getDisplayLayout(taskInfo.displayId)?.width()
- ?.minus(transitionAreaWidth) ?: return)) {
- releaseVisualIndicator()
- snapToHalfScreen(taskInfo, SnapPosition.RIGHT)
- return
+
+ val indicator = visualIndicator ?: return
+ val indicatorType = indicator.updateIndicatorType(
+ PointF(inputCoordinate.x, taskBounds.top.toFloat()),
+ taskInfo.windowingMode
+ )
+ when (indicatorType) {
+ DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR -> {
+ moveToFullscreenWithAnimation(taskInfo, position)
+ }
+ DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_LEFT_INDICATOR -> {
+ releaseVisualIndicator()
+ snapToHalfScreen(taskInfo, SnapPosition.LEFT)
+ }
+ DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_RIGHT_INDICATOR -> {
+ releaseVisualIndicator()
+ snapToHalfScreen(taskInfo, SnapPosition.RIGHT)
+ }
+ DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR,
+ DesktopModeVisualIndicator.IndicatorType.NO_INDICATOR -> {
+ releaseVisualIndicator()
+ }
}
// A freeform drag-move ended, remove the indicator immediately.
releaseVisualIndicator()
@@ -982,14 +991,28 @@
* @param y height of drag, to be checked against status bar height.
*/
fun onDragPositioningEndThroughStatusBar(
+ inputCoordinates: PointF,
taskInfo: RunningTaskInfo,
freeformBounds: Rect
) {
- finalizeDragToDesktop(taskInfo, freeformBounds)
- }
-
- private fun getStatusBarHeight(taskInfo: RunningTaskInfo): Int {
- return displayController.getDisplayLayout(taskInfo.displayId)?.stableInsets()?.top ?: 0
+ val indicator = visualIndicator ?: return
+ val indicatorType = indicator
+ .updateIndicatorType(inputCoordinates, taskInfo.windowingMode)
+ when (indicatorType) {
+ DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR -> {
+ finalizeDragToDesktop(taskInfo, freeformBounds)
+ }
+ DesktopModeVisualIndicator.IndicatorType.NO_INDICATOR,
+ DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR -> {
+ cancelDragToDesktop(taskInfo)
+ }
+ DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_LEFT_INDICATOR -> {
+ finalizeDragToDesktop(taskInfo, getSnapBounds(taskInfo, SnapPosition.LEFT))
+ }
+ DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_RIGHT_INDICATOR -> {
+ finalizeDragToDesktop(taskInfo, getSnapBounds(taskInfo, SnapPosition.RIGHT))
+ }
+ }
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index 2cdec81..4d47ca9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -122,6 +122,8 @@
private static final long PIP_KEEP_CLEAR_AREAS_DELAY =
SystemProperties.getLong("persist.wm.debug.pip_keep_clear_areas_delay", 200);
+ private static final long ENABLE_TOUCH_DELAY_MS = 200L;
+
private Context mContext;
protected ShellExecutor mMainExecutor;
private DisplayController mDisplayController;
@@ -152,6 +154,8 @@
private final Runnable mMovePipInResponseToKeepClearAreasChangeCallback =
this::onKeepClearAreasChangedCallback;
+ private final Runnable mEnableTouchCallback = () -> mTouchHandler.setTouchEnabled(true);
+
private void onKeepClearAreasChangedCallback() {
if (mIsKeyguardShowingOrAnimating) {
// early bail out if the change was caused by keyguard showing up
@@ -1037,6 +1041,7 @@
saveReentryState(pipBounds);
}
// Disable touches while the animation is running
+ mMainExecutor.removeCallbacks(mEnableTouchCallback);
mTouchHandler.setTouchEnabled(false);
if (mPinnedStackAnimationRecentsCallback != null) {
mPinnedStackAnimationRecentsCallback.onPipAnimationStarted();
@@ -1067,7 +1072,7 @@
InteractionJankMonitor.getInstance().end(CUJ_PIP_TRANSITION);
// Re-enable touches after the animation completes
- mTouchHandler.setTouchEnabled(true);
+ mMainExecutor.executeDelayed(mEnableTouchCallback, ENABLE_TOUCH_DELAY_MS);
mTouchHandler.onPinnedStackAnimationEnded(direction);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index fa3d8a6..f4ccd68 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -30,6 +30,10 @@
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
+import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR;
+import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR;
+import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_LEFT_INDICATOR;
+import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_RIGHT_INDICATOR;
import static com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler.FREEFORM_ANIMATION_DURATION;
import static com.android.wm.shell.windowdecor.MoveToDesktopAnimator.DRAG_FREEFORM_SCALE;
@@ -81,6 +85,7 @@
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
import com.android.wm.shell.desktopmode.DesktopModeStatus;
+import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator;
import com.android.wm.shell.desktopmode.DesktopTasksController;
import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition;
import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
@@ -119,9 +124,8 @@
private final Choreographer mMainChoreographer;
private final DisplayController mDisplayController;
private final SyncTransactionQueue mSyncQueue;
- private final Optional<DesktopTasksController> mDesktopTasksController;
+ private final DesktopTasksController mDesktopTasksController;
private final InputManager mInputManager;
-
private boolean mTransitionDragActive;
private SparseArray<EventReceiver> mEventReceiversByDisplay = new SparseArray<>();
@@ -129,8 +133,7 @@
private final ExclusionRegionListener mExclusionRegionListener =
new ExclusionRegionListenerImpl();
- private final SparseArray<DesktopModeWindowDecoration> mWindowDecorByTaskId =
- new SparseArray<>();
+ private final SparseArray<DesktopModeWindowDecoration> mWindowDecorByTaskId;
private final DragStartListenerImpl mDragStartListener = new DragStartListenerImpl();
private final InputMonitorFactory mInputMonitorFactory;
private TaskOperations mTaskOperations;
@@ -197,7 +200,8 @@
new DesktopModeWindowDecoration.Factory(),
new InputMonitorFactory(),
SurfaceControl.Transaction::new,
- rootTaskDisplayAreaOrganizer);
+ rootTaskDisplayAreaOrganizer,
+ new SparseArray<>());
}
@VisibleForTesting
@@ -219,7 +223,8 @@
DesktopModeWindowDecoration.Factory desktopModeWindowDecorFactory,
InputMonitorFactory inputMonitorFactory,
Supplier<SurfaceControl.Transaction> transactionFactory,
- RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) {
+ RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
+ SparseArray<DesktopModeWindowDecoration> windowDecorByTaskId) {
mContext = context;
mMainExecutor = shellExecutor;
mMainHandler = mainHandler;
@@ -231,7 +236,7 @@
mDisplayInsetsController = displayInsetsController;
mSyncQueue = syncQueue;
mTransitions = transitions;
- mDesktopTasksController = desktopTasksController;
+ mDesktopTasksController = desktopTasksController.get();
mShellCommandHandler = shellCommandHandler;
mWindowManager = windowManager;
mDesktopModeWindowDecorFactory = desktopModeWindowDecorFactory;
@@ -239,6 +244,7 @@
mTransactionFactory = transactionFactory;
mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer;
mInputManager = mContext.getSystemService(InputManager.class);
+ mWindowDecorByTaskId = windowDecorByTaskId;
shellInit.addInitCallback(this::onInit, this);
}
@@ -248,8 +254,8 @@
mShellCommandHandler.addDumpCallback(this::dump, this);
mDisplayInsetsController.addInsetsChangedListener(mContext.getDisplayId(),
new DesktopModeOnInsetsChangedListener());
- mDesktopTasksController.ifPresent(c -> c.setOnTaskResizeAnimationListener(
- new DeskopModeOnTaskResizeAnimationListener()));
+ mDesktopTasksController.setOnTaskResizeAnimationListener(
+ new DeskopModeOnTaskResizeAnimationListener());
try {
mWindowManager.registerSystemGestureExclusionListener(mGestureExclusionListener,
mContext.getDisplayId());
@@ -273,7 +279,7 @@
DesktopModeWindowDecoration decor = mWindowDecorByTaskId.get(taskId);
if (decor != null && DesktopModeStatus.isEnabled()
&& decor.mTaskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
- mDesktopTasksController.ifPresent(c -> c.moveToSplit(decor.mTaskInfo));
+ mDesktopTasksController.moveToSplit(decor.mTaskInfo);
}
}
}
@@ -340,8 +346,7 @@
@Override
public void destroyWindowDecoration(RunningTaskInfo taskInfo) {
- final DesktopModeWindowDecoration decoration =
- mWindowDecorByTaskId.removeReturnOld(taskInfo.taskId);
+ final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskInfo.taskId);
if (decoration == null) return;
decoration.close();
@@ -349,6 +354,10 @@
if (mEventReceiversByDisplay.contains(displayId)) {
removeTaskFromEventReceiver(displayId);
}
+ // Remove the decoration from the cache last because WindowDecoration#close could still
+ // issue CANCEL MotionEvents to touch listeners before its view host is released.
+ // See b/327664694.
+ mWindowDecorByTaskId.remove(taskInfo.taskId);
}
private class DesktopModeTouchEventListener extends GestureDetector.SimpleOnGestureListener
@@ -414,13 +423,11 @@
decoration.closeHandleMenu();
}
} else if (id == R.id.desktop_button) {
- if (mDesktopTasksController.isPresent()) {
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- // App sometimes draws before the insets from WindowDecoration#relayout have
- // been added, so they must be added here
- mWindowDecorByTaskId.get(mTaskId).addCaptionInset(wct);
- mDesktopTasksController.get().moveToDesktop(mTaskId, wct);
- }
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ // App sometimes draws before the insets from WindowDecoration#relayout have
+ // been added, so they must be added here
+ mWindowDecorByTaskId.get(mTaskId).addCaptionInset(wct);
+ mDesktopTasksController.moveToDesktop(mTaskId, wct);
decoration.closeHandleMenu();
} else if (id == R.id.fullscreen_button) {
decoration.closeHandleMenu();
@@ -428,42 +435,37 @@
mSplitScreenController.moveTaskToFullscreen(mTaskId,
SplitScreenController.EXIT_REASON_DESKTOP_MODE);
} else {
- mDesktopTasksController.ifPresent(c ->
- c.moveToFullscreen(mTaskId));
+ mDesktopTasksController.moveToFullscreen(mTaskId);
}
} else if (id == R.id.split_screen_button) {
decoration.closeHandleMenu();
- mDesktopTasksController.ifPresent(c -> {
- c.requestSplit(decoration.mTaskInfo);
- });
+ mDesktopTasksController.requestSplit(decoration.mTaskInfo);
} else if (id == R.id.collapse_menu_button) {
decoration.closeHandleMenu();
} else if (id == R.id.select_button) {
if (DesktopModeStatus.IS_DISPLAY_CHANGE_ENABLED) {
// TODO(b/278084491): dev option to enable display switching
// remove when select is implemented
- mDesktopTasksController.ifPresent(c -> c.moveToNextDisplay(mTaskId));
+ mDesktopTasksController.moveToNextDisplay(mTaskId);
}
} else if (id == R.id.maximize_window) {
final RunningTaskInfo taskInfo = decoration.mTaskInfo;
decoration.closeHandleMenu();
decoration.closeMaximizeMenu();
- mDesktopTasksController.ifPresent(c -> c.toggleDesktopTaskSize(taskInfo));
+ mDesktopTasksController.toggleDesktopTaskSize(taskInfo);
} else if (id == R.id.maximize_menu_maximize_button) {
final RunningTaskInfo taskInfo = decoration.mTaskInfo;
- mDesktopTasksController.ifPresent(c -> c.toggleDesktopTaskSize(taskInfo));
+ mDesktopTasksController.toggleDesktopTaskSize(taskInfo);
decoration.closeHandleMenu();
decoration.closeMaximizeMenu();
} else if (id == R.id.maximize_menu_snap_left_button) {
final RunningTaskInfo taskInfo = decoration.mTaskInfo;
- mDesktopTasksController.ifPresent(c -> c.snapToHalfScreen(
- taskInfo, SnapPosition.LEFT));
+ mDesktopTasksController.snapToHalfScreen(taskInfo, SnapPosition.LEFT);
decoration.closeHandleMenu();
decoration.closeMaximizeMenu();
} else if (id == R.id.maximize_menu_snap_right_button) {
final RunningTaskInfo taskInfo = decoration.mTaskInfo;
- mDesktopTasksController.ifPresent(c -> c.snapToHalfScreen(
- taskInfo, SnapPosition.RIGHT));
+ mDesktopTasksController.snapToHalfScreen(taskInfo, SnapPosition.RIGHT);
decoration.closeHandleMenu();
decoration.closeMaximizeMenu();
}
@@ -572,7 +574,7 @@
private void moveTaskToFront(RunningTaskInfo taskInfo) {
if (!taskInfo.isFocused) {
- mDesktopTasksController.ifPresent(c -> c.moveTaskToFront(taskInfo));
+ mDesktopTasksController.moveTaskToFront(taskInfo);
}
}
@@ -616,10 +618,10 @@
final int dragPointerIdx = e.findPointerIndex(mDragPointerId);
final Rect newTaskBounds = mDragPositioningCallback.onDragPositioningMove(
e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
- mDesktopTasksController.ifPresent(c -> c.onDragPositioningMove(taskInfo,
+ mDesktopTasksController.onDragPositioningMove(taskInfo,
decoration.mTaskSurface,
e.getRawX(dragPointerIdx),
- newTaskBounds));
+ newTaskBounds);
mIsDragging = true;
return true;
}
@@ -641,10 +643,9 @@
(int) (e.getRawY(dragPointerIdx) - e.getY(dragPointerIdx)));
final Rect newTaskBounds = mDragPositioningCallback.onDragPositioningEnd(
e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
- mDesktopTasksController.ifPresent(c -> c.onDragPositioningEnd(taskInfo,
- position,
+ mDesktopTasksController.onDragPositioningEnd(taskInfo, position,
new PointF(e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx)),
- newTaskBounds));
+ newTaskBounds);
if (touchingButton && !mHasLongClicked) {
// We need the input event to not be consumed here to end the ripple
// effect on the touched button. We will reset drag state in the ensuing
@@ -672,10 +673,8 @@
&& action != MotionEvent.ACTION_CANCEL)) {
return false;
}
- mDesktopTasksController.ifPresent(c -> {
- final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId);
- c.toggleDesktopTaskSize(decoration.mTaskInfo);
- });
+ final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId);
+ mDesktopTasksController.toggleDesktopTaskSize(decoration.mTaskInfo);
return true;
}
}
@@ -839,20 +838,29 @@
return;
}
if (mTransitionDragActive) {
+ final DesktopModeVisualIndicator.IndicatorType indicatorType =
+ mDesktopTasksController.updateVisualIndicator(relevantDecor.mTaskInfo,
+ relevantDecor.mTaskSurface, ev.getRawX(), ev.getRawY());
mTransitionDragActive = false;
- final int statusBarHeight = getStatusBarHeight(
- relevantDecor.mTaskInfo.displayId);
- if (ev.getRawY() > 2 * statusBarHeight) {
+ if (indicatorType == TO_DESKTOP_INDICATOR
+ || indicatorType == TO_SPLIT_LEFT_INDICATOR
+ || indicatorType == TO_SPLIT_RIGHT_INDICATOR) {
if (DesktopModeStatus.isEnabled()) {
animateToDesktop(relevantDecor, ev);
}
mMoveToDesktopAnimator = null;
return;
} else if (mMoveToDesktopAnimator != null) {
- mDesktopTasksController.ifPresent(
- c -> c.cancelDragToDesktop(relevantDecor.mTaskInfo));
+ mDesktopTasksController.onDragPositioningEndThroughStatusBar(
+ new PointF(ev.getRawX(), ev.getRawY()),
+ relevantDecor.mTaskInfo,
+ calculateFreeformBounds(ev.getDisplayId(), DRAG_FREEFORM_SCALE));
mMoveToDesktopAnimator = null;
return;
+ } else {
+ // In cases where we create an indicator but do not start the
+ // move-to-desktop animation, we need to dismiss it.
+ mDesktopTasksController.releaseVisualIndicator();
}
}
relevantDecor.checkClickEvent(ev);
@@ -864,20 +872,17 @@
return;
}
if (mTransitionDragActive) {
- mDesktopTasksController.ifPresent(
- c -> c.updateVisualIndicator(
+ final DesktopModeVisualIndicator.IndicatorType indicatorType =
+ mDesktopTasksController.updateVisualIndicator(
relevantDecor.mTaskInfo,
- relevantDecor.mTaskSurface, ev.getRawX(), ev.getRawY()));
- final int statusBarHeight = getStatusBarHeight(
- relevantDecor.mTaskInfo.displayId);
- if (ev.getRawY() > statusBarHeight) {
+ relevantDecor.mTaskSurface, ev.getRawX(), ev.getRawY());
+ if (indicatorType != TO_FULLSCREEN_INDICATOR) {
if (mMoveToDesktopAnimator == null) {
mMoveToDesktopAnimator = new MoveToDesktopAnimator(
mContext, mDragToDesktopAnimationStartBounds,
relevantDecor.mTaskInfo, relevantDecor.mTaskSurface);
- mDesktopTasksController.ifPresent(
- c -> c.startDragToDesktop(relevantDecor.mTaskInfo,
- mMoveToDesktopAnimator));
+ mDesktopTasksController.startDragToDesktop(relevantDecor.mTaskInfo,
+ mMoveToDesktopAnimator);
}
}
if (mMoveToDesktopAnimator != null) {
@@ -923,6 +928,8 @@
* Animates a window to the center, grows to freeform size, and transitions to Desktop Mode.
* @param relevantDecor the window decor of the task to be animated
* @param ev the motion event that triggers the animation
+ * TODO(b/315527000): This animation needs to be adjusted to allow snap left/right cases.
+ * Currently fullscreen -> split snap still animates to center screen before readjusting.
*/
private void centerAndMoveToDesktopWithAnimation(DesktopModeWindowDecoration relevantDecor,
MotionEvent ev) {
@@ -946,13 +953,12 @@
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
- mDesktopTasksController.ifPresent(
- c -> {
- c.onDragPositioningEndThroughStatusBar(relevantDecor.mTaskInfo,
- calculateFreeformBounds(ev.getDisplayId(),
- DesktopTasksController
- .DESKTOP_MODE_INITIAL_BOUNDS_SCALE));
- });
+ mDesktopTasksController.onDragPositioningEndThroughStatusBar(
+ new PointF(ev.getRawX(), ev.getRawY()),
+ relevantDecor.mTaskInfo,
+ calculateFreeformBounds(ev.getDisplayId(),
+ DesktopTasksController
+ .DESKTOP_MODE_INITIAL_BOUNDS_SCALE));
}
});
animator.start();
@@ -1082,7 +1088,7 @@
final DragPositioningCallback dragPositioningCallback;
final int transitionAreaHeight = mContext.getResources().getDimensionPixelSize(
- R.dimen.desktop_mode_transition_area_height);
+ R.dimen.desktop_mode_fullscreen_from_desktop_height);
if (!DesktopModeStatus.isVeiledResizeEnabled()) {
dragPositioningCallback = new FluidResizeTaskPositioner(
mTaskOrganizer, mTransitions, windowDecoration, mDisplayController,
@@ -1181,12 +1187,12 @@
@Override
public void onExclusionRegionChanged(int taskId, Region region) {
- mDesktopTasksController.ifPresent(d -> d.onExclusionRegionChanged(taskId, region));
+ mDesktopTasksController.onExclusionRegionChanged(taskId, region);
}
@Override
public void onExclusionRegionDismissed(int taskId) {
- mDesktopTasksController.ifPresent(d -> d.removeExclusionRegionForTask(taskId));
+ mDesktopTasksController.removeExclusionRegionForTask(taskId);
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt
index f8ce4ee..9703dce 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt
@@ -56,18 +56,16 @@
context, taskSurface, taskDisplayAreaOrganizer)
whenever(displayLayout.width()).thenReturn(DISPLAY_BOUNDS.width())
whenever(displayLayout.height()).thenReturn(DISPLAY_BOUNDS.height())
+ whenever(displayLayout.stableInsets()).thenReturn(STABLE_INSETS)
}
@Test
fun testFullscreenRegionCalculation() {
val transitionHeight = context.resources.getDimensionPixelSize(
- R.dimen.desktop_mode_transition_area_height)
+ R.dimen.desktop_mode_fullscreen_from_desktop_height)
val fromFreeformWidth = mContext.resources.getDimensionPixelSize(
R.dimen.desktop_mode_fullscreen_from_desktop_width
)
- val fromFreeformHeight = mContext.resources.getDimensionPixelSize(
- R.dimen.desktop_mode_fullscreen_from_desktop_height
- )
var testRegion = visualIndicator.calculateFullscreenRegion(displayLayout,
WINDOWING_MODE_FULLSCREEN, CAPTION_HEIGHT)
assertThat(testRegion.bounds).isEqualTo(Rect(0, -50, 2400, transitionHeight))
@@ -77,7 +75,7 @@
DISPLAY_BOUNDS.width() / 2 - fromFreeformWidth / 2,
-50,
DISPLAY_BOUNDS.width() / 2 + fromFreeformWidth / 2,
- fromFreeformHeight))
+ 2 * STABLE_INSETS.top))
testRegion = visualIndicator.calculateFullscreenRegion(displayLayout,
WINDOWING_MODE_MULTI_WINDOW, CAPTION_HEIGHT)
assertThat(testRegion.bounds).isEqualTo(Rect(0, -50, 2400, transitionHeight))
@@ -135,5 +133,12 @@
private const val TRANSITION_AREA_WIDTH = 32
private const val CAPTION_HEIGHT = 50
private val DISPLAY_BOUNDS = Rect(0, 0, 2400, 1600)
+ private const val NAVBAR_HEIGHT = 50
+ private val STABLE_INSETS = Rect(
+ DISPLAY_BOUNDS.left,
+ DISPLAY_BOUNDS.top + CAPTION_HEIGHT,
+ DISPLAY_BOUNDS.right,
+ DISPLAY_BOUNDS.bottom - NAVBAR_HEIGHT
+ )
}
}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
index 917fd71..9bb5482 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
@@ -29,6 +29,7 @@
import android.os.Handler
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
+import android.util.SparseArray
import android.view.Choreographer
import android.view.Display.DEFAULT_DISPLAY
import android.view.IWindowManager
@@ -72,6 +73,8 @@
import org.mockito.kotlin.whenever
import java.util.Optional
import java.util.function.Supplier
+import org.mockito.Mockito
+import org.mockito.kotlin.spy
/** Tests of [DesktopModeWindowDecorViewModel] */
@@ -102,6 +105,7 @@
private val transactionFactory = Supplier<SurfaceControl.Transaction> {
SurfaceControl.Transaction()
}
+ private val windowDecorByTaskIdSpy = spy(SparseArray<DesktopModeWindowDecoration>())
private lateinit var shellInit: ShellInit
private lateinit var desktopModeOnInsetsChangedListener: DesktopModeOnInsetsChangedListener
@@ -110,6 +114,7 @@
@Before
fun setUp() {
shellInit = ShellInit(mockShellExecutor)
+ windowDecorByTaskIdSpy.clear()
desktopModeWindowDecorViewModel = DesktopModeWindowDecorViewModel(
mContext,
mockShellExecutor,
@@ -128,7 +133,8 @@
mockDesktopModeWindowDecorFactory,
mockInputMonitorFactory,
transactionFactory,
- mockRootTaskDisplayAreaOrganizer
+ mockRootTaskDisplayAreaOrganizer,
+ windowDecorByTaskIdSpy
)
whenever(mockDisplayController.getDisplayLayout(any())).thenReturn(mockDisplayLayout)
@@ -332,6 +338,19 @@
verify(decoration, times(1)).relayout(task)
}
+ @Test
+ fun testDestroyWindowDecoration_closesBeforeCleanup() {
+ val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM)
+ val decoration = setUpMockDecorationForTask(task)
+ val inOrder = Mockito.inOrder(decoration, windowDecorByTaskIdSpy)
+
+ onTaskOpening(task)
+ desktopModeWindowDecorViewModel.destroyWindowDecoration(task)
+
+ inOrder.verify(decoration).close()
+ inOrder.verify(windowDecorByTaskIdSpy).remove(task.taskId)
+ }
+
private fun onTaskOpening(task: RunningTaskInfo, leash: SurfaceControl = SurfaceControl()) {
desktopModeWindowDecorViewModel.onTaskOpening(
task,
diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/CredentialKtx.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/CredentialKtx.kt
index b435bb8..892eabf 100644
--- a/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/CredentialKtx.kt
+++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/CredentialKtx.kt
@@ -138,6 +138,7 @@
entryGroupId = credentialEntry.entryGroupId.toString(),
isDefaultIconPreferredAsSingleProvider =
credentialEntry.isDefaultIconPreferredAsSingleProvider,
+ affiliatedDomain = credentialEntry.affiliatedDomain?.toString(),
)
)
}
@@ -165,6 +166,7 @@
entryGroupId = credentialEntry.entryGroupId.toString(),
isDefaultIconPreferredAsSingleProvider =
credentialEntry.isDefaultIconPreferredAsSingleProvider,
+ affiliatedDomain = credentialEntry.affiliatedDomain?.toString(),
)
)
}
@@ -191,6 +193,7 @@
entryGroupId = credentialEntry.entryGroupId.toString(),
isDefaultIconPreferredAsSingleProvider =
credentialEntry.isDefaultIconPreferredAsSingleProvider,
+ affiliatedDomain = credentialEntry.affiliatedDomain?.toString(),
)
)
}
diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/model/get/CredentialEntryInfo.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/model/get/CredentialEntryInfo.kt
index ac42b60..a657e97 100644
--- a/packages/CredentialManager/shared/src/com/android/credentialmanager/model/get/CredentialEntryInfo.kt
+++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/model/get/CredentialEntryInfo.kt
@@ -48,6 +48,7 @@
val entryGroupId: String, // Used for deduplication, and displayed as the grouping title
// "For <value-of-entryGroupId>" on the more-option screen.
val isDefaultIconPreferredAsSingleProvider: Boolean,
+ val affiliatedDomain: String?,
) : EntryInfo(
providerId,
entryKey,
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt
index 8ff17e0..965ee86 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt
@@ -78,6 +78,8 @@
isLockedAuthEntry: Boolean = false,
enforceOneLine: Boolean = false,
onTextLayout: (TextLayoutResult) -> Unit = {},
+ /** Get flow only, if present, where be drawn as a line above the headline. */
+ affiliatedDomainText: String? = null,
) {
val iconPadding = Modifier.wrapContentSize().padding(
// Horizontal padding should be 16dp, but the suggestion chip itself
@@ -102,6 +104,13 @@
) {
// Apply weight so that the trailing icon can always show.
Column(modifier = Modifier.wrapContentHeight().fillMaxWidth().weight(1f)) {
+ if (!affiliatedDomainText.isNullOrBlank()) {
+ BodySmallText(
+ text = affiliatedDomainText,
+ enforceOneLine = enforceOneLine,
+ onTextLayout = onTextLayout,
+ )
+ }
SmallTitleText(
text = entryHeadlineText,
enforceOneLine = enforceOneLine,
@@ -143,14 +152,14 @@
},
)
}
- } else if (entrySecondLineText != null) {
+ } else if (!entrySecondLineText.isNullOrBlank()) {
BodySmallText(
text = entrySecondLineText,
enforceOneLine = enforceOneLine,
onTextLayout = onTextLayout,
)
}
- if (entryThirdLineText != null) {
+ if (!entryThirdLineText.isNullOrBlank()) {
BodySmallText(
text = entryThirdLineText,
enforceOneLine = enforceOneLine,
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
index 1fef522..bc0ea02 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
@@ -376,6 +376,7 @@
}
internal const val MAX_ENTRY_FOR_PRIMARY_PAGE = 4
+
/** Draws the primary credential selection page, used starting from android V. */
@Composable
fun PrimarySelectionCardVImpl(
@@ -785,16 +786,16 @@
else if (credentialEntryInfo.icon == null) painterResource(R.drawable.ic_other_sign_in_24)
else null,
entryHeadlineText = username,
- entrySecondLineText =
+ entrySecondLineText = displayName,
+ entryThirdLineText =
(if (hasSingleEntry != null && hasSingleEntry)
if (credentialEntryInfo.credentialType == CredentialType.PASSKEY ||
credentialEntryInfo.credentialType == CredentialType.PASSWORD)
- listOf(displayName)
+ emptyList()
// Still show the type display name for all non-password/passkey types since it won't be
// mentioned in the bottom sheet heading.
- else listOf(displayName, credentialEntryInfo.credentialTypeDisplayName)
+ else listOf(credentialEntryInfo.credentialTypeDisplayName)
else listOf(
- displayName,
credentialEntryInfo.credentialTypeDisplayName,
credentialEntryInfo.providerDisplayName
)).filterNot(TextUtils::isEmpty).let { itemsToDisplay ->
@@ -805,6 +806,7 @@
},
enforceOneLine = enforceOneLine,
onTextLayout = onTextLayout,
+ affiliatedDomainText = credentialEntryInfo.affiliatedDomain,
)
}
diff --git a/packages/CredentialManager/tests/robotests/screenshot/src/com/android/credentialmanager/GetCredScreenshotTest.kt b/packages/CredentialManager/tests/robotests/screenshot/src/com/android/credentialmanager/GetCredScreenshotTest.kt
index 0820d26..28d83ee 100644
--- a/packages/CredentialManager/tests/robotests/screenshot/src/com/android/credentialmanager/GetCredScreenshotTest.kt
+++ b/packages/CredentialManager/tests/robotests/screenshot/src/com/android/credentialmanager/GetCredScreenshotTest.kt
@@ -151,6 +151,7 @@
entryGroupId = "username",
isDefaultIconPreferredAsSingleProvider = false,
rawCredentialType = "unknown-type",
+ affiliatedDomain = null,
)
),
authenticationEntryList = emptyList(),
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java
index 634e067..cf2f85e 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java
@@ -20,7 +20,6 @@
import static com.android.packageinstaller.PackageInstallerActivity.EXTRA_STAGED_SESSION_ID;
-import android.Manifest;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
@@ -28,10 +27,10 @@
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
-import android.content.pm.Flags;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
import android.content.res.AssetFileDescriptor;
+import android.Manifest;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
@@ -201,7 +200,7 @@
params.setPermissionState(Manifest.permission.USE_FULL_SCREEN_INTENT,
PackageInstaller.SessionParams.PERMISSION_STATE_DENIED);
- if (pfd != null && Flags.readInstallInfo()) {
+ if (pfd != null) {
try {
final PackageInstaller.InstallInfo result = installer.readInstallInfo(pfd,
debugPathName, 0);
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
index e95a8e6..45bfe54 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
@@ -31,7 +31,6 @@
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
-import android.content.pm.Flags;
import android.content.pm.InstallSourceInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageInstaller;
@@ -400,10 +399,7 @@
final int sessionId = intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID,
-1 /* defaultValue */);
final SessionInfo info = mInstaller.getSessionInfo(sessionId);
- String resolvedPath = null;
- if (info != null && Flags.getResolvedApkPath()) {
- resolvedPath = info.getResolvedBaseApkPath();
- }
+ String resolvedPath = info != null ? info.getResolvedBaseApkPath() : null;
if (info == null || !info.isSealed() || resolvedPath == null) {
Log.w(TAG, "Session " + sessionId + " in funky state; ignoring");
finish();
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt
index 22caabd..aeabbd5 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt
@@ -25,7 +25,6 @@
import android.content.Context
import android.content.Intent
import android.content.pm.ApplicationInfo
-import android.content.pm.Flags
import android.content.pm.PackageInfo
import android.content.pm.PackageInstaller
import android.content.pm.PackageInstaller.SessionInfo
@@ -363,7 +362,7 @@
params.setPermissionState(
Manifest.permission.USE_FULL_SCREEN_INTENT, SessionParams.PERMISSION_STATE_DENIED
)
- if (pfd != null && Flags.readInstallInfo()) {
+ if (pfd != null) {
try {
val installInfo = packageInstaller.readInstallInfo(pfd, debugPathName, 0)
params.setAppPackageName(installInfo.packageName)
@@ -426,8 +425,7 @@
if (PackageInstaller.ACTION_CONFIRM_INSTALL == intent.action) {
val info = packageInstaller.getSessionInfo(sessionId)
- val resolvedPath =
- if (Flags.getResolvedApkPath()) info?.resolvedBaseApkPath else null
+ val resolvedPath = info?.resolvedBaseApkPath
if (info == null || !info.isSealed || resolvedPath == null) {
Log.w(LOG_TAG, "Session $sessionId in funky state; ignoring")
return InstallAborted(ABORT_REASON_INTERNAL_ERROR)
diff --git a/packages/SettingsLib/DataStore/tests/Android.bp b/packages/SettingsLib/DataStore/tests/Android.bp
new file mode 100644
index 0000000..8770dfa
--- /dev/null
+++ b/packages/SettingsLib/DataStore/tests/Android.bp
@@ -0,0 +1,24 @@
+package {
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_app {
+ name: "SettingsLibDataStoreShell",
+ platform_apis: true,
+}
+
+android_robolectric_test {
+ name: "SettingsLibDataStoreTest",
+ srcs: ["src/**/*"],
+ static_libs: [
+ "SettingsLibDataStore",
+ "androidx.test.ext.junit",
+ "guava",
+ "mockito-robolectric-prebuilt", // mockito deps order matters!
+ "mockito-kotlin2",
+ ],
+ java_resource_dirs: ["config"],
+ instrumentation_for: "SettingsLibDataStoreShell",
+ coverage_libs: ["SettingsLibDataStore"],
+ upstream: true,
+}
diff --git a/packages/SettingsLib/DataStore/tests/AndroidManifest.xml b/packages/SettingsLib/DataStore/tests/AndroidManifest.xml
new file mode 100644
index 0000000..ffc24e4
--- /dev/null
+++ b/packages/SettingsLib/DataStore/tests/AndroidManifest.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.settingslib.datastore.test">
+
+ <application android:debuggable="true" />
+</manifest>
diff --git a/packages/SettingsLib/DataStore/tests/config/robolectric.properties b/packages/SettingsLib/DataStore/tests/config/robolectric.properties
new file mode 100644
index 0000000..fab7251
--- /dev/null
+++ b/packages/SettingsLib/DataStore/tests/config/robolectric.properties
@@ -0,0 +1 @@
+sdk=NEWEST_SDK
diff --git a/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/ObserverTest.kt b/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/ObserverTest.kt
new file mode 100644
index 0000000..bb791dc
--- /dev/null
+++ b/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/ObserverTest.kt
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2024 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.datastore
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import com.google.common.util.concurrent.MoreExecutors
+import java.util.concurrent.Executor
+import java.util.concurrent.atomic.AtomicInteger
+import org.junit.Assert.assertThrows
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+import org.mockito.kotlin.any
+import org.mockito.kotlin.never
+import org.mockito.kotlin.reset
+import org.mockito.kotlin.verify
+
+@RunWith(AndroidJUnit4::class)
+class ObserverTest {
+ @get:Rule val mockitoRule: MockitoRule = MockitoJUnit.rule()
+
+ @Mock private lateinit var observer1: Observer
+
+ @Mock private lateinit var observer2: Observer
+
+ @Mock private lateinit var executor: Executor
+
+ private val observable = DataObservable()
+
+ @Test
+ fun addObserver_sameExecutor() {
+ observable.addObserver(observer1, executor)
+ observable.addObserver(observer1, executor)
+ }
+
+ @Test
+ fun addObserver_differentExecutor() {
+ observable.addObserver(observer1, executor)
+ assertThrows(IllegalStateException::class.java) {
+ observable.addObserver(observer1, MoreExecutors.directExecutor())
+ }
+ }
+
+ @Test
+ fun addObserver_weaklyReferenced() {
+ val counter = AtomicInteger()
+ var observer: Observer? = Observer { counter.incrementAndGet() }
+ observable.addObserver(observer!!, MoreExecutors.directExecutor())
+
+ observable.notifyChange(ChangeReason.UPDATE)
+ assertThat(counter.get()).isEqualTo(1)
+
+ // trigger GC, the observer callback should not be invoked
+ @Suppress("unused")
+ observer = null
+ System.gc()
+ System.runFinalization()
+
+ observable.notifyChange(ChangeReason.UPDATE)
+ assertThat(counter.get()).isEqualTo(1)
+ }
+
+ @Test
+ fun addObserver_notifyObservers_removeObserver() {
+ observable.addObserver(observer1, MoreExecutors.directExecutor())
+ observable.addObserver(observer2, executor)
+
+ observable.notifyChange(ChangeReason.DELETE)
+
+ verify(observer1).onChanged(ChangeReason.DELETE)
+ verify(observer2, never()).onChanged(any())
+ verify(executor).execute(any())
+
+ reset(observer1, executor)
+ observable.removeObserver(observer2)
+
+ observable.notifyChange(ChangeReason.UPDATE)
+ verify(observer1).onChanged(ChangeReason.UPDATE)
+ verify(executor, never()).execute(any())
+ }
+
+ @Test
+ fun notifyChange_addObserverWithinCallback() {
+ // ConcurrentModificationException is raised if it is not implemented correctly
+ observable.addObserver(
+ { observable.addObserver(observer1, executor) },
+ MoreExecutors.directExecutor()
+ )
+ observable.notifyChange(ChangeReason.UPDATE)
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
index bdb5871..5f026c4 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
@@ -74,6 +74,7 @@
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArrayList;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -84,6 +85,7 @@
private static final String TAG = "InfoMediaManager";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+ protected final List<MediaDevice> mMediaDevices = new CopyOnWriteArrayList<>();
/** Checked exception that signals the specified package is not present in the system. */
public static class PackageNotAvailableException extends Exception {
@@ -227,6 +229,16 @@
Api34Impl.onRouteListingPreferenceUpdated(routeListingPreference, mPreferenceItemMap);
}
+ protected final MediaDevice findMediaDevice(@NonNull String id) {
+ for (MediaDevice mediaDevice : mMediaDevices) {
+ if (mediaDevice.getId().equals(id)) {
+ return mediaDevice;
+ }
+ }
+ Log.e(TAG, "findMediaDevice() can't find device with id: " + id);
+ return null;
+ }
+
/**
* Get current device that played media.
* @return MediaDevice
@@ -433,7 +445,7 @@
protected final synchronized void refreshDevices() {
rebuildDeviceList();
- dispatchDeviceListAdded();
+ dispatchDeviceListAdded(mMediaDevices);
}
// MediaRoute2Info.getType was made public on API 34, but exists since API 30.
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/MediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/MediaManager.java
index 8bebd6e..d562c8a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/MediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/MediaManager.java
@@ -15,9 +15,9 @@
*/
package com.android.settingslib.media;
+import android.annotation.NonNull;
import android.app.Notification;
import android.content.Context;
-import android.util.Log;
import java.util.ArrayList;
import java.util.Collection;
@@ -29,10 +29,7 @@
*/
public abstract class MediaManager {
- private static final String TAG = "MediaManager";
-
protected final Collection<MediaDeviceCallback> mCallbacks = new CopyOnWriteArrayList<>();
- protected final List<MediaDevice> mMediaDevices = new CopyOnWriteArrayList<>();
protected Context mContext;
protected Notification mNotification;
@@ -54,19 +51,9 @@
}
}
- protected MediaDevice findMediaDevice(String id) {
- for (MediaDevice mediaDevice : mMediaDevices) {
- if (mediaDevice.getId().equals(id)) {
- return mediaDevice;
- }
- }
- Log.e(TAG, "findMediaDevice() can't found device");
- return null;
- }
-
- protected void dispatchDeviceListAdded() {
+ protected void dispatchDeviceListAdded(@NonNull List<MediaDevice> devices) {
for (MediaDeviceCallback callback : getCallbacks()) {
- callback.onDeviceListAdded(new ArrayList<>(mMediaDevices));
+ callback.onDeviceListAdded(new ArrayList<>(devices));
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.kt b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.kt
index 65b73ca..0117ece 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.kt
@@ -46,7 +46,7 @@
/**
* Wrapper the [.getInternetIconResource] for testing compatibility.
*/
- class InternetIconInjector(protected val context: Context) {
+ open class InternetIconInjector(protected val context: Context) {
/**
* Returns the Internet icon for a given RSSI level.
*
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaManagerTest.java
index 46e724d..c3237f0 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaManagerTest.java
@@ -16,7 +16,6 @@
package com.android.settingslib.media;
-import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.verify;
@@ -32,6 +31,8 @@
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
+import java.util.Collections;
+
@RunWith(RobolectricTestRunner.class)
public class MediaManagerTest {
@@ -59,7 +60,7 @@
public void dispatchDeviceListAdded_registerCallback_shouldDispatchCallback() {
mMediaManager.registerCallback(mCallback);
- mMediaManager.dispatchDeviceListAdded();
+ mMediaManager.dispatchDeviceListAdded(Collections.emptyList());
verify(mCallback).onDeviceListAdded(any());
}
@@ -68,9 +69,9 @@
public void dispatchDeviceListRemoved_registerCallback_shouldDispatchCallback() {
mMediaManager.registerCallback(mCallback);
- mMediaManager.dispatchDeviceListRemoved(mMediaManager.mMediaDevices);
+ mMediaManager.dispatchDeviceListRemoved(Collections.emptyList());
- verify(mCallback).onDeviceListRemoved(mMediaManager.mMediaDevices);
+ verify(mCallback).onDeviceListRemoved(Collections.emptyList());
}
@Test
@@ -83,24 +84,6 @@
}
@Test
- public void findMediaDevice_idExist_shouldReturnMediaDevice() {
- mMediaManager.mMediaDevices.add(mDevice);
-
- final MediaDevice device = mMediaManager.findMediaDevice(TEST_ID);
-
- assertThat(device.getId()).isEqualTo(mDevice.getId());
- }
-
- @Test
- public void findMediaDevice_idNotExist_shouldReturnNull() {
- mMediaManager.mMediaDevices.add(mDevice);
-
- final MediaDevice device = mMediaManager.findMediaDevice("123");
-
- assertThat(device).isNull();
- }
-
- @Test
public void dispatchOnRequestFailed_registerCallback_shouldDispatchCallback() {
mMediaManager.registerCallback(mCallback);
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 2fa1c6e..a490b6f 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -154,6 +154,7 @@
Settings.Secure.MANUAL_RINGER_TOGGLE_COUNT,
Settings.Secure.LOW_POWER_WARNING_ACKNOWLEDGED,
Settings.Secure.EXTRA_LOW_POWER_WARNING_ACKNOWLEDGED,
+ Settings.Secure.EMERGENCY_THERMAL_ALERT_DISABLED,
Settings.Secure.HUSH_GESTURE_USED,
Settings.Secure.IN_CALL_NOTIFICATION_ENABLED,
Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS,
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index 2535fdb..4cdf98cb 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -235,6 +235,7 @@
VALIDATORS.put(Secure.MANUAL_RINGER_TOGGLE_COUNT, NON_NEGATIVE_INTEGER_VALIDATOR);
VALIDATORS.put(Secure.LOW_POWER_WARNING_ACKNOWLEDGED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.EXTRA_LOW_POWER_WARNING_ACKNOWLEDGED, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(Secure.EMERGENCY_THERMAL_ALERT_DISABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.IN_CALL_NOTIFICATION_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, BOOLEAN_VALIDATOR);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java b/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java
index 6ff36d4..ad3eb92 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java
@@ -20,8 +20,6 @@
import static android.provider.Settings.Config.SYNC_DISABLED_MODE_PERSISTENT;
import static android.provider.Settings.Config.SYNC_DISABLED_MODE_UNTIL_REBOOT;
-import static com.android.providers.settings.Flags.supportOverrides;
-
import android.aconfig.Aconfig.parsed_flag;
import android.aconfig.Aconfig.parsed_flags;
import android.annotation.SuppressLint;
@@ -269,9 +267,9 @@
verb = CommandVerb.GET;
} else if ("put".equalsIgnoreCase(cmd)) {
verb = CommandVerb.PUT;
- } else if (supportOverrides() && "override".equalsIgnoreCase(cmd)) {
+ } else if ("override".equalsIgnoreCase(cmd)) {
verb = CommandVerb.OVERRIDE;
- } else if (supportOverrides() && "clear_override".equalsIgnoreCase(cmd)) {
+ } else if ("clear_override".equalsIgnoreCase(cmd)) {
verb = CommandVerb.CLEAR_OVERRIDE;
} else if ("delete".equalsIgnoreCase(cmd)) {
verb = CommandVerb.DELETE;
@@ -285,7 +283,7 @@
if (peekNextArg() == null) {
isValid = true;
}
- } else if (supportOverrides() && "list_local_overrides".equalsIgnoreCase(cmd)) {
+ } else if ("list_local_overrides".equalsIgnoreCase(cmd)) {
verb = CommandVerb.LIST_LOCAL_OVERRIDES;
if (peekNextArg() == null) {
isValid = true;
@@ -427,14 +425,10 @@
DeviceConfig.setProperty(namespace, key, value, makeDefault);
break;
case OVERRIDE:
- if (supportOverrides()) {
- DeviceConfig.setLocalOverride(namespace, key, value);
- }
+ DeviceConfig.setLocalOverride(namespace, key, value);
break;
case CLEAR_OVERRIDE:
- if (supportOverrides()) {
- DeviceConfig.clearLocalOverride(namespace, key);
- }
+ DeviceConfig.clearLocalOverride(namespace, key);
break;
case DELETE:
pout.println(delete(iprovider, namespace, key)
@@ -452,19 +446,15 @@
}
} else {
for (String line : listAll(iprovider)) {
- if (supportOverrides()) {
- boolean isPrivate = false;
- for (String privateNamespace : PRIVATE_NAMESPACES) {
- if (line.startsWith(privateNamespace)) {
- isPrivate = true;
- break;
- }
+ boolean isPrivate = false;
+ for (String privateNamespace : PRIVATE_NAMESPACES) {
+ if (line.startsWith(privateNamespace)) {
+ isPrivate = true;
+ break;
}
+ }
- if (!isPrivate) {
- pout.println(line);
- }
- } else {
+ if (!isPrivate) {
pout.println(line);
}
}
@@ -495,18 +485,16 @@
}
break;
case LIST_LOCAL_OVERRIDES:
- if (supportOverrides()) {
- Map<String, Map<String, String>> underlyingValues =
- DeviceConfig.getUnderlyingValuesForOverriddenFlags();
- for (String overrideNamespace : underlyingValues.keySet()) {
- Map<String, String> flagToValue =
- underlyingValues.get(overrideNamespace);
- for (String flag : flagToValue.keySet()) {
- String flagText = overrideNamespace + "/" + flag;
- String valueText =
- DeviceConfig.getProperty(overrideNamespace, flag);
- pout.println(flagText + "=" + valueText);
- }
+ Map<String, Map<String, String>> underlyingValues =
+ DeviceConfig.getUnderlyingValuesForOverriddenFlags();
+ for (String overrideNamespace : underlyingValues.keySet()) {
+ Map<String, String> flagToValue =
+ underlyingValues.get(overrideNamespace);
+ for (String flag : flagToValue.keySet()) {
+ String flagText = overrideNamespace + "/" + flag;
+ String valueText =
+ DeviceConfig.getProperty(overrideNamespace, flag);
+ pout.println(flagText + "=" + valueText);
}
}
break;
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/AccessibilityMenuService.java b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/AccessibilityMenuService.java
index 085fc29..88181e7 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/AccessibilityMenuService.java
+++ b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/AccessibilityMenuService.java
@@ -56,10 +56,12 @@
implements View.OnTouchListener {
public static final String PACKAGE_NAME = AccessibilityMenuService.class.getPackageName();
+ public static final String PACKAGE_TESTS = ".tests";
public static final String INTENT_TOGGLE_MENU = ".toggle_menu";
public static final String INTENT_HIDE_MENU = ".hide_menu";
public static final String INTENT_GLOBAL_ACTION = ".global_action";
public static final String INTENT_GLOBAL_ACTION_EXTRA = "GLOBAL_ACTION";
+ public static final String INTENT_OPEN_BLOCKED = "OPEN_BLOCKED";
private static final String TAG = "A11yMenuService";
private static final long BUFFER_MILLISECONDS_TO_PREVENT_UPDATE_FAILURE = 100L;
@@ -192,7 +194,7 @@
IntentFilter hideMenuFilter = new IntentFilter();
hideMenuFilter.addAction(Intent.ACTION_SCREEN_OFF);
- hideMenuFilter.addAction(PACKAGE_NAME + INTENT_HIDE_MENU);
+ hideMenuFilter.addAction(INTENT_HIDE_MENU);
// Including WRITE_SECURE_SETTINGS enforces that we only listen to apps
// with the restricted WRITE_SECURE_SETTINGS permission who broadcast this intent.
@@ -200,7 +202,7 @@
Manifest.permission.WRITE_SECURE_SETTINGS, null,
Context.RECEIVER_EXPORTED);
registerReceiver(mToggleMenuReceiver,
- new IntentFilter(PACKAGE_NAME + INTENT_TOGGLE_MENU),
+ new IntentFilter(INTENT_TOGGLE_MENU),
Manifest.permission.WRITE_SECURE_SETTINGS, null,
Context.RECEIVER_EXPORTED);
@@ -245,8 +247,9 @@
* @return {@code true} if successful, {@code false} otherwise.
*/
private boolean performGlobalActionInternal(int globalAction) {
- Intent intent = new Intent(PACKAGE_NAME + INTENT_GLOBAL_ACTION);
+ Intent intent = new Intent(INTENT_GLOBAL_ACTION);
intent.putExtra(INTENT_GLOBAL_ACTION_EXTRA, globalAction);
+ intent.setPackage(PACKAGE_NAME + PACKAGE_TESTS);
sendBroadcast(intent);
Log.i("A11yMenuService", "Broadcasting global action " + globalAction);
return performGlobalAction(globalAction);
@@ -410,9 +413,16 @@
private void toggleVisibility() {
boolean locked = mKeyguardManager != null && mKeyguardManager.isKeyguardLocked();
- if (!locked && SystemClock.uptimeMillis() - mLastTimeTouchedOutside
- > BUTTON_CLICK_TIMEOUT) {
- mA11yMenuLayout.toggleVisibility();
+ if (!locked) {
+ if (SystemClock.uptimeMillis() - mLastTimeTouchedOutside
+ > BUTTON_CLICK_TIMEOUT) {
+ mA11yMenuLayout.toggleVisibility();
+ }
+ } else {
+ // Broadcast for testing.
+ Intent intent = new Intent(INTENT_OPEN_BLOCKED);
+ intent.setPackage(PACKAGE_NAME + PACKAGE_TESTS);
+ sendBroadcast(intent);
}
}
}
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java b/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java
index 72c1092..6546b87 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java
+++ b/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java
@@ -23,6 +23,7 @@
import static android.accessibilityservice.AccessibilityService.GLOBAL_ACTION_RECENTS;
import static android.accessibilityservice.AccessibilityService.GLOBAL_ACTION_TAKE_SCREENSHOT;
+import static com.android.systemui.accessibility.accessibilitymenu.AccessibilityMenuService.INTENT_OPEN_BLOCKED;
import static com.android.systemui.accessibility.accessibilitymenu.AccessibilityMenuService.INTENT_GLOBAL_ACTION;
import static com.android.systemui.accessibility.accessibilitymenu.AccessibilityMenuService.INTENT_GLOBAL_ACTION_EXTRA;
import static com.android.systemui.accessibility.accessibilitymenu.AccessibilityMenuService.INTENT_HIDE_MENU;
@@ -65,6 +66,7 @@
import org.junit.runner.RunWith;
import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
@RunWith(AndroidJUnit4.class)
@@ -75,12 +77,11 @@
private static final int TIMEOUT_SERVICE_STATUS_CHANGE_S = 5;
private static final int TIMEOUT_UI_CHANGE_S = 5;
private static final int NO_GLOBAL_ACTION = -1;
- private static final String INPUT_KEYEVENT_KEYCODE_BACK = "input keyevent KEYCODE_BACK";
- private static final String TEST_PIN = "1234";
private static Instrumentation sInstrumentation;
private static UiAutomation sUiAutomation;
- private static AtomicInteger sLastGlobalAction;
+ private static final AtomicInteger sLastGlobalAction = new AtomicInteger(NO_GLOBAL_ACTION);
+ private static final AtomicBoolean sOpenBlocked = new AtomicBoolean(false);
private static AccessibilityManager sAccessibilityManager;
private static PowerManager sPowerManager;
@@ -122,8 +123,6 @@
() -> sAccessibilityManager.getEnabledAccessibilityServiceList(
AccessibilityServiceInfo.FEEDBACK_ALL_MASK).stream().filter(
info -> info.getId().contains(serviceName)).count() == 1);
-
- sLastGlobalAction = new AtomicInteger(NO_GLOBAL_ACTION);
context.registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -131,20 +130,28 @@
sLastGlobalAction.set(
intent.getIntExtra(INTENT_GLOBAL_ACTION_EXTRA, NO_GLOBAL_ACTION));
}},
- new IntentFilter(PACKAGE_NAME + INTENT_GLOBAL_ACTION),
+ new IntentFilter(INTENT_GLOBAL_ACTION),
+ null, null, Context.RECEIVER_EXPORTED);
+
+ context.registerReceiver(new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Log.i(TAG, "Received notification that menu cannot be opened.");
+ sOpenBlocked.set(true);
+ }},
+ new IntentFilter(INTENT_OPEN_BLOCKED),
null, null, Context.RECEIVER_EXPORTED);
}
@AfterClass
- public static void classTeardown() throws Throwable {
- clearPin();
+ public static void classTeardown() {
Settings.Secure.putString(sInstrumentation.getTargetContext().getContentResolver(),
Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, "");
}
@Before
public void setup() throws Throwable {
- clearPin();
+ sOpenBlocked.set(false);
wakeUpScreen();
sUiAutomation.executeShellCommand("input keyevent KEYCODE_MENU");
openMenu();
@@ -154,20 +161,8 @@
public void tearDown() throws Throwable {
closeMenu();
sLastGlobalAction.set(NO_GLOBAL_ACTION);
- }
-
- private static void clearPin() throws Throwable {
- sUiAutomation.executeShellCommand("locksettings clear --old " + TEST_PIN);
- TestUtils.waitUntil("Device did not register as unlocked & insecure.",
- TIMEOUT_SERVICE_STATUS_CHANGE_S,
- () -> !sKeyguardManager.isDeviceSecure());
- }
-
- private static void setPin() throws Throwable {
- sUiAutomation.executeShellCommand("locksettings set-pin " + TEST_PIN);
- TestUtils.waitUntil("Device did not recognize as locked & secure.",
- TIMEOUT_SERVICE_STATUS_CHANGE_S,
- () -> sKeyguardManager.isDeviceSecure());
+ // dismisses screenshot popup if present.
+ sUiAutomation.executeShellCommand("input keyevent KEYCODE_BACK");
}
private static boolean isMenuVisible() {
@@ -184,7 +179,6 @@
private static void closeScreen() throws Throwable {
Display display = sDisplayManager.getDisplay(Display.DEFAULT_DISPLAY);
- setPin();
sUiAutomation.performGlobalAction(GLOBAL_ACTION_LOCK_SCREEN);
TestUtils.waitUntil("Screen did not close.",
TIMEOUT_UI_CHANGE_S,
@@ -194,12 +188,20 @@
}
private static void openMenu() throws Throwable {
- Intent intent = new Intent(PACKAGE_NAME + INTENT_TOGGLE_MENU);
+ openMenu(false);
+ }
+
+ private static void openMenu(boolean abandonOnBlock) throws Throwable {
+ Intent intent = new Intent(INTENT_TOGGLE_MENU);
+ intent.setPackage(PACKAGE_NAME);
sInstrumentation.getContext().sendBroadcast(intent);
TestUtils.waitUntil("Timed out before menu could appear.",
TIMEOUT_UI_CHANGE_S,
() -> {
+ if (sOpenBlocked.get() && abandonOnBlock) {
+ throw new IllegalStateException();
+ }
if (isMenuVisible()) {
return true;
} else {
@@ -213,7 +215,8 @@
if (!isMenuVisible()) {
return;
}
- Intent intent = new Intent(PACKAGE_NAME + INTENT_HIDE_MENU);
+ Intent intent = new Intent(INTENT_HIDE_MENU);
+ intent.setPackage(PACKAGE_NAME);
sInstrumentation.getContext().sendBroadcast(intent);
TestUtils.waitUntil("Timed out before menu could close.",
TIMEOUT_UI_CHANGE_S, () -> !isMenuVisible());
@@ -444,13 +447,13 @@
closeScreen();
wakeUpScreen();
- boolean timedOut = false;
+ boolean blocked = false;
try {
- openMenu();
- } catch (AssertionError e) {
+ openMenu(true);
+ } catch (IllegalStateException e) {
// Expected
- timedOut = true;
+ blocked = true;
}
- assertThat(timedOut).isTrue();
+ assertThat(blocked).isTrue();
}
}
diff --git a/packages/SystemUI/aconfig/predictive_back.aconfig b/packages/SystemUI/aconfig/predictive_back.aconfig
index d0e6b28..7bbe82c 100644
--- a/packages/SystemUI/aconfig/predictive_back.aconfig
+++ b/packages/SystemUI/aconfig/predictive_back.aconfig
@@ -4,26 +4,26 @@
name: "predictive_back_sysui"
namespace: "systemui"
description: "Predictive Back Dispatching for SysUI"
- bug: "309545085"
+ bug: "327737297"
}
flag {
name: "predictive_back_animate_shade"
namespace: "systemui"
description: "Enable Shade Animations"
- bug: "309545085"
+ bug: "327732946"
}
flag {
name: "predictive_back_animate_bouncer"
namespace: "systemui"
description: "Enable Predictive Back Animation in Bouncer"
- bug: "309545085"
+ bug: "327733487"
}
flag {
name: "predictive_back_animate_dialogs"
namespace: "systemui"
description: "Enable Predictive Back Animation for SysUI dialogs"
- bug: "309545085"
+ bug: "327721544"
}
\ No newline at end of file
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index 078da1c86..515c816 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -31,8 +31,8 @@
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
@@ -93,6 +93,7 @@
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.dimensionResource
+import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.semantics.testTagsAsResourceId
@@ -118,6 +119,7 @@
import com.android.systemui.communal.ui.compose.extensions.observeTapsWithoutConsuming
import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel
+import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.communal.widgets.WidgetConfigurator
import com.android.systemui.res.R
import kotlinx.coroutines.launch
@@ -197,26 +199,37 @@
}
},
) {
- CommunalHubLazyGrid(
- communalContent = communalContent,
- viewModel = viewModel,
- contentPadding = contentPadding,
- contentOffset = contentOffset,
- setGridCoordinates = { gridCoordinates = it },
- updateDragPositionForRemove = { offset ->
- isDraggingToRemove =
- isPointerWithinCoordinates(
- offset = gridCoordinates?.let { it.positionInWindow() + offset },
- containerToCheck = removeButtonCoordinates
- )
- isDraggingToRemove
- },
- onOpenWidgetPicker = onOpenWidgetPicker,
- gridState = gridState,
- contentListState = contentListState,
- selectedKey = selectedKey,
- widgetConfigurator = widgetConfigurator,
- )
+ Column(Modifier.align(Alignment.TopStart)) {
+ CommunalHubLazyGrid(
+ communalContent = communalContent,
+ viewModel = viewModel,
+ contentPadding = contentPadding,
+ contentOffset = contentOffset,
+ setGridCoordinates = { gridCoordinates = it },
+ updateDragPositionForRemove = { offset ->
+ isDraggingToRemove =
+ isPointerWithinCoordinates(
+ offset = gridCoordinates?.let { it.positionInWindow() + offset },
+ containerToCheck = removeButtonCoordinates
+ )
+ isDraggingToRemove
+ },
+ onOpenWidgetPicker = onOpenWidgetPicker,
+ gridState = gridState,
+ contentListState = contentListState,
+ selectedKey = selectedKey,
+ widgetConfigurator = widgetConfigurator,
+ )
+ // TODO(b/326060686): Remove this once keyguard indication area can persist over hub
+ if (viewModel is CommunalViewModel) {
+ val isUnlocked by viewModel.deviceUnlocked.collectAsState(initial = false)
+ Spacer(Modifier.height(24.dp))
+ LockStateIcon(
+ isUnlocked = isUnlocked,
+ modifier = Modifier.align(Alignment.CenterHorizontally),
+ )
+ }
+ }
if (viewModel.isEditMode && onOpenWidgetPicker != null && onEditDone != null) {
Toolbar(
@@ -268,7 +281,7 @@
@OptIn(ExperimentalFoundationApi::class)
@Composable
-private fun BoxScope.CommunalHubLazyGrid(
+private fun ColumnScope.CommunalHubLazyGrid(
communalContent: List<CommunalContentModel>,
viewModel: BaseCommunalViewModel,
contentPadding: PaddingValues,
@@ -282,7 +295,7 @@
widgetConfigurator: WidgetConfigurator?,
) {
var gridModifier =
- Modifier.align(Alignment.TopStart).onGloballyPositioned { setGridCoordinates(it) }
+ Modifier.align(Alignment.Start).onGloballyPositioned { setGridCoordinates(it) }
var list = communalContent
var dragDropState: GridDragDropState? = null
if (viewModel.isEditMode && viewModel is CommunalEditModeViewModel) {
@@ -364,6 +377,26 @@
}
}
+@Composable
+private fun LockStateIcon(
+ isUnlocked: Boolean,
+ modifier: Modifier = Modifier,
+) {
+ val colors = LocalAndroidColorScheme.current
+ val resource =
+ if (isUnlocked) {
+ R.drawable.ic_unlocked
+ } else {
+ R.drawable.ic_lock
+ }
+ Icon(
+ painter = painterResource(id = resource),
+ contentDescription = null,
+ tint = colors.onPrimaryContainer,
+ modifier = modifier.size(52.dp)
+ )
+}
+
/**
* Toolbar that contains action buttons to
* 1) open the widget picker
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/mediaoutput/ui/composable/MediaOutputComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/mediaoutput/ui/composable/MediaOutputComponent.kt
index b3fcc30..53de5bc 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/mediaoutput/ui/composable/MediaOutputComponent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/mediaoutput/ui/composable/MediaOutputComponent.kt
@@ -27,12 +27,14 @@
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically
import androidx.compose.animation.togetherWith
+import androidx.compose.foundation.background
import androidx.compose.foundation.basicMarquee
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
+import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
@@ -137,12 +139,14 @@
}
}
) { targetViewModel ->
- Expandable(
- modifier = Modifier.fillMaxSize(),
- color = targetViewModel.backgroundColor.toColor(),
- shape = RoundedCornerShape(12.dp),
- onClick = { viewModel.onDeviceClick(it) },
- ) {}
+ Spacer(
+ modifier =
+ Modifier.fillMaxSize()
+ .background(
+ color = targetViewModel.backgroundColor.toColor(),
+ shape = RoundedCornerShape(12.dp),
+ ),
+ )
}
transition.AnimatedContent(
contentKey = { it.icon },
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
index 37b135e..ce6445b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
@@ -23,7 +23,6 @@
import com.android.systemui.communal.domain.interactor.setCommunalAvailable
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.dock.DockManager
import com.android.systemui.dock.dockManager
import com.android.systemui.dock.fakeDockManager
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
@@ -40,6 +39,7 @@
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
+import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
@@ -92,6 +92,7 @@
}
}
+ @Ignore("Ignored until custom animations are implemented in b/322787129")
@Test
fun deviceDocked_forceCommunalScene() =
with(kosmos) {
@@ -110,6 +111,23 @@
}
@Test
+ fun exitingDream_forceCommunalScene() =
+ with(kosmos) {
+ testScope.runTest {
+ val scene by collectLastValue(communalInteractor.desiredScene)
+ assertThat(scene).isEqualTo(CommunalScenes.Blank)
+
+ updateDocked(true)
+ fakeKeyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.DREAMING,
+ to = KeyguardState.LOCKSCREEN,
+ testScope = this
+ )
+ assertThat(scene).isEqualTo(CommunalScenes.Communal)
+ }
+ }
+
+ @Test
fun deviceDocked_doesNotForceCommunalIfTransitioningFromCommunal() =
with(kosmos) {
testScope.runTest {
@@ -175,6 +193,7 @@
}
}
+ @Ignore("Ignored until custom animations are implemented in b/322787129")
@Test
fun dockingOnLockscreen_forcesCommunal() =
with(kosmos) {
@@ -196,6 +215,7 @@
}
}
+ @Ignore("Ignored until custom animations are implemented in b/322787129")
@Test
fun dockingOnLockscreen_doesNotForceCommunalIfDreamStarts() =
with(kosmos) {
@@ -230,7 +250,8 @@
with(kosmos) {
runCurrent()
fakeDockManager.setIsDocked(docked)
- fakeDockManager.setDockEvent(DockManager.STATE_DOCKED)
+ // TODO(b/322787129): uncomment once custom animations are in place
+ // fakeDockManager.setDockEvent(DockManager.STATE_DOCKED)
runCurrent()
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
index 563aad1..8f802b8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
@@ -39,6 +39,7 @@
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel.Companion.POPUP_AUTO_HIDE_TIMEOUT_MS
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
import com.android.systemui.flags.Flags.COMMUNAL_SERVICE_ENABLED
import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
@@ -113,6 +114,7 @@
kosmos.communalInteractor,
kosmos.communalTutorialInteractor,
kosmos.shadeInteractor,
+ kosmos.deviceEntryInteractor,
mediaHost,
logcatLogBuffer("CommunalViewModelTest"),
)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
index 19b80da..128b465 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
@@ -25,6 +25,7 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.AuthController
import com.android.systemui.biometrics.data.repository.FakeFacePropertyRepository
+import com.android.systemui.common.shared.model.Position
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.doze.DozeMachine
import com.android.systemui.doze.DozeTransitionCallback
@@ -151,6 +152,24 @@
}
@Test
+ fun clockPosition() =
+ testScope.runTest {
+ assertThat(underTest.clockPosition.value).isEqualTo(Position(0, 0))
+
+ underTest.setClockPosition(0, 1)
+ assertThat(underTest.clockPosition.value).isEqualTo(Position(0, 1))
+
+ underTest.setClockPosition(1, 9)
+ assertThat(underTest.clockPosition.value).isEqualTo(Position(1, 9))
+
+ underTest.setClockPosition(1, 0)
+ assertThat(underTest.clockPosition.value).isEqualTo(Position(1, 0))
+
+ underTest.setClockPosition(3, 1)
+ assertThat(underTest.clockPosition.value).isEqualTo(Position(3, 1))
+ }
+
+ @Test
fun dozeTimeTick() =
testScope.runTest {
val lastDozeTimeTick by collectLastValue(underTest.dozeTimeTick)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt
index 225b5b1..b0f59fe 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt
@@ -71,7 +71,7 @@
mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
MockitoAnnotations.initMocks(this)
- whenever(burnInInteractor.burnIn(anyInt(), anyInt())).thenReturn(burnInFlow)
+ whenever(burnInInteractor.keyguardBurnIn).thenReturn(burnInFlow)
kosmos.burnInInteractor = burnInInteractor
whenever(goneToAodTransitionViewModel.enterFromTopTranslationY(anyInt()))
.thenReturn(emptyFlow())
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModelTest.kt
deleted file mode 100644
index ce089b1..0000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModelTest.kt
+++ /dev/null
@@ -1,186 +0,0 @@
-/*
- * Copyright (C) 2023 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.ui.viewmodel
-
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.systemui.Flags as AConfigFlags
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
-import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.doze.util.BurnInHelperWrapper
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.domain.interactor.BurnInInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
-import com.android.systemui.keyguard.shared.model.BurnInModel
-import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
-import com.android.systemui.kosmos.testScope
-import com.android.systemui.testKosmos
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.mockito.whenever
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.test.runTest
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.ArgumentMatchers.anyInt
-import org.mockito.Mock
-import org.mockito.MockitoAnnotations
-
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class KeyguardIndicationAreaViewModelTest : SysuiTestCase() {
- private val kosmos = testKosmos()
- private val testScope = kosmos.testScope
-
- @Mock private lateinit var burnInHelperWrapper: BurnInHelperWrapper
- @Mock private lateinit var shortcutsCombinedViewModel: KeyguardQuickAffordancesCombinedViewModel
-
- @Mock private lateinit var burnInInteractor: BurnInInteractor
- private val burnInFlow = MutableStateFlow(BurnInModel())
-
- private lateinit var bottomAreaInteractor: KeyguardBottomAreaInteractor
- private lateinit var underTest: KeyguardIndicationAreaViewModel
- private lateinit var repository: FakeKeyguardRepository
-
- private val startButtonFlow =
- MutableStateFlow<KeyguardQuickAffordanceViewModel>(
- KeyguardQuickAffordanceViewModel(
- slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId()
- )
- )
- private val endButtonFlow =
- MutableStateFlow<KeyguardQuickAffordanceViewModel>(
- KeyguardQuickAffordanceViewModel(
- slotId = KeyguardQuickAffordancePosition.BOTTOM_END.toSlotId()
- )
- )
- private val alphaFlow = MutableStateFlow<Float>(1f)
-
- @Before
- fun setUp() {
- MockitoAnnotations.initMocks(this)
-
- mSetFlagsRule.disableFlags(AConfigFlags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR)
- mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-
- whenever(burnInHelperWrapper.burnInOffset(anyInt(), any()))
- .thenReturn(RETURNED_BURN_IN_OFFSET)
- whenever(burnInInteractor.burnIn(anyInt(), anyInt())).thenReturn(burnInFlow)
-
- val withDeps = KeyguardInteractorFactory.create()
- val keyguardInteractor = withDeps.keyguardInteractor
- repository = withDeps.repository
-
- val bottomAreaViewModel: KeyguardBottomAreaViewModel = mock()
- whenever(bottomAreaViewModel.startButton).thenReturn(startButtonFlow)
- whenever(bottomAreaViewModel.endButton).thenReturn(endButtonFlow)
- whenever(bottomAreaViewModel.alpha).thenReturn(alphaFlow)
- bottomAreaInteractor = KeyguardBottomAreaInteractor(repository = repository)
- underTest =
- KeyguardIndicationAreaViewModel(
- keyguardInteractor = keyguardInteractor,
- bottomAreaInteractor = bottomAreaInteractor,
- keyguardBottomAreaViewModel = bottomAreaViewModel,
- burnInHelperWrapper = burnInHelperWrapper,
- burnInInteractor = burnInInteractor,
- shortcutsCombinedViewModel = shortcutsCombinedViewModel,
- configurationInteractor = ConfigurationInteractor(FakeConfigurationRepository()),
- )
- }
-
- @Test
- fun alpha() =
- testScope.runTest {
- val value = collectLastValue(underTest.alpha)
-
- assertThat(value()).isEqualTo(1f)
- alphaFlow.value = 0.1f
- assertThat(value()).isEqualTo(0.1f)
- alphaFlow.value = 0.5f
- assertThat(value()).isEqualTo(0.5f)
- alphaFlow.value = 0.2f
- assertThat(value()).isEqualTo(0.2f)
- alphaFlow.value = 0f
- assertThat(value()).isEqualTo(0f)
- }
-
- @Test
- fun isIndicationAreaPadded() =
- testScope.runTest {
- repository.setKeyguardShowing(true)
- val value = collectLastValue(underTest.isIndicationAreaPadded)
-
- assertThat(value()).isFalse()
- startButtonFlow.value = startButtonFlow.value.copy(isVisible = true)
- assertThat(value()).isTrue()
- endButtonFlow.value = endButtonFlow.value.copy(isVisible = true)
- assertThat(value()).isTrue()
- startButtonFlow.value = startButtonFlow.value.copy(isVisible = false)
- assertThat(value()).isTrue()
- endButtonFlow.value = endButtonFlow.value.copy(isVisible = false)
- assertThat(value()).isFalse()
- }
-
- @Test
- fun indicationAreaTranslationX() =
- testScope.runTest {
- val value = collectLastValue(underTest.indicationAreaTranslationX)
-
- assertThat(value()).isEqualTo(0f)
- bottomAreaInteractor.setClockPosition(100, 100)
- assertThat(value()).isEqualTo(100f)
- bottomAreaInteractor.setClockPosition(200, 100)
- assertThat(value()).isEqualTo(200f)
- bottomAreaInteractor.setClockPosition(200, 200)
- assertThat(value()).isEqualTo(200f)
- bottomAreaInteractor.setClockPosition(300, 100)
- assertThat(value()).isEqualTo(300f)
- }
-
- @Test
- fun indicationAreaTranslationY() =
- testScope.runTest {
- val value =
- collectLastValue(underTest.indicationAreaTranslationY(DEFAULT_BURN_IN_OFFSET))
-
- // Negative 0 - apparently there's a difference in floating point arithmetic - FML
- assertThat(value()).isEqualTo(-0f)
- val expected1 = setDozeAmountAndCalculateExpectedTranslationY(0.1f)
- assertThat(value()).isEqualTo(expected1)
- val expected2 = setDozeAmountAndCalculateExpectedTranslationY(0.2f)
- assertThat(value()).isEqualTo(expected2)
- val expected3 = setDozeAmountAndCalculateExpectedTranslationY(0.5f)
- assertThat(value()).isEqualTo(expected3)
- val expected4 = setDozeAmountAndCalculateExpectedTranslationY(1f)
- assertThat(value()).isEqualTo(expected4)
- }
-
- private fun setDozeAmountAndCalculateExpectedTranslationY(dozeAmount: Float): Float {
- repository.setDozeAmount(dozeAmount)
- return dozeAmount * (RETURNED_BURN_IN_OFFSET - DEFAULT_BURN_IN_OFFSET)
- }
-
- companion object {
- private const val DEFAULT_BURN_IN_OFFSET = 5
- private const val RETURNED_BURN_IN_OFFSET = 3
- }
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModelTest.kt
index 243aab2..dcf635e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModelTest.kt
@@ -32,8 +32,8 @@
import com.android.systemui.volume.localMediaRepository
import com.android.systemui.volume.mediaController
import com.android.systemui.volume.mediaControllerRepository
+import com.android.systemui.volume.mediaOutputActionsInteractor
import com.android.systemui.volume.mediaOutputInteractor
-import com.android.systemui.volume.panel.mediaOutputActionsInteractor
import com.android.systemui.volume.panel.volumePanelViewModel
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
diff --git a/packages/SystemUI/res/layout/notif_half_shelf.xml b/packages/SystemUI/res/layout/notif_half_shelf.xml
index 68c8dd9..d8d2985 100644
--- a/packages/SystemUI/res/layout/notif_half_shelf.xml
+++ b/packages/SystemUI/res/layout/notif_half_shelf.xml
@@ -19,11 +19,11 @@
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
android:id="@+id/half_shelf_dialog"
android:orientation="vertical"
- android:layout_width="wrap_content"
+ android:layout_width="@dimen/large_dialog_width"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal|bottom"
- android:paddingStart="4dp"
- android:paddingEnd="4dp">
+ android:paddingLeft="@dimen/dialog_side_padding"
+ android:paddingRight="@dimen/dialog_side_padding">
<LinearLayout
android:id="@+id/half_shelf"
diff --git a/packages/SystemUI/res/layout/scene_window_root.xml b/packages/SystemUI/res/layout/scene_window_root.xml
index bb8de4c..0dcd15b 100644
--- a/packages/SystemUI/res/layout/scene_window_root.xml
+++ b/packages/SystemUI/res/layout/scene_window_root.xml
@@ -24,7 +24,7 @@
android:id="@+id/scene_window_root"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:fitsSystemWindows="false">
+ android:fitsSystemWindows="true">
<include layout="@layout/super_notification_shade"
android:layout_width="match_parent"
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index 035cfdc..71ae0d7 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -223,7 +223,6 @@
<item type="id" name="lock_icon" />
<item type="id" name="lock_icon_bg" />
<item type="id" name="burn_in_layer" />
- <item type="id" name="burn_in_layer_empty_view" />
<item type="id" name="communal_tutorial_indicator" />
<item type="id" name="nssl_placeholder_barrier_bottom" />
<item type="id" name="ambient_indication_container" />
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 617eadb..ce08ca3 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -1581,4 +1581,8 @@
<style name="Theme.PrivacyDialog" parent="@style/Theme.SystemUI.Dialog">
<item name="android:colorBackground">?androidprv:attr/materialColorSurfaceContainer</item>
</style>
+
+ <style name="Theme.SystemUI.Dialog.StickyKeys" parent="@style/Theme.SystemUI.Dialog">
+ <item name="android:colorBackground">@color/transparent</item>
+ </style>
</resources>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index c0ae4a1..9421f15 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -53,6 +53,9 @@
import com.android.systemui.animation.ViewHierarchyAnimator;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
+import com.android.systemui.keyguard.shared.model.TransitionState;
+import com.android.systemui.keyguard.shared.model.TransitionStep;
import com.android.systemui.plugins.clocks.ClockController;
import com.android.systemui.power.domain.interactor.PowerInteractor;
import com.android.systemui.power.shared.model.ScreenPowerState;
@@ -101,6 +104,7 @@
private final Rect mClipBounds = new Rect();
private final KeyguardInteractor mKeyguardInteractor;
private final PowerInteractor mPowerInteractor;
+ private final KeyguardTransitionInteractor mKeyguardTransitionInteractor;
private final DozeParameters mDozeParameters;
private View mStatusArea = null;
@@ -108,6 +112,7 @@
private Boolean mSplitShadeEnabled = false;
private Boolean mStatusViewCentered = true;
+ private boolean mGoneToAodTransitionRunning = false;
private DumpManager mDumpManager;
private final TransitionListenerAdapter mKeyguardStatusAlignmentTransitionListener =
@@ -176,6 +181,7 @@
KeyguardLogger logger,
InteractionJankMonitor interactionJankMonitor,
KeyguardInteractor keyguardInteractor,
+ KeyguardTransitionInteractor keyguardTransitionInteractor,
DumpManager dumpManager,
PowerInteractor powerInteractor) {
super(keyguardStatusView);
@@ -191,6 +197,7 @@
mDumpManager = dumpManager;
mKeyguardInteractor = keyguardInteractor;
mPowerInteractor = powerInteractor;
+ mKeyguardTransitionInteractor = keyguardTransitionInteractor;
}
@Override
@@ -225,7 +232,6 @@
mDumpManager.registerDumpable(getInstanceName(), this);
if (migrateClocksToBlueprint()) {
startCoroutines(EmptyCoroutineContext.INSTANCE);
- mView.setVisibility(View.GONE);
}
}
@@ -241,6 +247,15 @@
dozeTimeTick();
}
}, context);
+
+ collectFlow(mView, mKeyguardTransitionInteractor.getGoneToAodTransition(),
+ (TransitionStep step) -> {
+ if (step.getTransitionState() == TransitionState.RUNNING) {
+ mGoneToAodTransitionRunning = true;
+ } else {
+ mGoneToAodTransitionRunning = false;
+ }
+ }, context);
}
public KeyguardStatusView getView() {
@@ -311,7 +326,7 @@
* Set keyguard status view alpha.
*/
public void setAlpha(float alpha) {
- if (!mKeyguardVisibilityHelper.isVisibilityAnimating()) {
+ if (!mKeyguardVisibilityHelper.isVisibilityAnimating() && !mGoneToAodTransitionRunning) {
mView.setAlpha(alpha);
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
index 77054bd..2000028 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
@@ -88,10 +88,6 @@
boolean keyguardFadingAway,
boolean goingToFullShade,
int oldStatusBarState) {
- if (migrateClocksToBlueprint()) {
- log("Ignoring all of KeyguardVisibilityelper");
- return;
- }
Assert.isMainThread();
PropertyAnimator.cancelAnimation(mView, AnimatableProperty.ALPHA);
boolean isOccluded = mKeyguardStateController.isOccluded();
diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
index d5cb4d5..c3c7411 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
@@ -24,18 +24,15 @@
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dock.DockManager
-import com.android.systemui.dock.retrieveIsDocked
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionStep
-import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.delay
-import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.mapLatest
@@ -66,19 +63,21 @@
.onEach { nextScene -> communalInteractor.onSceneChanged(nextScene) }
.launchIn(applicationScope)
+ // TODO(b/322787129): re-enable once custom animations are in place
// Handle automatically switching to communal when docked.
- dockManager
- .retrieveIsDocked()
- // Allow some time after docking to ensure the dream doesn't start. If the dream
- // starts, then we don't want to automatically transition to glanceable hub.
- .debounce(DOCK_DEBOUNCE_DELAY)
- .sample(keyguardTransitionInteractor.startedKeyguardState, ::Pair)
- .onEach { (docked, lastStartedState) ->
- if (docked && lastStartedState == KeyguardState.LOCKSCREEN) {
- communalInteractor.onSceneChanged(CommunalScenes.Communal)
- }
- }
- .launchIn(bgScope)
+ // dockManager
+ // .retrieveIsDocked()
+ // // Allow some time after docking to ensure the dream doesn't start. If the
+ // dream
+ // // starts, then we don't want to automatically transition to glanceable hub.
+ // .debounce(DOCK_DEBOUNCE_DELAY)
+ // .sample(keyguardTransitionInteractor.startedKeyguardState, ::Pair)
+ // .onEach { (docked, lastStartedState) ->
+ // if (docked && lastStartedState == KeyguardState.LOCKSCREEN) {
+ // communalInteractor.onSceneChanged(CommunalScenes.Communal)
+ // }
+ // }
+ // .launchIn(bgScope)
}
private suspend fun determineSceneAfterTransition(
@@ -89,7 +88,7 @@
val docked = dockManager.isDocked
return when {
- docked && to == KeyguardState.LOCKSCREEN && from != KeyguardState.GLANCEABLE_HUB -> {
+ docked && to == KeyguardState.LOCKSCREEN && from == KeyguardState.DREAMING -> {
CommunalScenes.Communal
}
to == KeyguardState.GONE -> CommunalScenes.Blank
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
index fc9a7df..35b27aa 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
@@ -21,6 +21,7 @@
import com.android.systemui.communal.domain.model.CommunalContentModel
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.Logger
import com.android.systemui.log.dagger.CommunalLog
@@ -46,6 +47,7 @@
import kotlinx.coroutines.launch
/** The default view model used for showing the communal hub. */
+@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
class CommunalViewModel
@Inject
@@ -54,6 +56,7 @@
private val communalInteractor: CommunalInteractor,
tutorialInteractor: CommunalTutorialInteractor,
shadeInteractor: ShadeInteractor,
+ deviceEntryInteractor: DeviceEntryInteractor,
@Named(MediaModule.COMMUNAL_HUB) mediaHost: MediaHost,
@CommunalLog logBuffer: LogBuffer,
) : BaseCommunalViewModel(communalInteractor, mediaHost) {
@@ -87,6 +90,8 @@
/** Whether touches should be disabled in communal */
val touchesAllowed: Flow<Boolean> = not(shadeInteractor.isAnyFullyExpanded)
+ val deviceUnlocked: Flow<Boolean> = deviceEntryInteractor.isUnlocked
+
init {
// Initialize our media host for the UMO. This only needs to happen once and must be done
// before the MediaHierarchyManager attempts to move the UMO to the hub.
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeyDialogFactory.kt b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeyDialogFactory.kt
index 3501f51..89433d3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeyDialogFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeyDialogFactory.kt
@@ -46,7 +46,7 @@
}
private fun createStickyKeyIndicator(viewModel: StickyKeysIndicatorViewModel): Dialog {
- return ComponentDialog(context, R.style.Theme_SystemUI_Dialog).apply {
+ return ComponentDialog(context, R.style.Theme_SystemUI_Dialog_StickyKeys).apply {
// because we're requesting window feature it must be called before setting content
window?.setStickyKeyWindowAttributes()
setContentView(createStickyKeyIndicatorView(context, viewModel))
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index ad0c3fb..64e2870 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -24,6 +24,7 @@
import com.android.systemui.biometrics.data.repository.FacePropertyRepository
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.common.shared.model.Position
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
@@ -79,6 +80,12 @@
val keyguardAlpha: StateFlow<Float>
/**
+ * Observable of the relative offset of the lock-screen clock from its natural position on the
+ * screen.
+ */
+ val clockPosition: StateFlow<Position>
+
+ /**
* Observable for whether the keyguard is showing.
*
* Note: this is also `true` when the lock-screen is occluded with an `Activity` "above" it in
@@ -233,6 +240,11 @@
fun setKeyguardAlpha(alpha: Float)
/**
+ * Sets the relative offset of the lock-screen clock from its natural position on the screen.
+ */
+ fun setClockPosition(x: Int, y: Int)
+
+ /**
* Returns whether the keyguard bottom area should be constrained to the top of the lock icon
*/
fun isUdfpsSupported(): Boolean
@@ -311,6 +323,9 @@
private val _keyguardAlpha = MutableStateFlow(1f)
override val keyguardAlpha = _keyguardAlpha.asStateFlow()
+ private val _clockPosition = MutableStateFlow(Position(0, 0))
+ override val clockPosition = _clockPosition.asStateFlow()
+
private val _clockShouldBeCentered = MutableStateFlow(true)
override val clockShouldBeCentered: Flow<Boolean> = _clockShouldBeCentered.asStateFlow()
@@ -662,6 +677,10 @@
_keyguardAlpha.value = alpha
}
+ override fun setClockPosition(x: Int, y: Int) {
+ _clockPosition.value = Position(x, y)
+ }
+
override fun isUdfpsSupported(): Boolean = keyguardUpdateMonitor.isUdfpsSupported
override fun setQuickSettingsVisible(isVisible: Boolean) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractor.kt
index ee892a8..7ae70a9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractor.kt
@@ -63,19 +63,18 @@
burnInHelperWrapper.burnInProgressOffset()
)
- /** Given the max x,y dimens, determine the current translation shifts. */
- fun burnIn(xDimenResourceId: Int, yDimenResourceId: Int): Flow<BurnInModel> {
- return combine(
- burnInOffset(xDimenResourceId, isXAxis = true),
- burnInOffset(yDimenResourceId, isXAxis = false).map {
- it * 2 - context.resources.getDimensionPixelSize(yDimenResourceId)
+ val keyguardBurnIn: Flow<BurnInModel> =
+ combine(
+ burnInOffset(R.dimen.burn_in_prevention_offset_x, isXAxis = true),
+ burnInOffset(R.dimen.burn_in_prevention_offset_y, isXAxis = false).map {
+ it * 2 -
+ context.resources.getDimensionPixelSize(R.dimen.burn_in_prevention_offset_y)
}
) { translationX, translationY ->
BurnInModel(translationX, translationY, burnInHelperWrapper.burnInScale())
}
.distinctUntilChanged()
.stateIn(scope, SharingStarted.Lazily, BurnInModel())
- }
/**
* Use for max burn-in offsets that are NOT specified in pixels. This flow will recalculate the
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
index 617982f..dbd5e26 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
@@ -93,14 +93,22 @@
startedKeyguardTransitionStep,
keyguardInteractor.isKeyguardOccluded,
keyguardInteractor.biometricUnlockState,
+ keyguardInteractor.primaryBouncerShowing,
)
- .collect { (_, isKeyguardShowing, lastStartedStep, occluded, biometricUnlockState)
- ->
+ .collect {
+ (
+ _,
+ isKeyguardShowing,
+ lastStartedStep,
+ occluded,
+ biometricUnlockState,
+ primaryBouncerShowing) ->
if (
lastStartedStep.to == KeyguardState.AOD &&
!occluded &&
!isWakeAndUnlock(biometricUnlockState) &&
- isKeyguardShowing
+ isKeyguardShowing &&
+ !primaryBouncerShowing
) {
val modeOnCanceled =
if (lastStartedStep.from == KeyguardState.LOCKSCREEN) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
index baa865d..8591fe7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
@@ -31,6 +31,8 @@
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.launch
@@ -59,6 +61,14 @@
listenForTransitionToCamera(scope, keyguardInteractor)
}
+ private val canDismissLockScreen: Flow<Boolean> =
+ combine(
+ keyguardInteractor.isKeyguardShowing,
+ keyguardInteractor.isKeyguardDismissible,
+ ) { isKeyguardShowing, isKeyguardDismissible ->
+ isKeyguardDismissible && !isKeyguardShowing
+ }
+
private fun listenForDozingToAny() {
scope.launch {
powerInteractor.isAwake
@@ -68,8 +78,8 @@
startedKeyguardTransitionStep,
keyguardInteractor.isKeyguardOccluded,
communalInteractor.isIdleOnCommunal,
- keyguardInteractor.isKeyguardShowing,
- keyguardInteractor.isKeyguardDismissible,
+ canDismissLockScreen,
+ keyguardInteractor.primaryBouncerShowing,
)
.collect {
(
@@ -78,16 +88,18 @@
lastStartedTransition,
occluded,
isIdleOnCommunal,
- isKeyguardShowing,
- isKeyguardDismissible) ->
+ canDismissLockScreen,
+ primaryBouncerShowing) ->
if (!(isAwake && lastStartedTransition.to == KeyguardState.DOZING)) {
return@collect
}
startTransitionTo(
if (isWakeAndUnlock(biometricUnlockState)) {
KeyguardState.GONE
- } else if (isKeyguardDismissible && !isKeyguardShowing) {
+ } else if (canDismissLockScreen) {
KeyguardState.GONE
+ } else if (primaryBouncerShowing) {
+ KeyguardState.PRIMARY_BOUNCER
} else if (occluded) {
KeyguardState.OCCLUDED
} else if (isIdleOnCommunal) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBottomAreaInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBottomAreaInteractor.kt
index e5bb5a0..d2a7486 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBottomAreaInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBottomAreaInteractor.kt
@@ -22,8 +22,6 @@
import com.android.systemui.keyguard.data.repository.KeyguardRepository
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.asStateFlow
/** Encapsulates business-logic specifically related to the keyguard bottom area. */
@SysUISingleton
@@ -37,12 +35,10 @@
/** The amount of alpha for the UI components of the bottom area. */
val alpha: Flow<Float> = repository.bottomAreaAlpha
/** The position of the keyguard clock. */
- private val _clockPosition = MutableStateFlow(Position(0, 0))
- @Deprecated("with migrateClocksToBlueprint()")
- val clockPosition: Flow<Position> = _clockPosition.asStateFlow()
+ val clockPosition: Flow<Position> = repository.clockPosition
fun setClockPosition(x: Int, y: Int) {
- _clockPosition.value = Position(x, y)
+ repository.setClockPosition(x, y)
}
fun setAlpha(alpha: Float) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index fbf1e22..5410b10 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -27,6 +27,7 @@
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.common.shared.model.NotificationContainerBounds
+import com.android.systemui.common.shared.model.Position
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.data.repository.KeyguardRepository
@@ -231,6 +232,9 @@
/** The approximate location on the screen of the face unlock sensor, if one is available. */
val faceSensorLocation: Flow<Point?> = repository.faceSensorLocation
+ /** The position of the keyguard clock. */
+ val clockPosition: Flow<Position> = repository.clockPosition
+
@Deprecated("Use the relevant TransitionViewModel")
val keyguardAlpha: Flow<Float> = repository.keyguardAlpha
@@ -338,6 +342,10 @@
repository.setQuickSettingsVisible(isVisible)
}
+ fun setClockPosition(x: Int, y: Int) {
+ repository.setClockPosition(x, y)
+ }
+
fun setAlpha(alpha: Float) {
repository.setKeyguardAlpha(alpha)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt
index 462d5e6..4812e03 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt
@@ -42,7 +42,7 @@
import kotlinx.coroutines.launch
private const val TAG = "KeyguardBlueprintViewBinder"
-private const val DEBUG = true
+private const val DEBUG = false
@SysUISingleton
class KeyguardBlueprintViewBinder
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt
index e67a324..98bebd0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt
@@ -21,8 +21,6 @@
import android.view.View
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
-import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
-import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
import com.android.systemui.Flags.migrateClocksToBlueprint
import com.android.systemui.keyguard.shared.model.KeyguardSection
import com.android.systemui.keyguard.ui.view.KeyguardRootView
@@ -39,18 +37,13 @@
private val clockViewModel: KeyguardClockViewModel,
) : KeyguardSection() {
private lateinit var burnInLayer: AodBurnInLayer
- private lateinit var emptyView: View
override fun addViews(constraintLayout: ConstraintLayout) {
if (!migrateClocksToBlueprint()) {
return
}
// The burn-in layer requires at least 1 view at all times
- emptyView =
- View(context, null).apply {
- id = R.id.burn_in_layer_empty_view
- visibility = View.GONE
- }
+ val emptyView = View(context, null).apply { id = View.generateViewId() }
constraintLayout.addView(emptyView)
burnInLayer =
AodBurnInLayer(context).apply {
@@ -77,13 +70,6 @@
if (!migrateClocksToBlueprint()) {
return
}
-
- constraintSet.apply {
- // The empty view should not occupy any space
- constrainHeight(R.id.burn_in_layer_empty_view, 1)
- constrainWidth(R.id.burn_in_layer_empty_view, 0)
- connect(R.id.burn_in_layer_empty_view, BOTTOM, PARENT_ID, BOTTOM)
- }
}
override fun removeViews(constraintLayout: ConstraintLayout) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt
index 4ddd039..6184c82 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt
@@ -199,7 +199,7 @@
private val TRANSITION_PROPERTIES =
arrayOf(PROP_VISIBILITY, PROP_ALPHA, PROP_BOUNDS, SMARTSPACE_BOUNDS)
- private val DEBUG = true
+ private val DEBUG = false
private val TAG = VisibilityBoundsTransition::class.simpleName!!
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt
index 62fc1da..7be390a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt
@@ -18,7 +18,6 @@
package com.android.systemui.keyguard.ui.viewmodel
-import android.util.Log
import android.util.MathUtils
import com.android.app.animation.Interpolators
import com.android.keyguard.KeyguardClockSwitch
@@ -63,8 +62,6 @@
private val occludedToLockscreenTransitionViewModel: OccludedToLockscreenTransitionViewModel,
private val keyguardClockViewModel: KeyguardClockViewModel,
) {
- private val TAG = "AodBurnInViewModel"
-
/** Horizontal translation for elements that need to apply anti-burn-in tactics. */
fun translationX(
params: BurnInParameters,
@@ -74,17 +71,8 @@
/** Vertical translation for elements that need to apply anti-burn-in tactics. */
fun translationY(
- burnInParams: BurnInParameters,
+ params: BurnInParameters,
): Flow<Float> {
- val params =
- if (burnInParams.minViewY < burnInParams.topInset) {
- // minViewY should never be below the inset. Correct it if needed
- Log.w(TAG, "minViewY is below topInset: $burnInParams")
- burnInParams.copy(minViewY = burnInParams.topInset)
- } else {
- burnInParams
- }
-
return configurationInteractor
.dimensionPixelSize(R.dimen.keyguard_enter_from_top_translation_y)
.flatMapLatest { enterFromTopAmount ->
@@ -137,10 +125,7 @@
keyguardTransitionInteractor.dozeAmountTransition.map {
Interpolators.FAST_OUT_SLOW_IN.getInterpolation(it.value)
},
- burnInInteractor.burnIn(
- xDimenResourceId = R.dimen.burn_in_prevention_offset_x,
- yDimenResourceId = R.dimen.burn_in_prevention_offset_y
- )
+ burnInInteractor.keyguardBurnIn,
) { interpolated, burnIn ->
val useScaleOnly =
(clockController(params.clockControllerProvider)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
index 31e8093..bd19c80 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
@@ -180,7 +180,7 @@
}
private val isUnlocked: Flow<Boolean> =
- deviceEntryInteractor.isUnlocked.flatMapLatest { isUnlocked ->
+ keyguardInteractor.isKeyguardDismissible.flatMapLatest { isUnlocked ->
if (!isUnlocked) {
flowOf(false)
} else {
@@ -197,7 +197,7 @@
val iconType: Flow<DeviceEntryIconView.IconType> =
combine(
deviceEntryUdfpsInteractor.isListeningForUdfps,
- keyguardInteractor.isKeyguardDismissible,
+ isUnlocked,
) { isListeningForUdfps, isUnlocked ->
if (isListeningForUdfps) {
DeviceEntryIconView.IconType.FINGERPRINT
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModel.kt
index e35e065..6458eda 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModel.kt
@@ -17,14 +17,10 @@
package com.android.systemui.keyguard.ui.viewmodel
import com.android.systemui.Flags.keyguardBottomAreaRefactor
-import com.android.systemui.Flags.migrateClocksToBlueprint
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.doze.util.BurnInHelperWrapper
-import com.android.systemui.keyguard.domain.interactor.BurnInInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
-import com.android.systemui.keyguard.shared.model.BurnInModel
-import com.android.systemui.res.R
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
@@ -36,10 +32,9 @@
@Inject
constructor(
private val keyguardInteractor: KeyguardInteractor,
- private val bottomAreaInteractor: KeyguardBottomAreaInteractor,
+ bottomAreaInteractor: KeyguardBottomAreaInteractor,
keyguardBottomAreaViewModel: KeyguardBottomAreaViewModel,
private val burnInHelperWrapper: BurnInHelperWrapper,
- private val burnInInteractor: BurnInInteractor,
private val shortcutsCombinedViewModel: KeyguardQuickAffordancesCombinedViewModel,
configurationInteractor: ConfigurationInteractor,
) {
@@ -68,37 +63,24 @@
}
.distinctUntilChanged()
}
-
- private val burnIn: Flow<BurnInModel> =
- burnInInteractor
- .burnIn(
- xDimenResourceId = R.dimen.burn_in_prevention_offset_x,
- yDimenResourceId = R.dimen.default_burn_in_prevention_offset,
- )
- .distinctUntilChanged()
-
/** An observable for the x-offset by which the indication area should be translated. */
val indicationAreaTranslationX: Flow<Float> =
- if (migrateClocksToBlueprint() || keyguardBottomAreaRefactor()) {
- burnIn.map { it.translationX.toFloat() }
+ if (keyguardBottomAreaRefactor()) {
+ keyguardInteractor.clockPosition.map { it.x.toFloat() }.distinctUntilChanged()
} else {
bottomAreaInteractor.clockPosition.map { it.x.toFloat() }.distinctUntilChanged()
}
/** Returns an observable for the y-offset by which the indication area should be translated. */
fun indicationAreaTranslationY(defaultBurnInOffset: Int): Flow<Float> {
- return if (migrateClocksToBlueprint()) {
- burnIn.map { it.translationY.toFloat() }
- } else {
- keyguardInteractor.dozeAmount
- .map { dozeAmount ->
- dozeAmount *
- (burnInHelperWrapper.burnInOffset(
- /* amplitude = */ defaultBurnInOffset * 2,
- /* xAxis= */ false,
- ) - defaultBurnInOffset)
- }
- .distinctUntilChanged()
- }
+ return keyguardInteractor.dozeAmount
+ .map { dozeAmount ->
+ dozeAmount *
+ (burnInHelperWrapper.burnInOffset(
+ /* amplitude = */ defaultBurnInOffset * 2,
+ /* xAxis= */ false,
+ ) - defaultBurnInOffset)
+ }
+ .distinctUntilChanged()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
index 38d5e0f..1760b92 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
@@ -75,6 +75,7 @@
private val aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel,
private val dozingToGoneTransitionViewModel: DozingToGoneTransitionViewModel,
private val dozingToLockscreenTransitionViewModel: DozingToLockscreenTransitionViewModel,
+ private val dreamingToLockscreenTransitionViewModel: DreamingToLockscreenTransitionViewModel,
private val glanceableHubToLockscreenTransitionViewModel:
GlanceableHubToLockscreenTransitionViewModel,
private val goneToAodTransitionViewModel: GoneToAodTransitionViewModel,
@@ -171,6 +172,7 @@
aodToLockscreenTransitionViewModel.lockscreenAlpha(viewState),
dozingToGoneTransitionViewModel.lockscreenAlpha(viewState),
dozingToLockscreenTransitionViewModel.lockscreenAlpha,
+ dreamingToLockscreenTransitionViewModel.lockscreenAlpha,
glanceableHubToLockscreenTransitionViewModel.keyguardAlpha,
goneToAodTransitionViewModel.enterFromTopAnimationAlpha,
goneToDozingTransitionViewModel.lockscreenAlpha,
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
index 306aad1..75bf131 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
@@ -156,6 +156,7 @@
* desired scene. Once enough of the transition has occurred, the [currentScene] will become
* [toScene] (unless the transition is canceled by user action or another call to this method).
*/
+ @JvmOverloads
fun changeScene(
toScene: SceneKey,
loggingReason: String,
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/WindowRootView.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/WindowRootView.kt
index 22645c4..ea19020 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/WindowRootView.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/WindowRootView.kt
@@ -71,7 +71,6 @@
override fun onApplyWindowInsets(windowInsets: WindowInsets): WindowInsets? {
val insets = windowInsets.getInsetsIgnoringVisibility(WindowInsets.Type.systemBars())
- val displayCutout = rootWindowInsets.displayCutout
if (fitsSystemWindows) {
val paddingChanged = insets.top != paddingTop || insets.bottom != paddingBottom
@@ -79,23 +78,22 @@
if (paddingChanged) {
setPadding(0, 0, 0, 0)
}
-
- val pairInsets: Pair<Int, Int> =
- layoutInsetsController.getinsets(windowInsets, displayCutout)
- leftInset = pairInsets.first
- rightInset = pairInsets.second
- applyMargins()
} else {
val changed =
paddingLeft != 0 || paddingRight != 0 || paddingTop != 0 || paddingBottom != 0
if (changed) {
setPadding(0, 0, 0, 0)
}
-
- leftInset = 0
- rightInset = 0
}
+ leftInset = 0
+ rightInset = 0
+ val displayCutout = rootWindowInsets.displayCutout
+ val pairInsets: Pair<Int, Int> =
+ layoutInsetsController.getinsets(windowInsets, displayCutout)
+ leftInset = pairInsets.first
+ rightInset = pairInsets.second
+ applyMargins()
return windowInsets
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt
new file mode 100644
index 0000000..2294fc0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2024 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.screenshot
+
+import android.animation.Animator
+import android.app.Notification
+import android.content.Context
+import android.graphics.Bitmap
+import android.graphics.Rect
+import android.graphics.drawable.Drawable
+import android.view.Display
+import android.view.LayoutInflater
+import android.view.ScrollCaptureResponse
+import android.view.View
+import android.view.ViewTreeObserver
+import android.view.WindowInsets
+import android.window.OnBackInvokedDispatcher
+import com.android.internal.logging.UiEventLogger
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.res.R
+
+/**
+ * Legacy implementation of screenshot view methods. Just proxies the calls down into the original
+ * ScreenshotView.
+ */
+class LegacyScreenshotViewProxy(context: Context) : ScreenshotViewProxy {
+ override val view: ScreenshotView =
+ LayoutInflater.from(context).inflate(R.layout.screenshot, null) as ScreenshotView
+ override val internalInsetsListener: ViewTreeObserver.OnComputeInternalInsetsListener
+ override val screenshotPreview: View
+
+ override var defaultDisplay: Int = Display.DEFAULT_DISPLAY
+ set(value) {
+ view.setDefaultDisplay(value)
+ }
+ override var defaultTimeoutMillis: Long = 6000
+ set(value) {
+ view.setDefaultTimeoutMillis(value)
+ }
+ override var onKeyListener: View.OnKeyListener? = null
+ set(value) {
+ view.setOnKeyListener(value)
+ }
+ override var flags: FeatureFlags? = null
+ set(value) {
+ view.setFlags(value)
+ }
+ override var packageName: String = ""
+ set(value) {
+ view.setPackageName(value)
+ }
+ override var logger: UiEventLogger? = null
+ set(value) {
+ view.setUiEventLogger(value)
+ }
+ override var callbacks: ScreenshotView.ScreenshotViewCallback? = null
+ set(value) {
+ view.setCallbacks(value)
+ }
+ override var screenshot: ScreenshotData? = null
+ set(value) {
+ view.setScreenshot(value)
+ }
+
+ override val isAttachedToWindow
+ get() = view.isAttachedToWindow
+ override val isDismissing
+ get() = view.isDismissing
+ override val isPendingSharedTransition
+ get() = view.isPendingSharedTransition
+
+ init {
+ internalInsetsListener = view
+ screenshotPreview = view.screenshotPreview
+ }
+
+ override fun reset() = view.reset()
+ override fun updateInsets(insets: WindowInsets) = view.updateInsets(insets)
+ override fun updateOrientation(insets: WindowInsets) = view.updateOrientation(insets)
+
+ override fun badgeScreenshot(userBadgedIcon: Drawable) = view.badgeScreenshot(userBadgedIcon)
+
+ override fun createScreenshotDropInAnimation(screenRect: Rect, showFlash: Boolean): Animator =
+ view.createScreenshotDropInAnimation(screenRect, showFlash)
+
+ override fun addQuickShareChip(quickShareAction: Notification.Action) =
+ view.addQuickShareChip(quickShareAction)
+
+ override fun setChipIntents(imageData: ScreenshotController.SavedImageData) =
+ view.setChipIntents(imageData)
+
+ override fun animateDismissal() = view.animateDismissal()
+
+ override fun showScrollChip(packageName: String, onClick: Runnable) =
+ view.showScrollChip(packageName, onClick)
+
+ override fun hideScrollChip() = view.hideScrollChip()
+
+ override fun prepareScrollingTransition(
+ response: ScrollCaptureResponse,
+ screenBitmap: Bitmap,
+ newScreenshot: Bitmap,
+ screenshotTakenInPortrait: Boolean
+ ) =
+ view.prepareScrollingTransition(
+ response,
+ screenBitmap,
+ newScreenshot,
+ screenshotTakenInPortrait
+ )
+
+ override fun startLongScreenshotTransition(
+ transitionDestination: Rect,
+ onTransitionEnd: Runnable,
+ longScreenshot: ScrollCaptureController.LongScreenshot
+ ) = view.startLongScreenshotTransition(transitionDestination, onTransitionEnd, longScreenshot)
+
+ override fun restoreNonScrollingUi() = view.restoreNonScrollingUi()
+
+ override fun stopInputListening() = view.stopInputListening()
+
+ override fun requestFocus() {
+ view.requestFocus()
+ }
+
+ override fun announceForAccessibility(string: String) = view.announceForAccessibility(string)
+
+ override fun addOnAttachStateChangeListener(listener: View.OnAttachStateChangeListener) =
+ view.addOnAttachStateChangeListener(listener)
+
+ override fun findOnBackInvokedDispatcher(): OnBackInvokedDispatcher? =
+ view.findOnBackInvokedDispatcher()
+
+ override fun getViewTreeObserver(): ViewTreeObserver = view.viewTreeObserver
+
+ override fun post(runnable: Runnable) {
+ view.post(runnable)
+ }
+
+ class Factory : ScreenshotViewProxy.Factory {
+ override fun getProxy(context: Context): ScreenshotViewProxy {
+ return LegacyScreenshotViewProxy(context)
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index ee3e7ba..13448d2 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -65,7 +65,6 @@
import android.view.IRemoteAnimationFinishedCallback;
import android.view.IRemoteAnimationRunner;
import android.view.KeyEvent;
-import android.view.LayoutInflater;
import android.view.RemoteAnimationAdapter;
import android.view.RemoteAnimationTarget;
import android.view.ScrollCaptureResponse;
@@ -165,7 +164,7 @@
/**
* Structure returned by the SaveImageInBackgroundTask
*/
- static class SavedImageData {
+ public static class SavedImageData {
public Uri uri;
public List<Notification.Action> smartActions;
public Notification.Action quickShareAction;
@@ -237,6 +236,7 @@
private final WindowContext mContext;
private final FeatureFlags mFlags;
+ private final ScreenshotViewProxy mViewProxy;
private final ScreenshotNotificationsController mNotificationsController;
private final ScreenshotSmartActions mScreenshotSmartActions;
private final UiEventLogger mUiEventLogger;
@@ -272,7 +272,6 @@
respondToKeyDismissal();
};
- private ScreenshotView mScreenshotView;
private final MessageContainerController mMessageContainerController;
private Bitmap mScreenBitmap;
private SaveImageInBackgroundTask mSaveInBgTask;
@@ -305,6 +304,7 @@
ScreenshotController(
Context context,
FeatureFlags flags,
+ ScreenshotViewProxy.Factory viewProxyFactory,
ScreenshotSmartActions screenshotSmartActions,
ScreenshotNotificationsController.Factory screenshotNotificationsControllerFactory,
ScrollCaptureClient scrollCaptureClient,
@@ -360,6 +360,8 @@
mMessageContainerController = messageContainerController;
mAssistContentRequester = assistContentRequester;
+ mViewProxy = viewProxyFactory.getProxy(mContext);
+
mAccessibilityManager = AccessibilityManager.getInstance(mContext);
// Setup the window that we are going to use
@@ -461,7 +463,7 @@
// The window is focusable by default
setWindowFocusable(true);
- mScreenshotView.requestFocus();
+ mViewProxy.requestFocus();
enqueueScrollCaptureRequest(screenshot.getUserHandle());
@@ -485,10 +487,10 @@
mMessageContainerController.onScreenshotTaken(screenshot);
});
- mScreenshotView.badgeScreenshot(mContext.getPackageManager().getUserBadgedIcon(
+ mViewProxy.badgeScreenshot(mContext.getPackageManager().getUserBadgedIcon(
mContext.getDrawable(R.drawable.overlay_badge_background),
screenshot.getUserHandle()));
- mScreenshotView.setScreenshot(screenshot);
+ mViewProxy.setScreenshot(screenshot);
// ignore system bar insets for the purpose of window layout
mWindow.getDecorView().setOnApplyWindowInsetsListener(
@@ -503,31 +505,31 @@
void prepareViewForNewScreenshot(ScreenshotData screenshot, String oldPackageName) {
withWindowAttached(() -> {
if (mUserManager.isManagedProfile(screenshot.getUserHandle().getIdentifier())) {
- mScreenshotView.announceForAccessibility(mContext.getResources().getString(
+ mViewProxy.announceForAccessibility(mContext.getResources().getString(
R.string.screenshot_saving_work_profile_title));
} else {
- mScreenshotView.announceForAccessibility(
+ mViewProxy.announceForAccessibility(
mContext.getResources().getString(R.string.screenshot_saving_title));
}
});
- mScreenshotView.reset();
+ mViewProxy.reset();
- if (mScreenshotView.isAttachedToWindow()) {
+ if (mViewProxy.isAttachedToWindow()) {
// if we didn't already dismiss for another reason
- if (!mScreenshotView.isDismissing()) {
+ if (!mViewProxy.isDismissing()) {
mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_REENTERED, 0,
oldPackageName);
}
if (DEBUG_WINDOW) {
Log.d(TAG, "saveScreenshot: screenshotView is already attached, resetting. "
- + "(dismissing=" + mScreenshotView.isDismissing() + ")");
+ + "(dismissing=" + mViewProxy.isDismissing() + ")");
}
}
- mScreenshotView.setPackageName(mPackageName);
+ mViewProxy.setPackageName(mPackageName);
- mScreenshotView.updateOrientation(
+ mViewProxy.updateOrientation(
mWindowManager.getCurrentWindowMetrics().getWindowInsets());
}
@@ -539,7 +541,7 @@
Log.d(TAG, "dismissScreenshot");
}
// If we're already animating out, don't restart the animation
- if (mScreenshotView.isDismissing()) {
+ if (mViewProxy.isDismissing()) {
if (DEBUG_DISMISS) {
Log.v(TAG, "Already dismissing, ignoring duplicate command");
}
@@ -547,11 +549,11 @@
}
mUiEventLogger.log(event, 0, mPackageName);
mScreenshotHandler.cancelTimeout();
- mScreenshotView.animateDismissal();
+ mViewProxy.animateDismissal();
}
boolean isPendingSharedTransition() {
- return mScreenshotView.isPendingSharedTransition();
+ return mViewProxy.isPendingSharedTransition();
}
// Any cleanup needed when the service is being destroyed.
@@ -576,7 +578,7 @@
private void releaseMediaPlayer() {
if (mScreenshotSoundController == null) return;
- mScreenshotSoundController.releaseScreenshotSound();
+ mScreenshotSoundController.releaseScreenshotSoundAsync();
}
private void respondToKeyDismissal() {
@@ -591,18 +593,15 @@
Log.d(TAG, "reloadAssets()");
}
- // Inflate the screenshot layout
- mScreenshotView = (ScreenshotView)
- LayoutInflater.from(mContext).inflate(R.layout.screenshot, null);
- mMessageContainerController.setView(mScreenshotView);
- mScreenshotView.addOnAttachStateChangeListener(
+ mMessageContainerController.setView(mViewProxy.getView());
+ mViewProxy.addOnAttachStateChangeListener(
new View.OnAttachStateChangeListener() {
@Override
public void onViewAttachedToWindow(@NonNull View v) {
if (DEBUG_INPUT) {
Log.d(TAG, "Registering Predictive Back callback");
}
- mScreenshotView.findOnBackInvokedDispatcher().registerOnBackInvokedCallback(
+ mViewProxy.findOnBackInvokedDispatcher().registerOnBackInvokedCallback(
OnBackInvokedDispatcher.PRIORITY_DEFAULT, mOnBackInvokedCallback);
}
@@ -611,11 +610,12 @@
if (DEBUG_INPUT) {
Log.d(TAG, "Unregistering Predictive Back callback");
}
- mScreenshotView.findOnBackInvokedDispatcher()
+ mViewProxy.findOnBackInvokedDispatcher()
.unregisterOnBackInvokedCallback(mOnBackInvokedCallback);
}
});
- mScreenshotView.init(mUiEventLogger, new ScreenshotView.ScreenshotViewCallback() {
+ mViewProxy.setLogger(mUiEventLogger);
+ mViewProxy.setCallbacks(new ScreenshotView.ScreenshotViewCallback() {
@Override
public void onUserInteraction() {
if (DEBUG_INPUT) {
@@ -640,11 +640,12 @@
// TODO(159460485): Remove this when focus is handled properly in the system
setWindowFocusable(false);
}
- }, mFlags);
- mScreenshotView.setDefaultDisplay(mDisplayId);
- mScreenshotView.setDefaultTimeoutMillis(mScreenshotHandler.getDefaultTimeoutMillis());
+ });
+ mViewProxy.setFlags(mFlags);
+ mViewProxy.setDefaultDisplay(mDisplayId);
+ mViewProxy.setDefaultTimeoutMillis(mScreenshotHandler.getDefaultTimeoutMillis());
- mScreenshotView.setOnKeyListener((v, keyCode, event) -> {
+ mViewProxy.setOnKeyListener((v, keyCode, event) -> {
if (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_ESCAPE) {
if (DEBUG_INPUT) {
Log.d(TAG, "onKeyEvent: " + keyCode);
@@ -658,23 +659,24 @@
if (DEBUG_WINDOW) {
Log.d(TAG, "adding OnComputeInternalInsetsListener");
}
- mScreenshotView.getViewTreeObserver().addOnComputeInternalInsetsListener(mScreenshotView);
+ mViewProxy.getViewTreeObserver().addOnComputeInternalInsetsListener(
+ mViewProxy.getInternalInsetsListener());
if (DEBUG_WINDOW) {
- Log.d(TAG, "setContentView: " + mScreenshotView);
+ Log.d(TAG, "setContentView: " + mViewProxy.getView());
}
- setContentView(mScreenshotView);
+ setContentView(mViewProxy.getView());
}
private void prepareAnimation(Rect screenRect, boolean showFlash,
Runnable onAnimationComplete) {
- mScreenshotView.getViewTreeObserver().addOnPreDrawListener(
+ mViewProxy.getViewTreeObserver().addOnPreDrawListener(
new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
if (DEBUG_WINDOW) {
Log.d(TAG, "onPreDraw: startAnimation");
}
- mScreenshotView.getViewTreeObserver().removeOnPreDrawListener(this);
+ mViewProxy.getViewTreeObserver().removeOnPreDrawListener(this);
startAnimation(screenRect, showFlash, onAnimationComplete);
return true;
}
@@ -694,13 +696,13 @@
if (mConfigChanges.applyNewConfig(mContext.getResources())) {
// Hide the scroll chip until we know it's available in this
// orientation
- mScreenshotView.hideScrollChip();
+ mViewProxy.hideScrollChip();
// Delay scroll capture eval a bit to allow the underlying activity
// to set up in the new orientation.
mScreenshotHandler.postDelayed(() -> {
requestScrollCapture(owner);
}, 150);
- mScreenshotView.updateInsets(
+ mViewProxy.updateInsets(
mWindowManager.getCurrentWindowMetrics().getWindowInsets());
// Screenshot animation calculations won't be valid anymore,
// so just end
@@ -759,16 +761,16 @@
+ mLastScrollCaptureResponse.getWindowTitle() + "]");
final ScrollCaptureResponse response = mLastScrollCaptureResponse;
- mScreenshotView.showScrollChip(response.getPackageName(), /* onClick */ () -> {
+ mViewProxy.showScrollChip(response.getPackageName(), /* onClick */ () -> {
DisplayMetrics displayMetrics = new DisplayMetrics();
getDisplay().getRealMetrics(displayMetrics);
Bitmap newScreenshot = mImageCapture.captureDisplay(mDisplayId,
new Rect(0, 0, displayMetrics.widthPixels, displayMetrics.heightPixels));
- mScreenshotView.prepareScrollingTransition(response, mScreenBitmap, newScreenshot,
+ mViewProxy.prepareScrollingTransition(response, mScreenBitmap, newScreenshot,
mScreenshotTakenInPortrait);
// delay starting scroll capture to make sure the scrim is up before the app moves
- mScreenshotView.post(() -> runBatchScrollCapture(response, owner));
+ mViewProxy.post(() -> runBatchScrollCapture(response, owner));
});
} catch (InterruptedException | ExecutionException e) {
Log.e(TAG, "requestScrollCapture failed", e);
@@ -794,19 +796,19 @@
return;
} catch (InterruptedException | ExecutionException e) {
Log.e(TAG, "Exception", e);
- mScreenshotView.restoreNonScrollingUi();
+ mViewProxy.restoreNonScrollingUi();
return;
}
if (longScreenshot.getHeight() == 0) {
- mScreenshotView.restoreNonScrollingUi();
+ mViewProxy.restoreNonScrollingUi();
return;
}
mLongScreenshotHolder.setLongScreenshot(longScreenshot);
mLongScreenshotHolder.setTransitionDestinationCallback(
(transitionDestination, onTransitionEnd) -> {
- mScreenshotView.startLongScreenshotTransition(
+ mViewProxy.startLongScreenshotTransition(
transitionDestination, onTransitionEnd,
longScreenshot);
// TODO: Do this via ActionIntentExecutor instead.
@@ -882,16 +884,14 @@
}
mWindowManager.removeViewImmediate(decorView);
}
- // Ensure that we remove the input monitor
- if (mScreenshotView != null) {
- mScreenshotView.stopInputListening();
- }
+
+ mViewProxy.stopInputListening();
}
private void playCameraSoundIfNeeded() {
if (mScreenshotSoundController == null) return;
// the controller is not-null only on the default display controller
- mScreenshotSoundController.playCameraSound();
+ mScreenshotSoundController.playScreenshotSoundAsync();
}
/**
@@ -932,7 +932,7 @@
}
mScreenshotAnimation =
- mScreenshotView.createScreenshotDropInAnimation(screenRect, showFlash);
+ mViewProxy.createScreenshotDropInAnimation(screenRect, showFlash);
if (onAnimationComplete != null) {
mScreenshotAnimation.addListener(new AnimatorListenerAdapter() {
@Override
@@ -975,7 +975,7 @@
};
Pair<ActivityOptions, ExitTransitionCoordinator> transition =
ActivityOptions.startSharedElementAnimation(mWindow, callbacks, null,
- Pair.create(mScreenshotView.getScreenshotPreview(),
+ Pair.create(mViewProxy.getScreenshotPreview(),
ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME));
return transition;
@@ -999,7 +999,7 @@
mCurrentRequestCallback.onFinish();
mCurrentRequestCallback = null;
}
- mScreenshotView.reset();
+ mViewProxy.reset();
removeWindow();
mScreenshotHandler.cancelTimeout();
}
@@ -1067,7 +1067,7 @@
}
private void doPostAnimation(ScreenshotController.SavedImageData imageData) {
- mScreenshotView.setChipIntents(imageData);
+ mViewProxy.setChipIntents(imageData);
}
/**
@@ -1084,11 +1084,11 @@
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
- mScreenshotView.addQuickShareChip(quickShareData.quickShareAction);
+ mViewProxy.addQuickShareChip(quickShareData.quickShareAction);
}
});
} else {
- mScreenshotView.addQuickShareChip(quickShareData.quickShareAction);
+ mViewProxy.addQuickShareChip(quickShareData.quickShareAction);
}
});
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSoundController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSoundController.kt
index 2c0bdde..d3a7fc4a 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSoundController.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSoundController.kt
@@ -21,22 +21,34 @@
import com.android.app.tracing.coroutines.async
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
-import com.google.errorprone.annotations.CanIgnoreReturnValue
import javax.inject.Inject
import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.TimeoutCancellationException
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
import kotlinx.coroutines.withTimeout
/** Controls sound reproduction after a screenshot is taken. */
interface ScreenshotSoundController {
/** Reproduces the camera sound. */
- @CanIgnoreReturnValue fun playCameraSound(): Deferred<Unit>
+ suspend fun playScreenshotSound()
- /** Releases the sound. [playCameraSound] behaviour is undefined after this has been called. */
- @CanIgnoreReturnValue fun releaseScreenshotSound(): Deferred<Unit>
+ /**
+ * Releases the sound. [playScreenshotSound] behaviour is undefined after this has been called.
+ */
+ suspend fun releaseScreenshotSound()
+
+ /** Reproduces the camera sound. Used for compatibility with Java code. */
+ fun playScreenshotSoundAsync()
+
+ /**
+ * Releases the sound. [playScreenshotSound] behaviour is undefined after this has been called.
+ * Used for compatibility with Java code.
+ */
+ fun releaseScreenshotSoundAsync()
}
class ScreenshotSoundControllerImpl
@@ -47,8 +59,8 @@
@Background private val bgDispatcher: CoroutineDispatcher
) : ScreenshotSoundController {
- val player: Deferred<MediaPlayer?> =
- coroutineScope.async("loadCameraSound", bgDispatcher) {
+ private val player: Deferred<MediaPlayer?> =
+ coroutineScope.async("loadScreenshotSound", bgDispatcher) {
try {
soundProvider.getScreenshotSound()
} catch (e: IllegalStateException) {
@@ -57,8 +69,8 @@
}
}
- override fun playCameraSound(): Deferred<Unit> {
- return coroutineScope.async("playCameraSound", bgDispatcher) {
+ override suspend fun playScreenshotSound() {
+ withContext(bgDispatcher) {
try {
player.await()?.start()
} catch (e: IllegalStateException) {
@@ -68,8 +80,8 @@
}
}
- override fun releaseScreenshotSound(): Deferred<Unit> {
- return coroutineScope.async("releaseScreenshotSound", bgDispatcher) {
+ override suspend fun releaseScreenshotSound() {
+ withContext(bgDispatcher) {
try {
withTimeout(1.seconds) { player.await()?.release() }
} catch (e: TimeoutCancellationException) {
@@ -79,6 +91,14 @@
}
}
+ override fun playScreenshotSoundAsync() {
+ coroutineScope.launch { playScreenshotSound() }
+ }
+
+ override fun releaseScreenshotSoundAsync() {
+ coroutineScope.launch { releaseScreenshotSound() }
+ }
+
private companion object {
const val TAG = "ScreenshotSoundControllerImpl"
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
index be30a15..8a8766d 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
@@ -102,7 +102,7 @@
public class ScreenshotView extends FrameLayout implements
ViewTreeObserver.OnComputeInternalInsetsListener {
- interface ScreenshotViewCallback {
+ public interface ScreenshotViewCallback {
void onUserInteraction();
void onAction(Intent intent, UserHandle owner, boolean overrideTransition);
@@ -426,15 +426,15 @@
return mScreenshotPreview;
}
- /**
- * Set up the logger and callback on dismissal.
- *
- * Note: must be called before any other (non-constructor) method or null pointer exceptions
- * may occur.
- */
- void init(UiEventLogger uiEventLogger, ScreenshotViewCallback callbacks, FeatureFlags flags) {
+ void setUiEventLogger(UiEventLogger uiEventLogger) {
mUiEventLogger = uiEventLogger;
+ }
+
+ void setCallbacks(ScreenshotViewCallback callbacks) {
mCallbacks = callbacks;
+ }
+
+ void setFlags(FeatureFlags flags) {
mFlags = flags;
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt
new file mode 100644
index 0000000..0064521
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2024 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.screenshot
+
+import android.animation.Animator
+import android.app.Notification
+import android.content.Context
+import android.graphics.Bitmap
+import android.graphics.Rect
+import android.graphics.drawable.Drawable
+import android.view.ScrollCaptureResponse
+import android.view.View
+import android.view.View.OnKeyListener
+import android.view.ViewGroup
+import android.view.ViewTreeObserver
+import android.view.WindowInsets
+import android.window.OnBackInvokedDispatcher
+import com.android.internal.logging.UiEventLogger
+import com.android.systemui.flags.FeatureFlags
+
+/** Abstraction of the surface between ScreenshotController and ScreenshotView */
+interface ScreenshotViewProxy {
+ val view: ViewGroup
+ val internalInsetsListener: ViewTreeObserver.OnComputeInternalInsetsListener
+ val screenshotPreview: View
+
+ var defaultDisplay: Int
+ var defaultTimeoutMillis: Long
+ var onKeyListener: OnKeyListener?
+ var flags: FeatureFlags?
+ var packageName: String
+ var logger: UiEventLogger?
+ var callbacks: ScreenshotView.ScreenshotViewCallback?
+ var screenshot: ScreenshotData?
+
+ val isAttachedToWindow: Boolean
+ val isDismissing: Boolean
+ val isPendingSharedTransition: Boolean
+
+ fun reset()
+ fun updateInsets(insets: WindowInsets)
+ fun updateOrientation(insets: WindowInsets)
+ fun badgeScreenshot(userBadgedIcon: Drawable)
+ fun createScreenshotDropInAnimation(screenRect: Rect, showFlash: Boolean): Animator
+ fun addQuickShareChip(quickShareAction: Notification.Action)
+ fun setChipIntents(imageData: ScreenshotController.SavedImageData)
+ fun animateDismissal()
+
+ fun showScrollChip(packageName: String, onClick: Runnable)
+ fun hideScrollChip()
+ fun prepareScrollingTransition(
+ response: ScrollCaptureResponse,
+ screenBitmap: Bitmap,
+ newScreenshot: Bitmap,
+ screenshotTakenInPortrait: Boolean
+ )
+ fun startLongScreenshotTransition(
+ transitionDestination: Rect,
+ onTransitionEnd: Runnable,
+ longScreenshot: ScrollCaptureController.LongScreenshot
+ )
+ fun restoreNonScrollingUi()
+
+ fun stopInputListening()
+ fun requestFocus()
+ fun announceForAccessibility(string: String)
+ fun addOnAttachStateChangeListener(listener: View.OnAttachStateChangeListener)
+ fun findOnBackInvokedDispatcher(): OnBackInvokedDispatcher?
+ fun getViewTreeObserver(): ViewTreeObserver
+ fun post(runnable: Runnable)
+
+ interface Factory {
+ fun getProxy(context: Context): ScreenshotViewProxy
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
index bb34ede..8a2678c 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
@@ -75,7 +75,7 @@
private String mWindowOwner;
private volatile boolean mCancelled;
- static class LongScreenshot {
+ public static class LongScreenshot {
private final ImageTileSet mImageTileSet;
private final Session mSession;
// TODO: Add UserHandle so LongScreenshots can adhere to work profile screenshot policy
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java b/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
index 3797b8b..a00c81d 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
@@ -20,6 +20,7 @@
import com.android.systemui.screenshot.ImageCapture;
import com.android.systemui.screenshot.ImageCaptureImpl;
+import com.android.systemui.screenshot.LegacyScreenshotViewProxy;
import com.android.systemui.screenshot.RequestProcessor;
import com.android.systemui.screenshot.ScreenshotPolicy;
import com.android.systemui.screenshot.ScreenshotPolicyImpl;
@@ -29,12 +30,14 @@
import com.android.systemui.screenshot.ScreenshotSoundControllerImpl;
import com.android.systemui.screenshot.ScreenshotSoundProvider;
import com.android.systemui.screenshot.ScreenshotSoundProviderImpl;
+import com.android.systemui.screenshot.ScreenshotViewProxy;
import com.android.systemui.screenshot.TakeScreenshotService;
import com.android.systemui.screenshot.appclips.AppClipsScreenshotHelperService;
import com.android.systemui.screenshot.appclips.AppClipsService;
import dagger.Binds;
import dagger.Module;
+import dagger.Provides;
import dagger.multibindings.ClassKey;
import dagger.multibindings.IntoMap;
@@ -81,4 +84,9 @@
@Binds
abstract ScreenshotSoundController bindScreenshotSoundController(
ScreenshotSoundControllerImpl screenshotSoundProviderImpl);
+
+ @Provides
+ static ScreenshotViewProxy.Factory providesScreenshotViewProxyFactory() {
+ return new LegacyScreenshotViewProxy.Factory();
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index cf7c3e0..1566de5 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -1288,9 +1288,10 @@
mView.getContext().getDisplay());
mKeyguardStatusViewController = statusViewComponent.getKeyguardStatusViewController();
mKeyguardStatusViewController.init();
+ }
- mKeyguardStatusViewController.setSplitShadeEnabled(mSplitShadeEnabled);
- mKeyguardStatusViewController.getView().addOnLayoutChangeListener(
+ mKeyguardStatusViewController.setSplitShadeEnabled(mSplitShadeEnabled);
+ mKeyguardStatusViewController.getView().addOnLayoutChangeListener(
(v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
int oldHeight = oldBottom - oldTop;
if (v.getHeight() != oldHeight) {
@@ -1298,8 +1299,7 @@
}
});
- updateClockAppearance();
- }
+ updateClockAppearance();
}
@Override
@@ -1326,9 +1326,7 @@
private void onSplitShadeEnabledChanged() {
mShadeLog.logSplitShadeChanged(mSplitShadeEnabled);
- if (!migrateClocksToBlueprint()) {
- mKeyguardStatusViewController.setSplitShadeEnabled(mSplitShadeEnabled);
- }
+ mKeyguardStatusViewController.setSplitShadeEnabled(mSplitShadeEnabled);
// Reset any left over overscroll state. It is a rare corner case but can happen.
mQsController.setOverScrollAmount(0);
mScrimController.setNotificationsOverScrollAmount(0);
@@ -1443,13 +1441,11 @@
mStatusBarStateListener.onDozeAmountChanged(mStatusBarStateController.getDozeAmount(),
mStatusBarStateController.getInterpolatedDozeAmount());
- if (!migrateClocksToBlueprint()) {
- mKeyguardStatusViewController.setKeyguardStatusViewVisibility(
- mBarState,
- false,
- false,
- mBarState);
- }
+ mKeyguardStatusViewController.setKeyguardStatusViewVisibility(
+ mBarState,
+ false,
+ false,
+ mBarState);
if (mKeyguardQsUserSwitchController != null) {
mKeyguardQsUserSwitchController.setKeyguardQsUserSwitchVisibility(
mBarState,
@@ -1669,11 +1665,13 @@
mKeyguardStatusViewController.setLockscreenClockY(
mClockPositionAlgorithm.getExpandedPreferredClockY());
}
- if (!(migrateClocksToBlueprint() || keyguardBottomAreaRefactor())) {
+ if (keyguardBottomAreaRefactor()) {
+ mKeyguardInteractor.setClockPosition(
+ mClockPositionResult.clockX, mClockPositionResult.clockY);
+ } else {
mKeyguardBottomAreaInteractor.setClockPosition(
mClockPositionResult.clockX, mClockPositionResult.clockY);
}
-
boolean animate = mNotificationStackScrollLayoutController.isAddOrRemoveAnimationPending();
boolean animateClock = (animate || mAnimateNextPositionUpdate) && shouldAnimateClockChange;
@@ -1751,11 +1749,13 @@
}
private void updateKeyguardStatusViewAlignment(boolean animate) {
- if (migrateClocksToBlueprint()) {
- return;
- }
boolean shouldBeCentered = shouldKeyguardStatusViewBeCentered();
- ConstraintLayout layout = mNotificationContainerParent;
+ ConstraintLayout layout;
+ if (migrateClocksToBlueprint()) {
+ layout = mKeyguardViewConfigurator.getKeyguardRootView();
+ } else {
+ layout = mNotificationContainerParent;
+ }
mKeyguardStatusViewController.updateAlignment(
layout, mSplitShadeEnabled, shouldBeCentered, animate);
mKeyguardUnfoldTransition.ifPresent(t -> t.setStatusViewCentered(shouldBeCentered));
@@ -3316,9 +3316,6 @@
/** Updates the views to the initial state for the fold to AOD animation. */
@Override
public void prepareFoldToAodAnimation() {
- if (migrateClocksToBlueprint()) {
- return;
- }
// Force show AOD UI even if we are not locked
showAodUi();
@@ -3340,9 +3337,6 @@
@Override
public void startFoldToAodAnimation(Runnable startAction, Runnable endAction,
Runnable cancelAction) {
- if (migrateClocksToBlueprint()) {
- return;
- }
final ViewPropertyAnimator viewAnimator = mView.animate();
viewAnimator.cancel();
viewAnimator
@@ -3378,9 +3372,6 @@
/** Cancels fold to AOD transition and resets view state. */
@Override
public void cancelFoldToAodAnimation() {
- if (migrateClocksToBlueprint()) {
- return;
- }
cancelAnimation();
resetAlpha();
resetTranslation();
@@ -4455,13 +4446,11 @@
}
}
- if (!migrateClocksToBlueprint()) {
- mKeyguardStatusViewController.setKeyguardStatusViewVisibility(
- statusBarState,
- keyguardFadingAway,
- goingToFullShade,
- mBarState);
- }
+ mKeyguardStatusViewController.setKeyguardStatusViewVisibility(
+ statusBarState,
+ keyguardFadingAway,
+ goingToFullShade,
+ mBarState);
if (!keyguardBottomAreaRefactor()) {
setKeyguardBottomAreaVisibility(statusBarState, goingToFullShade);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
index 8f9cef3..f7fed53 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
@@ -60,7 +60,6 @@
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
import com.android.systemui.res.R;
-import com.android.systemui.scene.shared.flag.SceneContainerFlag;
import com.android.systemui.scene.shared.flag.SceneContainerFlags;
import com.android.systemui.scene.ui.view.WindowRootViewComponent;
import com.android.systemui.settings.UserTracker;
@@ -507,7 +506,7 @@
private void applyFitsSystemWindows(NotificationShadeWindowState state) {
boolean fitsSystemWindows = !state.isKeyguardShowingAndNotOccluded();
- if (!SceneContainerFlag.isEnabled() && mWindowRootView != null
+ if (mWindowRootView != null
&& mWindowRootView.getFitsSystemWindows() != fitsSystemWindows) {
mWindowRootView.setFitsSystemWindows(fitsSystemWindows);
mWindowRootView.requestApplyInsets();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java
index 9445d56..ea3036e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java
@@ -31,6 +31,7 @@
import com.android.systemui.res.R;
import com.android.systemui.statusbar.InflationTask;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.util.time.SystemClock;
import javax.inject.Inject;
@@ -46,9 +47,14 @@
private NotificationEntry mEntry;
private boolean mCancelled;
private Throwable mInflateOrigin;
+ private final SystemClock mSystemClock;
+ private final RowInflaterTaskLogger mLogger;
+ private long mInflateStartTimeMs;
@Inject
- public RowInflaterTask() {
+ public RowInflaterTask(SystemClock systemClock, RowInflaterTaskLogger logger) {
+ mSystemClock = systemClock;
+ mLogger = logger;
}
/**
@@ -61,29 +67,49 @@
}
mListener = listener;
AsyncLayoutInflater inflater = com.android.systemui.Flags.notificationRowUserContext()
- ? new AsyncLayoutInflater(context, new RowAsyncLayoutInflater(entry))
+ ? new AsyncLayoutInflater(context, makeRowInflater(entry))
: new AsyncLayoutInflater(context);
mEntry = entry;
entry.setInflationTask(this);
+
+ mLogger.logInflateStart(entry);
+ mInflateStartTimeMs = mSystemClock.elapsedRealtime();
inflater.inflate(R.layout.status_bar_notification_row, parent, this);
}
+ private RowAsyncLayoutInflater makeRowInflater(NotificationEntry entry) {
+ return new RowAsyncLayoutInflater(entry, mSystemClock, mLogger);
+ }
+
@VisibleForTesting
static class RowAsyncLayoutInflater implements AsyncLayoutFactory {
private final NotificationEntry mEntry;
+ private final SystemClock mSystemClock;
+ private final RowInflaterTaskLogger mLogger;
- RowAsyncLayoutInflater(NotificationEntry entry) {
+ RowAsyncLayoutInflater(NotificationEntry entry, SystemClock systemClock,
+ RowInflaterTaskLogger logger) {
mEntry = entry;
+ mSystemClock = systemClock;
+ mLogger = logger;
}
@Nullable
@Override
public View onCreateView(@Nullable View parent, @NonNull String name,
@NonNull Context context, @NonNull AttributeSet attrs) {
- if (name.equals(ExpandableNotificationRow.class.getName())) {
- return new ExpandableNotificationRow(context, attrs, mEntry);
+ if (!name.equals(ExpandableNotificationRow.class.getName())) {
+ return null;
}
- return null;
+
+ final long startMs = mSystemClock.elapsedRealtime();
+ final ExpandableNotificationRow row =
+ new ExpandableNotificationRow(context, attrs, mEntry);
+ final long elapsedMs = mSystemClock.elapsedRealtime() - startMs;
+
+ mLogger.logCreatedRow(mEntry, elapsedMs);
+
+ return row;
}
@Nullable
@@ -101,6 +127,9 @@
@Override
public void onInflateFinished(View view, int resid, ViewGroup parent) {
+ final long elapsedMs = mSystemClock.elapsedRealtime() - mInflateStartTimeMs;
+ mLogger.logInflateFinish(mEntry, elapsedMs, mCancelled);
+
if (!mCancelled) {
try {
mEntry.onInflationTaskFinished();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTaskLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTaskLogger.kt
new file mode 100644
index 0000000..94da6be
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTaskLogger.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row
+
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.dagger.NotifInflationLog
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.logKey
+import javax.inject.Inject
+
+class RowInflaterTaskLogger @Inject constructor(@NotifInflationLog private val buffer: LogBuffer) {
+ fun logInflateStart(entry: NotificationEntry) {
+ buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ { str1 = entry.logKey },
+ { "started row inflation for $str1" }
+ )
+ }
+
+ fun logCreatedRow(entry: NotificationEntry, elapsedMs: Long) {
+ buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ {
+ str1 = entry.logKey
+ long1 = elapsedMs
+ },
+ { "created row in $long1 ms for $str1" }
+ )
+ }
+
+ fun logInflateFinish(entry: NotificationEntry, elapsedMs: Long, cancelled: Boolean) {
+ buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ {
+ str1 = entry.logKey
+ long1 = elapsedMs
+ bool1 = cancelled
+ },
+ { "finished ${if (bool1) "cancelled " else ""}row inflation in $long1 ms for $str1" }
+ )
+ }
+}
+
+private const val TAG = "RowInflaterTask"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index d1055c7..ee84434 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -81,6 +81,9 @@
import com.android.systemui.navigationbar.TaskbarDelegate;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.scene.domain.interactor.SceneInteractor;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
+import com.android.systemui.scene.shared.model.Scenes;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.shade.ShadeExpansionChangeEvent;
import com.android.systemui.shade.ShadeExpansionListener;
@@ -157,6 +160,7 @@
private final AlternateBouncerInteractor mAlternateBouncerInteractor;
private final BouncerView mPrimaryBouncerView;
private final Lazy<ShadeController> mShadeController;
+ private final Lazy<SceneInteractor> mSceneInteractorLazy;
// Local cache of expansion events, to avoid duplicates
private float mFraction = -1f;
@@ -381,7 +385,8 @@
Lazy<KeyguardDismissActionInteractor> keyguardDismissActionInteractorLazy,
SelectedUserInteractor selectedUserInteractor,
Lazy<KeyguardSurfaceBehindInteractor> surfaceBehindInteractor,
- JavaAdapter javaAdapter
+ JavaAdapter javaAdapter,
+ Lazy<SceneInteractor> sceneInteractorLazy
) {
mContext = context;
mViewMediatorCallback = callback;
@@ -415,6 +420,7 @@
mSelectedUserInteractor = selectedUserInteractor;
mSurfaceBehindInteractor = surfaceBehindInteractor;
mJavaAdapter = javaAdapter;
+ mSceneInteractorLazy = sceneInteractorLazy;
}
KeyguardTransitionInteractor mKeyguardTransitionInteractor;
@@ -633,8 +639,11 @@
public void show(Bundle options) {
Trace.beginSection("StatusBarKeyguardViewManager#show");
mNotificationShadeWindowController.setKeyguardShowing(true);
- mKeyguardStateController.notifyKeyguardState(true,
- mKeyguardStateController.isOccluded());
+ if (SceneContainerFlag.isEnabled()) {
+ mSceneInteractorLazy.get().changeScene(
+ Scenes.Lockscreen, "StatusBarKeyguardViewManager.show");
+ }
+ mKeyguardStateController.notifyKeyguardState(true, mKeyguardStateController.isOccluded());
reset(true /* hideBouncerWhenShowing */);
SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_STATE_CHANGED,
SysUiStatsLog.KEYGUARD_STATE_CHANGED__STATE__SHOWN);
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimation.kt b/packages/SystemUI/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimation.kt
index d19a336..5c53ff9 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimation.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimation.kt
@@ -16,8 +16,6 @@
package com.android.systemui.unfold
-import android.animation.Animator
-import android.animation.AnimatorListenerAdapter
import android.animation.ValueAnimator
import android.annotation.BinderThread
import android.content.Context
@@ -25,6 +23,7 @@
import android.os.SystemProperties
import android.util.Log
import android.view.animation.DecelerateInterpolator
+import androidx.core.animation.addListener
import com.android.internal.foldables.FoldLockSettingAvailabilityProvider
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.display.data.repository.DeviceStateRepository
@@ -37,25 +36,17 @@
import com.android.systemui.unfold.dagger.UnfoldBg
import com.android.systemui.util.animation.data.repository.AnimationStatusRepository
import javax.inject.Inject
-import kotlin.coroutines.resume
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.TimeoutCancellationException
import kotlinx.coroutines.android.asCoroutineDispatcher
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.first
-import kotlinx.coroutines.flow.flatMapLatest
-import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.launch
-import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withTimeout
-@OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class)
class FoldLightRevealOverlayAnimation
@Inject
constructor(
@@ -70,9 +61,6 @@
private val revealProgressValueAnimator: ValueAnimator =
ValueAnimator.ofFloat(ALPHA_OPAQUE, ALPHA_TRANSPARENT)
- private val areAnimationEnabled: Flow<Boolean>
- get() = animationStatusRepository.areAnimationsEnabled()
-
private lateinit var controller: FullscreenLightRevealAnimationController
@Volatile private var readyCallback: CompletableDeferred<Runnable>? = null
@@ -101,30 +89,33 @@
applicationScope.launch(bgHandler.asCoroutineDispatcher()) {
deviceStateRepository.state
- .map { it == DeviceStateRepository.DeviceState.FOLDED }
+ .map { it != DeviceStateRepository.DeviceState.FOLDED }
.distinctUntilChanged()
- .flatMapLatest { isFolded ->
- flow<Nothing> {
- if (!areAnimationEnabled.first() || !isFolded) {
- return@flow
- }
- withTimeout(WAIT_FOR_ANIMATION_TIMEOUT_MS) {
- readyCallback = CompletableDeferred()
- val onReady = readyCallback?.await()
- readyCallback = null
- controller.addOverlay(ALPHA_OPAQUE, onReady)
- waitForScreenTurnedOn()
- }
+ .filter { isUnfolded -> isUnfolded }
+ .collect { controller.ensureOverlayRemoved() }
+ }
+
+ applicationScope.launch(bgHandler.asCoroutineDispatcher()) {
+ deviceStateRepository.state
+ .filter {
+ animationStatusRepository.areAnimationsEnabled().first() &&
+ it == DeviceStateRepository.DeviceState.FOLDED
+ }
+ .collect {
+ try {
+ withTimeout(WAIT_FOR_ANIMATION_TIMEOUT_MS) {
+ readyCallback = CompletableDeferred()
+ val onReady = readyCallback?.await()
+ readyCallback = null
+ controller.addOverlay(ALPHA_OPAQUE, onReady)
+ waitForScreenTurnedOn()
playFoldLightRevealOverlayAnimation()
}
- .catchTimeoutAndLog()
- .onCompletion {
- val onReady = readyCallback?.takeIf { it.isCompleted }?.getCompleted()
- onReady?.run()
- readyCallback = null
- }
+ } catch (e: TimeoutCancellationException) {
+ Log.e(TAG, "Fold light reveal animation timed out")
+ ensureOverlayRemovedInternal()
+ }
}
- .collect {}
}
}
@@ -137,34 +128,19 @@
powerInteractor.screenPowerState.filter { it == ScreenPowerState.SCREEN_ON }.first()
}
- private suspend fun playFoldLightRevealOverlayAnimation() {
+ private fun ensureOverlayRemovedInternal() {
+ revealProgressValueAnimator.cancel()
+ controller.ensureOverlayRemoved()
+ }
+
+ private fun playFoldLightRevealOverlayAnimation() {
revealProgressValueAnimator.duration = ANIMATION_DURATION
revealProgressValueAnimator.interpolator = DecelerateInterpolator()
revealProgressValueAnimator.addUpdateListener { animation ->
controller.updateRevealAmount(animation.animatedFraction)
}
- revealProgressValueAnimator.startAndAwaitCompletion()
- }
-
- private suspend fun ValueAnimator.startAndAwaitCompletion(): Unit =
- suspendCancellableCoroutine { continuation ->
- val listener =
- object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator) {
- continuation.resume(Unit)
- removeListener(this)
- }
- }
- addListener(listener)
- continuation.invokeOnCancellation { removeListener(listener) }
- start()
- }
-
- private fun <T> Flow<T>.catchTimeoutAndLog() = catch { exception ->
- when (exception) {
- is TimeoutCancellationException -> Log.e(TAG, "Fold light reveal animation timed out")
- else -> throw exception
- }
+ revealProgressValueAnimator.addListener(onEnd = { controller.ensureOverlayRemoved() })
+ revealProgressValueAnimator.start()
}
private companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputActionsInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputActionsInteractor.kt
index 170b32c..2ab5998 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputActionsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputActionsInteractor.kt
@@ -16,14 +16,11 @@
package com.android.systemui.volume.panel.component.mediaoutput.domain.interactor
-import android.content.Intent
-import android.provider.Settings
import com.android.internal.jank.InteractionJankMonitor
import com.android.systemui.animation.DialogCuj
import com.android.systemui.animation.DialogTransitionAnimator
import com.android.systemui.animation.Expandable
import com.android.systemui.media.dialog.MediaOutputDialogFactory
-import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.volume.panel.component.mediaoutput.domain.model.MediaDeviceSession
import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
import javax.inject.Inject
@@ -34,17 +31,8 @@
@Inject
constructor(
private val mediaOutputDialogFactory: MediaOutputDialogFactory,
- private val activityStarter: ActivityStarter,
) {
- fun onDeviceClick(expandable: Expandable) {
- activityStarter.startActivity(
- Intent(Settings.ACTION_BLUETOOTH_SETTINGS),
- true,
- expandable.activityTransitionController(),
- )
- }
-
fun onBarClick(session: MediaDeviceSession, expandable: Expandable) {
when (session) {
is MediaDeviceSession.Active -> {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/DeviceIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/DeviceIconViewModel.kt
index e518ed0..37bf661 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/DeviceIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/DeviceIconViewModel.kt
@@ -26,13 +26,13 @@
val iconColor: Color
val backgroundColor: Color
- class IsPlaying(
+ data class IsPlaying(
override val icon: Icon,
override val iconColor: Color,
override val backgroundColor: Color,
) : DeviceIconViewModel
- class IsNotPlaying(
+ data class IsNotPlaying(
override val icon: Icon,
override val iconColor: Color,
override val backgroundColor: Color,
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt
index 85d6c9e..37661b5 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt
@@ -113,11 +113,6 @@
private fun MediaDeviceSession.isPlaying(): Boolean =
this is MediaDeviceSession.Active && playbackState?.isActive == true
- fun onDeviceClick(expandable: Expandable) {
- actionsInteractor.onDeviceClick(expandable)
- volumePanelViewModel.dismissPanel()
- }
-
fun onBarClick(expandable: Expandable) {
actionsInteractor.onBarClick(mediaDeviceSession.value, expandable)
volumePanelViewModel.dismissPanel()
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java
index 90587d7..13fb42c 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java
@@ -16,6 +16,8 @@
package com.android.keyguard;
+import static kotlinx.coroutines.flow.FlowKt.emptyFlow;
+
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -30,6 +32,7 @@
import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory;
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
import com.android.systemui.power.data.repository.FakePowerRepository;
import com.android.systemui.power.domain.interactor.PowerInteractorFactory;
import com.android.systemui.res.R;
@@ -59,6 +62,7 @@
@Mock protected KeyguardStatusViewController mControllerMock;
@Mock protected InteractionJankMonitor mInteractionJankMonitor;
@Mock protected ViewTreeObserver mViewTreeObserver;
+ @Mock protected KeyguardTransitionInteractor mKeyguardTransitionInteractor;
@Mock protected DumpManager mDumpManager;
protected FakeKeyguardRepository mFakeKeyguardRepository;
protected FakePowerRepository mFakePowerRepository;
@@ -89,6 +93,7 @@
mKeyguardLogger,
mInteractionJankMonitor,
deps.getKeyguardInteractor(),
+ mKeyguardTransitionInteractor,
mDumpManager,
PowerInteractorFactory.create(
mFakePowerRepository
@@ -105,6 +110,7 @@
when(mKeyguardStatusView.getViewTreeObserver()).thenReturn(mViewTreeObserver);
when(mKeyguardClockSwitchController.getView()).thenReturn(mKeyguardClockSwitch);
+ when(mKeyguardTransitionInteractor.getGoneToAodTransition()).thenReturn(emptyFlow());
when(mKeyguardStatusView.findViewById(R.id.keyguard_status_area))
.thenReturn(mKeyguardStatusAreaView);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorTest.kt
index d0b1dd5..df52265 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorTest.kt
@@ -122,13 +122,7 @@
testScope.runTest {
whenever(burnInHelperWrapper.burnInScale()).thenReturn(0.5f)
- val burnInModel by
- collectLastValue(
- underTest.burnIn(
- xDimenResourceId = R.dimen.burn_in_prevention_offset_x,
- yDimenResourceId = R.dimen.burn_in_prevention_offset_y
- )
- )
+ val burnInModel by collectLastValue(underTest.keyguardBurnIn)
// After time tick, returns the configured values
fakeKeyguardRepository.dozeTimeTick(10)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index eae0467..c65a9ef 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -654,6 +654,30 @@
coroutineContext.cancelChildren()
}
+ @Test
+ fun dozingToPrimaryBouncer() =
+ testScope.runTest {
+ // GIVEN a prior transition has run to DOZING
+ runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.DOZING)
+ runCurrent()
+
+ // WHEN awaked by a request to show the primary bouncer, as can happen if SPFS is
+ // touched after boot
+ powerInteractor.setAwakeForTest()
+ bouncerRepository.setPrimaryShow(true)
+ advanceTimeBy(60L)
+
+ assertThat(transitionRepository)
+ .startedTransition(
+ to = KeyguardState.PRIMARY_BOUNCER,
+ from = KeyguardState.DOZING,
+ ownerName = "FromDozingTransitionInteractor",
+ animatorAssertion = { it.isNotNull() }
+ )
+
+ coroutineContext.cancelChildren()
+ }
+
/** This handles security method NONE and screen off with lock timeout */
@Test
fun dozingToGoneWithKeyguardNotShowing() =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelTest.kt
index 02bd810..4bb0d47 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelTest.kt
@@ -27,11 +27,14 @@
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.keyguardRepository
+import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryIconViewModel.Companion.UNLOCKED_DELAY_MS
import com.android.systemui.kosmos.testScope
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlin.test.Test
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.advanceTimeBy
+import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.runner.RunWith
@@ -71,7 +74,10 @@
fun isLongPressEnabled_unlocked() =
testScope.runTest {
val isLongPressEnabled by collectLastValue(underTest.isLongPressEnabled)
+ fingerprintPropertyRepository.supportsUdfps()
keyguardRepository.setKeyguardDismissible(true)
+ advanceTimeBy(UNLOCKED_DELAY_MS * 2) // wait for unlocked delay
+ runCurrent()
assertThat(isLongPressEnabled).isTrue()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModelTest.kt
new file mode 100644
index 0000000..864acfb
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModelTest.kt
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2023 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.ui.viewmodel
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.Flags as AConfigFlags
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
+import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.doze.util.BurnInHelperWrapper
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
+import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(JUnit4::class)
+class KeyguardIndicationAreaViewModelTest : SysuiTestCase() {
+
+ @Mock private lateinit var burnInHelperWrapper: BurnInHelperWrapper
+ @Mock private lateinit var shortcutsCombinedViewModel: KeyguardQuickAffordancesCombinedViewModel
+
+ private lateinit var underTest: KeyguardIndicationAreaViewModel
+ private lateinit var repository: FakeKeyguardRepository
+
+ private val startButtonFlow =
+ MutableStateFlow<KeyguardQuickAffordanceViewModel>(
+ KeyguardQuickAffordanceViewModel(
+ slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId()
+ )
+ )
+ private val endButtonFlow =
+ MutableStateFlow<KeyguardQuickAffordanceViewModel>(
+ KeyguardQuickAffordanceViewModel(
+ slotId = KeyguardQuickAffordancePosition.BOTTOM_END.toSlotId()
+ )
+ )
+ private val alphaFlow = MutableStateFlow<Float>(1f)
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ mSetFlagsRule.disableFlags(AConfigFlags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR)
+
+ whenever(burnInHelperWrapper.burnInOffset(anyInt(), any()))
+ .thenReturn(RETURNED_BURN_IN_OFFSET)
+
+ val withDeps = KeyguardInteractorFactory.create()
+ val keyguardInteractor = withDeps.keyguardInteractor
+ repository = withDeps.repository
+
+ val bottomAreaViewModel: KeyguardBottomAreaViewModel = mock()
+ whenever(bottomAreaViewModel.startButton).thenReturn(startButtonFlow)
+ whenever(bottomAreaViewModel.endButton).thenReturn(endButtonFlow)
+ whenever(bottomAreaViewModel.alpha).thenReturn(alphaFlow)
+ underTest =
+ KeyguardIndicationAreaViewModel(
+ keyguardInteractor = keyguardInteractor,
+ bottomAreaInteractor = KeyguardBottomAreaInteractor(repository = repository),
+ keyguardBottomAreaViewModel = bottomAreaViewModel,
+ burnInHelperWrapper = burnInHelperWrapper,
+ shortcutsCombinedViewModel = shortcutsCombinedViewModel,
+ configurationInteractor = ConfigurationInteractor(FakeConfigurationRepository()),
+ )
+ }
+
+ @Test
+ fun alpha() = runTest {
+ val value = collectLastValue(underTest.alpha)
+
+ assertThat(value()).isEqualTo(1f)
+ alphaFlow.value = 0.1f
+ assertThat(value()).isEqualTo(0.1f)
+ alphaFlow.value = 0.5f
+ assertThat(value()).isEqualTo(0.5f)
+ alphaFlow.value = 0.2f
+ assertThat(value()).isEqualTo(0.2f)
+ alphaFlow.value = 0f
+ assertThat(value()).isEqualTo(0f)
+ }
+
+ @Test
+ fun isIndicationAreaPadded() = runTest {
+ repository.setKeyguardShowing(true)
+ val value = collectLastValue(underTest.isIndicationAreaPadded)
+
+ assertThat(value()).isFalse()
+ startButtonFlow.value = startButtonFlow.value.copy(isVisible = true)
+ assertThat(value()).isTrue()
+ endButtonFlow.value = endButtonFlow.value.copy(isVisible = true)
+ assertThat(value()).isTrue()
+ startButtonFlow.value = startButtonFlow.value.copy(isVisible = false)
+ assertThat(value()).isTrue()
+ endButtonFlow.value = endButtonFlow.value.copy(isVisible = false)
+ assertThat(value()).isFalse()
+ }
+
+ @Test
+ fun indicationAreaTranslationX() = runTest {
+ val value = collectLastValue(underTest.indicationAreaTranslationX)
+
+ assertThat(value()).isEqualTo(0f)
+ repository.setClockPosition(100, 100)
+ assertThat(value()).isEqualTo(100f)
+ repository.setClockPosition(200, 100)
+ assertThat(value()).isEqualTo(200f)
+ repository.setClockPosition(200, 200)
+ assertThat(value()).isEqualTo(200f)
+ repository.setClockPosition(300, 100)
+ assertThat(value()).isEqualTo(300f)
+ }
+
+ @Test
+ fun indicationAreaTranslationY() = runTest {
+ val value = collectLastValue(underTest.indicationAreaTranslationY(DEFAULT_BURN_IN_OFFSET))
+
+ // Negative 0 - apparently there's a difference in floating point arithmetic - FML
+ assertThat(value()).isEqualTo(-0f)
+ val expected1 = setDozeAmountAndCalculateExpectedTranslationY(0.1f)
+ assertThat(value()).isEqualTo(expected1)
+ val expected2 = setDozeAmountAndCalculateExpectedTranslationY(0.2f)
+ assertThat(value()).isEqualTo(expected2)
+ val expected3 = setDozeAmountAndCalculateExpectedTranslationY(0.5f)
+ assertThat(value()).isEqualTo(expected3)
+ val expected4 = setDozeAmountAndCalculateExpectedTranslationY(1f)
+ assertThat(value()).isEqualTo(expected4)
+ }
+
+ private fun setDozeAmountAndCalculateExpectedTranslationY(dozeAmount: Float): Float {
+ repository.setDozeAmount(dozeAmount)
+ return dozeAmount * (RETURNED_BURN_IN_OFFSET - DEFAULT_BURN_IN_OFFSET)
+ }
+
+ companion object {
+ private const val DEFAULT_BURN_IN_OFFSET = 5
+ private const val RETURNED_BURN_IN_OFFSET = 3
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotSoundControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotSoundControllerTest.kt
index 2f911fff..92c2404 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotSoundControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotSoundControllerTest.kt
@@ -22,8 +22,10 @@
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import java.lang.IllegalStateException
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
@@ -31,12 +33,14 @@
import org.mockito.Mockito.verify
@SmallTest
+@OptIn(ExperimentalCoroutinesApi::class)
class ScreenshotSoundControllerTest : SysuiTestCase() {
private val soundProvider = mock<ScreenshotSoundProvider>()
private val mediaPlayer = mock<MediaPlayer>()
private val bgDispatcher = UnconfinedTestDispatcher()
private val scope = TestScope(bgDispatcher)
+
@Before
fun setup() {
whenever(soundProvider.getScreenshotSound()).thenReturn(mediaPlayer)
@@ -45,52 +49,59 @@
@Test
fun init_soundLoading() {
createController()
- bgDispatcher.scheduler.runCurrent()
+ scope.advanceUntilIdle()
verify(soundProvider).getScreenshotSound()
}
@Test
- fun init_soundLoadingException_playAndReleaseDoNotThrow() = runTest {
- whenever(soundProvider.getScreenshotSound()).thenThrow(IllegalStateException())
+ fun init_soundLoadingException_playAndReleaseDoNotThrow() =
+ scope.runTest {
+ whenever(soundProvider.getScreenshotSound()).thenThrow(IllegalStateException())
- val controller = createController()
+ val controller = createController()
- controller.playCameraSound().await()
- controller.releaseScreenshotSound().await()
+ controller.playScreenshotSound()
+ advanceUntilIdle()
- verify(mediaPlayer, never()).start()
- verify(mediaPlayer, never()).release()
- }
+ verify(mediaPlayer, never()).start()
+ verify(mediaPlayer, never()).release()
+ }
@Test
- fun playCameraSound_soundLoadingSuccessful_mediaPlayerPlays() = runTest {
- val controller = createController()
+ fun playCameraSound_soundLoadingSuccessful_mediaPlayerPlays() =
+ scope.runTest {
+ val controller = createController()
- controller.playCameraSound().await()
+ controller.playScreenshotSound()
+ advanceUntilIdle()
- verify(mediaPlayer).start()
- }
+ verify(mediaPlayer).start()
+ }
@Test
- fun playCameraSound_illegalStateException_doesNotThrow() = runTest {
- whenever(mediaPlayer.start()).thenThrow(IllegalStateException())
+ fun playCameraSound_illegalStateException_doesNotThrow() =
+ scope.runTest {
+ whenever(mediaPlayer.start()).thenThrow(IllegalStateException())
- val controller = createController()
- controller.playCameraSound().await()
+ val controller = createController()
+ controller.playScreenshotSound()
+ advanceUntilIdle()
- verify(mediaPlayer).start()
- verify(mediaPlayer).release()
- }
+ verify(mediaPlayer).start()
+ verify(mediaPlayer).release()
+ }
@Test
- fun playCameraSound_soundLoadingSuccessful_mediaPlayerReleases() = runTest {
- val controller = createController()
+ fun playCameraSound_soundLoadingSuccessful_mediaPlayerReleases() =
+ scope.runTest {
+ val controller = createController()
- controller.releaseScreenshotSound().await()
+ controller.releaseScreenshotSound()
+ advanceUntilIdle()
- verify(mediaPlayer).release()
- }
+ verify(mediaPlayer).release()
+ }
private fun createController() =
ScreenshotSoundControllerImpl(soundProvider, scope, bgDispatcher)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index f8771b2..fd7b139 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -461,6 +461,7 @@
mKeyguardLogger,
mInteractionJankMonitor,
mKeyguardInteractor,
+ mKeyguardTransitionInteractor,
mDumpManager,
mPowerInteractor));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
index e78081f..fb49499f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
@@ -89,6 +89,8 @@
import com.android.systemui.statusbar.policy.SmartReplyConstants;
import com.android.systemui.statusbar.policy.SmartReplyStateInflater;
import com.android.systemui.statusbar.policy.dagger.RemoteInputViewSubcomponent;
+import com.android.systemui.util.time.SystemClock;
+import com.android.systemui.util.time.SystemClockImpl;
import com.android.systemui.wmshell.BubblesManager;
import com.android.systemui.wmshell.BubblesTestActivity;
@@ -136,6 +138,8 @@
public final Runnable mFutureDismissalRunnable;
private @InflationFlag int mDefaultInflationFlags;
private final FakeFeatureFlags mFeatureFlags;
+ private final SystemClock mSystemClock;
+ private final RowInflaterTaskLogger mRowInflaterTaskLogger;
public NotificationTestHelper(
Context context,
@@ -199,6 +203,9 @@
mFutureDismissalRunnable = mock(Runnable.class);
when(mOnUserInteractionCallback.registerFutureDismissal(any(), anyInt()))
.thenReturn(mFutureDismissalRunnable);
+
+ mSystemClock = new SystemClockImpl();
+ mRowInflaterTaskLogger = mock(RowInflaterTaskLogger.class);
}
public void setDefaultInflationFlags(@InflationFlag int defaultInflationFlags) {
@@ -572,7 +579,8 @@
LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
if (com.android.systemui.Flags.notificationRowUserContext()) {
- inflater.setFactory2(new RowInflaterTask.RowAsyncLayoutInflater(entry));
+ inflater.setFactory2(new RowInflaterTask.RowAsyncLayoutInflater(entry, mSystemClock,
+ mRowInflaterTaskLogger));
}
mRow = (ExpandableNotificationRow) inflater.inflate(
R.layout.status_bar_notification_row,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index 3666248..f050857 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -86,6 +86,7 @@
import com.android.systemui.navigationbar.TaskbarDelegate;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
+import com.android.systemui.scene.domain.interactor.SceneInteractor;
import com.android.systemui.shade.NotificationShadeWindowView;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.shade.ShadeExpansionChangeEvent;
@@ -224,7 +225,8 @@
() -> mock(KeyguardDismissActionInteractor.class),
mSelectedUserInteractor,
() -> mock(KeyguardSurfaceBehindInteractor.class),
- mock(JavaAdapter.class)) {
+ mock(JavaAdapter.class),
+ () -> mock(SceneInteractor.class)) {
@Override
public ViewRootImpl getViewRootImpl() {
return mViewRootImpl;
@@ -733,7 +735,8 @@
() -> mock(KeyguardDismissActionInteractor.class),
mSelectedUserInteractor,
() -> mock(KeyguardSurfaceBehindInteractor.class),
- mock(JavaAdapter.class)) {
+ mock(JavaAdapter.class),
+ () -> mock(SceneInteractor.class)) {
@Override
public ViewRootImpl getViewRootImpl() {
return mViewRootImpl;
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
index 1e305d6..793e2d7 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
@@ -18,6 +18,7 @@
package com.android.systemui.keyguard.data.repository
import android.graphics.Point
+import com.android.systemui.common.shared.model.Position
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
@@ -57,6 +58,9 @@
private val _bottomAreaAlpha = MutableStateFlow(1f)
override val bottomAreaAlpha: StateFlow<Float> = _bottomAreaAlpha
+ private val _clockPosition = MutableStateFlow(Position(0, 0))
+ override val clockPosition: StateFlow<Position> = _clockPosition
+
private val _isKeyguardShowing = MutableStateFlow(false)
override val isKeyguardShowing: Flow<Boolean> = _isKeyguardShowing
@@ -145,6 +149,10 @@
_bottomAreaAlpha.value = alpha
}
+ override fun setClockPosition(x: Int, y: Int) {
+ _clockPosition.value = Position(x, y)
+ }
+
fun setKeyguardShowing(isShowing: Boolean) {
_isKeyguardShowing.value = isShowing
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
index d5d357f..c2300a1e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
@@ -43,6 +43,7 @@
aodToLockscreenTransitionViewModel = aodToLockscreenTransitionViewModel,
dozingToGoneTransitionViewModel = dozingToGoneTransitionViewModel,
dozingToLockscreenTransitionViewModel = dozingToLockscreenTransitionViewModel,
+ dreamingToLockscreenTransitionViewModel = dreamingToLockscreenTransitionViewModel,
glanceableHubToLockscreenTransitionViewModel = glanceableHubToLockscreenTransitionViewModel,
goneToAodTransitionViewModel = goneToAodTransitionViewModel,
goneToDozingTransitionViewModel = goneToDozingTransitionViewModel,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt
index 3f20df3..a3b1a0e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt
@@ -25,7 +25,6 @@
import com.android.systemui.kosmos.testCase
import com.android.systemui.kosmos.testScope
import com.android.systemui.media.mediaOutputDialogFactory
-import com.android.systemui.plugins.activityStarter
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
@@ -43,7 +42,7 @@
Kosmos.Fixture { FakeLocalMediaRepositoryFactory { localMediaRepository } }
val Kosmos.mediaOutputActionsInteractor by
- Kosmos.Fixture { MediaOutputActionsInteractor(mediaOutputDialogFactory, activityStarter) }
+ Kosmos.Fixture { MediaOutputActionsInteractor(mediaOutputDialogFactory) }
val Kosmos.mediaControllerRepository by Kosmos.Fixture { FakeMediaControllerRepository() }
val Kosmos.mediaOutputInteractor by
Kosmos.Fixture {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/MediaOutputComponentKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/MediaOutputComponentKosmos.kt
deleted file mode 100644
index ad8ccb0..0000000
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/MediaOutputComponentKosmos.kt
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2024 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.volume.panel
-
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.media.mediaOutputDialogFactory
-import com.android.systemui.plugins.activityStarter
-import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputActionsInteractor
-
-val Kosmos.mediaOutputActionsInteractor by
- Kosmos.Fixture { MediaOutputActionsInteractor(mediaOutputDialogFactory, activityStarter) }
diff --git a/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/runtimehelper/ClassLoadHook.java b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/runtimehelper/ClassLoadHook.java
index cc94090..9057d16 100644
--- a/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/runtimehelper/ClassLoadHook.java
+++ b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/runtimehelper/ClassLoadHook.java
@@ -42,7 +42,6 @@
public static final String KEYBOARD_PATHS = "keyboard_paths";
public static final String GRAPHICS_NATIVE_CLASSES = "graphics_native_classes";
- public static final String VALUE_N_A = "**n/a**";
public static final String LIBANDROID_RUNTIME_NAME = "android_runtime";
private static String sInitialDir = new File("").getAbsolutePath();
@@ -130,8 +129,6 @@
}
setProperty(CORE_NATIVE_CLASSES, jniClasses);
setProperty(GRAPHICS_NATIVE_CLASSES, "");
- setProperty(ICU_DATA_PATH, VALUE_N_A);
- setProperty(KEYBOARD_PATHS, VALUE_N_A);
RavenwoodUtils.loadJniLibrary(LIBANDROID_RUNTIME_NAME);
}
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
index f4ea754..279bd72 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
@@ -1004,17 +1004,19 @@
&& overscrollState(event, mFirstPointerDownLocation)
== OVERSCROLL_VERTICAL_EDGE) {
transitionToDelegatingStateAndClear();
- } // TODO(b/319537921): should there be an else here?
- //Primary pointer is swiping, so transit to PanningScalingState
- transitToPanningScalingStateAndClear();
+ } else {
+ //Primary pointer is swiping, so transit to PanningScalingState
+ transitToPanningScalingStateAndClear();
+ }
} else if (mIsSinglePanningEnabled
&& isActivated()
&& event.getPointerCount() == 1) {
if (overscrollState(event, mFirstPointerDownLocation)
== OVERSCROLL_VERTICAL_EDGE) {
transitionToDelegatingStateAndClear();
- } // TODO(b/319537921): should there be an else here?
- transitToSinglePanningStateAndClear();
+ } else {
+ transitToSinglePanningStateAndClear();
+ }
} else if (!mIsTwoFingerCountReached) {
// If it is a two-finger gesture, do not transition to the
// delegating state to ensure the reachability of
@@ -1257,17 +1259,19 @@
&& overscrollState(event, mFirstPointerDownLocation)
== OVERSCROLL_VERTICAL_EDGE) {
transitionToDelegatingStateAndClear();
+ } else {
+ //Primary pointer is swiping, so transit to PanningScalingState
+ transitToPanningScalingStateAndClear();
}
- //Primary pointer is swiping, so transit to PanningScalingState
- transitToPanningScalingStateAndClear();
} else if (mIsSinglePanningEnabled
&& isActivated()
&& event.getPointerCount() == 1) {
if (overscrollState(event, mFirstPointerDownLocation)
== OVERSCROLL_VERTICAL_EDGE) {
transitionToDelegatingStateAndClear();
+ } else {
+ transitToSinglePanningStateAndClear();
}
- transitToSinglePanningStateAndClear();
} else {
transitionToDelegatingStateAndClear();
}
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index 6f45f60..29b9d44 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -476,8 +476,12 @@
} else {
// If the package is being updated, we'll receive a PACKAGE_ADDED
// shortly, otherwise it is removed permanently.
- final boolean packageRemovedPermanently = (extras == null
- || !extras.getBoolean(Intent.EXTRA_REPLACING, false));
+ boolean isReplacing = extras != null && extras.getBoolean(Intent.EXTRA_REPLACING,
+ false);
+ boolean isArchival = extras != null && extras.getBoolean(Intent.EXTRA_ARCHIVAL,
+ false);
+ final boolean packageRemovedPermanently =
+ (extras == null || !isReplacing || (isReplacing && isArchival));
if (packageRemovedPermanently) {
for (String pkgName : pkgList) {
@@ -2074,6 +2078,7 @@
private void handleNotifyAppWidgetViewDataChanged(Host host, IAppWidgetHost callbacks,
int appWidgetId, int viewId, long requestId) {
try {
+ Slog.d(TAG, "Trying to notify widget view data changed");
callbacks.viewDataChanged(appWidgetId, viewId);
host.lastWidgetUpdateSequenceNo = requestId;
} catch (RemoteException re) {
@@ -2158,6 +2163,9 @@
private void handleNotifyUpdateAppWidget(Host host, IAppWidgetHost callbacks,
int appWidgetId, RemoteViews views, long requestId) {
try {
+ Slog.d(TAG, "Trying to notify widget update for package "
+ + (views == null ? "null" : views.getPackage())
+ + " with widget id: " + appWidgetId);
callbacks.updateAppWidget(appWidgetId, views);
host.lastWidgetUpdateSequenceNo = requestId;
} catch (RemoteException re) {
@@ -2196,6 +2204,7 @@
private void handleNotifyProviderChanged(Host host, IAppWidgetHost callbacks,
int appWidgetId, AppWidgetProviderInfo info, long requestId) {
try {
+ Slog.d(TAG, "Trying to notify provider update");
callbacks.providerChanged(appWidgetId, info);
host.lastWidgetUpdateSequenceNo = requestId;
} catch (RemoteException re) {
@@ -2239,6 +2248,7 @@
private void handleNotifyAppWidgetRemoved(Host host, IAppWidgetHost callbacks, int appWidgetId,
long requestId) {
try {
+ Slog.d(TAG, "Trying to notify widget removed");
callbacks.appWidgetRemoved(appWidgetId);
host.lastWidgetUpdateSequenceNo = requestId;
} catch (RemoteException re) {
@@ -2286,6 +2296,7 @@
private void handleNotifyProvidersChanged(Host host, IAppWidgetHost callbacks) {
try {
+ Slog.d(TAG, "Trying to notify widget providers changed");
callbacks.providersChanged();
} catch (RemoteException re) {
synchronized (mLock) {
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index a478a3d..17ba073 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -228,8 +228,6 @@
/* cdmService */ this, mAssociationStore, mPersistentStore,
mSystemDataTransferRequestStore, mAssociationRequestsProcessor);
- loadAssociationsFromDisk();
-
mObservableUuidStore.getObservableUuidsForUser(getContext().getUserId());
mAssociationStore.registerListener(mAssociationStoreChangeListener);
@@ -240,13 +238,18 @@
mCompanionAppController = new CompanionApplicationController(
context, mAssociationStore, mObservableUuidStore, mDevicePresenceMonitor,
mPowerManagerInternal);
+
+ mAssociationRevokeProcessor = new AssociationRevokeProcessor(this, mAssociationStore,
+ mPackageManagerInternal, mDevicePresenceMonitor, mCompanionAppController,
+ mSystemDataTransferRequestStore);
+
+ loadAssociationsFromDisk();
+
mTransportManager = new CompanionTransportManager(context, mAssociationStore);
mSystemDataTransferProcessor = new SystemDataTransferProcessor(this,
mPackageManagerInternal, mAssociationStore,
mSystemDataTransferRequestStore, mTransportManager);
- mAssociationRevokeProcessor = new AssociationRevokeProcessor(this, mAssociationStore,
- mPackageManagerInternal, mDevicePresenceMonitor, mCompanionAppController,
- mSystemDataTransferRequestStore);
+
// TODO(b/279663946): move context sync to a dedicated system service
mCrossDeviceSyncController = new CrossDeviceSyncController(getContext(), mTransportManager);
diff --git a/services/core/java/com/android/server/SystemConfig.java b/services/core/java/com/android/server/SystemConfig.java
index 9189ea7..e1d7be1 100644
--- a/services/core/java/com/android/server/SystemConfig.java
+++ b/services/core/java/com/android/server/SystemConfig.java
@@ -348,6 +348,9 @@
// marked as stopped by the system
@NonNull private final Set<String> mInitialNonStoppedSystemPackages = new ArraySet<>();
+ // Which packages (key) are allowed to join particular SharedUid (value).
+ @NonNull private final Map<String, String> mPackageToSharedUidAllowList = new ArrayMap<>();
+
// A map of preloaded package names and the path to its app metadata file path.
private final ArrayMap<String, String> mAppMetadataFilePaths = new ArrayMap<>();
@@ -567,6 +570,11 @@
return mInitialNonStoppedSystemPackages;
}
+ @NonNull
+ public Map<String, String> getPackageToSharedUidAllowList() {
+ return mPackageToSharedUidAllowList;
+ }
+
public ArrayMap<String, String> getAppMetadataFilePaths() {
return mAppMetadataFilePaths;
}
@@ -1563,6 +1571,19 @@
mInitialNonStoppedSystemPackages.add(pkgName);
}
} break;
+ case "allow-package-shareduid": {
+ String pkgName = parser.getAttributeValue(null, "package");
+ String sharedUid = parser.getAttributeValue(null, "shareduid");
+ if (TextUtils.isEmpty(pkgName)) {
+ Slog.w(TAG, "<" + name + "> without package in " + permFile
+ + " at " + parser.getPositionDescription());
+ } else if (TextUtils.isEmpty(sharedUid)) {
+ Slog.w(TAG, "<" + name + "> without shareduid in " + permFile
+ + " at " + parser.getPositionDescription());
+ } else {
+ mPackageToSharedUidAllowList.put(pkgName, sharedUid);
+ }
+ }
case "asl-file": {
String packageName = parser.getAttributeValue(null, "package");
String path = parser.getAttributeValue(null, "path");
diff --git a/services/core/java/com/android/server/TEST_MAPPING b/services/core/java/com/android/server/TEST_MAPPING
index 5e9d1cb..8dc15ad 100644
--- a/services/core/java/com/android/server/TEST_MAPPING
+++ b/services/core/java/com/android/server/TEST_MAPPING
@@ -163,6 +163,9 @@
}
],
"file_patterns": ["PinnerService\\.java"]
+ },
+ {
+ "name": "FrameworksVpnTests"
}
]
}
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index b8e09cc..258f53d 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -372,6 +372,15 @@
@Overridable
public static final long FGS_BOOT_COMPLETED_RESTRICTIONS = 296558535L;
+ /**
+ * Disables foreground service background starts in System Alert Window for all types
+ * unless it already has a System Overlay Window.
+ */
+ @ChangeId
+ @EnabledSince(targetSdkVersion = VERSION_CODES.VANILLA_ICE_CREAM)
+ @Overridable
+ public static final long FGS_SAW_RESTRICTIONS = 319471980L;
+
final ActivityManagerService mAm;
// Maximum number of services that we allow to start in the background
@@ -8526,10 +8535,31 @@
}
}
+ // The flag being enabled isn't enough to deny background start: we need to also check
+ // if there is a system alert UI present.
if (ret == REASON_DENIED) {
- if (mAm.mAtmInternal.hasSystemAlertWindowPermission(callingUid, callingPid,
- callingPackage)) {
- ret = REASON_SYSTEM_ALERT_WINDOW_PERMISSION;
+ // Flag check: are we disabling SAW FGS background starts?
+ final boolean shouldDisableSaw = Flags.fgsDisableSaw()
+ && CompatChanges.isChangeEnabled(FGS_BOOT_COMPLETED_RESTRICTIONS, callingUid);
+ if (shouldDisableSaw) {
+ final ProcessRecord processRecord = mAm
+ .getProcessRecordLocked(targetService.processName,
+ targetService.appInfo.uid);
+ if (processRecord != null) {
+ if (processRecord.mState.hasOverlayUi()) {
+ if (mAm.mAtmInternal.hasSystemAlertWindowPermission(callingUid, callingPid,
+ callingPackage)) {
+ ret = REASON_SYSTEM_ALERT_WINDOW_PERMISSION;
+ }
+ }
+ } else {
+ Slog.e(TAG, "Could not find process record for SAW check");
+ }
+ } else {
+ if (mAm.mAtmInternal.hasSystemAlertWindowPermission(callingUid, callingPid,
+ callingPackage)) {
+ ret = REASON_SYSTEM_ALERT_WINDOW_PERMISSION;
+ }
}
}
diff --git a/services/core/java/com/android/server/am/flags.aconfig b/services/core/java/com/android/server/am/flags.aconfig
index c06bdf9..b1823b4 100644
--- a/services/core/java/com/android/server/am/flags.aconfig
+++ b/services/core/java/com/android/server/am/flags.aconfig
@@ -23,6 +23,13 @@
}
flag {
+ name: "fgs_disable_saw"
+ namespace: "backstage_power"
+ description: "Disable System Alert Window FGS start"
+ bug: "296558535"
+}
+
+flag {
name: "bfgs_managed_network_access"
namespace: "backstage_power"
description: "Restrict network access for certain applications in BFGS process state"
diff --git a/services/core/java/com/android/server/biometrics/log/ALSProbe.java b/services/core/java/com/android/server/biometrics/log/ALSProbe.java
index d584c99..d4e46a9 100644
--- a/services/core/java/com/android/server/biometrics/log/ALSProbe.java
+++ b/services/core/java/com/android/server/biometrics/log/ALSProbe.java
@@ -179,15 +179,18 @@
nextConsumer.consume(current);
} else if (mNextConsumer != null) {
mNextConsumer.add(nextConsumer);
- } else {
+ } else if (mLightSensor != null) {
mDestroyed = false;
mNextConsumer = nextConsumer;
enableLightSensorLoggingLocked();
+ } else {
+ Slog.w(TAG, "No light sensor - use current to consume");
+ nextConsumer.consume(current);
}
}
private void enableLightSensorLoggingLocked() {
- if (!mEnabled) {
+ if (!mEnabled && mLightSensor != null) {
mEnabled = true;
mLastAmbientLux = -1;
mSensorManager.registerListener(mLightSensorListener, mLightSensor,
@@ -201,7 +204,7 @@
private void disableLightSensorLoggingLocked(boolean destroying) {
resetTimerLocked(false /* start */);
- if (mEnabled) {
+ if (mEnabled && mLightSensor != null) {
mEnabled = false;
if (!destroying) {
mLastAmbientLux = -1;
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
index 6b8586a..68b4e3f 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
@@ -119,7 +119,7 @@
* Receives the incoming binder calls from FaceManager.
*/
@VisibleForTesting final class FaceServiceWrapper extends IFaceService.Stub {
- @android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC)
+ @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override
public ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback,
@NonNull String opPackageName) {
diff --git a/services/core/java/com/android/server/connectivity/TEST_MAPPING b/services/core/java/com/android/server/connectivity/TEST_MAPPING
index 687d4b0..f508319 100644
--- a/services/core/java/com/android/server/connectivity/TEST_MAPPING
+++ b/services/core/java/com/android/server/connectivity/TEST_MAPPING
@@ -7,7 +7,7 @@
"exclude-annotation": "com.android.testutils.SkipPresubmit"
}
],
- "file_patterns": ["Vpn\\.java", "VpnIkeV2Utils\\.java", "VpnProfileStore\\.java"]
+ "file_patterns": ["VpnIkeV2Utils\\.java", "VpnProfileStore\\.java"]
}
],
"presubmit-large": [
@@ -26,5 +26,10 @@
],
"file_patterns": ["Vpn\\.java", "VpnIkeV2Utils\\.java", "VpnProfileStore\\.java"]
}
+ ],
+ "postsubmit":[
+ {
+ "name":"FrameworksVpnTests"
+ }
]
}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/flags/services.aconfig b/services/core/java/com/android/server/flags/services.aconfig
new file mode 100644
index 0000000..10b5eff
--- /dev/null
+++ b/services/core/java/com/android/server/flags/services.aconfig
@@ -0,0 +1,8 @@
+package: "com.android.server.flags"
+
+flag {
+ namespace: "wear_frameworks"
+ name: "enable_odp_feature_guard"
+ description: "Enable guard based on system feature to prevent OnDevicePersonalization service from starting on form factors."
+ bug: "322249125"
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index a79e771..05b1cb69 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -54,6 +54,7 @@
import android.hardware.input.InputSensorInfo;
import android.hardware.input.InputSettings;
import android.hardware.input.KeyboardLayout;
+import android.hardware.input.KeyboardLayoutSelectionResult;
import android.hardware.input.TouchCalibration;
import android.hardware.lights.Light;
import android.hardware.lights.LightState;
@@ -1244,9 +1245,9 @@
}
@Override // Binder call
- public String getKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
- @UserIdInt int userId, @NonNull InputMethodInfo imeInfo,
- @Nullable InputMethodSubtype imeSubtype) {
+ public KeyboardLayoutSelectionResult getKeyboardLayoutForInputDevice(
+ InputDeviceIdentifier identifier, @UserIdInt int userId,
+ @NonNull InputMethodInfo imeInfo, @Nullable InputMethodSubtype imeSubtype) {
return mKeyboardLayoutManager.getKeyboardLayoutForInputDevice(identifier, userId,
imeInfo, imeSubtype);
}
diff --git a/services/core/java/com/android/server/input/KeyboardLayoutManager.java b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
index 46668de..283e692 100644
--- a/services/core/java/com/android/server/input/KeyboardLayoutManager.java
+++ b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
@@ -16,10 +16,11 @@
package com.android.server.input;
-import static com.android.server.input.KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEFAULT;
-import static com.android.server.input.KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEVICE;
-import static com.android.server.input.KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_USER;
-import static com.android.server.input.KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD;
+import static android.hardware.input.KeyboardLayoutSelectionResult.FAILED;
+import static android.hardware.input.KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_USER;
+import static android.hardware.input.KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_DEVICE;
+import static android.hardware.input.KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD;
+import static android.hardware.input.KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_DEFAULT;
import android.annotation.AnyThread;
import android.annotation.MainThread;
@@ -46,6 +47,7 @@
import android.hardware.input.InputDeviceIdentifier;
import android.hardware.input.InputManager;
import android.hardware.input.KeyboardLayout;
+import android.hardware.input.KeyboardLayoutSelectionResult;
import android.icu.lang.UScript;
import android.icu.util.ULocale;
import android.os.Bundle;
@@ -79,7 +81,6 @@
import com.android.server.LocalServices;
import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
import com.android.server.input.KeyboardMetricsCollector.KeyboardConfigurationEvent;
-import com.android.server.input.KeyboardMetricsCollector.LayoutSelectionCriteria;
import com.android.server.inputmethod.InputMethodManagerInternal;
import libcore.io.Streams;
@@ -130,7 +131,8 @@
// This cache stores "best-matched" layouts so that we don't need to run the matching
// algorithm repeatedly.
@GuardedBy("mKeyboardLayoutCache")
- private final Map<String, KeyboardLayoutInfo> mKeyboardLayoutCache = new ArrayMap<>();
+ private final Map<String, KeyboardLayoutSelectionResult> mKeyboardLayoutCache =
+ new ArrayMap<>();
private final Object mImeInfoLock = new Object();
@Nullable
@GuardedBy("mImeInfoLock")
@@ -222,17 +224,17 @@
} else {
Set<String> selectedLayouts = new HashSet<>();
List<ImeInfo> imeInfoList = getImeInfoListForLayoutMapping();
- List<KeyboardLayoutInfo> layoutInfoList = new ArrayList<>();
+ List<KeyboardLayoutSelectionResult> resultList = new ArrayList<>();
boolean hasMissingLayout = false;
for (ImeInfo imeInfo : imeInfoList) {
// Check if the layout has been previously configured
- KeyboardLayoutInfo layoutInfo = getKeyboardLayoutForInputDeviceInternal(
+ KeyboardLayoutSelectionResult result = getKeyboardLayoutForInputDeviceInternal(
keyboardIdentifier, imeInfo);
- boolean noLayoutFound = layoutInfo == null || layoutInfo.mDescriptor == null;
+ boolean noLayoutFound = result.getLayoutDescriptor() == null;
if (!noLayoutFound) {
- selectedLayouts.add(layoutInfo.mDescriptor);
+ selectedLayouts.add(result.getLayoutDescriptor());
}
- layoutInfoList.add(layoutInfo);
+ resultList.add(result);
hasMissingLayout |= noLayoutFound;
}
@@ -260,7 +262,7 @@
}
if (shouldLogConfiguration) {
- logKeyboardConfigurationEvent(inputDevice, imeInfoList, layoutInfoList,
+ logKeyboardConfigurationEvent(inputDevice, imeInfoList, resultList,
!mDataStore.hasInputDeviceEntry(key));
}
} finally {
@@ -757,10 +759,10 @@
String keyboardLayoutDescriptor;
if (useNewSettingsUi()) {
synchronized (mImeInfoLock) {
- KeyboardLayoutInfo layoutInfo = getKeyboardLayoutForInputDeviceInternal(
+ KeyboardLayoutSelectionResult result = getKeyboardLayoutForInputDeviceInternal(
new KeyboardIdentifier(identifier, languageTag, layoutType),
mCurrentImeInfo);
- keyboardLayoutDescriptor = layoutInfo == null ? null : layoutInfo.mDescriptor;
+ keyboardLayoutDescriptor = result.getLayoutDescriptor();
}
} else {
keyboardLayoutDescriptor = getCurrentKeyboardLayoutForInputDevice(identifier);
@@ -788,26 +790,26 @@
}
@AnyThread
- @Nullable
- public String getKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
- @UserIdInt int userId, @NonNull InputMethodInfo imeInfo,
- @Nullable InputMethodSubtype imeSubtype) {
+ @NonNull
+ public KeyboardLayoutSelectionResult getKeyboardLayoutForInputDevice(
+ InputDeviceIdentifier identifier, @UserIdInt int userId,
+ @NonNull InputMethodInfo imeInfo, @Nullable InputMethodSubtype imeSubtype) {
if (!useNewSettingsUi()) {
Slog.e(TAG, "getKeyboardLayoutForInputDevice() API not supported");
- return null;
+ return FAILED;
}
InputDevice inputDevice = getInputDevice(identifier);
if (inputDevice == null || inputDevice.isVirtual() || !inputDevice.isFullKeyboard()) {
- return null;
+ return FAILED;
}
KeyboardIdentifier keyboardIdentifier = new KeyboardIdentifier(inputDevice);
- KeyboardLayoutInfo layoutInfo = getKeyboardLayoutForInputDeviceInternal(
+ KeyboardLayoutSelectionResult result = getKeyboardLayoutForInputDeviceInternal(
keyboardIdentifier, new ImeInfo(userId, imeInfo, imeSubtype));
if (DEBUG) {
Slog.d(TAG, "getKeyboardLayoutForInputDevice() " + identifier.toString() + ", userId : "
- + userId + ", subtype = " + imeSubtype + " -> " + layoutInfo);
+ + userId + ", subtype = " + imeSubtype + " -> " + result);
}
- return layoutInfo != null ? layoutInfo.mDescriptor : null;
+ return result;
}
@AnyThread
@@ -942,13 +944,13 @@
}
@Nullable
- private KeyboardLayoutInfo getKeyboardLayoutForInputDeviceInternal(
+ private KeyboardLayoutSelectionResult getKeyboardLayoutForInputDeviceInternal(
KeyboardIdentifier keyboardIdentifier, @Nullable ImeInfo imeInfo) {
String layoutKey = new LayoutKey(keyboardIdentifier, imeInfo).toString();
synchronized (mDataStore) {
String layout = mDataStore.getKeyboardLayout(keyboardIdentifier.toString(), layoutKey);
if (layout != null) {
- return new KeyboardLayoutInfo(layout, LAYOUT_SELECTION_CRITERIA_USER);
+ return new KeyboardLayoutSelectionResult(layout, LAYOUT_SELECTION_CRITERIA_USER);
}
}
@@ -961,17 +963,17 @@
KeyboardLayout[] layoutList = getKeyboardLayoutListForInputDeviceInternal(
keyboardIdentifier, imeInfo);
// Call auto-matching algorithm to find the best matching layout
- KeyboardLayoutInfo layoutInfo =
+ KeyboardLayoutSelectionResult result =
getDefaultKeyboardLayoutBasedOnImeInfo(keyboardIdentifier, imeInfo,
layoutList);
- mKeyboardLayoutCache.put(layoutKey, layoutInfo);
- return layoutInfo;
+ mKeyboardLayoutCache.put(layoutKey, result);
+ return result;
}
}
}
- @Nullable
- private static KeyboardLayoutInfo getDefaultKeyboardLayoutBasedOnImeInfo(
+ @NonNull
+ private static KeyboardLayoutSelectionResult getDefaultKeyboardLayoutBasedOnImeInfo(
KeyboardIdentifier keyboardIdentifier, @Nullable ImeInfo imeInfo,
KeyboardLayout[] layoutList) {
Arrays.sort(layoutList);
@@ -986,7 +988,7 @@
+ "vendor and product Ids. " + keyboardIdentifier
+ " : " + layout.getDescriptor());
}
- return new KeyboardLayoutInfo(layout.getDescriptor(),
+ return new KeyboardLayoutSelectionResult(layout.getDescriptor(),
LAYOUT_SELECTION_CRITERIA_DEVICE);
}
}
@@ -1004,13 +1006,14 @@
+ "HW information (Language tag and Layout type). "
+ keyboardIdentifier + " : " + layoutDesc);
}
- return new KeyboardLayoutInfo(layoutDesc, LAYOUT_SELECTION_CRITERIA_DEVICE);
+ return new KeyboardLayoutSelectionResult(layoutDesc,
+ LAYOUT_SELECTION_CRITERIA_DEVICE);
}
}
if (imeInfo == null || imeInfo.mImeSubtypeHandle == null || imeInfo.mImeSubtype == null) {
// Can't auto select layout based on IME info is null
- return null;
+ return FAILED;
}
InputMethodSubtype subtype = imeInfo.mImeSubtype;
@@ -1027,9 +1030,10 @@
+ layoutDesc);
}
if (layoutDesc != null) {
- return new KeyboardLayoutInfo(layoutDesc, LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD);
+ return new KeyboardLayoutSelectionResult(layoutDesc,
+ LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD);
}
- return null;
+ return FAILED;
}
@Nullable
@@ -1246,21 +1250,23 @@
}
private void logKeyboardConfigurationEvent(@NonNull InputDevice inputDevice,
- @NonNull List<ImeInfo> imeInfoList, @NonNull List<KeyboardLayoutInfo> layoutInfoList,
+ @NonNull List<ImeInfo> imeInfoList,
+ @NonNull List<KeyboardLayoutSelectionResult> resultList,
boolean isFirstConfiguration) {
- if (imeInfoList.isEmpty() || layoutInfoList.isEmpty()) {
+ if (imeInfoList.isEmpty() || resultList.isEmpty()) {
return;
}
KeyboardConfigurationEvent.Builder configurationEventBuilder =
new KeyboardConfigurationEvent.Builder(inputDevice).setIsFirstTimeConfiguration(
isFirstConfiguration);
for (int i = 0; i < imeInfoList.size(); i++) {
- KeyboardLayoutInfo layoutInfo = layoutInfoList.get(i);
+ KeyboardLayoutSelectionResult result = resultList.get(i);
String layoutName = null;
int layoutSelectionCriteria = LAYOUT_SELECTION_CRITERIA_DEFAULT;
- if (layoutInfo != null && layoutInfo.mDescriptor != null) {
- layoutSelectionCriteria = layoutInfo.mSelectionCriteria;
- KeyboardLayoutDescriptor d = KeyboardLayoutDescriptor.parse(layoutInfo.mDescriptor);
+ if (result != null && result.getLayoutDescriptor() != null) {
+ layoutSelectionCriteria = result.getSelectionCriteria();
+ KeyboardLayoutDescriptor d = KeyboardLayoutDescriptor.parse(
+ result.getLayoutDescriptor());
if (d != null) {
layoutName = d.keyboardLayoutName;
}
@@ -1477,33 +1483,6 @@
}
}
- private static class KeyboardLayoutInfo {
- @Nullable
- private final String mDescriptor;
- @LayoutSelectionCriteria
- private final int mSelectionCriteria;
-
- private KeyboardLayoutInfo(@Nullable String descriptor,
- @LayoutSelectionCriteria int selectionCriteria) {
- mDescriptor = descriptor;
- mSelectionCriteria = selectionCriteria;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (obj instanceof KeyboardLayoutInfo) {
- return Objects.equals(mDescriptor, ((KeyboardLayoutInfo) obj).mDescriptor)
- && mSelectionCriteria == ((KeyboardLayoutInfo) obj).mSelectionCriteria;
- }
- return false;
- }
-
- @Override
- public int hashCode() {
- return 31 * mSelectionCriteria + mDescriptor.hashCode();
- }
- }
-
private interface KeyboardLayoutVisitor {
void visitKeyboardLayout(Resources resources,
int keyboardLayoutResId, KeyboardLayout layout);
diff --git a/services/core/java/com/android/server/input/KeyboardMetricsCollector.java b/services/core/java/com/android/server/input/KeyboardMetricsCollector.java
index f53b941..b8ae737 100644
--- a/services/core/java/com/android/server/input/KeyboardMetricsCollector.java
+++ b/services/core/java/com/android/server/input/KeyboardMetricsCollector.java
@@ -16,14 +16,18 @@
package com.android.server.input;
-import static java.lang.annotation.RetentionPolicy.SOURCE;
+import static android.hardware.input.KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_USER;
+import static android.hardware.input.KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_DEVICE;
+import static android.hardware.input.KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD;
+import static android.hardware.input.KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_DEFAULT;
+import static android.hardware.input.KeyboardLayoutSelectionResult.layoutSelectionCriteriaToString;
-import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.role.RoleManager;
import android.content.Intent;
import android.hardware.input.KeyboardLayout;
+import android.hardware.input.KeyboardLayoutSelectionResult.LayoutSelectionCriteria;
import android.icu.util.ULocale;
import android.text.TextUtils;
import android.util.Log;
@@ -40,7 +44,6 @@
import com.android.internal.util.FrameworkStatsLog;
import com.android.server.policy.ModifierShortcutManager;
-import java.lang.annotation.Retention;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -57,32 +60,6 @@
// (requires restart)
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
- @Retention(SOURCE)
- @IntDef(prefix = {"LAYOUT_SELECTION_CRITERIA_"}, value = {
- LAYOUT_SELECTION_CRITERIA_UNSPECIFIED,
- LAYOUT_SELECTION_CRITERIA_USER,
- LAYOUT_SELECTION_CRITERIA_DEVICE,
- LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD,
- LAYOUT_SELECTION_CRITERIA_DEFAULT
- })
- public @interface LayoutSelectionCriteria {
- }
-
- /** Unspecified layout selection criteria */
- public static final int LAYOUT_SELECTION_CRITERIA_UNSPECIFIED = 0;
-
- /** Manual selection by user */
- public static final int LAYOUT_SELECTION_CRITERIA_USER = 1;
-
- /** Auto-detection based on device provided language tag and layout type */
- public static final int LAYOUT_SELECTION_CRITERIA_DEVICE = 2;
-
- /** Auto-detection based on IME provided language tag and layout type */
- public static final int LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD = 3;
-
- /** Default selection */
- public static final int LAYOUT_SELECTION_CRITERIA_DEFAULT = 4;
-
@VisibleForTesting
static final String DEFAULT_LAYOUT_NAME = "Default";
@@ -629,30 +606,16 @@
@Override
public String toString() {
- return "{keyboardLanguageTag = " + keyboardLanguageTag + " keyboardLayoutType = "
+ return "{keyboardLanguageTag = " + keyboardLanguageTag
+ + " keyboardLayoutType = "
+ KeyboardLayout.LayoutType.getLayoutNameFromValue(keyboardLayoutType)
- + " keyboardLayoutName = " + keyboardLayoutName + " layoutSelectionCriteria = "
- + getStringForSelectionCriteria(layoutSelectionCriteria)
- + "imeLanguageTag = " + imeLanguageTag + " imeLayoutType = "
- + KeyboardLayout.LayoutType.getLayoutNameFromValue(imeLayoutType) + "}";
- }
- }
-
- private static String getStringForSelectionCriteria(
- @LayoutSelectionCriteria int layoutSelectionCriteria) {
- switch (layoutSelectionCriteria) {
- case LAYOUT_SELECTION_CRITERIA_UNSPECIFIED:
- return "LAYOUT_SELECTION_CRITERIA_UNSPECIFIED";
- case LAYOUT_SELECTION_CRITERIA_USER:
- return "LAYOUT_SELECTION_CRITERIA_USER";
- case LAYOUT_SELECTION_CRITERIA_DEVICE:
- return "LAYOUT_SELECTION_CRITERIA_DEVICE";
- case LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD:
- return "LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD";
- case LAYOUT_SELECTION_CRITERIA_DEFAULT:
- return "LAYOUT_SELECTION_CRITERIA_DEFAULT";
- default:
- return "INVALID_CRITERIA";
+ + " keyboardLayoutName = " + keyboardLayoutName
+ + " layoutSelectionCriteria = "
+ + layoutSelectionCriteriaToString(layoutSelectionCriteria)
+ + " imeLanguageTag = " + imeLanguageTag
+ + " imeLayoutType = " + KeyboardLayout.LayoutType.getLayoutNameFromValue(
+ imeLayoutType)
+ + "}";
}
}
diff --git a/services/core/java/com/android/server/input/debug/FocusEventDebugView.java b/services/core/java/com/android/server/input/debug/FocusEventDebugView.java
index b30f5ec..6eae9a4 100644
--- a/services/core/java/com/android/server/input/debug/FocusEventDebugView.java
+++ b/services/core/java/com/android/server/input/debug/FocusEventDebugView.java
@@ -229,7 +229,8 @@
/** Report a key event to the debug view. */
@AnyThread
public void reportKeyEvent(KeyEvent event) {
- post(() -> handleKeyEvent(KeyEvent.obtain((KeyEvent) event)));
+ KeyEvent keyEvent = KeyEvent.obtain(event);
+ post(() -> handleKeyEvent(keyEvent));
}
/** Report a motion event to the debug view. */
diff --git a/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java b/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java
index 8826e3d..73f1aad 100644
--- a/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java
+++ b/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java
@@ -91,10 +91,10 @@
if (DEBUG_IME_VISIBILITY) {
EventLog.writeEvent(IMF_SHOW_IME,
statsToken != null ? statsToken.getTag() : ImeTracker.TOKEN_NONE,
- Objects.toString(mService.mCurFocusedWindow),
+ Objects.toString(mService.mImeBindingState.mFocusedWindow),
InputMethodDebug.softInputDisplayReasonToString(reason),
InputMethodDebug.softInputModeToString(
- mService.mCurFocusedWindowSoftInputMode));
+ mService.mImeBindingState.mFocusedWindowSoftInputMode));
}
mService.onShowHideSoftInputRequested(true /* show */, showInputToken, reason,
statsToken);
@@ -122,10 +122,10 @@
if (DEBUG_IME_VISIBILITY) {
EventLog.writeEvent(IMF_HIDE_IME,
statsToken != null ? statsToken.getTag() : ImeTracker.TOKEN_NONE,
- Objects.toString(mService.mCurFocusedWindow),
+ Objects.toString(mService.mImeBindingState.mFocusedWindow),
InputMethodDebug.softInputDisplayReasonToString(reason),
InputMethodDebug.softInputModeToString(
- mService.mCurFocusedWindowSoftInputMode));
+ mService.mImeBindingState.mFocusedWindowSoftInputMode));
}
mService.onShowHideSoftInputRequested(false /* show */, hideInputToken, reason,
statsToken);
@@ -207,7 +207,8 @@
@Override
public boolean removeImeScreenshot(int displayId) {
if (mImeTargetVisibilityPolicy.removeImeScreenshot(displayId)) {
- mService.onShowHideSoftInputRequested(false /* show */, mService.mCurFocusedWindow,
+ mService.onShowHideSoftInputRequested(false /* show */,
+ mService.mImeBindingState.mFocusedWindow,
REMOVE_IME_SCREENSHOT_FROM_IMMS, null /* statsToken */);
return true;
}
diff --git a/services/core/java/com/android/server/inputmethod/ImeBindingState.java b/services/core/java/com/android/server/inputmethod/ImeBindingState.java
new file mode 100644
index 0000000..4c20c3b
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/ImeBindingState.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2024 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.inputmethod;
+
+import static android.server.inputmethod.InputMethodManagerServiceProto.CUR_FOCUSED_WINDOW_NAME;
+import static android.server.inputmethod.InputMethodManagerServiceProto.CUR_FOCUSED_WINDOW_SOFT_INPUT_MODE;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED;
+
+import android.annotation.Nullable;
+import android.os.IBinder;
+import android.util.Printer;
+import android.util.proto.ProtoOutputStream;
+import android.view.WindowManager;
+import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
+import android.view.inputmethod.EditorInfo;
+
+import com.android.internal.inputmethod.InputMethodDebug;
+import com.android.server.wm.WindowManagerInternal;
+
+/**
+ * Stores information related to one active IME client on one display.
+ */
+final class ImeBindingState {
+
+ /**
+ * The last window token that we confirmed to be focused. This is always updated upon
+ * reports from the input method client. If the window state is already changed before the
+ * report is handled, this field just keeps the last value.
+ */
+ @Nullable
+ final IBinder mFocusedWindow;
+
+ /**
+ * {@link WindowManager.LayoutParams#softInputMode} of {@link #mFocusedWindow}.
+ *
+ * @see #mFocusedWindow
+ */
+ @SoftInputModeFlags
+ final int mFocusedWindowSoftInputMode;
+
+ /**
+ * The client by which {@link #mFocusedWindow} was reported. This gets updated whenever
+ * an
+ * IME-focusable window gained focus (without necessarily starting an input connection),
+ * while {@link InputMethodManagerService#mClient} only gets updated when we actually start an
+ * input connection.
+ *
+ * @see #mFocusedWindow
+ */
+ @Nullable
+ final ClientState mFocusedWindowClient;
+
+ /**
+ * The editor info by which {@link #mFocusedWindow} was reported. This differs from
+ * {@link InputMethodManagerService#mCurEditorInfo} the same way {@link #mFocusedWindowClient}
+ * differs from {@link InputMethodManagerService#mCurClient}.
+ *
+ * @see #mFocusedWindow
+ */
+ @Nullable
+ final EditorInfo mFocusedWindowEditorInfo;
+
+ void dumpDebug(ProtoOutputStream proto, WindowManagerInternal windowManagerInternal) {
+ proto.write(CUR_FOCUSED_WINDOW_NAME,
+ windowManagerInternal.getWindowName(mFocusedWindow));
+ proto.write(CUR_FOCUSED_WINDOW_SOFT_INPUT_MODE,
+ InputMethodDebug.softInputModeToString(mFocusedWindowSoftInputMode));
+ }
+
+ void dump(String prefix, Printer p) {
+ p.println(prefix + "mFocusedWindow()=" + mFocusedWindow);
+ p.println(prefix + "softInputMode=" + InputMethodDebug.softInputModeToString(
+ mFocusedWindowSoftInputMode));
+ p.println(prefix + "mFocusedWindowClient=" + mFocusedWindowClient);
+ }
+
+ static ImeBindingState newEmptyState() {
+ return new ImeBindingState(
+ /*focusedWindow=*/ null,
+ /*focusedWindowSoftInputMode=*/ SOFT_INPUT_STATE_UNSPECIFIED,
+ /*focusedWindowClient=*/ null,
+ /*focusedWindowEditorInfo=*/ null
+ );
+ }
+
+ ImeBindingState(@Nullable IBinder focusedWindow,
+ @SoftInputModeFlags int focusedWindowSoftInputMode,
+ @Nullable ClientState focusedWindowClient,
+ @Nullable EditorInfo focusedWindowEditorInfo) {
+ mFocusedWindow = focusedWindow;
+ mFocusedWindowSoftInputMode = focusedWindowSoftInputMode;
+ mFocusedWindowClient = focusedWindowClient;
+ mFocusedWindowEditorInfo = focusedWindowEditorInfo;
+ }
+}
diff --git a/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java b/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java
index 743b8e3..cdfde87 100644
--- a/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java
+++ b/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java
@@ -548,7 +548,7 @@
}
}
// Fallback to the focused window for some edge cases (e.g. relaunching the activity)
- return mService.mCurFocusedWindow;
+ return mService.mImeBindingState.mFocusedWindow;
}
IBinder getWindowTokenFrom(ImeTargetWindowState windowState) {
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 1f3c2f3..2205986 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -28,7 +28,6 @@
import static android.server.inputmethod.InputMethodManagerServiceProto.BOUND_TO_METHOD;
import static android.server.inputmethod.InputMethodManagerServiceProto.CUR_ATTRIBUTE;
import static android.server.inputmethod.InputMethodManagerServiceProto.CUR_CLIENT;
-import static android.server.inputmethod.InputMethodManagerServiceProto.CUR_FOCUSED_WINDOW_NAME;
import static android.server.inputmethod.InputMethodManagerServiceProto.CUR_FOCUSED_WINDOW_SOFT_INPUT_MODE;
import static android.server.inputmethod.InputMethodManagerServiceProto.CUR_ID;
import static android.server.inputmethod.InputMethodManagerServiceProto.CUR_METHOD_ID;
@@ -40,7 +39,6 @@
import static android.server.inputmethod.InputMethodManagerServiceProto.IN_FULLSCREEN_MODE;
import static android.server.inputmethod.InputMethodManagerServiceProto.IS_INTERACTIVE;
import static android.server.inputmethod.InputMethodManagerServiceProto.LAST_IME_TARGET_WINDOW_NAME;
-import static android.server.inputmethod.InputMethodManagerServiceProto.LAST_SWITCH_USER_ID;
import static android.server.inputmethod.InputMethodManagerServiceProto.SHOW_IME_WITH_HARD_KEYBOARD;
import static android.server.inputmethod.InputMethodManagerServiceProto.SYSTEM_READY;
import static android.view.Display.DEFAULT_DISPLAY;
@@ -277,9 +275,6 @@
@NonNull
private final String[] mNonPreemptibleInputMethods;
- @UserIdInt
- private int mLastSwitchUserId;
-
final Context mContext;
final Resources mRes;
private final Handler mHandler;
@@ -423,6 +418,11 @@
private final ClientController mClientController;
/**
+ * Holds the current IME binding state info.
+ */
+ ImeBindingState mImeBindingState;
+
+ /**
* Set once the system is ready to run third party code.
*/
boolean mSystemReady;
@@ -482,13 +482,6 @@
private ClientState mCurClient;
/**
- * The last window token that we confirmed to be focused. This is always updated upon reports
- * from the input method client. If the window state is already changed before the report is
- * handled, this field just keeps the last value.
- */
- IBinder mCurFocusedWindow;
-
- /**
* The last window token that we confirmed that IME started talking to. This is always updated
* upon reports from the input method. If the window state is already changed before the report
* is handled, this field just keeps the last value.
@@ -496,34 +489,6 @@
IBinder mLastImeTargetWindow;
/**
- * {@link LayoutParams#softInputMode} of {@link #mCurFocusedWindow}.
- *
- * @see #mCurFocusedWindow
- */
- @SoftInputModeFlags
- int mCurFocusedWindowSoftInputMode;
-
- /**
- * The client by which {@link #mCurFocusedWindow} was reported. This gets updated whenever an
- * IME-focusable window gained focus (without necessarily starting an input connection),
- * while {@link #mCurClient} only gets updated when we actually start an input connection.
- *
- * @see #mCurFocusedWindow
- */
- @Nullable
- ClientState mCurFocusedWindowClient;
-
- /**
- * The editor info by which {@link #mCurFocusedWindow} was reported. This differs from
- * {@link #mCurEditorInfo} the same way {@link #mCurFocusedWindowClient} differs
- * from {@link #mCurClient}.
- *
- * @see #mCurFocusedWindow
- */
- @Nullable
- EditorInfo mCurFocusedWindowEditorInfo;
-
- /**
* The {@link IRemoteInputConnection} last provided by the current client.
*/
IRemoteInputConnection mCurInputConnection;
@@ -1131,10 +1096,11 @@
mVisibilityStateComputer.getImePolicy().setA11yRequestNoSoftKeyboard(
accessibilitySoftKeyboardSetting);
if (mVisibilityStateComputer.getImePolicy().isA11yRequestNoSoftKeyboard()) {
- hideCurrentInputLocked(mCurFocusedWindow, 0 /* flags */,
+ hideCurrentInputLocked(mImeBindingState.mFocusedWindow, 0 /* flags */,
SoftInputShowHideReason.HIDE_SETTINGS_ON_CHANGE);
} else if (isShowRequestedForCurrentWindow()) {
- showCurrentInputLocked(mCurFocusedWindow, InputMethodManager.SHOW_IMPLICIT,
+ showCurrentInputLocked(mImeBindingState.mFocusedWindow,
+ InputMethodManager.SHOW_IMPLICIT,
SoftInputShowHideReason.SHOW_SETTINGS_ON_CHANGE);
}
} else if (stylusHandwritingEnabledUri.equals(uri)) {
@@ -1644,7 +1610,7 @@
}
// Hide soft input before user switch task since switch task may block main handler a while
// and delayed the hideCurrentInputLocked().
- hideCurrentInputLocked(mCurFocusedWindow, 0 /* flags */,
+ hideCurrentInputLocked(mImeBindingState.mFocusedWindow, 0 /* flags */,
SoftInputShowHideReason.HIDE_SWITCH_USER);
final UserSwitchHandlerTask task = new UserSwitchHandlerTask(this, userId,
clientToBeReset);
@@ -1694,8 +1660,6 @@
final int userId = mActivityManagerInternal.getCurrentUserId();
- mLastSwitchUserId = userId;
-
// mSettings should be created before buildInputMethodListLocked
mSettings = InputMethodSettings.createEmptyMap(userId);
@@ -1719,6 +1683,7 @@
mClientController = new ClientController(mPackageManagerInternal);
synchronized (ImfLock.class) {
mClientController.addClientControllerCallback(c -> onClientRemoved(c));
+ mImeBindingState = ImeBindingState.newEmptyState();
}
mPreventImeStartupUnlessTextEditor = mRes.getBoolean(
@@ -1881,7 +1846,6 @@
+ " selectedIme=" + mSettings.getSelectedInputMethod());
}
- mLastSwitchUserId = newUserId;
if (mIsInteractive && clientToBeReset != null) {
final ClientState cs = mClientController.getClient(clientToBeReset.asBinder());
if (cs == null) {
@@ -2233,7 +2197,7 @@
clearClientSessionLocked(client);
clearClientSessionForAccessibilityLocked(client);
if (mCurClient == client) {
- hideCurrentInputLocked(mCurFocusedWindow, 0 /* flags */,
+ hideCurrentInputLocked(mImeBindingState.mFocusedWindow, 0 /* flags */,
SoftInputShowHideReason.HIDE_REMOVE_CLIENT);
if (mBoundToMethod) {
mBoundToMethod = false;
@@ -2247,9 +2211,8 @@
}
mBoundToAccessibility = false;
mCurClient = null;
- if (mCurFocusedWindowClient == client) {
- mCurFocusedWindowClient = null;
- mCurFocusedWindowEditorInfo = null;
+ if (mImeBindingState.mFocusedWindowClient == client) {
+ mImeBindingState = ImeBindingState.newEmptyState();
}
}
}
@@ -2298,7 +2261,7 @@
@GuardedBy("ImfLock.class")
void onUnbindCurrentMethodByReset() {
final ImeTargetWindowState winState = mVisibilityStateComputer.getWindowStateOrNull(
- mCurFocusedWindow);
+ mImeBindingState.mFocusedWindow);
if (winState != null && !winState.isRequestedImeVisible()
&& !mVisibilityStateComputer.isInputShown()) {
// Normally, the focus window will apply the IME visibility state to
@@ -2310,7 +2273,8 @@
// binding states in the first place.
final var statsToken = createStatsTokenForFocusedClient(false /* show */,
SoftInputShowHideReason.UNBIND_CURRENT_METHOD);
- mVisibilityApplier.applyImeVisibility(mCurFocusedWindow, statsToken, STATE_HIDE_IME);
+ mVisibilityApplier.applyImeVisibility(mImeBindingState.mFocusedWindow, statsToken,
+ STATE_HIDE_IME);
}
}
@@ -2343,7 +2307,7 @@
@GuardedBy("ImfLock.class")
private boolean isShowRequestedForCurrentWindow() {
final ImeTargetWindowState state = mVisibilityStateComputer.getWindowStateOrNull(
- mCurFocusedWindow);
+ mImeBindingState.mFocusedWindow);
return state != null && state.isRequestedImeVisible();
}
@@ -2361,10 +2325,9 @@
getCurTokenLocked(),
mCurTokenDisplayId, getCurIdLocked(), startInputReason, restarting,
UserHandle.getUserId(mCurClient.mUid),
- mCurClient.mSelfReportedDisplayId,
- mCurFocusedWindow, mCurEditorInfo, mCurFocusedWindowSoftInputMode,
- getSequenceNumberLocked());
- mImeTargetWindowMap.put(startInputToken, mCurFocusedWindow);
+ mCurClient.mSelfReportedDisplayId, mImeBindingState.mFocusedWindow, mCurEditorInfo,
+ mImeBindingState.mFocusedWindowSoftInputMode, getSequenceNumberLocked());
+ mImeTargetWindowMap.put(startInputToken, mImeBindingState.mFocusedWindow);
mStartInputHistory.addEntry(info);
// Seems that PackageManagerInternal#grantImplicitAccess() doesn't handle cross-user
@@ -2391,7 +2354,7 @@
: createStatsTokenForFocusedClient(true /* show */,
SoftInputShowHideReason.ATTACH_NEW_INPUT);
mCurStatsToken = null;
- showCurrentInputLocked(mCurFocusedWindow, statsToken,
+ showCurrentInputLocked(mImeBindingState.mFocusedWindow, statsToken,
mVisibilityStateComputer.getShowFlags(), MotionEvent.TOOL_TYPE_UNKNOWN,
null /* resultReceiver */, SoftInputShowHideReason.ATTACH_NEW_INPUT);
}
@@ -2465,7 +2428,7 @@
// Compute the final shown display ID with validated cs.selfReportedDisplayId for this
// session & other conditions.
ImeTargetWindowState winState = mVisibilityStateComputer.getWindowStateOrNull(
- mCurFocusedWindow);
+ mImeBindingState.mFocusedWindow);
if (winState == null) {
return InputBindResult.NOT_IME_TARGET_WINDOW;
}
@@ -2484,7 +2447,7 @@
}
if (mVisibilityStateComputer.getImePolicy().isImeHiddenByDisplayPolicy()) {
- hideCurrentInputLocked(mCurFocusedWindow, 0 /* flags */,
+ hideCurrentInputLocked(mImeBindingState.mFocusedWindow, 0 /* flags */,
SoftInputShowHideReason.HIDE_DISPLAY_IME_POLICY_HIDE);
return InputBindResult.NO_IME;
}
@@ -3653,7 +3616,8 @@
Binder.withCleanCallingIdentity(() -> {
Objects.requireNonNull(windowToken, "windowToken must not be null");
synchronized (ImfLock.class) {
- if (mCurFocusedWindow != windowToken || mCurPerceptible == perceptible) {
+ if (mImeBindingState.mFocusedWindow != windowToken
+ || mCurPerceptible == perceptible) {
return;
}
mCurPerceptible = perceptible;
@@ -3747,7 +3711,7 @@
super.hideSoftInputFromServerForTest_enforcePermission();
synchronized (ImfLock.class) {
- hideCurrentInputLocked(mCurFocusedWindow, 0 /* flags */,
+ hideCurrentInputLocked(mImeBindingState.mFocusedWindow, 0 /* flags */,
SoftInputShowHideReason.HIDE_SOFT_INPUT);
}
}
@@ -3922,7 +3886,8 @@
final boolean shouldClearFlag =
mImePlatformCompatUtils.shouldClearShowForcedFlag(cs.mUid);
final boolean showForced = mVisibilityStateComputer.mShowForced;
- if (mCurFocusedWindow != windowToken && showForced && shouldClearFlag) {
+ if (mImeBindingState.mFocusedWindow != windowToken
+ && showForced && shouldClearFlag) {
mVisibilityStateComputer.mShowForced = false;
}
@@ -3940,7 +3905,7 @@
Slog.w(TAG, "If you need to impersonate a foreground user/profile from"
+ " a background user, use EditorInfo.targetInputMethodUser with"
+ " INTERACT_ACROSS_USERS_FULL permission.");
- hideCurrentInputLocked(mCurFocusedWindow, 0 /* flags */,
+ hideCurrentInputLocked(mImeBindingState.mFocusedWindow, 0 /* flags */,
SoftInputShowHideReason.HIDE_INVALID_USER);
return InputBindResult.INVALID_USER;
}
@@ -4001,7 +3966,7 @@
+ " cs=" + cs);
}
- final boolean sameWindowFocused = mCurFocusedWindow == windowToken;
+ final boolean sameWindowFocused = mImeBindingState.mFocusedWindow == windowToken;
final boolean isTextEditor = (startInputFlags & StartInputFlags.IS_TEXT_EDITOR) != 0;
final boolean startInputByWinGainedFocus =
(startInputFlags & StartInputFlags.WINDOW_GAINED_FOCUS) != 0;
@@ -4032,10 +3997,7 @@
null, null, null, null, -1, false);
}
- mCurFocusedWindow = windowToken;
- mCurFocusedWindowSoftInputMode = softInputMode;
- mCurFocusedWindowClient = cs;
- mCurFocusedWindowEditorInfo = editorInfo;
+ mImeBindingState = new ImeBindingState(windowToken, softInputMode, cs, editorInfo);
mCurPerceptible = true;
// We want to start input before showing the IME, but after closing
@@ -4066,9 +4028,8 @@
break;
}
final var statsToken = createStatsTokenForFocusedClient(isShow, imeVisRes.getReason());
- mVisibilityApplier.applyImeVisibility(mCurFocusedWindow, statsToken,
+ mVisibilityApplier.applyImeVisibility(mImeBindingState.mFocusedWindow, statsToken,
imeVisRes.getState(), imeVisRes.getReason());
-
if (imeVisRes.getReason() == SoftInputShowHideReason.HIDE_UNSPECIFIED_WINDOW) {
// If focused display changed, we should unbind current method
// to make app window in previous display relayout after Ime
@@ -4107,7 +4068,7 @@
throw new IllegalArgumentException("unknown client " + client.asBinder());
}
ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_SERVER_CLIENT_KNOWN);
- if (!isImeClientFocused(mCurFocusedWindow, cs)) {
+ if (!isImeClientFocused(mImeBindingState.mFocusedWindow, cs)) {
Slog.w(TAG, String.format("Ignoring %s of uid %d : %s", methodName, uid, client));
return false;
}
@@ -4119,8 +4080,8 @@
@GuardedBy("ImfLock.class")
private boolean canShowInputMethodPickerLocked(IInputMethodClient client) {
final int uid = Binder.getCallingUid();
- if (mCurFocusedWindowClient != null && client != null
- && mCurFocusedWindowClient.mClient.asBinder() == client.asBinder()) {
+ if (mImeBindingState.mFocusedWindowClient != null && client != null
+ && mImeBindingState.mFocusedWindowClient.mClient.asBinder() == client.asBinder()) {
return true;
}
if (mSettings.getUserId() != UserHandle.getUserId(uid)) {
@@ -4743,12 +4704,11 @@
proto.write(CUR_METHOD_ID, getSelectedMethodIdLocked());
proto.write(CUR_SEQ, getSequenceNumberLocked());
proto.write(CUR_CLIENT, Objects.toString(mCurClient));
- proto.write(CUR_FOCUSED_WINDOW_NAME,
- mWindowManagerInternal.getWindowName(mCurFocusedWindow));
+ mImeBindingState.dumpDebug(proto, mWindowManagerInternal);
proto.write(LAST_IME_TARGET_WINDOW_NAME,
mWindowManagerInternal.getWindowName(mLastImeTargetWindow));
- proto.write(CUR_FOCUSED_WINDOW_SOFT_INPUT_MODE,
- InputMethodDebug.softInputModeToString(mCurFocusedWindowSoftInputMode));
+ proto.write(CUR_FOCUSED_WINDOW_SOFT_INPUT_MODE, InputMethodDebug.softInputModeToString(
+ mImeBindingState.mFocusedWindowSoftInputMode));
if (mCurEditorInfo != null) {
mCurEditorInfo.dumpDebug(proto, CUR_ATTRIBUTE);
}
@@ -4758,7 +4718,6 @@
proto.write(CUR_TOKEN, Objects.toString(getCurTokenLocked()));
proto.write(CUR_TOKEN_DISPLAY_ID, mCurTokenDisplayId);
proto.write(SYSTEM_READY, mSystemReady);
- proto.write(LAST_SWITCH_USER_ID, mLastSwitchUserId);
proto.write(HAVE_CONNECTION, hasConnectionLocked());
proto.write(BOUND_TO_METHOD, mBoundToMethod);
proto.write(IS_INTERACTIVE, mIsInteractive);
@@ -4870,12 +4829,12 @@
final IBinder requestToken = mVisibilityStateComputer.getWindowTokenFrom(requestImeToken);
final WindowManagerInternal.ImeTargetInfo info =
mWindowManagerInternal.onToggleImeRequested(
- show, mCurFocusedWindow, requestToken, mCurTokenDisplayId);
+ show, mImeBindingState.mFocusedWindow, requestToken, mCurTokenDisplayId);
mSoftInputShowHideHistory.addEntry(new SoftInputShowHideHistory.Entry(
- mCurFocusedWindowClient, mCurFocusedWindowEditorInfo, info.focusedWindowName,
- mCurFocusedWindowSoftInputMode, reason, mInFullscreenMode,
- info.requestWindowName, info.imeControlTargetName, info.imeLayerTargetName,
- info.imeSurfaceParentName));
+ mImeBindingState.mFocusedWindowClient, mImeBindingState.mFocusedWindowEditorInfo,
+ info.focusedWindowName, mImeBindingState.mFocusedWindowSoftInputMode, reason,
+ mInFullscreenMode, info.requestWindowName, info.imeControlTargetName,
+ info.imeLayerTargetName, info.imeSurfaceParentName));
if (statsToken != null) {
mImeTrackerService.onImmsUpdate(statsToken, info.requestWindowName);
@@ -5046,8 +5005,7 @@
case MSG_HIDE_ALL_INPUT_METHODS:
synchronized (ImfLock.class) {
@SoftInputShowHideReason final int reason = (int) msg.obj;
- hideCurrentInputLocked(mCurFocusedWindow, 0 /* flags */, reason);
-
+ hideCurrentInputLocked(mImeBindingState.mFocusedWindow, 0 /* flags */, reason);
}
return true;
case MSG_REMOVE_IME_SURFACE: {
@@ -5066,7 +5024,7 @@
IBinder windowToken = (IBinder) msg.obj;
synchronized (ImfLock.class) {
try {
- if (windowToken == mCurFocusedWindow
+ if (windowToken == mImeBindingState.mFocusedWindow
&& mEnabledSession != null && mEnabledSession.mSession != null) {
mEnabledSession.mSession.removeImeSurface();
}
@@ -5141,7 +5099,7 @@
case MSG_START_HANDWRITING:
synchronized (ImfLock.class) {
IInputMethodInvoker curMethod = getCurMethodLocked();
- if (curMethod == null || mCurFocusedWindow == null) {
+ if (curMethod == null || mImeBindingState.mFocusedWindow == null) {
return true;
}
final HandwritingModeController.HandwritingSession session =
@@ -5149,7 +5107,7 @@
msg.arg1 /*requestId*/,
msg.arg2 /*pid*/,
mBindingController.getCurMethodUid(),
- mCurFocusedWindow);
+ mImeBindingState.mFocusedWindow);
if (session == null) {
Slog.e(TAG,
"Failed to start handwriting session for requestId: " + msg.arg1);
@@ -5202,11 +5160,11 @@
// Handle IME visibility when interactive changed before finishing the input to
// ensure we preserve the last state as possible.
final ImeVisibilityResult imeVisRes = mVisibilityStateComputer.onInteractiveChanged(
- mCurFocusedWindow, interactive);
+ mImeBindingState.mFocusedWindow, interactive);
if (imeVisRes != null) {
// Pass in a null statsToken as the IME snapshot is not tracked by ImeTracker.
- mVisibilityApplier.applyImeVisibility(mCurFocusedWindow, null /* statsToken */,
- imeVisRes.getState(), imeVisRes.getReason());
+ mVisibilityApplier.applyImeVisibility(mImeBindingState.mFocusedWindow,
+ null /* statsToken */, imeVisRes.getState(), imeVisRes.getReason());
}
// Eligible IME processes use new "setInteractive" protocol.
mCurClient.mClient.setInteractive(mIsInteractive, mInFullscreenMode);
@@ -5896,7 +5854,7 @@
@Override
public void reportImeControl(@Nullable IBinder windowToken) {
synchronized (ImfLock.class) {
- if (mCurFocusedWindow != windowToken) {
+ if (mImeBindingState.mFocusedWindow != windowToken) {
// mCurPerceptible was set by the focused window, but it is no longer in
// control, so we reset mCurPerceptible.
mCurPerceptible = true;
@@ -5910,7 +5868,7 @@
// Hide the IME method menu only when the IME surface parent is changed by the
// input target changed, in case seeing the dialog dismiss flickering during
// the next focused window starting the input connection.
- if (mLastImeTargetWindow != mCurFocusedWindow) {
+ if (mLastImeTargetWindow != mImeBindingState.mFocusedWindow) {
mMenuController.hideInputMethodMenuLocked();
}
}
@@ -6204,11 +6162,7 @@
client = mCurClient;
p.println(" mCurClient=" + client + " mCurSeq=" + getSequenceNumberLocked());
p.println(" mCurPerceptible=" + mCurPerceptible);
- p.println(" mCurFocusedWindow=" + mCurFocusedWindow
- + " softInputMode="
- + InputMethodDebug.softInputModeToString(mCurFocusedWindowSoftInputMode)
- + " client=" + mCurFocusedWindowClient);
- focusedWindowClient = mCurFocusedWindowClient;
+ mImeBindingState.dump(" ", p);
p.println(" mCurId=" + getCurIdLocked() + " mHaveConnection=" + hasConnectionLocked()
+ " mBoundToMethod=" + mBoundToMethod + " mVisibleBound="
+ mBindingController.isVisibleBound());
@@ -6259,7 +6213,8 @@
p.println("No input method client.");
}
- if (focusedWindowClient != null && client != focusedWindowClient) {
+ if (mImeBindingState.mFocusedWindowClient != null
+ && client != mImeBindingState.mFocusedWindowClient) {
p.println(" ");
p.println("Warning: Current input method client doesn't match the last focused. "
+ "window.");
@@ -6267,7 +6222,8 @@
p.println(" ");
pw.flush();
try {
- TransferPipe.dumpAsync(focusedWindowClient.mClient.asBinder(), fd, args);
+ TransferPipe.dumpAsync(
+ mImeBindingState.mFocusedWindowClient.mClient.asBinder(), fd, args);
} catch (IOException | RemoteException e) {
p.println("Failed to dump input method client in focused window: " + e);
}
@@ -6339,8 +6295,6 @@
@ShellCommandResult
private int onCommandWithSystemIdentity(@Nullable String cmd) {
switch (TextUtils.emptyIfNull(cmd)) {
- case "get-last-switch-user-id":
- return mService.getLastSwitchUserId(this);
case "tracing":
return mService.handleShellCommandTraceInputMethod(this);
case "ime": { // For "adb shell ime <command>".
@@ -6454,15 +6408,6 @@
// ----------------------------------------------------------------------
// Shell command handlers:
- @BinderThread
- @ShellCommandResult
- private int getLastSwitchUserId(@NonNull ShellCommand shellCommand) {
- synchronized (ImfLock.class) {
- shellCommand.getOutPrintWriter().println(mLastSwitchUserId);
- return ShellCommandResult.SUCCESS;
- }
- }
-
/**
* Handles {@code adb shell ime list}.
* @param shellCommand {@link ShellCommand} object that is handling this command.
@@ -6713,7 +6658,7 @@
final String nextIme;
final List<InputMethodInfo> nextEnabledImes;
if (userId == mSettings.getUserId()) {
- hideCurrentInputLocked(mCurFocusedWindow, 0 /* flags */,
+ hideCurrentInputLocked(mImeBindingState.mFocusedWindow, 0 /* flags */,
SoftInputShowHideReason.HIDE_RESET_SHELL_COMMAND);
mBindingController.unbindCurrentMethod();
@@ -6847,11 +6792,11 @@
@NonNull
private ImeTracker.Token createStatsTokenForFocusedClient(boolean show,
@SoftInputShowHideReason int reason) {
- final int uid = mCurFocusedWindowClient != null
- ? mCurFocusedWindowClient.mUid
+ final int uid = mImeBindingState.mFocusedWindowClient != null
+ ? mImeBindingState.mFocusedWindowClient.mUid
: -1;
- final var packageName = mCurFocusedWindowEditorInfo != null
- ? mCurFocusedWindowEditorInfo.packageName
+ final var packageName = mImeBindingState.mFocusedWindowEditorInfo != null
+ ? mImeBindingState.mFocusedWindowEditorInfo.packageName
: "uid(" + uid + ")";
return ImeTracker.forLogging().onStart(packageName, uid,
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 3a7ac0b..ba5882c 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -12050,11 +12050,12 @@
@Override
public void onServiceAdded(ManagedServiceInfo info) {
if (lifetimeExtensionRefactor()) {
- // Generally, only System or System UI should have the permissions to call
- // registerSystemService.
- // isCallerSystemorPhone tells us whether the caller is System. Then, if it's not
- // the system, we know it's system UI.
- info.isSystemUi = !isCallerSystemOrPhone();
+ // We explicitly check the status bar permission for the uid in the info object.
+ // We can't use the calling uid here because it's probably always system server.
+ // Note that this will also be true for the shell.
+ info.isSystemUi = getContext().checkPermission(
+ android.Manifest.permission.STATUS_BAR_SERVICE, -1, info.uid)
+ == PERMISSION_GRANTED;
}
final INotificationListener listener = (INotificationListener) info.service;
final NotificationRankingUpdate update;
diff --git a/services/core/java/com/android/server/notification/ZenAdapters.java b/services/core/java/com/android/server/notification/ZenAdapters.java
deleted file mode 100644
index 37b263c..0000000
--- a/services/core/java/com/android/server/notification/ZenAdapters.java
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright (C) 2023 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.notification;
-
-import android.app.Flags;
-import android.app.NotificationManager.Policy;
-import android.service.notification.ZenModeConfig;
-import android.service.notification.ZenPolicy;
-
-/**
- * Converters between different Zen representations.
- */
-class ZenAdapters {
-
- static ZenPolicy notificationPolicyToZenPolicy(Policy policy) {
- ZenPolicy.Builder zenPolicyBuilder = new ZenPolicy.Builder()
- .allowAlarms(policy.allowAlarms())
- .allowCalls(
- policy.allowCalls()
- ? ZenModeConfig.getZenPolicySenders(policy.allowCallsFrom())
- : ZenPolicy.PEOPLE_TYPE_NONE)
- .allowConversations(
- policy.allowConversations()
- ? notificationPolicyConversationSendersToZenPolicy(
- policy.allowConversationsFrom())
- : ZenPolicy.CONVERSATION_SENDERS_NONE)
- .allowEvents(policy.allowEvents())
- .allowMedia(policy.allowMedia())
- .allowMessages(
- policy.allowMessages()
- ? ZenModeConfig.getZenPolicySenders(policy.allowMessagesFrom())
- : ZenPolicy.PEOPLE_TYPE_NONE)
- .allowReminders(policy.allowReminders())
- .allowRepeatCallers(policy.allowRepeatCallers())
- .allowSystem(policy.allowSystem());
-
- if (policy.suppressedVisualEffects != Policy.SUPPRESSED_EFFECTS_UNSET) {
- zenPolicyBuilder.showBadges(policy.showBadges())
- .showFullScreenIntent(policy.showFullScreenIntents())
- .showInAmbientDisplay(policy.showAmbient())
- .showInNotificationList(policy.showInNotificationList())
- .showLights(policy.showLights())
- .showPeeking(policy.showPeeking())
- .showStatusBarIcons(policy.showStatusBarIcons());
- }
-
- if (Flags.modesApi()) {
- zenPolicyBuilder.allowPriorityChannels(policy.allowPriorityChannels());
- }
-
- return zenPolicyBuilder.build();
- }
-
- @ZenPolicy.ConversationSenders
- private static int notificationPolicyConversationSendersToZenPolicy(
- int npPriorityConversationSenders) {
- switch (npPriorityConversationSenders) {
- case Policy.CONVERSATION_SENDERS_ANYONE:
- return ZenPolicy.CONVERSATION_SENDERS_ANYONE;
- case Policy.CONVERSATION_SENDERS_IMPORTANT:
- return ZenPolicy.CONVERSATION_SENDERS_IMPORTANT;
- case Policy.CONVERSATION_SENDERS_NONE:
- return ZenPolicy.CONVERSATION_SENDERS_NONE;
- case Policy.CONVERSATION_SENDERS_UNSET:
- default:
- return ZenPolicy.CONVERSATION_SENDERS_UNSET;
- }
- }
-}
diff --git a/services/core/java/com/android/server/notification/ZenModeEventLogger.java b/services/core/java/com/android/server/notification/ZenModeEventLogger.java
index b9a267f..8e37b4f 100644
--- a/services/core/java/com/android/server/notification/ZenModeEventLogger.java
+++ b/services/core/java/com/android/server/notification/ZenModeEventLogger.java
@@ -32,6 +32,7 @@
import android.content.pm.PackageManager;
import android.os.Process;
import android.service.notification.DNDPolicyProto;
+import android.service.notification.ZenAdapters;
import android.service.notification.ZenModeConfig;
import android.service.notification.ZenModeConfig.ConfigChangeOrigin;
import android.service.notification.ZenModeConfig.ZenRule;
@@ -591,9 +592,11 @@
// This applies to both call and message senders, but not conversation senders,
// where they use the same enum values.
proto.write(DNDPolicyProto.ALLOW_CALLS_FROM,
- ZenModeConfig.getZenPolicySenders(mNewPolicy.allowCallsFrom()));
+ ZenAdapters.notificationPolicySendersToZenPolicyPeopleType(
+ mNewPolicy.allowCallsFrom()));
proto.write(DNDPolicyProto.ALLOW_MESSAGES_FROM,
- ZenModeConfig.getZenPolicySenders(mNewPolicy.allowMessagesFrom()));
+ ZenAdapters.notificationPolicySendersToZenPolicyPeopleType(
+ mNewPolicy.allowMessagesFrom()));
proto.write(DNDPolicyProto.ALLOW_CONVERSATIONS_FROM,
mNewPolicy.allowConversationsFrom());
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 6857869..bc86c82 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -84,6 +84,7 @@
import android.service.notification.Condition;
import android.service.notification.ConditionProviderService;
import android.service.notification.DeviceEffectsApplier;
+import android.service.notification.ZenAdapters;
import android.service.notification.ZenDeviceEffects;
import android.service.notification.ZenModeConfig;
import android.service.notification.ZenModeConfig.ConfigChangeOrigin;
diff --git a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
index 8452c0e..4f86adf 100644
--- a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
+++ b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
@@ -21,7 +21,6 @@
import android.Manifest;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
-import android.app.ActivityManager;
import android.app.AppOpsManager;
import android.app.admin.DevicePolicyManager;
import android.app.role.RoleManager;
@@ -39,6 +38,7 @@
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
+import android.os.UserManager;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.ArrayMap;
@@ -96,6 +96,7 @@
private static final long DEFAULT_BUGREPORT_SERVICE_TIMEOUT_MILLIS = 30 * 1000;
private final Object mLock = new Object();
+ private final Injector mInjector;
private final Context mContext;
private final AppOpsManager mAppOps;
private final TelephonyManager mTelephonyManager;
@@ -345,6 +346,14 @@
AtomicFile getMappingFile() {
return mMappingFile;
}
+
+ UserManager getUserManager() {
+ return mContext.getSystemService(UserManager.class);
+ }
+
+ DevicePolicyManager getDevicePolicyManager() {
+ return mContext.getSystemService(DevicePolicyManager.class);
+ }
}
BugreportManagerServiceImpl(Context context) {
@@ -356,6 +365,7 @@
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
BugreportManagerServiceImpl(Injector injector) {
+ mInjector = injector;
mContext = injector.getContext();
mAppOps = mContext.getSystemService(AppOpsManager.class);
mTelephonyManager = mContext.getSystemService(TelephonyManager.class);
@@ -388,12 +398,7 @@
int callingUid = Binder.getCallingUid();
enforcePermission(callingPackage, callingUid, bugreportMode
== BugreportParams.BUGREPORT_MODE_TELEPHONY /* checkCarrierPrivileges */);
- final long identity = Binder.clearCallingIdentity();
- try {
- ensureUserCanTakeBugReport(bugreportMode);
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
+ ensureUserCanTakeBugReport(bugreportMode);
Slogf.i(TAG, "Starting bugreport for %s / %d", callingPackage, callingUid);
synchronized (mLock) {
@@ -432,7 +437,6 @@
@RequiresPermission(value = Manifest.permission.DUMP, conditional = true)
public void retrieveBugreport(int callingUidUnused, String callingPackage, int userId,
FileDescriptor bugreportFd, String bugreportFile,
-
boolean keepBugreportOnRetrievalUnused, IDumpstateListener listener) {
int callingUid = Binder.getCallingUid();
enforcePermission(callingPackage, callingUid, false);
@@ -564,54 +568,59 @@
}
/**
- * Validates that the current user is an admin user or, when bugreport is requested remotely
- * that the current user is an affiliated user.
+ * Validates that the calling user is an admin user or, when bugreport is requested remotely
+ * that the user is an affiliated user.
*
- * @throws IllegalArgumentException if the current user is not an admin user
+ * @throws IllegalArgumentException if the calling user or the parent of the calling profile
+ * user is not an admin user.
*/
private void ensureUserCanTakeBugReport(int bugreportMode) {
- UserInfo currentUser = null;
+ // Get the calling userId before clearing the caller identity.
+ int effectiveCallingUserId = UserHandle.getUserId(Binder.getCallingUid());
+ boolean isAdminUser = false;
+ final long identity = Binder.clearCallingIdentity();
try {
- currentUser = ActivityManager.getService().getCurrentUser();
- } catch (RemoteException e) {
- // Impossible to get RemoteException for an in-process call.
+ UserInfo profileParent =
+ mInjector.getUserManager().getProfileParent(effectiveCallingUserId);
+ if (profileParent == null) {
+ isAdminUser = mInjector.getUserManager().isUserAdmin(effectiveCallingUserId);
+ } else {
+ // If the caller is a profile, we need to check its parent user instead.
+ // Therefore setting the profile parent user as the effective calling user.
+ effectiveCallingUserId = profileParent.id;
+ isAdminUser = profileParent.isAdmin();
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
}
-
- if (currentUser == null) {
- logAndThrow("There is no current user, so no bugreport can be requested.");
- }
-
- if (!currentUser.isAdmin()) {
+ if (!isAdminUser) {
if (bugreportMode == BugreportParams.BUGREPORT_MODE_REMOTE
- && isCurrentUserAffiliated(currentUser.id)) {
+ && isUserAffiliated(effectiveCallingUserId)) {
return;
}
- logAndThrow(TextUtils.formatSimple("Current user %s is not an admin user."
- + " Only admin users are allowed to take bugreport.", currentUser.id));
+ logAndThrow(TextUtils.formatSimple("Calling user %s is not an admin user."
+ + " Only admin users and their profiles are allowed to take bugreport.",
+ effectiveCallingUserId));
}
}
/**
- * Returns {@code true} if the device has device owner and the current user is affiliated
+ * Returns {@code true} if the device has device owner and the specified user is affiliated
* with the device owner.
*/
- private boolean isCurrentUserAffiliated(int currentUserId) {
- DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
+ private boolean isUserAffiliated(int userId) {
+ DevicePolicyManager dpm = mInjector.getDevicePolicyManager();
int deviceOwnerUid = dpm.getDeviceOwnerUserId();
if (deviceOwnerUid == UserHandle.USER_NULL) {
return false;
}
- int callingUserId = UserHandle.getUserId(Binder.getCallingUid());
-
- Slog.i(TAG, "callingUid: " + callingUserId + " deviceOwnerUid: " + deviceOwnerUid
- + " currentUserId: " + currentUserId);
-
- if (callingUserId != deviceOwnerUid) {
- logAndThrow("Caller is not device owner on provisioned device.");
+ if (DEBUG) {
+ Slog.d(TAG, "callingUid: " + userId + " deviceOwnerUid: " + deviceOwnerUid);
}
- if (!dpm.isAffiliatedUser(currentUserId)) {
- logAndThrow("Current user is not affiliated to the device owner.");
+
+ if (userId != deviceOwnerUid && !dpm.isAffiliatedUser(userId)) {
+ logAndThrow("User " + userId + " is not affiliated to the device owner.");
}
return true;
}
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 186cf5e..4bfd077 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -1080,7 +1080,7 @@
reconciledPackages = ReconcilePackageUtils.reconcilePackages(
requests, Collections.unmodifiableMap(mPm.mPackages),
versionInfos, mSharedLibraries, mPm.mSettings.getKeySetManagerService(),
- mPm.mSettings);
+ mPm.mSettings, mPm.mInjector.getSystemConfig());
} catch (ReconcileFailure e) {
for (InstallRequest request : requests) {
request.setError("Reconciliation failed...", e);
@@ -3810,7 +3810,7 @@
mPm.mPackages, Collections.singletonMap(pkgName,
mPm.getSettingsVersionForPackage(parsedPackage)),
mSharedLibraries, mPm.mSettings.getKeySetManagerService(),
- mPm.mSettings);
+ mPm.mSettings, mPm.mInjector.getSystemConfig());
if ((scanFlags & SCAN_AS_APEX) == 0) {
appIdCreated = optimisticallyRegisterAppId(installRequest);
} else {
diff --git a/services/core/java/com/android/server/pm/ReconcilePackageUtils.java b/services/core/java/com/android/server/pm/ReconcilePackageUtils.java
index 9a7916a..90d6adc 100644
--- a/services/core/java/com/android/server/pm/ReconcilePackageUtils.java
+++ b/services/core/java/com/android/server/pm/ReconcilePackageUtils.java
@@ -17,6 +17,7 @@
package com.android.server.pm;
import static android.content.pm.PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE;
+import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_SHARED_USER_ID;
import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES;
import static android.content.pm.SigningDetails.CapabilityMergeRule.MERGE_RESTRICTED_CAPABILITY;
@@ -25,6 +26,7 @@
import static com.android.server.pm.PackageManagerService.SCAN_DONT_KILL_APP;
import static com.android.server.pm.PackageManagerService.TAG;
+import android.content.pm.Flags;
import android.content.pm.PackageManager;
import android.content.pm.SharedLibraryInfo;
import android.content.pm.SigningDetails;
@@ -36,6 +38,7 @@
import com.android.internal.pm.parsing.pkg.ParsedPackage;
import com.android.internal.pm.pkg.parsing.ParsingPackageUtils;
+import com.android.server.SystemConfig;
import com.android.server.pm.pkg.AndroidPackage;
import com.android.server.utils.WatchedLongSparseArray;
@@ -53,14 +56,17 @@
* as install) led to the request.
*/
final class ReconcilePackageUtils {
- private static final boolean ALLOW_NON_PRELOADS_SYSTEM_SIGNATURE = Build.IS_DEBUGGABLE || true;
+ // TODO(b/308573259): with allow-list, we should be able to disallow such installs even in
+ // debuggable builds.
+ private static final boolean ALLOW_NON_PRELOADS_SYSTEM_SHAREDUIDS = Build.IS_DEBUGGABLE
+ || !Flags.restrictNonpreloadsSystemShareduids();
public static List<ReconciledPackage> reconcilePackages(
List<InstallRequest> installRequests,
Map<String, AndroidPackage> allPackages,
Map<String, Settings.VersionInfo> versionInfos,
SharedLibrariesImpl sharedLibraries,
- KeySetManagerService ksms, Settings settings)
+ KeySetManagerService ksms, Settings settings, SystemConfig systemConfig)
throws ReconcileFailure {
final List<ReconciledPackage> result = new ArrayList<>(installRequests.size());
@@ -187,11 +193,19 @@
SigningDetails.CertCapabilities.PERMISSION)) {
Slog.d(TAG, "Non-preload app associated with system signature: "
+ signatureCheckPs.getPackageName());
- if (!ALLOW_NON_PRELOADS_SYSTEM_SIGNATURE) {
- throw new ReconcileFailure(
- INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES,
- "Non-preload app associated with system signature: "
- + signatureCheckPs.getPackageName());
+ if (sharedUserSetting != null && !ALLOW_NON_PRELOADS_SYSTEM_SHAREDUIDS) {
+ // Check the allow-list.
+ var allowList = systemConfig.getPackageToSharedUidAllowList();
+ var sharedUidName = allowList.get(signatureCheckPs.getPackageName());
+ if (sharedUidName == null
+ || !sharedUserSetting.name.equals(sharedUidName)) {
+ var msg = "Non-preload app " + signatureCheckPs.getPackageName()
+ + " signed with platform signature and joining shared uid: "
+ + sharedUserSetting.name;
+ Slog.e(TAG, msg + ", allowList: " + allowList);
+ throw new ReconcileFailure(
+ INSTALL_PARSE_FAILED_BAD_SHARED_USER_ID, msg);
+ }
}
}
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index fe65010..aaf21c8 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -4558,6 +4558,10 @@
for (int i = 0; i < size; i++) {
final PackageSetting ps = mPackages.valueAt(i);
if (ps.getPkg() == null) {
+ // This would force-create correct per-user state.
+ ps.setInstalled(false, userHandle);
+ // Make sure the app is excluded from storage mapping for this user.
+ writeKernelMappingLPr(ps);
continue;
}
final boolean shouldMaybeInstall = ps.isSystem() &&
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index c1ab3f9..211b754 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -3795,6 +3795,7 @@
}
final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
+ final boolean archival = intent.getBooleanExtra(Intent.EXTRA_ARCHIVAL, false);
switch (action) {
case Intent.ACTION_PACKAGE_ADDED:
@@ -3805,7 +3806,7 @@
}
break;
case Intent.ACTION_PACKAGE_REMOVED:
- if (!replacing) {
+ if (!replacing || (replacing && archival)) {
handlePackageRemoved(packageName, userId);
}
break;
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java
index 47032ea..754b141 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java
@@ -408,6 +408,9 @@
/**
* Gets the permission states for requested package, persistent device and user.
+ * <p>
+ * <strong>Note: </strong>Default device permissions are not inherited in this API. Returns the
+ * exact permission states for the requested device.
*
* @param packageName name of the package you are checking against
* @param deviceId id of the persistent device you are checking against
diff --git a/services/core/java/com/android/server/policy/SoftRestrictedPermissionPolicy.java b/services/core/java/com/android/server/policy/SoftRestrictedPermissionPolicy.java
index 32a21c5..cebf7fb 100644
--- a/services/core/java/com/android/server/policy/SoftRestrictedPermissionPolicy.java
+++ b/services/core/java/com/android/server/policy/SoftRestrictedPermissionPolicy.java
@@ -21,7 +21,6 @@
import static android.Manifest.permission.WRITE_MEDIA_STORAGE;
import static android.app.AppOpsManager.OP_LEGACY_STORAGE;
import static android.app.AppOpsManager.OP_NONE;
-import static android.content.pm.PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION;
import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT;
import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT;
import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT;
@@ -148,7 +147,7 @@
pkg.hasPreserveLegacyExternalStorage();
targetSDK = getMinimumTargetSDK(context, appInfo, user);
- shouldApplyRestriction = (flags & FLAG_PERMISSION_APPLY_RESTRICTION) != 0;
+ shouldApplyRestriction = !isWhiteListed;
isForcedScopedStorage = sForcedScopedStorageAppWhitelist
.contains(appInfo.packageName);
} else {
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperCropper.java b/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
index 601c7f4..9616c28 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
@@ -39,6 +39,7 @@
import android.view.DisplayInfo;
import android.view.View;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.server.utils.TimingsTraceAndSlog;
import libcore.io.IoUtils;
@@ -65,7 +66,7 @@
* Maximum acceptable parallax.
* A value of 1 means "the additional width for parallax is at most 100% of the screen width"
*/
- private static final float MAX_PARALLAX = 1f;
+ @VisibleForTesting static final float MAX_PARALLAX = 1f;
/**
* We define three ways to adjust a crop. These modes are used depending on the situation:
@@ -73,10 +74,9 @@
* - When going from folded to unfolded, we want to add content
* - For a screen rotation, we want to keep the same amount of content
*/
- private static final int ADD = 1;
- private static final int REMOVE = 2;
- private static final int BALANCE = 3;
-
+ @VisibleForTesting static final int ADD = 1;
+ @VisibleForTesting static final int REMOVE = 2;
+ @VisibleForTesting static final int BALANCE = 3;
private final WallpaperDisplayHelper mWallpaperDisplayHelper;
@@ -209,7 +209,8 @@
* Given a crop, a displaySize for the orientation of that crop, compute the visible part of the
* crop. This removes any additional width used for parallax. No-op if displaySize == null.
*/
- private static Rect noParallax(Rect crop, Point displaySize, Point bitmapSize, boolean rtl) {
+ @VisibleForTesting
+ static Rect noParallax(Rect crop, Point displaySize, Point bitmapSize, boolean rtl) {
if (displaySize == null) return crop;
Rect adjustedCrop = getAdjustedCrop(crop, bitmapSize, displaySize, true, rtl, ADD);
// only keep the visible part (without parallax)
@@ -240,12 +241,13 @@
* </li>
* </ul>
*/
- private static Rect getAdjustedCrop(Rect crop, Point bitmapSize, Point screenSize,
+ @VisibleForTesting
+ static Rect getAdjustedCrop(Rect crop, Point bitmapSize, Point screenSize,
boolean parallax, boolean rtl, int mode) {
Rect adjustedCrop = new Rect(crop);
float cropRatio = ((float) crop.width()) / crop.height();
float screenRatio = ((float) screenSize.x) / screenSize.y;
- if (cropRatio >= screenRatio) {
+ if (cropRatio > screenRatio) {
if (!parallax) {
// rotate everything 90 degrees clockwise, compute the result, and rotate back
int newLeft = bitmapSize.y - crop.bottom;
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 7d5aa96d..d30a216 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -553,7 +553,6 @@
boolean launchFailed; // set if a launched failed, to abort on 2nd try
boolean delayedResume; // not yet resumed because of stopped app switches?
boolean finishing; // activity in pending finish list?
- int configChangeFlags; // which config values have changed
private boolean keysPaused; // has key dispatching been paused for it?
int launchMode; // the launch mode activity attribute.
int lockTaskLaunchMode; // the lockTaskMode manifest attribute, subject to override
@@ -1295,10 +1294,6 @@
if (mDeferHidingClient) {
pw.println(prefix + "mDeferHidingClient=" + mDeferHidingClient);
}
- if (configChangeFlags != 0) {
- pw.print(prefix); pw.print(" configChangeFlags=");
- pw.println(Integer.toHexString(configChangeFlags));
- }
if (mServiceConnectionsHolder != null) {
pw.print(prefix); pw.print("connections="); pw.println(mServiceConnectionsHolder);
}
@@ -4064,7 +4059,7 @@
try {
if (DEBUG_SWITCH) Slog.i(TAG_SWITCH, "Destroying: " + this);
mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(),
- DestroyActivityItem.obtain(token, finishing, configChangeFlags));
+ DestroyActivityItem.obtain(token, finishing));
} catch (Exception e) {
// We can just ignore exceptions here... if the process has crashed, our death
// notification will clean things up.
@@ -4106,8 +4101,6 @@
}
}
- configChangeFlags = 0;
-
return removedFromHistory;
}
@@ -5026,7 +5019,7 @@
return PauseActivityItem.obtain(token);
case STOPPING:
case STOPPED:
- return StopActivityItem.obtain(token, configChangeFlags);
+ return StopActivityItem.obtain(token);
default:
// Do not send a result immediately if the activity is in state INITIALIZING,
// RESTARTING_PROCESS, FINISHING, DESTROYING, or DESTROYED.
@@ -6300,7 +6293,7 @@
try {
mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(),
PauseActivityItem.obtain(token, finishing, false /* userLeaving */,
- configChangeFlags, false /* dontReport */, mAutoEnteringPip));
+ false /* dontReport */, mAutoEnteringPip));
} catch (Exception e) {
Slog.w(TAG, "Exception thrown sending pause: " + intent.getComponent(), e);
}
@@ -6614,7 +6607,7 @@
EventLogTags.writeWmStopActivity(
mUserId, System.identityHashCode(this), shortComponentName);
mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(),
- StopActivityItem.obtain(token, configChangeFlags));
+ StopActivityItem.obtain(token));
mAtmService.mH.postDelayed(mStopTimeoutRunnable, STOP_TIMEOUT);
} catch (Exception e) {
@@ -9858,7 +9851,6 @@
if (shouldRelaunchLocked(changes, mTmpConfig)) {
// Aha, the activity isn't handling the change, so DIE DIE DIE.
- configChangeFlags |= changes;
if (mVisible && mAtmService.mTmpUpdateConfigurationResult.mIsUpdating
&& !mTransitionController.isShellTransitionsEnabled()) {
startFreezingScreenLocked(app, mAtmService.mTmpUpdateConfigurationResult.changes);
@@ -9887,7 +9879,7 @@
ProtoLog.v(WM_DEBUG_STATES, "Config is relaunching invisible "
+ "activity %s called by %s", this, Debug.getCallers(4));
}
- relaunchActivityLocked(preserveWindow);
+ relaunchActivityLocked(preserveWindow, changes);
// All done... tell the caller we weren't able to keep this activity around.
return false;
@@ -10029,9 +10021,8 @@
| CONFIG_SCREEN_LAYOUT)) != 0;
}
- void relaunchActivityLocked(boolean preserveWindow) {
+ void relaunchActivityLocked(boolean preserveWindow, int configChangeFlags) {
if (mAtmService.mSuppressResizeConfigChanges && preserveWindow) {
- configChangeFlags = 0;
return;
}
if (!preserveWindow) {
@@ -10104,8 +10095,6 @@
// The activity may be waiting for stop, but that is no longer appropriate for it.
mTaskSupervisor.mStoppingActivities.remove(this);
-
- configChangeFlags = 0;
}
/**
@@ -10174,7 +10163,7 @@
// {@link ActivityTaskManagerService.activityStopped}).
try {
mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(),
- StopActivityItem.obtain(token, 0 /* configChanges */));
+ StopActivityItem.obtain(token));
} catch (RemoteException e) {
Slog.w(TAG, "Exception thrown during restart " + this, e);
}
diff --git a/services/core/java/com/android/server/wm/SnapshotCache.java b/services/core/java/com/android/server/wm/SnapshotCache.java
index 64d8c75..8680436 100644
--- a/services/core/java/com/android/server/wm/SnapshotCache.java
+++ b/services/core/java/com/android/server/wm/SnapshotCache.java
@@ -16,7 +16,6 @@
package com.android.server.wm;
import android.annotation.Nullable;
-import android.hardware.HardwareBuffer;
import android.util.ArrayMap;
import android.window.TaskSnapshot;
@@ -93,10 +92,6 @@
if (entry != null) {
mAppIdMap.remove(entry.topApp);
mRunningCache.remove(id);
- final HardwareBuffer buffer = entry.snapshot.getHardwareBuffer();
- if (buffer != null) {
- buffer.close();
- }
}
}
}
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 85d81c4..78ababc 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -1881,7 +1881,7 @@
mAtmService.getLifecycleManager().scheduleTransactionItem(prev.app.getThread(),
PauseActivityItem.obtain(prev.token, prev.finishing, userLeaving,
- prev.configChangeFlags, pauseImmediately, autoEnteringPip));
+ pauseImmediately, autoEnteringPip));
} catch (Exception e) {
// Ignore exception, if process died other code will cleanup.
Slog.w(TAG, "Exception thrown during pause", e);
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index 594043d..9b19a70 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -349,7 +349,7 @@
final Rect lastWallpaperBounds = wallpaperWin.getParentFrame();
int screenWidth = lastWallpaperBounds.width();
int screenHeight = lastWallpaperBounds.height();
- float screenRatio = ((float) screenWidth) / screenHeight;
+ float screenRatio = (float) screenWidth / screenHeight;
Point screenSize = new Point(screenWidth, screenHeight);
WallpaperWindowToken token = wallpaperWin.mToken.asWallpaperToken();
@@ -399,20 +399,32 @@
Point bitmapSize = new Point(
wallpaperWin.mRequestedWidth, wallpaperWin.mRequestedHeight);
SparseArray<Rect> cropHints = token.getCropHints();
- wallpaperFrame = mWallpaperCropUtils.getCrop(
- screenSize, bitmapSize, cropHints, wallpaperWin.isRtl());
+ wallpaperFrame = bitmapSize.x <= 0 || bitmapSize.y <= 0 ? wallpaperWin.getFrame()
+ : mWallpaperCropUtils.getCrop(screenSize, bitmapSize, cropHints,
+ wallpaperWin.isRtl());
+ int frameWidth = wallpaperFrame.width();
+ int frameHeight = wallpaperFrame.height();
+ float frameRatio = (float) frameWidth / frameHeight;
- cropZoom = wallpaperFrame.isEmpty() ? 1f
- : ((float) screenHeight) / wallpaperFrame.height() / wallpaperWin.mVScale;
+ // If the crop is proportionally wider/taller than the screen, scale it so that its
+ // height/width matches the screen height/width, and use the additional width/height
+ // for parallax (respectively).
+ boolean scaleHeight = frameRatio >= screenRatio;
+ cropZoom = wallpaperFrame.isEmpty() ? 1f : scaleHeight
+ ? (float) screenHeight / frameHeight / wallpaperWin.mVScale
+ : (float) screenWidth / frameWidth / wallpaperWin.mHScale;
- // A positive x / y offset shifts the wallpaper to the right / bottom respectively.
- cropOffsetX = -wallpaperFrame.left
- + (int) ((cropZoom - 1f) * wallpaperFrame.height() * screenRatio / 2f);
- cropOffsetY = -wallpaperFrame.top
- + (int) ((cropZoom - 1f) * wallpaperFrame.height() / 2f);
+ // The dimensions of the frame, without the additional width or height for parallax.
+ float w = scaleHeight ? frameHeight * screenRatio : frameWidth;
+ float h = scaleHeight ? frameHeight : frameWidth / screenRatio;
- diffWidth = (int) (wallpaperFrame.width() * wallpaperWin.mHScale) - screenWidth;
- diffHeight = (int) (wallpaperFrame.height() * wallpaperWin.mVScale) - screenHeight;
+ // Note: a positive x/y offset shifts the wallpaper to the right/bottom respectively.
+ cropOffsetX = -wallpaperFrame.left + (int) ((cropZoom - 1f) * w / 2f);
+ cropOffsetY = -wallpaperFrame.top + (int) ((cropZoom - 1f) * h / 2f);
+
+ // Available width or height for parallax
+ diffWidth = (int) ((frameWidth - w) * wallpaperWin.mHScale);
+ diffHeight = (int) ((frameHeight - h) * wallpaperWin.mVScale);
} else {
wallpaperFrame = wallpaperWin.getFrame();
cropZoom = 1f;
diff --git a/services/core/jni/com_android_server_hint_HintManagerService.cpp b/services/core/jni/com_android_server_hint_HintManagerService.cpp
index ccd9bd0..b2bdaa3 100644
--- a/services/core/jni/com_android_server_hint_HintManagerService.cpp
+++ b/services/core/jni/com_android_server_hint_HintManagerService.cpp
@@ -24,16 +24,17 @@
#include <nativehelper/JNIHelp.h>
#include <nativehelper/ScopedPrimitiveArray.h>
#include <powermanager/PowerHalController.h>
+#include <powermanager/PowerHintSessionWrapper.h>
#include <utils/Log.h>
#include <unordered_map>
#include "jni.h"
-using aidl::android::hardware::power::IPowerHintSession;
using aidl::android::hardware::power::SessionHint;
using aidl::android::hardware::power::SessionMode;
using aidl::android::hardware::power::WorkDuration;
+using android::power::PowerHintSessionWrapper;
using android::base::StringPrintf;
@@ -49,7 +50,7 @@
} gWorkDurationInfo;
static power::PowerHalController gPowerHalController;
-static std::unordered_map<jlong, std::shared_ptr<IPowerHintSession>> gSessionMap;
+static std::unordered_map<jlong, std::shared_ptr<PowerHintSessionWrapper>> gSessionMap;
static std::mutex gSessionMapLock;
static int64_t getHintSessionPreferredRate() {
@@ -76,45 +77,45 @@
}
static void pauseHintSession(JNIEnv* env, int64_t session_ptr) {
- auto appSession = reinterpret_cast<IPowerHintSession*>(session_ptr);
+ auto appSession = reinterpret_cast<PowerHintSessionWrapper*>(session_ptr);
appSession->pause();
}
static void resumeHintSession(JNIEnv* env, int64_t session_ptr) {
- auto appSession = reinterpret_cast<IPowerHintSession*>(session_ptr);
+ auto appSession = reinterpret_cast<PowerHintSessionWrapper*>(session_ptr);
appSession->resume();
}
static void closeHintSession(JNIEnv* env, int64_t session_ptr) {
- auto appSession = reinterpret_cast<IPowerHintSession*>(session_ptr);
+ auto appSession = reinterpret_cast<PowerHintSessionWrapper*>(session_ptr);
appSession->close();
std::unique_lock<std::mutex> sessionLock(gSessionMapLock);
gSessionMap.erase(session_ptr);
}
static void updateTargetWorkDuration(int64_t session_ptr, int64_t targetDurationNanos) {
- auto appSession = reinterpret_cast<IPowerHintSession*>(session_ptr);
+ auto appSession = reinterpret_cast<PowerHintSessionWrapper*>(session_ptr);
appSession->updateTargetWorkDuration(targetDurationNanos);
}
static void reportActualWorkDuration(int64_t session_ptr,
const std::vector<WorkDuration>& actualDurations) {
- auto appSession = reinterpret_cast<IPowerHintSession*>(session_ptr);
+ auto appSession = reinterpret_cast<PowerHintSessionWrapper*>(session_ptr);
appSession->reportActualWorkDuration(actualDurations);
}
static void sendHint(int64_t session_ptr, SessionHint hint) {
- auto appSession = reinterpret_cast<IPowerHintSession*>(session_ptr);
+ auto appSession = reinterpret_cast<PowerHintSessionWrapper*>(session_ptr);
appSession->sendHint(hint);
}
static void setThreads(int64_t session_ptr, const std::vector<int32_t>& threadIds) {
- auto appSession = reinterpret_cast<IPowerHintSession*>(session_ptr);
+ auto appSession = reinterpret_cast<PowerHintSessionWrapper*>(session_ptr);
appSession->setThreads(threadIds);
}
static void setMode(int64_t session_ptr, SessionMode mode, bool enabled) {
- auto appSession = reinterpret_cast<IPowerHintSession*>(session_ptr);
+ auto appSession = reinterpret_cast<PowerHintSessionWrapper*>(session_ptr);
appSession->setMode(mode, enabled);
}
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
index 027337f..f5e1e41 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
@@ -113,8 +113,8 @@
/** Creates intent that is ot be invoked to cancel an in-progress UI session. */
public Intent createCancelIntent(IBinder requestId, String packageName) {
- return IntentFactory.createCancelUiIntent(requestId, /*shouldShowCancellationUi=*/ true,
- packageName);
+ return IntentFactory.createCancelUiIntent(mContext, requestId,
+ /*shouldShowCancellationUi=*/ true, packageName);
}
/**
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
index 12f4407..6aeb4fd5 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
@@ -225,7 +225,7 @@
synchronized (mLock) {
PolicyState<V> localPolicyState = getLocalPolicyStateLocked(policyDefinition, userId);
- if (Flags.devicePolicySizeTrackingEnabled() && false) {
+ if (Flags.devicePolicySizeTrackingInternalEnabled()) {
if (!handleAdminPolicySizeLimit(localPolicyState, enforcingAdmin, value,
policyDefinition, userId)) {
return;
@@ -350,7 +350,7 @@
}
PolicyState<V> localPolicyState = getLocalPolicyStateLocked(policyDefinition, userId);
- if (Flags.devicePolicySizeTrackingEnabled() && false) {
+ if (Flags.devicePolicySizeTrackingInternalEnabled()) {
decreasePolicySizeForAdmin(localPolicyState, enforcingAdmin);
}
@@ -496,7 +496,7 @@
synchronized (mLock) {
PolicyState<V> globalPolicyState = getGlobalPolicyStateLocked(policyDefinition);
- if (Flags.devicePolicySizeTrackingEnabled() && false) {
+ if (Flags.devicePolicySizeTrackingInternalEnabled()) {
if (!handleAdminPolicySizeLimit(globalPolicyState, enforcingAdmin, value,
policyDefinition, UserHandle.USER_ALL)) {
return;
@@ -568,7 +568,7 @@
synchronized (mLock) {
PolicyState<V> policyState = getGlobalPolicyStateLocked(policyDefinition);
- if (Flags.devicePolicySizeTrackingEnabled() && false) {
+ if (Flags.devicePolicySizeTrackingInternalEnabled()) {
decreasePolicySizeForAdmin(policyState, enforcingAdmin);
}
@@ -1892,7 +1892,7 @@
private void writeEnforcingAdminSizeInner(TypedXmlSerializer serializer)
throws IOException {
- if (Flags.devicePolicySizeTrackingEnabled() && false) {
+ if (Flags.devicePolicySizeTrackingInternalEnabled()) {
if (mAdminPolicySize != null) {
for (int i = 0; i < mAdminPolicySize.size(); i++) {
int userId = mAdminPolicySize.keyAt(i);
@@ -1916,7 +1916,7 @@
private void writeMaxPolicySizeInner(TypedXmlSerializer serializer)
throws IOException {
- if (!Flags.devicePolicySizeTrackingEnabled() || true) {
+ if (!Flags.devicePolicySizeTrackingInternalEnabled()) {
return;
}
serializer.startTag(/* namespace= */ null, TAG_MAX_POLICY_SIZE_LIMIT);
@@ -2081,7 +2081,7 @@
private void readMaxPolicySizeInner(TypedXmlPullParser parser)
throws XmlPullParserException, IOException {
- if (!Flags.devicePolicySizeTrackingEnabled() || true) {
+ if (!Flags.devicePolicySizeTrackingInternalEnabled()) {
return;
}
mPolicySizeLimit = parser.getAttributeInt(/* namespace= */ null, ATTR_POLICY_SUM_SIZE);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 0f97f4a..80046b60 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -27,6 +27,7 @@
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_AIRPLANE_MODE;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_APPS_CONTROL;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_APP_RESTRICTIONS;
+import static android.Manifest.permission.MANAGE_DEVICE_POLICY_ASSIST_CONTENT;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_AUDIO_OUTPUT;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_AUTOFILL;
@@ -83,7 +84,6 @@
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_TIME;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_USB_DATA_SIGNALLING;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_USB_FILE_TRANSFER;
-import static android.Manifest.permission.MANAGE_DEVICE_POLICY_ASSIST_CONTENT;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_VPN;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_WALLPAPER;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_WIFI;
@@ -196,11 +196,11 @@
import static android.app.admin.DevicePolicyManager.STATUS_DEVICE_ADMIN_NOT_SUPPORTED;
import static android.app.admin.DevicePolicyManager.STATUS_HAS_DEVICE_OWNER;
import static android.app.admin.DevicePolicyManager.STATUS_HAS_PAIRED;
+import static android.app.admin.DevicePolicyManager.STATUS_HEADLESS_ONLY_SYSTEM_USER;
import static android.app.admin.DevicePolicyManager.STATUS_HEADLESS_SYSTEM_USER_MODE_NOT_SUPPORTED;
import static android.app.admin.DevicePolicyManager.STATUS_MANAGED_USERS_NOT_SUPPORTED;
import static android.app.admin.DevicePolicyManager.STATUS_NONSYSTEM_USER_EXISTS;
import static android.app.admin.DevicePolicyManager.STATUS_NOT_SYSTEM_USER;
-import static android.app.admin.DevicePolicyManager.STATUS_HEADLESS_ONLY_SYSTEM_USER;
import static android.app.admin.DevicePolicyManager.STATUS_OK;
import static android.app.admin.DevicePolicyManager.STATUS_PROVISIONING_NOT_ALLOWED_FOR_NON_DEVELOPER_USERS;
import static android.app.admin.DevicePolicyManager.STATUS_SYSTEM_USER;
@@ -12062,7 +12062,7 @@
}
if (packageList != null) {
- if (!Flags.devicePolicySizeTrackingEnabled()) {
+ if (!Flags.devicePolicySizeTrackingInternalEnabled()) {
for (String pkg : packageList) {
PolicySizeVerifier.enforceMaxPackageNameLength(pkg);
}
@@ -13771,7 +13771,7 @@
return;
}
- if (!Flags.devicePolicySizeTrackingEnabled()) {
+ if (!Flags.devicePolicySizeTrackingInternalEnabled()) {
PolicySizeVerifier.enforceMaxStringLength(accountType, "account type");
}
@@ -14385,7 +14385,7 @@
public void setLockTaskPackages(ComponentName who, String callerPackageName, String[] packages)
throws SecurityException {
Objects.requireNonNull(packages, "packages is null");
- if (!Flags.devicePolicySizeTrackingEnabled()) {
+ if (!Flags.devicePolicySizeTrackingInternalEnabled()) {
for (String pkg : packages) {
PolicySizeVerifier.enforceMaxPackageNameLength(pkg);
}
@@ -24235,7 +24235,7 @@
@Override
public void setMaxPolicyStorageLimit(String callerPackageName, int storageLimit) {
- if (!Flags.devicePolicySizeTrackingEnabled() || true) {
+ if (!Flags.devicePolicySizeTrackingInternalEnabled()) {
return;
}
CallerIdentity caller = getCallerIdentity(callerPackageName);
@@ -24247,7 +24247,7 @@
@Override
public int getMaxPolicyStorageLimit(String callerPackageName) {
- if (!Flags.devicePolicySizeTrackingEnabled() || true) {
+ if (!Flags.devicePolicySizeTrackingInternalEnabled()) {
return -1;
}
CallerIdentity caller = getCallerIdentity(callerPackageName);
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index e19f08c..9d95c5b 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -2774,9 +2774,12 @@
t.traceEnd();
// OnDevicePersonalizationSystemService
- t.traceBegin("StartOnDevicePersonalizationSystemService");
- mSystemServiceManager.startService(ON_DEVICE_PERSONALIZATION_SYSTEM_SERVICE_CLASS);
- t.traceEnd();
+ if (!com.android.server.flags.Flags.enableOdpFeatureGuard()
+ || SystemProperties.getBoolean("ro.system_settings.service.odp_enabled", true)) {
+ t.traceBegin("StartOnDevicePersonalizationSystemService");
+ mSystemServiceManager.startService(ON_DEVICE_PERSONALIZATION_SYSTEM_SERVICE_CLASS);
+ t.traceEnd();
+ }
// Profiling
if (android.server.Flags.telemetryApisService()) {
diff --git a/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt b/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt
index fc2eb26..c0d988d 100644
--- a/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt
+++ b/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt
@@ -20,27 +20,63 @@
import android.companion.virtual.VirtualDeviceManager
import android.os.Handler
import android.os.UserHandle
+import android.permission.flags.Flags
import android.util.ArrayMap
import android.util.ArraySet
+import android.util.LongSparseArray
+import android.util.Slog
+import android.util.SparseArray
import android.util.SparseBooleanArray
import android.util.SparseIntArray
import com.android.internal.annotations.VisibleForTesting
+import com.android.internal.util.IntPair
import com.android.server.appop.AppOpsCheckingServiceInterface
import com.android.server.appop.AppOpsCheckingServiceInterface.AppOpsModeChangedListener
import com.android.server.permission.access.AccessCheckingService
import com.android.server.permission.access.AppOpUri
+import com.android.server.permission.access.DevicePermissionUri
+import com.android.server.permission.access.GetStateScope
import com.android.server.permission.access.PackageUri
+import com.android.server.permission.access.PermissionUri
import com.android.server.permission.access.UidUri
+import com.android.server.permission.access.appop.AppOpModes.MODE_ALLOWED
+import com.android.server.permission.access.appop.AppOpModes.MODE_FOREGROUND
+import com.android.server.permission.access.appop.AppOpModes.MODE_IGNORED
import com.android.server.permission.access.collection.forEachIndexed
import com.android.server.permission.access.collection.set
+import com.android.server.permission.access.permission.AppIdPermissionPolicy
+import com.android.server.permission.access.permission.DevicePermissionPolicy
+import com.android.server.permission.access.permission.PermissionFlags
+import com.android.server.permission.access.permission.PermissionService
class AppOpService(private val service: AccessCheckingService) : AppOpsCheckingServiceInterface {
private val packagePolicy =
service.getSchemePolicy(PackageUri.SCHEME, AppOpUri.SCHEME) as PackageAppOpPolicy
private val appIdPolicy =
service.getSchemePolicy(UidUri.SCHEME, AppOpUri.SCHEME) as AppIdAppOpPolicy
+ private val permissionPolicy =
+ service.getSchemePolicy(UidUri.SCHEME, PermissionUri.SCHEME) as AppIdPermissionPolicy
+ private val devicePermissionPolicy =
+ service.getSchemePolicy(UidUri.SCHEME, DevicePermissionUri.SCHEME) as DevicePermissionPolicy
private val context = service.context
+
+ // Maps appop code to its runtime permission
+ private val runtimeAppOpToPermissionNames = SparseArray<String>()
+
+ // Maps runtime permission to its appop codes
+ private val runtimePermissionNameToAppOp = ArrayMap<String, Int>()
+
+ private var foregroundableOps = SparseBooleanArray()
+
+ /* Maps foreground permissions to their background permission. Background permissions aren't
+ required to be runtime */
+ private val foregroundToBackgroundPermissionName = ArrayMap<String, String>()
+
+ /* Maps background permissions to their foreground permissions. Background permissions aren't
+ required to be runtime */
+ private val backgroundToForegroundPermissionNames = ArrayMap<String, ArraySet<String>>()
+
private lateinit var handler: Handler
@Volatile private var listeners = ArraySet<AppOpsModeChangedListener>()
@@ -69,11 +105,60 @@
}
override fun systemReady() {
- // Not implemented because upgrades are handled automatically.
+ if (Flags.runtimePermissionAppopsMappingEnabled()) {
+ createPermissionAppOpMapping()
+ val permissionListener = OnPermissionFlagsChangedListener()
+ permissionPolicy.addOnPermissionFlagsChangedListener(permissionListener)
+ devicePermissionPolicy.addOnPermissionFlagsChangedListener(permissionListener)
+ }
+ }
+
+ private fun createPermissionAppOpMapping() {
+ val permissions = service.getState { with(permissionPolicy) { getPermissions() } }
+
+ for (appOpCode in 0 until AppOpsManager._NUM_OP) {
+ AppOpsManager.opToPermission(appOpCode)?.let { permissionName ->
+ // Multiple ops might map to a single permission but only one is considered the
+ // runtime appop calculations.
+ if (appOpCode == AppOpsManager.permissionToOpCode(permissionName)) {
+ val permission = permissions[permissionName]!!
+ if (permission.isRuntime) {
+ runtimePermissionNameToAppOp[permissionName] = appOpCode
+ runtimeAppOpToPermissionNames[appOpCode] = permissionName
+ permission.permissionInfo.backgroundPermission?.let {
+ backgroundPermissionName ->
+ // Note: background permission may not be runtime,
+ // e.g. microphone/camera.
+ foregroundableOps[appOpCode] = true
+ foregroundToBackgroundPermissionName[permissionName] =
+ backgroundPermissionName
+ backgroundToForegroundPermissionNames
+ .getOrPut(backgroundPermissionName, ::ArraySet)
+ .add(permissionName)
+ }
+ }
+ }
+ }
+ }
}
override fun getNonDefaultUidModes(uid: Int, persistentDeviceId: String): SparseIntArray {
- return opNameMapToOpSparseArray(getUidModes(uid))
+ val appId = UserHandle.getAppId(uid)
+ val userId = UserHandle.getUserId(uid)
+ service.getState {
+ val modes =
+ with(appIdPolicy) { opNameMapToOpSparseArray(getAppOpModes(appId, userId)?.map) }
+ if (Flags.runtimePermissionAppopsMappingEnabled()) {
+ runtimePermissionNameToAppOp.forEachIndexed { _, permissionName, appOpCode ->
+ val mode = getUidModeFromPermissionState(appId, userId, permissionName)
+ if (mode != AppOpsManager.opToDefaultMode(appOpCode)) {
+ modes[appOpCode] = mode
+ }
+ }
+ }
+
+ return modes
+ }
}
override fun getNonDefaultPackageModes(packageName: String, userId: Int): SparseIntArray {
@@ -84,7 +169,13 @@
val appId = UserHandle.getAppId(uid)
val userId = UserHandle.getUserId(uid)
val opName = AppOpsManager.opToPublicName(op)
- return service.getState { with(appIdPolicy) { getAppOpMode(appId, userId, opName) } }
+ val permissionName = runtimeAppOpToPermissionNames[op]
+
+ return if (!Flags.runtimePermissionAppopsMappingEnabled() || permissionName == null) {
+ service.getState { with(appIdPolicy) { getAppOpMode(appId, userId, opName) } }
+ } else {
+ service.getState { getUidModeFromPermissionState(appId, userId, permissionName) }
+ }
}
private fun getUidModes(uid: Int): ArrayMap<String, Int>? {
@@ -93,13 +184,66 @@
return service.getState { with(appIdPolicy) { getAppOpModes(appId, userId) } }?.map
}
- override fun setUidMode(uid: Int, persistentDeviceId: String, op: Int, mode: Int): Boolean {
+ private fun GetStateScope.getUidModeFromPermissionState(
+ appId: Int,
+ userId: Int,
+ permissionName: String
+ ): Int {
+ val permissionFlags =
+ with(permissionPolicy) { getPermissionFlags(appId, userId, permissionName) }
+ val backgroundPermissionName = foregroundToBackgroundPermissionName[permissionName]
+ val backgroundPermissionFlags =
+ if (backgroundPermissionName != null) {
+ with(permissionPolicy) {
+ getPermissionFlags(appId, userId, backgroundPermissionName)
+ }
+ } else {
+ PermissionFlags.RUNTIME_GRANTED
+ }
+ val result = evaluateModeFromPermissionFlags(permissionFlags, backgroundPermissionFlags)
+ if (result != MODE_IGNORED) {
+ return result
+ }
+
+ val fullerPermissionName =
+ PermissionService.getFullerPermission(permissionName) ?: return result
+ return getUidModeFromPermissionState(appId, userId, fullerPermissionName)
+ }
+
+ private fun evaluateModeFromPermissionFlags(
+ foregroundFlags: Int,
+ backgroundFlags: Int = PermissionFlags.RUNTIME_GRANTED
+ ): Int =
+ if (PermissionFlags.isAppOpGranted(foregroundFlags)) {
+ if (PermissionFlags.isAppOpGranted(backgroundFlags)) {
+ MODE_ALLOWED
+ } else {
+ MODE_FOREGROUND
+ }
+ } else {
+ MODE_IGNORED
+ }
+
+ override fun setUidMode(uid: Int, persistentDeviceId: String, code: Int, mode: Int): Boolean {
+ if (
+ Flags.runtimePermissionAppopsMappingEnabled() && code in runtimeAppOpToPermissionNames
+ ) {
+ Slog.w(
+ LOG_TAG,
+ "Cannot set UID mode for runtime permission app op, uid = $uid," +
+ " code = ${AppOpsManager.opToName(code)}," +
+ " mode = ${AppOpsManager.modeToName(mode)}",
+ RuntimeException()
+ )
+ return false
+ }
+
val appId = UserHandle.getAppId(uid)
val userId = UserHandle.getUserId(uid)
- val opName = AppOpsManager.opToPublicName(op)
- var wasChanged = false
+ val appOpName = AppOpsManager.opToPublicName(code)
+ var wasChanged: Boolean
service.mutateState {
- wasChanged = with(appIdPolicy) { setAppOpMode(appId, userId, opName, mode) }
+ wasChanged = with(appIdPolicy) { setAppOpMode(appId, userId, appOpName, mode) }
}
return wasChanged
}
@@ -114,10 +258,23 @@
private fun getPackageModes(packageName: String, userId: Int): ArrayMap<String, Int>? =
service.getState { with(packagePolicy) { getAppOpModes(packageName, userId) } }?.map
- override fun setPackageMode(packageName: String, op: Int, mode: Int, userId: Int) {
- val opName = AppOpsManager.opToPublicName(op)
+ override fun setPackageMode(packageName: String, appOpCode: Int, mode: Int, userId: Int) {
+ val appOpName = AppOpsManager.opToPublicName(appOpCode)
+
+ if (
+ Flags.runtimePermissionAppopsMappingEnabled() &&
+ appOpCode in runtimeAppOpToPermissionNames
+ ) {
+ Slog.w(
+ LOG_TAG,
+ "(packageName=$packageName, userId=$userId)'s appop state" +
+ " for runtime op $appOpName should not be set directly.",
+ RuntimeException()
+ )
+ return
+ }
service.mutateState {
- with(packagePolicy) { setAppOpMode(packageName, userId, opName, mode) }
+ with(packagePolicy) { setAppOpMode(packageName, userId, appOpName, mode) }
}
}
@@ -128,7 +285,7 @@
}
override fun removePackage(packageName: String, userId: Int): Boolean {
- var wasChanged = false
+ var wasChanged: Boolean
service.mutateState {
wasChanged = with(packagePolicy) { removeAppOpModes(packageName, userId) }
}
@@ -158,6 +315,13 @@
this[AppOpsManager.strOpToOp(op)] = true
}
}
+ if (Flags.runtimePermissionAppopsMappingEnabled()) {
+ foregroundableOps.forEachIndexed { _, op, _ ->
+ if (getUidMode(uid, persistentDeviceId, op) == AppOpsManager.MODE_FOREGROUND) {
+ this[op] = true
+ }
+ }
+ }
}
}
@@ -168,6 +332,13 @@
this[AppOpsManager.strOpToOp(op)] = true
}
}
+ if (Flags.runtimePermissionAppopsMappingEnabled()) {
+ foregroundableOps.forEachIndexed { _, op, _ ->
+ if (getPackageMode(packageName, op, userId) == AppOpsManager.MODE_FOREGROUND) {
+ this[op] = true
+ }
+ }
+ }
}
}
@@ -189,9 +360,10 @@
}
}
- inner class OnAppIdAppOpModeChangedListener : AppIdAppOpPolicy.OnAppOpModeChangedListener() {
+ private inner class OnAppIdAppOpModeChangedListener :
+ AppIdAppOpPolicy.OnAppOpModeChangedListener() {
// (uid, appOpCode) -> newMode
- val pendingChanges = ArrayMap<Pair<Int, Int>, Int>()
+ private val pendingChanges = LongSparseArray<Int>()
override fun onAppOpModeChanged(
appId: Int,
@@ -202,7 +374,7 @@
) {
val uid = UserHandle.getUid(userId, appId)
val appOpCode = AppOpsManager.strOpToOp(appOpName)
- val key = Pair(uid, appOpCode)
+ val key = IntPair.of(uid, appOpCode)
pendingChanges[key] = newMode
}
@@ -211,13 +383,15 @@
val listenersLocal = listeners
pendingChanges.forEachIndexed { _, key, mode ->
listenersLocal.forEachIndexed { _, listener ->
- val uid = key.first
- val appOpCode = key.second
+ val uid = IntPair.first(key)
+ val appOpCode = IntPair.second(key)
- listener.onUidModeChanged(uid,
+ listener.onUidModeChanged(
+ uid,
appOpCode,
mode,
- VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT)
+ VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT
+ )
}
}
@@ -228,7 +402,7 @@
private inner class OnPackageAppOpModeChangedListener :
PackageAppOpPolicy.OnAppOpModeChangedListener() {
// (packageName, userId, appOpCode) -> newMode
- val pendingChanges = ArrayMap<Triple<String, Int, Int>, Int>()
+ private val pendingChanges = ArrayMap<Triple<String, Int, Int>, Int>()
override fun onAppOpModeChanged(
packageName: String,
@@ -258,4 +432,130 @@
pendingChanges.clear()
}
}
+
+ private inner class OnPermissionFlagsChangedListener :
+ AppIdPermissionPolicy.OnPermissionFlagsChangedListener,
+ DevicePermissionPolicy.OnDevicePermissionFlagsChangedListener {
+ // (uid, deviceId, appOpCode) -> newMode
+ private val pendingChanges = ArrayMap<Triple<Int, String, Int>, Int>()
+
+ override fun onPermissionFlagsChanged(
+ appId: Int,
+ userId: Int,
+ permissionName: String,
+ oldFlags: Int,
+ newFlags: Int
+ ) {
+ onDevicePermissionFlagsChanged(
+ appId,
+ userId,
+ VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT,
+ permissionName,
+ oldFlags,
+ newFlags
+ )
+ }
+
+ override fun onDevicePermissionFlagsChanged(
+ appId: Int,
+ userId: Int,
+ deviceId: String,
+ permissionName: String,
+ oldFlags: Int,
+ newFlags: Int
+ ) {
+ backgroundToForegroundPermissionNames[permissionName]?.let { foregroundPermissions ->
+ // This is a background permission; there may be multiple foreground permissions
+ // affected.
+ foregroundPermissions.forEachIndexed { _, foregroundPermissionName ->
+ runtimePermissionNameToAppOp[foregroundPermissionName]?.let { appOpCode ->
+ val foregroundPermissionFlags =
+ getPermissionFlags(appId, userId, foregroundPermissionName)
+ addPendingChangedModeIfNeeded(
+ appId,
+ userId,
+ deviceId,
+ appOpCode,
+ foregroundPermissionFlags,
+ oldFlags,
+ foregroundPermissionFlags,
+ newFlags
+ )
+ }
+ }
+ }
+ ?: foregroundToBackgroundPermissionName[permissionName]?.let { backgroundPermission
+ ->
+ runtimePermissionNameToAppOp[permissionName]?.let { appOpCode ->
+ val backgroundPermissionFlags =
+ getPermissionFlags(appId, userId, backgroundPermission)
+ addPendingChangedModeIfNeeded(
+ appId,
+ userId,
+ deviceId,
+ appOpCode,
+ oldFlags,
+ backgroundPermissionFlags,
+ newFlags,
+ backgroundPermissionFlags
+ )
+ }
+ }
+ ?: runtimePermissionNameToAppOp[permissionName]?.let { appOpCode ->
+ addPendingChangedModeIfNeeded(
+ appId,
+ userId,
+ deviceId,
+ appOpCode,
+ oldFlags,
+ PermissionFlags.RUNTIME_GRANTED,
+ newFlags,
+ PermissionFlags.RUNTIME_GRANTED
+ )
+ }
+ }
+
+ private fun getPermissionFlags(appId: Int, userId: Int, permissionName: String): Int =
+ service.getState {
+ with(permissionPolicy) { getPermissionFlags(appId, userId, permissionName) }
+ }
+
+ private fun addPendingChangedModeIfNeeded(
+ appId: Int,
+ userId: Int,
+ deviceId: String,
+ appOpCode: Int,
+ oldForegroundFlags: Int,
+ oldBackgroundFlags: Int,
+ newForegroundFlags: Int,
+ newBackgroundFlags: Int,
+ ) {
+ val oldMode = evaluateModeFromPermissionFlags(oldForegroundFlags, oldBackgroundFlags)
+ val newMode = evaluateModeFromPermissionFlags(newForegroundFlags, newBackgroundFlags)
+
+ if (oldMode != newMode) {
+ val uid = UserHandle.getUid(userId, appId)
+ pendingChanges[Triple(uid, deviceId, appOpCode)] = newMode
+ }
+ }
+
+ override fun onStateMutated() {
+ val listenersLocal = listeners
+ pendingChanges.forEachIndexed { _, key, mode ->
+ listenersLocal.forEachIndexed { _, listener ->
+ val uid = key.first
+ val deviceId = key.second
+ val appOpCode = key.third
+
+ listener.onUidModeChanged(uid, appOpCode, mode, deviceId)
+ }
+ }
+
+ pendingChanges.clear()
+ }
+ }
+
+ companion object {
+ private val LOG_TAG = AppOpService::class.java.simpleName
+ }
}
diff --git a/services/permission/java/com/android/server/permission/access/collection/LongSparseArrayExtensions.kt b/services/permission/java/com/android/server/permission/access/collection/LongSparseArrayExtensions.kt
new file mode 100644
index 0000000..827dd0e
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/collection/LongSparseArrayExtensions.kt
@@ -0,0 +1,105 @@
+/*
+ * 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.permission.access.collection
+
+import android.util.LongSparseArray
+
+inline fun <T> LongSparseArray<T>.allIndexed(predicate: (Int, Long, T) -> Boolean): Boolean {
+ forEachIndexed { index, key, value ->
+ if (!predicate(index, key, value)) {
+ return false
+ }
+ }
+ return true
+}
+
+inline fun <T> LongSparseArray<T>.anyIndexed(predicate: (Int, Long, T) -> Boolean): Boolean {
+ forEachIndexed { index, key, value ->
+ if (predicate(index, key, value)) {
+ return true
+ }
+ }
+ return false
+}
+
+inline fun <T> LongSparseArray<T>.forEachIndexed(action: (Int, Long, T) -> Unit) {
+ for (index in 0 until size) {
+ action(index, keyAt(index), valueAt(index))
+ }
+}
+
+inline fun <T> LongSparseArray<T>.forEachReversedIndexed(action: (Int, Long, T) -> Unit) {
+ for (index in lastIndex downTo 0) {
+ action(index, keyAt(index), valueAt(index))
+ }
+}
+
+inline fun <T> LongSparseArray<T>.getOrPut(key: Long, defaultValue: () -> T): T {
+ val index = indexOfKey(key)
+ return if (index >= 0) {
+ valueAt(index)
+ } else {
+ defaultValue().also { put(key, it) }
+ }
+}
+
+inline val <T> LongSparseArray<T>.lastIndex: Int
+ get() = size - 1
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun <T> LongSparseArray<T>.minusAssign(key: Long) {
+ delete(key)
+}
+
+inline fun <T> LongSparseArray<T>.noneIndexed(predicate: (Int, Long, T) -> Boolean): Boolean {
+ forEachIndexed { index, key, value ->
+ if (predicate(index, key, value)) {
+ return false
+ }
+ }
+ return true
+}
+
+inline fun <T> LongSparseArray<T>.removeAllIndexed(predicate: (Int, Long, T) -> Boolean): Boolean {
+ var isChanged = false
+ forEachReversedIndexed { index, key, value ->
+ if (predicate(index, key, value)) {
+ removeAt(index)
+ isChanged = true
+ }
+ }
+ return isChanged
+}
+
+inline fun <T> LongSparseArray<T>.retainAllIndexed(predicate: (Int, Long, T) -> Boolean): Boolean {
+ var isChanged = false
+ forEachReversedIndexed { index, key, value ->
+ if (!predicate(index, key, value)) {
+ removeAt(index)
+ isChanged = true
+ }
+ }
+ return isChanged
+}
+
+inline val <T> LongSparseArray<T>.size: Int
+ get() = size()
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun <T> LongSparseArray<T>.set(key: Long, value: T) {
+ put(key, value)
+}
diff --git a/services/permission/java/com/android/server/permission/access/collection/SparseIntArrayExtensions.kt b/services/permission/java/com/android/server/permission/access/collection/SparseIntArrayExtensions.kt
new file mode 100644
index 0000000..a582431
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/collection/SparseIntArrayExtensions.kt
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2023 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.permission.access.collection
+
+import android.util.SparseIntArray
+
+inline fun SparseIntArray.allIndexed(predicate: (Int, Int, Int) -> Boolean): Boolean {
+ forEachIndexed { index, key, value ->
+ if (!predicate(index, key, value)) {
+ return false
+ }
+ }
+ return true
+}
+
+inline fun SparseIntArray.anyIndexed(predicate: (Int, Int, Int) -> Boolean): Boolean {
+ forEachIndexed { index, key, value ->
+ if (predicate(index, key, value)) {
+ return true
+ }
+ }
+ return false
+}
+
+inline fun SparseIntArray.forEachIndexed(action: (Int, Int, Int) -> Unit) {
+ for (index in 0 until size) {
+ action(index, keyAt(index), valueAt(index))
+ }
+}
+
+inline fun SparseIntArray.forEachReversedIndexed(action: (Int, Int, Int) -> Unit) {
+ for (index in lastIndex downTo 0) {
+ action(index, keyAt(index), valueAt(index))
+ }
+}
+
+inline fun SparseIntArray.getOrPut(key: Int, defaultValue: () -> Int): Int {
+ val index = indexOfKey(key)
+ return if (index >= 0) {
+ valueAt(index)
+ } else {
+ defaultValue().also { put(key, it) }
+ }
+}
+
+inline val SparseIntArray.lastIndex: Int
+ get() = size - 1
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun SparseIntArray.minusAssign(key: Int) {
+ delete(key)
+}
+
+inline fun SparseIntArray.noneIndexed(predicate: (Int, Int, Int) -> Boolean): Boolean {
+ forEachIndexed { index, key, value ->
+ if (predicate(index, key, value)) {
+ return false
+ }
+ }
+ return true
+}
+
+fun SparseIntArray.remove(key: Int) {
+ delete(key)
+}
+
+fun SparseIntArray.remove(key: Int, defaultValue: Int): Int {
+ val index = indexOfKey(key)
+ return if (index >= 0) {
+ val oldValue = valueAt(index)
+ removeAt(index)
+ oldValue
+ } else {
+ defaultValue
+ }
+}
+
+inline fun SparseIntArray.removeAllIndexed(predicate: (Int, Int, Int) -> Boolean): Boolean {
+ var isChanged = false
+ forEachReversedIndexed { index, key, value ->
+ if (predicate(index, key, value)) {
+ removeAt(index)
+ isChanged = true
+ }
+ }
+ return isChanged
+}
+
+inline fun SparseIntArray.retainAllIndexed(predicate: (Int, Int, Int) -> Boolean): Boolean {
+ var isChanged = false
+ forEachReversedIndexed { index, key, value ->
+ if (!predicate(index, key, value)) {
+ removeAt(index)
+ isChanged = true
+ }
+ }
+ return isChanged
+}
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun SparseIntArray.set(key: Int, value: Int) {
+ put(key, value)
+}
+
+inline val SparseIntArray.size: Int
+ get() = size()
diff --git a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
index 4b086b3..67df67f 100644
--- a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
@@ -227,25 +227,59 @@
if (isRequestedBySystemPackage) {
return@forEach
}
- val oldFlags = getPermissionFlags(appId, userId, permissionName)
- var newFlags = oldFlags andInv PermissionFlags.UPGRADE_EXEMPT
- val isExempt = newFlags.hasAnyBit(PermissionFlags.MASK_EXEMPT)
- newFlags =
- if (permission.isHardRestricted && !isExempt) {
- newFlags or PermissionFlags.RESTRICTION_REVOKED
- } else {
- newFlags andInv PermissionFlags.RESTRICTION_REVOKED
- }
- newFlags =
- if (permission.isSoftRestricted && !isExempt) {
- newFlags or PermissionFlags.SOFT_RESTRICTED
- } else {
- newFlags andInv PermissionFlags.SOFT_RESTRICTED
- }
- setPermissionFlags(appId, userId, permissionName, newFlags)
+ updatePermissionExemptFlags(
+ appId,
+ userId,
+ permission,
+ PermissionFlags.UPGRADE_EXEMPT,
+ 0
+ )
}
}
+ fun MutateStateScope.updatePermissionExemptFlags(
+ appId: Int,
+ userId: Int,
+ permission: Permission,
+ exemptFlagMask: Int,
+ exemptFlagValues: Int
+ ) {
+ val permissionName = permission.name
+ val oldFlags = getPermissionFlags(appId, userId, permissionName)
+ var newFlags = (oldFlags andInv exemptFlagMask) or (exemptFlagValues and exemptFlagMask)
+ if (oldFlags == newFlags) {
+ return
+ }
+ val isExempt = newFlags.hasAnyBit(PermissionFlags.MASK_EXEMPT)
+ if (permission.isHardRestricted && !isExempt) {
+ newFlags = newFlags or PermissionFlags.RESTRICTION_REVOKED
+ // If the permission was policy fixed as granted but it is no longer on any of the
+ // allowlists we need to clear the policy fixed flag as allowlisting trumps policy i.e.
+ // policy cannot grant a non grantable permission.
+ if (PermissionFlags.isPermissionGranted(oldFlags)) {
+ newFlags = newFlags andInv PermissionFlags.POLICY_FIXED
+ }
+ } else {
+ newFlags = newFlags andInv PermissionFlags.RESTRICTION_REVOKED
+ }
+ newFlags =
+ if (
+ permission.isSoftRestricted && !isExempt &&
+ !anyPackageInAppId(appId) {
+ permissionName in it.androidPackage!!.requestedPermissions &&
+ isSoftRestrictedPermissionExemptForPackage(it, permissionName)
+ }
+ ) {
+ newFlags or PermissionFlags.SOFT_RESTRICTED
+ } else {
+ newFlags andInv PermissionFlags.SOFT_RESTRICTED
+ }
+ if (oldFlags == newFlags) {
+ return
+ }
+ setPermissionFlags(appId, userId, permissionName, newFlags)
+ }
+
override fun MutateStateScope.onPackageUninstalled(
packageName: String,
appId: Int,
@@ -1118,7 +1152,12 @@
newFlags andInv PermissionFlags.RESTRICTION_REVOKED
}
newFlags =
- if (permission.isSoftRestricted && !isExempt) {
+ if (
+ permission.isSoftRestricted && !isExempt &&
+ !requestingPackageStates.anyIndexed { _, it ->
+ isSoftRestrictedPermissionExemptForPackage(it, permissionName)
+ }
+ ) {
newFlags or PermissionFlags.SOFT_RESTRICTED
} else {
newFlags andInv PermissionFlags.SOFT_RESTRICTED
@@ -1398,6 +1437,17 @@
}
}
+ // See also SoftRestrictedPermissionPolicy.mayGrantPermission()
+ private fun isSoftRestrictedPermissionExemptForPackage(
+ packageState: PackageState,
+ permissionName: String
+ ): Boolean =
+ when (permissionName) {
+ Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE ->
+ packageState.androidPackage!!.targetSdkVersion >= Build.VERSION_CODES.Q
+ else -> false
+ }
+
private inline fun MutateStateScope.anyPackageInAppId(
appId: Int,
state: AccessState = newState,
diff --git a/services/permission/java/com/android/server/permission/access/permission/PermissionFlags.kt b/services/permission/java/com/android/server/permission/access/permission/PermissionFlags.kt
index 28889de..c5c921d 100644
--- a/services/permission/java/com/android/server/permission/access/permission/PermissionFlags.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/PermissionFlags.kt
@@ -346,9 +346,18 @@
return flags.hasBits(RUNTIME_GRANTED)
}
- fun isAppOpGranted(flags: Int): Boolean =
- isPermissionGranted(flags) && !flags.hasBits(RESTRICTION_REVOKED) &&
- !flags.hasBits(APP_OP_REVOKED)
+ fun isAppOpGranted(flags: Int): Boolean {
+ if (!isPermissionGranted(flags)) {
+ return false
+ }
+ if (flags.hasAnyBit(MASK_RESTRICTED)) {
+ return false
+ }
+ if (flags.hasBits(APP_OP_REVOKED)) {
+ return false
+ }
+ return true
+ }
fun toApiFlags(flags: Int): Int {
var apiFlags = 0
diff --git a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
index 0704c8f..0b58543 100644
--- a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
@@ -88,7 +88,6 @@
import com.android.server.pm.PackageManagerLocal
import com.android.server.pm.UserManagerInternal
import com.android.server.pm.UserManagerService
-import com.android.server.pm.parsing.pkg.AndroidPackageUtils
import com.android.server.pm.permission.LegacyPermission
import com.android.server.pm.permission.LegacyPermissionSettings
import com.android.server.pm.permission.LegacyPermissionState
@@ -97,7 +96,6 @@
import com.android.server.pm.permission.PermissionManagerServiceInternal
import com.android.server.pm.pkg.AndroidPackage
import com.android.server.pm.pkg.PackageState
-import com.android.server.policy.SoftRestrictedPermissionPolicy
import java.io.FileDescriptor
import java.io.PrintWriter
import java.util.concurrent.CompletableFuture
@@ -1006,25 +1004,14 @@
}
if (isGranted && oldFlags.hasBits(PermissionFlags.SOFT_RESTRICTED)) {
- // TODO: Refactor SoftRestrictedPermissionPolicy.
- val softRestrictedPermissionPolicy =
- SoftRestrictedPermissionPolicy.forPermission(
- context,
- AndroidPackageUtils.generateAppInfoWithoutState(androidPackage),
- androidPackage,
- UserHandle.of(userId),
- permissionName
+ if (reportError) {
+ Slog.e(
+ LOG_TAG,
+ "$methodName: Cannot grant soft-restricted non-exempt permission" +
+ " $permissionName to package $packageName"
)
- if (!softRestrictedPermissionPolicy.mayGrantPermission()) {
- if (reportError) {
- Slog.e(
- LOG_TAG,
- "$methodName: Cannot grant soft-restricted non-exempt permission" +
- " $permissionName to package $packageName"
- )
- }
- return
}
+ return
}
val newFlags = PermissionFlags.updateRuntimePermissionGranted(oldFlags, isGranted)
@@ -1135,25 +1122,23 @@
return emptyMap()
}
- val permissionFlagsMap =
- service.getState {
+ service.getState {
+ val permissionFlags =
if (deviceId == VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT) {
with(policy) { getAllPermissionFlags(packageState.appId, userId) }
} else {
with(devicePolicy) {
getAllPermissionFlags(packageState.appId, deviceId, userId)
}
- }
+ } ?: return emptyMap()
+ val permissionStates = ArrayMap<String, PermissionState>()
+ permissionFlags.forEachIndexed { _, permissionName, flags ->
+ val granted = isPermissionGranted(packageState, userId, permissionName, deviceId)
+ val apiFlags = PermissionFlags.toApiFlags(flags)
+ permissionStates[permissionName] = PermissionState(granted, apiFlags)
}
- ?: return emptyMap()
-
- val permissionStates = ArrayMap<String, PermissionState>()
- permissionFlagsMap.forEachIndexed { _, permissionName, flags ->
- val granted = PermissionFlags.isPermissionGranted(flags)
- val apiFlags = PermissionFlags.toApiFlags(flags)
- permissionStates[permissionName] = PermissionState(granted, apiFlags)
+ return permissionStates
}
- return permissionStates
}
override fun isPermissionRevokedByPolicy(
@@ -1852,10 +1837,19 @@
allowlistedFlags: Int,
userId: Int
) {
+ var exemptMask = 0
+ if (allowlistedFlags.hasBits(PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM)) {
+ exemptMask = exemptMask or PermissionFlags.SYSTEM_EXEMPT
+ }
+ if (allowlistedFlags.hasBits(PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE)) {
+ exemptMask = exemptMask or PermissionFlags.UPGRADE_EXEMPT
+ }
+ if (allowlistedFlags.hasBits(PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER)) {
+ exemptMask = exemptMask or PermissionFlags.INSTALLER_EXEMPT
+ }
+
service.mutateState {
with(policy) {
- val permissionsFlags = getUidPermissionFlags(appId, userId) ?: return@mutateState
-
val permissions = getPermissions()
androidPackage.requestedPermissions.forEachIndexed { _, requestedPermission ->
val permission = permissions[requestedPermission]
@@ -1863,81 +1857,8 @@
return@forEachIndexed
}
- val oldFlags = permissionsFlags[requestedPermission] ?: 0
- val wasGranted = PermissionFlags.isPermissionGranted(oldFlags)
-
- var newFlags = oldFlags
- var mask = 0
- var allowlistFlagsCopy = allowlistedFlags
- while (allowlistFlagsCopy != 0) {
- val flag = 1 shl allowlistFlagsCopy.countTrailingZeroBits()
- allowlistFlagsCopy = allowlistFlagsCopy and flag.inv()
- when (flag) {
- PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM -> {
- mask = mask or PermissionFlags.SYSTEM_EXEMPT
- newFlags =
- if (permissionNames.contains(requestedPermission)) {
- newFlags or PermissionFlags.SYSTEM_EXEMPT
- } else {
- newFlags andInv PermissionFlags.SYSTEM_EXEMPT
- }
- }
- PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE -> {
- mask = mask or PermissionFlags.UPGRADE_EXEMPT
- newFlags =
- if (permissionNames.contains(requestedPermission)) {
- newFlags or PermissionFlags.UPGRADE_EXEMPT
- } else {
- newFlags andInv PermissionFlags.UPGRADE_EXEMPT
- }
- }
- PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER -> {
- mask = mask or PermissionFlags.INSTALLER_EXEMPT
- newFlags =
- if (permissionNames.contains(requestedPermission)) {
- newFlags or PermissionFlags.INSTALLER_EXEMPT
- } else {
- newFlags andInv PermissionFlags.INSTALLER_EXEMPT
- }
- }
- }
- }
-
- if (oldFlags == newFlags) {
- return@forEachIndexed
- }
-
- val isExempt = newFlags.hasAnyBit(PermissionFlags.MASK_EXEMPT)
-
- // If the permission is policy fixed as granted but it is no longer
- // on any of the allowlists we need to clear the policy fixed flag
- // as allowlisting trumps policy i.e. policy cannot grant a non
- // grantable permission.
- if (oldFlags.hasBits(PermissionFlags.POLICY_FIXED)) {
- if (!isExempt && wasGranted) {
- mask = mask or PermissionFlags.POLICY_FIXED
- newFlags = newFlags andInv PermissionFlags.POLICY_FIXED
- }
- }
-
- newFlags =
- if (permission.isHardRestricted && !isExempt) {
- newFlags or PermissionFlags.RESTRICTION_REVOKED
- } else {
- newFlags andInv PermissionFlags.RESTRICTION_REVOKED
- }
- newFlags =
- if (permission.isSoftRestricted && !isExempt) {
- newFlags or PermissionFlags.SOFT_RESTRICTED
- } else {
- newFlags andInv PermissionFlags.SOFT_RESTRICTED
- }
- mask =
- mask or
- PermissionFlags.RESTRICTION_REVOKED or
- PermissionFlags.SOFT_RESTRICTED
-
- updatePermissionFlags(appId, userId, requestedPermission, mask, newFlags)
+ var exemptFlags = if (requestedPermission in permissionNames) exemptMask else 0
+ updatePermissionExemptFlags(appId, userId, permission, exemptMask, exemptFlags)
}
}
}
@@ -2905,5 +2826,8 @@
} else {
emptySet<String>()
}
+
+ fun getFullerPermission(permissionName: String): String? =
+ FULLER_PERMISSIONS[permissionName]
}
}
diff --git a/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyTest.kt b/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyTest.kt
index cde46ab..96753b6 100644
--- a/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyTest.kt
+++ b/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyTest.kt
@@ -233,24 +233,6 @@
.isEqualTo(expectedNewFlags)
}
- @Test
- fun testOnPackageInstalled_restrictedPermissionsIsExempted_clearsRestrictionFlags() {
- val oldFlags = PermissionFlags.SOFT_RESTRICTED or PermissionFlags.INSTALLER_EXEMPT
- testOnPackageInstalled(
- oldFlags,
- permissionInfoFlags = PermissionInfo.FLAG_SOFT_RESTRICTED
- ) {}
- val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0)
- val expectedNewFlags = PermissionFlags.INSTALLER_EXEMPT
- assertWithMessage(
- "After onPackageInstalled() is called for a non-system app that requests a runtime" +
- " soft restricted permission that is exempted. The actual permission flags" +
- " $actualFlags should match the expected flags $expectedNewFlags"
- )
- .that(actualFlags)
- .isEqualTo(expectedNewFlags)
- }
-
private fun testOnPackageInstalled(
oldFlags: Int,
permissionInfoFlags: Int = 0,
diff --git a/services/tests/mockingservicestests/src/com/android/server/rollback/Android.bp b/services/tests/mockingservicestests/src/com/android/server/rollback/Android.bp
new file mode 100644
index 0000000..e94b8ad
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/rollback/Android.bp
@@ -0,0 +1,56 @@
+// Copyright (C) 2024 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 {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test {
+ name: "RollbackPackageHealthObserverTests",
+
+ srcs: [
+ "*.java",
+ ],
+
+ static_libs: [
+ "androidx.test.runner",
+ "mockito-target-extended-minus-junit4",
+ "services.core",
+ "truth",
+ "flag-junit",
+ ],
+
+ libs: [
+ "android.test.mock",
+ "android.test.base",
+ "android.test.runner",
+ ],
+
+ jni_libs: [
+ "libdexmakerjvmtiagent",
+ "libstaticjvmtiagent",
+ ],
+
+ certificate: "platform",
+ platform_apis: true,
+ test_suites: [
+ "device-tests",
+ "automotive-tests",
+ ],
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/rollback/AndroidManifest.xml b/services/tests/mockingservicestests/src/com/android/server/rollback/AndroidManifest.xml
new file mode 100644
index 0000000..c52dbde
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/rollback/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.server.rollback">
+
+ <uses-sdk android:targetSdkVersion="35" />
+
+ <application android:testOnly="true"
+ android:debuggable="true">
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.server.rollback"
+ android:label="Frameworks Rollback Package Health Observer test" />
+</manifest>
diff --git a/services/tests/mockingservicestests/src/com/android/server/rollback/AndroidTest.xml b/services/tests/mockingservicestests/src/com/android/server/rollback/AndroidTest.xml
new file mode 100644
index 0000000..635183c
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/rollback/AndroidTest.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 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.
+ -->
+<configuration description="Runs Rollback Package Health Observer Tests.">
+ <option name="test-suite-tag" value="apct" />
+ <option name="test-suite-tag" value="apct-instrumentation" />
+
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="install-arg" value="-t" />
+ <option name="test-file-name" value="RollbackPackageHealthObserverTests.apk" />
+ </target_preparer>
+
+ <option name="test-tag" value="RollbackPackageHealthObserverTests" />
+
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="com.android.server.rollback" />
+ <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+ <option name="hidden-api-checks" value="false"/>
+ </test>
+</configuration>
diff --git a/services/tests/mockingservicestests/src/com/android/server/rollback/TEST_MAPPING b/services/tests/mockingservicestests/src/com/android/server/rollback/TEST_MAPPING
index e42bdad..6ac56bf 100644
--- a/services/tests/mockingservicestests/src/com/android/server/rollback/TEST_MAPPING
+++ b/services/tests/mockingservicestests/src/com/android/server/rollback/TEST_MAPPING
@@ -1,7 +1,7 @@
{
"postsubmit": [
{
- "name": "FrameworksMockingServicesTests",
+ "name": "RollbackPackageHealthObserverTests",
"options": [
{
"include-filter": "com.android.server.rollback"
diff --git a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperCropperTest.java b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperCropperTest.java
new file mode 100644
index 0000000..7ecc7fd
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperCropperTest.java
@@ -0,0 +1,640 @@
+/*
+ * Copyright (C) 2024 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.wallpaper;
+
+import static android.app.WallpaperManager.LANDSCAPE;
+import static android.app.WallpaperManager.ORIENTATION_UNKNOWN;
+import static android.app.WallpaperManager.PORTRAIT;
+import static android.app.WallpaperManager.SQUARE_LANDSCAPE;
+import static android.app.WallpaperManager.SQUARE_PORTRAIT;
+import static android.app.WallpaperManager.getOrientation;
+import static android.app.WallpaperManager.getRotatedOrientation;
+
+import static com.android.window.flags.Flags.FLAG_MULTI_CROP;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.when;
+import static org.mockito.MockitoAnnotations.initMocks;
+
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.util.SparseArray;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+
+import java.util.Comparator;
+import java.util.List;
+
+/**
+ * Unit tests for the most important helpers of {@link WallpaperCropper}, in particular
+ * {@link WallpaperCropper#getCrop(Point, Point, SparseArray, boolean)}.
+ */
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+@RequiresFlagsEnabled(FLAG_MULTI_CROP)
+public class WallpaperCropperTest {
+
+ @Mock
+ private WallpaperDisplayHelper mWallpaperDisplayHelper;
+ private WallpaperCropper mWallpaperCropper;
+
+ private static final Point PORTRAIT_ONE = new Point(500, 800);
+ private static final Point PORTRAIT_TWO = new Point(400, 1000);
+ private static final Point PORTRAIT_THREE = new Point(2000, 800);
+ private static final Point PORTRAIT_FOUR = new Point(1600, 1000);
+
+ private static final Point SQUARE_PORTRAIT_ONE = new Point(1000, 800);
+ private static final Point SQUARE_LANDSCAPE_ONE = new Point(800, 1000);
+
+ /**
+ * Common device: a single screen of portrait/landscape orientation
+ */
+ private static final List<Point> STANDARD_DISPLAY = List.of(PORTRAIT_ONE);
+
+ /** 1: folded: portrait, unfolded: square with w < h */
+ private static final List<Point> FOLDABLE_ONE = List.of(PORTRAIT_ONE, SQUARE_PORTRAIT_ONE);
+
+ /** 2: folded: portrait, unfolded: square with w > h */
+ private static final List<Point> FOLDABLE_TWO = List.of(PORTRAIT_TWO, SQUARE_LANDSCAPE_ONE);
+
+ /** 3: folded: square with w < h, unfolded: portrait */
+ private static final List<Point> FOLDABLE_THREE = List.of(SQUARE_PORTRAIT_ONE, PORTRAIT_THREE);
+
+ /** 4: folded: square with w > h, unfolded: portrait */
+ private static final List<Point> FOLDABLE_FOUR = List.of(SQUARE_LANDSCAPE_ONE, PORTRAIT_FOUR);
+
+ /**
+ * List of different sets of displays for foldable devices. Foldable devices have two displays:
+ * a folded (smaller) unfolded (larger).
+ */
+ private static final List<List<Point>> ALL_FOLDABLE_DISPLAYS = List.of(
+ FOLDABLE_ONE, FOLDABLE_TWO, FOLDABLE_THREE, FOLDABLE_FOUR);
+
+ private SparseArray<Point> mDisplaySizes = new SparseArray<>();
+ private int mFolded = ORIENTATION_UNKNOWN;
+ private int mFoldedRotated = ORIENTATION_UNKNOWN;
+ private int mUnfolded = ORIENTATION_UNKNOWN;
+ private int mUnfoldedRotated = ORIENTATION_UNKNOWN;
+
+ private static final List<Integer> ALL_MODES = List.of(
+ WallpaperCropper.ADD, WallpaperCropper.REMOVE, WallpaperCropper.BALANCE);
+
+ @Before
+ public void setUp() {
+ initMocks(this);
+ mWallpaperCropper = new WallpaperCropper(mWallpaperDisplayHelper);
+ }
+
+ private void setUpWithDisplays(List<Point> displaySizes) {
+ mDisplaySizes = new SparseArray<>();
+ displaySizes.forEach(size -> {
+ mDisplaySizes.put(getOrientation(size), size);
+ Point rotated = new Point(size.y, size.x);
+ mDisplaySizes.put(getOrientation(rotated), rotated);
+ });
+ when(mWallpaperDisplayHelper.getDefaultDisplaySizes()).thenReturn(mDisplaySizes);
+ if (displaySizes.size() == 2) {
+ Point largestDisplay = displaySizes.stream().max(
+ Comparator.comparingInt(p -> p.x * p.y)).get();
+ Point smallestDisplay = displaySizes.stream().min(
+ Comparator.comparingInt(p -> p.x * p.y)).get();
+ mUnfolded = getOrientation(largestDisplay);
+ mFolded = getOrientation(smallestDisplay);
+ mUnfoldedRotated = getRotatedOrientation(mUnfolded);
+ mFoldedRotated = getRotatedOrientation(mFolded);
+ }
+ doAnswer(invocation -> getFoldedOrientation(invocation.getArgument(0)))
+ .when(mWallpaperDisplayHelper).getFoldedOrientation(anyInt());
+ doAnswer(invocation -> getUnfoldedOrientation(invocation.getArgument(0)))
+ .when(mWallpaperDisplayHelper).getUnfoldedOrientation(anyInt());
+ }
+
+ private int getFoldedOrientation(int orientation) {
+ if (orientation == ORIENTATION_UNKNOWN) return ORIENTATION_UNKNOWN;
+ if (orientation == mUnfolded) return mFolded;
+ if (orientation == mUnfoldedRotated) return mFoldedRotated;
+ return ORIENTATION_UNKNOWN;
+ }
+
+ private int getUnfoldedOrientation(int orientation) {
+ if (orientation == ORIENTATION_UNKNOWN) return ORIENTATION_UNKNOWN;
+ if (orientation == mFolded) return mUnfolded;
+ if (orientation == mFoldedRotated) return mUnfoldedRotated;
+ return ORIENTATION_UNKNOWN;
+ }
+
+ /**
+ * Test that {@link WallpaperCropper#noParallax} successfully removes the parallax in a simple
+ * case, removing the right or left part depending on the "rtl" argument.
+ */
+ @Test
+ public void testNoParallax_noScale() {
+ Point displaySize = new Point(1000, 1000);
+ Point bitmapSize = new Point(1200, 1000);
+ Point expectedCropSize = new Point(1000, 1000);
+ Rect crop = new Rect(0, 0, bitmapSize.x, bitmapSize.y);
+ assertThat(WallpaperCropper.noParallax(crop, displaySize, bitmapSize, /* rtl */ false))
+ .isEqualTo(leftOf(crop, expectedCropSize));
+ assertThat(WallpaperCropper.noParallax(crop, displaySize, bitmapSize, /* rtl */ true))
+ .isEqualTo(rightOf(crop, expectedCropSize));
+ }
+
+ /**
+ * Test that {@link WallpaperCropper#noParallax} correctly takes zooming into account.
+ */
+ @Test
+ public void testNoParallax_withScale() {
+ Point displaySize = new Point(1000, 1000);
+ Point bitmapSize = new Point(600, 500);
+ Rect crop = new Rect(0, 0, bitmapSize.x, bitmapSize.y);
+ Point expectedCropSize = new Point(500, 500);
+ assertThat(WallpaperCropper.noParallax(crop, displaySize, bitmapSize, /* rtl */ false))
+ .isEqualTo(leftOf(crop, expectedCropSize));
+ assertThat(WallpaperCropper.noParallax(crop, displaySize, bitmapSize, /* rtl */ true))
+ .isEqualTo(rightOf(crop, expectedCropSize));
+ }
+
+ /**
+ * Test that {@link WallpaperCropper#noParallax} correctly removes parallax when the image is
+ * cropped, i.e. when the crop rectangle is not the full bitmap.
+ */
+ @Test
+ public void testNoParallax_withScaleAndCrop() {
+ Point displaySize = new Point(1000, 1000);
+ Point bitmapSize = new Point(2000, 2000);
+ Rect crop = new Rect(300, 1000, 900, 1500);
+ Point expectedCropSize = new Point(500, 500);
+ assertThat(WallpaperCropper.noParallax(crop, displaySize, bitmapSize, /* rtl */ false))
+ .isEqualTo(leftOf(crop, expectedCropSize));
+ assertThat(WallpaperCropper.noParallax(crop, displaySize, bitmapSize, /* rtl */ true))
+ .isEqualTo(rightOf(crop, expectedCropSize));
+ }
+
+ /**
+ * Test that {@link WallpaperCropper#getAdjustedCrop} does nothing when the crop has the same
+ * width/height ratio than the screen.
+ */
+ @Test
+ public void testGetAdjustedCrop_noOp() {
+ Point displaySize = new Point(1000, 1000);
+
+ for (Point bitmapSize: List.of(
+ new Point(1000, 1000),
+ new Point(2000, 2000),
+ new Point(500, 500))) {
+ for (Rect crop: List.of(
+ new Rect(0, 0, bitmapSize.x, bitmapSize.y),
+ new Rect(100, 200, bitmapSize.x - 100, bitmapSize.y))) {
+ for (int mode: ALL_MODES) {
+ for (boolean rtl: List.of(true, false)) {
+ for (boolean parallax: List.of(true, false)) {
+ assertThat(WallpaperCropper.getAdjustedCrop(
+ crop, bitmapSize, displaySize, parallax, rtl, mode))
+ .isEqualTo(crop);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Test that {@link WallpaperCropper#getAdjustedCrop}, when called with parallax = true,
+ * does not keep more width than needed for {@link WallpaperCropper#MAX_PARALLAX}.
+ */
+ @Test
+ public void testGetAdjustedCrop_tooMuchParallax() {
+ Point displaySize = new Point(1000, 1000);
+ int tooLargeWidth = (int) (displaySize.x * (1 + 2 * WallpaperCropper.MAX_PARALLAX));
+ Point bitmapSize = new Point(tooLargeWidth, 1000);
+ Rect crop = new Rect(0, 0, bitmapSize.x, bitmapSize.y);
+ int expectedWidth = (int) (displaySize.x * (1 + WallpaperCropper.MAX_PARALLAX));
+ Point expectedCropSize = new Point(expectedWidth, 1000);
+ for (int mode: ALL_MODES) {
+ assertThat(WallpaperCropper.getAdjustedCrop(
+ crop, bitmapSize, displaySize, true, false, mode))
+ .isEqualTo(leftOf(crop, expectedCropSize));
+ assertThat(WallpaperCropper.getAdjustedCrop(
+ crop, bitmapSize, displaySize, true, true, mode))
+ .isEqualTo(rightOf(crop, expectedCropSize));
+ }
+ }
+
+ /**
+ * Test that {@link WallpaperCropper#getAdjustedCrop}, when called with parallax = true,
+ * does not remove parallax if the parallax is below {@link WallpaperCropper#MAX_PARALLAX}.
+ */
+ @Test
+ public void testGetAdjustedCrop_acceptableParallax() {
+ Point displaySize = new Point(1000, 1000);
+ List<Integer> acceptableWidths = List.of(displaySize.x,
+ (int) (displaySize.x * (1 + 0.5 * WallpaperCropper.MAX_PARALLAX)),
+ (int) (displaySize.x * (1 + 0.9 * WallpaperCropper.MAX_PARALLAX)),
+ (int) (displaySize.x * (1 + 1.0 * WallpaperCropper.MAX_PARALLAX)));
+ for (int acceptableWidth: acceptableWidths) {
+ Point bitmapSize = new Point(acceptableWidth, 1000);
+ Rect crop = new Rect(0, 0, bitmapSize.x, bitmapSize.y);
+ for (int mode : ALL_MODES) {
+ for (boolean rtl : List.of(false, true)) {
+ assertThat(WallpaperCropper.getAdjustedCrop(
+ crop, bitmapSize, displaySize, true, rtl, mode))
+ .isEqualTo(crop);
+ }
+ }
+ }
+ }
+
+ /**
+ * Test that {@link WallpaperCropper#getAdjustedCrop}, when called with
+ * {@link WallpaperCropper#ADD}, correctly enlarges the crop to match the display dimensions,
+ * and adds content to the crop by an equal amount on both sides when possible.
+ */
+ @Test
+ public void testGetAdjustedCrop_add() {
+ Point displaySize = new Point(1000, 1000);
+ Point bitmapSize = new Point(1000, 1000);
+
+ List<Rect> crops = List.of(
+ new Rect(0, 0, 900, 1000),
+ new Rect(0, 0, 1000, 900),
+ new Rect(0, 0, 400, 500),
+ new Rect(500, 600, 1000, 1000));
+
+ List<Rect> expectedAdjustedCrops = List.of(
+ new Rect(0, 0, 1000, 1000),
+ new Rect(0, 0, 1000, 1000),
+ new Rect(0, 0, 500, 500),
+ new Rect(500, 500, 1000, 1000));
+
+ for (int i = 0; i < crops.size(); i++) {
+ Rect crop = crops.get(i);
+ Rect expectedCrop = expectedAdjustedCrops.get(i);
+ for (boolean rtl: List.of(false, true)) {
+ assertThat(WallpaperCropper.getAdjustedCrop(
+ crop, bitmapSize, displaySize, false, rtl, WallpaperCropper.ADD))
+ .isEqualTo(expectedCrop);
+ }
+ }
+ }
+
+ /**
+ * Test that {@link WallpaperCropper#getAdjustedCrop}, when called with
+ * {@link WallpaperCropper#REMOVE}, correctly shrinks the crop to match the display dimensions,
+ * and removes content by an equal amount on both sides.
+ */
+ @Test
+ public void testGetAdjustedCrop_remove() {
+ Point displaySize = new Point(1000, 1000);
+ Point bitmapSize = new Point(1500, 1500);
+
+ List<Rect> crops = List.of(
+ new Rect(50, 0, 1150, 1000),
+ new Rect(0, 50, 1000, 1150));
+
+ Point expectedCropSize = new Point(1000, 1000);
+
+ for (Rect crop: crops) {
+ for (boolean rtl : List.of(false, true)) {
+ assertThat(WallpaperCropper.getAdjustedCrop(
+ crop, bitmapSize, displaySize, false, rtl, WallpaperCropper.REMOVE))
+ .isEqualTo(centerOf(crop, expectedCropSize));
+ }
+ }
+ }
+
+ /**
+ * Test that {@link WallpaperCropper#getAdjustedCrop}, when called with
+ * {@link WallpaperCropper#BALANCE}, gives an adjusted crop with the same center and same number
+ * of pixels when possible.
+ */
+ @Test
+ public void testGetAdjustedCrop_balance() {
+ Point displaySize = new Point(500, 1000);
+ Point transposedDisplaySize = new Point(1000, 500);
+ Point bitmapSize = new Point(1000, 1000);
+
+ List<Rect> crops = List.of(
+ new Rect(0, 250, 1000, 750),
+ new Rect(100, 0, 300, 100));
+
+ List<Rect> expectedAdjustedCrops = List.of(
+ new Rect(250, 0, 750, 1000),
+ new Rect(150, 0, 250, 200));
+
+ for (int i = 0; i < crops.size(); i++) {
+ Rect crop = crops.get(i);
+ Rect expected = expectedAdjustedCrops.get(i);
+ assertThat(WallpaperCropper.getAdjustedCrop(
+ crop, bitmapSize, displaySize, false, false, WallpaperCropper.BALANCE))
+ .isEqualTo(expected);
+
+ Rect transposedCrop = new Rect(crop.top, crop.left, crop.bottom, crop.right);
+ Rect expectedTransposed = new Rect(
+ expected.top, expected.left, expected.bottom, expected.right);
+ assertThat(WallpaperCropper.getAdjustedCrop(transposedCrop, bitmapSize,
+ transposedDisplaySize, false, false, WallpaperCropper.BALANCE))
+ .isEqualTo(expectedTransposed);
+ }
+ }
+
+ /**
+ * Test that {@link WallpaperCropper#getCrop} follows a simple centre-align strategy when
+ * no suggested crops are provided.
+ */
+ @Test
+ public void testGetCrop_noSuggestedCrops_centersWallpaper() {
+ setUpWithDisplays(STANDARD_DISPLAY);
+ Point bitmapSize = new Point(800, 1000);
+ Rect bitmapRect = new Rect(0, 0, bitmapSize.x, bitmapSize.y);
+ SparseArray<Rect> suggestedCrops = new SparseArray<>();
+
+ List<Point> displaySizes = List.of(
+ new Point(500, 1000),
+ new Point(1000, 500));
+ List<Point> expectedCropSizes = List.of(
+ new Point(500, 1000),
+ new Point(800, 400));
+
+ for (int i = 0; i < displaySizes.size(); i++) {
+ Point displaySize = displaySizes.get(i);
+ Point expectedCropSize = expectedCropSizes.get(i);
+ for (boolean rtl : List.of(false, true)) {
+ assertThat(mWallpaperCropper.getCrop(
+ displaySize, bitmapSize, suggestedCrops, rtl))
+ .isEqualTo(centerOf(bitmapRect, expectedCropSize));
+ }
+ }
+ }
+
+ /**
+ * Test that {@link WallpaperCropper#getCrop} reuses a suggested crop of the same orientation
+ * as the display if possible, and does not remove additional width for parallax,
+ * but adds width if necessary.
+ */
+ @Test
+ public void testGetCrop_hasSuggestedCrop() {
+ setUpWithDisplays(STANDARD_DISPLAY);
+ Point bitmapSize = new Point(800, 1000);
+ SparseArray<Rect> suggestedCrops = new SparseArray<>();
+ suggestedCrops.put(PORTRAIT, new Rect(0, 0, 400, 800));
+ for (int otherOrientation: List.of(LANDSCAPE, SQUARE_LANDSCAPE, SQUARE_PORTRAIT)) {
+ suggestedCrops.put(otherOrientation, new Rect(0, 0, 10, 10));
+ }
+
+ for (boolean rtl : List.of(false, true)) {
+ assertThat(mWallpaperCropper.getCrop(
+ new Point(300, 800), bitmapSize, suggestedCrops, rtl))
+ .isEqualTo(suggestedCrops.get(PORTRAIT));
+ assertThat(mWallpaperCropper.getCrop(
+ new Point(500, 800), bitmapSize, suggestedCrops, rtl))
+ .isEqualTo(new Rect(0, 0, 500, 800));
+ }
+ }
+
+ /**
+ * Test that {@link WallpaperCropper#getCrop}, if there is no suggested crop of the same
+ * orientation as the display, reuses a suggested crop of the rotated orientation if possible,
+ * and preserves the center and number of pixels of the crop if possible.
+ * <p>
+ * To simplify, in this test case all crops have the same size as the display (no zoom)
+ * and are at the center of the image. Also the image is large enough to preserver the number
+ * of pixels (no additional zoom required).
+ */
+ @Test
+ public void testGetCrop_hasRotatedSuggestedCrop() {
+ setUpWithDisplays(STANDARD_DISPLAY);
+ Point bitmapSize = new Point(2000, 1800);
+ Rect bitmapRect = new Rect(0, 0, bitmapSize.x, bitmapSize.y);
+ SparseArray<Rect> suggestedCrops = new SparseArray<>();
+ Point portrait = PORTRAIT_ONE;
+ Point landscape = new Point(PORTRAIT_ONE.y, PORTRAIT_ONE.x);
+ Point squarePortrait = SQUARE_PORTRAIT_ONE;
+ Point squareLandscape = new Point(SQUARE_PORTRAIT_ONE.y, SQUARE_PORTRAIT_ONE.y);
+ suggestedCrops.put(PORTRAIT, centerOf(bitmapRect, portrait));
+ suggestedCrops.put(SQUARE_LANDSCAPE, centerOf(bitmapRect, squareLandscape));
+ for (boolean rtl : List.of(false, true)) {
+ assertThat(mWallpaperCropper.getCrop(
+ landscape, bitmapSize, suggestedCrops, rtl))
+ .isEqualTo(centerOf(bitmapRect, landscape));
+
+ assertThat(mWallpaperCropper.getCrop(
+ squarePortrait, bitmapSize, suggestedCrops, rtl))
+ .isEqualTo(centerOf(bitmapRect, squarePortrait));
+ }
+ }
+
+ /**
+ * Test that {@link WallpaperCropper#getCrop}, when asked for a folded crop with a suggested
+ * crop only for the relative unfolded orientation, creates the folded crop at the center of the
+ * unfolded crop, by removing content on two sides to match the folded screen dimensions.
+ * <p>
+ * To simplify, in this test case all crops have the same size as the display (no zoom)
+ * and are at the center of the image.
+ */
+ @Test
+ public void testGetCrop_hasUnfoldedSuggestedCrop() {
+ for (List<Point> displaySizes : ALL_FOLDABLE_DISPLAYS) {
+ setUpWithDisplays(displaySizes);
+ Point bitmapSize = new Point(2000, 2400);
+ Rect bitmapRect = new Rect(0, 0, bitmapSize.x, bitmapSize.y);
+
+ Point largestDisplay = displaySizes.stream().max(
+ Comparator.comparingInt(a -> a.x * a.y)).orElseThrow();
+ int unfoldedOne = getOrientation(largestDisplay);
+ int unfoldedTwo = getRotatedOrientation(unfoldedOne);
+ Rect unfoldedCropOne = centerOf(bitmapRect, mDisplaySizes.get(unfoldedOne));
+ Rect unfoldedCropTwo = centerOf(bitmapRect, mDisplaySizes.get(unfoldedTwo));
+ SparseArray<Rect> suggestedCrops = new SparseArray<>();
+ suggestedCrops.put(unfoldedOne, unfoldedCropOne);
+ suggestedCrops.put(unfoldedTwo, unfoldedCropTwo);
+
+ int foldedOne = getFoldedOrientation(unfoldedOne);
+ int foldedTwo = getFoldedOrientation(unfoldedTwo);
+ Point foldedDisplayOne = mDisplaySizes.get(foldedOne);
+ Point foldedDisplayTwo = mDisplaySizes.get(foldedTwo);
+
+ for (boolean rtl : List.of(false, true)) {
+ assertThat(mWallpaperCropper.getCrop(
+ foldedDisplayOne, bitmapSize, suggestedCrops, rtl))
+ .isEqualTo(centerOf(unfoldedCropOne, foldedDisplayOne));
+
+ assertThat(mWallpaperCropper.getCrop(
+ foldedDisplayTwo, bitmapSize, suggestedCrops, rtl))
+ .isEqualTo(centerOf(unfoldedCropTwo, foldedDisplayTwo));
+ }
+ }
+ }
+
+ /**
+ * Test that {@link WallpaperCropper#getCrop}, when asked for an unfolded crop with a suggested
+ * crop only for the relative folded orientation, creates the unfolded crop with the same center
+ * as the folded crop, by adding content on two sides to match the unfolded screen dimensions.
+ * <p>
+ * To simplify, in this test case all crops have the same size as the display (no zoom) and are
+ * at the center of the image. Also the image is large enough to add content.
+ */
+ @Test
+ public void testGetCrop_hasFoldedSuggestedCrop() {
+ for (List<Point> displaySizes : ALL_FOLDABLE_DISPLAYS) {
+ setUpWithDisplays(displaySizes);
+ Point bitmapSize = new Point(2000, 2000);
+ Rect bitmapRect = new Rect(0, 0, 2000, 2000);
+
+ Point smallestDisplay = displaySizes.stream().min(
+ Comparator.comparingInt(a -> a.x * a.y)).orElseThrow();
+ int foldedOne = getOrientation(smallestDisplay);
+ int foldedTwo = getRotatedOrientation(foldedOne);
+ Point foldedDisplayOne = mDisplaySizes.get(foldedOne);
+ Point foldedDisplayTwo = mDisplaySizes.get(foldedTwo);
+ Rect foldedCropOne = centerOf(bitmapRect, foldedDisplayOne);
+ Rect foldedCropTwo = centerOf(bitmapRect, foldedDisplayTwo);
+ SparseArray<Rect> suggestedCrops = new SparseArray<>();
+ suggestedCrops.put(foldedOne, foldedCropOne);
+ suggestedCrops.put(foldedTwo, foldedCropTwo);
+
+ int unfoldedOne = getUnfoldedOrientation(foldedOne);
+ int unfoldedTwo = getUnfoldedOrientation(foldedTwo);
+ Point unfoldedDisplayOne = mDisplaySizes.get(unfoldedOne);
+ Point unfoldedDisplayTwo = mDisplaySizes.get(unfoldedTwo);
+
+ for (boolean rtl : List.of(false, true)) {
+ assertThat(centerOf(mWallpaperCropper.getCrop(
+ unfoldedDisplayOne, bitmapSize, suggestedCrops, rtl), foldedDisplayOne))
+ .isEqualTo(foldedCropOne);
+
+ assertThat(centerOf(mWallpaperCropper.getCrop(
+ unfoldedDisplayTwo, bitmapSize, suggestedCrops, rtl), foldedDisplayTwo))
+ .isEqualTo(foldedCropTwo);
+ }
+ }
+ }
+
+ /**
+ * Test that {@link WallpaperCropper#getCrop}, when asked for an folded crop with a suggested
+ * crop only for the rotated unfolded orientation, creates the folded crop from that crop by
+ * combining a rotate + fold operation. The folded crop should have less pixels than the
+ * unfolded crop due to the fold operation which removes content on both sides of the image.
+ * <p>
+ * To simplify, in this test case all crops have the same size as the display (no zoom)
+ * and are at the center of the image.
+ */
+ @Test
+ public void testGetCrop_hasRotatedUnfoldedSuggestedCrop() {
+ for (List<Point> displaySizes : ALL_FOLDABLE_DISPLAYS) {
+ setUpWithDisplays(displaySizes);
+ Point bitmapSize = new Point(2000, 2000);
+ Rect bitmapRect = new Rect(0, 0, 2000, 2000);
+ Point largestDisplay = displaySizes.stream().max(
+ Comparator.comparingInt(a -> a.x * a.y)).orElseThrow();
+ int unfoldedOne = getOrientation(largestDisplay);
+ int unfoldedTwo = getRotatedOrientation(unfoldedOne);
+ for (int unfolded: List.of(unfoldedOne, unfoldedTwo)) {
+ Rect unfoldedCrop = centerOf(bitmapRect, mDisplaySizes.get(unfolded));
+ int rotatedUnfolded = getRotatedOrientation(unfolded);
+ Rect rotatedUnfoldedCrop = centerOf(bitmapRect, mDisplaySizes.get(rotatedUnfolded));
+ SparseArray<Rect> suggestedCrops = new SparseArray<>();
+ suggestedCrops.put(unfolded, unfoldedCrop);
+ int rotatedFolded = getFoldedOrientation(rotatedUnfolded);
+ Point rotatedFoldedDisplay = mDisplaySizes.get(rotatedFolded);
+
+ for (boolean rtl : List.of(false, true)) {
+ assertThat(mWallpaperCropper.getCrop(
+ rotatedFoldedDisplay, bitmapSize, suggestedCrops, rtl))
+ .isEqualTo(centerOf(rotatedUnfoldedCrop, rotatedFoldedDisplay));
+ }
+ }
+ }
+ }
+
+ /**
+ * Test that {@link WallpaperCropper#getCrop}, when asked for an unfolded crop with a suggested
+ * crop only for the rotated folded orientation, creates the unfolded crop from that crop by
+ * combining a rotate + unfold operation. The unfolded crop should have more pixels than the
+ * folded crop due to the unfold operation which adds content on two sides of the image.
+ * <p>
+ * To simplify, in this test case all crops have the same size as the display (no zoom)
+ * and are centered inside the image. Also the image is large enough to add content.
+ */
+ @Test
+ public void testGetCrop_hasRotatedFoldedSuggestedCrop() {
+ for (List<Point> displaySizes : ALL_FOLDABLE_DISPLAYS) {
+ setUpWithDisplays(displaySizes);
+ Point bitmapSize = new Point(2000, 2000);
+ Rect bitmapRect = new Rect(0, 0, 2000, 2000);
+
+ Point smallestDisplay = displaySizes.stream().min(
+ Comparator.comparingInt(a -> a.x * a.y)).orElseThrow();
+ int foldedOne = getOrientation(smallestDisplay);
+ int foldedTwo = getRotatedOrientation(foldedOne);
+ for (int folded: List.of(foldedOne, foldedTwo)) {
+ Rect foldedCrop = centerOf(bitmapRect, mDisplaySizes.get(folded));
+ SparseArray<Rect> suggestedCrops = new SparseArray<>();
+ suggestedCrops.put(folded, foldedCrop);
+ int rotatedFolded = getRotatedOrientation(folded);
+ int rotatedUnfolded = getUnfoldedOrientation(rotatedFolded);
+ Point rotatedFoldedDisplay = mDisplaySizes.get(rotatedFolded);
+ Rect rotatedFoldedCrop = centerOf(bitmapRect, rotatedFoldedDisplay);
+ Point rotatedUnfoldedDisplay = mDisplaySizes.get(rotatedUnfolded);
+
+ for (boolean rtl : List.of(false, true)) {
+ Rect rotatedUnfoldedCrop = mWallpaperCropper.getCrop(
+ rotatedUnfoldedDisplay, bitmapSize, suggestedCrops, rtl);
+ assertThat(centerOf(rotatedUnfoldedCrop, rotatedFoldedDisplay))
+ .isEqualTo(rotatedFoldedCrop);
+ }
+ }
+ }
+ }
+
+ private static Rect centerOf(Rect container, Point point) {
+ checkSubset(container, point);
+ int diffWidth = container.width() - point.x;
+ int diffHeight = container.height() - point.y;
+ int startX = container.left + diffWidth / 2;
+ int startY = container.top + diffHeight / 2;
+ return new Rect(startX, startY, startX + point.x, startY + point.y);
+ }
+
+ private static Rect leftOf(Rect container, Point point) {
+ Rect result = centerOf(container, point);
+ result.offset(container.left - result.left, 0);
+ return result;
+ }
+
+ private static Rect rightOf(Rect container, Point point) {
+ checkSubset(container, point);
+ Rect result = centerOf(container, point);
+ result.offset(container.right - result.right, 0);
+ return result;
+ }
+
+ private static void checkSubset(Rect container, Point point) {
+ if (container.width() < point.x || container.height() < point.y) {
+ throw new IllegalArgumentException();
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/log/ALSProbeTest.java b/services/tests/servicestests/src/com/android/server/biometrics/log/ALSProbeTest.java
index bb00634..fa1fd90 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/log/ALSProbeTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/log/ALSProbeTest.java
@@ -344,6 +344,21 @@
verifyNoMoreInteractions(mSensorManager);
}
+ @Test
+ public void testAwaitLuxWhenNoLightSensor() {
+ when(mSensorManager.getDefaultSensor(Sensor.TYPE_LIGHT)).thenReturn(null);
+ mProbe = new ALSProbe(mSensorManager, new Handler(mLooper.getLooper()), TIMEOUT_MS - 1);
+
+ AtomicInteger lux = new AtomicInteger(-5);
+ mProbe.awaitNextLux((v) -> lux.set(Math.round(v)), null /* handler */);
+
+ // Verify that no light sensor will be registered.
+ verify(mSensorManager, times(0)).registerListener(
+ mSensorEventListenerCaptor.capture(), any(), anyInt());
+
+ assertThat(lux.get()).isEqualTo(-1);
+ }
+
private void moveTimeBy(long millis) {
mLooper.moveTimeForward(millis);
mLooper.processAllMessages();
diff --git a/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java b/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java
index ea84eb2..e0ef035 100644
--- a/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java
@@ -16,8 +16,6 @@
package com.android.server.os;
-import android.app.admin.flags.Flags;
-
import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
import static com.google.common.truth.Truth.assertThat;
@@ -27,15 +25,19 @@
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.when;
+import android.app.admin.DevicePolicyManager;
+import android.app.admin.flags.Flags;
import android.app.role.RoleManager;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Binder;
import android.os.BugreportManager.BugreportCallback;
+import android.os.BugreportParams;
import android.os.IBinder;
import android.os.IDumpstateListener;
import android.os.Process;
import android.os.RemoteException;
+import android.os.UserManager;
import android.platform.test.annotations.RequiresFlagsDisabled;
import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
@@ -75,6 +77,10 @@
@Mock
private PackageManager mPackageManager;
+ @Mock
+ private UserManager mMockUserManager;
+ @Mock
+ private DevicePolicyManager mMockDevicePolicyManager;
private int mCallingUid = 1234;
private String mCallingPackage = "test.package";
@@ -91,10 +97,12 @@
ArraySet<String> mAllowlistedPackages = new ArraySet<>();
mAllowlistedPackages.add(mContext.getPackageName());
mService = new BugreportManagerServiceImpl(
- new BugreportManagerServiceImpl.Injector(mContext, mAllowlistedPackages,
- mMappingFile));
+ new TestInjector(mContext, mAllowlistedPackages, mMappingFile,
+ mMockUserManager, mMockDevicePolicyManager));
mBugreportFileManager = new BugreportManagerServiceImpl.BugreportFileManager(mMappingFile);
when(mPackageManager.getPackageUidAsUser(anyString(), anyInt())).thenReturn(mCallingUid);
+ // The calling user is an admin user by default.
+ when(mMockUserManager.isUserAdmin(anyInt())).thenReturn(true);
}
@After
@@ -182,6 +190,36 @@
}
@Test
+ public void testStartBugreport_throwsForNonAdminUser() throws Exception {
+ when(mMockUserManager.isUserAdmin(anyInt())).thenReturn(false);
+
+ Exception thrown = assertThrows(IllegalArgumentException.class,
+ () -> mService.startBugreport(mCallingUid, mContext.getPackageName(),
+ new FileDescriptor(), /* screenshotFd= */ null,
+ BugreportParams.BUGREPORT_MODE_FULL,
+ /* flags= */ 0, new Listener(new CountDownLatch(1)),
+ /* isScreenshotRequested= */ false));
+
+ assertThat(thrown.getMessage()).contains("not an admin user");
+ }
+
+ @Test
+ public void testStartBugreport_throwsForNotAffiliatedUser() throws Exception {
+ when(mMockUserManager.isUserAdmin(anyInt())).thenReturn(false);
+ when(mMockDevicePolicyManager.getDeviceOwnerUserId()).thenReturn(-1);
+ when(mMockDevicePolicyManager.isAffiliatedUser(anyInt())).thenReturn(false);
+
+ Exception thrown = assertThrows(IllegalArgumentException.class,
+ () -> mService.startBugreport(mCallingUid, mContext.getPackageName(),
+ new FileDescriptor(), /* screenshotFd= */ null,
+ BugreportParams.BUGREPORT_MODE_REMOTE,
+ /* flags= */ 0, new Listener(new CountDownLatch(1)),
+ /* isScreenshotRequested= */ false));
+
+ assertThat(thrown.getMessage()).contains("not affiliated to the device owner");
+ }
+
+ @Test
public void testRetrieveBugreportWithoutFilesForCaller() throws Exception {
CountDownLatch latch = new CountDownLatch(1);
Listener listener = new Listener(latch);
@@ -224,7 +262,8 @@
private void clearAllowlist() {
mService = new BugreportManagerServiceImpl(
- new BugreportManagerServiceImpl.Injector(mContext, new ArraySet<>(), mMappingFile));
+ new TestInjector(mContext, new ArraySet<>(), mMappingFile,
+ mMockUserManager, mMockDevicePolicyManager));
}
private static class Listener implements IDumpstateListener {
@@ -275,4 +314,27 @@
complete(successful);
}
}
+
+ private static class TestInjector extends BugreportManagerServiceImpl.Injector {
+
+ private final UserManager mUserManager;
+ private final DevicePolicyManager mDevicePolicyManager;
+
+ TestInjector(Context context, ArraySet<String> allowlistedPackages, AtomicFile mappingFile,
+ UserManager um, DevicePolicyManager dpm) {
+ super(context, allowlistedPackages, mappingFile);
+ mUserManager = um;
+ mDevicePolicyManager = dpm;
+ }
+
+ @Override
+ public UserManager getUserManager() {
+ return mUserManager;
+ }
+
+ @Override
+ public DevicePolicyManager getDevicePolicyManager() {
+ return mDevicePolicyManager;
+ }
+ }
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java
index 33ca5c2..a45b102 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java
@@ -20,6 +20,7 @@
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertTrue;
+import static junit.framework.Assert.fail;
import static org.junit.Assert.assertNull;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.eq;
@@ -32,6 +33,7 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.app.ActivityManager;
import android.app.INotificationManager;
import android.content.ComponentName;
import android.content.Context;
@@ -47,9 +49,12 @@
import android.util.ArraySet;
import android.util.IntArray;
import android.util.Xml;
+import android.Manifest;
+import com.android.internal.util.CollectionUtils;
import com.android.internal.util.function.TriPredicate;
import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.UiServiceTestCase;
import com.android.server.notification.NotificationManagerService.NotificationAssistants;
@@ -59,7 +64,9 @@
import org.mockito.MockitoAnnotations;
import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -89,11 +96,15 @@
UserInfo mZero = new UserInfo(0, "zero", 0);
UserInfo mTen = new UserInfo(10, "ten", 0);
+ ComponentName mCn = new ComponentName("a", "b");
+
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
mContext.setMockPackageManager(mPm);
mContext.addMockSystemService(Context.USER_SERVICE, mUm);
+ mContext.getOrCreateTestableResources().addOverride(
+ com.android.internal.R.string.config_defaultAssistantAccessComponent, "a/a");
mAssistants = spy(mNm.new NotificationAssistants(mContext, mLock, mUserProfiles, miPm));
when(mNm.getBinderService()).thenReturn(mINm);
mContext.ensureTestableResources();
@@ -102,8 +113,9 @@
ResolveInfo resolve = new ResolveInfo();
approved.add(resolve);
ServiceInfo info = new ServiceInfo();
- info.packageName = "a";
- info.name="a";
+ info.packageName = mCn.getPackageName();
+ info.name = mCn.getClassName();
+ info.permission = Manifest.permission.BIND_NOTIFICATION_ASSISTANT_SERVICE;
resolve.serviceInfo = info;
when(mPm.queryIntentServicesAsUser(any(), anyInt(), anyInt()))
.thenReturn(approved);
@@ -137,6 +149,51 @@
}
@Test
+ public void testWriteXml_userTurnedOffNAS() throws Exception {
+ int userId = ActivityManager.getCurrentUser();
+
+ mAssistants.loadDefaultsFromConfig(true);
+
+ mAssistants.setPackageOrComponentEnabled(mCn.flattenToString(), userId, true,
+ true, true);
+
+ ComponentName current = CollectionUtils.firstOrNull(
+ mAssistants.getAllowedComponents(userId));
+ assertNotNull(current);
+ mAssistants.setUserSet(userId, true);
+ mAssistants.setPackageOrComponentEnabled(current.flattenToString(), userId, true, false,
+ true);
+
+ TypedXmlSerializer serializer = Xml.newFastSerializer();
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ serializer.setOutput(new BufferedOutputStream(baos), "utf-8");
+ serializer.startDocument(null, true);
+ mAssistants.writeXml(serializer, true, userId);
+ serializer.endDocument();
+ serializer.flush();
+
+ //fail(baos.toString("UTF-8"));
+
+ final TypedXmlPullParser parser = Xml.newFastPullParser();
+ parser.setInput(new BufferedInputStream(
+ new ByteArrayInputStream(baos.toByteArray())), null);
+ TriPredicate<String, Integer, String> allowedManagedServicePackages =
+ mNm::canUseManagedServices;
+
+ parser.nextTag();
+ mAssistants = spy(mNm.new NotificationAssistants(mContext, mLock, mUserProfiles, miPm));
+ mAssistants.readXml(parser, allowedManagedServicePackages, false, UserHandle.USER_ALL);
+
+ ArrayMap<Boolean, ArraySet<String>> approved = mAssistants.mApproved.get(0);
+ // approved should not be null
+ assertNotNull(approved);
+ assertEquals(new ArraySet<>(), approved.get(true));
+
+ // user set is maintained
+ assertTrue(mAssistants.mIsUserChanged.get(ActivityManager.getCurrentUser()));
+ }
+
+ @Test
public void testReadXml_userDisabled() throws Exception {
String xml = "<enabled_assistants version=\"4\" defaults=\"b/b\">"
+ "<service_listing approved=\"\" user=\"0\" primary=\"true\""
@@ -160,6 +217,33 @@
}
@Test
+ public void testReadXml_userDisabled_restore() throws Exception {
+ String xml = "<enabled_assistants version=\"4\" defaults=\"b/b\">"
+ + "<service_listing approved=\"\" user=\"0\" primary=\"true\""
+ + "user_changed=\"true\"/>"
+ + "</enabled_assistants>";
+
+ final TypedXmlPullParser parser = Xml.newFastPullParser();
+ parser.setInput(new BufferedInputStream(
+ new ByteArrayInputStream(xml.toString().getBytes())), null);
+ TriPredicate<String, Integer, String> allowedManagedServicePackages =
+ mNm::canUseManagedServices;
+
+ parser.nextTag();
+ mAssistants.readXml(parser, allowedManagedServicePackages, true,
+ ActivityManager.getCurrentUser());
+
+ ArrayMap<Boolean, ArraySet<String>> approved = mAssistants.mApproved.get(0);
+
+ // approved should not be null
+ assertNotNull(approved);
+ assertEquals(new ArraySet<>(), approved.get(true));
+
+ // user set is maintained
+ assertTrue(mAssistants.mIsUserChanged.get(ActivityManager.getCurrentUser()));
+ }
+
+ @Test
public void testReadXml_upgradeUserSet() throws Exception {
String xml = "<enabled_assistants version=\"3\" defaults=\"b/b\">"
+ "<service_listing approved=\"\" user=\"0\" primary=\"true\""
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 3499a12..bf8cfa5c 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryJobServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryJobServiceTest.java
@@ -20,8 +20,12 @@
import static junit.framework.Assert.assertTrue;
import static junit.framework.TestCase.assertFalse;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -70,10 +74,11 @@
@Before
public void setUp() throws Exception {
- mJobService = new NotificationHistoryJobService();
+ mJobService = spy(new NotificationHistoryJobService());
mJobService.attachBaseContext(mContext);
mJobService.onCreate();
mJobService.onBind(/* intent= */ null); // Create JobServiceEngine within JobService.
+ doNothing().when(mJobService).jobFinished(any(), eq(false));
mContext.addMockSystemService(JobScheduler.class, mMockJobScheduler);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenAdaptersTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenAdaptersTest.java
index 99d5a6d..75552bc 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenAdaptersTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenAdaptersTest.java
@@ -16,7 +16,7 @@
package com.android.server.notification;
-import static com.android.server.notification.ZenAdapters.notificationPolicyToZenPolicy;
+import static android.service.notification.ZenAdapters.notificationPolicyToZenPolicy;
import static com.google.common.truth.Truth.assertThat;
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index 495e01a..7c1adbc 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -136,6 +136,7 @@
import android.provider.Settings.Global;
import android.service.notification.Condition;
import android.service.notification.DeviceEffectsApplier;
+import android.service.notification.ZenAdapters;
import android.service.notification.ZenDeviceEffects;
import android.service.notification.ZenModeConfig;
import android.service.notification.ZenModeConfig.ConfigChangeOrigin;
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
index 6013063..da11e6a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
@@ -554,7 +554,7 @@
mRootWindowContainer.applySleepTokens(true);
// The display orientation should be changed by the activity so there is no relaunch.
- verify(activity, never()).relaunchActivityLocked(anyBoolean());
+ verify(activity, never()).relaunchActivityLocked(anyBoolean(), anyInt());
assertEquals(rotatedConfig.orientation, display.getConfiguration().orientation);
}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index 1e1dd00..5a52968 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -66,7 +66,6 @@
import android.os.IBinder;
import android.os.Parcel;
import android.os.ParcelFileDescriptor;
-import android.os.PermissionEnforcer;
import android.os.PersistableBundle;
import android.os.RemoteCallback;
import android.os.RemoteCallbackList;
@@ -76,7 +75,6 @@
import android.os.ShellCallback;
import android.os.Trace;
import android.os.UserHandle;
-import android.permission.flags.Flags;
import android.provider.Settings;
import android.service.voice.IMicrophoneHotwordDetectionVoiceInteractionCallback;
import android.service.voice.IVisualQueryDetectionVoiceInteractionCallback;
@@ -1407,17 +1405,6 @@
}
}
- // Enforce permissions that are flag controlled. The flag value decides if the permission
- // should be enforced.
- private void initAndVerifyDetector_enforcePermissionWithFlags() {
- PermissionEnforcer enforcer = mContext.getSystemService(PermissionEnforcer.class);
- if (Flags.voiceActivationPermissionApis()) {
- enforcer.enforcePermission(
- android.Manifest.permission.RECEIVE_SANDBOX_TRIGGER_AUDIO,
- getCallingPid(), getCallingUid());
- }
- }
-
@android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_HOTWORD_DETECTION)
@Override
public void initAndVerifyDetector(
@@ -1427,13 +1414,7 @@
@NonNull IBinder token,
IHotwordRecognitionStatusCallback callback,
int detectorType) {
- // TODO(b/305787465): Remove the MANAGE_HOTWORD_DETECTION permission enforcement on the
- // {@link #initAndVerifyDetector(Identity, PersistableBundle, ShareMemory, IBinder,
- // IHotwordRecognitionStatusCallback, int)}
- // and replace with the permission RECEIVE_SANDBOX_TRIGGER_AUDIO when it is fully
- // launched.
super.initAndVerifyDetector_enforcePermission();
- initAndVerifyDetector_enforcePermissionWithFlags();
synchronized (this) {
enforceIsCurrentVoiceInteractionService();
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index 9792cdd..048b1b2 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -1434,11 +1434,14 @@
}
/**
- * This API will return all {@link PhoneAccount}s registered via
- * {@link TelecomManager#registerPhoneAccount(PhoneAccount)}. If a {@link PhoneAccount} appears
- * to be missing from the list, Telecom has either unregistered the {@link PhoneAccount}
- * or the caller registered the {@link PhoneAccount} under a different user and does not
- * have the {@link android.Manifest.permission#INTERACT_ACROSS_USERS} permission.
+ * This API will return all {@link PhoneAccount}s the caller registered via
+ * {@link TelecomManager#registerPhoneAccount(PhoneAccount)}. If a {@link PhoneAccount} appears
+ * to be missing from the list, Telecom has either unregistered the {@link PhoneAccount} (for
+ * cleanup purposes) or the caller registered the {@link PhoneAccount} under a different user
+ * and does not have the {@link android.Manifest.permission#INTERACT_ACROSS_USERS} permission.
+ * <b>Note:</b> This API will only return {@link PhoneAccount}s registered by the same app. For
+ * system Dialers that need all the {@link PhoneAccount}s registered by every application, see
+ * {@link TelecomManager#getAllPhoneAccounts()}.
*
* @return all the {@link PhoneAccount}s registered by the caller.
*/
diff --git a/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt b/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt
index 7343ba1c..e60764f 100644
--- a/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt
+++ b/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt
@@ -24,6 +24,7 @@
import android.content.pm.PackageManager
import android.content.pm.ResolveInfo
import android.content.pm.ServiceInfo
+import android.hardware.input.KeyboardLayoutSelectionResult
import android.hardware.input.IInputManager
import android.hardware.input.InputManager
import android.hardware.input.InputManagerGlobal
@@ -525,13 +526,13 @@
keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype,
ENGLISH_UK_LAYOUT_DESCRIPTOR
)
- val keyboardLayout =
+ assertEquals(
+ "Default UI: getKeyboardLayoutForInputDevice API should always return " +
+ "KeyboardLayoutSelectionResult.FAILED",
+ KeyboardLayoutSelectionResult.FAILED,
keyboardLayoutManager.getKeyboardLayoutForInputDevice(
keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype
)
- assertNull(
- "Default UI: getKeyboardLayoutForInputDevice API should always return null",
- keyboardLayout
)
}
}
@@ -545,12 +546,14 @@
keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype,
ENGLISH_UK_LAYOUT_DESCRIPTOR
)
- assertEquals(
- "New UI: getKeyboardLayoutForInputDevice API should return the set layout",
- ENGLISH_UK_LAYOUT_DESCRIPTOR,
+ var result =
keyboardLayoutManager.getKeyboardLayoutForInputDevice(
keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype
)
+ assertEquals(
+ "New UI: getKeyboardLayoutForInputDevice API should return the set layout",
+ ENGLISH_UK_LAYOUT_DESCRIPTOR,
+ result.layoutDescriptor
)
// This should replace previously set layout
@@ -558,12 +561,14 @@
keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype,
ENGLISH_US_LAYOUT_DESCRIPTOR
)
- assertEquals(
- "New UI: getKeyboardLayoutForInputDevice API should return the last set layout",
- ENGLISH_US_LAYOUT_DESCRIPTOR,
+ result =
keyboardLayoutManager.getKeyboardLayoutForInputDevice(
keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype
)
+ assertEquals(
+ "New UI: getKeyboardLayoutForInputDevice API should return the last set layout",
+ ENGLISH_US_LAYOUT_DESCRIPTOR,
+ result.layoutDescriptor
)
}
}
@@ -734,17 +739,20 @@
createImeSubtypeForLanguageTag("ru"),
createLayoutDescriptor("keyboard_layout_russian")
)
- assertNull(
- "New UI: getDefaultKeyboardLayoutForInputDevice should return null when no " +
- "layout available",
+ assertEquals(
+ "New UI: getDefaultKeyboardLayoutForInputDevice should return " +
+ "KeyboardLayoutSelectionResult.FAILED when no layout available",
+ KeyboardLayoutSelectionResult.FAILED,
keyboardLayoutManager.getKeyboardLayoutForInputDevice(
keyboardDevice.identifier, USER_ID, imeInfo,
createImeSubtypeForLanguageTag("it")
)
)
- assertNull(
- "New UI: getDefaultKeyboardLayoutForInputDevice should return null when no " +
- "layout for script code is available",
+ assertEquals(
+ "New UI: getDefaultKeyboardLayoutForInputDevice should return " +
+ "KeyboardLayoutSelectionResult.FAILED when no layout for script code is" +
+ "available",
+ KeyboardLayoutSelectionResult.FAILED,
keyboardLayoutManager.getKeyboardLayoutForInputDevice(
keyboardDevice.identifier, USER_ID, imeInfo,
createImeSubtypeForLanguageTag("en-Deva")
@@ -811,8 +819,10 @@
createImeSubtypeForLanguageTagAndLayoutType("ru", ""),
createLayoutDescriptor("keyboard_layout_russian")
)
- assertNull("New UI: getDefaultKeyboardLayoutForInputDevice should return null when " +
- "no layout for script code is available",
+ assertEquals("New UI: getDefaultKeyboardLayoutForInputDevice should return " +
+ "KeyboardLayoutSelectionResult.FAILED when no layout for script code is" +
+ "available",
+ KeyboardLayoutSelectionResult.FAILED,
keyboardLayoutManager.getKeyboardLayoutForInputDevice(
keyboardDevice.identifier, USER_ID, imeInfo,
createImeSubtypeForLanguageTagAndLayoutType("en-Deva-US", "")
@@ -865,14 +875,16 @@
ArgumentMatchers.anyBoolean(),
ArgumentMatchers.eq(keyboardDevice.vendorId),
ArgumentMatchers.eq(keyboardDevice.productId),
- ArgumentMatchers.eq(createByteArray(
+ ArgumentMatchers.eq(
+ createByteArray(
KeyboardMetricsCollector.DEFAULT_LANGUAGE_TAG,
LAYOUT_TYPE_DEFAULT,
GERMAN_LAYOUT_NAME,
- KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD,
+ KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD,
"de-Latn",
- LAYOUT_TYPE_QWERTZ),
+ LAYOUT_TYPE_QWERTZ
),
+ ),
ArgumentMatchers.eq(keyboardDevice.deviceBus),
)
}
@@ -893,13 +905,16 @@
ArgumentMatchers.anyBoolean(),
ArgumentMatchers.eq(englishQwertyKeyboardDevice.vendorId),
ArgumentMatchers.eq(englishQwertyKeyboardDevice.productId),
- ArgumentMatchers.eq(createByteArray(
+ ArgumentMatchers.eq(
+ createByteArray(
"en",
LAYOUT_TYPE_QWERTY,
ENGLISH_US_LAYOUT_NAME,
- KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEVICE,
+ KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_DEVICE,
"de-Latn",
- LAYOUT_TYPE_QWERTZ)),
+ LAYOUT_TYPE_QWERTZ
+ )
+ ),
ArgumentMatchers.eq(keyboardDevice.deviceBus),
)
}
@@ -918,14 +933,16 @@
ArgumentMatchers.anyBoolean(),
ArgumentMatchers.eq(keyboardDevice.vendorId),
ArgumentMatchers.eq(keyboardDevice.productId),
- ArgumentMatchers.eq(createByteArray(
+ ArgumentMatchers.eq(
+ createByteArray(
KeyboardMetricsCollector.DEFAULT_LANGUAGE_TAG,
LAYOUT_TYPE_DEFAULT,
"Default",
- KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEFAULT,
+ KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_DEFAULT,
KeyboardMetricsCollector.DEFAULT_LANGUAGE_TAG,
- LAYOUT_TYPE_DEFAULT),
+ LAYOUT_TYPE_DEFAULT
),
+ ),
ArgumentMatchers.eq(keyboardDevice.deviceBus),
)
}
@@ -998,12 +1015,13 @@
imeSubtype: InputMethodSubtype,
expectedLayout: String
) {
+ val result = keyboardLayoutManager.getKeyboardLayoutForInputDevice(
+ device.identifier, USER_ID, imeInfo, imeSubtype
+ )
assertEquals(
"New UI: getDefaultKeyboardLayoutForInputDevice should return $expectedLayout",
expectedLayout,
- keyboardLayoutManager.getKeyboardLayoutForInputDevice(
- device.identifier, USER_ID, imeInfo, imeSubtype
- )
+ result.layoutDescriptor
)
}
diff --git a/tests/Input/src/com/android/server/input/KeyboardMetricsCollectorTests.kt b/tests/Input/src/com/android/server/input/KeyboardMetricsCollectorTests.kt
index 89a47b9..0615941 100644
--- a/tests/Input/src/com/android/server/input/KeyboardMetricsCollectorTests.kt
+++ b/tests/Input/src/com/android/server/input/KeyboardMetricsCollectorTests.kt
@@ -17,6 +17,7 @@
package com.android.server.input
import android.hardware.input.KeyboardLayout
+import android.hardware.input.KeyboardLayoutSelectionResult
import android.icu.util.ULocale
import android.platform.test.annotations.Presubmit
import android.view.InputDevice
@@ -120,15 +121,15 @@
val event = builder.addLayoutSelection(
createImeSubtype(1, ULocale.forLanguageTag("en-US"), "qwerty"),
"English(US)(Qwerty)",
- KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD
+ KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD
).addLayoutSelection(
createImeSubtype(2, ULocale.forLanguageTag("en-US"), "azerty"),
null, // Default layout type
- KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_USER
+ KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_USER
).addLayoutSelection(
createImeSubtype(3, ULocale.forLanguageTag("en-US"), "qwerty"),
"German",
- KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEVICE
+ KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_DEVICE
).setIsFirstTimeConfiguration(true).build()
assertEquals(
@@ -158,7 +159,7 @@
"de-CH",
KeyboardLayout.LayoutType.getLayoutTypeEnumValue("qwertz"),
"English(US)(Qwerty)",
- KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD,
+ KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD,
"en-US",
KeyboardLayout.LayoutType.getLayoutTypeEnumValue("qwerty"),
)
@@ -167,7 +168,7 @@
"de-CH",
KeyboardLayout.LayoutType.getLayoutTypeEnumValue("qwertz"),
KeyboardMetricsCollector.DEFAULT_LAYOUT_NAME,
- KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_USER,
+ KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_USER,
"en-US",
KeyboardLayout.LayoutType.getLayoutTypeEnumValue("azerty"),
)
@@ -176,7 +177,7 @@
"de-CH",
KeyboardLayout.LayoutType.getLayoutTypeEnumValue("qwertz"),
"German",
- KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEVICE,
+ KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_DEVICE,
"en-US",
KeyboardLayout.LayoutType.getLayoutTypeEnumValue("qwerty"),
)
@@ -197,7 +198,7 @@
val event = builder.addLayoutSelection(
createImeSubtype(4, null, "qwerty"), // Default language tag
"German",
- KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEVICE
+ KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_DEVICE
).build()
assertExpectedLayoutConfiguration(
@@ -205,7 +206,7 @@
KeyboardMetricsCollector.DEFAULT_LANGUAGE_TAG,
KeyboardLayout.LayoutType.getLayoutTypeEnumValue("azerty"),
"German",
- KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEVICE,
+ KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_DEVICE,
KeyboardMetricsCollector.DEFAULT_LANGUAGE_TAG,
KeyboardLayout.LayoutType.getLayoutTypeEnumValue("qwerty"),
)
diff --git a/tools/aapt2/Android.bp b/tools/aapt2/Android.bp
index b054a57..b4e2758 100644
--- a/tools/aapt2/Android.bp
+++ b/tools/aapt2/Android.bp
@@ -15,12 +15,7 @@
//
package {
- // See: http://go/android-license-faq
- // A large-scale-change added 'default_applicable_licenses' to import
- // all of the 'license_kinds' from "frameworks_base_license"
- // to get the below license kinds:
- // SPDX-license-identifier-Apache-2.0
- default_applicable_licenses: ["frameworks_base_license"],
+ default_applicable_licenses: ["Android-Apache-2.0"],
}
toolSources = [
diff --git a/tools/aapt2/Android.mk b/tools/aapt2/Android.mk
deleted file mode 100644
index 15ae2ba..0000000
--- a/tools/aapt2/Android.mk
+++ /dev/null
@@ -1,4 +0,0 @@
-include $(CLEAR_VARS)
-aapt2_results := ./out/soong/.intermediates/frameworks/base/tools/aapt2/aapt2_results
-$(call declare-1p-target,$(aapt2_results))
-aapt2_results :=