Merge "Update factory reset dialog to new UX spec" into main
diff --git a/Android.bp b/Android.bp
index 93d6f53..be589b2 100644
--- a/Android.bp
+++ b/Android.bp
@@ -246,7 +246,6 @@
"android.system.suspend.control.internal-java",
"devicepolicyprotosnano",
- "com.android.sysprop.apex",
"com.android.sysprop.init",
"com.android.sysprop.localization",
"PlatformProperties",
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
index 13903ac..f429966 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
@@ -56,6 +56,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FrameworkStatsLog;
+import com.android.modules.expresslog.Counter;
import com.android.server.LocalServices;
import com.android.server.job.GrantedUriPermissions;
import com.android.server.job.JobSchedulerInternal;
@@ -161,6 +162,9 @@
/** If the job is going to be passed an unmetered network. */
private boolean mHasAccessToUnmetered;
+ /** If the effective bucket has been downgraded once due to being buggy. */
+ private boolean mIsDowngradedDueToBuggyApp;
+
/**
* The additional set of dynamic constraints that must be met if this is an expedited job that
* had a long enough run while the device was Dozing or in battery saver.
@@ -1173,18 +1177,32 @@
// like other ACTIVE apps.
return ACTIVE_INDEX;
}
+
+ final int bucketWithMediaExemption;
+ if (actualBucket != RESTRICTED_INDEX && actualBucket != NEVER_INDEX
+ && mHasMediaBackupExemption) {
+ // Treat it as if it's at most WORKING_INDEX (lower index grants higher quota) since
+ // media backup jobs are important to the user, and the source package may not have
+ // been used directly in a while.
+ bucketWithMediaExemption = Math.min(WORKING_INDEX, actualBucket);
+ } else {
+ bucketWithMediaExemption = actualBucket;
+ }
+
// If the app is considered buggy, but hasn't yet been put in the RESTRICTED bucket
// (potentially because it's used frequently by the user), limit its effective bucket
// so that it doesn't get to run as much as a normal ACTIVE app.
- final int highestBucket = isBuggy ? WORKING_INDEX : ACTIVE_INDEX;
- if (actualBucket != RESTRICTED_INDEX && actualBucket != NEVER_INDEX
- && mHasMediaBackupExemption) {
- // Treat it as if it's at least WORKING_INDEX since media backup jobs are important
- // to the user, and the
- // source package may not have been used directly in a while.
- return Math.max(highestBucket, Math.min(WORKING_INDEX, actualBucket));
+ if (isBuggy && bucketWithMediaExemption < WORKING_INDEX) {
+ if (!mIsDowngradedDueToBuggyApp) {
+ // Safety check to avoid logging multiple times for the same job.
+ Counter.logIncrementWithUid(
+ "job_scheduler.value_job_quota_reduced_due_to_buggy_uid",
+ getTimeoutBlameUid());
+ mIsDowngradedDueToBuggyApp = true;
+ }
+ return WORKING_INDEX;
}
- return Math.max(highestBucket, actualBucket);
+ return bucketWithMediaExemption;
}
/** Returns the real standby bucket of the job. */
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java b/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java
index fb342b9..913a76a 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java
+++ b/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java
@@ -61,6 +61,7 @@
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
+import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
@@ -802,6 +803,9 @@
}
}
}
+ } catch (FileNotFoundException e) {
+ // Expected on first boot
+ Slog.d(TAG, "App idle file for user " + userId + " does not exist");
} catch (IOException | XmlPullParserException e) {
Slog.e(TAG, "Unable to read app idle file for user " + userId, e);
} finally {
diff --git a/api/Android.bp b/api/Android.bp
index f5bafe8..c16bce5 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -60,14 +60,14 @@
metalava_cmd = "$(location metalava)"
// Silence reflection warnings. See b/168689341
metalava_cmd += " -J--add-opens=java.base/java.util=ALL-UNNAMED "
-metalava_cmd += " --quiet --no-banner --format=v2 "
+metalava_cmd += " --quiet "
genrule {
name: "current-api-xml",
tools: ["metalava"],
srcs: [":frameworks-base-api-current.txt"],
out: ["current.api"],
- cmd: metalava_cmd + "-convert2xmlnostrip $(in) $(out)",
+ cmd: metalava_cmd + "signature-to-jdiff $(in) $(out)",
visibility: ["//visibility:public"],
}
diff --git a/cmds/uiautomator/library/testrunner-src/com/android/uiautomator/core/UiAutomationShellWrapper.java b/cmds/uiautomator/library/testrunner-src/com/android/uiautomator/core/UiAutomationShellWrapper.java
index 39248730..7489ce3 100644
--- a/cmds/uiautomator/library/testrunner-src/com/android/uiautomator/core/UiAutomationShellWrapper.java
+++ b/cmds/uiautomator/library/testrunner-src/com/android/uiautomator/core/UiAutomationShellWrapper.java
@@ -8,6 +8,7 @@
import android.app.UiAutomationConnection;
import android.content.Intent;
import android.os.HandlerThread;
+import android.os.Looper;
import android.os.RemoteException;
/**
@@ -26,6 +27,10 @@
throw new IllegalStateException("Already connected!");
}
mHandlerThread.start();
+ // The AccessibilityInteractionClient used by UiAutomation expects the main looper to
+ // be prepared. In most contexts this is normally done automatically, but must be called
+ // explicitly here because this is a shell tool.
+ Looper.prepareMainLooper();
mUiAutomation = new UiAutomation(mHandlerThread.getLooper(),
new UiAutomationConnection());
mUiAutomation.connect();
diff --git a/core/api/current.txt b/core/api/current.txt
index d03122d..ff011cf 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -43494,6 +43494,7 @@
field public static final String KEY_CARRIER_SERVICE_NUMBER_STRING_ARRAY = "carrier_service_number_array";
field public static final String KEY_CARRIER_SETTINGS_ACTIVITY_COMPONENT_NAME_STRING = "carrier_settings_activity_component_name_string";
field public static final String KEY_CARRIER_SETTINGS_ENABLE_BOOL = "carrier_settings_enable_bool";
+ field public static final String KEY_CARRIER_SUPPORTED_SATELLITE_SERVICES_PER_PROVIDER_BUNDLE = "carrier_supported_satellite_services_per_provider_bundle";
field public static final String KEY_CARRIER_SUPPORTS_OPP_DATA_AUTO_PROVISIONING_BOOL = "carrier_supports_opp_data_auto_provisioning_bool";
field public static final String KEY_CARRIER_SUPPORTS_SS_OVER_UT_BOOL = "carrier_supports_ss_over_ut_bool";
field public static final String KEY_CARRIER_SUPPORTS_TETHERING_BOOL = "carrier_supports_tethering_bool";
@@ -46017,6 +46018,7 @@
field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_THROTTLED = 2; // 0x2
field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_TIMEOUT = 9; // 0x9
field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_CANCELED = 6; // 0x6
+ field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_DISABLED = 16; // 0x10
field public static final int SET_OPPORTUNISTIC_SUB_INACTIVE_SUBSCRIPTION = 2; // 0x2
field public static final int SET_OPPORTUNISTIC_SUB_NO_OPPORTUNISTIC_SUB_AVAILABLE = 3; // 0x3
field public static final int SET_OPPORTUNISTIC_SUB_REMOTE_SERVICE_EXCEPTION = 4; // 0x4
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 7c3d8fb..4330386 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -3622,6 +3622,8 @@
method public default void holdLock(android.os.IBinder, int);
method public default boolean isGlobalKey(int);
method public default boolean isTaskSnapshotSupported();
+ method @RequiresPermission(android.Manifest.permission.ACCESS_SURFACE_FLINGER) public default boolean replaceContentOnDisplayWithMirror(int, @NonNull android.view.Window);
+ method @RequiresPermission(android.Manifest.permission.ACCESS_SURFACE_FLINGER) public default boolean replaceContentOnDisplayWithSc(int, @NonNull android.view.SurfaceControl);
method public default void setDisplayImePolicy(int, int);
method public default void setShouldShowSystemDecors(int, boolean);
method public default void setShouldShowWithInsecureKeyguard(int, boolean);
@@ -4233,6 +4235,7 @@
field public final boolean isTrustedOverlay;
field public final boolean isVisible;
field @NonNull public final String name;
+ field @NonNull public final android.graphics.Matrix transform;
field @NonNull public final android.os.IBinder windowToken;
}
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index d76e201..37a1e62 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -4699,8 +4699,14 @@
@UnsupportedAppUsage
public static int checkComponentPermission(String permission, int uid,
int owningUid, boolean exported) {
+ return checkComponentPermission(permission, uid, Context.DEVICE_ID_DEFAULT,
+ owningUid, exported);
+ }
+
+ /** @hide */
+ public static int checkComponentPermission(String permission, int uid, int deviceId,
+ int owningUid, boolean exported) {
// Root, system server get to do everything.
- final int appId = UserHandle.getAppId(uid);
if (canAccessUnexportedComponents(uid)) {
return PackageManager.PERMISSION_GRANTED;
}
@@ -4727,8 +4733,7 @@
return PackageManager.PERMISSION_GRANTED;
}
try {
- return AppGlobals.getPackageManager()
- .checkUidPermission(permission, uid);
+ return AppGlobals.getPermissionManager().checkUidPermission(uid, permission, deviceId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -4737,8 +4742,8 @@
/** @hide */
public static int checkUidPermission(String permission, int uid) {
try {
- return AppGlobals.getPackageManager()
- .checkUidPermission(permission, uid);
+ return AppGlobals.getPermissionManager().checkUidPermission(
+ uid, permission, Context.DEVICE_ID_DEFAULT);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/app/ApplicationExitInfo.java b/core/java/android/app/ApplicationExitInfo.java
index d859f3f..7b3d017 100644
--- a/core/java/android/app/ApplicationExitInfo.java
+++ b/core/java/android/app/ApplicationExitInfo.java
@@ -460,6 +460,14 @@
*/
public static final int SUBREASON_SDK_SANDBOX_NOT_NEEDED = 28;
+ /**
+ * The process was killed because the binder proxy limit for system server was exceeded.
+ *
+ * For internal use only.
+ * @hide
+ */
+ public static final int SUBREASON_EXCESSIVE_BINDER_OBJECTS = 29;
+
// If there is any OEM code which involves additional app kill reasons, it should
// be categorized in {@link #REASON_OTHER}, with subreason code starting from 1000.
@@ -635,6 +643,7 @@
SUBREASON_KILL_BACKGROUND,
SUBREASON_PACKAGE_UPDATE,
SUBREASON_UNDELIVERED_BROADCAST,
+ SUBREASON_EXCESSIVE_BINDER_OBJECTS,
})
@Retention(RetentionPolicy.SOURCE)
public @interface SubReason {}
@@ -1360,6 +1369,8 @@
return "PACKAGE UPDATE";
case SUBREASON_UNDELIVERED_BROADCAST:
return "UNDELIVERED BROADCAST";
+ case SUBREASON_EXCESSIVE_BINDER_OBJECTS:
+ return "EXCESSIVE BINDER OBJECTS";
default:
return "UNKNOWN";
}
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 0e3a695..9121cf0 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -827,7 +827,8 @@
@Override
public int checkPermission(String permName, String pkgName) {
- return PermissionManager.checkPackageNamePermission(permName, pkgName, getUserId());
+ return PermissionManager.checkPackageNamePermission(permName, pkgName,
+ mContext.getDeviceId(), getUserId());
}
@Override
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 2056eca..4eaebe0 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -27,6 +27,7 @@
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.annotation.UiContext;
+import android.app.servertransaction.WindowTokenClientController;
import android.companion.virtual.VirtualDeviceManager;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.AttributionSource;
@@ -2262,7 +2263,7 @@
Log.v(TAG, "Treating renounced permission " + permission + " as denied");
return PERMISSION_DENIED;
}
- return PermissionManager.checkPermission(permission, pid, uid);
+ return PermissionManager.checkPermission(permission, pid, uid, getDeviceId());
}
/** @hide */
@@ -3276,7 +3277,8 @@
// if this Context is not a WindowContext. WindowContext finalization is handled in
// WindowContext class.
if (mToken instanceof WindowTokenClient && mOwnsToken) {
- ((WindowTokenClient) mToken).detachFromWindowContainerIfNeeded();
+ WindowTokenClientController.getInstance().detachIfNeeded(
+ (WindowTokenClient) mToken);
}
super.finalize();
}
@@ -3304,7 +3306,7 @@
final WindowTokenClient token = new WindowTokenClient();
final ContextImpl context = systemContext.createWindowContextBase(token, displayId);
token.attachContext(context);
- token.attachToDisplayContent(displayId);
+ WindowTokenClientController.getInstance().attachToDisplayContent(token, displayId);
context.mContextType = CONTEXT_TYPE_SYSTEM_OR_SYSTEM_UI;
context.mOwnsToken = true;
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 46260ea..a8b1688 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -924,4 +924,6 @@
void unregisterUidFrozenStateChangedCallback(in IUidFrozenStateChangedCallback callback);
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS)")
int[] getUidFrozenState(in int[] uids);
+
+ int checkPermissionForDevice(in String permission, int pid, int uid, int deviceId);
}
diff --git a/core/java/android/app/IGameManagerService.aidl b/core/java/android/app/IGameManagerService.aidl
index 3d6ab6f..9a818e4 100644
--- a/core/java/android/app/IGameManagerService.aidl
+++ b/core/java/android/app/IGameManagerService.aidl
@@ -20,6 +20,7 @@
import android.app.GameModeInfo;
import android.app.GameState;
import android.app.IGameModeListener;
+import android.app.IGameStateListener;
/**
* @hide
@@ -49,4 +50,6 @@
void addGameModeListener(IGameModeListener gameModeListener);
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_GAME_MODE)")
void removeGameModeListener(IGameModeListener gameModeListener);
+ void addGameStateListener(IGameStateListener gameStateListener);
+ void removeGameStateListener(IGameStateListener gameStateListener);
}
diff --git a/services/core/java/com/android/server/biometrics/BiometricSensorPrivacy.java b/core/java/android/app/IGameStateListener.aidl
similarity index 69%
copy from services/core/java/com/android/server/biometrics/BiometricSensorPrivacy.java
copy to core/java/android/app/IGameStateListener.aidl
index 6727fbc..34cff48 100644
--- a/services/core/java/com/android/server/biometrics/BiometricSensorPrivacy.java
+++ b/core/java/android/app/IGameStateListener.aidl
@@ -14,12 +14,14 @@
* limitations under the License.
*/
-package com.android.server.biometrics;
+package android.app;
-/**
- * Interface for biometric operations to get camera privacy state.
- */
-public interface BiometricSensorPrivacy {
- /* Returns true if privacy is enabled and camera access is disabled. */
- boolean isCameraPrivacyEnabled();
+import android.app.GameState;
+
+/** @hide */
+interface IGameStateListener {
+ /**
+ * Called when the state of the game has changed.
+ */
+ oneway void onGameStateChanged(String packageName, in GameState state, int userId);
}
diff --git a/core/java/android/app/OWNERS b/core/java/android/app/OWNERS
index f7d2afb..e1c45d9 100644
--- a/core/java/android/app/OWNERS
+++ b/core/java/android/app/OWNERS
@@ -63,10 +63,11 @@
# Multiuser
per-file *User* = file:/MULTIUSER_OWNERS
-# Notification, DND, Status bar
+# Notification, DND, Status bar, UiModeManager
per-file *Notification* = file:/packages/SystemUI/OWNERS
per-file *Zen* = file:/packages/SystemUI/OWNERS
per-file *StatusBar* = file:/packages/SystemUI/OWNERS
+per-file *UiModeManager* = file:/packages/SystemUI/OWNERS
# PackageManager
per-file ApplicationPackageManager.java = file:/services/core/java/com/android/server/pm/OWNERS
diff --git a/core/java/android/app/UiAutomationConnection.java b/core/java/android/app/UiAutomationConnection.java
index 52949d6..6f4abfd 100644
--- a/core/java/android/app/UiAutomationConnection.java
+++ b/core/java/android/app/UiAutomationConnection.java
@@ -343,6 +343,9 @@
}
}
+ /**
+ * Grants permission for the {@link Context#DEVICE_ID_DEFAULT default device}
+ */
@Override
public void grantRuntimePermission(String packageName, String permission, int userId)
throws RemoteException {
@@ -353,12 +356,16 @@
}
final long identity = Binder.clearCallingIdentity();
try {
- mPermissionManager.grantRuntimePermission(packageName, permission, userId);
+ mPermissionManager.grantRuntimePermission(packageName, permission,
+ Context.DEVICE_ID_DEFAULT, userId);
} finally {
Binder.restoreCallingIdentity(identity);
}
}
+ /**
+ * Revokes permission for the {@link Context#DEVICE_ID_DEFAULT default device}
+ */
@Override
public void revokeRuntimePermission(String packageName, String permission, int userId)
throws RemoteException {
@@ -369,7 +376,8 @@
}
final long identity = Binder.clearCallingIdentity();
try {
- mPermissionManager.revokeRuntimePermission(packageName, permission, userId, null);
+ mPermissionManager.revokeRuntimePermission(packageName, permission,
+ Context.DEVICE_ID_DEFAULT, userId, null);
} finally {
Binder.restoreCallingIdentity(identity);
}
diff --git a/core/java/android/app/UiModeManager.java b/core/java/android/app/UiModeManager.java
index d90257a..0ccb9cd 100644
--- a/core/java/android/app/UiModeManager.java
+++ b/core/java/android/app/UiModeManager.java
@@ -315,7 +315,7 @@
@SystemApi
public static final int MODE_NIGHT_CUSTOM_TYPE_BEDTIME = 1;
- private IUiModeManager mService;
+ private static Globals sGlobals;
/**
* Context required for getting the opPackageName of API caller; maybe be {@code null} if the
@@ -341,6 +341,60 @@
mOnProjectionStateChangedListenerResourceManager =
new OnProjectionStateChangedListenerResourceManager();
+ private static class Globals extends IUiModeManagerCallback.Stub {
+
+ private final IUiModeManager mService;
+ private final Object mGlobalsLock = new Object();
+
+ private float mContrast = ContrastUtils.CONTRAST_DEFAULT_VALUE;
+
+ /**
+ * Map that stores user provided {@link ContrastChangeListener} callbacks,
+ * and the executors on which these callbacks should be called.
+ */
+ private final ArrayMap<ContrastChangeListener, Executor>
+ mContrastChangeListeners = new ArrayMap<>();
+
+ Globals(IUiModeManager service) {
+ mService = service;
+ try {
+ mService.addCallback(this);
+ mContrast = mService.getContrast();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Setup failed: UiModeManagerService is dead", e);
+ }
+ }
+
+ private float getContrast() {
+ synchronized (mGlobalsLock) {
+ return mContrast;
+ }
+ }
+
+ private void addContrastChangeListener(ContrastChangeListener listener, Executor executor) {
+ synchronized (mGlobalsLock) {
+ mContrastChangeListeners.put(listener, executor);
+ }
+ }
+
+ private void removeContrastChangeListener(ContrastChangeListener listener) {
+ synchronized (mGlobalsLock) {
+ mContrastChangeListeners.remove(listener);
+ }
+ }
+
+ @Override
+ public void notifyContrastChanged(float contrast) {
+ synchronized (mGlobalsLock) {
+ // if value changed in the settings, update the cached value and notify listeners
+ if (Math.abs(mContrast - contrast) < 1e-10) return;
+ mContrast = contrast;
+ mContrastChangeListeners.forEach((listener, executor) -> executor.execute(
+ () -> listener.onContrastChanged(contrast)));
+ }
+ }
+ }
+
/**
* Define constants and conversions between {@link ContrastLevel}s and contrast values.
* <p>
@@ -407,43 +461,18 @@
}
}
- /**
- * Map that stores user provided {@link ContrastChangeListener} callbacks,
- * and the executors on which these callbacks should be called.
- */
- private final ArrayMap<ContrastChangeListener, Executor>
- mContrastChangeListeners = new ArrayMap<>();
- private float mContrast;
-
- private final IUiModeManagerCallback.Stub mCallback = new IUiModeManagerCallback.Stub() {
- @Override
- public void notifyContrastChanged(float contrast) {
- final ArrayMap<ContrastChangeListener, Executor> listeners;
- synchronized (mLock) {
- // if value changed in the settings, update the cached value and notify listeners
- if (Math.abs(mContrast - contrast) < 1e-10) return;
- mContrast = contrast;
- listeners = new ArrayMap<>(mContrastChangeListeners);
- }
- listeners.forEach((listener, executor) -> executor.execute(
- () -> listener.onContrastChanged(mContrast)));
- }
- };
-
@UnsupportedAppUsage
/*package*/ UiModeManager() throws ServiceNotFoundException {
this(null /* context */);
}
/*package*/ UiModeManager(Context context) throws ServiceNotFoundException {
- mService = IUiModeManager.Stub.asInterface(
+ IUiModeManager service = IUiModeManager.Stub.asInterface(
ServiceManager.getServiceOrThrow(Context.UI_MODE_SERVICE));
mContext = context;
- try {
- mService.addCallback(mCallback);
- mContrast = mService.getContrast();
- } catch (RemoteException e) {
- Log.e(TAG, "Setup failed: UiModeManagerService is dead", e);
+ if (service == null) return;
+ synchronized (mLock) {
+ if (sGlobals == null) sGlobals = new Globals(service);
}
}
@@ -533,9 +562,9 @@
@SystemApi
@RequiresPermission(android.Manifest.permission.ENTER_CAR_MODE_PRIORITIZED)
public void enableCarMode(@IntRange(from = 0) int priority, @EnableCarMode int flags) {
- if (mService != null) {
+ if (sGlobals != null) {
try {
- mService.enableCarMode(flags, priority,
+ sGlobals.mService.enableCarMode(flags, priority,
mContext == null ? null : mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -585,9 +614,9 @@
* @param flags One of the disable car mode flags.
*/
public void disableCarMode(@DisableCarMode int flags) {
- if (mService != null) {
+ if (sGlobals != null) {
try {
- mService.disableCarModeByCallingPackage(flags,
+ sGlobals.mService.disableCarModeByCallingPackage(flags,
mContext == null ? null : mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -606,9 +635,9 @@
* {@link Configuration#UI_MODE_TYPE_VR_HEADSET Configuration.UI_MODE_TYPE_VR_HEADSET}.
*/
public int getCurrentModeType() {
- if (mService != null) {
+ if (sGlobals != null) {
try {
- return mService.getCurrentModeType();
+ return sGlobals.mService.getCurrentModeType();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -653,9 +682,9 @@
* @see #setApplicationNightMode(int)
*/
public void setNightMode(@NightMode int mode) {
- if (mService != null) {
+ if (sGlobals != null) {
try {
- mService.setNightMode(mode);
+ sGlobals.mService.setNightMode(mode);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -674,9 +703,9 @@
@SystemApi
@RequiresPermission(android.Manifest.permission.MODIFY_DAY_NIGHT_MODE)
public void setNightModeCustomType(@NightModeCustomType int nightModeCustomType) {
- if (mService != null) {
+ if (sGlobals != null) {
try {
- mService.setNightModeCustomType(nightModeCustomType);
+ sGlobals.mService.setNightModeCustomType(nightModeCustomType);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -693,9 +722,9 @@
@SystemApi
@RequiresPermission(android.Manifest.permission.MODIFY_DAY_NIGHT_MODE)
public @NightModeCustomReturnType int getNightModeCustomType() {
- if (mService != null) {
+ if (sGlobals != null) {
try {
- return mService.getNightModeCustomType();
+ return sGlobals.mService.getNightModeCustomType();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -732,9 +761,9 @@
* @see #setNightMode(int)
*/
public void setApplicationNightMode(@NightMode int mode) {
- if (mService != null) {
+ if (sGlobals != null) {
try {
- mService.setApplicationNightMode(mode);
+ sGlobals.mService.setApplicationNightMode(mode);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -757,9 +786,9 @@
* @see #setNightMode(int)
*/
public @NightMode int getNightMode() {
- if (mService != null) {
+ if (sGlobals != null) {
try {
- return mService.getNightMode();
+ return sGlobals.mService.getNightMode();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -774,9 +803,9 @@
*/
@TestApi
public boolean isUiModeLocked() {
- if (mService != null) {
+ if (sGlobals != null) {
try {
- return mService.isUiModeLocked();
+ return sGlobals.mService.isUiModeLocked();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -796,9 +825,9 @@
*/
@TestApi
public boolean isNightModeLocked() {
- if (mService != null) {
+ if (sGlobals != null) {
try {
- return mService.isNightModeLocked();
+ return sGlobals.mService.isNightModeLocked();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -820,9 +849,10 @@
@RequiresPermission(android.Manifest.permission.MODIFY_DAY_NIGHT_MODE)
public boolean setNightModeActivatedForCustomMode(@NightModeCustomType int nightModeCustomType,
boolean active) {
- if (mService != null) {
+ if (sGlobals != null) {
try {
- return mService.setNightModeActivatedForCustomMode(nightModeCustomType, active);
+ return sGlobals.mService.setNightModeActivatedForCustomMode(
+ nightModeCustomType, active);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -838,9 +868,9 @@
*/
@RequiresPermission(android.Manifest.permission.MODIFY_DAY_NIGHT_MODE)
public boolean setNightModeActivated(boolean active) {
- if (mService != null) {
+ if (sGlobals != null) {
try {
- return mService.setNightModeActivated(active);
+ return sGlobals.mService.setNightModeActivated(active);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -856,9 +886,9 @@
*/
@NonNull
public LocalTime getCustomNightModeStart() {
- if (mService != null) {
+ if (sGlobals != null) {
try {
- return LocalTime.ofNanoOfDay(mService.getCustomNightModeStart() * 1000);
+ return LocalTime.ofNanoOfDay(sGlobals.mService.getCustomNightModeStart() * 1000);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -874,9 +904,9 @@
* @param time The time of the day Dark theme should activate
*/
public void setCustomNightModeStart(@NonNull LocalTime time) {
- if (mService != null) {
+ if (sGlobals != null) {
try {
- mService.setCustomNightModeStart(time.toNanoOfDay() / 1000);
+ sGlobals.mService.setCustomNightModeStart(time.toNanoOfDay() / 1000);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -891,9 +921,9 @@
*/
@NonNull
public LocalTime getCustomNightModeEnd() {
- if (mService != null) {
+ if (sGlobals != null) {
try {
- return LocalTime.ofNanoOfDay(mService.getCustomNightModeEnd() * 1000);
+ return LocalTime.ofNanoOfDay(sGlobals.mService.getCustomNightModeEnd() * 1000);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -909,9 +939,9 @@
* @param time The time of the day Dark theme should deactivate
*/
public void setCustomNightModeEnd(@NonNull LocalTime time) {
- if (mService != null) {
+ if (sGlobals != null) {
try {
- mService.setCustomNightModeEnd(time.toNanoOfDay() / 1000);
+ sGlobals.mService.setCustomNightModeEnd(time.toNanoOfDay() / 1000);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -976,9 +1006,9 @@
@RequiresPermission(value = android.Manifest.permission.TOGGLE_AUTOMOTIVE_PROJECTION,
conditional = true)
public boolean requestProjection(@ProjectionType int projectionType) {
- if (mService != null) {
+ if (sGlobals != null) {
try {
- return mService.requestProjection(new Binder(), projectionType,
+ return sGlobals.mService.requestProjection(new Binder(), projectionType,
mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -1005,9 +1035,10 @@
@RequiresPermission(value = android.Manifest.permission.TOGGLE_AUTOMOTIVE_PROJECTION,
conditional = true)
public boolean releaseProjection(@ProjectionType int projectionType) {
- if (mService != null) {
+ if (sGlobals != null) {
try {
- return mService.releaseProjection(projectionType, mContext.getOpPackageName());
+ return sGlobals.mService.releaseProjection(
+ projectionType, mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1028,9 +1059,9 @@
@RequiresPermission(android.Manifest.permission.READ_PROJECTION_STATE)
@NonNull
public Set<String> getProjectingPackages(@ProjectionType int projectionType) {
- if (mService != null) {
+ if (sGlobals != null) {
try {
- return new ArraySet<>(mService.getProjectingPackages(projectionType));
+ return new ArraySet<>(sGlobals.mService.getProjectingPackages(projectionType));
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1046,9 +1077,9 @@
@SystemApi
@RequiresPermission(android.Manifest.permission.READ_PROJECTION_STATE)
public @ProjectionType int getActiveProjectionTypes() {
- if (mService != null) {
+ if (sGlobals != null) {
try {
- return mService.getActiveProjectionTypes();
+ return sGlobals.mService.getActiveProjectionTypes();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1076,11 +1107,12 @@
Slog.i(TAG, "Attempted to add listener that was already added.");
return;
}
- if (mService != null) {
+ if (sGlobals != null) {
InnerListener innerListener = new InnerListener(executor, listener,
mOnProjectionStateChangedListenerResourceManager);
try {
- mService.addOnProjectionStateChangedListener(innerListener, projectionType);
+ sGlobals.mService.addOnProjectionStateChangedListener(
+ innerListener, projectionType);
mProjectionStateListenerMap.put(listener, innerListener);
} catch (RemoteException e) {
mOnProjectionStateChangedListenerResourceManager.remove(innerListener);
@@ -1107,9 +1139,9 @@
Slog.i(TAG, "Attempted to remove listener that was not added.");
return;
}
- if (mService != null) {
+ if (sGlobals != null) {
try {
- mService.removeOnProjectionStateChangedListener(innerListener);
+ sGlobals.mService.removeOnProjectionStateChangedListener(innerListener);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1197,15 +1229,10 @@
* <li> -1 corresponds to the minimum contrast </li>
* <li> 1 corresponds to the maximum contrast </li>
* </ul>
- *
- *
- *
*/
@FloatRange(from = -1.0f, to = 1.0f)
public float getContrast() {
- synchronized (mLock) {
- return mContrast;
- }
+ return sGlobals.getContrast();
}
/**
@@ -1219,9 +1246,7 @@
@NonNull ContrastChangeListener listener) {
Objects.requireNonNull(executor);
Objects.requireNonNull(listener);
- synchronized (mLock) {
- mContrastChangeListeners.put(listener, executor);
- }
+ sGlobals.addContrastChangeListener(listener, executor);
}
/**
@@ -1232,8 +1257,6 @@
*/
public void removeContrastChangeListener(@NonNull ContrastChangeListener listener) {
Objects.requireNonNull(listener);
- synchronized (mLock) {
- mContrastChangeListeners.remove(listener);
- }
+ sGlobals.removeContrastChangeListener(listener);
}
}
diff --git a/core/java/android/app/admin/PasswordMetrics.java b/core/java/android/app/admin/PasswordMetrics.java
index 93c0c8a..dd31175 100644
--- a/core/java/android/app/admin/PasswordMetrics.java
+++ b/core/java/android/app/admin/PasswordMetrics.java
@@ -179,17 +179,11 @@
};
/**
- * Returns the {@code PasswordMetrics} for a given credential.
- *
- * If the credential is a pin or a password, equivalent to
- * {@link #computeForPasswordOrPin(byte[], boolean)}. {@code credential} cannot be null
- * when {@code type} is
- * {@link com.android.internal.widget.LockPatternUtils#CREDENTIAL_TYPE_PASSWORD}.
+ * Returns the {@code PasswordMetrics} for the given credential.
*/
public static PasswordMetrics computeForCredential(LockscreenCredential credential) {
if (credential.isPassword() || credential.isPin()) {
- return PasswordMetrics.computeForPasswordOrPin(credential.getCredential(),
- credential.isPin());
+ return computeForPasswordOrPin(credential.getCredential(), credential.isPin());
} else if (credential.isPattern()) {
PasswordMetrics metrics = new PasswordMetrics(CREDENTIAL_TYPE_PATTERN);
metrics.length = credential.size();
@@ -202,10 +196,10 @@
}
/**
- * Returns the {@code PasswordMetrics} for a given password or pin
+ * Returns the {@code PasswordMetrics} for the given password or pin.
*/
- public static PasswordMetrics computeForPasswordOrPin(byte[] password, boolean isPin) {
- // Analyse the characters used
+ private static PasswordMetrics computeForPasswordOrPin(byte[] credential, boolean isPin) {
+ // Analyze the characters used.
int letters = 0;
int upperCase = 0;
int lowerCase = 0;
@@ -213,8 +207,8 @@
int symbols = 0;
int nonLetter = 0;
int nonNumeric = 0;
- final int length = password.length;
- for (byte b : password) {
+ final int length = credential.length;
+ for (byte b : credential) {
switch (categoryChar((char) b)) {
case CHAR_LOWER_CASE:
letters++;
@@ -239,7 +233,7 @@
}
final int credType = isPin ? CREDENTIAL_TYPE_PIN : CREDENTIAL_TYPE_PASSWORD;
- final int seqLength = maxLengthSequence(password);
+ final int seqLength = maxLengthSequence(credential);
return new PasswordMetrics(credType, length, letters, upperCase, lowerCase,
numeric, symbols, nonLetter, nonNumeric, seqLength);
}
diff --git a/core/java/android/app/cloudsearch/OWNERS b/core/java/android/app/cloudsearch/OWNERS
index aa4da3b..3a5841e 100644
--- a/core/java/android/app/cloudsearch/OWNERS
+++ b/core/java/android/app/cloudsearch/OWNERS
@@ -1,4 +1,3 @@
# Bug component: 758286
-huiwu@google.com
srazdan@google.com
diff --git a/core/java/android/app/servertransaction/WindowTokenClientController.java b/core/java/android/app/servertransaction/WindowTokenClientController.java
new file mode 100644
index 0000000..28e2040
--- /dev/null
+++ b/core/java/android/app/servertransaction/WindowTokenClientController.java
@@ -0,0 +1,164 @@
+/*
+ * 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.app.servertransaction;
+
+import static android.view.WindowManager.LayoutParams.WindowType;
+import static android.view.WindowManagerGlobal.getWindowManagerService;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+import android.view.IWindowManager;
+import android.window.WindowContext;
+import android.window.WindowTokenClient;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * Singleton controller to manage the attached {@link WindowTokenClient}s, and to dispatch
+ * corresponding window configuration change from server side.
+ * @hide
+ */
+public class WindowTokenClientController {
+
+ private static WindowTokenClientController sController;
+
+ private final Object mLock = new Object();
+
+ /** Mapping from a client defined token to the {@link WindowTokenClient} it represents. */
+ @GuardedBy("mLock")
+ private final ArrayMap<IBinder, WindowTokenClient> mWindowTokenClientMap = new ArrayMap<>();
+
+ /** Gets the singleton controller. */
+ public static WindowTokenClientController getInstance() {
+ synchronized (WindowTokenClientController.class) {
+ if (sController == null) {
+ sController = new WindowTokenClientController();
+ }
+ return sController;
+ }
+ }
+
+ /** Overrides the {@link #getInstance()} for test only. */
+ @VisibleForTesting
+ public static void overrideInstance(@NonNull WindowTokenClientController controller) {
+ synchronized (WindowTokenClientController.class) {
+ sController = controller;
+ }
+ }
+
+ private WindowTokenClientController() {}
+
+ /**
+ * Attaches a {@link WindowTokenClient} to a {@link com.android.server.wm.DisplayArea}.
+ *
+ * @param client The {@link WindowTokenClient} to attach.
+ * @param type The window type of the {@link WindowContext}
+ * @param displayId The {@link Context#getDisplayId() ID of display} to associate with
+ * @param options The window context launched option
+ * @return {@code true} if attaching successfully.
+ */
+ public boolean attachToDisplayArea(@NonNull WindowTokenClient client,
+ @WindowType int type, int displayId, @Nullable Bundle options) {
+ final Configuration configuration;
+ try {
+ configuration = getWindowManagerService()
+ .attachWindowContextToDisplayArea(client, type, displayId, options);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ if (configuration == null) {
+ return false;
+ }
+ onWindowContainerTokenAttached(client, displayId, configuration);
+ return true;
+ }
+
+ /**
+ * Attaches a {@link WindowTokenClient} to a {@code DisplayContent}.
+ *
+ * @param client The {@link WindowTokenClient} to attach.
+ * @param displayId The {@link Context#getDisplayId() ID of display} to associate with
+ * @return {@code true} if attaching successfully.
+ */
+ public boolean attachToDisplayContent(@NonNull WindowTokenClient client, int displayId) {
+ final IWindowManager wms = getWindowManagerService();
+ // #createSystemUiContext may call this method before WindowManagerService is initialized.
+ if (wms == null) {
+ return false;
+ }
+ final Configuration configuration;
+ try {
+ configuration = wms.attachToDisplayContent(client, displayId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ if (configuration == null) {
+ return false;
+ }
+ onWindowContainerTokenAttached(client, displayId, configuration);
+ return true;
+ }
+
+ /**
+ * Attaches this {@link WindowTokenClient} to a {@code windowToken}.
+ *
+ * @param client The {@link WindowTokenClient} to attach.
+ * @param windowToken the window token to associated with
+ */
+ public void attachToWindowToken(@NonNull WindowTokenClient client,
+ @NonNull IBinder windowToken) {
+ try {
+ getWindowManagerService().attachWindowContextToWindowToken(client, windowToken);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ // We don't report configuration change for now.
+ synchronized (mLock) {
+ mWindowTokenClientMap.put(client.asBinder(), client);
+ }
+ }
+
+ /** Detaches a {@link WindowTokenClient} from associated WindowContainer if there's one. */
+ public void detachIfNeeded(@NonNull WindowTokenClient client) {
+ synchronized (mLock) {
+ if (mWindowTokenClientMap.remove(client.asBinder()) == null) {
+ return;
+ }
+ }
+ try {
+ getWindowManagerService().detachWindowContextFromWindowContainer(client);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ private void onWindowContainerTokenAttached(@NonNull WindowTokenClient client, int displayId,
+ @NonNull Configuration configuration) {
+ synchronized (mLock) {
+ mWindowTokenClientMap.put(client.asBinder(), client);
+ }
+ client.onConfigurationChanged(configuration, displayId,
+ false /* shouldReportConfigChange */);
+ }
+}
diff --git a/core/java/android/app/wallpapereffectsgeneration/OWNERS b/core/java/android/app/wallpapereffectsgeneration/OWNERS
index 2bc0154..30949f8 100644
--- a/core/java/android/app/wallpapereffectsgeneration/OWNERS
+++ b/core/java/android/app/wallpapereffectsgeneration/OWNERS
@@ -1,5 +1,4 @@
susharon@google.com
shanh@google.com
-huiwu@google.com
srazdan@google.com
diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java
index 4dea4a7..e4b2cf3 100644
--- a/core/java/android/companion/CompanionDeviceManager.java
+++ b/core/java/android/companion/CompanionDeviceManager.java
@@ -208,14 +208,6 @@
public static final String EXTRA_ASSOCIATION = "android.companion.extra.ASSOCIATION";
/**
- * The package name of the companion device discovery component.
- *
- * @hide
- */
- public static final String COMPANION_DEVICE_DISCOVERY_PACKAGE_NAME =
- "com.android.companiondevicemanager";
-
- /**
* Callback for applications to receive updates about and the outcome of
* {@link AssociationRequest} issued via {@code associate()} call.
*
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 6d82922..2a6d84b 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -5298,6 +5298,13 @@
public static final String APP_PREDICTION_SERVICE = "app_prediction";
/**
+ * Used for reading system-wide, overridable flags.
+ *
+ * @hide
+ */
+ public static final String FEATURE_FLAGS_SERVICE = "feature_flags";
+
+ /**
* Official published name of the search ui service.
*
* <p><b>NOTE: </b> this service is optional; callers of
diff --git a/core/java/android/content/om/IOverlayManager.aidl b/core/java/android/content/om/IOverlayManager.aidl
index a0f3d7a..122ab48 100644
--- a/core/java/android/content/om/IOverlayManager.aidl
+++ b/core/java/android/content/om/IOverlayManager.aidl
@@ -190,4 +190,15 @@
* @throws SecurityException if the transaction failed
*/
void commit(in OverlayManagerTransaction transaction);
+
+ /**
+ * Returns a String of a list of partitions from low priority to high.
+ */
+ String getPartitionOrder();
+
+ /**
+ * Returns a boolean which represent whether the partition list is sorted by default.
+ * If not then it should be sorted by /product/overlay/partition_order.xml.
+ */
+ boolean isDefaultPartitionOrder();
}
diff --git a/core/java/android/content/om/TEST_MAPPING b/core/java/android/content/om/TEST_MAPPING
index a9aaf1a..eb9254d 100644
--- a/core/java/android/content/om/TEST_MAPPING
+++ b/core/java/android/content/om/TEST_MAPPING
@@ -18,12 +18,7 @@
"name": "OverlayHostTests"
},
{
- "name": "CtsAppSecurityHostTestCases",
- "options": [
- {
- "include-filter": "android.appsecurity.cts.OverlayHostTest"
- }
- ]
+ "name": "CtsOverlayHostTestCases"
}
],
"presubmit-large": [
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index 679dd48..b43639a 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -4132,7 +4132,8 @@
* @param label
* The label representing the app to be installed.
* @param locale
- * The locale of the app label being used.
+ * The locale is used to get the app label from the APKs (includes the base APK and
+ * split APKs) related to the package to be installed.
* @param packageName
* The package name of the app to be installed.
* @hide
@@ -4239,7 +4240,10 @@
}
/**
- * The locale of the app label being used.
+ * The locale is used to get the app label from the APKs (includes the base APK and
+ * split APKs) related to the package to be installed. The caller needs to make sure
+ * the app label is consistent with the app label of {@link PreapprovalDetails} when
+ * validating the installation. Otherwise, the pre-approval install session will fail.
*/
public @NonNull Builder setLocale(@NonNull ULocale value) {
checkNotUsed();
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 2948bd9..bef023e 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -1432,7 +1432,6 @@
INSTALL_ALLOW_DOWNGRADE,
INSTALL_STAGED,
INSTALL_REQUEST_UPDATE_OWNERSHIP,
- INSTALL_DONT_EXTRACT_BASELINE_PROFILES,
})
@Retention(RetentionPolicy.SOURCE)
public @interface InstallFlags {}
@@ -1647,13 +1646,6 @@
*/
public static final int INSTALL_FROM_MANAGED_USER_OR_PROFILE = 1 << 26;
- /**
- * Flag parameter for {@link PackageInstaller.SessionParams} to indicate that do not extract
- * the baseline profiles when parsing the apk
- * @hide
- */
- public static final int INSTALL_DONT_EXTRACT_BASELINE_PROFILES = 1 << 27;
-
/** @hide */
@IntDef(flag = true, value = {
DONT_KILL_APP,
diff --git a/core/java/android/content/pm/TEST_MAPPING b/core/java/android/content/pm/TEST_MAPPING
index 3ffbe1d..3364b7a 100644
--- a/core/java/android/content/pm/TEST_MAPPING
+++ b/core/java/android/content/pm/TEST_MAPPING
@@ -97,6 +97,23 @@
"include-filter":"android.incrementalinstall.cts.IncrementalFeatureTest"
}
]
+ },
+ {
+ "name":"CtsPackageManagerHostTestCases",
+ "options":[
+ {
+ "include-annotation":"android.platform.test.annotations.Presubmit"
+ },
+ {
+ "exclude-annotation":"android.platform.test.annotations.Postsubmit"
+ },
+ {
+ "exclude-annotation":"androidx.test.filters.FlakyTest"
+ },
+ {
+ "exclude-annotation":"org.junit.Ignore"
+ }
+ ]
}
],
"presubmit-large":[
@@ -135,23 +152,6 @@
"exclude-annotation":"org.junit.Ignore"
}
]
- },
- {
- "name":"CtsAppSecurityHostTestCases",
- "options":[
- {
- "include-annotation":"android.platform.test.annotations.Presubmit"
- },
- {
- "exclude-annotation":"android.platform.test.annotations.Postsubmit"
- },
- {
- "exclude-annotation":"androidx.test.filters.FlakyTest"
- },
- {
- "exclude-annotation":"org.junit.Ignore"
- }
- ]
}
],
"postsubmit":[
diff --git a/core/java/android/content/pm/dex/DexMetadataHelper.java b/core/java/android/content/pm/dex/DexMetadataHelper.java
index 3b53c25..e75aa06 100644
--- a/core/java/android/content/pm/dex/DexMetadataHelper.java
+++ b/core/java/android/content/pm/dex/DexMetadataHelper.java
@@ -23,9 +23,6 @@
import android.content.pm.parsing.PackageLite;
import android.content.pm.parsing.result.ParseInput;
import android.content.pm.parsing.result.ParseResult;
-import android.content.res.AssetFileDescriptor;
-import android.content.res.AssetManager;
-import android.os.ParcelFileDescriptor.AutoCloseInputStream;
import android.os.SystemProperties;
import android.util.ArrayMap;
import android.util.JsonReader;
@@ -36,7 +33,6 @@
import com.android.internal.security.VerityUtils;
import java.io.File;
-import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
@@ -48,7 +44,6 @@
import java.util.List;
import java.util.Map;
import java.util.zip.ZipEntry;
-import java.util.zip.ZipOutputStream;
/**
* Helper class used to compute and validate the location of dex metadata files.
@@ -67,11 +62,6 @@
private static final String DEX_METADATA_FILE_EXTENSION = ".dm";
- private static final String PROFILE_FILE_NAME = "primary.prof";
- private static final String PROFILE_METADATA_FILE_NAME = "primary.profm";
- private static final String BASELINE_PROFILE_SOURCE_RELATIVE_PATH = "dexopt/baseline.prof";
- private static final String BASELINE_PROFILE_METADATA_RELATIVE_PATH = "dexopt/baseline.profm";
-
private DexMetadataHelper() {}
/** Return true if the given file is a dex metadata file. */
@@ -323,76 +313,4 @@
}
}
- /**
- * Extract the baseline profiles from the assets directory in the apk. Then create a
- * ZIP archive with the (.dm) file extension (e.g. foo.dm from foo.apk) to include the
- * baseline profiles and put the DexMetadata file in the same directory with the apk.
- *
- * @param assetManager The {@link AssetManager} to use.
- * @param apkPath The path of the apk
- * @return {@code true} if the extraction is successful. Otherwise, return {@code false}.
- *
- * @see #buildDexMetadataPathForApk(String)
- */
- public static boolean extractBaselineProfilesToDexMetadataFileFromApk(AssetManager assetManager,
- String apkPath) {
- if (!ApkLiteParseUtils.isApkPath(apkPath)) {
- if (DEBUG) {
- Log.d(TAG, "It is not an apk file: " + apkPath);
- }
- return false;
- }
-
- // get the name of the DexMetadata file from the path of the apk
- final File dmFile = new File(buildDexMetadataPathForApk(apkPath));
- boolean success = false;
-
- // load profile and profile metadata from assets directory in the apk
- try (InputStream profileIs = openStreamFromAssets(assetManager,
- BASELINE_PROFILE_SOURCE_RELATIVE_PATH);
- InputStream profileMetadataIs = openStreamFromAssets(assetManager,
- BASELINE_PROFILE_METADATA_RELATIVE_PATH)) {
- // Create the zip archive file and write the baseline profiles into it.
- try (FileOutputStream fos = new FileOutputStream(dmFile)) {
- try (ZipOutputStream zipOs = new ZipOutputStream(fos)) {
- zipOs.putNextEntry(new ZipEntry(PROFILE_FILE_NAME));
- zipOs.write(profileIs.readAllBytes());
- zipOs.closeEntry();
-
- zipOs.putNextEntry(new ZipEntry(PROFILE_METADATA_FILE_NAME));
- zipOs.write(profileMetadataIs.readAllBytes());
- zipOs.closeEntry();
- success = true;
- }
- }
- } catch (IOException e) {
- if (DEBUG) {
- Log.e(TAG, "Extract baseline profiles from apk failed: " + e.getMessage());
- }
- } finally {
- if (!success) {
- if (dmFile.exists()) {
- dmFile.delete();
- }
- }
- }
- return success;
- }
-
- /**
- * Loads an {@link AutoCloseInputStream} from assets with the path.
- *
- * @param assetManager The {@link AssetManager} to use.
- * @param path The source file's relative path.
- * @return An AutoCloseInputStream in case the file was successfully read.
- * @throws IOException If anything goes wrong while opening or reading the file.
- */
- private static AutoCloseInputStream openStreamFromAssets(AssetManager assetManager, String path)
- throws IOException {
- AssetFileDescriptor descriptor = assetManager.openFd(path);
- // Based on the java doc of AssetFileDescriptor#createInputStream, it will always return
- // an AutoCloseInputStream. It should be fine we cast it from FileInputStream to
- // AutoCloseInputStream here.
- return (AutoCloseInputStream) descriptor.createInputStream();
- }
}
diff --git a/core/java/android/content/res/ApkAssets.java b/core/java/android/content/res/ApkAssets.java
index 4089cfe..653e243 100644
--- a/core/java/android/content/res/ApkAssets.java
+++ b/core/java/android/content/res/ApkAssets.java
@@ -15,8 +15,6 @@
*/
package android.content.res;
-import static android.content.res.Resources.ID_NULL;
-
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -388,7 +386,7 @@
synchronized (this) {
long nativeXmlPtr = nativeOpenXml(mNativePtr, fileName);
try (XmlBlock block = new XmlBlock(null, nativeXmlPtr)) {
- XmlResourceParser parser = block.newParser(ID_NULL, new Validator());
+ XmlResourceParser parser = block.newParser();
// If nativeOpenXml doesn't throw, it will always return a valid native pointer,
// which makes newParser always return non-null. But let's be careful.
if (parser == null) {
diff --git a/core/java/android/content/res/Element.java b/core/java/android/content/res/Element.java
index a6fea6a..62a46b6 100644
--- a/core/java/android/content/res/Element.java
+++ b/core/java/android/content/res/Element.java
@@ -17,7 +17,6 @@
package android.content.res;
import android.annotation.NonNull;
-import android.util.ArrayMap;
import android.util.Pools.SimplePool;
import androidx.annotation.StyleableRes;
@@ -27,9 +26,6 @@
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
-import java.util.Iterator;
-import java.util.Map;
-
/**
* Defines the string attribute length and child tag count restrictions for a xml element.
*
@@ -161,9 +157,11 @@
private static final String[] NAME_VALUE_ATTRS = {TAG_ATTR_NAME, TAG_ATTR_VALUE};
private String[] mStringAttrNames = new String[0];
- private final Map<String, TagCounter> mTagCounters = new ArrayMap<>();
+ // The length of mTagCounters corresponds to the number of tags defined in getCounterIdx. If new
+ // tags are added then the size here should be increased to match.
+ private final TagCounter[] mTagCounters = new TagCounter[35];
- private String mTag;
+ String mTag;
private static final ThreadLocal<SimplePool<Element>> sPool =
ThreadLocal.withInitial(() -> new SimplePool<>(MAX_POOL_SIZE));
@@ -180,17 +178,91 @@
void recycle() {
mStringAttrNames = new String[0];
- Iterator<Map.Entry<String, TagCounter>> it = mTagCounters.entrySet().iterator();
- while (it.hasNext()) {
- it.next().getValue().recycle();
- it.remove();
- }
mTag = null;
sPool.get().release(this);
}
+ private long mChildTagMask = 0;
+
+ private static int getCounterIdx(String tag) {
+ switch(tag) {
+ case TAG_LAYOUT:
+ return 0;
+ case TAG_META_DATA:
+ return 1;
+ case TAG_INTENT_FILTER:
+ return 2;
+ case TAG_PROFILEABLE:
+ return 3;
+ case TAG_USES_NATIVE_LIBRARY:
+ return 4;
+ case TAG_RECEIVER:
+ return 5;
+ case TAG_SERVICE:
+ return 6;
+ case TAG_ACTIVITY_ALIAS:
+ return 7;
+ case TAG_USES_LIBRARY:
+ return 8;
+ case TAG_PROVIDER:
+ return 9;
+ case TAG_ACTIVITY:
+ return 10;
+ case TAG_ACTION:
+ return 11;
+ case TAG_CATEGORY:
+ return 12;
+ case TAG_DATA:
+ return 13;
+ case TAG_APPLICATION:
+ return 14;
+ case TAG_OVERLAY:
+ return 15;
+ case TAG_INSTRUMENTATION:
+ return 16;
+ case TAG_PERMISSION_GROUP:
+ return 17;
+ case TAG_PERMISSION_TREE:
+ return 18;
+ case TAG_SUPPORTS_GL_TEXTURE:
+ return 19;
+ case TAG_SUPPORTS_SCREENS:
+ return 20;
+ case TAG_USES_CONFIGURATION:
+ return 21;
+ case TAG_USES_PERMISSION_SDK_23:
+ return 22;
+ case TAG_USES_SDK:
+ return 23;
+ case TAG_COMPATIBLE_SCREENS:
+ return 24;
+ case TAG_QUERIES:
+ return 25;
+ case TAG_ATTRIBUTION:
+ return 26;
+ case TAG_USES_FEATURE:
+ return 27;
+ case TAG_PERMISSION:
+ return 28;
+ case TAG_USES_PERMISSION:
+ return 29;
+ case TAG_GRANT_URI_PERMISSION:
+ return 30;
+ case TAG_PATH_PERMISSION:
+ return 31;
+ case TAG_PACKAGE:
+ return 32;
+ case TAG_INTENT:
+ return 33;
+ default:
+ // The size of the mTagCounters array should be equal to this value+1
+ return 34;
+ }
+ }
+
private void init(String tag) {
this.mTag = tag;
+ mChildTagMask = 0;
switch (tag) {
case TAG_ACTION:
case TAG_CATEGORY:
@@ -208,29 +280,29 @@
break;
case TAG_ACTIVITY:
setStringAttrNames(ACTIVITY_STR_ATTR_NAMES);
- addTagCounter(1000, TAG_LAYOUT);
- addTagCounter(8000, TAG_META_DATA);
- addTagCounter(20000, TAG_INTENT_FILTER);
+ initializeCounter(TAG_LAYOUT, 1000);
+ initializeCounter(TAG_META_DATA, 8000);
+ initializeCounter(TAG_INTENT_FILTER, 20000);
break;
case TAG_ACTIVITY_ALIAS:
setStringAttrNames(ACTIVITY_ALIAS_STR_ATTR_NAMES);
- addTagCounter(8000, TAG_META_DATA);
- addTagCounter(20000, TAG_INTENT_FILTER);
+ initializeCounter(TAG_META_DATA, 8000);
+ initializeCounter(TAG_INTENT_FILTER, 20000);
break;
case TAG_APPLICATION:
setStringAttrNames(APPLICATION_STR_ATTR_NAMES);
- addTagCounter(100, TAG_PROFILEABLE);
- addTagCounter(100, TAG_USES_NATIVE_LIBRARY);
- addTagCounter(1000, TAG_RECEIVER);
- addTagCounter(1000, TAG_SERVICE);
- addTagCounter(4000, TAG_ACTIVITY_ALIAS);
- addTagCounter(4000, TAG_USES_LIBRARY);
- addTagCounter(8000, TAG_PROVIDER);
- addTagCounter(8000, TAG_META_DATA);
- addTagCounter(40000, TAG_ACTIVITY);
+ initializeCounter(TAG_PROFILEABLE, 100);
+ initializeCounter(TAG_USES_NATIVE_LIBRARY, 100);
+ initializeCounter(TAG_RECEIVER, 1000);
+ initializeCounter(TAG_SERVICE, 1000);
+ initializeCounter(TAG_ACTIVITY_ALIAS, 4000);
+ initializeCounter(TAG_USES_LIBRARY, 4000);
+ initializeCounter(TAG_PROVIDER, 8000);
+ initializeCounter(TAG_META_DATA, 8000);
+ initializeCounter(TAG_ACTIVITY, 40000);
break;
case TAG_COMPATIBLE_SCREENS:
- addTagCounter(4000, TAG_SCREEN);
+ initializeCounter(TAG_SCREEN, 4000);
break;
case TAG_DATA:
setStringAttrNames(DATA_STR_ATTR_NAMES);
@@ -243,28 +315,28 @@
break;
case TAG_INTENT:
case TAG_INTENT_FILTER:
- addTagCounter(20000, TAG_ACTION);
- addTagCounter(40000, TAG_CATEGORY);
- addTagCounter(40000, TAG_DATA);
+ initializeCounter(TAG_ACTION, 20000);
+ initializeCounter(TAG_CATEGORY, 40000);
+ initializeCounter(TAG_DATA, 40000);
break;
case TAG_MANIFEST:
setStringAttrNames(MANIFEST_STR_ATTR_NAMES);
- addTagCounter(100, TAG_APPLICATION);
- addTagCounter(100, TAG_OVERLAY);
- addTagCounter(100, TAG_INSTRUMENTATION);
- addTagCounter(100, TAG_PERMISSION_GROUP);
- addTagCounter(100, TAG_PERMISSION_TREE);
- addTagCounter(100, TAG_SUPPORTS_GL_TEXTURE);
- addTagCounter(100, TAG_SUPPORTS_SCREENS);
- addTagCounter(100, TAG_USES_CONFIGURATION);
- addTagCounter(100, TAG_USES_PERMISSION_SDK_23);
- addTagCounter(100, TAG_USES_SDK);
- addTagCounter(200, TAG_COMPATIBLE_SCREENS);
- addTagCounter(200, TAG_QUERIES);
- addTagCounter(400, TAG_ATTRIBUTION);
- addTagCounter(400, TAG_USES_FEATURE);
- addTagCounter(2000, TAG_PERMISSION);
- addTagCounter(20000, TAG_USES_PERMISSION);
+ initializeCounter(TAG_APPLICATION, 100);
+ initializeCounter(TAG_OVERLAY, 100);
+ initializeCounter(TAG_INSTRUMENTATION, 100);
+ initializeCounter(TAG_PERMISSION_GROUP, 100);
+ initializeCounter(TAG_PERMISSION_TREE, 100);
+ initializeCounter(TAG_SUPPORTS_GL_TEXTURE, 100);
+ initializeCounter(TAG_SUPPORTS_SCREENS, 100);
+ initializeCounter(TAG_USES_CONFIGURATION, 100);
+ initializeCounter(TAG_USES_PERMISSION_SDK_23, 100);
+ initializeCounter(TAG_USES_SDK, 100);
+ initializeCounter(TAG_COMPATIBLE_SCREENS, 200);
+ initializeCounter(TAG_QUERIES, 200);
+ initializeCounter(TAG_ATTRIBUTION, 400);
+ initializeCounter(TAG_USES_FEATURE, 400);
+ initializeCounter(TAG_PERMISSION, 2000);
+ initializeCounter(TAG_USES_PERMISSION, 20000);
break;
case TAG_META_DATA:
case TAG_PROPERTY:
@@ -281,21 +353,21 @@
break;
case TAG_PROVIDER:
setStringAttrNames(PROVIDER_STR_ATTR_NAMES);
- addTagCounter(100, TAG_GRANT_URI_PERMISSION);
- addTagCounter(100, TAG_PATH_PERMISSION);
- addTagCounter(8000, TAG_META_DATA);
- addTagCounter(20000, TAG_INTENT_FILTER);
+ initializeCounter(TAG_GRANT_URI_PERMISSION, 100);
+ initializeCounter(TAG_PATH_PERMISSION, 100);
+ initializeCounter(TAG_META_DATA, 8000);
+ initializeCounter(TAG_INTENT_FILTER, 20000);
break;
case TAG_QUERIES:
- addTagCounter(1000, TAG_PACKAGE);
- addTagCounter(2000, TAG_INTENT);
- addTagCounter(8000, TAG_PROVIDER);
+ initializeCounter(TAG_PACKAGE, 1000);
+ initializeCounter(TAG_INTENT, 2000);
+ initializeCounter(TAG_PROVIDER, 8000);
break;
case TAG_RECEIVER:
case TAG_SERVICE:
setStringAttrNames(RECEIVER_SERVICE_STR_ATTR_NAMES);
- addTagCounter(8000, TAG_META_DATA);
- addTagCounter(20000, TAG_INTENT_FILTER);
+ initializeCounter(TAG_META_DATA, 8000);
+ initializeCounter(TAG_INTENT_FILTER, 20000);
break;
}
}
@@ -365,12 +437,17 @@
}
}
- private void addTagCounter(int max, String tag) {
- mTagCounters.put(tag, TagCounter.obtain(max));
+ private void initializeCounter(String tag, int max) {
+ int idx = getCounterIdx(tag);
+ if (mTagCounters[idx] == null) {
+ mTagCounters[idx] = new TagCounter();
+ }
+ mTagCounters[idx].reset(max);
+ mChildTagMask |= 1 << idx;
}
boolean hasChild(String tag) {
- return mTagCounters.containsKey(tag);
+ return (mChildTagMask & (1 << getCounterIdx(tag))) != 0;
}
void validateStringAttrs(@NonNull XmlPullParser attrs) throws XmlPullParserException {
@@ -393,8 +470,8 @@
}
void seen(@NonNull Element element) throws XmlPullParserException {
- if (mTagCounters.containsKey(element.mTag)) {
- TagCounter counter = mTagCounters.get(element.mTag);
+ TagCounter counter = mTagCounters[getCounterIdx(element.mTag)];
+ if (counter != null) {
counter.increment();
if (!counter.isValid()) {
throw new XmlPullParserException("The number of child " + element.mTag
diff --git a/core/java/android/content/res/TagCounter.java b/core/java/android/content/res/TagCounter.java
index 0e5510f..94deee7 100644
--- a/core/java/android/content/res/TagCounter.java
+++ b/core/java/android/content/res/TagCounter.java
@@ -16,47 +16,25 @@
package android.content.res;
-import android.annotation.NonNull;
-import android.util.Pools.SimplePool;
-
/**
* Counter used to track the number of tags seen during manifest validation.
*
* {@hide}
*/
public class TagCounter {
- private static final int MAX_POOL_SIZE = 512;
private static final int DEFAULT_MAX_COUNT = 512;
- private static final ThreadLocal<SimplePool<TagCounter>> sPool =
- ThreadLocal.withInitial(() -> new SimplePool<>(MAX_POOL_SIZE));
-
private int mMaxValue;
private int mCount;
- @NonNull
- static TagCounter obtain(int max) {
- TagCounter counter = sPool.get().acquire();
- if (counter == null) {
- counter = new TagCounter();
- }
- counter.setMaxValue(max);
- return counter;
- }
-
- void recycle() {
- mCount = 0;
- mMaxValue = DEFAULT_MAX_COUNT;
- sPool.get().release(this);
- }
-
public TagCounter() {
mMaxValue = DEFAULT_MAX_COUNT;
mCount = 0;
}
- private void setMaxValue(int maxValue) {
+ void reset(int maxValue) {
this.mMaxValue = maxValue;
+ this.mCount = 0;
}
void increment() {
@@ -66,8 +44,4 @@
public boolean isValid() {
return mCount <= mMaxValue;
}
-
- int value() {
- return mCount;
- }
}
diff --git a/core/java/android/content/res/Validator.java b/core/java/android/content/res/Validator.java
index daa7dfe..8b5e6c6 100644
--- a/core/java/android/content/res/Validator.java
+++ b/core/java/android/content/res/Validator.java
@@ -24,9 +24,6 @@
import org.xmlpull.v1.XmlPullParserException;
import java.util.ArrayDeque;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.Map;
/**
* Validates manifest files by ensuring that tag counts and the length of string attributes are
@@ -36,30 +33,12 @@
*/
public class Validator {
- private static final int MAX_TAG_COUNT = 100000;
-
private final ArrayDeque<Element> mElements = new ArrayDeque<>();
- private final Map<String, TagCounter> mTagCounters = new HashMap<>();
private void cleanUp() {
while (!mElements.isEmpty()) {
mElements.pop().recycle();
}
- Iterator<Map.Entry<String, TagCounter>> it = mTagCounters.entrySet().iterator();
- while (it.hasNext()) {
- it.next().getValue().recycle();
- it.remove();
- }
- }
-
- private void seen(String tag) throws XmlPullParserException {
- mTagCounters.putIfAbsent(tag, TagCounter.obtain(MAX_TAG_COUNT));
- TagCounter counter = mTagCounters.get(tag);
- counter.increment();
- if (!counter.isValid()) {
- throw new XmlPullParserException("The number of " + tag
- + " tags exceeded " + MAX_TAG_COUNT);
- }
}
/**
@@ -84,7 +63,6 @@
}
Element parent = mElements.peek();
if (parent == null || parent.hasChild(tag)) {
- seen(tag);
Element element = Element.obtain(tag);
element.validateStringAttrs(parser);
if (parent != null) {
diff --git a/core/java/android/database/sqlite/SQLiteConnection.java b/core/java/android/database/sqlite/SQLiteConnection.java
index 382e4f8..b7489229 100644
--- a/core/java/android/database/sqlite/SQLiteConnection.java
+++ b/core/java/android/database/sqlite/SQLiteConnection.java
@@ -176,6 +176,8 @@
private static native void nativeCancel(long connectionPtr);
private static native void nativeResetCancel(long connectionPtr, boolean cancelable);
private static native int nativeLastInsertRowId(long connectionPtr);
+ private static native long nativeChanges(long connectionPtr);
+ private static native long nativeTotalChanges(long connectionPtr);
private SQLiteConnection(SQLiteConnectionPool pool,
SQLiteDatabaseConfiguration configuration,
@@ -1823,11 +1825,36 @@
* @return The ROWID of the last row to be inserted under this connection.
* @hide
*/
- long lastInsertRowId() {
+ long getLastInsertRowId() {
try {
return nativeLastInsertRowId(mConnectionPtr);
} finally {
Reference.reachabilityFence(this);
}
}
+
+ /**
+ * Return the number of database changes on the current connection made by the last SQL
+ * statement
+ * @hide
+ */
+ long getLastChangedRowsCount() {
+ try {
+ return nativeChanges(mConnectionPtr);
+ } finally {
+ Reference.reachabilityFence(this);
+ }
+ }
+
+ /**
+ * Return the total number of database changes made on the current connection.
+ * @hide
+ */
+ long getTotalChangedRowsCount() {
+ try {
+ return nativeTotalChanges(mConnectionPtr);
+ } finally {
+ Reference.reachabilityFence(this);
+ }
+ }
}
diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java
index eceec34..a3f8383 100644
--- a/core/java/android/database/sqlite/SQLiteDatabase.java
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -2197,7 +2197,49 @@
* @throws IllegalStateException if there is no current transaction.
*/
public long getLastInsertRowId() {
- return getThreadSession().lastInsertRowId();
+ return getThreadSession().getLastInsertRowId();
+ }
+
+ /**
+ * Return the number of database rows that were inserted, updated, or deleted by the most recent
+ * SQL statement within the current transaction.
+ *
+ * @see <a href="https://sqlite.org/c3ref/changes.html">sqlite3_changes64</a>
+ *
+ * @return The number of rows changed by the most recent sql statement
+ * @throws IllegalStateException if there is no current transaction.
+ * @hide
+ */
+ public long getLastChangedRowsCount() {
+ return getThreadSession().getLastChangedRowsCount();
+ }
+
+ /**
+ * Return the total number of database rows that have been inserted, updated, or deleted on
+ * the current connection since it was created. Due to Android's internal management of
+ * SQLite connections, the value may, or may not, include changes made in earlier
+ * transactions. Best practice is to compare values returned within a single transaction.
+ *
+ * <code><pre>
+ * database.beginTransaction();
+ * try {
+ * long initialValue = database.getTotalChangedRowsCount();
+ * // Execute SQL statements
+ * long changedRows = database.getTotalChangedRowsCount() - initialValue;
+ * // changedRows counts the total number of rows updated in the transaction.
+ * } finally {
+ * database.endTransaction();
+ * }
+ * </pre></code>
+ *
+ * @see <a href="https://sqlite.org/c3ref/changes.html">sqlite3_total_changes64</a>
+ *
+ * @return The number of rows changed on the current connection.
+ * @throws IllegalStateException if there is no current transaction.
+ * @hide
+ */
+ public long getTotalChangedRowsCount() {
+ return getThreadSession().getTotalChangedRowsCount();
}
/**
diff --git a/core/java/android/database/sqlite/SQLiteSession.java b/core/java/android/database/sqlite/SQLiteSession.java
index 2379c84..ef1a9cb 100644
--- a/core/java/android/database/sqlite/SQLiteSession.java
+++ b/core/java/android/database/sqlite/SQLiteSession.java
@@ -988,9 +988,29 @@
* necessary to acquire and release the connection: the connection has already been acquired.
* @hide
*/
- long lastInsertRowId() {
+ long getLastInsertRowId() {
throwIfNoTransaction();
- return mConnection.lastInsertRowId();
+ return mConnection.getLastInsertRowId();
+ }
+
+ /**
+ * Return the number of database rows that were changed by the most recent SQL statement on
+ * this connection.
+ * @hide
+ */
+ long getLastChangedRowsCount() {
+ throwIfNoTransaction();
+ return mConnection.getLastChangedRowsCount();
+ }
+
+ /**
+ * Return the total number of database rows that were changed on the current connection, since
+ * it was created.
+ * @hide
+ */
+ long getTotalChangedRowsCount() {
+ throwIfNoTransaction();
+ return mConnection.getTotalChangedRowsCount();
}
/**
diff --git a/core/java/android/flags/BooleanFlag.java b/core/java/android/flags/BooleanFlag.java
new file mode 100644
index 0000000..d4a35b2
--- /dev/null
+++ b/core/java/android/flags/BooleanFlag.java
@@ -0,0 +1,52 @@
+/*
+ * 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.flags;
+
+import android.annotation.NonNull;
+
+/**
+ * A flag representing a true or false value.
+ *
+ * The value will always be the same during the lifetime of the process it is read in.
+ *
+ * @hide
+ */
+public class BooleanFlag extends BooleanFlagBase {
+ private final boolean mDefault;
+
+ /**
+ * @param namespace A namespace for this flag. See {@link android.provider.DeviceConfig}.
+ * @param name A name for this flag.
+ * @param defaultValue The value of this flag if no other override is present.
+ */
+ BooleanFlag(String namespace, String name, boolean defaultValue) {
+ super(namespace, name);
+ mDefault = defaultValue;
+ }
+
+ @Override
+ @NonNull
+ public Boolean getDefault() {
+ return mDefault;
+ }
+
+ @Override
+ public BooleanFlag defineMetaData(String label, String description, String categoryName) {
+ super.defineMetaData(label, description, categoryName);
+ return this;
+ }
+}
diff --git a/core/java/android/flags/BooleanFlagBase.java b/core/java/android/flags/BooleanFlagBase.java
new file mode 100644
index 0000000..985dbe3
--- /dev/null
+++ b/core/java/android/flags/BooleanFlagBase.java
@@ -0,0 +1,82 @@
+/*
+ * 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.flags;
+
+import android.annotation.NonNull;
+
+abstract class BooleanFlagBase implements Flag<Boolean> {
+
+ private final String mNamespace;
+ private final String mName;
+ private String mLabel;
+ private String mDescription;
+ private String mCategoryName;
+
+ /**
+ * @param namespace A namespace for this flag. See {@link android.provider.DeviceConfig}.
+ * @param name A name for this flag.
+ */
+ BooleanFlagBase(String namespace, String name) {
+ mNamespace = namespace;
+ mName = name;
+ mLabel = name;
+ }
+
+ public abstract Boolean getDefault();
+
+ @Override
+ @NonNull
+ public String getNamespace() {
+ return mNamespace;
+ }
+
+ @Override
+ @NonNull
+ public String getName() {
+ return mName;
+ }
+
+ @Override
+ public BooleanFlagBase defineMetaData(String label, String description, String categoryName) {
+ mLabel = label;
+ mDescription = description;
+ mCategoryName = categoryName;
+ return this;
+ }
+
+ @Override
+ @NonNull
+ public String getLabel() {
+ return mLabel;
+ }
+
+ @Override
+ public String getDescription() {
+ return mDescription;
+ }
+
+ @Override
+ public String getCategoryName() {
+ return mCategoryName;
+ }
+
+ @Override
+ @NonNull
+ public String toString() {
+ return getNamespace() + "." + getName() + "[" + getDefault() + "]";
+ }
+}
diff --git a/core/java/android/flags/DynamicBooleanFlag.java b/core/java/android/flags/DynamicBooleanFlag.java
new file mode 100644
index 0000000..271a8c5f4
--- /dev/null
+++ b/core/java/android/flags/DynamicBooleanFlag.java
@@ -0,0 +1,50 @@
+/*
+ * 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.flags;
+
+/**
+ * A flag representing a true or false value.
+ *
+ * The value may be different from one read to the next.
+ *
+ * @hide
+ */
+public class DynamicBooleanFlag extends BooleanFlagBase implements DynamicFlag<Boolean> {
+
+ private final boolean mDefault;
+
+ /**
+ * @param namespace A namespace for this flag. See {@link android.provider.DeviceConfig}.
+ * @param name A name for this flag.
+ * @param defaultValue The value of this flag if no other override is present.
+ */
+ DynamicBooleanFlag(String namespace, String name, boolean defaultValue) {
+ super(namespace, name);
+ mDefault = defaultValue;
+ }
+
+ @Override
+ public Boolean getDefault() {
+ return mDefault;
+ }
+
+ @Override
+ public DynamicBooleanFlag defineMetaData(String label, String description, String categoryName) {
+ super.defineMetaData(label, description, categoryName);
+ return this;
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/BiometricSensorPrivacy.java b/core/java/android/flags/DynamicFlag.java
similarity index 66%
copy from services/core/java/com/android/server/biometrics/BiometricSensorPrivacy.java
copy to core/java/android/flags/DynamicFlag.java
index 6727fbc..68819c5 100644
--- a/services/core/java/com/android/server/biometrics/BiometricSensorPrivacy.java
+++ b/core/java/android/flags/DynamicFlag.java
@@ -14,12 +14,18 @@
* limitations under the License.
*/
-package com.android.server.biometrics;
+package android.flags;
/**
- * Interface for biometric operations to get camera privacy state.
+ * A flag for which the value may be different from one read to the next.
+ *
+ * @param <T> The type of value that this flag stores. E.g. Boolean or String.
+ *
+ * @hide
*/
-public interface BiometricSensorPrivacy {
- /* Returns true if privacy is enabled and camera access is disabled. */
- boolean isCameraPrivacyEnabled();
+public interface DynamicFlag<T> extends Flag<T> {
+ @Override
+ default boolean isDynamic() {
+ return true;
+ }
}
diff --git a/core/java/android/flags/FeatureFlags.java b/core/java/android/flags/FeatureFlags.java
new file mode 100644
index 0000000..8d3112c
--- /dev/null
+++ b/core/java/android/flags/FeatureFlags.java
@@ -0,0 +1,379 @@
+/*
+ * 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.flags;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.ArraySet;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A class for querying constants from the system - primarily booleans.
+ *
+ * Clients using this class can define their flags and their default values in one place,
+ * can override those values on running devices for debugging and testing purposes, and can control
+ * what flags are available to be used on release builds.
+ *
+ * TODO(b/279054964): A lot. This is skeleton code right now.
+ * @hide
+ */
+public class FeatureFlags {
+ private static final String TAG = "FeatureFlags";
+ private static FeatureFlags sInstance;
+ private static final Object sInstanceLock = new Object();
+
+ private final Set<Flag<?>> mKnownFlags = new ArraySet<>();
+ private final Set<Flag<?>> mDirtyFlags = new ArraySet<>();
+
+ private IFeatureFlags mIFeatureFlags;
+ private final Map<String, Map<String, Boolean>> mBooleanOverrides = new HashMap<>();
+ private final Set<ChangeListener> mListeners = new HashSet<>();
+
+ /**
+ * Obtain a per-process instance of FeatureFlags.
+ * @return A singleton instance of {@link FeatureFlags}.
+ */
+ @NonNull
+ public static FeatureFlags getInstance() {
+ synchronized (sInstanceLock) {
+ if (sInstance == null) {
+ sInstance = new FeatureFlags();
+ }
+ }
+
+ return sInstance;
+ }
+
+ /** See {@link FeatureFlagsFake}. */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ public static void setInstance(FeatureFlags instance) {
+ synchronized (sInstanceLock) {
+ sInstance = instance;
+ }
+ }
+
+ private final IFeatureFlagsCallback mIFeatureFlagsCallback = new IFeatureFlagsCallback.Stub() {
+ @Override
+ public void onFlagChange(SyncableFlag flag) {
+ for (Flag<?> f : mKnownFlags) {
+ if (flagEqualsSyncableFlag(f, flag)) {
+ if (f instanceof DynamicFlag<?>) {
+ if (f instanceof DynamicBooleanFlag) {
+ String value = flag.getValue();
+ if (value == null) { // Null means any existing overrides were erased.
+ value = ((DynamicBooleanFlag) f).getDefault().toString();
+ }
+ addBooleanOverride(flag.getNamespace(), flag.getName(), value);
+ }
+ FeatureFlags.this.onFlagChange((DynamicFlag<?>) f);
+ }
+ break;
+ }
+ }
+ }
+ };
+
+ private FeatureFlags() {
+ this(null);
+ }
+
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ public FeatureFlags(IFeatureFlags iFeatureFlags) {
+ mIFeatureFlags = iFeatureFlags;
+
+ if (mIFeatureFlags != null) {
+ try {
+ mIFeatureFlags.registerCallback(mIFeatureFlagsCallback);
+ } catch (RemoteException e) {
+ // Shouldn't happen with things passed into tests.
+ Log.e(TAG, "Could not register callbacks!", e);
+ }
+ }
+ }
+
+ /**
+ * Construct a new {@link BooleanFlag}.
+ *
+ * Use this instead of constructing a {@link BooleanFlag} directly, as it registers the flag
+ * with the internals of the flagging system.
+ */
+ @NonNull
+ public static BooleanFlag booleanFlag(
+ @NonNull String namespace, @NonNull String name, boolean def) {
+ return getInstance().addFlag(new BooleanFlag(namespace, name, def));
+ }
+
+ /**
+ * Construct a new {@link FusedOffFlag}.
+ *
+ * Use this instead of constructing a {@link FusedOffFlag} directly, as it registers the
+ * flag with the internals of the flagging system.
+ */
+ @NonNull
+ public static FusedOffFlag fusedOffFlag(@NonNull String namespace, @NonNull String name) {
+ return getInstance().addFlag(new FusedOffFlag(namespace, name));
+ }
+
+ /**
+ * Construct a new {@link FusedOnFlag}.
+ *
+ * Use this instead of constructing a {@link FusedOnFlag} directly, as it registers the flag
+ * with the internals of the flagging system.
+ */
+ @NonNull
+ public static FusedOnFlag fusedOnFlag(@NonNull String namespace, @NonNull String name) {
+ return getInstance().addFlag(new FusedOnFlag(namespace, name));
+ }
+
+ /**
+ * Construct a new {@link DynamicBooleanFlag}.
+ *
+ * Use this instead of constructing a {@link DynamicBooleanFlag} directly, as it registers
+ * the flag with the internals of the flagging system.
+ */
+ @NonNull
+ public static DynamicBooleanFlag dynamicBooleanFlag(
+ @NonNull String namespace, @NonNull String name, boolean def) {
+ return getInstance().addFlag(new DynamicBooleanFlag(namespace, name, def));
+ }
+
+ /**
+ * Add a listener to be alerted when a {@link DynamicFlag} changes.
+ *
+ * See also {@link #removeChangeListener(ChangeListener)}.
+ *
+ * @param listener The listener to add.
+ */
+ public void addChangeListener(@NonNull ChangeListener listener) {
+ mListeners.add(listener);
+ }
+
+ /**
+ * Remove a listener that was added earlier.
+ *
+ * See also {@link #addChangeListener(ChangeListener)}.
+ *
+ * @param listener The listener to remove.
+ */
+ public void removeChangeListener(@NonNull ChangeListener listener) {
+ mListeners.remove(listener);
+ }
+
+ protected void onFlagChange(@NonNull DynamicFlag<?> flag) {
+ for (ChangeListener l : mListeners) {
+ l.onFlagChanged(flag);
+ }
+ }
+
+ /**
+ * Returns whether the supplied flag is true or not.
+ *
+ * {@link BooleanFlag} should only be used in debug builds. They do not get optimized out.
+ *
+ * The first time a flag is read, its value is cached for the lifetime of the process.
+ */
+ public boolean isEnabled(@NonNull BooleanFlag flag) {
+ return getBooleanInternal(flag);
+ }
+
+ /**
+ * Returns whether the supplied flag is true or not.
+ *
+ * Always returns false.
+ */
+ public boolean isEnabled(@NonNull FusedOffFlag flag) {
+ return false;
+ }
+
+ /**
+ * Returns whether the supplied flag is true or not.
+ *
+ * Always returns true;
+ */
+ public boolean isEnabled(@NonNull FusedOnFlag flag) {
+ return true;
+ }
+
+ /**
+ * Returns whether the supplied flag is true or not.
+ *
+ * Can return a different value for the flag each time it is called if an override comes in.
+ */
+ public boolean isCurrentlyEnabled(@NonNull DynamicBooleanFlag flag) {
+ return getBooleanInternal(flag);
+ }
+
+ private boolean getBooleanInternal(Flag<Boolean> flag) {
+ sync();
+ Map<String, Boolean> ns = mBooleanOverrides.get(flag.getNamespace());
+ Boolean value = null;
+ if (ns != null) {
+ value = ns.get(flag.getName());
+ }
+ if (value == null) {
+ throw new IllegalStateException("Boolean flag being read but was not synced: " + flag);
+ }
+
+ return value;
+ }
+
+ private <T extends Flag<?>> T addFlag(T flag) {
+ synchronized (FeatureFlags.class) {
+ mDirtyFlags.add(flag);
+ mKnownFlags.add(flag);
+ }
+ return flag;
+ }
+
+ /**
+ * Sync any known flags that have not yet been synced.
+ *
+ * This is called implicitly when any flag is read, and is not generally needed except in
+ * exceptional circumstances.
+ */
+ public void sync() {
+ synchronized (FeatureFlags.class) {
+ if (mDirtyFlags.isEmpty()) {
+ return;
+ }
+ syncInternal(mDirtyFlags);
+ mDirtyFlags.clear();
+ }
+ }
+
+ /**
+ * Called when new flags have been declared. Gives the implementation a chance to act on them.
+ *
+ * Guaranteed to be called from a synchronized, thread-safe context.
+ */
+ protected void syncInternal(Set<Flag<?>> dirtyFlags) {
+ IFeatureFlags iFeatureFlags = bind();
+ List<SyncableFlag> syncableFlags = new ArrayList<>();
+ for (Flag<?> f : dirtyFlags) {
+ syncableFlags.add(flagToSyncableFlag(f));
+ }
+
+ List<SyncableFlag> serverFlags = List.of(); // Need to initialize the list with something.
+ try {
+ // New values come back from the service.
+ serverFlags = iFeatureFlags.syncFlags(syncableFlags);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+
+ for (Flag<?> f : dirtyFlags) {
+ boolean found = false;
+ for (SyncableFlag sf : serverFlags) {
+ if (flagEqualsSyncableFlag(f, sf)) {
+ if (f instanceof BooleanFlag || f instanceof DynamicBooleanFlag) {
+ addBooleanOverride(sf.getNamespace(), sf.getName(), sf.getValue());
+ }
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ if (f instanceof BooleanFlag) {
+ addBooleanOverride(
+ f.getNamespace(),
+ f.getName(),
+ ((BooleanFlag) f).getDefault() ? "true" : "false");
+ }
+ }
+ }
+ }
+
+ private void addBooleanOverride(String namespace, String name, String override) {
+ Map<String, Boolean> nsOverrides = mBooleanOverrides.get(namespace);
+ if (nsOverrides == null) {
+ nsOverrides = new HashMap<>();
+ mBooleanOverrides.put(namespace, nsOverrides);
+ }
+ nsOverrides.put(name, parseBoolean(override));
+ }
+
+ private SyncableFlag flagToSyncableFlag(Flag<?> f) {
+ return new SyncableFlag(
+ f.getNamespace(),
+ f.getName(),
+ f.getDefault().toString(),
+ f instanceof DynamicFlag<?>);
+ }
+
+ private IFeatureFlags bind() {
+ if (mIFeatureFlags == null) {
+ mIFeatureFlags = IFeatureFlags.Stub.asInterface(
+ ServiceManager.getService(Context.FEATURE_FLAGS_SERVICE));
+ try {
+ mIFeatureFlags.registerCallback(mIFeatureFlagsCallback);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to listen for flag changes!");
+ }
+ }
+
+ return mIFeatureFlags;
+ }
+
+ static boolean parseBoolean(String value) {
+ // Check for a truish string.
+ boolean result = value.equalsIgnoreCase("true")
+ || value.equals("1")
+ || value.equalsIgnoreCase("t")
+ || value.equalsIgnoreCase("on");
+ if (!result) { // Expect a falsish string, else log an error.
+ if (!(value.equalsIgnoreCase("false")
+ || value.equals("0")
+ || value.equalsIgnoreCase("f")
+ || value.equalsIgnoreCase("off"))) {
+ Log.e(TAG,
+ "Tried parsing " + value + " as boolean but it doesn't look like one. "
+ + "Value expected to be one of true|false, 1|0, t|f, on|off.");
+ }
+ }
+ return result;
+ }
+
+ private static boolean flagEqualsSyncableFlag(Flag<?> f, SyncableFlag sf) {
+ return f.getName().equals(sf.getName()) && f.getNamespace().equals(sf.getNamespace());
+ }
+
+
+ /**
+ * A simpler listener that is alerted when a {@link DynamicFlag} changes.
+ *
+ * See {@link #addChangeListener(ChangeListener)}
+ */
+ public interface ChangeListener {
+ /**
+ * Called when a {@link DynamicFlag} changes.
+ *
+ * @param flag The flag that has changed.
+ */
+ void onFlagChanged(DynamicFlag<?> flag);
+ }
+}
diff --git a/core/java/android/flags/FeatureFlagsFake.java b/core/java/android/flags/FeatureFlagsFake.java
new file mode 100644
index 0000000..daedcda
--- /dev/null
+++ b/core/java/android/flags/FeatureFlagsFake.java
@@ -0,0 +1,115 @@
+/*
+ * 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.flags;
+
+import android.annotation.NonNull;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * An implementation of {@link FeatureFlags} for testing.
+ *
+ * Before you read a flag from using this Fake, you must set that flag using
+ * {@link #setFlagValue(BooleanFlagBase, boolean)}. This ensures that your tests are deterministic.
+ *
+ * If you are relying on {@link FeatureFlags#getInstance()} to access FeatureFlags in your code
+ * under test, (instead of dependency injection), you can pass an instance of this fake to
+ * {@link FeatureFlags#setInstance(FeatureFlags)}. Be sure to call that method again, passing null,
+ * to ensure hermetic testing - you don't want static state persisting between your test methods.
+ *
+ * @hide
+ */
+public class FeatureFlagsFake extends FeatureFlags {
+ private final Map<BooleanFlagBase, Boolean> mFlagValues = new HashMap<>();
+ private final Set<BooleanFlagBase> mReadFlags = new HashSet<>();
+
+ public FeatureFlagsFake(IFeatureFlags iFeatureFlags) {
+ super(iFeatureFlags);
+ }
+
+ @Override
+ public boolean isEnabled(@NonNull BooleanFlag flag) {
+ return requireFlag(flag);
+ }
+
+ @Override
+ public boolean isEnabled(@NonNull FusedOffFlag flag) {
+ return requireFlag(flag);
+ }
+
+ @Override
+ public boolean isEnabled(@NonNull FusedOnFlag flag) {
+ return requireFlag(flag);
+ }
+
+ @Override
+ public boolean isCurrentlyEnabled(@NonNull DynamicBooleanFlag flag) {
+ return requireFlag(flag);
+ }
+
+ @Override
+ protected void syncInternal(Set<Flag<?>> dirtyFlags) {
+ }
+
+ /**
+ * Explicitly set a flag's value for reading in tests.
+ *
+ * You _must_ call this for every flag your code-under-test will read. Otherwise, an
+ * {@link IllegalStateException} will be thrown.
+ *
+ * You are able to set values for {@link FusedOffFlag} and {@link FusedOnFlag}, despite those
+ * flags having a fixed value at compile time, since unit tests should still test the state of
+ * those flags as both true and false. I.e. a flag that is off might be turned on in a future
+ * build or vice versa.
+ *
+ * You can not call this method _after_ a non-dynamic flag has been read. Non-dynamic flags
+ * are held stable in the system, so changing a value after reading would not match
+ * real-implementation behavior.
+ *
+ * Calling this method will trigger any {@link android.flags.FeatureFlags.ChangeListener}s that
+ * are registered for the supplied flag if the flag is a {@link DynamicFlag}.
+ *
+ * @param flag The BooleanFlag that you want to set a value for.
+ * @param value The value that the flag should return when accessed.
+ */
+ public void setFlagValue(@NonNull BooleanFlagBase flag, boolean value) {
+ if (!(flag instanceof DynamicBooleanFlag) && mReadFlags.contains(flag)) {
+ throw new RuntimeException(
+ "You can not set the value of a flag after it has been read. Tried to set "
+ + flag + " to " + value + " but it already " + mFlagValues.get(flag));
+ }
+ mFlagValues.put(flag, value);
+ if (flag instanceof DynamicBooleanFlag) {
+ onFlagChange((DynamicFlag<?>) flag);
+ }
+ }
+
+ private boolean requireFlag(BooleanFlagBase flag) {
+ if (!mFlagValues.containsKey(flag)) {
+ throw new IllegalStateException(
+ "Tried to access " + flag + " in test but no overrided specified. You must "
+ + "call #setFlagValue for each flag read in a test.");
+ }
+ mReadFlags.add(flag);
+
+ return mFlagValues.get(flag);
+ }
+
+}
diff --git a/core/java/android/flags/Flag.java b/core/java/android/flags/Flag.java
new file mode 100644
index 0000000..b97a4c8
--- /dev/null
+++ b/core/java/android/flags/Flag.java
@@ -0,0 +1,82 @@
+/*
+ * 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.flags;
+
+import android.annotation.NonNull;
+
+/**
+ * Base class for constants read via {@link android.flags.FeatureFlags}.
+ *
+ * @param <T> The type of value that this flag stores. E.g. Boolean or String.
+ *
+ * @hide
+ */
+public interface Flag<T> {
+ /** The namespace for a flag. Should combine uniquely with its name. */
+ @NonNull
+ String getNamespace();
+
+ /** The name of the flag. Should combine uniquely with its namespace. */
+ @NonNull
+ String getName();
+
+ /** The value of this flag if no override has been set. Null values are not supported. */
+ @NonNull
+ T getDefault();
+
+ /** Returns true if the value of this flag can change at runtime. */
+ default boolean isDynamic() {
+ return false;
+ }
+
+ /**
+ * Add human-readable details to the flag. Flag client's are not required to set this.
+ *
+ * See {@link #getLabel()}, {@link #getDescription()}, and {@link #getCategoryName()}.
+ *
+ * @return Returns `this`, to make a fluent api.
+ */
+ Flag<T> defineMetaData(String label, String description, String categoryName);
+
+ /**
+ * A human-readable name for the flag. Defaults to {@link #getName()}
+ *
+ * See {@link #defineMetaData(String, String, String)}
+ */
+ @NonNull
+ default String getLabel() {
+ return getName();
+ }
+
+ /**
+ * A human-readable description for the flag. Defaults to null if unset.
+ *
+ * See {@link #defineMetaData(String, String, String)}
+ */
+ default String getDescription() {
+ return null;
+ }
+
+ /**
+ * A human-readable category name for the flag. Defaults to null if unset.
+ *
+ * See {@link #defineMetaData(String, String, String)}
+ */
+ default String getCategoryName() {
+ return null;
+ }
+}
diff --git a/core/java/android/flags/FusedOffFlag.java b/core/java/android/flags/FusedOffFlag.java
new file mode 100644
index 0000000..6844b8f
--- /dev/null
+++ b/core/java/android/flags/FusedOffFlag.java
@@ -0,0 +1,49 @@
+/*
+ * 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.flags;
+
+import android.annotation.NonNull;
+import android.provider.DeviceConfig;
+
+/**
+ * A flag representing a false value.
+ *
+ * The flag can never be changed or overridden. It is false at compile time.
+ *
+ * @hide
+ */
+public final class FusedOffFlag extends BooleanFlagBase {
+ /**
+ * @param namespace A namespace for this flag. See {@link DeviceConfig}.
+ * @param name A name for this flag.
+ */
+ FusedOffFlag(String namespace, String name) {
+ super(namespace, name);
+ }
+
+ @Override
+ @NonNull
+ public Boolean getDefault() {
+ return false;
+ }
+
+ @Override
+ public FusedOffFlag defineMetaData(String label, String description, String categoryName) {
+ super.defineMetaData(label, description, categoryName);
+ return this;
+ }
+}
diff --git a/core/java/android/flags/FusedOnFlag.java b/core/java/android/flags/FusedOnFlag.java
new file mode 100644
index 0000000..e9adba7
--- /dev/null
+++ b/core/java/android/flags/FusedOnFlag.java
@@ -0,0 +1,49 @@
+/*
+ * 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.flags;
+
+import android.annotation.NonNull;
+import android.provider.DeviceConfig;
+
+/**
+ * A flag representing a true value.
+ *
+ * The flag can never be changed or overridden. It is true at compile time.
+ *
+ * @hide
+ */
+public final class FusedOnFlag extends BooleanFlagBase {
+ /**
+ * @param namespace A namespace for this flag. See {@link DeviceConfig}.
+ * @param name A name for this flag.
+ */
+ FusedOnFlag(String namespace, String name) {
+ super(namespace, name);
+ }
+
+ @Override
+ @NonNull
+ public Boolean getDefault() {
+ return true;
+ }
+
+ @Override
+ public FusedOnFlag defineMetaData(String label, String description, String categoryName) {
+ super.defineMetaData(label, description, categoryName);
+ return this;
+ }
+}
diff --git a/core/java/android/flags/IFeatureFlags.aidl b/core/java/android/flags/IFeatureFlags.aidl
new file mode 100644
index 0000000..3efcec9
--- /dev/null
+++ b/core/java/android/flags/IFeatureFlags.aidl
@@ -0,0 +1,89 @@
+/*
+ * 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.flags;
+
+import android.flags.IFeatureFlagsCallback;
+import android.flags.SyncableFlag;
+
+/**
+ * Binder interface for communicating with {@link com.android.server.flags.FeatureFlagsService}.
+ *
+ * This interface is used by {@link android.flags.FeatureFlags} and developers should use that to
+ * interface with the service. FeatureFlags is the "client" in this documentation.
+ *
+ * The methods allow client apps to communicate what flags they care about, and receive back
+ * current values for those flags. For stable flags, this is the finalized value until the device
+ * restarts. For {@link DynamicFlag}s, this is the last known value, though it may change in the
+ * future. Clients can listen for changes to flag values so that it can react accordingly.
+ * @hide
+ */
+interface IFeatureFlags {
+ /**
+ * Synchronize with the {@link com.android.server.flags.FeatureFlagsService} about flags of
+ * interest.
+ *
+ * The client should pass in a list of flags that it is using as {@link SyncableFlag}s, which
+ * includes what it thinks the default values of the flags are.
+ *
+ * The response will contain a list of matching SyncableFlags, whose values are set to what the
+ * value of the flags actually are. The client should update its internal state flag data to
+ * match.
+ *
+ * Generally speaking, if a flag that is passed in is new to the FeatureFlagsService, the
+ * service will cache the passed-in value, and return it back out. If, however, a different
+ * client has synced that flag with the service previously, FeatureFlagsService will return the
+ * existing cached value, which may or may not be what the current client passed in. This allows
+ * FeatureFlagsService to keep clients in agreement with one another.
+ */
+ List<SyncableFlag> syncFlags(in List<SyncableFlag> flagList);
+
+ /**
+ * Pass in an {@link IFeatureFlagsCallback} that will be called whenever a {@link DymamicFlag}
+ * changes.
+ */
+ void registerCallback(IFeatureFlagsCallback callback);
+
+ /**
+ * Remove a {@link IFeatureFlagsCallback} that was previously registered with
+ * {@link #registerCallback}.
+ */
+ void unregisterCallback(IFeatureFlagsCallback callback);
+
+ /**
+ * Query the {@link com.android.server.flags.FeatureFlagsService} for flags, but don't
+ * cache them. See {@link #syncFlags}.
+ *
+ * You almost certainly don't want this method. This is intended for the Flag Flipper
+ * application that needs to query the state of system but doesn't want to affect it by
+ * doing so. All other clients should use {@link syncFlags}.
+ */
+ List<SyncableFlag> queryFlags(in List<SyncableFlag> flagList);
+
+ /**
+ * Change a flags value in the system.
+ *
+ * This is intended for use by the Flag Flipper application.
+ */
+ void overrideFlag(in SyncableFlag flag);
+
+ /**
+ * Restore a flag to its default value.
+ *
+ * This is intended for use by the Flag Flipper application.
+ */
+ void resetFlag(in SyncableFlag flag);
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/biometrics/BiometricSensorPrivacy.java b/core/java/android/flags/IFeatureFlagsCallback.aidl
similarity index 62%
copy from services/core/java/com/android/server/biometrics/BiometricSensorPrivacy.java
copy to core/java/android/flags/IFeatureFlagsCallback.aidl
index 6727fbc..f708667 100644
--- a/services/core/java/com/android/server/biometrics/BiometricSensorPrivacy.java
+++ b/core/java/android/flags/IFeatureFlagsCallback.aidl
@@ -14,12 +14,18 @@
* limitations under the License.
*/
-package com.android.server.biometrics;
+ package android.flags;
+
+import android.flags.SyncableFlag;
/**
- * Interface for biometric operations to get camera privacy state.
+ * Callback for {@link IFeatureFlags#registerCallback} to get alerts when a {@link DynamicFlag}
+ * changes.
+ *
+ * DynamicFlags can change at run time. Stable flags will never result in a call to this method.
+ *
+ * @hide
*/
-public interface BiometricSensorPrivacy {
- /* Returns true if privacy is enabled and camera access is disabled. */
- boolean isCameraPrivacyEnabled();
-}
+oneway interface IFeatureFlagsCallback {
+ void onFlagChange(in SyncableFlag flag);
+}
\ No newline at end of file
diff --git a/core/java/android/flags/OWNERS b/core/java/android/flags/OWNERS
new file mode 100644
index 0000000..fa125c4
--- /dev/null
+++ b/core/java/android/flags/OWNERS
@@ -0,0 +1,7 @@
+# Bug component: 1306523
+
+mankoff@google.com
+pixel@google.com
+
+dsandler@android.com
+
diff --git a/services/core/java/com/android/server/biometrics/BiometricSensorPrivacy.java b/core/java/android/flags/SyncableFlag.aidl
similarity index 69%
copy from services/core/java/com/android/server/biometrics/BiometricSensorPrivacy.java
copy to core/java/android/flags/SyncableFlag.aidl
index 6727fbc..1526ec1 100644
--- a/services/core/java/com/android/server/biometrics/BiometricSensorPrivacy.java
+++ b/core/java/android/flags/SyncableFlag.aidl
@@ -14,12 +14,9 @@
* limitations under the License.
*/
-package com.android.server.biometrics;
+package android.flags;
/**
- * Interface for biometric operations to get camera privacy state.
+ * A parcelable data class for serializing {@link Flag} across a Binder.
*/
-public interface BiometricSensorPrivacy {
- /* Returns true if privacy is enabled and camera access is disabled. */
- boolean isCameraPrivacyEnabled();
-}
+parcelable SyncableFlag;
\ No newline at end of file
diff --git a/core/java/android/flags/SyncableFlag.java b/core/java/android/flags/SyncableFlag.java
new file mode 100644
index 0000000..449bcc3c
--- /dev/null
+++ b/core/java/android/flags/SyncableFlag.java
@@ -0,0 +1,112 @@
+/*
+ * 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.flags;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * @hide
+ */
+public final class SyncableFlag implements Parcelable {
+ private final String mNamespace;
+ private final String mName;
+ private final String mValue;
+ private final boolean mDynamic;
+ private final boolean mOverridden;
+
+ public SyncableFlag(
+ @NonNull String namespace,
+ @NonNull String name,
+ @NonNull String value,
+ boolean dynamic) {
+ this(namespace, name, value, dynamic, false);
+ }
+
+ public SyncableFlag(
+ @NonNull String namespace,
+ @NonNull String name,
+ @NonNull String value,
+ boolean dynamic,
+ boolean overridden
+ ) {
+ mNamespace = namespace;
+ mName = name;
+ mValue = value;
+ mDynamic = dynamic;
+ mOverridden = overridden;
+ }
+
+ @NonNull
+ public String getNamespace() {
+ return mNamespace;
+ }
+
+ @NonNull
+ public String getName() {
+ return mName;
+ }
+
+ @NonNull
+ public String getValue() {
+ return mValue;
+ }
+
+ public boolean isDynamic() {
+ return mDynamic;
+ }
+
+ public boolean isOverridden() {
+ return mOverridden;
+ }
+
+ @NonNull
+ public static final Parcelable.Creator<SyncableFlag> CREATOR = new Parcelable.Creator<>() {
+ public SyncableFlag createFromParcel(Parcel in) {
+ return new SyncableFlag(
+ in.readString(),
+ in.readString(),
+ in.readString(),
+ in.readBoolean(),
+ in.readBoolean());
+ }
+
+ public SyncableFlag[] newArray(int size) {
+ return new SyncableFlag[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeString(mNamespace);
+ dest.writeString(mName);
+ dest.writeString(mValue);
+ dest.writeBoolean(mDynamic);
+ dest.writeBoolean(mOverridden);
+ }
+
+ @Override
+ public String toString() {
+ return getNamespace() + "." + getName() + "[" + getValue() + "]";
+ }
+}
diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java
index 2e40f60..912e8df 100644
--- a/core/java/android/hardware/biometrics/BiometricPrompt.java
+++ b/core/java/android/hardware/biometrics/BiometricPrompt.java
@@ -144,6 +144,7 @@
private Context mContext;
private IAuthService mService;
+ // LINT.IfChange
/**
* Creates a builder for a {@link BiometricPrompt} dialog.
* @param context The {@link Context} that will be used to build the prompt.
@@ -417,6 +418,7 @@
* @hide
*/
@NonNull
+ @RequiresPermission(anyOf = {USE_BIOMETRIC_INTERNAL})
public Builder setDisallowBiometricsIfPolicyExists(boolean checkDevicePolicyManager) {
mPromptInfo.setDisallowBiometricsIfPolicyExists(checkDevicePolicyManager);
return this;
@@ -429,6 +431,7 @@
* @hide
*/
@NonNull
+ @RequiresPermission(anyOf = {USE_BIOMETRIC_INTERNAL})
public Builder setReceiveSystemEvents(boolean set) {
mPromptInfo.setReceiveSystemEvents(set);
return this;
@@ -442,6 +445,7 @@
* @hide
*/
@NonNull
+ @RequiresPermission(anyOf = {TEST_BIOMETRIC, USE_BIOMETRIC_INTERNAL})
public Builder setIgnoreEnrollmentState(boolean ignoreEnrollmentState) {
mPromptInfo.setIgnoreEnrollmentState(ignoreEnrollmentState);
return this;
@@ -454,10 +458,12 @@
* @hide
*/
@NonNull
+ @RequiresPermission(anyOf = {TEST_BIOMETRIC, USE_BIOMETRIC_INTERNAL})
public Builder setIsForLegacyFingerprintManager(int sensorId) {
mPromptInfo.setIsForLegacyFingerprintManager(sensorId);
return this;
}
+ // LINT.ThenChange(frameworks/base/core/java/android/hardware/biometrics/PromptInfo.java)
/**
* Creates a {@link BiometricPrompt}.
diff --git a/core/java/android/hardware/biometrics/PromptInfo.java b/core/java/android/hardware/biometrics/PromptInfo.java
index 02aad1d..e275078 100644
--- a/core/java/android/hardware/biometrics/PromptInfo.java
+++ b/core/java/android/hardware/biometrics/PromptInfo.java
@@ -113,6 +113,7 @@
dest.writeBoolean(mIsForLegacyFingerprintManager);
}
+ // LINT.IfChange
public boolean containsTestConfigurations() {
if (mIsForLegacyFingerprintManager
&& mAllowedSensorIds.size() == 1
@@ -122,6 +123,10 @@
return true;
} else if (mAllowBackgroundAuthentication) {
return true;
+ } else if (mIsForLegacyFingerprintManager) {
+ return true;
+ } else if (mIgnoreEnrollmentState) {
+ return true;
}
return false;
}
@@ -144,6 +149,7 @@
}
return false;
}
+ // LINT.ThenChange(frameworks/base/core/java/android/hardware/biometrics/BiometricPrompt.java)
// Setters
diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java
index 99b297a..0e45787 100644
--- a/core/java/android/hardware/camera2/CameraDevice.java
+++ b/core/java/android/hardware/camera2/CameraDevice.java
@@ -283,7 +283,8 @@
* @see StreamConfigurationMap#getInputFormats
* @see StreamConfigurationMap#getInputSizes
* @see StreamConfigurationMap#getValidOutputFormatsForInput
- * @see StreamConfigurationMap#getOutputSizes
+ * @see StreamConfigurationMap#getOutputSizes(int)
+ * @see StreamConfigurationMap#getOutputSizes(Class)
* @see android.media.ImageWriter
* @see android.media.ImageReader
* @deprecated Please use {@link
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index a098362..c2fe080 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -130,14 +130,17 @@
/**
* Enable physical camera availability callbacks when the logical camera is unavailable
*
- * <p>Previously once a logical camera becomes unavailable, no {@link
- * #onPhysicalCameraAvailable} or {@link #onPhysicalCameraUnavailable} will be called until
- * the logical camera becomes available again. The results in the app opening the logical
- * camera not able to receive physical camera availability change.</p>
+ * <p>Previously once a logical camera becomes unavailable, no
+ * {@link AvailabilityCallback#onPhysicalCameraAvailable} or
+ * {@link AvailabilityCallback#onPhysicalCameraUnavailable} will
+ * be called until the logical camera becomes available again. The
+ * results in the app opening the logical camera not able to
+ * receive physical camera availability change.</p>
*
- * <p>With this change, the {@link #onPhysicalCameraAvailable} and {@link
- * #onPhysicalCameraUnavailable} can still be called while the logical camera is unavailable.
- * </p>
+ * <p>With this change, the {@link
+ * AvailabilityCallback#onPhysicalCameraAvailable} and {@link
+ * AvailabilityCallback#onPhysicalCameraUnavailable} can still be
+ * called while the logical camera is unavailable. </p>
*/
@ChangeId
@EnabledSince(targetSdkVersion = android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
diff --git a/core/java/android/hardware/camera2/package.html b/core/java/android/hardware/camera2/package.html
index 719c2f6..3fd5d7c 100644
--- a/core/java/android/hardware/camera2/package.html
+++ b/core/java/android/hardware/camera2/package.html
@@ -62,12 +62,28 @@
done with {@link android.media.ImageReader} with the {@link
android.graphics.ImageFormat#JPEG} and {@link
android.graphics.ImageFormat#RAW_SENSOR} formats. Application-driven
-processing of camera data in RenderScript, OpenGL ES, or directly in
-managed or native code is best done through {@link
-android.renderscript.Allocation} with a YUV {@link
-android.renderscript.Type}, {@link android.graphics.SurfaceTexture},
-and {@link android.media.ImageReader} with a {@link
-android.graphics.ImageFormat#YUV_420_888} format, respectively.</p>
+processing of camera data in OpenGL ES, or directly in managed or
+native code is best done through {@link
+android.graphics.SurfaceTexture}, or {@link android.media.ImageReader}
+with a {@link android.graphics.ImageFormat#YUV_420_888} format,
+respectively. </p>
+
+<p>By default, YUV-format buffers provided by the camera are using the
+JFIF YUV<->RGB transform matrix (equivalent to Rec.601 full-range
+encoding), and after conversion to RGB with this matrix, the resulting
+RGB data is in the sRGB colorspace. Captured JPEG images may contain
+an ICC profile to specify their color space information; if not, they
+should be assumed to be in the sRGB space as well. On some devices,
+the output colorspace can be changed via {@link
+android.hardware.camera2.params.SessionConfiguration#setColorSpace}.
+</p>
+<p>
+Note that although the YUV->RGB transform is the JFIF matrix (Rec.601
+full-range), due to legacy and compatibility reasons, the output is in
+the sRGB colorspace, which uses the Rec.709 color primaries. Image
+processing code can safely treat the output RGB as being in the sRGB
+colorspace.
+</p>
<p>The application then needs to construct a {@link
android.hardware.camera2.CaptureRequest}, which defines all the
diff --git a/core/java/android/hardware/camera2/params/ColorSpaceProfiles.java b/core/java/android/hardware/camera2/params/ColorSpaceProfiles.java
index 2e3af80..bb154a9 100644
--- a/core/java/android/hardware/camera2/params/ColorSpaceProfiles.java
+++ b/core/java/android/hardware/camera2/params/ColorSpaceProfiles.java
@@ -192,7 +192,7 @@
* @see OutputConfiguration#setDynamicRangeProfile
* @see SessionConfiguration#setColorSpace
* @see ColorSpace.Named
- * @see DynamicRangeProfiles.Profile
+ * @see DynamicRangeProfiles
*/
public @NonNull Set<Long> getSupportedDynamicRangeProfiles(@NonNull ColorSpace.Named colorSpace,
@ImageFormat.Format int imageFormat) {
@@ -230,7 +230,7 @@
* @see SessionConfiguration#setColorSpace
* @see OutputConfiguration#setDynamicRangeProfile
* @see ColorSpace.Named
- * @see DynamicRangeProfiles.Profile
+ * @see DynamicRangeProfiles
*/
public @NonNull Set<ColorSpace.Named> getSupportedColorSpacesForDynamicRange(
@ImageFormat.Format int imageFormat,
diff --git a/core/java/android/hardware/camera2/params/RecommendedStreamConfigurationMap.java b/core/java/android/hardware/camera2/params/RecommendedStreamConfigurationMap.java
index 80db38f..d4ce0eb 100644
--- a/core/java/android/hardware/camera2/params/RecommendedStreamConfigurationMap.java
+++ b/core/java/android/hardware/camera2/params/RecommendedStreamConfigurationMap.java
@@ -45,7 +45,7 @@
* Immutable class to store the recommended stream configurations to set up
* {@link android.view.Surface Surfaces} for creating a
* {@link android.hardware.camera2.CameraCaptureSession capture session} with
- * {@link android.hardware.camera2.CameraDevice#createCaptureSession}.
+ * {@link android.hardware.camera2.CameraDevice#createCaptureSession(SessionConfiguration)}.
*
* <p>The recommended list does not replace or deprecate the exhaustive complete list found in
* {@link StreamConfigurationMap}. It is a suggestion about available power and performance
@@ -70,7 +70,7 @@
* }</code></pre>
*
* @see CameraCharacteristics#getRecommendedStreamConfigurationMap
- * @see CameraDevice#createCaptureSession
+ * @see CameraDevice#createCaptureSession(SessionConfiguration)
*/
public final class RecommendedStreamConfigurationMap {
@@ -282,7 +282,7 @@
/**
* Determine whether or not output surfaces with a particular user-defined format can be passed
- * {@link CameraDevice#createCaptureSession createCaptureSession}.
+ * {@link CameraDevice#createCaptureSession(SessionConfiguration) createCaptureSession}.
*
* <p>
* For further information refer to {@link StreamConfigurationMap#isOutputSupportedFor}.
@@ -292,7 +292,7 @@
* @param format an image format from either {@link ImageFormat} or {@link PixelFormat}
* @return
* {@code true} if using a {@code surface} with this {@code format} will be
- * supported with {@link CameraDevice#createCaptureSession}
+ * supported with {@link CameraDevice#createCaptureSession(SessionConfiguration)}
*
* @throws IllegalArgumentException
* if the image format was not a defined named constant
@@ -508,8 +508,10 @@
}
/**
- * Determine whether or not the {@code surface} in its current state is suitable to be included
- * in a {@link CameraDevice#createCaptureSession capture session} as an output.
+ * Determine whether or not the {@code surface} in its current
+ * state is suitable to be included in a {@link
+ * CameraDevice#createCaptureSession(SessionConfiguration) capture
+ * session} as an output.
*
* <p>For more information refer to {@link StreamConfigurationMap#isOutputSupportedFor}.
* </p>
diff --git a/core/java/android/hardware/camera2/params/SessionConfiguration.java b/core/java/android/hardware/camera2/params/SessionConfiguration.java
index 385f107..8f611a8 100644
--- a/core/java/android/hardware/camera2/params/SessionConfiguration.java
+++ b/core/java/android/hardware/camera2/params/SessionConfiguration.java
@@ -55,7 +55,7 @@
* at regular non high speed FPS ranges and optionally {@link InputConfiguration} for
* reprocessable sessions.
*
- * @see CameraDevice#createCaptureSession
+ * @see CameraDevice#createCaptureSession(SessionConfiguration)
* @see CameraDevice#createReprocessableCaptureSession
*/
public static final int SESSION_REGULAR = CameraDevice.SESSION_OPERATION_MODE_NORMAL;
@@ -110,10 +110,7 @@
*
* @see #SESSION_REGULAR
* @see #SESSION_HIGH_SPEED
- * @see CameraDevice#createCaptureSession(List, CameraCaptureSession.StateCallback, Handler)
- * @see CameraDevice#createCaptureSessionByOutputConfigurations
- * @see CameraDevice#createReprocessableCaptureSession
- * @see CameraDevice#createConstrainedHighSpeedCaptureSession
+ * @see CameraDevice#createCaptureSession(SessionConfiguration)
*/
public SessionConfiguration(@SessionMode int sessionType,
@NonNull List<OutputConfiguration> outputs,
diff --git a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
index aabe149..ef0db7f 100644
--- a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
+++ b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
@@ -41,7 +41,7 @@
* {@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP configurations} to set up
* {@link android.view.Surface Surfaces} for creating a
* {@link android.hardware.camera2.CameraCaptureSession capture session} with
- * {@link android.hardware.camera2.CameraDevice#createCaptureSession}.
+ * {@link android.hardware.camera2.CameraDevice#createCaptureSession(SessionConfiguration)}.
* <!-- TODO: link to input stream configuration -->
*
* <p>This is the authoritative list for all <!-- input/ -->output formats (and sizes respectively
@@ -62,7 +62,7 @@
* }</code></pre>
*
* @see CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP
- * @see CameraDevice#createCaptureSession
+ * @see CameraDevice#createCaptureSession(SessionConfiguration)
*/
public final class StreamConfigurationMap {
@@ -456,7 +456,7 @@
/**
* Determine whether or not output surfaces with a particular user-defined format can be passed
- * {@link CameraDevice#createCaptureSession createCaptureSession}.
+ * {@link CameraDevice#createCaptureSession(SessionConfiguration) createCaptureSession}.
*
* <p>This method determines that the output {@code format} is supported by the camera device;
* each output {@code surface} target may or may not itself support that {@code format}.
@@ -468,7 +468,7 @@
* @param format an image format from either {@link ImageFormat} or {@link PixelFormat}
* @return
* {@code true} iff using a {@code surface} with this {@code format} will be
- * supported with {@link CameraDevice#createCaptureSession}
+ * supported with {@link CameraDevice#createCaptureSession(SessionConfiguration)}
*
* @throws IllegalArgumentException
* if the image format was not a defined named constant
@@ -476,7 +476,7 @@
*
* @see ImageFormat
* @see PixelFormat
- * @see CameraDevice#createCaptureSession
+ * @see CameraDevice#createCaptureSession(SessionConfiguration)
*/
public boolean isOutputSupportedFor(int format) {
checkArgumentFormat(format);
@@ -521,7 +521,7 @@
*
* <p>Generally speaking this means that creating a {@link Surface} from that class <i>may</i>
* provide a producer endpoint that is suitable to be used with
- * {@link CameraDevice#createCaptureSession}.</p>
+ * {@link CameraDevice#createCaptureSession(SessionConfiguration)}.</p>
*
* <p>Since not all of the above classes support output of all format and size combinations,
* the particular combination should be queried with {@link #isOutputSupportedFor(Surface)}.</p>
@@ -531,7 +531,7 @@
*
* @throws NullPointerException if {@code klass} was {@code null}
*
- * @see CameraDevice#createCaptureSession
+ * @see CameraDevice#createCaptureSession(SessionConfiguration)
* @see #isOutputSupportedFor(Surface)
*/
public static <T> boolean isOutputSupportedFor(Class<T> klass) {
@@ -555,8 +555,10 @@
}
/**
- * Determine whether or not the {@code surface} in its current state is suitable to be included
- * in a {@link CameraDevice#createCaptureSession capture session} as an output.
+ * Determine whether or not the {@code surface} in its current
+ * state is suitable to be included in a {@link
+ * CameraDevice#createCaptureSession(SessionConfiguration) capture
+ * session} as an output.
*
* <p>Not all surfaces are usable with the {@link CameraDevice}, and not all configurations
* of that {@code surface} are compatible. Some classes that provide the {@code surface} are
@@ -588,7 +590,7 @@
* @throws NullPointerException if {@code surface} was {@code null}
* @throws IllegalArgumentException if the Surface endpoint is no longer valid
*
- * @see CameraDevice#createCaptureSession
+ * @see CameraDevice#createCaptureSession(SessionConfiguration)
* @see #isOutputSupportedFor(Class)
*/
public boolean isOutputSupportedFor(Surface surface) {
@@ -623,14 +625,16 @@
}
/**
- * Determine whether or not the particular stream configuration is suitable to be included
- * in a {@link CameraDevice#createCaptureSession capture session} as an output.
+ * Determine whether or not the particular stream configuration is
+ * suitable to be included in a {@link
+ * CameraDevice#createCaptureSession(SessionConfiguration) capture
+ * session} as an output.
*
* @param size stream configuration size
* @param format stream configuration format
* @return {@code true} if this is supported, {@code false} otherwise
*
- * @see CameraDevice#createCaptureSession
+ * @see CameraDevice#createCaptureSession(SessionConfiguration)
* @see #isOutputSupportedFor(Class)
* @hide
*/
diff --git a/core/java/android/hardware/input/InputManagerGlobal.java b/core/java/android/hardware/input/InputManagerGlobal.java
index c0877d3..e886f68 100644
--- a/core/java/android/hardware/input/InputManagerGlobal.java
+++ b/core/java/android/hardware/input/InputManagerGlobal.java
@@ -54,6 +54,7 @@
import android.view.PointerIcon;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.SomeArgs;
import java.util.ArrayList;
@@ -140,23 +141,27 @@
}
/**
- * Gets an instance of the input manager.
- *
- * @return The input manager instance.
+ * A test session tracker for InputManagerGlobal.
+ * @see #createTestSession(IInputManager)
*/
- public static InputManagerGlobal resetInstance(IInputManager inputManagerService) {
- synchronized (InputManager.class) {
- sInstance = new InputManagerGlobal(inputManagerService);
- return sInstance;
- }
+ @VisibleForTesting
+ public interface TestSession extends AutoCloseable {
+ @Override
+ void close();
}
/**
- * Clear the instance of the input manager.
+ * Create and set a test instance of InputManagerGlobal.
+ *
+ * @return The test session. The session must be {@link TestSession#close()}-ed at the end
+ * of the test.
*/
- public static void clearInstance() {
+ @VisibleForTesting
+ public static TestSession createTestSession(IInputManager inputManagerService) {
synchronized (InputManagerGlobal.class) {
- sInstance = null;
+ final var oldInstance = sInstance;
+ sInstance = new InputManagerGlobal(inputManagerService);
+ return () -> sInstance = oldInstance;
}
}
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index a9c4818..e472a40 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -103,11 +103,10 @@
import android.util.Printer;
import android.util.Xml;
import android.util.proto.ProtoOutputStream;
-import android.view.BatchedInputEventReceiver.SimpleBatchedInputEventReceiver;
-import android.view.Choreographer;
import android.view.Gravity;
import android.view.InputChannel;
import android.view.InputDevice;
+import android.view.InputEvent;
import android.view.InputEventReceiver;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
@@ -1052,17 +1051,22 @@
stylusEvents.forEach(InputMethodService.this::onStylusHandwritingMotionEvent);
// create receiver for channel
- mHandwritingEventReceiver = new SimpleBatchedInputEventReceiver(
- channel,
- Looper.getMainLooper(), Choreographer.getInstance(),
- event -> {
+ mHandwritingEventReceiver = new InputEventReceiver(channel, Looper.getMainLooper()) {
+ @Override
+ public void onInputEvent(InputEvent event) {
+ boolean handled = false;
+ try {
if (!(event instanceof MotionEvent)) {
- return false;
+ return;
}
onStylusHandwritingMotionEvent((MotionEvent) event);
scheduleHandwritingSessionTimeout();
- return true;
- });
+ handled = true;
+ } finally {
+ finishInputEvent(event, handled);
+ }
+ }
+ };
scheduleHandwritingSessionTimeout();
}
diff --git a/core/java/android/nfc/cardemulation/CardEmulation.java b/core/java/android/nfc/cardemulation/CardEmulation.java
index ac3344e..4909b08 100644
--- a/core/java/android/nfc/cardemulation/CardEmulation.java
+++ b/core/java/android/nfc/cardemulation/CardEmulation.java
@@ -594,9 +594,6 @@
if (activity == null || service == null) {
throw new NullPointerException("activity or service or category is null");
}
- if (!activity.isResumed()) {
- throw new IllegalArgumentException("Activity must be resumed.");
- }
try {
return sService.setPreferredService(service);
} catch (RemoteException e) {
@@ -629,9 +626,6 @@
if (activity == null) {
throw new NullPointerException("activity is null");
}
- if (!activity.isResumed()) {
- throw new IllegalArgumentException("Activity must be resumed.");
- }
try {
return sService.unsetPreferredService();
} catch (RemoteException e) {
diff --git a/core/java/android/os/GraphicsEnvironment.java b/core/java/android/os/GraphicsEnvironment.java
index 7664bad..e6bdfe1 100644
--- a/core/java/android/os/GraphicsEnvironment.java
+++ b/core/java/android/os/GraphicsEnvironment.java
@@ -112,17 +112,9 @@
private static final int ANGLE_GL_DRIVER_ALL_ANGLE_OFF = 0;
// Values for ANGLE_GL_DRIVER_SELECTION_VALUES
- private enum AngleDriverChoice {
- DEFAULT("default"),
- ANGLE("angle"),
- NATIVE("native");
-
- public final String choice;
-
- AngleDriverChoice(String choice) {
- this.choice = choice;
- }
- }
+ private static final String ANGLE_GL_DRIVER_CHOICE_DEFAULT = "default";
+ private static final String ANGLE_GL_DRIVER_CHOICE_ANGLE = "angle";
+ private static final String ANGLE_GL_DRIVER_CHOICE_NATIVE = "native";
private static final String PROPERTY_RO_ANGLE_SUPPORTED = "ro.gfx.angle.supported";
@@ -203,16 +195,15 @@
}
/**
- * Query to determine the ANGLE driver choice.
+ * Query to determine if ANGLE should be used
*/
- private AngleDriverChoice queryAngleChoice(Context context, Bundle coreSettings,
- String packageName) {
+ private boolean shouldUseAngle(Context context, Bundle coreSettings, String packageName) {
if (TextUtils.isEmpty(packageName)) {
Log.v(TAG, "No package name specified; use the system driver");
- return AngleDriverChoice.DEFAULT;
+ return false;
}
- return queryAngleChoiceInternal(context, coreSettings, packageName);
+ return shouldUseAngleInternal(context, coreSettings, packageName);
}
private int getVulkanVersion(PackageManager pm) {
@@ -433,11 +424,10 @@
* forces a choice;
* 3) Use ANGLE if isAngleEnabledByGameMode() returns true;
*/
- private AngleDriverChoice queryAngleChoiceInternal(Context context, Bundle bundle,
- String packageName) {
+ private boolean shouldUseAngleInternal(Context context, Bundle bundle, String packageName) {
// Make sure we have a good package name
if (TextUtils.isEmpty(packageName)) {
- return AngleDriverChoice.DEFAULT;
+ return false;
}
// Check the semi-global switch (i.e. once system has booted enough) for whether ANGLE
@@ -452,7 +442,7 @@
}
if (allUseAngle == ANGLE_GL_DRIVER_ALL_ANGLE_ON) {
Log.v(TAG, "Turn on ANGLE for all applications.");
- return AngleDriverChoice.ANGLE;
+ return true;
}
// Get the per-application settings lists
@@ -475,7 +465,7 @@
+ optInPackages.size() + ", "
+ "number of values: "
+ optInValues.size());
- return mEnabledByGameMode ? AngleDriverChoice.ANGLE : AngleDriverChoice.DEFAULT;
+ return mEnabledByGameMode;
}
// See if this application is listed in the per-application settings list
@@ -483,7 +473,7 @@
if (pkgIndex < 0) {
Log.v(TAG, packageName + " is not listed in per-application setting");
- return mEnabledByGameMode ? AngleDriverChoice.ANGLE : AngleDriverChoice.DEFAULT;
+ return mEnabledByGameMode;
}
mAngleOptInIndex = pkgIndex;
@@ -493,14 +483,14 @@
Log.v(TAG,
"ANGLE Developer option for '" + packageName + "' "
+ "set to: '" + optInValue + "'");
- if (optInValue.equals(AngleDriverChoice.ANGLE.choice)) {
- return AngleDriverChoice.ANGLE;
- } else if (optInValue.equals(AngleDriverChoice.NATIVE.choice)) {
- return AngleDriverChoice.NATIVE;
+ if (optInValue.equals(ANGLE_GL_DRIVER_CHOICE_ANGLE)) {
+ return true;
+ } else if (optInValue.equals(ANGLE_GL_DRIVER_CHOICE_NATIVE)) {
+ return false;
} else {
// The user either chose default or an invalid value; go with the default driver or what
// the game mode indicates
- return mEnabledByGameMode ? AngleDriverChoice.ANGLE : AngleDriverChoice.DEFAULT;
+ return mEnabledByGameMode;
}
}
@@ -568,13 +558,7 @@
private boolean setupAngle(Context context, Bundle bundle, PackageManager packageManager,
String packageName) {
- final AngleDriverChoice angleDriverChoice = queryAngleChoice(context, bundle, packageName);
- if (angleDriverChoice == AngleDriverChoice.DEFAULT) {
- return false;
- }
-
- if (queryAngleChoice(context, bundle, packageName) == AngleDriverChoice.NATIVE) {
- nativeSetAngleInfo("", true, packageName, null);
+ if (!shouldUseAngle(context, bundle, packageName)) {
return false;
}
@@ -643,10 +627,10 @@
Log.d(TAG, "ANGLE package libs: " + paths);
}
- // If we make it to here, ANGLE apk will be used. Call nativeSetAngleInfo() with the
- // application package name and ANGLE features to use.
+ // If we make it to here, ANGLE will be used. Call setAngleInfo() with the package name,
+ // and features to use.
final String[] features = getAngleEglFeatures(context, bundle);
- nativeSetAngleInfo(paths, false, packageName, features);
+ setAngleInfo(paths, false, packageName, features);
return true;
}
@@ -668,10 +652,10 @@
return false;
}
- // If we make it to here, system ANGLE will be used. Call nativeSetAngleInfo() with
- // the application package name and ANGLE features to use.
+ // If we make it to here, ANGLE will be used. Call setAngleInfo() with the package name,
+ // and features to use.
final String[] features = getAngleEglFeatures(context, bundle);
- nativeSetAngleInfo("system", false, packageName, features);
+ setAngleInfo("", true, packageName, features);
return true;
}
@@ -952,8 +936,8 @@
private static native void setDriverPathAndSphalLibraries(String path, String sphalLibraries);
private static native void setGpuStats(String driverPackageName, String driverVersionName,
long driverVersionCode, long driverBuildTime, String appPackageName, int vulkanVersion);
- private static native void nativeSetAngleInfo(String path, boolean useNativeDriver,
- String packageName, String[] features);
+ private static native void setAngleInfo(String path, boolean useSystemAngle, String packageName,
+ String[] features);
private static native boolean setInjectLayersPrSetDumpable();
private static native void nativeToggleAngleAsSystemDriver(boolean enabled);
diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java
index aa67693..0461b2e 100644
--- a/core/java/android/os/VibrationEffect.java
+++ b/core/java/android/os/VibrationEffect.java
@@ -547,6 +547,30 @@
}
/**
+ * Resolve default values into integer amplitude numbers.
+ *
+ * @param defaultAmplitude the default amplitude to apply, must be between 0 and
+ * MAX_AMPLITUDE
+ * @return this if amplitude value is already set, or a copy of this effect with given default
+ * amplitude otherwise
+ *
+ * @hide
+ */
+ public abstract <T extends VibrationEffect> T resolve(int defaultAmplitude);
+
+ /**
+ * Scale the vibration effect intensity with the given constraints.
+ *
+ * @param scaleFactor scale factor to be applied to the intensity. Values within [0,1) will
+ * scale down the intensity, values larger than 1 will scale up
+ * @return this if there is no scaling to be done, or a copy of this effect with scaled
+ * vibration intensity otherwise
+ *
+ * @hide
+ */
+ public abstract <T extends VibrationEffect> T scale(float scaleFactor);
+
+ /**
* Ensures that the effect is repeating indefinitely or not. This is a lossy operation and
* should only be applied once to an original effect - it shouldn't be applied to the
* result of this method.
@@ -822,6 +846,40 @@
/** @hide */
@NonNull
@Override
+ public Composed resolve(int defaultAmplitude) {
+ int segmentCount = mSegments.size();
+ ArrayList<VibrationEffectSegment> resolvedSegments = new ArrayList<>(segmentCount);
+ for (int i = 0; i < segmentCount; i++) {
+ resolvedSegments.add(mSegments.get(i).resolve(defaultAmplitude));
+ }
+ if (resolvedSegments.equals(mSegments)) {
+ return this;
+ }
+ Composed resolved = new Composed(resolvedSegments, mRepeatIndex);
+ resolved.validate();
+ return resolved;
+ }
+
+ /** @hide */
+ @NonNull
+ @Override
+ public Composed scale(float scaleFactor) {
+ int segmentCount = mSegments.size();
+ ArrayList<VibrationEffectSegment> scaledSegments = new ArrayList<>(segmentCount);
+ for (int i = 0; i < segmentCount; i++) {
+ scaledSegments.add(mSegments.get(i).scale(scaleFactor));
+ }
+ if (scaledSegments.equals(mSegments)) {
+ return this;
+ }
+ Composed scaled = new Composed(scaledSegments, mRepeatIndex);
+ scaled.validate();
+ return scaled;
+ }
+
+ /** @hide */
+ @NonNull
+ @Override
public Composed applyRepeatingIndefinitely(boolean wantRepeating, int loopDelayMs) {
boolean isRepeating = mRepeatIndex >= 0;
if (isRepeating == wantRepeating) {
diff --git a/core/java/android/os/vibrator/persistence/VibrationXmlParser.java b/core/java/android/os/vibrator/persistence/VibrationXmlParser.java
index d41a428..e91e04e 100644
--- a/core/java/android/os/vibrator/persistence/VibrationXmlParser.java
+++ b/core/java/android/os/vibrator/persistence/VibrationXmlParser.java
@@ -28,7 +28,6 @@
import com.android.internal.vibrator.persistence.XmlConstants;
import com.android.internal.vibrator.persistence.XmlParserException;
import com.android.internal.vibrator.persistence.XmlReader;
-import com.android.internal.vibrator.persistence.XmlSerializedVibration;
import com.android.modules.utils.TypedXmlPullParser;
import org.xmlpull.v1.XmlPullParser;
@@ -178,25 +177,62 @@
// Ensure XML starts with expected root tag.
XmlReader.readDocumentStartTag(parser, XmlConstants.TAG_VIBRATION);
- int parserFlags = 0;
- if ((flags & FLAG_ALLOW_HIDDEN_APIS) != 0) {
- parserFlags |= XmlConstants.FLAG_ALLOW_HIDDEN_APIS;
- }
-
// Parse root tag as a vibration effect.
- XmlSerializedVibration<VibrationEffect> serializedVibration =
- VibrationEffectXmlParser.parseTag(parser, parserFlags);
+ VibrationEffect effect = parseTag(parser, flags);
// Ensure XML ends after root tag is consumed.
XmlReader.readDocumentEndTag(parser);
- return serializedVibration.deserialize();
- } catch (XmlParserException e) {
+ return effect;
+ } catch (XmlParserException | VibrationXmlParserException e) {
Slog.w(TAG, "Error parsing vibration XML", e);
return null;
}
}
+ /**
+ * Parses XML content from given open {@link TypedXmlPullParser} into a {@link VibrationEffect}.
+ *
+ * <p>The provided parser should be pointing to a start of a valid vibration XML (i.e. to a
+ * start <vibration> tag). No other parser position, including start of document, is considered
+ * valid.
+ *
+ * <p>This method parses as long as it reads a valid vibration XML, and until an end vibration
+ * tag. After a successful parsing, the parser will point to the end vibration tag (i.e. to a
+ * </vibration> tag).
+ *
+ * @throws IOException error parsing from given {@link TypedXmlPullParser}.
+ * @throws VibrationXmlParserException if the XML tag cannot be parsed into a
+ * {@link VibrationEffect}. The given {@code parser} might be pointing to a child XML tag
+ * that caused the parser failure.
+ *
+ * @hide
+ */
+ @NonNull
+ public static VibrationEffect parseTag(@NonNull TypedXmlPullParser parser, @Flags int flags)
+ throws IOException, VibrationXmlParserException {
+ int parserFlags = 0;
+ if ((flags & VibrationXmlParser.FLAG_ALLOW_HIDDEN_APIS) != 0) {
+ parserFlags |= XmlConstants.FLAG_ALLOW_HIDDEN_APIS;
+ }
+ try {
+ return VibrationEffectXmlParser.parseTag(parser, parserFlags).deserialize();
+ } catch (XmlParserException e) {
+ throw new VibrationXmlParserException("Error parsing vibration effect.", e);
+ }
+ }
+
+ /**
+ * Represents an error while parsing a vibration XML input.
+ *
+ * @hide
+ */
+ public static final class VibrationXmlParserException extends Exception {
+ private VibrationXmlParserException(String message, Throwable cause) {
+ super(message, cause);
+ }
+ }
+
private VibrationXmlParser() {
}
}
diff --git a/core/java/android/permission/IPermissionManager.aidl b/core/java/android/permission/IPermissionManager.aidl
index d19fd8f..18ede44d 100644
--- a/core/java/android/permission/IPermissionManager.aidl
+++ b/core/java/android/permission/IPermissionManager.aidl
@@ -42,10 +42,10 @@
void removePermission(String permissionName);
- int getPermissionFlags(String packageName, String permissionName, int userId);
+ int getPermissionFlags(String packageName, String permissionName, int deviceId, int userId);
void updatePermissionFlags(String packageName, String permissionName, int flagMask,
- int flagValues, boolean checkAdjustPolicyFlagPermission, int userId);
+ int flagValues, boolean checkAdjustPolicyFlagPermission, int deviceId, int userId);
void updatePermissionFlagsForAllApps(int flagMask, int flagValues, int userId);
@@ -62,17 +62,18 @@
boolean removeAllowlistedRestrictedPermission(String packageName, String permissionName,
int flags, int userId);
- void grantRuntimePermission(String packageName, String permissionName, int userId);
+ void grantRuntimePermission(String packageName, String permissionName, int deviceId, int userId);
- void revokeRuntimePermission(String packageName, String permissionName, int userId,
- String reason);
+ void revokeRuntimePermission(String packageName, String permissionName, int deviceId,
+ int userId, String reason);
void revokePostNotificationPermissionWithoutKillForTest(String packageName, int userId);
boolean shouldShowRequestPermissionRationale(String packageName, String permissionName,
- int userId);
+ int deviceId, int userId);
- boolean isPermissionRevokedByPolicy(String packageName, String permissionName, int userId);
+ boolean isPermissionRevokedByPolicy(String packageName, String permissionName, int deviceId,
+ int userId);
List<SplitPermissionInfoParcelable> getSplitPermissions();
@@ -94,4 +95,8 @@
void registerAttributionSource(in AttributionSourceState source);
boolean isRegisteredAttributionSource(in AttributionSourceState source);
+
+ int checkPermission(String packageName, String permissionName, int deviceId, int userId);
+
+ int checkUidPermission(int uid, String permissionName, int deviceId);
}
diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java
index 9ae87de..7d39210 100644
--- a/core/java/android/permission/PermissionManager.java
+++ b/core/java/android/permission/PermissionManager.java
@@ -66,6 +66,7 @@
import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.UserHandle;
+import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.DebugUtils;
@@ -571,7 +572,7 @@
@NonNull String permissionName) {
try {
return mPermissionManager.isPermissionRevokedByPolicy(packageName, permissionName,
- mContext.getUserId());
+ mContext.getDeviceId(), mContext.getUserId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -615,7 +616,7 @@
}
try {
mPermissionManager.grantRuntimePermission(packageName, permissionName,
- user.getIdentifier());
+ mContext.getDeviceId(), user.getIdentifier());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -652,8 +653,8 @@
+ reason, new RuntimeException());
}
try {
- mPermissionManager
- .revokeRuntimePermission(packageName, permName, user.getIdentifier(), reason);
+ mPermissionManager.revokeRuntimePermission(packageName, permName,
+ mContext.getDeviceId(), user.getIdentifier(), reason);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -680,7 +681,7 @@
@NonNull UserHandle user) {
try {
return mPermissionManager.getPermissionFlags(packageName, permissionName,
- user.getIdentifier());
+ mContext.getDeviceId(), user.getIdentifier());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -719,7 +720,8 @@
final boolean checkAdjustPolicyFlagPermission =
mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.Q;
mPermissionManager.updatePermissionFlags(packageName, permissionName, flagMask,
- flagValues, checkAdjustPolicyFlagPermission, user.getIdentifier());
+ flagValues, checkAdjustPolicyFlagPermission,
+ mContext.getDeviceId(), user.getIdentifier());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -975,7 +977,7 @@
try {
final String packageName = mContext.getPackageName();
return mPermissionManager.shouldShowRequestPermissionRationale(packageName,
- permissionName, mContext.getUserId());
+ permissionName, mContext.getDeviceId(), mContext.getUserId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1509,8 +1511,8 @@
// to reduce duplicate logcat output.
private static volatile boolean sShouldWarnMissingActivityManager = true;
- /* @hide */
- private static int checkPermissionUncached(@Nullable String permission, int pid, int uid) {
+ private static int checkPermissionUncached(@Nullable String permission, int pid, int uid,
+ int deviceId) {
final IActivityManager am = ActivityManager.getService();
if (am == null) {
// Well this is super awkward; we somehow don't have an active ActivityManager
@@ -1531,7 +1533,7 @@
}
try {
sShouldWarnMissingActivityManager = true;
- return am.checkPermission(permission, pid, uid);
+ return am.checkPermissionForDevice(permission, pid, uid, deviceId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1551,26 +1553,26 @@
final String permission;
final int pid;
final int uid;
+ final int deviceId;
- PermissionQuery(@Nullable String permission, int pid, int uid) {
+ PermissionQuery(@Nullable String permission, int pid, int uid, int deviceId) {
this.permission = permission;
this.pid = pid;
this.uid = uid;
+ this.deviceId = deviceId;
}
@Override
public String toString() {
- return String.format("PermissionQuery(permission=\"%s\", pid=%s, uid=%s)",
- permission, pid, uid);
+ return TextUtils.formatSimple("PermissionQuery(permission=\"%s\", pid=%d, uid=%d, "
+ + "deviceId=%d)", permission, pid, uid, deviceId);
}
@Override
public int hashCode() {
// N.B. pid doesn't count toward equality and therefore shouldn't count for
// hashing either.
- int hash = Objects.hashCode(permission);
- hash = hash * 13 + Objects.hashCode(uid);
- return hash;
+ return Objects.hash(permission, uid, deviceId);
}
@Override
@@ -1585,7 +1587,7 @@
} catch (ClassCastException ex) {
return false;
}
- return uid == other.uid
+ return uid == other.uid && deviceId == other.deviceId
&& Objects.equals(permission, other.permission);
}
}
@@ -1599,13 +1601,14 @@
2048, CACHE_KEY_PACKAGE_INFO, "checkPermission") {
@Override
public Integer recompute(PermissionQuery query) {
- return checkPermissionUncached(query.permission, query.pid, query.uid);
+ return checkPermissionUncached(query.permission, query.pid, query.uid,
+ query.deviceId);
}
};
/** @hide */
- public static int checkPermission(@Nullable String permission, int pid, int uid) {
- return sPermissionCache.query(new PermissionQuery(permission, pid, uid));
+ public static int checkPermission(@Nullable String permission, int pid, int uid, int deviceId) {
+ return sPermissionCache.query(new PermissionQuery(permission, pid, uid, deviceId));
}
/**
@@ -1625,26 +1628,29 @@
private static final class PackageNamePermissionQuery {
final String permName;
final String pkgName;
+ final int deviceId;
@UserIdInt
final int userId;
PackageNamePermissionQuery(@Nullable String permName, @Nullable String pkgName,
- @UserIdInt int userId) {
+ int deviceId, @UserIdInt int userId) {
this.permName = permName;
this.pkgName = pkgName;
+ this.deviceId = deviceId;
this.userId = userId;
}
@Override
public String toString() {
- return String.format(
- "PackageNamePermissionQuery(pkgName=\"%s\", permName=\"%s, userId=%s\")",
- pkgName, permName, userId);
+ return TextUtils.formatSimple(
+ "PackageNamePermissionQuery(pkgName=\"%s\", permName=\"%s\", "
+ + "deviceId=%s, userId=%s\")",
+ pkgName, permName, deviceId, userId);
}
@Override
public int hashCode() {
- return Objects.hash(permName, pkgName, userId);
+ return Objects.hash(permName, pkgName, deviceId, userId);
}
@Override
@@ -1660,16 +1666,17 @@
}
return Objects.equals(permName, other.permName)
&& Objects.equals(pkgName, other.pkgName)
+ && deviceId == other.deviceId
&& userId == other.userId;
}
}
/* @hide */
private static int checkPackageNamePermissionUncached(
- String permName, String pkgName, @UserIdInt int userId) {
+ String permName, String pkgName, int deviceId, @UserIdInt int userId) {
try {
- return ActivityThread.getPackageManager().checkPermission(
- permName, pkgName, userId);
+ return ActivityThread.getPermissionManager().checkPermission(
+ pkgName, permName, deviceId, userId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1683,7 +1690,7 @@
@Override
public Integer recompute(PackageNamePermissionQuery query) {
return checkPackageNamePermissionUncached(
- query.permName, query.pkgName, query.userId);
+ query.permName, query.pkgName, query.deviceId, query.userId);
}
@Override
public boolean bypass(PackageNamePermissionQuery query) {
@@ -1692,14 +1699,14 @@
};
/**
- * Check whether a package has a permission.
+ * Check whether a package has a permission for given device.
*
* @hide
*/
- public static int checkPackageNamePermission(String permName, String pkgName,
+ public static int checkPackageNamePermission(String permName, String pkgName, int deviceId,
@UserIdInt int userId) {
return sPackageNamePermissionCache.query(
- new PackageNamePermissionQuery(permName, pkgName, userId));
+ new PackageNamePermissionQuery(permName, pkgName, deviceId, userId));
}
/**
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index d69c484..cb46674 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -2199,6 +2199,16 @@
= "android.settings.APP_NOTIFICATION_BUBBLE_SETTINGS";
/**
+ * Intent Extra: The value of {@link android.app.settings.SettingsEnums#EntryPointType} for
+ * settings metrics that logs the entry point about physical keyboard settings.
+ * <p>
+ * This must be passed as an extra field to the {@link #ACTION_HARD_KEYBOARD_SETTINGS}.
+ * @hide
+ */
+ public static final String EXTRA_ENTRYPOINT =
+ "com.android.settings.inputmethod.EXTRA_ENTRYPOINT";
+
+ /**
* Activity Extra: The package owner of the notification channel settings to display.
* <p>
* This must be passed as an extra field to the {@link #ACTION_CHANNEL_NOTIFICATION_SETTINGS}.
@@ -4837,6 +4847,15 @@
public static final String PEAK_REFRESH_RATE = "peak_refresh_rate";
/**
+ * Control whether to stay awake on fold
+ *
+ * If this isn't set, the system falls back to a device specific default.
+ * @hide
+ */
+ @Readable
+ public static final String STAY_AWAKE_ON_FOLD = "stay_awake_on_fold";
+
+ /**
* The amount of time in milliseconds before the device goes to sleep or begins
* to dream after a period of inactivity. This value is also known as the
* user activity timeout period since the screen isn't necessarily turned off
diff --git a/core/java/android/provider/TEST_MAPPING b/core/java/android/provider/TEST_MAPPING
index c55572e..8b4a99e 100644
--- a/core/java/android/provider/TEST_MAPPING
+++ b/core/java/android/provider/TEST_MAPPING
@@ -21,7 +21,7 @@
"name": "SettingsProviderTest"
},
{
- "name": "CtsAppSecurityHostTestCases",
+ "name": "CtsPackageManagerHostTestCases",
"options": [
{
"include-filter": "android.appsecurity.cts.ReadableSettingsFieldsTest"
diff --git a/core/java/android/security/OWNERS b/core/java/android/security/OWNERS
index 7140ff1..f6b1235 100644
--- a/core/java/android/security/OWNERS
+++ b/core/java/android/security/OWNERS
@@ -1,7 +1,6 @@
# Bug component: 36824
cbrubaker@google.com
-vishwath@google.com
per-file NetworkSecurityPolicy.java = cbrubaker@google.com
per-file NetworkSecurityPolicy.java = klyubin@google.com
diff --git a/core/java/android/service/cloudsearch/OWNERS b/core/java/android/service/cloudsearch/OWNERS
index aa4da3b..3a5841e 100644
--- a/core/java/android/service/cloudsearch/OWNERS
+++ b/core/java/android/service/cloudsearch/OWNERS
@@ -1,4 +1,3 @@
# Bug component: 758286
-huiwu@google.com
srazdan@google.com
diff --git a/core/java/android/service/contentcapture/ContentCaptureService.java b/core/java/android/service/contentcapture/ContentCaptureService.java
index 7fa0ac8..06e86af 100644
--- a/core/java/android/service/contentcapture/ContentCaptureService.java
+++ b/core/java/android/service/contentcapture/ContentCaptureService.java
@@ -15,6 +15,8 @@
*/
package android.service.contentcapture;
+import static android.view.contentcapture.ContentCaptureEvent.TYPE_SESSION_PAUSED;
+import static android.view.contentcapture.ContentCaptureEvent.TYPE_SESSION_RESUMED;
import static android.view.contentcapture.ContentCaptureHelper.sDebug;
import static android.view.contentcapture.ContentCaptureHelper.sVerbose;
import static android.view.contentcapture.ContentCaptureHelper.toList;
@@ -569,7 +571,16 @@
List<ContentCaptureEvent> events = parceledEvents.getList();
int sessionIdInt = events.isEmpty() ? NO_SESSION_ID : events.get(0).getSessionId();
ContentCaptureSessionId sessionId = new ContentCaptureSessionId(sessionIdInt);
+
+ ContentCaptureEvent startEvent =
+ new ContentCaptureEvent(sessionIdInt, TYPE_SESSION_RESUMED);
+ startEvent.setSelectionIndex(0, events.size());
+ onContentCaptureEvent(sessionId, startEvent);
+
events.forEach(event -> onContentCaptureEvent(sessionId, event));
+
+ ContentCaptureEvent endEvent = new ContentCaptureEvent(sessionIdInt, TYPE_SESSION_PAUSED);
+ onContentCaptureEvent(sessionId, endEvent);
}
private void handleOnActivitySnapshot(int sessionId, @NonNull SnapshotData snapshotData) {
diff --git a/core/java/android/service/trust/OWNERS b/core/java/android/service/trust/OWNERS
index 198925b..a895f7f 100644
--- a/core/java/android/service/trust/OWNERS
+++ b/core/java/android/service/trust/OWNERS
@@ -1,5 +1,4 @@
# Bug component: 36824
cbrubaker@google.com
-vishwath@google.com
-jacobhobbie@google.com
\ No newline at end of file
+jacobhobbie@google.com
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index cc3a662..e47fab4 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -868,6 +868,11 @@
* This will trigger a {@link #onComputeColors()} call.
*/
public void notifyColorsChanged() {
+ if (mDestroyed) {
+ Log.i(TAG, "Ignoring notifyColorsChanged(), Engine has already been destroyed.");
+ return;
+ }
+
final long now = mClockFunction.get();
if (now - mLastColorInvalidation < NOTIFY_COLORS_RATE_LIMIT_MS) {
Log.w(TAG, "This call has been deferred. You should only call "
@@ -2231,7 +2236,11 @@
}
}
- void detach() {
+ /**
+ * @hide
+ */
+ @VisibleForTesting
+ public void detach() {
if (mDestroyed) {
return;
}
@@ -2449,6 +2458,14 @@
}
public void reportShown() {
+ if (mEngine == null) {
+ Log.i(TAG, "Can't report null engine as shown.");
+ return;
+ }
+ if (mEngine.mDestroyed) {
+ Log.i(TAG, "Engine was destroyed before we could draw.");
+ return;
+ }
if (!mShownReported) {
mShownReported = true;
Trace.beginSection("WPMS.mConnection.engineShown");
diff --git a/core/java/android/service/wallpapereffectsgeneration/OWNERS b/core/java/android/service/wallpapereffectsgeneration/OWNERS
index d2d3e2c0..a351032 100644
--- a/core/java/android/service/wallpapereffectsgeneration/OWNERS
+++ b/core/java/android/service/wallpapereffectsgeneration/OWNERS
@@ -1,4 +1,3 @@
susharon@google.com
shanh@google.com
-huiwu@google.com
srazdan@google.com
diff --git a/core/java/android/transparency/OWNERS b/core/java/android/transparency/OWNERS
index ed0e5e5..b2621b5 100644
--- a/core/java/android/transparency/OWNERS
+++ b/core/java/android/transparency/OWNERS
@@ -1,5 +1,4 @@
# Bug component: 1164579
billylau@google.com
mpgroover@google.com
-vishwath@google.com
wenhaowang@google.com
diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java
index d80001d..654fa1d 100644
--- a/core/java/android/view/Choreographer.java
+++ b/core/java/android/view/Choreographer.java
@@ -749,6 +749,25 @@
return getExpectedPresentationTimeNanos() / TimeUtils.NANOS_PER_MS;
}
+ /**
+ * Same as {@link #getExpectedPresentationTimeNanos()},
+ * Should always use {@link #getExpectedPresentationTimeNanos()} if it's possilbe.
+ * This method involves a binder call to SF,
+ * calling this method can potentially influence the performance.
+ *
+ * @return The frame start time, in the {@link System#nanoTime()} time base.
+ *
+ * @hide
+ */
+ public long getLatestExpectedPresentTimeNanos() {
+ if (mDisplayEventReceiver == null) {
+ return System.nanoTime();
+ }
+
+ return mDisplayEventReceiver.getLatestVsyncEventData()
+ .preferredFrameTimeline().expectedPresentationTime;
+ }
+
private void scheduleFrameLocked(long now) {
if (!mFrameScheduled) {
mFrameScheduled = true;
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 072a7f5..bbd8255 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -1044,4 +1044,13 @@
* @return List of ComponentNames corresponding to the activities that were notified.
*/
List<ComponentName> notifyScreenshotListeners(int displayId);
+
+ /**
+ * Replace the content of the displayId with the SurfaceControl passed in. This can be used for
+ * tests when creating a VirtualDisplay, but only want to capture specific content and not
+ * mirror the entire display.
+ */
+ @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
+ + ".permission.ACCESS_SURFACE_FLINGER)")
+ boolean replaceContentOnDisplay(int displayId, in SurfaceControl sc);
}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index bf8c0d2..6d9f156 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -14830,7 +14830,8 @@
* view satisfies any of the following:
* <ul>
* <li>Is actionable, e.g. {@link #isClickable()},
- * {@link #isLongClickable()}, or {@link #isFocusable()}
+ * {@link #isLongClickable()}, {@link #isContextClickable()},
+ * {@link #isScreenReaderFocusable()}, or {@link #isFocusable()}
* <li>Has an {@link AccessibilityDelegate}
* <li>Has an interaction listener, e.g. {@link OnTouchListener},
* {@link OnKeyListener}, etc.
@@ -14839,6 +14840,7 @@
* {@link #ACCESSIBILITY_LIVE_REGION_NONE}.
* </ul>
* <li>Has an accessibility pane title, see {@link #setAccessibilityPaneTitle}</li>
+ * <li>Is an accessibility heading, see {@link #setAccessibilityHeading(boolean)}.</li>
* </ol>
*
* @return Whether the view is exposed for accessibility.
@@ -14865,7 +14867,7 @@
return mode == IMPORTANT_FOR_ACCESSIBILITY_YES || isActionableForAccessibility()
|| hasListenersForAccessibility() || mAccessibilityDelegate != null
|| getAccessibilityLiveRegion() != ACCESSIBILITY_LIVE_REGION_NONE
- || isAccessibilityPane();
+ || isAccessibilityPane() || isAccessibilityHeading();
}
/**
@@ -15025,7 +15027,8 @@
* @hide
*/
public boolean isActionableForAccessibility() {
- return (isClickable() || isLongClickable() || isFocusable());
+ return (isClickable() || isLongClickable() || isFocusable() || isContextClickable()
+ || isScreenReaderFocusable());
}
/**
@@ -21905,6 +21908,8 @@
mCurrentAnimation = null;
if ((mViewFlags & TOOLTIP) == TOOLTIP) {
+ removeCallbacks(mTooltipInfo.mShowTooltipRunnable);
+ removeCallbacks(mTooltipInfo.mHideTooltipRunnable);
hideTooltip();
}
@@ -32104,9 +32109,7 @@
return false;
}
- getLocationInWindow(mAttachInfo.mTmpLocation);
- return mAttachInfo.mTmpLocation[0] == insets.getStableInsetLeft()
- && mAttachInfo.mTmpLocation[1] == insets.getStableInsetTop();
+ return true;
}
/**
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index d702367..f1537ca 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -880,22 +880,23 @@
int LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP = 600;
/**
- * Application level {@link android.content.pm.PackageManager.Property PackageManager
- * .Property} for an app to inform the system that the app can be opted-in or opted-out
- * from the compatibility treatment that avoids {@link
- * android.app.Activity#setRequestedOrientation} loops. The loop can be trigerred by
- * ignoreRequestedOrientation display setting enabled on the device or by the landscape natural
- * orientation of the device.
+ * Application level {@link android.content.pm.PackageManager.Property PackageManager.Property}
+ * for an app to inform the system that the app can be opted-in or opted-out from the
+ * compatibility treatment that avoids {@link android.app.Activity#setRequestedOrientation
+ * Activity#setRequestedOrientation()} loops. Loops can be triggered by the OEM-configured
+ * ignore requested orientation display setting (on Android 12 (API level 31) and higher) or by
+ * the landscape natural orientation of the device.
*
* <p>The treatment is disabled by default but device manufacturers can enable the treatment
* using their discretion to improve display compatibility.
*
- * <p>With this property set to {@code true}, the system could ignore {@link
- * android.app.Activity#setRequestedOrientation} call from an app if one of the following
- * conditions are true:
+ * <p>With this property set to {@code true}, the system could ignore
+ * {@link android.app.Activity#setRequestedOrientation Activity#setRequestedOrientation()} call
+ * from an app if one of the following conditions are true:
* <ul>
- * <li>Activity is relaunching due to the previous {@link
- * android.app.Activity#setRequestedOrientation} call.
+ * <li>Activity is relaunching due to the previous
+ * {@link android.app.Activity#setRequestedOrientation Activity#setRequestedOrientation()}
+ * call.
* <li>Camera compatibility force rotation treatment is active for the package.
* </ul>
*
@@ -919,14 +920,16 @@
/**
* Application level {@link android.content.pm.PackageManager.Property PackageManager.Property}
* for an app to inform the system that the app can be opted-out from the compatibility
- * treatment that avoids {@link android.app.Activity#setRequestedOrientation} loops. The loop
- * can be trigerred by ignoreRequestedOrientation display setting enabled on the device or
- * by the landscape natural orientation of the device.
+ * treatment that avoids {@link android.app.Activity#setRequestedOrientation
+ * Activity#setRequestedOrientation()} loops. Loops can be triggered by the OEM-configured
+ * ignore requested orientation display setting (on Android 12 (API level 31) and higher) or by
+ * the landscape natural orientation of the device.
*
- * <p>The system could ignore {@link android.app.Activity#setRequestedOrientation}
- * call from an app if both of the following conditions are true:
+ * <p>The system could ignore {@link android.app.Activity#setRequestedOrientation
+ * Activity#setRequestedOrientation()} call from an app if both of the following conditions are
+ * true:
* <ul>
- * <li>Activity has requested orientation more than 2 times within 1-second timer
+ * <li>Activity has requested orientation more than two times within one-second timer
* <li>Activity is not letterboxed for fixed orientation
* </ul>
*
@@ -953,23 +956,21 @@
"android.window.PROPERTY_COMPAT_ALLOW_IGNORING_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED";
/**
- * Application level {@link android.content.pm.PackageManager.Property PackageManager
- * .Property} for an app to inform the system that it needs to be opted-out from the
- * compatibility treatment that sandboxes {@link android.view.View} API.
+ * Application level {@link android.content.pm.PackageManager.Property PackageManager.Property}
+ * for an app to inform the system that it needs to be opted-out from the compatibility
+ * treatment that sandboxes the {@link android.view.View View} API.
*
* <p>The treatment can be enabled by device manufacturers for applications which misuse
- * {@link android.view.View} APIs by expecting that
- * {@link android.view.View#getLocationOnScreen},
- * {@link android.view.View#getBoundsOnScreen},
- * {@link android.view.View#getWindowVisibleDisplayFrame},
- * {@link android.view.View#getWindowDisplayFrame}
+ * {@link android.view.View View} APIs by expecting that
+ * {@link android.view.View#getLocationOnScreen View#getLocationOnScreen()} and
+ * {@link android.view.View#getWindowVisibleDisplayFrame View#getWindowVisibleDisplayFrame()}
* return coordinates as if an activity is positioned in the top-left corner of the screen, with
- * left coordinate equal to 0. This may not be the case for applications in multi-window and in
+ * left coordinate equal to 0. This may not be the case for applications in multi-window and
* letterbox modes.
*
* <p>Setting this property to {@code false} informs the system that the application must be
- * opted-out from the "Sandbox {@link android.view.View} API to Activity bounds" treatment even
- * if the device manufacturer has opted the app into the treatment.
+ * opted-out from the "Sandbox View API to Activity bounds" treatment even if the device
+ * manufacturer has opted the app into the treatment.
*
* <p>Not setting this property at all, or setting this property to {@code true} has no effect.
*
@@ -987,12 +988,11 @@
"android.window.PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS";
/**
- * Application level {@link android.content.pm.PackageManager.Property PackageManager
- * .Property} for an app to inform the system that the application can be opted-in or opted-out
- * from the compatibility treatment that enables sending a fake focus event for unfocused
- * resumed split screen activities. This is needed because some game engines wait to get
- * focus before drawing the content of the app which isn't guaranteed by default in multi-window
- * modes.
+ * Application level {@link android.content.pm.PackageManager.Property PackageManager.Property}
+ * for an app to inform the system that the application can be opted-in or opted-out from the
+ * compatibility treatment that enables sending a fake focus event for unfocused resumed
+ * split-screen activities. This is needed because some game engines wait to get focus before
+ * drawing the content of the app which isn't guaranteed by default in multi-window mode.
*
* <p>Device manufacturers can enable this treatment using their discretion on a per-device
* basis to improve display compatibility. The treatment also needs to be specifically enabled
@@ -1022,9 +1022,9 @@
String PROPERTY_COMPAT_ENABLE_FAKE_FOCUS = "android.window.PROPERTY_COMPAT_ENABLE_FAKE_FOCUS";
/**
- * Application level {@link android.content.pm.PackageManager.Property PackageManager
- * .Property} for an app to inform the system that the app should be excluded from the
- * camera compatibility force rotation treatment.
+ * Application level {@link android.content.pm.PackageManager.Property PackageManager.Property}
+ * for an app to inform the system that the app should be excluded from the camera compatibility
+ * force rotation treatment.
*
* <p>The camera compatibility treatment aligns orientations of portrait app window and natural
* orientation of the device and set opposite to natural orientation for a landscape app
@@ -1034,10 +1034,11 @@
* rotation can cause letterboxing. The forced rotation is triggered as soon as app opens to
* camera and is removed once camera is closed.
*
- * <p>The camera compatibility can be enabled by device manufacturers on the displays that have
- * ignoreOrientationRequest display setting enabled (enables compatibility mode for fixed
- * orientation, see <a href="https://developer.android.com/guide/practices/enhanced-letterboxing">Enhanced letterboxing</a>
- * for more details).
+ * <p>The camera compatibility can be enabled by device manufacturers on displays that have the
+ * ignore requested orientation display setting enabled (enables compatibility mode for fixed
+ * orientation on Android 12 (API level 31) or higher; see
+ * <a href="https://developer.android.com/guide/practices/enhanced-letterboxing">Enhanced
+ * letterboxing</a> for more details).
*
* <p>With this property set to {@code true} or unset, the system may apply the force rotation
* treatment to fixed orientation activities. Device manufacturers can exclude packages from the
@@ -1060,9 +1061,9 @@
"android.window.PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION";
/**
- * Application level {@link android.content.pm.PackageManager.Property PackageManager
- * .Property} for an app to inform the system that the app should be excluded
- * from the activity "refresh" after the camera compatibility force rotation treatment.
+ * Application level {@link android.content.pm.PackageManager.Property PackageManager.Property}
+ * for an app to inform the system that the app should be excluded from the activity "refresh"
+ * after the camera compatibility force rotation treatment.
*
* <p>The camera compatibility treatment aligns orientations of portrait app window and natural
* orientation of the device and set opposite to natural orientation for a landscape app
@@ -1079,10 +1080,11 @@
* camera preview and can lead to sideways or stretching issues persisting even after force
* rotation.
*
- * <p>The camera compatibility can be enabled by device manufacturers on the displays that have
- * ignoreOrientationRequest display setting enabled (enables compatibility mode for fixed
- * orientation, see <a href="https://developer.android.com/guide/practices/enhanced-letterboxing">Enhanced letterboxing</a>
- * for more details).
+ * <p>The camera compatibility can be enabled by device manufacturers on displays that have the
+ * ignore requested orientation display setting enabled (enables compatibility mode for fixed
+ * orientation on Android 12 (API level 31) or higher; see
+ * <a href="https://developer.android.com/guide/practices/enhanced-letterboxing">Enhanced
+ * letterboxing</a> for more details).
*
* <p>With this property set to {@code true} or unset, the system may "refresh" activity after
* the force rotation treatment. Device manufacturers can exclude packages from the "refresh"
@@ -1105,10 +1107,10 @@
"android.window.PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH";
/**
- * Application level {@link android.content.pm.PackageManager.Property PackageManager
- * .Property} for an app to inform the system that the activity should be or shouldn't be
- * "refreshed" after the camera compatibility force rotation treatment using "paused ->
- * resumed" cycle rather than "stopped -> resumed".
+ * Application level {@link android.content.pm.PackageManager.Property PackageManager.Property}
+ * for an app to inform the system that the activity should be or shouldn't be "refreshed" after
+ * the camera compatibility force rotation treatment using "paused -> resumed" cycle rather than
+ * "stopped -> resumed".
*
* <p>The camera compatibility treatment aligns orientations of portrait app window and natural
* orientation of the device and set opposite to natural orientation for a landscape app
@@ -1124,10 +1126,11 @@
* values in apps (e.g., display or camera rotation) that influence camera preview and can lead
* to sideways or stretching issues persisting even after force rotation.
*
- * <p>The camera compatibility can be enabled by device manufacturers on the displays that have
- * ignoreOrientationRequest display setting enabled (enables compatibility mode for fixed
- * orientation, see <a href="https://developer.android.com/guide/practices/enhanced-letterboxing">Enhanced letterboxing</a>
- * for more details).
+ * <p>The camera compatibility can be enabled by device manufacturers on displays that have the
+ * ignore requested orientation display setting enabled (enables compatibility mode for fixed
+ * orientation on Android 12 (API level 31) or higher; see
+ * <a href="https://developer.android.com/guide/practices/enhanced-letterboxing">Enhanced
+ * letterboxing</a> for more details).
*
* <p>Device manufacturers can override packages to "refresh" via "resumed -> paused -> resumed"
* cycle using their discretion to improve display compatibility.
@@ -1153,22 +1156,23 @@
"android.window.PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE";
/**
- * Application level {@link android.content.pm.PackageManager.Property PackageManager
- * .Property} for an app to inform the system that the app should be excluded from the
- * compatibility override for orientation set by the device manufacturer. When the orientation
- * override is applied it can:
+ * Application level {@link android.content.pm.PackageManager.Property PackageManager.Property}
+ * for an app to inform the system that the app should be excluded from the compatibility
+ * override for orientation set by the device manufacturer. When the orientation override is
+ * applied it can:
* <ul>
* <li>Replace the specific orientation requested by the app with another selected by the
- device manufacturer, e.g. replace undefined requested by the app with portrait.
+ device manufacturer; for example, replace undefined requested by the app with portrait.
* <li>Always use an orientation selected by the device manufacturer.
* <li>Do one of the above but only when camera connection is open.
* </ul>
*
- * <p>This property is different from {@link PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION}
+ * <p>This property is different from {@link #PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION}
* (which is used to avoid orientation loops caused by the incorrect use of {@link
- * android.app.Activity#setRequestedOrientation}) because this property overrides the app to an
- * orientation selected by the device manufacturer rather than ignoring one of orientation
- * requests coming from the app while respecting the previous one.
+ * android.app.Activity#setRequestedOrientation Activity#setRequestedOrientation()}) because
+ * this property overrides the app to an orientation selected by the device manufacturer rather
+ * than ignoring one of orientation requests coming from the app while respecting the previous
+ * one.
*
* <p>With this property set to {@code true} or unset, device manufacturers can override
* orientation for the app using their discretion to improve display compatibility.
@@ -1190,10 +1194,10 @@
"android.window.PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE";
/**
- * Application level {@link android.content.pm.PackageManager.Property PackageManager
- * .Property} for an app to inform the system that the app should be opted-out from the
- * compatibility override that fixes display orientation to landscape natural orientation when
- * an activity is fullscreen.
+ * Application level {@link android.content.pm.PackageManager.Property PackageManager.Property}
+ * for an app to inform the system that the app should be opted-out from the compatibility
+ * override that fixes display orientation to landscape natural orientation when an activity is
+ * fullscreen.
*
* <p>When this compat override is enabled and while display is fixed to the landscape natural
* orientation, the orientation requested by the activity will be still respected by bounds
@@ -1202,16 +1206,17 @@
* lanscape natural orientation.
*
* <p>The treatment is disabled by default but device manufacturers can enable the treatment
- * using their discretion to improve display compatibility on the displays that have
- * ignoreOrientationRequest display setting enabled (enables compatibility mode for fixed
- * orientation, see <a href="https://developer.android.com/guide/practices/enhanced-letterboxing">Enhanced letterboxing</a>
- * for more details).
+ * using their discretion to improve display compatibility on displays that have the ignore
+ * orientation request display setting enabled by OEMs on the device (enables compatibility mode
+ * for fixed orientation on Android 12 (API level 31) or higher; see
+ * <a href="https://developer.android.com/guide/practices/enhanced-letterboxing">Enhanced
+ * letterboxing</a> for more details).
*
* <p>With this property set to {@code true} or unset, the system wiil use landscape display
* orientation when the following conditions are met:
* <ul>
* <li>Natural orientation of the display is landscape
- * <li>ignoreOrientationRequest display setting is enabled
+ * <li>ignore requested orientation display setting is enabled
* <li>Activity is fullscreen.
* <li>Device manufacturer enabled the treatment.
* </ul>
@@ -1233,9 +1238,9 @@
"android.window.PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE";
/**
- * Application level {@link android.content.pm.PackageManager.Property PackageManager
- * .Property} for an app to inform the system that the app should be opted-out from the
- * compatibility override that changes the min aspect ratio.
+ * Application level {@link android.content.pm.PackageManager.Property PackageManager.Property}
+ * for an app to inform the system that the app should be opted-out from the compatibility
+ * override that changes the min aspect ratio.
*
* <p>When this compat override is enabled the min aspect ratio given in the app's manifest can
* be overridden by the device manufacturer using their discretion to improve display
@@ -1264,14 +1269,14 @@
"android.window.PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE";
/**
- * Application level {@link android.content.pm.PackageManager.Property PackageManager
- * .Property} for an app to inform the system that the app should be opted-out from the
- * compatibility overrides that change the resizability of the app.
+ * Application level {@link android.content.pm.PackageManager.Property PackageManager.Property}
+ * for an app to inform the system that the app should be opted-out from the compatibility
+ * overrides that change the resizability of the app.
*
* <p>When these compat overrides are enabled they force the packages they are applied to to be
- * resizable / unresizable. If the app is forced to be resizable this won't change whether
- * the app can be put into multi-windowing mode, but allow the app to resize without going into
- * size-compat mode when the window container resizes, such as display size change or screen
+ * resizable/unresizable. If the app is forced to be resizable this won't change whether the app
+ * can be put into multi-windowing mode, but allow the app to resize without going into size
+ * compatibility mode when the window container resizes, such as display size change or screen
* rotation.
*
* <p>Setting this property to {@code false} informs the system that the app must be
@@ -1320,34 +1325,29 @@
}
/**
- * Application-level
- * {@link android.content.pm.PackageManager.Property PackageManager.Property}
- * tag that specifies whether OEMs are permitted to provide activity
- * embedding split-rule configurations on behalf of the app.
+ * Application-level {@link android.content.pm.PackageManager.Property PackageManager.Property}
+ * tag that specifies whether OEMs are permitted to provide activity embedding split-rule
+ * configurations on behalf of the app.
*
- * <p>If {@code true}, the system is permitted to override the app's
- * windowing behavior and implement activity embedding split rules, such as
- * displaying activities side by side. A system override informs the app
- * that the activity embedding APIs are disabled so the app will not provide
- * its own activity embedding rules, which would conflict with the system's
+ * <p>If {@code true}, the system is permitted to override the app's windowing behavior and
+ * implement activity embedding split rules, such as displaying activities side by side. A
+ * system override informs the app that the activity embedding APIs are disabled so the app
+ * doesn't provide its own activity embedding rules, which would conflict with the system's
* rules.
*
- * <p>If {@code false}, the system is not permitted to override the
- * windowing behavior of the app. Set the property to {@code false} if the
- * app provides its own activity embedding split rules, or if you want to
- * prevent the system override for any other reason.
+ * <p>If {@code false}, the system is not permitted to override the windowing behavior of the
+ * app. Set the property to {@code false} if the app provides its own activity embedding split
+ * rules, or if you want to prevent the system override for any other reason.
*
* <p>The default value is {@code false}.
*
- * <p class="note"><b>Note:</b> Refusal to permit the system override is not
- * enforceable. OEMs can override the app's activity embedding
- * implementation whether or not this property is specified and set to
- * <code>false</code>. The property is, in effect, a hint to OEMs.
+ * <p class="note"><b>Note:</b> Refusal to permit the system override is not enforceable. OEMs
+ * can override the app's activity embedding implementation whether or not this property is
+ * specified and set to {@code false}. The property is, in effect, a hint to OEMs.
*
- * <p>OEMs can implement activity embedding on any API level. The best
- * practice for apps is to always explicitly set this property in the app
- * manifest file regardless of targeted API level rather than rely on the
- * default value.
+ * <p>OEMs can implement activity embedding on any API level. The best practice for apps is to
+ * always explicitly set this property in the app manifest file regardless of targeted API level
+ * rather than rely on the default value.
*
* <p><b>Syntax:</b>
* <pre>
@@ -1362,14 +1362,15 @@
"android.window.PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE";
/**
- * Application level {@link android.content.pm.PackageManager.Property PackageManager
- * .Property} that an app can specify to inform the system that the app is ActivityEmbedding
- * split feature enabled.
+ * Application level {@link android.content.pm.PackageManager.Property PackageManager.Property}
+ * that an app can specify to inform the system that the app is activity embedding split feature
+ * enabled.
*
* <p>With this property, the system could provide custom behaviors for the apps that are
- * ActivityEmbedding split feature enabled. For example, the fixed-portrait orientation
+ * activity embedding split feature enabled. For example, the fixed-portrait orientation
* requests of the activities could be ignored by the system in order to provide seamless
- * ActivityEmbedding split experiences while holding the large-screen devices in landscape mode.
+ * activity embedding split experiences while holding large screen devices in landscape
+ * orientation.
*
* <p><b>Syntax:</b>
* <pre>
@@ -5701,4 +5702,35 @@
default @NonNull List<ComponentName> notifyScreenshotListeners(int displayId) {
throw new UnsupportedOperationException();
}
+
+ /**
+ * @param displayId The displayId to that should have its content replaced.
+ * @param window The window that should get mirrored and the mirrored content rendered on
+ * displayId passed in.
+ *
+ * @return Whether it successfully created a mirror SurfaceControl and replaced the display
+ * content with the mirror of the Window.
+ *
+ * @hide
+ */
+ @TestApi
+ @RequiresPermission(permission.ACCESS_SURFACE_FLINGER)
+ default boolean replaceContentOnDisplayWithMirror(int displayId, @NonNull Window window) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * @param displayId The displayId to that should have its content replaced.
+ * @param sc The SurfaceControl that should get rendered onto the displayId passed in.
+ *
+ * @return Whether it successfully created a mirror SurfaceControl and replaced the display
+ * content with the mirror of the Window.
+ *
+ * @hide
+ */
+ @TestApi
+ @RequiresPermission(permission.ACCESS_SURFACE_FLINGER)
+ default boolean replaceContentOnDisplayWithSc(int displayId, @NonNull SurfaceControl sc) {
+ throw new UnsupportedOperationException();
+ }
}
diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java
index b57163c..d7b74b3 100644
--- a/core/java/android/view/WindowManagerImpl.java
+++ b/core/java/android/view/WindowManagerImpl.java
@@ -34,6 +34,7 @@
import android.os.IBinder;
import android.os.RemoteException;
import android.os.StrictMode;
+import android.util.Log;
import android.window.ITaskFpsCallback;
import android.window.TaskFpsCallback;
import android.window.WindowContext;
@@ -80,6 +81,8 @@
* @hide
*/
public final class WindowManagerImpl implements WindowManager {
+ private static final String TAG = "WindowManager";
+
@UnsupportedAppUsage
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
@UiContext
@@ -467,4 +470,42 @@
throw e.rethrowFromSystemServer();
}
}
+
+ @Override
+ public boolean replaceContentOnDisplayWithMirror(int displayId, @NonNull Window window) {
+ View decorView = window.peekDecorView();
+ if (decorView == null) {
+ Log.e(TAG, "replaceContentOnDisplayWithMirror: Window's decorView was null.");
+ return false;
+ }
+
+ ViewRootImpl viewRoot = decorView.getViewRootImpl();
+ if (viewRoot == null) {
+ Log.e(TAG, "replaceContentOnDisplayWithMirror: Window's viewRootImpl was null.");
+ return false;
+ }
+
+ SurfaceControl sc = viewRoot.getSurfaceControl();
+ if (!sc.isValid()) {
+ Log.e(TAG, "replaceContentOnDisplayWithMirror: Window's SC is invalid.");
+ return false;
+ }
+ return replaceContentOnDisplayWithSc(displayId, SurfaceControl.mirrorSurface(sc));
+ }
+
+ @Override
+ public boolean replaceContentOnDisplayWithSc(int displayId, @NonNull SurfaceControl sc) {
+ if (!sc.isValid()) {
+ Log.e(TAG, "replaceContentOnDisplayWithSc: Invalid SC.");
+ return false;
+ }
+
+ try {
+ return WindowManagerGlobal.getWindowManagerService()
+ .replaceContentOnDisplay(displayId, sc);
+ } catch (RemoteException e) {
+ e.rethrowAsRuntimeException();
+ }
+ return false;
+ }
}
diff --git a/core/java/android/view/WindowManagerPolicyConstants.java b/core/java/android/view/WindowManagerPolicyConstants.java
index afc567e..c88048c 100644
--- a/core/java/android/view/WindowManagerPolicyConstants.java
+++ b/core/java/android/view/WindowManagerPolicyConstants.java
@@ -97,6 +97,12 @@
*/
String EXTRA_START_REASON = "android.intent.extra.EXTRA_START_REASON";
+ /**
+ * Set to {@code true} when intent was invoked from pressing one of the brightness keys.
+ * @hide
+ */
+ String EXTRA_FROM_BRIGHTNESS_KEY = "android.intent.extra.FROM_BRIGHTNESS_KEY";
+
// TODO: move this to a more appropriate place.
interface PointerEventListener {
/**
diff --git a/core/java/android/view/accessibility/AccessibilityEvent.java b/core/java/android/view/accessibility/AccessibilityEvent.java
index e098b63..a43906f 100644
--- a/core/java/android/view/accessibility/AccessibilityEvent.java
+++ b/core/java/android/view/accessibility/AccessibilityEvent.java
@@ -192,6 +192,8 @@
* <li>{@link #getEventTime()} - The event time.</li>
* <li>{@link #getScrollDeltaX()} - The difference in the horizontal position.</li>
* <li>{@link #getScrollDeltaY()} - The difference in the vertical position.</li>
+ * <li>{@link #getMaxScrollX()} ()} - The max scroll offset of the source left edge</li>
+ * <li>{@link #getMaxScrollY()} ()} - The max scroll offset of the source top edge.</li>
* </ul>
* </p>
* <p>
@@ -538,8 +540,18 @@
public static final int TYPE_WINDOW_CONTENT_CHANGED = 1 << 11;
/**
- * Represents the event of scrolling a view. This event type is generally not sent directly.
- * @see android.view.View#onScrollChanged(int, int, int, int)
+ * Represents the event of scrolling a view. This event type is generally not sent directly. In
+ * the View system, this is sent in
+ * {@link android.view.View#onScrollChanged(int, int, int, int)}
+ * <p>In addition to the source and package name, the event should populate scroll-specific
+ * properties like {@link #setScrollDeltaX(int)}, {@link #setScrollDeltaY(int)},
+ * {@link #setMaxScrollX(int)}, and {@link #setMaxScrollY(int)}.
+ * <p>Services are encouraged to rely on the source to query UI state over AccessibilityEvents
+ * properties. For example, to check after a scroll if the bottom of the scrolling UI element
+ * has been reached, check if the source node is scrollable and has the
+ * {@link AccessibilityNodeInfo.AccessibilityAction#ACTION_SCROLL_BACKWARD} action but not the
+ * {@link AccessibilityNodeInfo.AccessibilityAction#ACTION_SCROLL_FORWARD} action.
+ * For scrolling to a target, use {@link #TYPE_VIEW_TARGETED_BY_SCROLL}.
*/
public static final int TYPE_VIEW_SCROLLED = 1 << 12;
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index 2939954..e6a8b78 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -425,11 +425,13 @@
/**
* Action to scroll the node content forward.
+ * @see AccessibilityAction#ACTION_SCROLL_FORWARD
*/
public static final int ACTION_SCROLL_FORWARD = 1 << 12;
/**
* Action to scroll the node content backward.
+ * @see AccessibilityAction#ACTION_SCROLL_BACKWARD
*/
public static final int ACTION_SCROLL_BACKWARD = 1 << 13;
@@ -5404,6 +5406,42 @@
* <strong>Arguments:</strong>
* {@link #ACTION_ARGUMENT_SCROLL_AMOUNT_FLOAT}. This is an optional argument.
* </p>
+ * <p>The UI element that implements this should send a
+ * {@link AccessibilityEvent#TYPE_VIEW_SCROLLED} event. Depending on the orientation,
+ * this element should also add the relevant directional scroll actions of
+ * {@link #ACTION_SCROLL_LEFT}, {@link #ACTION_SCROLL_RIGHT},
+ * {@link #ACTION_SCROLL_UP}, and {@link #ACTION_SCROLL_DOWN}. If the scrolling brings
+ * the next or previous element into view as the center element, such as in a ViewPager2,
+ * use {@link #ACTION_PAGE_DOWN} and the other page actions instead of the directional
+ * actions.
+ * <p>Example: a scrolling UI of vertical orientation with a forward
+ * scroll action should also add the scroll down action:
+ * <pre class="prettyprint"><code>
+ * onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+ * super.onInitializeAccessibilityNodeInfo(info);
+ * if (canScrollForward) {
+ * info.addAction(ACTION_SCROLL_FORWARD);
+ * info.addAction(ACTION_SCROLL_DOWN);
+ * }
+ * }
+ * performAccessibilityAction(int action, Bundle bundle) {
+ * if (action == ACTION_SCROLL_FORWARD || action == ACTION_SCROLL_DOWN) {
+ * scrollForward();
+ * }
+ * }
+ * scrollForward() {
+ * ...
+ * if (mAccessibilityManager.isEnabled()) {
+ * event = new AccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SCROLLED);
+ * event.setScrollDeltaX(dx);
+ * event.setScrollDeltaY(dy);
+ * event.setMaxScrollX(maxDx);
+ * event.setMaxScrollY(maxDY);
+ * sendAccessibilityEventUnchecked(event);
+ * }
+ * }
+ * </code>
+ * </pre></p>
*/
public static final AccessibilityAction ACTION_SCROLL_FORWARD =
new AccessibilityAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
@@ -5414,6 +5452,54 @@
* <strong>Arguments:</strong>
* {@link #ACTION_ARGUMENT_SCROLL_AMOUNT_FLOAT}. This is an optional argument.
* </p>
+ * <p>The UI element that implements this should send a
+ * {@link AccessibilityEvent#TYPE_VIEW_SCROLLED} event. Depending on the orientation,
+ * this element should also add the relevant directional scroll actions of
+ * {@link #ACTION_SCROLL_LEFT}, {@link #ACTION_SCROLL_RIGHT},
+ * {@link #ACTION_SCROLL_UP}, and {@link #ACTION_SCROLL_DOWN}. If the scrolling brings
+ * the next or previous element into view as the center element, such as in a ViewPager2,
+ * use {@link #ACTION_PAGE_DOWN} and the other page actions instead of the directional
+ * actions.
+ * <p> Example: a scrolling UI of horizontal orientation with a backward
+ * scroll action should also add the scroll left/right action (LTR/RTL):
+ * <pre class="prettyprint"><code>
+ * onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+ * super.onInitializeAccessibilityNodeInfo(info);
+ * if (canScrollBackward) {
+ * info.addAction(ACTION_SCROLL_FORWARD);
+ * if (leftToRight) {
+ * info.addAction(ACTION_SCROLL_LEFT);
+ * } else {
+ * info.addAction(ACTION_SCROLL_RIGHT);
+ * }
+ * }
+ * }
+ * performAccessibilityAction(int action, Bundle bundle) {
+ * if (action == ACTION_SCROLL_BACKWARD) {
+ * scrollBackward();
+ * } else if (action == ACTION_SCROLL_LEFT) {
+ * if (!isRTL()){
+ * scrollBackward();
+ * }
+ * } else if (action == ACTION_SCROLL_RIGHT) {
+ * if (isRTL()){
+ * scrollBackward();
+ * }
+ * }
+ * }
+ * scrollBackward() {
+ * ...
+ * if (mAccessibilityManager.isEnabled()) {
+ * event = new AccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SCROLLED);
+ * event.setScrollDeltaX(dx);
+ * event.setScrollDeltaY(dy);
+ * event.setMaxScrollX(maxDx);
+ * event.setMaxScrollY(maxDY);
+ * sendAccessibilityEventUnchecked(event);
+ * }
+ * }
+ * </code>
+ * </pre></p>
*/
public static final AccessibilityAction ACTION_SCROLL_BACKWARD =
new AccessibilityAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
@@ -5509,6 +5595,8 @@
/**
* Action that requests the node make its bounding rectangle visible
* on the screen, scrolling if necessary just enough.
+ * <p>The UI element that implements this should send a
+ * {@link AccessibilityEvent#TYPE_VIEW_SCROLLED} event.
*
* @see View#requestRectangleOnScreen(Rect)
*/
@@ -5524,6 +5612,8 @@
* <li>{@link AccessibilityNodeInfo#ACTION_ARGUMENT_ROW_INT}</li>
* <li>{@link AccessibilityNodeInfo#ACTION_ARGUMENT_COLUMN_INT}</li>
* <ul>
+ * <p>The UI element that implements this should send a
+ * {@link AccessibilityEvent#TYPE_VIEW_SCROLLED} event.
*
* @see AccessibilityNodeInfo#getCollectionInfo()
*/
@@ -5562,6 +5652,8 @@
* <strong>Arguments:</strong>
* {@link #ACTION_ARGUMENT_SCROLL_AMOUNT_FLOAT}. This is an optional argument.
* </p>
+ * <p>The UI element that implements this should send a
+ * {@link AccessibilityEvent#TYPE_VIEW_SCROLLED} event.
*/
public static final AccessibilityAction ACTION_SCROLL_UP =
new AccessibilityAction(R.id.accessibilityActionScrollUp);
@@ -5572,6 +5664,8 @@
* <strong>Arguments:</strong>
* {@link #ACTION_ARGUMENT_SCROLL_AMOUNT_FLOAT}. This is an optional argument.
* </p>
+ * <p>The UI element that implements this should send a
+ * {@link AccessibilityEvent#TYPE_VIEW_SCROLLED} event.
*/
public static final AccessibilityAction ACTION_SCROLL_LEFT =
new AccessibilityAction(R.id.accessibilityActionScrollLeft);
@@ -5582,6 +5676,8 @@
* <strong>Arguments:</strong>
* {@link #ACTION_ARGUMENT_SCROLL_AMOUNT_FLOAT}. This is an optional argument.
* </p>
+ * <p>The UI element that implements this should send a
+ * {@link AccessibilityEvent#TYPE_VIEW_SCROLLED} event.
*/
public static final AccessibilityAction ACTION_SCROLL_DOWN =
new AccessibilityAction(R.id.accessibilityActionScrollDown);
@@ -5592,30 +5688,40 @@
* <strong>Arguments:</strong>
* {@link #ACTION_ARGUMENT_SCROLL_AMOUNT_FLOAT}. This is an optional argument.
* </p>
+ * <p>The UI element that implements this should send a
+ * {@link AccessibilityEvent#TYPE_VIEW_SCROLLED} event.
*/
public static final AccessibilityAction ACTION_SCROLL_RIGHT =
new AccessibilityAction(R.id.accessibilityActionScrollRight);
/**
* Action to move to the page above.
+ * <p>The UI element that implements this should send a
+ * {@link AccessibilityEvent#TYPE_VIEW_SCROLLED} event.
*/
public static final AccessibilityAction ACTION_PAGE_UP =
new AccessibilityAction(R.id.accessibilityActionPageUp);
/**
* Action to move to the page below.
+ * <p>The UI element that implements this should send a
+ * {@link AccessibilityEvent#TYPE_VIEW_SCROLLED} event.
*/
public static final AccessibilityAction ACTION_PAGE_DOWN =
new AccessibilityAction(R.id.accessibilityActionPageDown);
/**
* Action to move to the page left.
+ * <p>The UI element that implements this should send a
+ * {@link AccessibilityEvent#TYPE_VIEW_SCROLLED} event.
*/
public static final AccessibilityAction ACTION_PAGE_LEFT =
new AccessibilityAction(R.id.accessibilityActionPageLeft);
/**
* Action to move to the page right.
+ * <p>The UI element that implements this should send a
+ * {@link AccessibilityEvent#TYPE_VIEW_SCROLLED} event.
*/
public static final AccessibilityAction ACTION_PAGE_RIGHT =
new AccessibilityAction(R.id.accessibilityActionPageRight);
@@ -5720,8 +5826,9 @@
* This action initiates a drag & drop within the system. The source's dragged content is
* prepared before the drag begins. In View, this action should prepare the arguments to
* {@link View#startDragAndDrop(ClipData, View.DragShadowBuilder, Object, int)} and then
- * call {@link View#startDragAndDrop(ClipData, View.DragShadowBuilder, Object, int)}. The
- * equivalent should be performed for other UI toolkits.
+ * call {@link View#startDragAndDrop(ClipData, View.DragShadowBuilder, Object, int)} with
+ * {@link View#DRAG_FLAG_ACCESSIBILITY_ACTION}. The equivalent should be performed for other
+ * UI toolkits.
* </p>
*
* @see AccessibilityEvent#CONTENT_CHANGE_TYPE_DRAG_STARTED
@@ -5735,7 +5842,8 @@
* This action is added to potential drop targets if the source started a drag with
* {@link #ACTION_DRAG_START}. In View, these targets are Views that accepted
* {@link android.view.DragEvent#ACTION_DRAG_STARTED} and have an
- * {@link View.OnDragListener}.
+ * {@link View.OnDragListener}, and the drop occurs at the center location of the View's
+ * window bounds.
* </p>
*
* @see AccessibilityEvent#CONTENT_CHANGE_TYPE_DRAG_DROPPED
diff --git a/core/java/android/view/contentcapture/ContentCaptureManager.java b/core/java/android/view/contentcapture/ContentCaptureManager.java
index c9afdc0..2c7d326 100644
--- a/core/java/android/view/contentcapture/ContentCaptureManager.java
+++ b/core/java/android/view/contentcapture/ContentCaptureManager.java
@@ -414,7 +414,7 @@
/** @hide */
public static final boolean DEFAULT_ENABLE_CONTENT_PROTECTION_RECEIVER = false;
/** @hide */
- public static final int DEFAULT_CONTENT_PROTECTION_APPS_BLOCKLIST_SIZE = 1000;
+ public static final int DEFAULT_CONTENT_PROTECTION_APPS_BLOCKLIST_SIZE = 5000;
/** @hide */
public static final int DEFAULT_CONTENT_PROTECTION_BUFFER_SIZE = 150;
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index fb94d49..48bf973 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -50,6 +50,7 @@
import android.annotation.UiThread;
import android.annotation.UserIdInt;
import android.app.ActivityThread;
+import android.app.PropertyInvalidatedCache;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledSince;
import android.compat.annotation.UnsupportedAppUsage;
@@ -536,6 +537,13 @@
@UnsupportedAppUsage
Rect mCursorRect = new Rect();
+ /** Cached value for {@link #isStylusHandwritingAvailable} for userId. */
+ @GuardedBy("mH")
+ private PropertyInvalidatedCache<Integer, Boolean> mStylusHandwritingAvailableCache;
+
+ private static final String CACHE_KEY_STYLUS_HANDWRITING_PROPERTY =
+ "cache_key.system_server.stylus_handwriting";
+
@GuardedBy("mH")
private int mCursorSelStart;
@GuardedBy("mH")
@@ -662,6 +670,15 @@
private static final int MSG_UPDATE_VIRTUAL_DISPLAY_TO_SCREEN_MATRIX = 30;
private static final int MSG_ON_SHOW_REQUESTED = 31;
+ /**
+ * Calling this will invalidate Local stylus handwriting availability Cache which
+ * forces the next query in any process to recompute the cache.
+ * @hide
+ */
+ public static void invalidateLocalStylusHandwritingAvailabilityCaches() {
+ PropertyInvalidatedCache.invalidateCache(CACHE_KEY_STYLUS_HANDWRITING_PROPERTY);
+ }
+
private static boolean isAutofillUIShowing(View servedView) {
AutofillManager afm = servedView.getContext().getSystemService(AutofillManager.class);
return afm != null && afm.isAutofillUiShowing();
@@ -1577,8 +1594,21 @@
if (fallbackContext == null) {
return false;
}
-
- return IInputMethodManagerGlobalInvoker.isStylusHandwritingAvailableAsUser(userId);
+ boolean isAvailable;
+ synchronized (mH) {
+ if (mStylusHandwritingAvailableCache == null) {
+ mStylusHandwritingAvailableCache = new PropertyInvalidatedCache<>(
+ 4 /* maxEntries */, CACHE_KEY_STYLUS_HANDWRITING_PROPERTY) {
+ @Override
+ public Boolean recompute(Integer userId) {
+ return IInputMethodManagerGlobalInvoker.isStylusHandwritingAvailableAsUser(
+ userId);
+ }
+ };
+ }
+ isAvailable = mStylusHandwritingAvailableCache.query(userId);
+ }
+ return isAvailable;
}
/**
diff --git a/core/java/android/widget/RemoteViews.aidl b/core/java/android/widget/RemoteViews.aidl
index ec86410..6a5fc03 100644
--- a/core/java/android/widget/RemoteViews.aidl
+++ b/core/java/android/widget/RemoteViews.aidl
@@ -17,3 +17,4 @@
package android.widget;
parcelable RemoteViews;
+parcelable RemoteViews.RemoteCollectionItems;
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index a12656d..66aa66c 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -33,16 +33,19 @@
import android.app.Activity;
import android.app.ActivityOptions;
import android.app.ActivityThread;
+import android.app.AppGlobals;
import android.app.Application;
import android.app.LoadedApk;
import android.app.PendingIntent;
import android.app.RemoteInput;
import android.appwidget.AppWidgetHostView;
import android.compat.annotation.UnsupportedAppUsage;
+import android.content.ComponentName;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.Intent;
import android.content.IntentSender;
+import android.content.ServiceConnection;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.ColorStateList;
@@ -65,10 +68,12 @@
import android.os.Build;
import android.os.Bundle;
import android.os.CancellationSignal;
+import android.os.IBinder;
import android.os.Parcel;
import android.os.ParcelFileDescriptor;
import android.os.Parcelable;
import android.os.Process;
+import android.os.RemoteException;
import android.os.StrictMode;
import android.os.UserHandle;
import android.system.Os;
@@ -98,8 +103,10 @@
import android.widget.CompoundButton.OnCheckedChangeListener;
import com.android.internal.R;
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.internal.util.ContrastColorUtil;
import com.android.internal.util.Preconditions;
+import com.android.internal.widget.IRemoteViewsFactory;
import java.io.ByteArrayOutputStream;
import java.io.FileDescriptor;
@@ -124,7 +131,9 @@
import java.util.Map;
import java.util.Objects;
import java.util.Stack;
+import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.Predicate;
@@ -324,6 +333,13 @@
(clazz) -> clazz.isAnnotationPresent(RemoteViews.RemoteView.class);
/**
+ * The maximum waiting time for remote adapter conversion in milliseconds
+ *
+ * @hide
+ */
+ private static final int MAX_ADAPTER_CONVERSION_WAITING_TIME_MS = 2000;
+
+ /**
* Application that hosts the remote views.
*
* @hide
@@ -1042,28 +1058,96 @@
}
private class SetRemoteCollectionItemListAdapterAction extends Action {
- private final RemoteCollectionItems mItems;
+ private @NonNull CompletableFuture<RemoteCollectionItems> mItemsFuture;
- SetRemoteCollectionItemListAdapterAction(@IdRes int id, RemoteCollectionItems items) {
+ SetRemoteCollectionItemListAdapterAction(@IdRes int id,
+ @NonNull RemoteCollectionItems items) {
viewId = id;
- mItems = items;
- mItems.setHierarchyRootData(getHierarchyRootData());
+ items.setHierarchyRootData(getHierarchyRootData());
+ mItemsFuture = CompletableFuture.completedFuture(items);
+ }
+
+ SetRemoteCollectionItemListAdapterAction(@IdRes int id, Intent intent) {
+ viewId = id;
+ mItemsFuture = getItemsFutureFromIntentWithTimeout(intent);
+ setHierarchyRootData(getHierarchyRootData());
+ }
+
+ private static CompletableFuture<RemoteCollectionItems> getItemsFutureFromIntentWithTimeout(
+ Intent intent) {
+ if (intent == null) {
+ Log.e(LOG_TAG, "Null intent received when generating adapter future");
+ return CompletableFuture.completedFuture(new RemoteCollectionItems
+ .Builder().build());
+ }
+
+ final Context context = ActivityThread.currentApplication();
+ final CompletableFuture<RemoteCollectionItems> result = new CompletableFuture<>();
+
+ context.bindService(intent, Context.BindServiceFlags.of(Context.BIND_AUTO_CREATE),
+ result.defaultExecutor(), new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName componentName,
+ IBinder iBinder) {
+ RemoteCollectionItems items;
+ try {
+ items = IRemoteViewsFactory.Stub.asInterface(iBinder)
+ .getRemoteCollectionItems();
+ } catch (RemoteException re) {
+ items = new RemoteCollectionItems.Builder().build();
+ Log.e(LOG_TAG, "Error getting collection items from the factory",
+ re);
+ } finally {
+ context.unbindService(this);
+ }
+
+ result.complete(items);
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName componentName) { }
+ });
+
+ result.completeOnTimeout(
+ new RemoteCollectionItems.Builder().build(),
+ MAX_ADAPTER_CONVERSION_WAITING_TIME_MS, TimeUnit.MILLISECONDS);
+
+ return result;
}
SetRemoteCollectionItemListAdapterAction(Parcel parcel) {
viewId = parcel.readInt();
- mItems = new RemoteCollectionItems(parcel, getHierarchyRootData());
+ mItemsFuture = CompletableFuture.completedFuture(
+ new RemoteCollectionItems(parcel, getHierarchyRootData()));
}
@Override
public void setHierarchyRootData(HierarchyRootData rootData) {
- mItems.setHierarchyRootData(rootData);
+ mItemsFuture = mItemsFuture
+ .thenApply(rc -> {
+ rc.setHierarchyRootData(rootData);
+ return rc;
+ });
+ }
+
+ private static RemoteCollectionItems getCollectionItemsFromFuture(
+ CompletableFuture<RemoteCollectionItems> itemsFuture) {
+ RemoteCollectionItems items;
+ try {
+ items = itemsFuture.get();
+ } catch (Exception e) {
+ Log.e(LOG_TAG, "Error getting collection items from future", e);
+ items = new RemoteCollectionItems.Builder().build();
+ }
+
+ return items;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(viewId);
- mItems.writeToParcel(dest, flags, /* attached= */ true);
+ RemoteCollectionItems items = getCollectionItemsFromFuture(mItemsFuture);
+ items.writeToParcel(dest, flags, /* attached= */ true);
}
@Override
@@ -1072,6 +1156,8 @@
View target = root.findViewById(viewId);
if (target == null) return;
+ RemoteCollectionItems items = getCollectionItemsFromFuture(mItemsFuture);
+
// Ensure that we are applying to an AppWidget root
if (!(rootParent instanceof AppWidgetHostView)) {
Log.e(LOG_TAG, "setRemoteAdapter can only be used for "
@@ -1092,10 +1178,10 @@
// recycling in setAdapter, so we must call setAdapter again if the number of view types
// increases.
if (adapter instanceof RemoteCollectionItemsAdapter
- && adapter.getViewTypeCount() >= mItems.getViewTypeCount()) {
+ && adapter.getViewTypeCount() >= items.getViewTypeCount()) {
try {
((RemoteCollectionItemsAdapter) adapter).setData(
- mItems, params.handler, params.colorResources);
+ items, params.handler, params.colorResources);
} catch (Throwable throwable) {
// setData should never failed with the validation in the items builder, but if
// it does, catch and rethrow.
@@ -1105,7 +1191,7 @@
}
try {
- adapterView.setAdapter(new RemoteCollectionItemsAdapter(mItems,
+ adapterView.setAdapter(new RemoteCollectionItemsAdapter(items,
params.handler, params.colorResources));
} catch (Throwable throwable) {
// This could throw if the AdapterView somehow doesn't accept BaseAdapter due to
@@ -4682,6 +4768,12 @@
*/
@Deprecated
public void setRemoteAdapter(@IdRes int viewId, Intent intent) {
+ if (AppGlobals.getIntCoreSetting(
+ SystemUiDeviceConfigFlags.REMOTEVIEWS_ADAPTER_CONVERSION,
+ SystemUiDeviceConfigFlags.REMOTEVIEWS_ADAPTER_CONVERSION_DEFAULT ? 1 : 0) == 1) {
+ addAction(new SetRemoteCollectionItemListAdapterAction(viewId, intent));
+ return;
+ }
addAction(new SetRemoteViewsAdapterIntent(viewId, intent));
}
diff --git a/core/java/android/widget/RemoteViewsService.java b/core/java/android/widget/RemoteViewsService.java
index 214e5cc..d4f4d19 100644
--- a/core/java/android/widget/RemoteViewsService.java
+++ b/core/java/android/widget/RemoteViewsService.java
@@ -43,6 +43,13 @@
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 = 25;
+
+ /**
* 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}.
@@ -227,6 +234,30 @@
}
}
+ @Override
+ public RemoteViews.RemoteCollectionItems getRemoteCollectionItems() {
+ RemoteViews.RemoteCollectionItems items = new RemoteViews.RemoteCollectionItems
+ .Builder().build();
+
+ try {
+ RemoteViews.RemoteCollectionItems.Builder itemsBuilder =
+ new RemoteViews.RemoteCollectionItems.Builder();
+ mFactory.onDataSetChanged();
+
+ itemsBuilder.setHasStableIds(mFactory.hasStableIds());
+ final int numOfEntries = Math.min(mFactory.getCount(), MAX_NUM_ENTRY);
+ for (int i = 0; i < numOfEntries; i++) {
+ itemsBuilder.addItem(mFactory.getItemId(i), mFactory.getViewAt(i));
+ }
+
+ items = itemsBuilder.build();
+ } catch (Exception ex) {
+ Thread t = Thread.currentThread();
+ Thread.getDefaultUncaughtExceptionHandler().uncaughtException(t, ex);
+ }
+ return items;
+ }
+
private RemoteViewsFactory mFactory;
private boolean mIsCreated;
}
diff --git a/core/java/android/window/TaskFragmentOperation.java b/core/java/android/window/TaskFragmentOperation.java
index 413f0cc..e153bb7 100644
--- a/core/java/android/window/TaskFragmentOperation.java
+++ b/core/java/android/window/TaskFragmentOperation.java
@@ -73,6 +73,13 @@
/** Sets the relative bounds with {@link WindowContainerTransaction#setRelativeBounds}. */
public static final int OP_TYPE_SET_RELATIVE_BOUNDS = 9;
+ /**
+ * Reorders the TaskFragment to be the front-most TaskFragment in the Task.
+ * Note that there could still have other WindowContainer on top of the front-most
+ * TaskFragment, such as a non-embedded Activity.
+ */
+ public static final int OP_TYPE_REORDER_TO_FRONT = 10;
+
@IntDef(prefix = { "OP_TYPE_" }, value = {
OP_TYPE_UNKNOWN,
OP_TYPE_CREATE_TASK_FRAGMENT,
@@ -84,7 +91,8 @@
OP_TYPE_REQUEST_FOCUS_ON_TASK_FRAGMENT,
OP_TYPE_SET_COMPANION_TASK_FRAGMENT,
OP_TYPE_SET_ANIMATION_PARAMS,
- OP_TYPE_SET_RELATIVE_BOUNDS
+ OP_TYPE_SET_RELATIVE_BOUNDS,
+ OP_TYPE_REORDER_TO_FRONT
})
@Retention(RetentionPolicy.SOURCE)
public @interface OperationType {}
diff --git a/core/java/android/window/WindowContextController.java b/core/java/android/window/WindowContextController.java
index 4b9a957..eb270e2 100644
--- a/core/java/android/window/WindowContextController.java
+++ b/core/java/android/window/WindowContextController.java
@@ -21,6 +21,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.servertransaction.WindowTokenClientController;
import android.content.Context;
import android.os.Bundle;
import android.os.IBinder;
@@ -104,7 +105,8 @@
throw new IllegalStateException("A Window Context can be only attached to "
+ "a DisplayArea once.");
}
- mAttachedToDisplayArea = mToken.attachToDisplayArea(type, displayId, options)
+ mAttachedToDisplayArea = WindowTokenClientController.getInstance().attachToDisplayArea(
+ mToken, type, displayId, options)
? AttachStatus.STATUS_ATTACHED : AttachStatus.STATUS_FAILED;
if (mAttachedToDisplayArea == AttachStatus.STATUS_FAILED) {
Log.w(TAG, "attachToDisplayArea fail, type:" + type + ", displayId:"
@@ -140,13 +142,13 @@
throw new IllegalStateException("The Window Context should have been attached"
+ " to a DisplayArea. AttachToDisplayArea:" + mAttachedToDisplayArea);
}
- mToken.attachToWindowToken(windowToken);
+ WindowTokenClientController.getInstance().attachToWindowToken(mToken, windowToken);
}
/** Detaches the window context from the node it's currently associated with. */
public void detachIfNeeded() {
if (mAttachedToDisplayArea == AttachStatus.STATUS_ATTACHED) {
- mToken.detachFromWindowContainerIfNeeded();
+ WindowTokenClientController.getInstance().detachIfNeeded(mToken);
mAttachedToDisplayArea = AttachStatus.STATUS_DETACHED;
if (DEBUG_ATTACH) {
Log.d(TAG, "Detach Window Context.");
diff --git a/core/java/android/window/WindowInfosListenerForTest.java b/core/java/android/window/WindowInfosListenerForTest.java
index 7fc55a7..be88e53 100644
--- a/core/java/android/window/WindowInfosListenerForTest.java
+++ b/core/java/android/window/WindowInfosListenerForTest.java
@@ -20,6 +20,7 @@
import android.annotation.NonNull;
import android.annotation.RequiresPermission;
import android.annotation.TestApi;
+import android.graphics.Matrix;
import android.graphics.Rect;
import android.os.IBinder;
import android.os.InputConfig;
@@ -78,22 +79,31 @@
*/
public final boolean isVisible;
+ /**
+ * Return the transform to get the bounds from display space into window space.
+ */
+ @NonNull
+ public final Matrix transform;
+
WindowInfo(@NonNull IBinder windowToken, @NonNull String name, int displayId,
- @NonNull Rect bounds, int inputConfig) {
+ @NonNull Rect bounds, int inputConfig, @NonNull Matrix transform) {
this.windowToken = windowToken;
this.name = name;
this.displayId = displayId;
this.bounds = bounds;
this.isTrustedOverlay = (inputConfig & InputConfig.TRUSTED_OVERLAY) != 0;
this.isVisible = (inputConfig & InputConfig.NOT_VISIBLE) == 0;
+ this.transform = transform;
}
@Override
public String toString() {
- return name + ", frame=" + bounds
+ return name + ", displayId=" + displayId
+ + ", frame=" + bounds
+ ", isVisible=" + isVisible
+ ", isTrustedOverlay=" + isTrustedOverlay
- + ", token=" + windowToken;
+ + ", token=" + windowToken
+ + ", transform=" + transform;
}
}
@@ -154,7 +164,7 @@
var bounds = new Rect(handle.frameLeft, handle.frameTop, handle.frameRight,
handle.frameBottom);
windowInfos.add(new WindowInfo(handle.getWindowToken(), handle.name, handle.displayId,
- bounds, handle.inputConfig));
+ bounds, handle.inputConfig, handle.transform));
}
return windowInfos;
}
diff --git a/core/java/android/window/WindowTokenClient.java b/core/java/android/window/WindowTokenClient.java
index a208634..55b823b 100644
--- a/core/java/android/window/WindowTokenClient.java
+++ b/core/java/android/window/WindowTokenClient.java
@@ -23,10 +23,10 @@
import android.annotation.BinderThread;
import android.annotation.MainThread;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.app.ActivityThread;
import android.app.IWindowToken;
import android.app.ResourcesManager;
+import android.app.servertransaction.WindowTokenClientController;
import android.content.Context;
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
@@ -36,14 +36,9 @@
import android.os.Debug;
import android.os.Handler;
import android.os.IBinder;
-import android.os.RemoteException;
import android.util.Log;
-import android.view.IWindowManager;
-import android.view.WindowManager.LayoutParams.WindowType;
-import android.view.WindowManagerGlobal;
import com.android.internal.annotations.GuardedBy;
-import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.function.pooled.PooledLambda;
import java.lang.ref.WeakReference;
@@ -70,15 +65,11 @@
private final ResourcesManager mResourcesManager = ResourcesManager.getInstance();
- private IWindowManager mWms;
-
@GuardedBy("itself")
private final Configuration mConfiguration = new Configuration();
private boolean mShouldDumpConfigForIme;
- private boolean mAttachToWindowContainer;
-
private final Handler mHandler = ActivityThread.currentActivityThread().getHandler();
/**
@@ -101,88 +92,6 @@
}
/**
- * Attaches this {@link WindowTokenClient} to a {@link com.android.server.wm.DisplayArea}.
- *
- * @param type The window type of the {@link WindowContext}
- * @param displayId The {@link Context#getDisplayId() ID of display} to associate with
- * @param options The window context launched option
- * @return {@code true} if attaching successfully.
- */
- public boolean attachToDisplayArea(@WindowType int type, int displayId,
- @Nullable Bundle options) {
- try {
- final Configuration configuration = getWindowManagerService()
- .attachWindowContextToDisplayArea(this, type, displayId, options);
- if (configuration == null) {
- return false;
- }
- onConfigurationChanged(configuration, displayId, false /* shouldReportConfigChange */);
- mAttachToWindowContainer = true;
- return true;
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
-
- /**
- * Attaches this {@link WindowTokenClient} to a {@code DisplayContent}.
- *
- * @param displayId The {@link Context#getDisplayId() ID of display} to associate with
- * @return {@code true} if attaching successfully.
- */
- public boolean attachToDisplayContent(int displayId) {
- final IWindowManager wms = getWindowManagerService();
- // #createSystemUiContext may call this method before WindowManagerService is initialized.
- if (wms == null) {
- return false;
- }
- try {
- final Configuration configuration = wms.attachToDisplayContent(this, displayId);
- if (configuration == null) {
- return false;
- }
- onConfigurationChanged(configuration, displayId, false /* shouldReportConfigChange */);
- mAttachToWindowContainer = true;
- return true;
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
-
- /**
- * Attaches this {@link WindowTokenClient} to a {@code windowToken}.
- *
- * @param windowToken the window token to associated with
- */
- public void attachToWindowToken(IBinder windowToken) {
- try {
- getWindowManagerService().attachWindowContextToWindowToken(this, windowToken);
- mAttachToWindowContainer = true;
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
-
- /** Detaches this {@link WindowTokenClient} from associated WindowContainer if there's one. */
- public void detachFromWindowContainerIfNeeded() {
- if (!mAttachToWindowContainer) {
- return;
- }
- try {
- getWindowManagerService().detachWindowContextFromWindowContainer(this);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
-
- private IWindowManager getWindowManagerService() {
- if (mWms == null) {
- mWms = WindowManagerGlobal.getWindowManagerService();
- }
- return mWms;
- }
-
- /**
* Called when {@link Configuration} updates from the server side receive.
*
* @param newConfig the updated {@link Configuration}
@@ -207,15 +116,14 @@
* {@code shouldReportConfigChange} is {@code true}, which is usually from
* {@link IWindowToken#onConfigurationChanged(Configuration, int)}
* directly, while this method could be run on any thread if it is used to initialize
- * Context's {@code Configuration} via {@link #attachToDisplayArea(int, int, Bundle)}
- * or {@link #attachToDisplayContent(int)}.
+ * Context's {@code Configuration} via {@link WindowTokenClientController#attachToDisplayArea}
+ * or {@link WindowTokenClientController#attachToDisplayContent}.
*
* @param shouldReportConfigChange {@code true} to indicate that the {@code Configuration}
* should be dispatched to listeners.
*
*/
@AnyThread
- @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public void onConfigurationChanged(Configuration newConfig, int newDisplayId,
boolean shouldReportConfigChange) {
final Context context = mContextRef.get();
diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
index 385612d..a2c4b23 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
@@ -532,6 +532,17 @@
public static final String TASK_MANAGER_SHOW_FOOTER_DOT = "task_manager_show_footer_dot";
/**
+ * (boolean) Whether to enable the adapter conversion in RemoteViews
+ */
+ public static final String REMOTEVIEWS_ADAPTER_CONVERSION = "remoteviews_adapter_conversion";
+
+ /**
+ * Default value for whether the adapter conversion is enabled or not. This is set for
+ * RemoteViews and should not be a common practice.
+ */
+ public static final boolean REMOTEVIEWS_ADAPTER_CONVERSION_DEFAULT = false;
+
+ /**
* (boolean) Whether the task manager should show a stop button if the app is allowlisted
* by the user.
*/
diff --git a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
index 0fe42e6..81cd280 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
@@ -84,7 +84,7 @@
/** Gating the holding of WakeLocks until NLSes are told about a new notification. */
public static final Flag WAKE_LOCK_FOR_POSTING_NOTIFICATION =
- devFlag("persist.sysui.notification.wake_lock_for_posting_notification");
+ releasedFlag("persist.sysui.notification.wake_lock_for_posting_notification");
/** Gating storing NotificationRankingUpdate ranking map in shared memory. */
public static final Flag RANKING_UPDATE_ASHMEM = devFlag(
diff --git a/core/java/com/android/internal/content/PackageMonitor.java b/core/java/com/android/internal/content/PackageMonitor.java
index b7a2c71..0d1871d 100644
--- a/core/java/com/android/internal/content/PackageMonitor.java
+++ b/core/java/com/android/internal/content/PackageMonitor.java
@@ -99,12 +99,6 @@
(thread == null) ? BackgroundThread.getHandler() : new Handler(thread));
}
- public void register(Context context, UserHandle user,
- boolean externalStorage, Handler handler) {
- // Remove until all using code are updated to new method.
- register(context, user, handler);
- }
-
/**
* Register for notifications of package changes such as install, removal and other events.
diff --git a/core/java/com/android/internal/content/om/OverlayConfig.java b/core/java/com/android/internal/content/om/OverlayConfig.java
index 2d04bdb..07e178c 100644
--- a/core/java/com/android/internal/content/om/OverlayConfig.java
+++ b/core/java/com/android/internal/content/om/OverlayConfig.java
@@ -34,8 +34,15 @@
import com.android.internal.util.Preconditions;
import com.android.internal.util.function.TriConsumer;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.SAXException;
+
import java.io.File;
import java.io.FileInputStream;
+import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
@@ -46,6 +53,10 @@
import java.util.Map;
import java.util.function.Supplier;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+
/**
* Responsible for reading overlay configuration files and handling queries of overlay mutability,
* default-enabled state, and priority.
@@ -61,6 +72,8 @@
@VisibleForTesting
public static final int DEFAULT_PRIORITY = Integer.MAX_VALUE;
+ public static final String PARTITION_ORDER_FILE_PATH = "/product/overlay/partition_order.xml";
+
@VisibleForTesting
public static final class Configuration {
@Nullable
@@ -119,6 +132,10 @@
// Singleton instance only assigned in system server
private static OverlayConfig sInstance;
+ private final String mPartitionOrder;
+
+ private final boolean mIsDefaultPartitionOrder;
+
@VisibleForTesting
public OverlayConfig(@Nullable File rootDirectory,
@Nullable Supplier<OverlayScanner> scannerFactory,
@@ -137,6 +154,8 @@
new File(rootDirectory, p.getNonConicalFolder().getPath()),
p)));
}
+ mIsDefaultPartitionOrder = !sortPartitions(PARTITION_ORDER_FILE_PATH, partitions);
+ mPartitionOrder = generatePartitionOrderString(partitions);
ArrayMap<Integer, List<String>> activeApexesPerPartition = getActiveApexes(partitions);
@@ -198,6 +217,96 @@
}
}
+ private static String generatePartitionOrderString(List<OverlayPartition> partitions) {
+ if (partitions == null || partitions.size() == 0) {
+ return "";
+ }
+ StringBuilder partitionOrder = new StringBuilder();
+ partitionOrder.append(partitions.get(0).getName());
+ for (int i = 1; i < partitions.size(); i++) {
+ partitionOrder.append(", ").append(partitions.get(i).getName());
+ }
+ return partitionOrder.toString();
+ }
+
+ private static boolean parseAndValidatePartitionsOrderXml(String partitionOrderFilePath,
+ Map<String, Integer> orderMap, List<OverlayPartition> partitions) {
+ try {
+ File file = new File(partitionOrderFilePath);
+ if (!file.exists()) {
+ Log.w(TAG, "partition_order.xml does not exist.");
+ return false;
+ }
+ var dbFactory = DocumentBuilderFactory.newInstance();
+ DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
+ Document doc = dBuilder.parse(file);
+ doc.getDocumentElement().normalize();
+
+ Element root = doc.getDocumentElement();
+ if (!root.getNodeName().equals("partition-order")) {
+ Log.w(TAG, "Invalid partition_order.xml, "
+ + "xml root element is not partition-order");
+ return false;
+ }
+
+ NodeList partitionList = doc.getElementsByTagName("partition");
+ for (int order = 0; order < partitionList.getLength(); order++) {
+ Node partitionNode = partitionList.item(order);
+ if (partitionNode.getNodeType() == Node.ELEMENT_NODE) {
+ Element partitionElement = (Element) partitionNode;
+ String partitionName = partitionElement.getAttribute("name");
+ if (orderMap.containsKey(partitionName)) {
+ Log.w(TAG, "Invalid partition_order.xml, "
+ + "it has duplicate partition: " + partitionName);
+ return false;
+ }
+ orderMap.put(partitionName, order);
+ }
+ }
+
+ if (orderMap.keySet().size() != partitions.size()) {
+ Log.w(TAG, "Invalid partition_order.xml, partition_order.xml has "
+ + orderMap.keySet().size() + " partitions, "
+ + "which is different from SYSTEM_PARTITIONS");
+ return false;
+ }
+ for (int i = 0; i < partitions.size(); i++) {
+ if (!orderMap.keySet().contains(partitions.get(i).getName())) {
+ Log.w(TAG, "Invalid Parsing partition_order.xml, "
+ + "partition_order.xml does not have partition: "
+ + partitions.get(i).getName());
+ return false;
+ }
+ }
+ } catch (ParserConfigurationException | SAXException | IOException e) {
+ Log.w(TAG, "Parsing or validating partition_order.xml failed, "
+ + "exception thrown: " + e.getMessage());
+ return false;
+ }
+ Log.i(TAG, "Sorting partitions in the specified order from partitions_order.xml");
+ return true;
+ }
+
+ /**
+ * Sort partitions by order in partition_order.xml if the file exists.
+ *
+ * @hide
+ */
+ @VisibleForTesting
+ public static boolean sortPartitions(String partitionOrderFilePath,
+ List<OverlayPartition> partitions) {
+ Map<String, Integer> orderMap = new HashMap<>();
+ if (!parseAndValidatePartitionsOrderXml(partitionOrderFilePath, orderMap, partitions)) {
+ return false;
+ }
+
+ Comparator<OverlayPartition> partitionComparator = Comparator.comparingInt(
+ o -> orderMap.get(o.getName()));
+ Collections.sort(partitions, partitionComparator);
+
+ return true;
+ }
+
/**
* Creates an instance of OverlayConfig for use in the zygote process.
* This instance will not include information of static overlays existing outside of a partition
@@ -476,4 +585,19 @@
*/
private static native String[] createIdmap(@NonNull String targetPath,
@NonNull String[] overlayPath, @NonNull String[] policies, boolean enforceOverlayable);
+
+ /**
+ * @hide
+ */
+ public boolean isDefaultPartitionOrder() {
+ return mIsDefaultPartitionOrder;
+ }
+
+ /**
+ * @hide
+ */
+ public String getPartitionOrder() {
+ return mPartitionOrder;
+ }
+
}
diff --git a/core/java/com/android/internal/content/om/OverlayConfigParser.java b/core/java/com/android/internal/content/om/OverlayConfigParser.java
index 5ab77d8..faaf7d5 100644
--- a/core/java/com/android/internal/content/om/OverlayConfigParser.java
+++ b/core/java/com/android/internal/content/om/OverlayConfigParser.java
@@ -28,6 +28,7 @@
import android.util.Log;
import android.util.Xml;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.content.om.OverlayScanner.ParsedOverlayInfo;
import com.android.internal.util.Preconditions;
import com.android.internal.util.XmlUtils;
@@ -54,8 +55,11 @@
*
* @see #parseOverlay(File, XmlPullParser, OverlayScanner, ParsingContext)
* @see #parseMerge(File, XmlPullParser, OverlayScanner, ParsingContext)
+ *
+ * @hide
**/
-final class OverlayConfigParser {
+@VisibleForTesting
+public final class OverlayConfigParser {
/** Represents a part of a parsed overlay configuration XML file. */
public static class ParsedConfigFile {
@@ -160,7 +164,11 @@
}
}
- static class OverlayPartition extends SystemPartition {
+ /**
+ * @hide
+ **/
+ @VisibleForTesting
+ public static class OverlayPartition extends SystemPartition {
// Policies passed to idmap2 during idmap creation.
// Keep partition policy constants in sync with f/b/cmds/idmap2/include/idmap2/Policies.h.
static final String POLICY_ODM = "odm";
@@ -173,7 +181,11 @@
@NonNull
public final String policy;
- OverlayPartition(@NonNull SystemPartition partition) {
+ /**
+ * @hide
+ **/
+ @VisibleForTesting
+ public OverlayPartition(@NonNull SystemPartition partition) {
super(partition);
this.policy = policyForPartition(partition);
}
diff --git a/core/java/com/android/internal/flags/CoreFlags.java b/core/java/com/android/internal/flags/CoreFlags.java
new file mode 100644
index 0000000..f177ef8
--- /dev/null
+++ b/core/java/com/android/internal/flags/CoreFlags.java
@@ -0,0 +1,93 @@
+/*
+ * 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.internal.flags;
+
+import android.flags.BooleanFlag;
+import android.flags.DynamicBooleanFlag;
+import android.flags.FeatureFlags;
+import android.flags.FusedOffFlag;
+import android.flags.FusedOnFlag;
+import android.flags.SyncableFlag;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Flags defined here are can be read by code in core.
+ *
+ * Flags not defined here will throw a security exception if third-party processes attempts to read
+ * them.
+ *
+ * DO NOT define a flag here unless you explicitly intend for that flag to be readable by code that
+ * runs inside a third party process.
+ */
+public abstract class CoreFlags {
+ private static final List<SyncableFlag> sKnownFlags = new ArrayList<>();
+
+ public static BooleanFlag BOOL_FLAG = booleanFlag("core", "bool_flag", false);
+ public static FusedOffFlag OFF_FLAG = fusedOffFlag("core", "off_flag");
+ public static FusedOnFlag ON_FLAG = fusedOnFlag("core", "on_flag");
+ public static DynamicBooleanFlag DYN_FLAG = dynamicBooleanFlag("core", "dyn_flag", true);
+
+ /** Returns true if the passed in flag matches a flag in this class. */
+ public static boolean isCoreFlag(SyncableFlag flag) {
+ for (SyncableFlag knownFlag : sKnownFlags) {
+ if (knownFlag.getName().equals(flag.getName())
+ && knownFlag.getNamespace().equals(flag.getNamespace())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public static List<SyncableFlag> getCoreFlags() {
+ return sKnownFlags;
+ }
+
+ private static BooleanFlag booleanFlag(String namespace, String name, boolean defaultValue) {
+ BooleanFlag f = FeatureFlags.booleanFlag(namespace, name, defaultValue);
+
+ sKnownFlags.add(new SyncableFlag(namespace, name, Boolean.toString(defaultValue), false));
+
+ return f;
+ }
+
+ private static FusedOffFlag fusedOffFlag(String namespace, String name) {
+ FusedOffFlag f = FeatureFlags.fusedOffFlag(namespace, name);
+
+ sKnownFlags.add(new SyncableFlag(namespace, name, "false", false));
+
+ return f;
+ }
+
+ private static FusedOnFlag fusedOnFlag(String namespace, String name) {
+ FusedOnFlag f = FeatureFlags.fusedOnFlag(namespace, name);
+
+ sKnownFlags.add(new SyncableFlag(namespace, name, "true", false));
+
+ return f;
+ }
+
+ private static DynamicBooleanFlag dynamicBooleanFlag(
+ String namespace, String name, boolean defaultValue) {
+ DynamicBooleanFlag f = FeatureFlags.dynamicBooleanFlag(namespace, name, defaultValue);
+
+ sKnownFlags.add(new SyncableFlag(namespace, name, Boolean.toString(defaultValue), true));
+
+ return f;
+ }
+}
diff --git a/core/java/com/android/internal/os/TimeoutRecord.java b/core/java/com/android/internal/os/TimeoutRecord.java
index 1d0ba3f..e9a8d4b 100644
--- a/core/java/com/android/internal/os/TimeoutRecord.java
+++ b/core/java/com/android/internal/os/TimeoutRecord.java
@@ -58,6 +58,7 @@
int APP_REGISTERED = 7;
int SHORT_FGS_TIMEOUT = 8;
int JOB_SERVICE = 9;
+ int APP_START = 10;
}
/** Kind of timeout, e.g. BROADCAST_RECEIVER, etc. */
@@ -190,4 +191,10 @@
public static TimeoutRecord forJobService(String reason) {
return TimeoutRecord.endingNow(TimeoutKind.JOB_SERVICE, reason);
}
+
+ /** Record for app startup timeout. */
+ @NonNull
+ public static TimeoutRecord forAppStart(String reason) {
+ return TimeoutRecord.endingNow(TimeoutKind.APP_START, reason);
+ }
}
diff --git a/core/java/com/android/internal/security/TEST_MAPPING b/core/java/com/android/internal/security/TEST_MAPPING
index b47ecec..2d598dc 100644
--- a/core/java/com/android/internal/security/TEST_MAPPING
+++ b/core/java/com/android/internal/security/TEST_MAPPING
@@ -20,12 +20,7 @@
"file_patterns": ["VerityUtils\\.java"]
},
{
- "name": "CtsAppSecurityHostTestCases",
- "options": [
- {
- "include-filter": "android.appsecurity.cts.ApkVerityInstallTest"
- }
- ],
+ "name": "CtsApkVerityInstallHostTestCases",
"file_patterns": ["VerityUtils\\.java"]
}
]
diff --git a/core/java/com/android/internal/widget/IRemoteViewsFactory.aidl b/core/java/com/android/internal/widget/IRemoteViewsFactory.aidl
index c06dab9..918d9c0 100644
--- a/core/java/com/android/internal/widget/IRemoteViewsFactory.aidl
+++ b/core/java/com/android/internal/widget/IRemoteViewsFactory.aidl
@@ -39,5 +39,6 @@
boolean hasStableIds();
@UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
boolean isCreated();
+ RemoteViews.RemoteCollectionItems getRemoteCollectionItems();
}
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index fe5850a..d5b8f62 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -1015,7 +1015,7 @@
CREDENTIAL_TYPE_API, CREDENTIAL_TYPE_API, mCredentialTypeQuery);
/**
- * Invalidate the credential cache
+ * Invalidate the credential type cache
* @hide
*/
public final static void invalidateCredentialTypeCache() {
diff --git a/core/java/com/android/internal/widget/OWNERS b/core/java/com/android/internal/widget/OWNERS
index d068a3a..e2672f5 100644
--- a/core/java/com/android/internal/widget/OWNERS
+++ b/core/java/com/android/internal/widget/OWNERS
@@ -21,3 +21,6 @@
per-file ObservableTextView.java = file:/services/core/java/com/android/server/notification/OWNERS
per-file RemeasuringLinearLayout.java = file:/services/core/java/com/android/server/notification/OWNERS
per-file ViewClippingUtil.java = file:/services/core/java/com/android/server/notification/OWNERS
+
+# Appwidget related
+per-file *RemoteViews* = file:/services/appwidget/java/com/android/server/appwidget/OWNERS
diff --git a/core/jni/android_database_SQLiteConnection.cpp b/core/jni/android_database_SQLiteConnection.cpp
index 7e827a8..29520c2 100644
--- a/core/jni/android_database_SQLiteConnection.cpp
+++ b/core/jni/android_database_SQLiteConnection.cpp
@@ -885,6 +885,16 @@
return sqlite3_last_insert_rowid(connection->db);
}
+static jlong nativeChanges(JNIEnv* env, jclass, jlong connectionPtr) {
+ SQLiteConnection* connection = reinterpret_cast<SQLiteConnection*>(connectionPtr);
+ return sqlite3_changes64(connection->db);
+}
+
+static jlong nativeTotalChanges(JNIEnv* env, jclass, jlong connectionPtr) {
+ SQLiteConnection* connection = reinterpret_cast<SQLiteConnection*>(connectionPtr);
+ return sqlite3_total_changes64(connection->db);
+}
+
static const JNINativeMethod sMethods[] =
{
/* name, signature, funcPtr */
@@ -943,7 +953,9 @@
{ "nativeResetCancel", "(JZ)V",
(void*)nativeResetCancel },
- { "nativeLastInsertRowId", "(J)I", (void*) nativeLastInsertRowId }
+ { "nativeLastInsertRowId", "(J)I", (void*) nativeLastInsertRowId },
+ { "nativeChanges", "(J)J", (void*) nativeChanges },
+ { "nativeTotalChanges", "(J)J", (void*) nativeTotalChanges },
};
int register_android_database_SQLiteConnection(JNIEnv *env)
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index e1be0cd..4cf17b7 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -30,6 +30,8 @@
#include <media/AudioSystem.h>
#include <nativehelper/JNIHelp.h>
#include <nativehelper/ScopedLocalRef.h>
+#include <nativehelper/ScopedPrimitiveArray.h>
+#include <nativehelper/jni_macros.h>
#include <system/audio.h>
#include <system/audio_policy.h>
#include <utils/Log.h>
@@ -285,7 +287,7 @@
ALOGE("Can't find class %s", kEventHandlerClassPathName);
return;
}
- mClass = (jclass)env->NewGlobalRef(clazz);
+ mClass = static_cast<jclass>(env->NewGlobalRef(clazz));
// We use a weak reference so the AudioPortEventHandler object can be garbage collected.
// The reference is only used as a proxy for callbacks.
@@ -337,15 +339,16 @@
const sp<JNIAudioPortCallback>& callback)
{
Mutex::Autolock l(gLock);
- sp<JNIAudioPortCallback> old =
- (JNIAudioPortCallback*)env->GetLongField(thiz, gEventHandlerFields.mJniCallback);
+ sp<JNIAudioPortCallback> old = reinterpret_cast<JNIAudioPortCallback *>(
+ env->GetLongField(thiz, gEventHandlerFields.mJniCallback));
if (callback.get()) {
- callback->incStrong((void*)setJniCallback);
+ callback->incStrong(reinterpret_cast<void *>(setJniCallback));
}
if (old != 0) {
- old->decStrong((void*)setJniCallback);
+ old->decStrong(reinterpret_cast<void *>(setJniCallback));
}
- env->SetLongField(thiz, gEventHandlerFields.mJniCallback, (jlong)callback.get());
+ env->SetLongField(thiz, gEventHandlerFields.mJniCallback,
+ reinterpret_cast<jlong>(callback.get()));
return old;
}
@@ -374,43 +377,44 @@
jobjectArray deviceAddresses,
AudioDeviceTypeAddrVector &audioDeviceTypeAddrVector) {
if (deviceTypes == nullptr || deviceAddresses == nullptr) {
- return (jint)AUDIO_JAVA_BAD_VALUE;
+ return AUDIO_JAVA_BAD_VALUE;
}
jsize deviceCount = env->GetArrayLength(deviceTypes);
if (deviceCount == 0 || deviceCount != env->GetArrayLength(deviceAddresses)) {
- return (jint)AUDIO_JAVA_BAD_VALUE;
+ return AUDIO_JAVA_BAD_VALUE;
}
// retrieve all device types
std::vector<audio_devices_t> deviceTypesVector;
jint *typesPtr = nullptr;
typesPtr = env->GetIntArrayElements(deviceTypes, 0);
if (typesPtr == nullptr) {
- return (jint)AUDIO_JAVA_BAD_VALUE;
+ return AUDIO_JAVA_BAD_VALUE;
}
for (jint i = 0; i < deviceCount; i++) {
- deviceTypesVector.push_back((audio_devices_t)typesPtr[i]);
+ deviceTypesVector.push_back(static_cast<audio_devices_t>(typesPtr[i]));
}
// check each address is a string and add device type/address to list
jclass stringClass = FindClassOrDie(env, "java/lang/String");
for (jint i = 0; i < deviceCount; i++) {
jobject addrJobj = env->GetObjectArrayElement(deviceAddresses, i);
if (!env->IsInstanceOf(addrJobj, stringClass)) {
- return (jint)AUDIO_JAVA_BAD_VALUE;
+ return AUDIO_JAVA_BAD_VALUE;
}
- const char *address = env->GetStringUTFChars((jstring)addrJobj, NULL);
- AudioDeviceTypeAddr dev = AudioDeviceTypeAddr((audio_devices_t)typesPtr[i], address);
+ const char *address = env->GetStringUTFChars(static_cast<jstring>(addrJobj), NULL);
+ AudioDeviceTypeAddr dev =
+ AudioDeviceTypeAddr(static_cast<audio_devices_t>(typesPtr[i]), address);
audioDeviceTypeAddrVector.push_back(dev);
- env->ReleaseStringUTFChars((jstring)addrJobj, address);
+ env->ReleaseStringUTFChars(static_cast<jstring>(addrJobj), address);
}
env->ReleaseIntArrayElements(deviceTypes, typesPtr, 0);
- return (jint)NO_ERROR;
+ return NO_ERROR;
}
static jint
android_media_AudioSystem_muteMicrophone(JNIEnv *env, jobject thiz, jboolean on)
{
- return (jint) check_AudioSystem_Command(AudioSystem::muteMicrophone(on));
+ return check_AudioSystem_Command(AudioSystem::muteMicrophone(on));
}
static jboolean
@@ -425,7 +429,7 @@
android_media_AudioSystem_isStreamActive(JNIEnv *env, jobject thiz, jint stream, jint inPastMs)
{
bool state = false;
- AudioSystem::isStreamActive((audio_stream_type_t) stream, &state, inPastMs);
+ AudioSystem::isStreamActive(static_cast<audio_stream_type_t>(stream), &state, inPastMs);
return state;
}
@@ -434,7 +438,7 @@
jint inPastMs)
{
bool state = false;
- AudioSystem::isStreamActiveRemotely((audio_stream_type_t) stream, &state, inPastMs);
+ AudioSystem::isStreamActiveRemotely(static_cast<audio_stream_type_t>(stream), &state, inPastMs);
return state;
}
@@ -442,7 +446,7 @@
android_media_AudioSystem_isSourceActive(JNIEnv *env, jobject thiz, jint source)
{
bool state = false;
- AudioSystem::isSourceActive((audio_source_t) source, &state);
+ AudioSystem::isSourceActive(static_cast<audio_source_t>(source), &state);
return state;
}
@@ -477,8 +481,7 @@
env->GetStringLength(keyValuePairs));
env->ReleaseStringCritical(keyValuePairs, c_keyValuePairs);
}
- int status = check_AudioSystem_Command(AudioSystem::setParameters(c_keyValuePairs8));
- return (jint) status;
+ return check_AudioSystem_Command(AudioSystem::setParameters(c_keyValuePairs8));
}
static jstring
@@ -558,15 +561,15 @@
return;
}
jint recParamData[REC_PARAM_SIZE];
- recParamData[0] = (jint) audioFormatFromNative(clientConfig->format);
+ recParamData[0] = audioFormatFromNative(clientConfig->format);
// FIXME this doesn't support index-based masks
- recParamData[1] = (jint) inChannelMaskFromNative(clientConfig->channel_mask);
- recParamData[2] = (jint) clientConfig->sample_rate;
- recParamData[3] = (jint) audioFormatFromNative(deviceConfig->format);
+ recParamData[1] = inChannelMaskFromNative(clientConfig->channel_mask);
+ recParamData[2] = clientConfig->sample_rate;
+ recParamData[3] = audioFormatFromNative(deviceConfig->format);
// FIXME this doesn't support index-based masks
- recParamData[4] = (jint) inChannelMaskFromNative(deviceConfig->channel_mask);
- recParamData[5] = (jint) deviceConfig->sample_rate;
- recParamData[6] = (jint) patchHandle;
+ recParamData[4] = inChannelMaskFromNative(deviceConfig->channel_mask);
+ recParamData[5] = deviceConfig->sample_rate;
+ recParamData[6] = patchHandle;
env->SetIntArrayRegion(recParamArray, 0, REC_PARAM_SIZE, recParamData);
jobjectArray jClientEffects;
@@ -580,10 +583,9 @@
env->CallStaticVoidMethod(clazz,
gAudioPolicyEventHandlerMethods.postRecordConfigEventFromNative,
- event, (jint) clientInfo->riid, (jint) clientInfo->uid,
- clientInfo->session, clientInfo->source, clientInfo->port_id,
- clientInfo->silenced, recParamArray, jClientEffects, jEffects,
- source);
+ event, clientInfo->riid, clientInfo->uid, clientInfo->session,
+ clientInfo->source, clientInfo->port_id, clientInfo->silenced,
+ recParamArray, jClientEffects, jEffects, source);
env->DeleteLocalRef(clazz);
env->DeleteLocalRef(recParamArray);
env->DeleteLocalRef(jClientEffects);
@@ -626,11 +628,9 @@
if (Parcel *parcel = parcelForJavaObject(env, jParcel); parcel != nullptr) {
android::media::audio::common::AudioPort port{};
if (status_t statusOfParcel = port.readFromParcel(parcel); statusOfParcel == OK) {
- status = check_AudioSystem_Command(
- AudioSystem::setDeviceConnectionState(static_cast<audio_policy_dev_state_t>(
- state),
- port,
- static_cast<audio_format_t>(codec)));
+ status = check_AudioSystem_Command(
+ AudioSystem::setDeviceConnectionState(static_cast<audio_policy_dev_state_t>(state),
+ port, static_cast<audio_format_t>(codec)));
} else {
ALOGE("Failed to read from parcel: %s", statusToString(statusOfParcel).c_str());
status = kAudioStatusError;
@@ -639,17 +639,17 @@
ALOGE("Failed to retrieve the native parcel from Java parcel");
status = kAudioStatusError;
}
- return (jint) status;
+ return status;
}
static jint
android_media_AudioSystem_getDeviceConnectionState(JNIEnv *env, jobject thiz, jint device, jstring device_address)
{
const char *c_address = env->GetStringUTFChars(device_address, NULL);
- int state = static_cast <int>(AudioSystem::getDeviceConnectionState(static_cast <audio_devices_t>(device),
- c_address));
+ int state = static_cast<int>(
+ AudioSystem::getDeviceConnectionState(static_cast<audio_devices_t>(device), c_address));
env->ReleaseStringUTFChars(device_address, c_address);
- return (jint) state;
+ return state;
}
static jint
@@ -658,38 +658,41 @@
{
const char *c_address = env->GetStringUTFChars(device_address, NULL);
const char *c_name = env->GetStringUTFChars(device_name, NULL);
- int status = check_AudioSystem_Command(AudioSystem::handleDeviceConfigChange(static_cast <audio_devices_t>(device),
- c_address, c_name, static_cast <audio_format_t>(codec)));
+ int status = check_AudioSystem_Command(
+ AudioSystem::handleDeviceConfigChange(static_cast<audio_devices_t>(device), c_address,
+ c_name, static_cast<audio_format_t>(codec)));
env->ReleaseStringUTFChars(device_address, c_address);
env->ReleaseStringUTFChars(device_name, c_name);
- return (jint) status;
+ return status;
}
static jint android_media_AudioSystem_setPhoneState(JNIEnv *env, jobject thiz, jint state,
jint uid) {
- return (jint)check_AudioSystem_Command(
- AudioSystem::setPhoneState((audio_mode_t)state, (uid_t)uid));
+ return check_AudioSystem_Command(
+ AudioSystem::setPhoneState(static_cast<audio_mode_t>(state), static_cast<uid_t>(uid)));
}
static jint
android_media_AudioSystem_setForceUse(JNIEnv *env, jobject thiz, jint usage, jint config)
{
- return (jint) check_AudioSystem_Command(AudioSystem::setForceUse(static_cast <audio_policy_force_use_t>(usage),
- static_cast <audio_policy_forced_cfg_t>(config)));
+ return check_AudioSystem_Command(
+ AudioSystem::setForceUse(static_cast<audio_policy_force_use_t>(usage),
+ static_cast<audio_policy_forced_cfg_t>(config)));
}
static jint
android_media_AudioSystem_getForceUse(JNIEnv *env, jobject thiz, jint usage)
{
- return static_cast <jint>(AudioSystem::getForceUse(static_cast <audio_policy_force_use_t>(usage)));
+ return static_cast<jint>(
+ AudioSystem::getForceUse(static_cast<audio_policy_force_use_t>(usage)));
}
static jint
android_media_AudioSystem_initStreamVolume(JNIEnv *env, jobject thiz, jint stream, jint indexMin, jint indexMax)
{
- return (jint) check_AudioSystem_Command(AudioSystem::initStreamVolume(static_cast <audio_stream_type_t>(stream),
- indexMin,
- indexMax));
+ return check_AudioSystem_Command(
+ AudioSystem::initStreamVolume(static_cast<audio_stream_type_t>(stream), indexMin,
+ indexMax));
}
static jint
@@ -699,10 +702,9 @@
jint index,
jint device)
{
- return (jint) check_AudioSystem_Command(
- AudioSystem::setStreamVolumeIndex(static_cast <audio_stream_type_t>(stream),
- index,
- (audio_devices_t)device));
+ return check_AudioSystem_Command(
+ AudioSystem::setStreamVolumeIndex(static_cast<audio_stream_type_t>(stream), index,
+ static_cast<audio_devices_t>(device)));
}
static jint
@@ -712,13 +714,11 @@
jint device)
{
int index;
- if (AudioSystem::getStreamVolumeIndex(static_cast <audio_stream_type_t>(stream),
- &index,
- (audio_devices_t)device)
- != NO_ERROR) {
+ if (AudioSystem::getStreamVolumeIndex(static_cast<audio_stream_type_t>(stream), &index,
+ static_cast<audio_devices_t>(device)) != NO_ERROR) {
index = -1;
}
- return (jint) index;
+ return index;
}
static jint
@@ -731,11 +731,12 @@
// read the AudioAttributes values
JNIAudioAttributeHelper::UniqueAaPtr paa = JNIAudioAttributeHelper::makeUnique();
jint jStatus = JNIAudioAttributeHelper::nativeFromJava(env, jaa, paa.get());
- if (jStatus != (jint)AUDIO_JAVA_SUCCESS) {
+ if (jStatus != AUDIO_JAVA_SUCCESS) {
return jStatus;
}
- return (jint) check_AudioSystem_Command(
- AudioSystem::setVolumeIndexForAttributes(*(paa.get()), index, (audio_devices_t)device));
+ return check_AudioSystem_Command(
+ AudioSystem::setVolumeIndexForAttributes(*(paa.get()), index,
+ static_cast<audio_devices_t>(device)));
}
static jint
@@ -747,15 +748,16 @@
// read the AudioAttributes values
JNIAudioAttributeHelper::UniqueAaPtr paa = JNIAudioAttributeHelper::makeUnique();
jint jStatus = JNIAudioAttributeHelper::nativeFromJava(env, jaa, paa.get());
- if (jStatus != (jint)AUDIO_JAVA_SUCCESS) {
+ if (jStatus != AUDIO_JAVA_SUCCESS) {
return jStatus;
}
int index;
- if (AudioSystem::getVolumeIndexForAttributes(*(paa.get()), index, (audio_devices_t)device)
- != NO_ERROR) {
+ if (AudioSystem::getVolumeIndexForAttributes(*(paa.get()), index,
+ static_cast<audio_devices_t>(device)) !=
+ NO_ERROR) {
index = -1;
}
- return (jint) index;
+ return index;
}
static jint
@@ -766,7 +768,7 @@
// read the AudioAttributes values
JNIAudioAttributeHelper::UniqueAaPtr paa = JNIAudioAttributeHelper::makeUnique();
jint jStatus = JNIAudioAttributeHelper::nativeFromJava(env, jaa, paa.get());
- if (jStatus != (jint)AUDIO_JAVA_SUCCESS) {
+ if (jStatus != AUDIO_JAVA_SUCCESS) {
return jStatus;
}
int index;
@@ -774,7 +776,7 @@
!= NO_ERROR) {
index = -1;
}
- return (jint) index;
+ return index;
}
static jint
@@ -785,7 +787,7 @@
// read the AudioAttributes values
JNIAudioAttributeHelper::UniqueAaPtr paa = JNIAudioAttributeHelper::makeUnique();
jint jStatus = JNIAudioAttributeHelper::nativeFromJava(env, jaa, paa.get());
- if (jStatus != (jint)AUDIO_JAVA_SUCCESS) {
+ if (jStatus != AUDIO_JAVA_SUCCESS) {
return jStatus;
}
int index;
@@ -793,13 +795,13 @@
!= NO_ERROR) {
index = -1;
}
- return (jint) index;
+ return index;
}
static jint
android_media_AudioSystem_setMasterVolume(JNIEnv *env, jobject thiz, jfloat value)
{
- return (jint) check_AudioSystem_Command(AudioSystem::setMasterVolume(value));
+ return check_AudioSystem_Command(AudioSystem::setMasterVolume(value));
}
static jfloat
@@ -815,7 +817,7 @@
static jint
android_media_AudioSystem_setMasterMute(JNIEnv *env, jobject thiz, jboolean mute)
{
- return (jint) check_AudioSystem_Command(AudioSystem::setMasterMute(mute));
+ return check_AudioSystem_Command(AudioSystem::setMasterMute(mute));
}
static jboolean
@@ -831,7 +833,7 @@
static jint
android_media_AudioSystem_setMasterMono(JNIEnv *env, jobject thiz, jboolean mono)
{
- return (jint) check_AudioSystem_Command(AudioSystem::setMasterMono(mono));
+ return check_AudioSystem_Command(AudioSystem::setMasterMono(mono));
}
static jboolean
@@ -847,7 +849,7 @@
static jint
android_media_AudioSystem_setMasterBalance(JNIEnv *env, jobject thiz, jfloat balance)
{
- return (jint) check_AudioSystem_Command(AudioSystem::setMasterBalance(balance));
+ return check_AudioSystem_Command(AudioSystem::setMasterBalance(balance));
}
static jfloat
@@ -865,37 +867,37 @@
static jint
android_media_AudioSystem_getPrimaryOutputSamplingRate(JNIEnv *env, jobject clazz)
{
- return (jint) AudioSystem::getPrimaryOutputSamplingRate();
+ return AudioSystem::getPrimaryOutputSamplingRate();
}
static jint
android_media_AudioSystem_getPrimaryOutputFrameCount(JNIEnv *env, jobject clazz)
{
- return (jint) AudioSystem::getPrimaryOutputFrameCount();
+ return AudioSystem::getPrimaryOutputFrameCount();
}
static jint
android_media_AudioSystem_getOutputLatency(JNIEnv *env, jobject clazz, jint stream)
{
uint32_t afLatency;
- if (AudioSystem::getOutputLatency(&afLatency, static_cast <audio_stream_type_t>(stream))
- != NO_ERROR) {
+ if (AudioSystem::getOutputLatency(&afLatency, static_cast<audio_stream_type_t>(stream)) !=
+ NO_ERROR) {
afLatency = -1;
}
- return (jint) afLatency;
+ return afLatency;
}
static jint
android_media_AudioSystem_setLowRamDevice(
JNIEnv *env, jobject clazz, jboolean isLowRamDevice, jlong totalMemory)
{
- return (jint) AudioSystem::setLowRamDevice((bool) isLowRamDevice, (int64_t) totalMemory);
+ return AudioSystem::setLowRamDevice(isLowRamDevice, totalMemory);
}
static jint
android_media_AudioSystem_checkAudioFlinger(JNIEnv *env, jobject clazz)
{
- return (jint) check_AudioSystem_Command(AudioSystem::checkAudioFlinger());
+ return check_AudioSystem_Command(AudioSystem::checkAudioFlinger());
}
static void android_media_AudioSystem_setAudioFlingerBinder(JNIEnv *env, jobject clazz,
@@ -909,8 +911,8 @@
bool useInMask)
{
nAudioGainConfig->index = env->GetIntField(jAudioGainConfig, gAudioGainConfigFields.mIndex);
- nAudioGainConfig->mode =
- (audio_gain_mode_t)env->GetIntField(jAudioGainConfig, gAudioGainConfigFields.mMode);
+ nAudioGainConfig->mode = static_cast<audio_gain_mode_t>(
+ env->GetIntField(jAudioGainConfig, gAudioGainConfigFields.mMode));
ALOGV("convertAudioGainConfigToNative got gain index %d", nAudioGainConfig->index);
jint jMask = env->GetIntField(jAudioGainConfig, gAudioGainConfigFields.mChannelMask);
audio_channel_mask_t nMask;
@@ -924,8 +926,8 @@
nAudioGainConfig->channel_mask = nMask;
nAudioGainConfig->ramp_duration_ms = env->GetIntField(jAudioGainConfig,
gAudioGainConfigFields.mRampDurationMs);
- jintArray jValues = (jintArray)env->GetObjectField(jAudioGainConfig,
- gAudioGainConfigFields.mValues);
+ jintArray jValues = static_cast<jintArray>(
+ env->GetObjectField(jAudioGainConfig, gAudioGainConfigFields.mValues));
int *nValues = env->GetIntArrayElements(jValues, NULL);
size_t size = env->GetArrayLength(jValues);
memcpy(nAudioGainConfig->values, nValues, size * sizeof(int));
@@ -940,8 +942,8 @@
jobject jAudioPort = env->GetObjectField(jAudioPortConfig, gAudioPortConfigFields.mPort);
jobject jHandle = env->GetObjectField(jAudioPort, gAudioPortFields.mHandle);
nAudioPortConfig->id = env->GetIntField(jHandle, gAudioHandleFields.mId);
- nAudioPortConfig->role = (audio_port_role_t)env->GetIntField(jAudioPort,
- gAudioPortFields.mRole);
+ nAudioPortConfig->role =
+ static_cast<audio_port_role_t>(env->GetIntField(jAudioPort, gAudioPortFields.mRole));
if (env->IsInstanceOf(jAudioPort, gAudioDevicePortClass)) {
nAudioPortConfig->type = AUDIO_PORT_TYPE_DEVICE;
} else if (env->IsInstanceOf(jAudioPort, gAudioMixPortClass)) {
@@ -949,7 +951,7 @@
} else {
env->DeleteLocalRef(jAudioPort);
env->DeleteLocalRef(jHandle);
- return (jint)AUDIO_JAVA_ERROR;
+ return AUDIO_JAVA_ERROR;
}
ALOGV("convertAudioPortConfigToNative handle %d role %d type %d",
nAudioPortConfig->id, nAudioPortConfig->role, nAudioPortConfig->type);
@@ -1004,7 +1006,7 @@
}
env->DeleteLocalRef(jAudioPort);
env->DeleteLocalRef(jHandle);
- return (jint)AUDIO_JAVA_SUCCESS;
+ return AUDIO_JAVA_SUCCESS;
}
/**
@@ -1025,15 +1027,15 @@
}
// Supports AUDIO_PORT_TYPE_DEVICE only
if (nAudioPortConfig->type != AUDIO_PORT_TYPE_DEVICE) {
- return (jint)AUDIO_JAVA_BAD_VALUE;
+ return AUDIO_JAVA_BAD_VALUE;
}
jobject jAudioDevicePort = env->GetObjectField(jAudioPortConfig,
gAudioPortConfigFields.mPort);
- nAudioPortConfig->ext.device.type =
- (audio_devices_t)env->GetIntField(jAudioDevicePort, gAudioPortFields.mType);
- jstring jDeviceAddress = (jstring)env->GetObjectField(jAudioDevicePort,
- gAudioPortFields.mAddress);
+ nAudioPortConfig->ext.device.type = static_cast<audio_devices_t>(
+ env->GetIntField(jAudioDevicePort, gAudioPortFields.mType));
+ jstring jDeviceAddress =
+ static_cast<jstring>(env->GetObjectField(jAudioDevicePort, gAudioPortFields.mAddress));
const char *nDeviceAddress = env->GetStringUTFChars(jDeviceAddress, NULL);
strncpy(nAudioPortConfig->ext.device.address,
nDeviceAddress, AUDIO_DEVICE_MAX_ADDRESS_LEN - 1);
@@ -1043,45 +1045,41 @@
return jStatus;
}
-static jint convertAudioPortConfigFromNative(JNIEnv *env,
- jobject jAudioPort,
- jobject *jAudioPortConfig,
- const struct audio_port_config *nAudioPortConfig)
-{
- jint jStatus = AUDIO_JAVA_SUCCESS;
- jobject jAudioGainConfig = NULL;
- jobject jAudioGain = NULL;
+static jint convertAudioPortConfigFromNative(JNIEnv *env, ScopedLocalRef<jobject> *jAudioPort,
+ ScopedLocalRef<jobject> *jAudioPortConfig,
+ const struct audio_port_config *nAudioPortConfig) {
jintArray jGainValues;
bool audioportCreated = false;
ALOGV("convertAudioPortConfigFromNative jAudioPort %p", jAudioPort);
- if (jAudioPort == NULL) {
- jobject jHandle = env->NewObject(gAudioHandleClass, gAudioHandleCstor,
- nAudioPortConfig->id);
+ if (*jAudioPort == nullptr) {
+ ScopedLocalRef<jobject> jHandle(env,
+ env->NewObject(gAudioHandleClass, gAudioHandleCstor,
+ nAudioPortConfig->id));
ALOGV("convertAudioPortConfigFromNative handle %d is a %s", nAudioPortConfig->id,
nAudioPortConfig->type == AUDIO_PORT_TYPE_DEVICE ? "device" : "mix");
if (jHandle == NULL) {
- return (jint)AUDIO_JAVA_ERROR;
+ return AUDIO_JAVA_ERROR;
}
// create placeholder port and port config objects with just the correct handle
// and configuration data. The actual AudioPortConfig objects will be
// constructed by java code with correct class type (device, mix etc...)
// and reference to AudioPort instance in this client
- jAudioPort = env->NewObject(gAudioPortClass, gAudioPortCstor,
- jHandle, // handle
- 0, // role
- NULL, // name
- NULL, // samplingRates
- NULL, // channelMasks
- NULL, // channelIndexMasks
- NULL, // formats
- NULL); // gains
- env->DeleteLocalRef(jHandle);
- if (jAudioPort == NULL) {
- return (jint)AUDIO_JAVA_ERROR;
+ jAudioPort->reset(env->NewObject(gAudioPortClass, gAudioPortCstor,
+ jHandle.get(), // handle
+ 0, // role
+ nullptr, // name
+ nullptr, // samplingRates
+ nullptr, // channelMasks
+ nullptr, // channelIndexMasks
+ nullptr, // formats
+ nullptr)); // gains
+
+ if (*jAudioPort == nullptr) {
+ return AUDIO_JAVA_ERROR;
}
ALOGV("convertAudioPortConfigFromNative jAudioPort created for handle %d",
nAudioPortConfig->id);
@@ -1089,6 +1087,9 @@
audioportCreated = true;
}
+ ScopedLocalRef<jobject> jAudioGainConfig(env, nullptr);
+ ScopedLocalRef<jobject> jAudioGain(env, nullptr);
+
bool useInMask = audio_port_config_has_input_direction(nAudioPortConfig);
audio_channel_mask_t nMask;
@@ -1102,36 +1103,28 @@
gainIndex, nAudioPortConfig->gain.mode);
if (audioportCreated) {
ALOGV("convertAudioPortConfigFromNative creating gain");
- jAudioGain = env->NewObject(gAudioGainClass, gAudioGainCstor,
- gainIndex,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0);
+ jAudioGain.reset(env->NewObject(gAudioGainClass, gAudioGainCstor, gainIndex, 0 /*mode*/,
+ 0 /*channelMask*/, 0 /*minValue*/, 0 /*maxValue*/,
+ 0 /*defaultValue*/, 0 /*stepValue*/,
+ 0 /*rampDurationMinMs*/, 0 /*rampDurationMaxMs*/));
if (jAudioGain == NULL) {
ALOGV("convertAudioPortConfigFromNative creating gain FAILED");
- jStatus = (jint)AUDIO_JAVA_ERROR;
- goto exit;
+ return AUDIO_JAVA_ERROR;
}
} else {
ALOGV("convertAudioPortConfigFromNative reading gain from port");
- jobjectArray jGains = (jobjectArray)env->GetObjectField(jAudioPort,
- gAudioPortFields.mGains);
+ ScopedLocalRef<jobjectArray>
+ jGains(env,
+ static_cast<jobjectArray>(env->GetObjectField(jAudioPort->get(),
+ gAudioPortFields.mGains)));
if (jGains == NULL) {
ALOGV("convertAudioPortConfigFromNative could not get gains from port");
- jStatus = (jint)AUDIO_JAVA_ERROR;
- goto exit;
+ return AUDIO_JAVA_ERROR;
}
- jAudioGain = env->GetObjectArrayElement(jGains, gainIndex);
- env->DeleteLocalRef(jGains);
+ jAudioGain.reset(env->GetObjectArrayElement(jGains.get(), gainIndex));
if (jAudioGain == NULL) {
ALOGV("convertAudioPortConfigFromNative could not get gain at index %d", gainIndex);
- jStatus = (jint)AUDIO_JAVA_ERROR;
- goto exit;
+ return AUDIO_JAVA_ERROR;
}
}
int numValues;
@@ -1143,8 +1136,7 @@
jGainValues = env->NewIntArray(numValues);
if (jGainValues == NULL) {
ALOGV("convertAudioPortConfigFromNative could not create gain values %d", numValues);
- jStatus = (jint)AUDIO_JAVA_ERROR;
- goto exit;
+ return AUDIO_JAVA_ERROR;
}
env->SetIntArrayRegion(jGainValues, 0, numValues,
nAudioPortConfig->gain.values);
@@ -1158,19 +1150,14 @@
ALOGV("convertAudioPortConfigFromNative OUT mask java %x native %x", jMask, nMask);
}
- jAudioGainConfig = env->NewObject(gAudioGainConfigClass,
- gAudioGainConfigCstor,
- gainIndex,
- jAudioGain,
- nAudioPortConfig->gain.mode,
- jMask,
- jGainValues,
- nAudioPortConfig->gain.ramp_duration_ms);
+ jAudioGainConfig.reset(env->NewObject(gAudioGainConfigClass, gAudioGainConfigCstor,
+ gainIndex, jAudioGain.get(),
+ nAudioPortConfig->gain.mode, jMask, jGainValues,
+ nAudioPortConfig->gain.ramp_duration_ms));
env->DeleteLocalRef(jGainValues);
if (jAudioGainConfig == NULL) {
ALOGV("convertAudioPortConfigFromNative could not create gain config");
- jStatus = (jint)AUDIO_JAVA_ERROR;
- goto exit;
+ return AUDIO_JAVA_ERROR;
}
}
jclass clazz;
@@ -1180,17 +1167,16 @@
methodID = gAudioPortConfigCstor;
ALOGV("convertAudioPortConfigFromNative building a generic port config");
} else {
- if (env->IsInstanceOf(jAudioPort, gAudioDevicePortClass)) {
+ if (env->IsInstanceOf(jAudioPort->get(), gAudioDevicePortClass)) {
clazz = gAudioDevicePortConfigClass;
methodID = gAudioDevicePortConfigCstor;
ALOGV("convertAudioPortConfigFromNative building a device config");
- } else if (env->IsInstanceOf(jAudioPort, gAudioMixPortClass)) {
+ } else if (env->IsInstanceOf(jAudioPort->get(), gAudioMixPortClass)) {
clazz = gAudioMixPortConfigClass;
methodID = gAudioMixPortConfigCstor;
ALOGV("convertAudioPortConfigFromNative building a mix config");
} else {
- jStatus = (jint)AUDIO_JAVA_ERROR;
- goto exit;
+ return AUDIO_JAVA_ERROR;
}
}
nMask = (nAudioPortConfig->config_mask & AUDIO_PORT_CONFIG_CHANNEL_MASK)
@@ -1204,8 +1190,8 @@
ALOGV("convertAudioPortConfigFromNative OUT mask java %x native %x", jMask, nMask);
}
- *jAudioPortConfig =
- env->NewObject(clazz, methodID, jAudioPort,
+ jAudioPortConfig->reset(
+ env->NewObject(clazz, methodID, jAudioPort->get(),
(nAudioPortConfig->config_mask & AUDIO_PORT_CONFIG_SAMPLE_RATE)
? nAudioPortConfig->sample_rate
: AUDIO_CONFIG_BASE_INITIALIZER.sample_rate,
@@ -1214,31 +1200,14 @@
(nAudioPortConfig->config_mask & AUDIO_PORT_CONFIG_FORMAT)
? nAudioPortConfig->format
: AUDIO_CONFIG_BASE_INITIALIZER.format),
- jAudioGainConfig);
+ jAudioGainConfig.get()));
if (*jAudioPortConfig == NULL) {
ALOGV("convertAudioPortConfigFromNative could not create new port config");
- jStatus = (jint)AUDIO_JAVA_ERROR;
+ return AUDIO_JAVA_ERROR;
} else {
ALOGV("convertAudioPortConfigFromNative OK");
}
-
-exit:
- if (audioportCreated) {
- env->DeleteLocalRef(jAudioPort);
- if (jAudioGain != NULL) {
- env->DeleteLocalRef(jAudioGain);
- }
- }
- if (jAudioGainConfig != NULL) {
- env->DeleteLocalRef(jAudioGainConfig);
- }
- return jStatus;
-}
-
-// TODO: pull out to separate file
-template <typename T, size_t N>
-static constexpr size_t array_size(const T (&)[N]) {
- return N;
+ return AUDIO_JAVA_SUCCESS;
}
static jintArray convertEncapsulationInfoFromNative(JNIEnv *env, uint32_t encapsulationInfo) {
@@ -1252,7 +1221,8 @@
}
}
jintArray result = env->NewIntArray(encapsulation.size());
- env->SetIntArrayRegion(result, 0, encapsulation.size(), (jint *)encapsulation.data());
+ env->SetIntArrayRegion(result, 0, encapsulation.size(),
+ reinterpret_cast<jint *>(encapsulation.data()));
return result;
}
@@ -1260,8 +1230,8 @@
std::stringstream &ss) {
ss << " num_audio_profiles " << nAudioPort->num_audio_profiles << " num_gains "
<< nAudioPort->num_gains;
- if (nAudioPort->num_audio_profiles > array_size(nAudioPort->audio_profiles) ||
- nAudioPort->num_gains > array_size(nAudioPort->gains)) {
+ if (nAudioPort->num_audio_profiles > std::size(nAudioPort->audio_profiles) ||
+ nAudioPort->num_gains > std::size(nAudioPort->gains)) {
return true;
}
for (size_t i = 0; i < nAudioPort->num_audio_profiles; ++i) {
@@ -1269,16 +1239,16 @@
<< " num_sample_rates " << nAudioPort->audio_profiles[i].num_sample_rates
<< " num_channel_masks " << nAudioPort->audio_profiles[i].num_channel_masks;
if (nAudioPort->audio_profiles[i].num_sample_rates >
- array_size(nAudioPort->audio_profiles[i].sample_rates) ||
+ std::size(nAudioPort->audio_profiles[i].sample_rates) ||
nAudioPort->audio_profiles[i].num_channel_masks >
- array_size(nAudioPort->audio_profiles[i].channel_masks)) {
+ std::size(nAudioPort->audio_profiles[i].channel_masks)) {
return true;
}
}
return false;
}
-static jint convertAudioProfileFromNative(JNIEnv *env, jobject *jAudioProfile,
+static jint convertAudioProfileFromNative(JNIEnv *env, ScopedLocalRef<jobject> *jAudioProfile,
const audio_profile *nAudioProfile, bool useInMask) {
size_t numPositionMasks = 0;
size_t numIndexMasks = 0;
@@ -1309,7 +1279,8 @@
if (nAudioProfile->num_sample_rates) {
env->SetIntArrayRegion(jSamplingRates.get(), 0 /*start*/, nAudioProfile->num_sample_rates,
- (jint *)nAudioProfile->sample_rates);
+ const_cast<jint *>(reinterpret_cast<const jint *>(
+ nAudioProfile->sample_rates)));
}
// put the masks in the output arrays
@@ -1331,10 +1302,9 @@
ALOGW("Unknown encapsulation type for JAVA API: %u", nAudioProfile->encapsulation_type);
}
- *jAudioProfile = env->NewObject(gAudioProfileClass, gAudioProfileCstor, audioFormat,
- jSamplingRates.get(), jChannelMasks.get(),
- jChannelIndexMasks.get(), encapsulationType);
-
+ jAudioProfile->reset(env->NewObject(gAudioProfileClass, gAudioProfileCstor, audioFormat,
+ jSamplingRates.get(), jChannelMasks.get(),
+ jChannelIndexMasks.get(), encapsulationType));
if (*jAudioProfile == nullptr) {
return AUDIO_JAVA_ERROR;
}
@@ -1342,18 +1312,8 @@
return AUDIO_JAVA_SUCCESS;
}
-static jint convertAudioPortFromNative(JNIEnv *env, jobject *jAudioPort,
+static jint convertAudioPortFromNative(JNIEnv *env, ScopedLocalRef<jobject> *jAudioPort,
const struct audio_port_v7 *nAudioPort) {
- jint jStatus = (jint)AUDIO_JAVA_SUCCESS;
- jintArray jEncapsulationModes = NULL;
- jintArray jEncapsulationMetadataTypes = NULL;
- jobjectArray jGains = NULL;
- jobject jHandle = NULL;
- jobject jAudioPortConfig = NULL;
- jstring jDeviceName = NULL;
- jobject jAudioProfiles = NULL;
- jobject jAudioDescriptors = nullptr;
- ScopedLocalRef<jobject> jPcmFloatProfileFromExtendedInteger(env, nullptr);
bool hasFloat = false;
bool useInMask;
@@ -1377,30 +1337,30 @@
} else {
ALOGE("%s", s.c_str());
}
- jStatus = (jint)AUDIO_JAVA_ERROR;
- goto exit;
+ return AUDIO_JAVA_ERROR;
}
useInMask = audio_has_input_direction(nAudioPort->type, nAudioPort->role);
- jAudioProfiles = env->NewObject(gArrayListClass, gArrayListMethods.cstor);
+ ScopedLocalRef<jobject> jAudioProfiles(env,
+ env->NewObject(gArrayListClass,
+ gArrayListMethods.cstor));
if (jAudioProfiles == nullptr) {
- jStatus = (jint)AUDIO_JAVA_ERROR;
- goto exit;
+ return AUDIO_JAVA_ERROR;
}
+ ScopedLocalRef<jobject> jPcmFloatProfileFromExtendedInteger(env, nullptr);
for (size_t i = 0; i < nAudioPort->num_audio_profiles; ++i) {
- jobject jAudioProfile = nullptr;
- jStatus = convertAudioProfileFromNative(env, &jAudioProfile, &nAudioPort->audio_profiles[i],
- useInMask);
+ ScopedLocalRef<jobject> jAudioProfile(env);
+ jint jStatus = convertAudioProfileFromNative(env, &jAudioProfile,
+ &nAudioPort->audio_profiles[i], useInMask);
if (jStatus == AUDIO_JAVA_BAD_VALUE) {
// skipping Java layer unsupported audio formats
continue;
}
if (jStatus != NO_ERROR) {
- jStatus = (jint)AUDIO_JAVA_ERROR;
- goto exit;
+ return AUDIO_JAVA_ERROR;
}
- env->CallBooleanMethod(jAudioProfiles, gArrayListMethods.add, jAudioProfile);
+ env->CallBooleanMethod(jAudioProfiles.get(), gArrayListMethods.add, jAudioProfile.get());
if (nAudioPort->audio_profiles[i].format == AUDIO_FORMAT_PCM_FLOAT) {
hasFloat = true;
@@ -1409,21 +1369,23 @@
audio_bytes_per_sample(nAudioPort->audio_profiles[i].format) > 2) {
ScopedLocalRef<jintArray>
jSamplingRates(env,
- (jintArray)
- env->GetObjectField(jAudioProfile,
- gAudioProfileFields.mSamplingRates));
+ static_cast<jintArray>(
+ env->GetObjectField(jAudioProfile.get(),
+ gAudioProfileFields
+ .mSamplingRates)));
ScopedLocalRef<jintArray>
jChannelMasks(env,
- (jintArray)
- env->GetObjectField(jAudioProfile,
- gAudioProfileFields.mChannelMasks));
+ static_cast<jintArray>(
+ env->GetObjectField(jAudioProfile.get(),
+ gAudioProfileFields.mChannelMasks)));
ScopedLocalRef<jintArray>
jChannelIndexMasks(env,
- (jintArray)env->GetObjectField(jAudioProfile,
- gAudioProfileFields
- .mChannelIndexMasks));
+ static_cast<jintArray>(
+ env->GetObjectField(jAudioProfile.get(),
+ gAudioProfileFields
+ .mChannelIndexMasks)));
int encapsulationType =
- env->GetIntField(jAudioProfile, gAudioProfileFields.mEncapsulationType);
+ env->GetIntField(jAudioProfile.get(), gAudioProfileFields.mEncapsulationType);
jPcmFloatProfileFromExtendedInteger.reset(
env->NewObject(gAudioProfileClass, gAudioProfileCstor,
@@ -1431,24 +1393,21 @@
jSamplingRates.get(), jChannelMasks.get(),
jChannelIndexMasks.get(), encapsulationType));
}
-
- if (jAudioProfile != nullptr) {
- env->DeleteLocalRef(jAudioProfile);
- }
}
if (!hasFloat && jPcmFloatProfileFromExtendedInteger.get() != nullptr) {
// R and earlier compatibility - add ENCODING_PCM_FLOAT to the end
// (replacing the zero pad). This ensures pre-S apps that look
// for ENCODING_PCM_FLOAT continue to see that encoding if the device supports
// extended precision integers.
- env->CallBooleanMethod(jAudioProfiles, gArrayListMethods.add,
+ env->CallBooleanMethod(jAudioProfiles.get(), gArrayListMethods.add,
jPcmFloatProfileFromExtendedInteger.get());
}
- jAudioDescriptors = env->NewObject(gArrayListClass, gArrayListMethods.cstor);
+ ScopedLocalRef<jobject> jAudioDescriptors(env,
+ env->NewObject(gArrayListClass,
+ gArrayListMethods.cstor));
if (jAudioDescriptors == nullptr) {
- jStatus = (jint)AUDIO_JAVA_ERROR;
- goto exit;
+ return AUDIO_JAVA_ERROR;
}
for (size_t i = 0; i < nAudioPort->num_extra_audio_descriptors; ++i) {
const auto &extraAudioDescriptor = nAudioPort->extra_audio_descriptors[i];
@@ -1478,15 +1437,16 @@
env->NewObject(gAudioDescriptorClass, gAudioDescriptorCstor,
standard, encapsulationType,
jDescriptor.get()));
- env->CallBooleanMethod(jAudioDescriptors, gArrayListMethods.add, jAudioDescriptor.get());
+ env->CallBooleanMethod(jAudioDescriptors.get(), gArrayListMethods.add,
+ jAudioDescriptor.get());
}
// gains
- jGains = env->NewObjectArray(nAudioPort->num_gains,
- gAudioGainClass, NULL);
- if (jGains == NULL) {
- jStatus = (jint)AUDIO_JAVA_ERROR;
- goto exit;
+ ScopedLocalRef<jobjectArray> jGains(env,
+ env->NewObjectArray(nAudioPort->num_gains, gAudioGainClass,
+ nullptr));
+ if (jGains == nullptr) {
+ return AUDIO_JAVA_ERROR;
}
for (size_t j = 0; j < nAudioPort->num_gains; j++) {
@@ -1511,88 +1471,71 @@
nAudioPort->gains[j].min_ramp_ms,
nAudioPort->gains[j].max_ramp_ms);
if (jGain == NULL) {
- jStatus = (jint)AUDIO_JAVA_ERROR;
- goto exit;
+ return AUDIO_JAVA_ERROR;
}
- env->SetObjectArrayElement(jGains, j, jGain);
+ env->SetObjectArrayElement(jGains.get(), j, jGain);
env->DeleteLocalRef(jGain);
}
- jHandle = env->NewObject(gAudioHandleClass, gAudioHandleCstor,
- nAudioPort->id);
- if (jHandle == NULL) {
- jStatus = (jint)AUDIO_JAVA_ERROR;
- goto exit;
+ ScopedLocalRef<jobject> jHandle(env,
+ env->NewObject(gAudioHandleClass, gAudioHandleCstor,
+ nAudioPort->id));
+ if (jHandle == nullptr) {
+ return AUDIO_JAVA_ERROR;
}
- jDeviceName = env->NewStringUTF(nAudioPort->name);
-
+ ScopedLocalRef<jstring> jDeviceName(env, env->NewStringUTF(nAudioPort->name));
if (nAudioPort->type == AUDIO_PORT_TYPE_DEVICE) {
- ALOGV("convertAudioPortFromNative is a device %08x", nAudioPort->ext.device.type);
- jstring jAddress = env->NewStringUTF(nAudioPort->ext.device.address);
- jEncapsulationModes =
- convertEncapsulationInfoFromNative(env, nAudioPort->ext.device.encapsulation_modes);
- jEncapsulationMetadataTypes =
+ ScopedLocalRef<jintArray> jEncapsulationModes(
+ env,
+ convertEncapsulationInfoFromNative(env,
+ nAudioPort->ext.device.encapsulation_modes));
+ ScopedLocalRef<jintArray> jEncapsulationMetadataTypes(
+ env,
convertEncapsulationInfoFromNative(env,
nAudioPort->ext.device
- .encapsulation_metadata_types);
- *jAudioPort =
- env->NewObject(gAudioDevicePortClass, gAudioDevicePortCstor, jHandle, jDeviceName,
- jAudioProfiles, jGains, nAudioPort->ext.device.type, jAddress,
- jEncapsulationModes, jEncapsulationMetadataTypes, jAudioDescriptors);
- env->DeleteLocalRef(jAddress);
+ .encapsulation_metadata_types));
+ ALOGV("convertAudioPortFromNative is a device %08x", nAudioPort->ext.device.type);
+ ScopedLocalRef<jstring> jAddress(env, env->NewStringUTF(nAudioPort->ext.device.address));
+ jAudioPort->reset(env->NewObject(gAudioDevicePortClass, gAudioDevicePortCstor,
+ jHandle.get(), jDeviceName.get(), jAudioProfiles.get(),
+ jGains.get(), nAudioPort->ext.device.type, jAddress.get(),
+ jEncapsulationModes.get(),
+ jEncapsulationMetadataTypes.get(),
+ jAudioDescriptors.get()));
} else if (nAudioPort->type == AUDIO_PORT_TYPE_MIX) {
ALOGV("convertAudioPortFromNative is a mix");
- *jAudioPort = env->NewObject(gAudioMixPortClass, gAudioMixPortCstor, jHandle,
- nAudioPort->ext.mix.handle, nAudioPort->role, jDeviceName,
- jAudioProfiles, jGains);
+ jAudioPort->reset(env->NewObject(gAudioMixPortClass, gAudioMixPortCstor, jHandle.get(),
+ nAudioPort->ext.mix.handle, nAudioPort->role,
+ jDeviceName.get(), jAudioProfiles.get(), jGains.get()));
} else {
ALOGE("convertAudioPortFromNative unknown nAudioPort type %d", nAudioPort->type);
- jStatus = (jint)AUDIO_JAVA_ERROR;
- goto exit;
+ return AUDIO_JAVA_ERROR;
}
if (*jAudioPort == NULL) {
- jStatus = (jint)AUDIO_JAVA_ERROR;
- goto exit;
+ return AUDIO_JAVA_ERROR;
}
- jStatus = convertAudioPortConfigFromNative(env,
- *jAudioPort,
- &jAudioPortConfig,
+ ScopedLocalRef<jobject> jAudioPortConfig(env, nullptr);
+
+ if (int jStatus = convertAudioPortConfigFromNative(env, jAudioPort, &jAudioPortConfig,
&nAudioPort->active_config);
- if (jStatus != AUDIO_JAVA_SUCCESS) {
- goto exit;
+ jStatus != AUDIO_JAVA_SUCCESS) {
+ return jStatus;
}
- env->SetObjectField(*jAudioPort, gAudioPortFields.mActiveConfig, jAudioPortConfig);
+ env->SetObjectField(jAudioPort->get(), gAudioPortFields.mActiveConfig, jAudioPortConfig.get());
+ return AUDIO_JAVA_SUCCESS;
+}
-exit:
- if (jDeviceName != NULL) {
- env->DeleteLocalRef(jDeviceName);
+static bool setGeneration(JNIEnv *env, jintArray jGeneration, unsigned int generation1) {
+ ScopedIntArrayRW nGeneration(env, jGeneration);
+ if (nGeneration.get() == nullptr) {
+ return false;
+ } else {
+ nGeneration[0] = generation1;
+ return true;
}
- if (jEncapsulationModes != NULL) {
- env->DeleteLocalRef(jEncapsulationModes);
- }
- if (jEncapsulationMetadataTypes != NULL) {
- env->DeleteLocalRef(jEncapsulationMetadataTypes);
- }
- if (jAudioProfiles != NULL) {
- env->DeleteLocalRef(jAudioProfiles);
- }
- if (jGains != NULL) {
- env->DeleteLocalRef(jGains);
- }
- if (jHandle != NULL) {
- env->DeleteLocalRef(jHandle);
- }
- if (jAudioPortConfig != NULL) {
- env->DeleteLocalRef(jAudioPortConfig);
- }
- if (jAudioDescriptors != nullptr) {
- env->DeleteLocalRef(jAudioDescriptors);
- }
-
- return jStatus;
}
static jint
@@ -1603,23 +1546,22 @@
if (jPorts == NULL) {
ALOGE("listAudioPorts NULL AudioPort ArrayList");
- return (jint)AUDIO_JAVA_BAD_VALUE;
+ return AUDIO_JAVA_BAD_VALUE;
}
if (!env->IsInstanceOf(jPorts, gArrayListClass)) {
ALOGE("listAudioPorts not an arraylist");
- return (jint)AUDIO_JAVA_BAD_VALUE;
+ return AUDIO_JAVA_BAD_VALUE;
}
if (jGeneration == NULL || env->GetArrayLength(jGeneration) != 1) {
- return (jint)AUDIO_JAVA_BAD_VALUE;
+ return AUDIO_JAVA_BAD_VALUE;
}
status_t status;
- unsigned int generation1;
+ unsigned int generation1 = 0;
unsigned int generation;
unsigned int numPorts;
- jint *nGeneration;
- struct audio_port_v7 *nPorts = nullptr;
+ std::vector<audio_port_v7> nPorts;
int attempts = MAX_PORT_GENERATION_SYNC_ATTEMPTS;
jint jStatus;
@@ -1638,43 +1580,29 @@
break;
}
if (numPorts == 0) {
- jStatus = (jint)AUDIO_JAVA_SUCCESS;
- goto exit;
+ return setGeneration(env, jGeneration, generation1) ? AUDIO_JAVA_SUCCESS
+ : AUDIO_JAVA_ERROR;
}
- nPorts = (struct audio_port_v7 *)realloc(nPorts, numPorts * sizeof(struct audio_port_v7));
+ nPorts.resize(numPorts);
status = AudioSystem::listAudioPorts(AUDIO_PORT_ROLE_NONE, AUDIO_PORT_TYPE_NONE, &numPorts,
- nPorts, &generation);
+ &nPorts[0], &generation);
ALOGV("listAudioPorts AudioSystem::listAudioPorts numPorts %d generation %d generation1 %d",
numPorts, generation, generation1);
} while (generation1 != generation && status == NO_ERROR);
jStatus = nativeToJavaStatus(status);
- if (jStatus != AUDIO_JAVA_SUCCESS) {
- goto exit;
- }
-
- for (size_t i = 0; i < numPorts; i++) {
- jobject jAudioPort = NULL;
- jStatus = convertAudioPortFromNative(env, &jAudioPort, &nPorts[i]);
- if (jStatus != AUDIO_JAVA_SUCCESS) {
- goto exit;
- }
- env->CallBooleanMethod(jPorts, gArrayListMethods.add, jAudioPort);
- if (jAudioPort != NULL) {
- env->DeleteLocalRef(jAudioPort);
+ if (jStatus == AUDIO_JAVA_SUCCESS) {
+ for (size_t i = 0; i < numPorts; i++) {
+ ScopedLocalRef<jobject> jAudioPort(env, nullptr);
+ jStatus = convertAudioPortFromNative(env, &jAudioPort, &nPorts[i]);
+ if (jStatus != AUDIO_JAVA_SUCCESS) break;
+ env->CallBooleanMethod(jPorts, gArrayListMethods.add, jAudioPort.get());
}
}
-
-exit:
- nGeneration = env->GetIntArrayElements(jGeneration, NULL);
- if (nGeneration == NULL) {
- jStatus = (jint)AUDIO_JAVA_ERROR;
- } else {
- nGeneration[0] = generation1;
- env->ReleaseIntArrayElements(jGeneration, nGeneration, 0);
+ if (!setGeneration(env, jGeneration, generation1)) {
+ jStatus = AUDIO_JAVA_ERROR;
}
- free(nPorts);
return jStatus;
}
@@ -1687,64 +1615,56 @@
ALOGV("createAudioPatch");
if (jPatches == NULL || jSources == NULL || jSinks == NULL) {
- return (jint)AUDIO_JAVA_BAD_VALUE;
+ return AUDIO_JAVA_BAD_VALUE;
}
if (env->GetArrayLength(jPatches) != 1) {
- return (jint)AUDIO_JAVA_BAD_VALUE;
+ return AUDIO_JAVA_BAD_VALUE;
}
jint numSources = env->GetArrayLength(jSources);
if (numSources == 0 || numSources > AUDIO_PATCH_PORTS_MAX) {
- return (jint)AUDIO_JAVA_BAD_VALUE;
+ return AUDIO_JAVA_BAD_VALUE;
}
jint numSinks = env->GetArrayLength(jSinks);
if (numSinks == 0 || numSinks > AUDIO_PATCH_PORTS_MAX) {
- return (jint)AUDIO_JAVA_BAD_VALUE;
+ return AUDIO_JAVA_BAD_VALUE;
}
- audio_patch_handle_t handle = (audio_patch_handle_t)0;
- jobject jPatch = env->GetObjectArrayElement(jPatches, 0);
- jobject jPatchHandle = NULL;
- if (jPatch != NULL) {
- if (!env->IsInstanceOf(jPatch, gAudioPatchClass)) {
- return (jint)AUDIO_JAVA_BAD_VALUE;
+ audio_patch_handle_t handle = static_cast<audio_patch_handle_t>(AUDIO_PATCH_HANDLE_NONE);
+ ScopedLocalRef<jobject> jPatch(env, env->GetObjectArrayElement(jPatches, 0));
+ ScopedLocalRef<jobject> jPatchHandle(env, nullptr);
+ if (jPatch != nullptr) {
+ if (!env->IsInstanceOf(jPatch.get(), gAudioPatchClass)) {
+ return AUDIO_JAVA_BAD_VALUE;
}
- jPatchHandle = env->GetObjectField(jPatch, gAudioPatchFields.mHandle);
- handle = (audio_patch_handle_t)env->GetIntField(jPatchHandle, gAudioHandleFields.mId);
+ jPatchHandle.reset(env->GetObjectField(jPatch.get(), gAudioPatchFields.mHandle));
+ handle = static_cast<audio_patch_handle_t>(
+ env->GetIntField(jPatchHandle.get(), gAudioHandleFields.mId));
}
struct audio_patch nPatch = { .id = handle };
- jobject jSource = NULL;
- jobject jSink = NULL;
-
for (jint i = 0; i < numSources; i++) {
- jSource = env->GetObjectArrayElement(jSources, i);
- if (!env->IsInstanceOf(jSource, gAudioPortConfigClass)) {
- jStatus = (jint)AUDIO_JAVA_BAD_VALUE;
- goto exit;
+ ScopedLocalRef<jobject> jSource(env, env->GetObjectArrayElement(jSources, i));
+ if (!env->IsInstanceOf(jSource.get(), gAudioPortConfigClass)) {
+ return AUDIO_JAVA_BAD_VALUE;
}
- jStatus = convertAudioPortConfigToNative(env, &nPatch.sources[i], jSource, false);
- env->DeleteLocalRef(jSource);
- jSource = NULL;
+ jStatus = convertAudioPortConfigToNative(env, &nPatch.sources[i], jSource.get(), false);
if (jStatus != AUDIO_JAVA_SUCCESS) {
- goto exit;
+ return jStatus;
}
nPatch.num_sources++;
}
for (jint i = 0; i < numSinks; i++) {
- jSink = env->GetObjectArrayElement(jSinks, i);
- if (!env->IsInstanceOf(jSink, gAudioPortConfigClass)) {
- jStatus = (jint)AUDIO_JAVA_BAD_VALUE;
- goto exit;
+ ScopedLocalRef<jobject> jSink(env, env->GetObjectArrayElement(jSinks, i));
+ if (!env->IsInstanceOf(jSink.get(), gAudioPortConfigClass)) {
+ return AUDIO_JAVA_BAD_VALUE;
}
- jStatus = convertAudioPortConfigToNative(env, &nPatch.sinks[i], jSink, false);
- env->DeleteLocalRef(jSink);
- jSink = NULL;
+ jStatus = convertAudioPortConfigToNative(env, &nPatch.sinks[i], jSink.get(), false);
if (jStatus != AUDIO_JAVA_SUCCESS) {
- goto exit;
+ return jStatus;
}
nPatch.num_sinks++;
}
@@ -1755,38 +1675,22 @@
jStatus = nativeToJavaStatus(status);
if (jStatus != AUDIO_JAVA_SUCCESS) {
- goto exit;
+ return jStatus;
}
- if (jPatchHandle == NULL) {
- jPatchHandle = env->NewObject(gAudioHandleClass, gAudioHandleCstor,
- handle);
- if (jPatchHandle == NULL) {
- jStatus = (jint)AUDIO_JAVA_ERROR;
- goto exit;
+ if (jPatchHandle == nullptr) {
+ jPatchHandle.reset(env->NewObject(gAudioHandleClass, gAudioHandleCstor, handle));
+ if (jPatchHandle == nullptr) {
+ return AUDIO_JAVA_ERROR;
}
- jPatch = env->NewObject(gAudioPatchClass, gAudioPatchCstor, jPatchHandle, jSources, jSinks);
- if (jPatch == NULL) {
- jStatus = (jint)AUDIO_JAVA_ERROR;
- goto exit;
+ jPatch.reset(env->NewObject(gAudioPatchClass, gAudioPatchCstor, jPatchHandle.get(),
+ jSources, jSinks));
+ if (jPatch == nullptr) {
+ return AUDIO_JAVA_ERROR;
}
- env->SetObjectArrayElement(jPatches, 0, jPatch);
+ env->SetObjectArrayElement(jPatches, 0, jPatch.get());
} else {
- env->SetIntField(jPatchHandle, gAudioHandleFields.mId, handle);
- }
-
-exit:
- if (jPatchHandle != NULL) {
- env->DeleteLocalRef(jPatchHandle);
- }
- if (jPatch != NULL) {
- env->DeleteLocalRef(jPatch);
- }
- if (jSource != NULL) {
- env->DeleteLocalRef(jSource);
- }
- if (jSink != NULL) {
- env->DeleteLocalRef(jSink);
+ env->SetIntField(jPatchHandle.get(), gAudioHandleFields.mId, handle);
}
return jStatus;
}
@@ -1797,16 +1701,17 @@
{
ALOGV("releaseAudioPatch");
if (jPatch == NULL) {
- return (jint)AUDIO_JAVA_BAD_VALUE;
+ return AUDIO_JAVA_BAD_VALUE;
}
- audio_patch_handle_t handle = (audio_patch_handle_t)0;
+ audio_patch_handle_t handle = static_cast<audio_patch_handle_t>(AUDIO_PATCH_HANDLE_NONE);
jobject jPatchHandle = NULL;
if (!env->IsInstanceOf(jPatch, gAudioPatchClass)) {
- return (jint)AUDIO_JAVA_BAD_VALUE;
+ return AUDIO_JAVA_BAD_VALUE;
}
jPatchHandle = env->GetObjectField(jPatch, gAudioPatchFields.mHandle);
- handle = (audio_patch_handle_t)env->GetIntField(jPatchHandle, gAudioHandleFields.mId);
+ handle = static_cast<audio_patch_handle_t>(
+ env->GetIntField(jPatchHandle, gAudioHandleFields.mId));
env->DeleteLocalRef(jPatchHandle);
ALOGV("AudioSystem::releaseAudioPatch");
@@ -1823,28 +1728,22 @@
ALOGV("listAudioPatches");
if (jPatches == NULL) {
ALOGE("listAudioPatches NULL AudioPatch ArrayList");
- return (jint)AUDIO_JAVA_BAD_VALUE;
+ return AUDIO_JAVA_BAD_VALUE;
}
if (!env->IsInstanceOf(jPatches, gArrayListClass)) {
ALOGE("listAudioPatches not an arraylist");
- return (jint)AUDIO_JAVA_BAD_VALUE;
+ return AUDIO_JAVA_BAD_VALUE;
}
if (jGeneration == NULL || env->GetArrayLength(jGeneration) != 1) {
- return (jint)AUDIO_JAVA_BAD_VALUE;
+ return AUDIO_JAVA_BAD_VALUE;
}
status_t status;
unsigned int generation1;
unsigned int generation;
unsigned int numPatches;
- jint *nGeneration;
- struct audio_patch *nPatches = NULL;
- jobjectArray jSources = NULL;
- jobject jSource = NULL;
- jobjectArray jSinks = NULL;
- jobject jSink = NULL;
- jobject jPatch = NULL;
+ std::vector<audio_patch> nPatches;
int attempts = MAX_PORT_GENERATION_SYNC_ATTEMPTS;
jint jStatus;
@@ -1865,15 +1764,13 @@
break;
}
if (numPatches == 0) {
- jStatus = (jint)AUDIO_JAVA_SUCCESS;
- goto exit;
+ return setGeneration(env, jGeneration, generation1) ? AUDIO_JAVA_SUCCESS
+ : AUDIO_JAVA_ERROR;
}
- nPatches = (struct audio_patch *)realloc(nPatches, numPatches * sizeof(struct audio_patch));
+ nPatches.resize(numPatches);
- status = AudioSystem::listAudioPatches(&numPatches,
- nPatches,
- &generation);
+ status = AudioSystem::listAudioPatches(&numPatches, nPatches.data(), &generation);
ALOGV("listAudioPatches AudioSystem::listAudioPatches numPatches %d generation %d generation1 %d",
numPatches, generation, generation1);
@@ -1881,15 +1778,21 @@
jStatus = nativeToJavaStatus(status);
if (jStatus != AUDIO_JAVA_SUCCESS) {
- goto exit;
+ if (!setGeneration(env, jGeneration, generation1)) {
+ jStatus = AUDIO_JAVA_ERROR;
+ }
+ return jStatus;
}
for (size_t i = 0; i < numPatches; i++) {
+ ScopedLocalRef<jobject> jPatch(env, nullptr);
+ ScopedLocalRef<jobjectArray> jSources(env, nullptr);
+ ScopedLocalRef<jobjectArray> jSinks(env, nullptr);
jobject patchHandle = env->NewObject(gAudioHandleClass, gAudioHandleCstor,
nPatches[i].id);
if (patchHandle == NULL) {
- jStatus = AUDIO_JAVA_ERROR;
- goto exit;
+ setGeneration(env, jGeneration, generation1);
+ return AUDIO_JAVA_ERROR;
}
ALOGV("listAudioPatches patch %zu num_sources %d num_sinks %d",
i, nPatches[i].num_sources, nPatches[i].num_sinks);
@@ -1897,96 +1800,66 @@
env->SetIntField(patchHandle, gAudioHandleFields.mId, nPatches[i].id);
// load sources
- jSources = env->NewObjectArray(nPatches[i].num_sources,
- gAudioPortConfigClass, NULL);
- if (jSources == NULL) {
- jStatus = AUDIO_JAVA_ERROR;
- goto exit;
+ jSources.reset(env->NewObjectArray(nPatches[i].num_sources, gAudioPortConfigClass, NULL));
+ if (jSources == nullptr) {
+ setGeneration(env, jGeneration, generation1);
+ return AUDIO_JAVA_ERROR;
}
for (size_t j = 0; j < nPatches[i].num_sources; j++) {
- jStatus = convertAudioPortConfigFromNative(env,
- NULL,
- &jSource,
- &nPatches[i].sources[j]);
+ ScopedLocalRef<jobject> jSource(env, nullptr);
+ ScopedLocalRef<jobject> jAudioPort(env, nullptr);
+ jStatus = convertAudioPortConfigFromNative(env, &jAudioPort, &jSource,
+ &nPatches[i].sources[j]);
if (jStatus != AUDIO_JAVA_SUCCESS) {
- goto exit;
+ if (!setGeneration(env, jGeneration, generation1)) {
+ jStatus = AUDIO_JAVA_ERROR;
+ }
+ return jStatus;
}
- env->SetObjectArrayElement(jSources, j, jSource);
- env->DeleteLocalRef(jSource);
- jSource = NULL;
+ env->SetObjectArrayElement(jSources.get(), j, jSource.get());
ALOGV("listAudioPatches patch %zu source %zu is a %s handle %d",
i, j,
nPatches[i].sources[j].type == AUDIO_PORT_TYPE_DEVICE ? "device" : "mix",
nPatches[i].sources[j].id);
}
// load sinks
- jSinks = env->NewObjectArray(nPatches[i].num_sinks,
- gAudioPortConfigClass, NULL);
- if (jSinks == NULL) {
- jStatus = AUDIO_JAVA_ERROR;
- goto exit;
+ jSinks.reset(env->NewObjectArray(nPatches[i].num_sinks, gAudioPortConfigClass, NULL));
+ if (jSinks == nullptr) {
+ setGeneration(env, jGeneration, generation1);
+ return AUDIO_JAVA_ERROR;
}
for (size_t j = 0; j < nPatches[i].num_sinks; j++) {
- jStatus = convertAudioPortConfigFromNative(env,
- NULL,
- &jSink,
- &nPatches[i].sinks[j]);
+ ScopedLocalRef<jobject> jSink(env, nullptr);
+ ScopedLocalRef<jobject> jAudioPort(env, nullptr);
+ jStatus = convertAudioPortConfigFromNative(env, &jAudioPort, &jSink,
+ &nPatches[i].sinks[j]);
if (jStatus != AUDIO_JAVA_SUCCESS) {
- goto exit;
+ if (!setGeneration(env, jGeneration, generation1)) {
+ jStatus = AUDIO_JAVA_ERROR;
+ }
+ return jStatus;
}
- env->SetObjectArrayElement(jSinks, j, jSink);
- env->DeleteLocalRef(jSink);
- jSink = NULL;
+ env->SetObjectArrayElement(jSinks.get(), j, jSink.get());
ALOGV("listAudioPatches patch %zu sink %zu is a %s handle %d",
i, j,
nPatches[i].sinks[j].type == AUDIO_PORT_TYPE_DEVICE ? "device" : "mix",
nPatches[i].sinks[j].id);
}
- jPatch = env->NewObject(gAudioPatchClass, gAudioPatchCstor,
- patchHandle, jSources, jSinks);
- env->DeleteLocalRef(jSources);
- jSources = NULL;
- env->DeleteLocalRef(jSinks);
- jSinks = NULL;
- if (jPatch == NULL) {
- jStatus = AUDIO_JAVA_ERROR;
- goto exit;
+ jPatch.reset(env->NewObject(gAudioPatchClass, gAudioPatchCstor, patchHandle, jSources.get(),
+ jSinks.get()));
+ if (jPatch == nullptr) {
+ setGeneration(env, jGeneration, generation1);
+ return AUDIO_JAVA_ERROR;
}
- env->CallBooleanMethod(jPatches, gArrayListMethods.add, jPatch);
- env->DeleteLocalRef(jPatch);
- jPatch = NULL;
+ env->CallBooleanMethod(jPatches, gArrayListMethods.add, jPatch.get());
}
-
-exit:
-
- nGeneration = env->GetIntArrayElements(jGeneration, NULL);
- if (nGeneration == NULL) {
+ if (!setGeneration(env, jGeneration, generation1)) {
jStatus = AUDIO_JAVA_ERROR;
- } else {
- nGeneration[0] = generation1;
- env->ReleaseIntArrayElements(jGeneration, nGeneration, 0);
}
-
- if (jSources != NULL) {
- env->DeleteLocalRef(jSources);
- }
- if (jSource != NULL) {
- env->DeleteLocalRef(jSource);
- }
- if (jSinks != NULL) {
- env->DeleteLocalRef(jSinks);
- }
- if (jSink != NULL) {
- env->DeleteLocalRef(jSink);
- }
- if (jPatch != NULL) {
- env->DeleteLocalRef(jPatch);
- }
- free(nPatches);
return jStatus;
}
@@ -2035,7 +1908,7 @@
}
auto paa = JNIAudioAttributeHelper::makeUnique();
jStatus = JNIAudioAttributeHelper::nativeFromJava(env, jAudioAttributes, paa.get());
- if (jStatus != (jint)AUDIO_JAVA_SUCCESS) {
+ if (jStatus != AUDIO_JAVA_SUCCESS) {
return jStatus;
}
audio_port_handle_t handle;
@@ -2052,8 +1925,7 @@
android_media_AudioSystem_stopAudioSource(JNIEnv *env, jobject clazz, jint handle)
{
ALOGV("stopAudioSource");
- status_t status = AudioSystem::stopAudioSource(
- static_cast <audio_port_handle_t>(handle));
+ status_t status = AudioSystem::stopAudioSource(static_cast<audio_port_handle_t>(handle));
ALOGV("AudioSystem::stopAudioSource() returned %d", status);
return nativeToJavaStatus(status);
}
@@ -2085,7 +1957,7 @@
static jint
android_media_AudioSystem_getAudioHwSyncForSession(JNIEnv *env, jobject thiz, jint sessionId)
{
- return (jint) AudioSystem::getAudioHwSyncForSession((audio_session_t) sessionId);
+ return AudioSystem::getAudioHwSyncForSession(static_cast<audio_session_t>(sessionId));
}
static void
@@ -2204,11 +2076,11 @@
{
nAudioMix->mMixType = env->GetIntField(jAudioMix, gAudioMixFields.mMixType);
nAudioMix->mRouteFlags = env->GetIntField(jAudioMix, gAudioMixFields.mRouteFlags);
- nAudioMix->mDeviceType = (audio_devices_t)
- env->GetIntField(jAudioMix, gAudioMixFields.mDeviceType);
+ nAudioMix->mDeviceType =
+ static_cast<audio_devices_t>(env->GetIntField(jAudioMix, gAudioMixFields.mDeviceType));
- jstring jDeviceAddress = (jstring)env->GetObjectField(jAudioMix,
- gAudioMixFields.mDeviceAddress);
+ jstring jDeviceAddress =
+ static_cast<jstring>(env->GetObjectField(jAudioMix, gAudioMixFields.mDeviceAddress));
const char *nDeviceAddress = env->GetStringUTFChars(jDeviceAddress, NULL);
nAudioMix->mDeviceAddress = String8(nDeviceAddress);
env->ReleaseStringUTFChars(jDeviceAddress, nDeviceAddress);
@@ -2227,8 +2099,8 @@
nAudioMix->mVoiceCommunicationCaptureAllowed =
env->GetBooleanField(jRule, gAudioMixingRuleFields.mVoiceCommunicationCaptureAllowed);
env->DeleteLocalRef(jRule);
- jobjectArray jCriteria = (jobjectArray)env->CallObjectMethod(jRuleCriteria,
- gArrayListMethods.toArray);
+ jobjectArray jCriteria = static_cast<jobjectArray>(
+ env->CallObjectMethod(jRuleCriteria, gArrayListMethods.toArray));
env->DeleteLocalRef(jRuleCriteria);
jint numCriteria = env->GetArrayLength(jCriteria);
@@ -2264,8 +2136,8 @@
auto paa = JNIAudioAttributeHelper::makeUnique();
jint jStatus = JNIAudioAttributeHelper::nativeFromJava(env, jAttributes, paa.get());
- if (jStatus != (jint)AUDIO_JAVA_SUCCESS) {
- return jStatus;
+ if (jStatus != AUDIO_JAVA_SUCCESS) {
+ return jStatus;
}
if (match_rule == RULE_MATCH_ATTRIBUTE_USAGE) {
nCriterion.mValue.mUsage = paa->usage;
@@ -2283,7 +2155,7 @@
env->DeleteLocalRef(jCriteria);
- return (jint)AUDIO_JAVA_SUCCESS;
+ return AUDIO_JAVA_SUCCESS;
}
static jint
@@ -2293,34 +2165,29 @@
ALOGV("registerPolicyMixes");
if (jMixesList == NULL) {
- return (jint)AUDIO_JAVA_BAD_VALUE;
+ return AUDIO_JAVA_BAD_VALUE;
}
if (!env->IsInstanceOf(jMixesList, gArrayListClass)) {
- return (jint)AUDIO_JAVA_BAD_VALUE;
+ return AUDIO_JAVA_BAD_VALUE;
}
- jobjectArray jMixes = (jobjectArray)env->CallObjectMethod(jMixesList,
- gArrayListMethods.toArray);
+ jobjectArray jMixes =
+ static_cast<jobjectArray>(env->CallObjectMethod(jMixesList, gArrayListMethods.toArray));
jint numMixes = env->GetArrayLength(jMixes);
if (numMixes > MAX_MIXES_PER_POLICY) {
numMixes = MAX_MIXES_PER_POLICY;
}
status_t status;
- jint jStatus;
- jobject jAudioMix = NULL;
Vector <AudioMix> mixes;
for (jint i = 0; i < numMixes; i++) {
- jAudioMix = env->GetObjectArrayElement(jMixes, i);
- if (!env->IsInstanceOf(jAudioMix, gAudioMixClass)) {
- jStatus = (jint)AUDIO_JAVA_BAD_VALUE;
- goto exit;
+ ScopedLocalRef<jobject> jAudioMix(env, env->GetObjectArrayElement(jMixes, i));
+ if (!env->IsInstanceOf(jAudioMix.get(), gAudioMixClass)) {
+ return AUDIO_JAVA_BAD_VALUE;
}
AudioMix mix;
- jStatus = convertAudioMixToNative(env, &mix, jAudioMix);
- env->DeleteLocalRef(jAudioMix);
- jAudioMix = NULL;
- if (jStatus != AUDIO_JAVA_SUCCESS) {
- goto exit;
+ if (jint jStatus = convertAudioMixToNative(env, &mix, jAudioMix.get());
+ jStatus != AUDIO_JAVA_SUCCESS) {
+ return jStatus;
}
mixes.add(mix);
}
@@ -2329,16 +2196,7 @@
status = AudioSystem::registerPolicyMixes(mixes, registration);
ALOGV("AudioSystem::registerPolicyMixes() returned %d", status);
- jStatus = nativeToJavaStatus(status);
- if (jStatus != AUDIO_JAVA_SUCCESS) {
- goto exit;
- }
-
-exit:
- if (jAudioMix != NULL) {
- env->DeleteLocalRef(jAudioMix);
- }
- return jStatus;
+ return nativeToJavaStatus(status);
}
static jint android_media_AudioSystem_setUidDeviceAffinities(JNIEnv *env, jobject clazz,
@@ -2348,14 +2206,14 @@
if (results != NO_ERROR) {
return results;
}
- status_t status = AudioSystem::setUidDeviceAffinities((uid_t) uid, deviceVector);
- return (jint) nativeToJavaStatus(status);
+ status_t status = AudioSystem::setUidDeviceAffinities(uid, deviceVector);
+ return nativeToJavaStatus(status);
}
static jint android_media_AudioSystem_removeUidDeviceAffinities(JNIEnv *env, jobject clazz,
jint uid) {
- status_t status = AudioSystem::removeUidDeviceAffinities((uid_t) uid);
- return (jint) nativeToJavaStatus(status);
+ status_t status = AudioSystem::removeUidDeviceAffinities(static_cast<uid_t>(uid));
+ return nativeToJavaStatus(status);
}
static jint android_media_AudioSystem_setUserIdDeviceAffinities(JNIEnv *env, jobject clazz,
@@ -2366,14 +2224,14 @@
if (results != NO_ERROR) {
return results;
}
- status_t status = AudioSystem::setUserIdDeviceAffinities((int)userId, deviceVector);
- return (jint)nativeToJavaStatus(status);
+ status_t status = AudioSystem::setUserIdDeviceAffinities(userId, deviceVector);
+ return nativeToJavaStatus(status);
}
static jint android_media_AudioSystem_removeUserIdDeviceAffinities(JNIEnv *env, jobject clazz,
jint userId) {
- status_t status = AudioSystem::removeUserIdDeviceAffinities((int)userId);
- return (jint)nativeToJavaStatus(status);
+ status_t status = AudioSystem::removeUserIdDeviceAffinities(userId);
+ return nativeToJavaStatus(status);
}
static jint
@@ -2386,19 +2244,18 @@
android_media_AudioSystem_getStreamVolumeDB(JNIEnv *env, jobject thiz,
jint stream, jint index, jint device)
{
- return (jfloat)AudioSystem::getStreamVolumeDB((audio_stream_type_t)stream,
- (int)index,
- (audio_devices_t)device);
+ return AudioSystem::getStreamVolumeDB(static_cast<audio_stream_type_t>(stream), index,
+ static_cast<audio_devices_t>(device));
}
static jint android_media_AudioSystem_getOffloadSupport(JNIEnv *env, jobject thiz, jint encoding,
jint sampleRate, jint channelMask,
jint channelIndexMask, jint streamType) {
audio_offload_info_t format = AUDIO_INFO_INITIALIZER;
- format.format = (audio_format_t) audioFormatToNative(encoding);
- format.sample_rate = (uint32_t) sampleRate;
+ format.format = static_cast<audio_format_t>(audioFormatToNative(encoding));
+ format.sample_rate = sampleRate;
format.channel_mask = nativeChannelMaskFromJavaChannelMasks(channelMask, channelIndexMask);
- format.stream_type = (audio_stream_type_t) streamType;
+ format.stream_type = static_cast<audio_stream_type_t>(streamType);
format.has_video = false;
format.is_streaming = false;
// offload duration unknown at this point:
@@ -2415,11 +2272,11 @@
if (jMicrophonesInfo == NULL) {
ALOGE("jMicrophonesInfo NULL MicrophoneInfo ArrayList");
- return (jint)AUDIO_JAVA_BAD_VALUE;
+ return AUDIO_JAVA_BAD_VALUE;
}
if (!env->IsInstanceOf(jMicrophonesInfo, gArrayListClass)) {
ALOGE("getMicrophones not an arraylist");
- return (jint)AUDIO_JAVA_BAD_VALUE;
+ return AUDIO_JAVA_BAD_VALUE;
}
jint jStatus;
@@ -2431,7 +2288,7 @@
return jStatus;
}
if (microphones.size() == 0) {
- jStatus = (jint)AUDIO_JAVA_SUCCESS;
+ jStatus = AUDIO_JAVA_SUCCESS;
return jStatus;
}
for (size_t i = 0; i < microphones.size(); i++) {
@@ -2453,7 +2310,7 @@
jint jStatus = AUDIO_JAVA_SUCCESS;
if (!env->IsInstanceOf(jEncodingFormatList, gArrayListClass)) {
ALOGE("%s: jEncodingFormatList not an ArrayList", __FUNCTION__);
- return (jint)AUDIO_JAVA_BAD_VALUE;
+ return AUDIO_JAVA_BAD_VALUE;
}
std::vector<audio_format_t> encodingFormats;
status_t status =
@@ -2572,12 +2429,10 @@
android_media_AudioSystem_setSurroundFormatEnabled(JNIEnv *env, jobject thiz,
jint audioFormat, jboolean enabled)
{
- status_t status = AudioSystem::setSurroundFormatEnabled(audioFormatToNative(audioFormat),
- (bool)enabled);
- if (status != NO_ERROR) {
- ALOGE_IF(status != NO_ERROR, "AudioSystem::setSurroundFormatEnabled error %d", status);
- }
- return (jint)nativeToJavaStatus(status);
+ status_t status =
+ AudioSystem::setSurroundFormatEnabled(audioFormatToNative(audioFormat), enabled);
+ ALOGE_IF(status != NO_ERROR, "AudioSystem::setSurroundFormatEnabled error %d", status);
+ return nativeToJavaStatus(status);
}
static jint android_media_AudioSystem_getMaxChannelCount(JNIEnv *env, jobject thiz) {
@@ -2618,7 +2473,7 @@
status_t status = AudioSystem::setAssistantServicesUids(nativeUidsVector);
- return (jint)nativeToJavaStatus(status);
+ return nativeToJavaStatus(status);
}
static jint android_media_AudioSystem_setActiveAssistantServicesUids(JNIEnv *env, jobject thiz,
@@ -2627,7 +2482,7 @@
status_t status = AudioSystem::setActiveAssistantServicesUids(nativeActiveUidsVector);
- return (jint)nativeToJavaStatus(status);
+ return nativeToJavaStatus(status);
}
static jint
@@ -2635,12 +2490,12 @@
std::vector<uid_t> nativeUidsVector = convertJIntArrayToUidVector(env, uids);
status_t status = AudioSystem::setA11yServicesUids(nativeUidsVector);
- return (jint)nativeToJavaStatus(status);
+ return nativeToJavaStatus(status);
}
static jint android_media_AudioSystem_setCurrentImeUid(JNIEnv *env, jobject thiz, jint uid) {
status_t status = AudioSystem::setCurrentImeUid(uid);
- return (jint)nativeToJavaStatus(status);
+ return nativeToJavaStatus(status);
}
static jboolean
@@ -2658,7 +2513,7 @@
std::vector<audio_usage_t> nativeSystemUsagesVector;
if (systemUsages == nullptr) {
- return (jint) AUDIO_JAVA_BAD_VALUE;
+ return AUDIO_JAVA_BAD_VALUE;
}
int *nativeSystemUsages = nullptr;
@@ -2675,7 +2530,7 @@
}
status_t status = AudioSystem::setSupportedSystemUsages(nativeSystemUsagesVector);
- return (jint)nativeToJavaStatus(status);
+ return nativeToJavaStatus(status);
}
static jint
@@ -2686,16 +2541,16 @@
static jint
android_media_AudioSystem_setRttEnabled(JNIEnv *env, jobject thiz, jboolean enabled)
{
- return (jint) check_AudioSystem_Command(AudioSystem::setRttEnabled(enabled));
+ return check_AudioSystem_Command(AudioSystem::setRttEnabled(enabled));
}
static jint
android_media_AudioSystem_setAudioHalPids(JNIEnv *env, jobject clazz, jintArray jPids)
{
if (jPids == NULL) {
- return (jint) AUDIO_JAVA_BAD_VALUE;
+ return AUDIO_JAVA_BAD_VALUE;
}
- pid_t *nPidsArray = (pid_t *) env->GetIntArrayElements(jPids, NULL);
+ pid_t *nPidsArray = reinterpret_cast<pid_t *>(env->GetIntArrayElements(jPids, nullptr));
std::vector<pid_t> nPids(nPidsArray, nPidsArray + env->GetArrayLength(jPids));
status_t status = AudioSystem::setAudioHalPids(nPids);
env->ReleaseIntArrayElements(jPids, nPidsArray, 0);
@@ -2719,9 +2574,9 @@
return results;
}
int status = check_AudioSystem_Command(
- AudioSystem::setDevicesRoleForStrategy((product_strategy_t)strategy,
- (device_role_t)role, nDevices));
- return (jint) status;
+ AudioSystem::setDevicesRoleForStrategy(static_cast<product_strategy_t>(strategy),
+ static_cast<device_role_t>(role), nDevices));
+ return status;
}
static jint android_media_AudioSystem_removeDevicesRoleForStrategy(JNIEnv *env, jobject thiz,
@@ -2734,8 +2589,8 @@
return results;
}
int status = check_AudioSystem_Command(
- AudioSystem::removeDevicesRoleForStrategy((product_strategy_t)strategy,
- (device_role_t)role, nDevices));
+ AudioSystem::removeDevicesRoleForStrategy(static_cast<product_strategy_t>(strategy),
+ static_cast<device_role_t>(role), nDevices));
return (jint)status;
}
@@ -2753,10 +2608,10 @@
jobject jDevices) {
AudioDeviceTypeAddrVector nDevices;
status_t status = check_AudioSystem_Command(
- AudioSystem::getDevicesForRoleAndStrategy((product_strategy_t)strategy,
- (device_role_t)role, nDevices));
+ AudioSystem::getDevicesForRoleAndStrategy(static_cast<product_strategy_t>(strategy),
+ static_cast<device_role_t>(role), nDevices));
if (status != NO_ERROR) {
- return (jint) status;
+ return status;
}
for (const auto &device : nDevices) {
jobject jAudioDeviceAttributes = NULL;
@@ -2779,9 +2634,10 @@
return results;
}
int status = check_AudioSystem_Command(
- AudioSystem::setDevicesRoleForCapturePreset((audio_source_t)capturePreset,
- (device_role_t)role, nDevices));
- return (jint)status;
+ AudioSystem::setDevicesRoleForCapturePreset(static_cast<audio_source_t>(capturePreset),
+ static_cast<device_role_t>(role),
+ nDevices));
+ return status;
}
static jint android_media_AudioSystem_addDevicesRoleForCapturePreset(
@@ -2793,9 +2649,10 @@
return results;
}
int status = check_AudioSystem_Command(
- AudioSystem::addDevicesRoleForCapturePreset((audio_source_t)capturePreset,
- (device_role_t)role, nDevices));
- return (jint)status;
+ AudioSystem::addDevicesRoleForCapturePreset(static_cast<audio_source_t>(capturePreset),
+ static_cast<device_role_t>(role),
+ nDevices));
+ return status;
}
static jint android_media_AudioSystem_removeDevicesRoleForCapturePreset(
@@ -2807,17 +2664,20 @@
return results;
}
int status = check_AudioSystem_Command(
- AudioSystem::removeDevicesRoleForCapturePreset((audio_source_t)capturePreset,
- (device_role_t)role, nDevices));
- return (jint)status;
+ AudioSystem::removeDevicesRoleForCapturePreset(static_cast<audio_source_t>(
+ capturePreset),
+ static_cast<device_role_t>(role),
+ nDevices));
+ return status;
}
static jint android_media_AudioSystem_clearDevicesRoleForCapturePreset(JNIEnv *env, jobject thiz,
jint capturePreset,
jint role) {
- return (jint)check_AudioSystem_Command(
- AudioSystem::clearDevicesRoleForCapturePreset((audio_source_t)capturePreset,
- (device_role_t)role));
+ return static_cast<jint>(check_AudioSystem_Command(
+ AudioSystem::clearDevicesRoleForCapturePreset(static_cast<audio_source_t>(
+ capturePreset),
+ static_cast<device_role_t>(role))));
}
static jint android_media_AudioSystem_getDevicesForRoleAndCapturePreset(JNIEnv *env, jobject thiz,
@@ -2826,10 +2686,12 @@
jobject jDevices) {
AudioDeviceTypeAddrVector nDevices;
status_t status = check_AudioSystem_Command(
- AudioSystem::getDevicesForRoleAndCapturePreset((audio_source_t)capturePreset,
- (device_role_t)role, nDevices));
+ AudioSystem::getDevicesForRoleAndCapturePreset(static_cast<audio_source_t>(
+ capturePreset),
+ static_cast<device_role_t>(role),
+ nDevices));
if (status != NO_ERROR) {
- return (jint)status;
+ return status;
}
for (const auto &device : nDevices) {
jobject jAudioDeviceAttributes = NULL;
@@ -2854,12 +2716,12 @@
// components call this method often
if (jDeviceArray == nullptr || maxResultSize == 0) {
ALOGE("%s invalid array to store AudioDeviceAttributes", __FUNCTION__);
- return (jint)AUDIO_JAVA_BAD_VALUE;
+ return AUDIO_JAVA_BAD_VALUE;
}
JNIAudioAttributeHelper::UniqueAaPtr paa = JNIAudioAttributeHelper::makeUnique();
jint jStatus = JNIAudioAttributeHelper::nativeFromJava(env, jaa, paa.get());
- if (jStatus != (jint) AUDIO_JAVA_SUCCESS) {
+ if (jStatus != AUDIO_JAVA_SUCCESS) {
return jStatus;
}
@@ -2888,7 +2750,7 @@
static jint android_media_AudioSystem_setVibratorInfos(JNIEnv *env, jobject thiz,
jobject jVibrators) {
if (!env->IsInstanceOf(jVibrators, gListClass)) {
- return (jint)AUDIO_JAVA_BAD_VALUE;
+ return AUDIO_JAVA_BAD_VALUE;
}
const jint size = env->CallIntMethod(jVibrators, gListMethods.size);
std::vector<media::AudioVibratorInfo> vibratorInfos;
@@ -2896,7 +2758,7 @@
ScopedLocalRef<jobject> jVibrator(env,
env->CallObjectMethod(jVibrators, gListMethods.get, i));
if (!env->IsInstanceOf(jVibrator.get(), gVibratorClass)) {
- return (jint)AUDIO_JAVA_BAD_VALUE;
+ return AUDIO_JAVA_BAD_VALUE;
}
media::AudioVibratorInfo vibratorInfo;
vibratorInfo.id = env->CallIntMethod(jVibrator.get(), gVibratorMethods.getId);
@@ -2907,7 +2769,7 @@
env->CallFloatMethod(jVibrator.get(), gVibratorMethods.getMaxAmplitude);
vibratorInfos.push_back(vibratorInfo);
}
- return (jint)check_AudioSystem_Command(AudioSystem::setVibratorInfos(vibratorInfos));
+ return check_AudioSystem_Command(AudioSystem::setVibratorInfos(vibratorInfos));
}
static jobject android_media_AudioSystem_getSpatializer(JNIEnv *env, jobject thiz,
@@ -2929,8 +2791,8 @@
jobjectArray jDeviceArray) {
JNIAudioAttributeHelper::UniqueAaPtr paa = JNIAudioAttributeHelper::makeUnique();
jint jStatus = JNIAudioAttributeHelper::nativeFromJava(env, jaa, paa.get());
- if (jStatus != (jint)AUDIO_JAVA_SUCCESS) {
- return false;
+ if (jStatus != AUDIO_JAVA_SUCCESS) {
+ return false;
}
AudioDeviceTypeAddrVector nDevices;
@@ -2943,7 +2805,7 @@
return false;
}
jStatus = createAudioDeviceTypeAddrFromJava(env, &device, jDevice);
- if (jStatus != (jint)AUDIO_JAVA_SUCCESS) {
+ if (jStatus != AUDIO_JAVA_SUCCESS) {
return false;
}
nDevices.push_back(device);
@@ -3000,7 +2862,7 @@
jobject jFormat, jobject jaa) {
JNIAudioAttributeHelper::UniqueAaPtr paa = JNIAudioAttributeHelper::makeUnique();
jint jStatus = JNIAudioAttributeHelper::nativeFromJava(env, jaa, paa.get());
- if (jStatus != (jint)AUDIO_JAVA_SUCCESS) {
+ if (jStatus != AUDIO_JAVA_SUCCESS) {
return DIRECT_NOT_SUPPORTED;
}
@@ -3023,20 +2885,20 @@
if (jAudioAttributes == nullptr) {
ALOGE("jAudioAttributes is NULL");
- return (jint)AUDIO_JAVA_BAD_VALUE;
+ return AUDIO_JAVA_BAD_VALUE;
}
if (jAudioProfilesList == nullptr) {
ALOGE("jAudioProfilesList is NULL");
- return (jint)AUDIO_JAVA_BAD_VALUE;
+ return AUDIO_JAVA_BAD_VALUE;
}
if (!env->IsInstanceOf(jAudioProfilesList, gArrayListClass)) {
ALOGE("jAudioProfilesList not an ArrayList");
- return (jint)AUDIO_JAVA_BAD_VALUE;
+ return AUDIO_JAVA_BAD_VALUE;
}
JNIAudioAttributeHelper::UniqueAaPtr paa = JNIAudioAttributeHelper::makeUnique();
jint jStatus = JNIAudioAttributeHelper::nativeFromJava(env, jAudioAttributes, paa.get());
- if (jStatus != (jint)AUDIO_JAVA_SUCCESS) {
+ if (jStatus != AUDIO_JAVA_SUCCESS) {
return jStatus;
}
@@ -3049,7 +2911,7 @@
}
for (const auto &audioProfile : audioProfiles) {
- jobject jAudioProfile;
+ ScopedLocalRef<jobject> jAudioProfile(env);
jint jConvertProfileStatus = convertAudioProfileFromNative(
env, &jAudioProfile, &audioProfile, false);
if (jConvertProfileStatus == AUDIO_JAVA_BAD_VALUE) {
@@ -3059,8 +2921,7 @@
if (jConvertProfileStatus != AUDIO_JAVA_SUCCESS) {
return jConvertProfileStatus;
}
- env->CallBooleanMethod(jAudioProfilesList, gArrayListMethods.add, jAudioProfile);
- env->DeleteLocalRef(jAudioProfile);
+ env->CallBooleanMethod(jAudioProfilesList, gArrayListMethods.add, jAudioProfile.get());
}
return jStatus;
}
@@ -3212,8 +3073,7 @@
static int android_media_AudioSystem_setBluetoothVariableLatencyEnabled(JNIEnv *env, jobject thiz,
jboolean enabled) {
- return (jint)check_AudioSystem_Command(
- AudioSystem::setBluetoothVariableLatencyEnabled(enabled));
+ return check_AudioSystem_Command(AudioSystem::setBluetoothVariableLatencyEnabled(enabled));
}
static jboolean android_media_AudioSystem_isBluetoothVariableLatencyEnabled(JNIEnv *env,
@@ -3227,191 +3087,182 @@
// ----------------------------------------------------------------------------
+#define MAKE_AUDIO_SYSTEM_METHOD(x) \
+ MAKE_JNI_NATIVE_METHOD_AUTOSIG(#x, android_media_AudioSystem_##x)
+
static const JNINativeMethod gMethods[] =
- {{"setParameters", "(Ljava/lang/String;)I",
- (void *)android_media_AudioSystem_setParameters},
- {"getParameters", "(Ljava/lang/String;)Ljava/lang/String;",
- (void *)android_media_AudioSystem_getParameters},
- {"muteMicrophone", "(Z)I", (void *)android_media_AudioSystem_muteMicrophone},
- {"isMicrophoneMuted", "()Z", (void *)android_media_AudioSystem_isMicrophoneMuted},
- {"isStreamActive", "(II)Z", (void *)android_media_AudioSystem_isStreamActive},
- {"isStreamActiveRemotely", "(II)Z",
- (void *)android_media_AudioSystem_isStreamActiveRemotely},
- {"isSourceActive", "(I)Z", (void *)android_media_AudioSystem_isSourceActive},
- {"newAudioSessionId", "()I", (void *)android_media_AudioSystem_newAudioSessionId},
- {"newAudioPlayerId", "()I", (void *)android_media_AudioSystem_newAudioPlayerId},
- {"newAudioRecorderId", "()I", (void *)android_media_AudioSystem_newAudioRecorderId},
- {"setDeviceConnectionState", "(ILandroid/os/Parcel;I)I",
- (void *)android_media_AudioSystem_setDeviceConnectionState},
- {"getDeviceConnectionState", "(ILjava/lang/String;)I",
- (void *)android_media_AudioSystem_getDeviceConnectionState},
- {"handleDeviceConfigChange", "(ILjava/lang/String;Ljava/lang/String;I)I",
- (void *)android_media_AudioSystem_handleDeviceConfigChange},
- {"setPhoneState", "(II)I", (void *)android_media_AudioSystem_setPhoneState},
- {"setForceUse", "(II)I", (void *)android_media_AudioSystem_setForceUse},
- {"getForceUse", "(I)I", (void *)android_media_AudioSystem_getForceUse},
- {"initStreamVolume", "(III)I", (void *)android_media_AudioSystem_initStreamVolume},
- {"setStreamVolumeIndex", "(III)I", (void *)android_media_AudioSystem_setStreamVolumeIndex},
- {"getStreamVolumeIndex", "(II)I", (void *)android_media_AudioSystem_getStreamVolumeIndex},
- {"setVolumeIndexForAttributes", "(Landroid/media/AudioAttributes;II)I",
- (void *)android_media_AudioSystem_setVolumeIndexForAttributes},
- {"getVolumeIndexForAttributes", "(Landroid/media/AudioAttributes;I)I",
- (void *)android_media_AudioSystem_getVolumeIndexForAttributes},
- {"getMinVolumeIndexForAttributes", "(Landroid/media/AudioAttributes;)I",
- (void *)android_media_AudioSystem_getMinVolumeIndexForAttributes},
- {"getMaxVolumeIndexForAttributes", "(Landroid/media/AudioAttributes;)I",
- (void *)android_media_AudioSystem_getMaxVolumeIndexForAttributes},
- {"setMasterVolume", "(F)I", (void *)android_media_AudioSystem_setMasterVolume},
- {"getMasterVolume", "()F", (void *)android_media_AudioSystem_getMasterVolume},
- {"setMasterMute", "(Z)I", (void *)android_media_AudioSystem_setMasterMute},
- {"getMasterMute", "()Z", (void *)android_media_AudioSystem_getMasterMute},
- {"setMasterMono", "(Z)I", (void *)android_media_AudioSystem_setMasterMono},
- {"getMasterMono", "()Z", (void *)android_media_AudioSystem_getMasterMono},
- {"setMasterBalance", "(F)I", (void *)android_media_AudioSystem_setMasterBalance},
- {"getMasterBalance", "()F", (void *)android_media_AudioSystem_getMasterBalance},
- {"getPrimaryOutputSamplingRate", "()I",
- (void *)android_media_AudioSystem_getPrimaryOutputSamplingRate},
- {"getPrimaryOutputFrameCount", "()I",
- (void *)android_media_AudioSystem_getPrimaryOutputFrameCount},
- {"getOutputLatency", "(I)I", (void *)android_media_AudioSystem_getOutputLatency},
- {"setLowRamDevice", "(ZJ)I", (void *)android_media_AudioSystem_setLowRamDevice},
- {"checkAudioFlinger", "()I", (void *)android_media_AudioSystem_checkAudioFlinger},
- {"setAudioFlingerBinder", "(Landroid/os/IBinder;)V",
- (void *)android_media_AudioSystem_setAudioFlingerBinder},
- {"listAudioPorts", "(Ljava/util/ArrayList;[I)I",
- (void *)android_media_AudioSystem_listAudioPorts},
- {"createAudioPatch",
- "([Landroid/media/AudioPatch;[Landroid/media/AudioPortConfig;[Landroid/media/"
- "AudioPortConfig;)I",
- (void *)android_media_AudioSystem_createAudioPatch},
- {"releaseAudioPatch", "(Landroid/media/AudioPatch;)I",
- (void *)android_media_AudioSystem_releaseAudioPatch},
- {"listAudioPatches", "(Ljava/util/ArrayList;[I)I",
- (void *)android_media_AudioSystem_listAudioPatches},
- {"setAudioPortConfig", "(Landroid/media/AudioPortConfig;)I",
- (void *)android_media_AudioSystem_setAudioPortConfig},
- {"startAudioSource", "(Landroid/media/AudioPortConfig;Landroid/media/AudioAttributes;)I",
- (void *)android_media_AudioSystem_startAudioSource},
- {"stopAudioSource", "(I)I", (void *)android_media_AudioSystem_stopAudioSource},
- {"getAudioHwSyncForSession", "(I)I",
- (void *)android_media_AudioSystem_getAudioHwSyncForSession},
- {"registerPolicyMixes", "(Ljava/util/ArrayList;Z)I",
- (void *)android_media_AudioSystem_registerPolicyMixes},
- {"setUidDeviceAffinities", "(I[I[Ljava/lang/String;)I",
- (void *)android_media_AudioSystem_setUidDeviceAffinities},
- {"removeUidDeviceAffinities", "(I)I",
- (void *)android_media_AudioSystem_removeUidDeviceAffinities},
- {"native_register_dynamic_policy_callback", "()V",
- (void *)android_media_AudioSystem_registerDynPolicyCallback},
- {"native_register_recording_callback", "()V",
- (void *)android_media_AudioSystem_registerRecordingCallback},
- {"native_register_routing_callback", "()V",
- (void *)android_media_AudioSystem_registerRoutingCallback},
- {"native_register_vol_range_init_req_callback", "()V",
- (void *)android_media_AudioSystem_registerVolRangeInitReqCallback},
- {"systemReady", "()I", (void *)android_media_AudioSystem_systemReady},
- {"getStreamVolumeDB", "(III)F", (void *)android_media_AudioSystem_getStreamVolumeDB},
- {"native_get_offload_support", "(IIIII)I",
- (void *)android_media_AudioSystem_getOffloadSupport},
- {"getMicrophones", "(Ljava/util/ArrayList;)I",
- (void *)android_media_AudioSystem_getMicrophones},
- {"getSurroundFormats", "(Ljava/util/Map;)I",
- (void *)android_media_AudioSystem_getSurroundFormats},
- {"getReportedSurroundFormats", "(Ljava/util/ArrayList;)I",
- (void *)android_media_AudioSystem_getReportedSurroundFormats},
- {"setSurroundFormatEnabled", "(IZ)I",
- (void *)android_media_AudioSystem_setSurroundFormatEnabled},
- {"setAssistantServicesUids", "([I)I",
- (void *)android_media_AudioSystem_setAssistantServicesUids},
- {"setActiveAssistantServicesUids", "([I)I",
- (void *)android_media_AudioSystem_setActiveAssistantServicesUids},
- {"setA11yServicesUids", "([I)I", (void *)android_media_AudioSystem_setA11yServicesUids},
- {"isHapticPlaybackSupported", "()Z",
- (void *)android_media_AudioSystem_isHapticPlaybackSupported},
- {"isUltrasoundSupported", "()Z", (void *)android_media_AudioSystem_isUltrasoundSupported},
- {"getHwOffloadFormatsSupportedForBluetoothMedia", "(ILjava/util/ArrayList;)I",
- (void *)android_media_AudioSystem_getHwOffloadFormatsSupportedForBluetoothMedia},
- {"setSupportedSystemUsages", "([I)I",
- (void *)android_media_AudioSystem_setSupportedSystemUsages},
- {"setAllowedCapturePolicy", "(II)I",
- (void *)android_media_AudioSystem_setAllowedCapturePolicy},
- {"setRttEnabled", "(Z)I", (void *)android_media_AudioSystem_setRttEnabled},
- {"setAudioHalPids", "([I)I", (void *)android_media_AudioSystem_setAudioHalPids},
- {"isCallScreeningModeSupported", "()Z",
- (void *)android_media_AudioSystem_isCallScreeningModeSupported},
- {"setDevicesRoleForStrategy", "(II[I[Ljava/lang/String;)I",
- (void *)android_media_AudioSystem_setDevicesRoleForStrategy},
- {"removeDevicesRoleForStrategy", "(II[I[Ljava/lang/String;)I",
- (void *)android_media_AudioSystem_removeDevicesRoleForStrategy},
- {"clearDevicesRoleForStrategy", "(II)I",
- (void *)android_media_AudioSystem_clearDevicesRoleForStrategy},
- {"getDevicesForRoleAndStrategy", "(IILjava/util/List;)I",
- (void *)android_media_AudioSystem_getDevicesForRoleAndStrategy},
- {"setDevicesRoleForCapturePreset", "(II[I[Ljava/lang/String;)I",
- (void *)android_media_AudioSystem_setDevicesRoleForCapturePreset},
- {"addDevicesRoleForCapturePreset", "(II[I[Ljava/lang/String;)I",
- (void *)android_media_AudioSystem_addDevicesRoleForCapturePreset},
- {"removeDevicesRoleForCapturePreset", "(II[I[Ljava/lang/String;)I",
- (void *)android_media_AudioSystem_removeDevicesRoleForCapturePreset},
- {"clearDevicesRoleForCapturePreset", "(II)I",
- (void *)android_media_AudioSystem_clearDevicesRoleForCapturePreset},
- {"getDevicesForRoleAndCapturePreset", "(IILjava/util/List;)I",
- (void *)android_media_AudioSystem_getDevicesForRoleAndCapturePreset},
- {"getDevicesForAttributes",
- "(Landroid/media/AudioAttributes;[Landroid/media/AudioDeviceAttributes;Z)I",
- (void *)android_media_AudioSystem_getDevicesForAttributes},
- {"setUserIdDeviceAffinities", "(I[I[Ljava/lang/String;)I",
- (void *)android_media_AudioSystem_setUserIdDeviceAffinities},
- {"removeUserIdDeviceAffinities", "(I)I",
- (void *)android_media_AudioSystem_removeUserIdDeviceAffinities},
- {"setCurrentImeUid", "(I)I", (void *)android_media_AudioSystem_setCurrentImeUid},
- {"setVibratorInfos", "(Ljava/util/List;)I",
- (void *)android_media_AudioSystem_setVibratorInfos},
- {"nativeGetSpatializer",
- "(Landroid/media/INativeSpatializerCallback;)Landroid/os/IBinder;",
- (void *)android_media_AudioSystem_getSpatializer},
- {"canBeSpatialized",
- "(Landroid/media/AudioAttributes;Landroid/media/AudioFormat;"
- "[Landroid/media/AudioDeviceAttributes;)Z",
- (void *)android_media_AudioSystem_canBeSpatialized},
- {"nativeGetSoundDose", "(Landroid/media/ISoundDoseCallback;)Landroid/os/IBinder;",
- (void *)android_media_AudioSystem_nativeGetSoundDose},
- {"getDirectPlaybackSupport",
- "(Landroid/media/AudioFormat;Landroid/media/AudioAttributes;)I",
- (void *)android_media_AudioSystem_getDirectPlaybackSupport},
- {"getDirectProfilesForAttributes",
- "(Landroid/media/AudioAttributes;Ljava/util/ArrayList;)I",
- (void *)android_media_AudioSystem_getDirectProfilesForAttributes},
- {"getSupportedMixerAttributes", "(ILjava/util/List;)I",
- (void *)android_media_AudioSystem_getSupportedMixerAttributes},
- {"setPreferredMixerAttributes",
- "(Landroid/media/AudioAttributes;IILandroid/media/AudioMixerAttributes;)I",
- (void *)android_media_AudioSystem_setPreferredMixerAttributes},
- {"getPreferredMixerAttributes", "(Landroid/media/AudioAttributes;ILjava/util/List;)I",
- (void *)android_media_AudioSystem_getPreferredMixerAttributes},
- {"clearPreferredMixerAttributes", "(Landroid/media/AudioAttributes;II)I",
- (void *)android_media_AudioSystem_clearPreferredMixerAttributes},
- {"supportsBluetoothVariableLatency", "()Z",
- (void *)android_media_AudioSystem_supportsBluetoothVariableLatency},
- {"setBluetoothVariableLatencyEnabled", "(Z)I",
- (void *)android_media_AudioSystem_setBluetoothVariableLatencyEnabled},
- {"isBluetoothVariableLatencyEnabled", "()Z",
- (void *)android_media_AudioSystem_isBluetoothVariableLatencyEnabled}};
+ {MAKE_AUDIO_SYSTEM_METHOD(setParameters),
+ MAKE_AUDIO_SYSTEM_METHOD(getParameters),
+ MAKE_AUDIO_SYSTEM_METHOD(muteMicrophone),
+ MAKE_AUDIO_SYSTEM_METHOD(isMicrophoneMuted),
+ MAKE_AUDIO_SYSTEM_METHOD(isStreamActive),
+ MAKE_AUDIO_SYSTEM_METHOD(isStreamActiveRemotely),
+ MAKE_AUDIO_SYSTEM_METHOD(isSourceActive),
+ MAKE_AUDIO_SYSTEM_METHOD(newAudioSessionId),
+ MAKE_AUDIO_SYSTEM_METHOD(newAudioPlayerId),
+ MAKE_AUDIO_SYSTEM_METHOD(newAudioRecorderId),
+ MAKE_JNI_NATIVE_METHOD("setDeviceConnectionState", "(ILandroid/os/Parcel;I)I",
+ android_media_AudioSystem_setDeviceConnectionState),
+ MAKE_AUDIO_SYSTEM_METHOD(getDeviceConnectionState),
+ MAKE_AUDIO_SYSTEM_METHOD(handleDeviceConfigChange),
+ MAKE_AUDIO_SYSTEM_METHOD(setPhoneState),
+ MAKE_AUDIO_SYSTEM_METHOD(setForceUse),
+ MAKE_AUDIO_SYSTEM_METHOD(getForceUse),
+ MAKE_AUDIO_SYSTEM_METHOD(initStreamVolume),
+ MAKE_AUDIO_SYSTEM_METHOD(setStreamVolumeIndex),
+ MAKE_AUDIO_SYSTEM_METHOD(getStreamVolumeIndex),
+ MAKE_JNI_NATIVE_METHOD("setVolumeIndexForAttributes",
+ "(Landroid/media/AudioAttributes;II)I",
+ android_media_AudioSystem_setVolumeIndexForAttributes),
+ MAKE_JNI_NATIVE_METHOD("getVolumeIndexForAttributes",
+ "(Landroid/media/AudioAttributes;I)I",
+ android_media_AudioSystem_getVolumeIndexForAttributes),
+ MAKE_JNI_NATIVE_METHOD("getMinVolumeIndexForAttributes",
+ "(Landroid/media/AudioAttributes;)I",
+ android_media_AudioSystem_getMinVolumeIndexForAttributes),
+ MAKE_JNI_NATIVE_METHOD("getMaxVolumeIndexForAttributes",
+ "(Landroid/media/AudioAttributes;)I",
+ android_media_AudioSystem_getMaxVolumeIndexForAttributes),
+ MAKE_AUDIO_SYSTEM_METHOD(setMasterVolume),
+ MAKE_AUDIO_SYSTEM_METHOD(getMasterVolume),
+ MAKE_AUDIO_SYSTEM_METHOD(setMasterMute),
+ MAKE_AUDIO_SYSTEM_METHOD(getMasterMute),
+ MAKE_AUDIO_SYSTEM_METHOD(setMasterMono),
+ MAKE_AUDIO_SYSTEM_METHOD(getMasterMono),
+ MAKE_AUDIO_SYSTEM_METHOD(setMasterBalance),
+ MAKE_AUDIO_SYSTEM_METHOD(getMasterBalance),
+ MAKE_AUDIO_SYSTEM_METHOD(getPrimaryOutputSamplingRate),
+ MAKE_AUDIO_SYSTEM_METHOD(getPrimaryOutputFrameCount),
+ MAKE_AUDIO_SYSTEM_METHOD(getOutputLatency),
+ MAKE_AUDIO_SYSTEM_METHOD(setLowRamDevice),
+ MAKE_AUDIO_SYSTEM_METHOD(checkAudioFlinger),
+ MAKE_JNI_NATIVE_METHOD("setAudioFlingerBinder", "(Landroid/os/IBinder;)V",
+ android_media_AudioSystem_setAudioFlingerBinder),
+ MAKE_JNI_NATIVE_METHOD("listAudioPorts", "(Ljava/util/ArrayList;[I)I",
+ android_media_AudioSystem_listAudioPorts),
+ MAKE_JNI_NATIVE_METHOD("createAudioPatch",
+ "([Landroid/media/AudioPatch;[Landroid/media/"
+ "AudioPortConfig;[Landroid/media/AudioPortConfig;)I",
+ android_media_AudioSystem_createAudioPatch),
+ MAKE_JNI_NATIVE_METHOD("releaseAudioPatch", "(Landroid/media/AudioPatch;)I",
+ android_media_AudioSystem_releaseAudioPatch),
+ MAKE_JNI_NATIVE_METHOD("listAudioPatches", "(Ljava/util/ArrayList;[I)I",
+ android_media_AudioSystem_listAudioPatches),
+ MAKE_JNI_NATIVE_METHOD("setAudioPortConfig", "(Landroid/media/AudioPortConfig;)I",
+ android_media_AudioSystem_setAudioPortConfig),
+ MAKE_JNI_NATIVE_METHOD("startAudioSource",
+ "(Landroid/media/AudioPortConfig;Landroid/media/AudioAttributes;)I",
+ android_media_AudioSystem_startAudioSource),
+ MAKE_AUDIO_SYSTEM_METHOD(stopAudioSource),
+ MAKE_AUDIO_SYSTEM_METHOD(getAudioHwSyncForSession),
+ MAKE_JNI_NATIVE_METHOD("registerPolicyMixes", "(Ljava/util/ArrayList;Z)I",
+ android_media_AudioSystem_registerPolicyMixes),
+ MAKE_JNI_NATIVE_METHOD("setUidDeviceAffinities", "(I[I[Ljava/lang/String;)I",
+ android_media_AudioSystem_setUidDeviceAffinities),
+ MAKE_AUDIO_SYSTEM_METHOD(removeUidDeviceAffinities),
+ MAKE_JNI_NATIVE_METHOD_AUTOSIG("native_register_dynamic_policy_callback",
+ android_media_AudioSystem_registerDynPolicyCallback),
+ MAKE_JNI_NATIVE_METHOD_AUTOSIG("native_register_recording_callback",
+ android_media_AudioSystem_registerRecordingCallback),
+ MAKE_JNI_NATIVE_METHOD_AUTOSIG("native_register_routing_callback",
+ android_media_AudioSystem_registerRoutingCallback),
+ MAKE_JNI_NATIVE_METHOD_AUTOSIG("native_register_vol_range_init_req_callback",
+ android_media_AudioSystem_registerVolRangeInitReqCallback),
+ MAKE_AUDIO_SYSTEM_METHOD(systemReady),
+ MAKE_AUDIO_SYSTEM_METHOD(getStreamVolumeDB),
+ MAKE_JNI_NATIVE_METHOD_AUTOSIG("native_get_offload_support",
+ android_media_AudioSystem_getOffloadSupport),
+ MAKE_JNI_NATIVE_METHOD("getMicrophones", "(Ljava/util/ArrayList;)I",
+ android_media_AudioSystem_getMicrophones),
+ MAKE_JNI_NATIVE_METHOD("getSurroundFormats", "(Ljava/util/Map;)I",
+ android_media_AudioSystem_getSurroundFormats),
+ MAKE_JNI_NATIVE_METHOD("getReportedSurroundFormats", "(Ljava/util/ArrayList;)I",
+ android_media_AudioSystem_getReportedSurroundFormats),
+ MAKE_AUDIO_SYSTEM_METHOD(setSurroundFormatEnabled),
+ MAKE_AUDIO_SYSTEM_METHOD(setAssistantServicesUids),
+ MAKE_AUDIO_SYSTEM_METHOD(setActiveAssistantServicesUids),
+ MAKE_AUDIO_SYSTEM_METHOD(setA11yServicesUids),
+ MAKE_AUDIO_SYSTEM_METHOD(isHapticPlaybackSupported),
+ MAKE_AUDIO_SYSTEM_METHOD(isUltrasoundSupported),
+ MAKE_JNI_NATIVE_METHOD(
+ "getHwOffloadFormatsSupportedForBluetoothMedia", "(ILjava/util/ArrayList;)I",
+ android_media_AudioSystem_getHwOffloadFormatsSupportedForBluetoothMedia),
+ MAKE_AUDIO_SYSTEM_METHOD(setSupportedSystemUsages),
+ MAKE_AUDIO_SYSTEM_METHOD(setAllowedCapturePolicy),
+ MAKE_AUDIO_SYSTEM_METHOD(setRttEnabled),
+ MAKE_AUDIO_SYSTEM_METHOD(setAudioHalPids),
+ MAKE_AUDIO_SYSTEM_METHOD(isCallScreeningModeSupported),
+ MAKE_JNI_NATIVE_METHOD("setDevicesRoleForStrategy", "(II[I[Ljava/lang/String;)I",
+ android_media_AudioSystem_setDevicesRoleForStrategy),
+ MAKE_JNI_NATIVE_METHOD("removeDevicesRoleForStrategy", "(II[I[Ljava/lang/String;)I",
+ android_media_AudioSystem_removeDevicesRoleForStrategy),
+ MAKE_AUDIO_SYSTEM_METHOD(clearDevicesRoleForStrategy),
+ MAKE_JNI_NATIVE_METHOD("getDevicesForRoleAndStrategy", "(IILjava/util/List;)I",
+ android_media_AudioSystem_getDevicesForRoleAndStrategy),
+ MAKE_JNI_NATIVE_METHOD("setDevicesRoleForCapturePreset", "(II[I[Ljava/lang/String;)I",
+ android_media_AudioSystem_setDevicesRoleForCapturePreset),
+ MAKE_JNI_NATIVE_METHOD("addDevicesRoleForCapturePreset", "(II[I[Ljava/lang/String;)I",
+ android_media_AudioSystem_addDevicesRoleForCapturePreset),
+ MAKE_JNI_NATIVE_METHOD("removeDevicesRoleForCapturePreset", "(II[I[Ljava/lang/String;)I",
+ android_media_AudioSystem_removeDevicesRoleForCapturePreset),
+ MAKE_AUDIO_SYSTEM_METHOD(clearDevicesRoleForCapturePreset),
+ MAKE_JNI_NATIVE_METHOD("getDevicesForRoleAndCapturePreset", "(IILjava/util/List;)I",
+ android_media_AudioSystem_getDevicesForRoleAndCapturePreset),
+ MAKE_JNI_NATIVE_METHOD("getDevicesForAttributes",
+ "(Landroid/media/AudioAttributes;[Landroid/media/"
+ "AudioDeviceAttributes;Z)I",
+ android_media_AudioSystem_getDevicesForAttributes),
+ MAKE_JNI_NATIVE_METHOD("setUserIdDeviceAffinities", "(I[I[Ljava/lang/String;)I",
+ android_media_AudioSystem_setUserIdDeviceAffinities),
+ MAKE_AUDIO_SYSTEM_METHOD(removeUserIdDeviceAffinities),
+ MAKE_AUDIO_SYSTEM_METHOD(setCurrentImeUid),
+ MAKE_JNI_NATIVE_METHOD("setVibratorInfos", "(Ljava/util/List;)I",
+ android_media_AudioSystem_setVibratorInfos),
+ MAKE_JNI_NATIVE_METHOD("nativeGetSpatializer",
+ "(Landroid/media/INativeSpatializerCallback;)Landroid/os/IBinder;",
+ android_media_AudioSystem_getSpatializer),
+ MAKE_JNI_NATIVE_METHOD("canBeSpatialized",
+ "(Landroid/media/AudioAttributes;Landroid/media/AudioFormat;"
+ "[Landroid/media/AudioDeviceAttributes;)Z",
+ android_media_AudioSystem_canBeSpatialized),
+ MAKE_JNI_NATIVE_METHOD("nativeGetSoundDose",
+ "(Landroid/media/ISoundDoseCallback;)Landroid/os/IBinder;",
+ android_media_AudioSystem_nativeGetSoundDose),
+ MAKE_JNI_NATIVE_METHOD("getDirectPlaybackSupport",
+ "(Landroid/media/AudioFormat;Landroid/media/AudioAttributes;)I",
+ android_media_AudioSystem_getDirectPlaybackSupport),
+ MAKE_JNI_NATIVE_METHOD("getDirectProfilesForAttributes",
+ "(Landroid/media/AudioAttributes;Ljava/util/ArrayList;)I",
+ android_media_AudioSystem_getDirectProfilesForAttributes),
+ MAKE_JNI_NATIVE_METHOD("getSupportedMixerAttributes", "(ILjava/util/List;)I",
+ android_media_AudioSystem_getSupportedMixerAttributes),
+ MAKE_JNI_NATIVE_METHOD("setPreferredMixerAttributes",
+ "(Landroid/media/AudioAttributes;IILandroid/media/"
+ "AudioMixerAttributes;)I",
+ android_media_AudioSystem_setPreferredMixerAttributes),
+ MAKE_JNI_NATIVE_METHOD("getPreferredMixerAttributes",
+ "(Landroid/media/AudioAttributes;ILjava/util/List;)I",
+ android_media_AudioSystem_getPreferredMixerAttributes),
+ MAKE_JNI_NATIVE_METHOD("clearPreferredMixerAttributes",
+ "(Landroid/media/AudioAttributes;II)I",
+ android_media_AudioSystem_clearPreferredMixerAttributes),
+ MAKE_AUDIO_SYSTEM_METHOD(supportsBluetoothVariableLatency),
+ MAKE_AUDIO_SYSTEM_METHOD(setBluetoothVariableLatencyEnabled),
+ MAKE_AUDIO_SYSTEM_METHOD(isBluetoothVariableLatencyEnabled)};
-static const JNINativeMethod gEventHandlerMethods[] = {
- {"native_setup",
- "(Ljava/lang/Object;)V",
- (void *)android_media_AudioSystem_eventHandlerSetup},
- {"native_finalize",
- "()V",
- (void *)android_media_AudioSystem_eventHandlerFinalize},
-};
+static const JNINativeMethod gEventHandlerMethods[] =
+ {MAKE_JNI_NATIVE_METHOD("native_setup", "(Ljava/lang/Object;)V",
+ android_media_AudioSystem_eventHandlerSetup),
+ MAKE_JNI_NATIVE_METHOD_AUTOSIG("native_finalize",
+ android_media_AudioSystem_eventHandlerFinalize)};
-static const JNINativeMethod gFrameworkCapabilities[] = {
- {"native_getMaxChannelCount", "()I", (void *)android_media_AudioSystem_getMaxChannelCount},
- {"native_getMaxSampleRate", "()I", (void *)android_media_AudioSystem_getMaxSampleRate},
- {"native_getMinSampleRate", "()I", (void *)android_media_AudioSystem_getMinSampleRate},
-};
+static const JNINativeMethod gFrameworkCapabilities[] =
+ {MAKE_JNI_NATIVE_METHOD_AUTOSIG("native_getMaxChannelCount",
+ android_media_AudioSystem_getMaxChannelCount),
+ MAKE_JNI_NATIVE_METHOD_AUTOSIG("native_getMaxSampleRate",
+ android_media_AudioSystem_getMaxSampleRate),
+ MAKE_JNI_NATIVE_METHOD_AUTOSIG("native_getMinSampleRate",
+ android_media_AudioSystem_getMinSampleRate)};
int register_android_media_AudioSystem(JNIEnv *env)
{
@@ -3589,7 +3440,7 @@
gClsAudioTrackRoutingProxy =
android::FindClassOrDie(env, "android/media/AudioTrackRoutingProxy");
// make sure this reference doesn't get deleted
- gClsAudioTrackRoutingProxy = (jclass)env->NewGlobalRef(gClsAudioTrackRoutingProxy);
+ gClsAudioTrackRoutingProxy = static_cast<jclass>(env->NewGlobalRef(gClsAudioTrackRoutingProxy));
gMidAudioTrackRoutingProxy_ctor =
android::GetMethodIDOrDie(env, gClsAudioTrackRoutingProxy, "<init>", "(J)V");
@@ -3600,7 +3451,8 @@
gClsAudioRecordRoutingProxy =
android::FindClassOrDie(env, "android/media/AudioRecordRoutingProxy");
// make sure this reference doesn't get deleted
- gClsAudioRecordRoutingProxy = (jclass)env->NewGlobalRef(gClsAudioRecordRoutingProxy);
+ gClsAudioRecordRoutingProxy =
+ static_cast<jclass>(env->NewGlobalRef(gClsAudioRecordRoutingProxy));
gMidAudioRecordRoutingProxy_ctor =
android::GetMethodIDOrDie(env, gClsAudioRecordRoutingProxy, "<init>", "(J)V");
diff --git a/core/jni/android_os_GraphicsEnvironment.cpp b/core/jni/android_os_GraphicsEnvironment.cpp
index 8fc30d1..afc3cbd 100644
--- a/core/jni/android_os_GraphicsEnvironment.cpp
+++ b/core/jni/android_os_GraphicsEnvironment.cpp
@@ -50,7 +50,7 @@
appPackageNameChars.c_str(), vulkanVersion);
}
-void setAngleInfo_native(JNIEnv* env, jobject clazz, jstring path, jboolean useNativeDriver,
+void setAngleInfo_native(JNIEnv* env, jobject clazz, jstring path, jboolean useSystemAngle,
jstring packageName, jobjectArray featuresObj) {
ScopedUtfChars pathChars(env, path);
ScopedUtfChars packageNameChars(env, packageName);
@@ -73,7 +73,7 @@
}
}
- android::GraphicsEnv::getInstance().setAngleInfo(pathChars.c_str(), useNativeDriver,
+ android::GraphicsEnv::getInstance().setAngleInfo(pathChars.c_str(), useSystemAngle,
packageNameChars.c_str(), features);
}
@@ -118,7 +118,7 @@
reinterpret_cast<void*>(setGpuStats_native)},
{"setInjectLayersPrSetDumpable", "()Z",
reinterpret_cast<void*>(setInjectLayersPrSetDumpable_native)},
- {"nativeSetAngleInfo", "(Ljava/lang/String;ZLjava/lang/String;[Ljava/lang/String;)V",
+ {"setAngleInfo", "(Ljava/lang/String;ZLjava/lang/String;[Ljava/lang/String;)V",
reinterpret_cast<void*>(setAngleInfo_native)},
{"setLayerPaths", "(Ljava/lang/ClassLoader;Ljava/lang/String;)V",
reinterpret_cast<void*>(setLayerPaths_native)},
diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp
index 8bc52b8..c198797 100644
--- a/core/jni/android_util_Binder.cpp
+++ b/core/jni/android_util_Binder.cpp
@@ -1305,103 +1305,6 @@
return alive ? JNI_TRUE : JNI_FALSE;
}
-static int getprocname(pid_t pid, char *buf, size_t len) {
- char filename[32];
- FILE *f;
-
- snprintf(filename, sizeof(filename), "/proc/%d/cmdline", pid);
- f = fopen(filename, "re");
- if (!f) {
- *buf = '\0';
- return 1;
- }
- if (!fgets(buf, len, f)) {
- *buf = '\0';
- fclose(f);
- return 2;
- }
- fclose(f);
- return 0;
-}
-
-static bool push_eventlog_string(char** pos, const char* end, const char* str) {
- jint len = strlen(str);
- int space_needed = 1 + sizeof(len) + len;
- if (end - *pos < space_needed) {
- ALOGW("not enough space for string. remain=%" PRIdPTR "; needed=%d",
- end - *pos, space_needed);
- return false;
- }
- **pos = EVENT_TYPE_STRING;
- (*pos)++;
- memcpy(*pos, &len, sizeof(len));
- *pos += sizeof(len);
- memcpy(*pos, str, len);
- *pos += len;
- return true;
-}
-
-static bool push_eventlog_int(char** pos, const char* end, jint val) {
- int space_needed = 1 + sizeof(val);
- if (end - *pos < space_needed) {
- ALOGW("not enough space for int. remain=%" PRIdPTR "; needed=%d",
- end - *pos, space_needed);
- return false;
- }
- **pos = EVENT_TYPE_INT;
- (*pos)++;
- memcpy(*pos, &val, sizeof(val));
- *pos += sizeof(val);
- return true;
-}
-
-// From frameworks/base/core/java/android/content/EventLogTags.logtags:
-
-static const bool kEnableBinderSample = false;
-
-#define LOGTAG_BINDER_OPERATION 52004
-
-static void conditionally_log_binder_call(int64_t start_millis,
- IBinder* target, jint code) {
- int duration_ms = static_cast<int>(uptimeMillis() - start_millis);
-
- int sample_percent;
- if (duration_ms >= 500) {
- sample_percent = 100;
- } else {
- sample_percent = 100 * duration_ms / 500;
- if (sample_percent == 0) {
- return;
- }
- if (sample_percent < (random() % 100 + 1)) {
- return;
- }
- }
-
- char process_name[40];
- getprocname(getpid(), process_name, sizeof(process_name));
- String8 desc(target->getInterfaceDescriptor());
-
- char buf[LOGGER_ENTRY_MAX_PAYLOAD];
- buf[0] = EVENT_TYPE_LIST;
- buf[1] = 5;
- char* pos = &buf[2];
- char* end = &buf[LOGGER_ENTRY_MAX_PAYLOAD - 1]; // leave room for final \n
- if (!push_eventlog_string(&pos, end, desc.string())) return;
- if (!push_eventlog_int(&pos, end, code)) return;
- if (!push_eventlog_int(&pos, end, duration_ms)) return;
- if (!push_eventlog_string(&pos, end, process_name)) return;
- if (!push_eventlog_int(&pos, end, sample_percent)) return;
- *(pos++) = '\n'; // conventional with EVENT_TYPE_LIST apparently.
- android_bWriteLog(LOGTAG_BINDER_OPERATION, buf, pos - buf);
-}
-
-// We only measure binder call durations to potentially log them if
-// we're on the main thread.
-static bool should_time_binder_calls() {
- return (getpid() == gettid());
-}
-
static jboolean android_os_BinderProxy_transact(JNIEnv* env, jobject obj,
jint code, jobject dataObj, jobject replyObj, jint flags) // throws RemoteException
{
@@ -1428,29 +1331,10 @@
ALOGV("Java code calling transact on %p in Java object %p with code %" PRId32 "\n",
target, obj, code);
-
- bool time_binder_calls;
- int64_t start_millis;
- if (kEnableBinderSample) {
- // Only log the binder call duration for things on the Java-level main thread.
- // But if we don't
- time_binder_calls = should_time_binder_calls();
-
- if (time_binder_calls) {
- start_millis = uptimeMillis();
- }
- }
-
//printf("Transact from Java code to %p sending: ", target); data->print();
status_t err = target->transact(code, *data, reply, flags);
//if (reply) printf("Transact from Java code to %p received: ", target); reply->print();
- if (kEnableBinderSample) {
- if (time_binder_calls) {
- conditionally_log_binder_call(start_millis, target, code);
- }
- }
-
if (err == NO_ERROR) {
return JNI_TRUE;
} else if (err == UNKNOWN_TRANSACTION) {
diff --git a/core/jni/fd_utils.cpp b/core/jni/fd_utils.cpp
index 5c71f69..8e4addd 100644
--- a/core/jni/fd_utils.cpp
+++ b/core/jni/fd_utils.cpp
@@ -17,6 +17,7 @@
#include "fd_utils.h"
#include <algorithm>
+#include <utility>
#include <fcntl.h>
#include <grp.h>
@@ -174,7 +175,7 @@
class FileDescriptorInfo {
public:
// Create a FileDescriptorInfo for a given file descriptor.
- static FileDescriptorInfo* CreateFromFd(int fd, fail_fn_t fail_fn);
+ static std::unique_ptr<FileDescriptorInfo> CreateFromFd(int fd, fail_fn_t fail_fn);
// Checks whether the file descriptor associated with this object refers to
// the same description.
@@ -213,7 +214,7 @@
DISALLOW_COPY_AND_ASSIGN(FileDescriptorInfo);
};
-FileDescriptorInfo* FileDescriptorInfo::CreateFromFd(int fd, fail_fn_t fail_fn) {
+std::unique_ptr<FileDescriptorInfo> FileDescriptorInfo::CreateFromFd(int fd, fail_fn_t fail_fn) {
struct stat f_stat;
// This should never happen; the zygote should always have the right set
// of permissions required to stat all its open files.
@@ -234,7 +235,7 @@
socket_name.c_str(), fd));
}
- return new FileDescriptorInfo(fd);
+ return std::unique_ptr<FileDescriptorInfo>(new FileDescriptorInfo(fd));
}
// We only handle allowlisted regular files and character devices. Allowlisted
@@ -315,7 +316,8 @@
int open_flags = fs_flags & (kOpenFlags);
fs_flags = fs_flags & (~(kOpenFlags));
- return new FileDescriptorInfo(f_stat, file_path, fd, open_flags, fd_flags, fs_flags, offset);
+ return std::unique_ptr<FileDescriptorInfo>(
+ new FileDescriptorInfo(f_stat, file_path, fd, open_flags, fd_flags, fs_flags, offset));
}
bool FileDescriptorInfo::RefersToSameFile() const {
@@ -482,11 +484,11 @@
FileDescriptorTable* FileDescriptorTable::Create(const std::vector<int>& fds_to_ignore,
fail_fn_t fail_fn) {
std::unique_ptr<std::set<int>> open_fds = GetOpenFdsIgnoring(fds_to_ignore, fail_fn);
- std::unordered_map<int, FileDescriptorInfo*> open_fd_map;
+ std::unordered_map<int, std::unique_ptr<FileDescriptorInfo>> open_fd_map;
for (auto fd : *open_fds) {
open_fd_map[fd] = FileDescriptorInfo::CreateFromFd(fd, fail_fn);
}
- return new FileDescriptorTable(open_fd_map);
+ return new FileDescriptorTable(std::move(open_fd_map));
}
static std::unique_ptr<std::set<int>> GetOpenFdsIgnoring(const std::vector<int>& fds_to_ignore,
@@ -535,9 +537,9 @@
// Reopens all file descriptors that are contained in the table.
void FileDescriptorTable::ReopenOrDetach(fail_fn_t fail_fn) {
- std::unordered_map<int, FileDescriptorInfo*>::const_iterator it;
+ std::unordered_map<int, std::unique_ptr<FileDescriptorInfo>>::const_iterator it;
for (it = open_fd_map_.begin(); it != open_fd_map_.end(); ++it) {
- const FileDescriptorInfo* info = it->second;
+ const FileDescriptorInfo* info = it->second.get();
if (info == nullptr) {
return;
} else {
@@ -547,15 +549,11 @@
}
FileDescriptorTable::FileDescriptorTable(
- const std::unordered_map<int, FileDescriptorInfo*>& map)
- : open_fd_map_(map) {
+ std::unordered_map<int, std::unique_ptr<FileDescriptorInfo>> map)
+ : open_fd_map_(std::move(map)) {
}
-FileDescriptorTable::~FileDescriptorTable() {
- for (auto& it : open_fd_map_) {
- delete it.second;
- }
-}
+FileDescriptorTable::~FileDescriptorTable() {}
void FileDescriptorTable::RestatInternal(std::set<int>& open_fds, fail_fn_t fail_fn) {
// ART creates a file through memfd for optimization purposes. We make sure
@@ -569,7 +567,7 @@
// (b) they refer to the same file.
//
// We'll only store the last error message.
- std::unordered_map<int, FileDescriptorInfo*>::iterator it = open_fd_map_.begin();
+ std::unordered_map<int, std::unique_ptr<FileDescriptorInfo>>::iterator it = open_fd_map_.begin();
while (it != open_fd_map_.end()) {
std::set<int>::const_iterator element = open_fds.find(it->first);
if (element == open_fds.end()) {
@@ -580,7 +578,6 @@
// TODO(narayan): This will be an error in a future android release.
// error = true;
// ALOGW("Zygote closed file descriptor %d.", it->first);
- delete it->second;
it = open_fd_map_.erase(it);
} else {
// The entry from the file descriptor table is still open. Restat
@@ -588,7 +585,6 @@
if (!it->second->RefersToSameFile()) {
// The file descriptor refers to a different description. We must
// update our entry in the table.
- delete it->second;
it->second = FileDescriptorInfo::CreateFromFd(*element, fail_fn);
} else {
// It's the same file. Nothing to do here. Move on to the next open
diff --git a/core/jni/fd_utils.h b/core/jni/fd_utils.h
index a28ebf1..ac2d2a4 100644
--- a/core/jni/fd_utils.h
+++ b/core/jni/fd_utils.h
@@ -17,6 +17,7 @@
#ifndef FRAMEWORKS_BASE_CORE_JNI_FD_UTILS_H_
#define FRAMEWORKS_BASE_CORE_JNI_FD_UTILS_H_
+#include <memory>
#include <set>
#include <string>
#include <unordered_map>
@@ -98,12 +99,12 @@
void ReopenOrDetach(fail_fn_t fail_fn);
private:
- explicit FileDescriptorTable(const std::unordered_map<int, FileDescriptorInfo*>& map);
+ explicit FileDescriptorTable(std::unordered_map<int, std::unique_ptr<FileDescriptorInfo>> map);
void RestatInternal(std::set<int>& open_fds, fail_fn_t fail_fn);
// Invariant: All values in this unordered_map are non-NULL.
- std::unordered_map<int, FileDescriptorInfo*> open_fd_map_;
+ std::unordered_map<int, std::unique_ptr<FileDescriptorInfo>> open_fd_map_;
DISALLOW_COPY_AND_ASSIGN(FileDescriptorTable);
};
diff --git a/core/proto/android/input/keyboard_configured.proto b/core/proto/android/input/keyboard_configured.proto
index 1699008..0b4bf8e 100644
--- a/core/proto/android/input/keyboard_configured.proto
+++ b/core/proto/android/input/keyboard_configured.proto
@@ -47,4 +47,8 @@
// IntDef annotation at:
// services/core/java/com/android/server/input/KeyboardMetricsCollector.java
optional int32 layout_selection_criteria = 4;
+ // Keyboard layout type provided by IME
+ optional int32 ime_layout_type = 5;
+ // Language tag provided by IME (e.g. en-US, ru-Cyrl etc.)
+ optional string ime_language_tag = 6;
}
diff --git a/core/proto/android/os/system_properties.proto b/core/proto/android/os/system_properties.proto
index 3cedba0..5a3539a 100644
--- a/core/proto/android/os/system_properties.proto
+++ b/core/proto/android/os/system_properties.proto
@@ -172,7 +172,7 @@
optional Status tombstoned = 29;
optional Status ueventd = 30;
optional Status update_engine = 31;
- optional Status update_verifier_nonencrypted = 32;
+ optional Status update_verifier = 32;
optional Status virtual_touchpad = 33;
optional Status vndservicemanager = 34;
optional Status vold = 35;
diff --git a/core/proto/android/service/package.proto b/core/proto/android/service/package.proto
index 0fe2a6b..eb14db0 100644
--- a/core/proto/android/service/package.proto
+++ b/core/proto/android/service/package.proto
@@ -101,6 +101,30 @@
COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED = 4;
}
+ // Information about the state of an archived app.
+ // All fields are gathered at the time of archival.
+ message ArchiveState {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ message ArchiveActivityInfo {
+ // Corresponds to android:label in the app's locale at the time of archival.
+ optional string title = 1;
+
+ // Icon of the archived app.
+ optional string icon_bitmap_path = 2;
+
+ // Only set if the app defined a monochrome icon.
+ optional string monochrome_icon_bitmap_path = 3;
+ }
+
+ /** Information about main activities. */
+ repeated ArchiveActivityInfo activity_infos = 1;
+
+ // Corresponds to android:label of the installer responsible for the unarchival of the
+ // app. Stored in the installer's locale at the time of archival.
+ optional string installer_title = 2;
+ }
+
optional int32 id = 1;
optional InstallType install_type = 2;
// Is the app restricted by owner / admin
@@ -114,6 +138,7 @@
optional int32 distraction_flags = 10;
// UTC timestamp of first install for the user
optional int32 first_install_time_ms = 11;
+ optional ArchiveState archive_state = 12;
}
message InstallSourceProto {
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 0c8707d..baa47da 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -7663,6 +7663,24 @@
<permission android:name="android.permission.GET_ANY_PROVIDER_TYPE"
android:protectionLevel="signature" />
+
+ <!-- @hide Allows internal applications to read and synchronize non-core flags.
+ Apps without this permission can only read a subset of flags specifically intended
+ for use in "core", (i.e. third party apps). Apps with this permission can define their
+ own flags, and federate those values with other system-level apps.
+ <p>Not for use by third-party applications.
+ <p>Protection level: signature
+ -->
+ <permission android:name="android.permission.SYNC_FLAGS"
+ android:protectionLevel="signature" />
+
+ <!-- @hide Allows internal applications to override flags in the FeatureFlags service.
+ <p>Not for use by third-party applications.
+ <p>Protection level: signature
+ -->
+ <permission android:name="android.permission.WRITE_FLAGS"
+ android:protectionLevel="signature" />
+
<!-- Attribution for Geofencing service. -->
<attribution android:tag="GeofencingService" android:label="@string/geofencing_service"/>
<!-- Attribution for Country Detector. -->
diff --git a/core/res/res/values-eu/strings.xml b/core/res/res/values-eu/strings.xml
index c50b63e..31ad27b2 100644
--- a/core/res/res/values-eu/strings.xml
+++ b/core/res/res/values-eu/strings.xml
@@ -673,7 +673,7 @@
<string name="face_recalibrate_notification_content" msgid="3064513770251355594">"Sakatu hau aurpegi-eredua ezabatzeko eta, gero, gehitu aurpegia berriro"</string>
<string name="face_setup_notification_title" msgid="8843461561970741790">"Konfiguratu Aurpegi bidez desblokeatzea"</string>
<string name="face_setup_notification_content" msgid="5463999831057751676">"Telefonoa desblokeatzeko, begira iezaiozu"</string>
- <string name="face_sensor_privacy_enabled" msgid="7407126963510598508">"Aurpegi bidez desblokeatzeko aukera erabiltzeko, aktibatu "<b>"kamera erabiltzeko baimena"</b>" Ezarpenak > Pribatutasuna atalean"</string>
+ <string name="face_sensor_privacy_enabled" msgid="7407126963510598508">"Aurpegi bidez desblokeatzeko eginbidea erabiltzeko, aktibatu "<b>"kamera erabiltzeko baimena"</b>" Ezarpenak > Pribatutasuna atalean"</string>
<string name="fingerprint_setup_notification_title" msgid="2002630611398849495">"Konfiguratu telefonoa desblokeatzeko modu gehiago"</string>
<string name="fingerprint_setup_notification_content" msgid="205578121848324852">"Sakatu hau hatz-marka bat gehitzeko"</string>
<string name="fingerprint_recalibrate_notification_name" msgid="1414578431898579354">"Hatz-marka bidez desblokeatzea"</string>
diff --git a/core/res/res/values-gl/strings.xml b/core/res/res/values-gl/strings.xml
index 33d47f5..20e3812 100644
--- a/core/res/res/values-gl/strings.xml
+++ b/core/res/res/values-gl/strings.xml
@@ -256,7 +256,7 @@
<string name="bugreport_option_interactive_summary" msgid="8493795476325339542">"Usa esta opción na maioría das circunstancias. Permíteche realizar un seguimento do progreso do informe, introducir máis detalles sobre o problema e facer capturas de pantalla. É posible que omita algunhas seccións menos usadas para as que se tarda máis en facer o informe."</string>
<string name="bugreport_option_full_title" msgid="7681035745950045690">"Informe completo"</string>
<string name="bugreport_option_full_summary" msgid="1975130009258435885">"Usa esta opción para que a interferencia sexa mínima cando o teu dispositivo non responda ou funcione demasiado lento, ou ben cando precises todas as seccións do informe. Non poderás introducir máis detalles nin facer máis capturas de pantalla."</string>
- <string name="bugreport_countdown" msgid="6418620521782120755">"{count,plural, =1{Vaise facer unha captura de pantalla para o informe de erro dentro de # segundo.}other{Vaise facer unha captura de pantalla para o informe de erro dentro de # segundos.}}"</string>
+ <string name="bugreport_countdown" msgid="6418620521782120755">"{count,plural, =1{Vaise facer unha captura de pantalla para o informe de erros dentro de # segundo.}other{Vaise facer unha captura de pantalla para o informe de erros dentro de # segundos.}}"</string>
<string name="bugreport_screenshot_success_toast" msgid="7986095104151473745">"Realizouse a captura de pantalla co informe de erros"</string>
<string name="bugreport_screenshot_failure_toast" msgid="6736320861311294294">"Produciuse un erro ao realizar a captura de pantalla co informe de erros"</string>
<string name="global_action_toggle_silent_mode" msgid="8464352592860372188">"Modo silencioso"</string>
diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml
index c100063..83510e7 100644
--- a/core/res/res/values-ko/strings.xml
+++ b/core/res/res/values-ko/strings.xml
@@ -1372,7 +1372,7 @@
<string name="usb_power_notification_message" msgid="7284765627437897702">"연결된 기기를 충전합니다. 옵션을 더 보려면 탭하세요."</string>
<string name="usb_unsupported_audio_accessory_title" msgid="2335775548086533065">"아날로그 오디오 액세서리가 감지됨"</string>
<string name="usb_unsupported_audio_accessory_message" msgid="1300168007129796621">"연결된 기기가 이 휴대전화와 호환되지 않습니다. 자세히 알아보려면 탭하세요."</string>
- <string name="adb_active_notification_title" msgid="408390247354560331">"USB 디버깅 연결됨"</string>
+ <string name="adb_active_notification_title" msgid="408390247354560331">"USB 디버깅 연결됨."</string>
<string name="adb_active_notification_message" msgid="5617264033476778211">"USB 디버깅을 사용 중지하려면 탭하세요."</string>
<string name="adb_active_notification_message" product="tv" msgid="6624498401272780855">"USB 디버깅을 사용하지 않으려면 선택합니다."</string>
<string name="adbwifi_active_notification_title" msgid="6147343659168302473">"무선 디버깅 연결됨"</string>
diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml
index 7f6e9f5..0089747 100644
--- a/core/res/res/values-zh-rTW/strings.xml
+++ b/core/res/res/values-zh-rTW/strings.xml
@@ -1943,7 +1943,7 @@
<string name="country_selection_title" msgid="5221495687299014379">"地區偏好設定"</string>
<string name="search_language_hint" msgid="7004225294308793583">"請輸入語言名稱"</string>
<string name="language_picker_section_suggested" msgid="6556199184638990447">"建議語言"</string>
- <string name="language_picker_regions_section_suggested" msgid="6080131515268225316">"建議的語言"</string>
+ <string name="language_picker_regions_section_suggested" msgid="6080131515268225316">"建議地區"</string>
<string name="language_picker_section_suggested_bilingual" msgid="5932198319583556613">"建議語言"</string>
<string name="region_picker_section_suggested_bilingual" msgid="704607569328224133">"建議地區"</string>
<string name="language_picker_section_all" msgid="1985809075777564284">"所有語言"</string>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index ef51217..42a249c 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -4125,6 +4125,10 @@
-->
<string name="config_wallpaperCropperPackage" translatable="false">com.android.wallpapercropper</string>
+ <!-- Wallpaper will get top app scheduling priority if this is set to true.
+ -->
+ <bool name="config_wallpaperTopApp">false</bool>
+
<!-- True if the device supports at least one form of multi-window.
E.g. freeform, split-screen, picture-in-picture. -->
<bool name="config_supportsMultiWindow">true</bool>
@@ -4384,12 +4388,12 @@
Example: "com.android.companiondevicemanager"
See android.companion.CompanionDeviceManager.
-->
- <string name="config_companionDeviceManagerPackage" translatable="false"></string>
+ <string name="config_companionDeviceManagerPackage" translatable="false">com.android.companiondevicemanager</string>
<!-- A list of packages managing companion device(s) by the same manufacturers as the main
device. It will fall back to showing a prompt if the association has been called multiple
times in a short period.
- Note that config_companionDeviceManagerPackage and config_companionDeviceCerts are
+ Note that config_companionDevicePackages and config_companionDeviceCerts are
parallel arrays.
-->
<string-array name="config_companionDevicePackages" translatable="false"></string-array>
@@ -4397,7 +4401,7 @@
<!-- A list of SHA256 Certificates managing companion device(s) by the same manufacturers as
the main device. It will fall back to showing a prompt if the association has been called
multiple times in a short period.
- Note that config_companionDeviceCerts and config_companionDeviceManagerPackage are parallel
+ Note that config_companionDeviceCerts and config_companionDevicePackages are parallel
arrays.
Example: "1A:2B:3C:4D"
-->
@@ -5255,7 +5259,7 @@
</string-array>
<!-- The integer index of the selected option in config_udfps_touch_detection_options -->
- <integer name="config_selected_udfps_touch_detection">3</integer>
+ <integer name="config_selected_udfps_touch_detection">0</integer>
<!-- An array of arrays of side fingerprint sensor properties relative to each display.
Note: this value is temporary and is expected to be queried directly
@@ -5593,13 +5597,13 @@
<!-- Blur radius for the Option 3 in R.integer.config_letterboxBackgroundType. Values < 0 are
ignored and 0 is used. -->
- <dimen name="config_letterboxBackgroundWallpaperBlurRadius">31dp</dimen>
+ <dimen name="config_letterboxBackgroundWallpaperBlurRadius">24dp</dimen>
<!-- Alpha of a black translucent scrim showed over wallpaper letterbox background when
the Option 3 is selected for R.integer.config_letterboxBackgroundType.
Values < 0 or >= 1 are ignored and 0.0 (transparent) is used instead. -->
<item name="config_letterboxBackgroundWallaperDarkScrimAlpha" format="float" type="dimen">
- 0.5
+ 0.68
</item>
<!-- Corners appearance of the letterbox background.
@@ -5624,7 +5628,7 @@
but isn't supported on the device or both dark scrim alpha and blur radius aren't
provided.
-->
- <color name="config_letterboxBackgroundColor">@color/letterbox_background</color>
+ <color name="config_letterboxBackgroundColor">@color/system_on_secondary_fixed</color>
<!-- Horizontal position of a center of the letterboxed app window.
0 corresponds to the left side of the screen and 1 to the right side. If given value < 0
diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml
index 08c40ba..b7a5bc8 100644
--- a/core/res/res/values/config_telephony.xml
+++ b/core/res/res/values/config_telephony.xml
@@ -151,6 +151,23 @@
<integer name="config_timeout_to_receive_delivered_ack_millis">300000</integer>
<java-symbol type="integer" name="config_timeout_to_receive_delivered_ack_millis" />
+ <!-- Telephony config for services supported by satellite providers. The format of each config
+ string in the array is as follows: "PLMN_1:service_1,service_2,..."
+ where PLMN is the satellite PLMN of a provider and service is an integer with the
+ following value:
+ 1 = {@link android.telephony.NetworkRegistrationInfo#SERVICE_TYPE_VOICE}
+ 2 = {@link android.telephony.NetworkRegistrationInfo#SERVICE_TYPE_DATA}
+ 3 = {@link android.telephony.NetworkRegistrationInfo#SERVICE_TYPE_SMS}
+ 4 = {@link android.telephony.NetworkRegistrationInfo#SERVICE_TYPE_VIDEO}
+ 5 = {@link android.telephony.NetworkRegistrationInfo#SERVICE_TYPE_EMERGENCY}
+ Example of a config string: "10011:2,3"
+
+ The PLMNs not configured in this array will be ignored and will not be used for satellite
+ scanning. -->
+ <string-array name="config_satellite_services_supported_by_providers" translatable="false">
+ </string-array>
+ <java-symbol type="array" name="config_satellite_services_supported_by_providers" />
+
<!-- Whether enhanced IWLAN handover check is enabled. If enabled, telephony frameworks
will not perform handover if the target transport is out of service, or VoPS not
supported. The network will be torn down on the source transport, and will be
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 7b1e0a4..0f2c264a 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -95,8 +95,10 @@
<dimen name="navigation_bar_height_landscape_car_mode">96dp</dimen>
<!-- Width of the navigation bar when it is placed vertically on the screen in car mode -->
<dimen name="navigation_bar_width_car_mode">96dp</dimen>
- <!-- Height of notification icons in the status bar -->
+ <!-- Original dp height of notification icons in the status bar -->
<dimen name="status_bar_icon_size">22dip</dimen>
+ <!-- New sp height of notification icons in the status bar -->
+ <dimen name="status_bar_icon_size_sp">22sp</dimen>
<!-- Desired size of system icons in status bar. -->
<dimen name="status_bar_system_icon_size">15dp</dimen>
<!-- Intrinsic size of most system icons in status bar. This is the default value that
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index d828f33..a11eaa9 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1786,6 +1786,10 @@
<string name="biometric_dialog_default_title">Verify it\u2019s you</string>
<!-- Subtitle shown on the system-provided biometric dialog, asking the user to authenticate with a biometric (e.g. fingerprint or face). [CHAR LIMIT=70] -->
<string name="biometric_dialog_default_subtitle">Use your biometric to continue</string>
+ <!-- Subtitle shown on the system-provided biometric dialog, asking the user to authenticate with fingerprint. [CHAR LIMIT=70] -->
+ <string name="biometric_dialog_fingerprint_subtitle">Use your fingerprint to continue</string>
+ <!-- Subtitle shown on the system-provided biometric dialog, asking the user to authenticate with face. [CHAR LIMIT=70] -->
+ <string name="biometric_dialog_face_subtitle">Use your face to continue</string>
<!-- Subtitle shown on the system-provided biometric dialog, asking the user to authenticate with a biometric (e.g. fingerprint or face) or their screen lock credential (i.e. PIN, pattern, or password). [CHAR LIMIT=90] -->
<string name="biometric_or_screen_lock_dialog_default_subtitle">Use your biometric or screen lock to continue</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index a18d2c5..0951aec 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -661,6 +661,7 @@
<java-symbol type="string" name="cfTemplateRegisteredTime" />
<java-symbol type="string" name="chooseActivity" />
<java-symbol type="string" name="checked" />
+ <java-symbol type="string" name="config_companionDeviceManagerPackage" />
<java-symbol type="array" name="config_companionDevicePackages" />
<java-symbol type="array" name="config_companionDeviceCerts" />
<java-symbol type="string" name="config_default_dns_server" />
@@ -2306,6 +2307,7 @@
<java-symbol type="bool" name="config_alwaysUseCdmaRssi" />
<java-symbol type="dimen" name="status_bar_icon_size" />
+ <java-symbol type="dimen" name="status_bar_icon_size_sp" />
<java-symbol type="dimen" name="status_bar_system_icon_size" />
<java-symbol type="dimen" name="status_bar_system_icon_intrinsic_size" />
<java-symbol type="drawable" name="list_selector_pressed_holo_dark" />
@@ -2579,6 +2581,8 @@
<java-symbol type="string" name="biometric_or_screen_lock_app_setting_name" />
<java-symbol type="string" name="biometric_dialog_default_title" />
<java-symbol type="string" name="biometric_dialog_default_subtitle" />
+ <java-symbol type="string" name="biometric_dialog_face_subtitle" />
+ <java-symbol type="string" name="biometric_dialog_fingerprint_subtitle" />
<java-symbol type="string" name="biometric_or_screen_lock_dialog_default_subtitle" />
<java-symbol type="string" name="biometric_error_hw_unavailable" />
<java-symbol type="string" name="biometric_error_user_canceled" />
@@ -3245,6 +3249,7 @@
<!-- WallpaperManager config -->
<java-symbol type="string" name="config_wallpaperCropperPackage" />
<java-symbol type="string" name="expand_action_accessibility" />
+ <java-symbol type="bool" name="config_wallpaperTopApp" />
<java-symbol type="id" name="textSpacerNoTitle" />
<java-symbol type="id" name="titleDividerNoCustom" />
@@ -4972,7 +4977,6 @@
<!-- For VirtualDeviceManager -->
<java-symbol type="string" name="vdm_camera_access_denied" />
<java-symbol type="string" name="vdm_secure_window" />
- <java-symbol type="string" name="vdm_pip_blocked" />
<java-symbol type="color" name="camera_privacy_light_day"/>
<java-symbol type="color" name="camera_privacy_light_night"/>
diff --git a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
index b73a87c..4857741 100644
--- a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
+++ b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
@@ -884,12 +884,13 @@
mConfig.setTo(config);
++mNumOfConfigChanges;
- if (mConfigLatch != null) {
+ final CountDownLatch configLatch = mConfigLatch;
+ if (configLatch != null) {
if (mTestLatch != null) {
mTestLatch.countDown();
}
try {
- mConfigLatch.await(TIMEOUT_SEC, TimeUnit.SECONDS);
+ configLatch.await(TIMEOUT_SEC, TimeUnit.SECONDS);
} catch (InterruptedException e) {
throw new IllegalStateException(e);
}
diff --git a/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java b/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java
index 3c47630..33e81c1 100644
--- a/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java
+++ b/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java
@@ -97,8 +97,7 @@
@Test
public void testComputeForPassword_metrics() {
- final PasswordMetrics metrics = PasswordMetrics.computeForPasswordOrPin(
- "6B~0z1Z3*8A".getBytes(), /* isPin */ false);
+ final PasswordMetrics metrics = metricsForPassword("6B~0z1Z3*8A");
assertEquals(11, metrics.length);
assertEquals(4, metrics.letters);
assertEquals(3, metrics.upperCase);
@@ -136,72 +135,54 @@
@Test
public void testDetermineComplexity_lowNumeric() {
- assertEquals(PASSWORD_COMPLEXITY_LOW,
- PasswordMetrics.computeForPasswordOrPin("1234".getBytes(),
- /* isPin */true).determineComplexity());
+ assertEquals(PASSWORD_COMPLEXITY_LOW, metricsForPin("1234").determineComplexity());
}
@Test
public void testDetermineComplexity_lowNumericComplex() {
- assertEquals(PASSWORD_COMPLEXITY_LOW,
- PasswordMetrics.computeForPasswordOrPin("124".getBytes(),
- /* isPin */ true).determineComplexity());
+ assertEquals(PASSWORD_COMPLEXITY_LOW, metricsForPin("124").determineComplexity());
}
@Test
public void testDetermineComplexity_lowAlphabetic() {
- assertEquals(PASSWORD_COMPLEXITY_LOW,
- PasswordMetrics.computeForPasswordOrPin("a!".getBytes(),
- /* isPin */ false).determineComplexity());
+ assertEquals(PASSWORD_COMPLEXITY_LOW, metricsForPassword("a!").determineComplexity());
}
@Test
public void testDetermineComplexity_lowAlphanumeric() {
- assertEquals(PASSWORD_COMPLEXITY_LOW,
- PasswordMetrics.computeForPasswordOrPin("a!1".getBytes(),
- /* isPin */ false).determineComplexity());
+ assertEquals(PASSWORD_COMPLEXITY_LOW, metricsForPassword("a!1").determineComplexity());
}
@Test
public void testDetermineComplexity_mediumNumericComplex() {
- assertEquals(PASSWORD_COMPLEXITY_MEDIUM,
- PasswordMetrics.computeForPasswordOrPin("1238".getBytes(),
- /* isPin */ true).determineComplexity());
+ assertEquals(PASSWORD_COMPLEXITY_MEDIUM, metricsForPin("1238").determineComplexity());
}
@Test
public void testDetermineComplexity_mediumAlphabetic() {
- assertEquals(PASSWORD_COMPLEXITY_MEDIUM,
- PasswordMetrics.computeForPasswordOrPin("ab!c".getBytes(),
- /* isPin */ false).determineComplexity());
+ assertEquals(PASSWORD_COMPLEXITY_MEDIUM, metricsForPassword("ab!c").determineComplexity());
}
@Test
public void testDetermineComplexity_mediumAlphanumeric() {
- assertEquals(PASSWORD_COMPLEXITY_MEDIUM,
- PasswordMetrics.computeForPasswordOrPin("ab!1".getBytes(),
- /* isPin */ false).determineComplexity());
+ assertEquals(PASSWORD_COMPLEXITY_MEDIUM, metricsForPassword("ab!1").determineComplexity());
}
@Test
public void testDetermineComplexity_highNumericComplex() {
- assertEquals(PASSWORD_COMPLEXITY_HIGH,
- PasswordMetrics.computeForPasswordOrPin("12389647!".getBytes(),
- /* isPin */ true).determineComplexity());
+ assertEquals(PASSWORD_COMPLEXITY_HIGH, metricsForPin("12389647!").determineComplexity());
}
@Test
public void testDetermineComplexity_highAlphabetic() {
assertEquals(PASSWORD_COMPLEXITY_HIGH,
- PasswordMetrics.computeForPasswordOrPin("alphabetic!".getBytes(),
- /* isPin */ false).determineComplexity());
+ metricsForPassword("alphabetic!").determineComplexity());
}
@Test
public void testDetermineComplexity_highAlphanumeric() {
assertEquals(PASSWORD_COMPLEXITY_HIGH,
- PasswordMetrics.computeForPasswordOrPin("alphanumeric123!".getBytes(),
- /* isPin */ false).determineComplexity());
+ metricsForPassword("alphanumeric123!").determineComplexity());
}
@Test
@@ -425,6 +406,14 @@
patternString.getBytes()));
}
+ private static PasswordMetrics metricsForPassword(String password) {
+ return PasswordMetrics.computeForCredential(LockscreenCredential.createPassword(password));
+ }
+
+ private static PasswordMetrics metricsForPin(String pin) {
+ return PasswordMetrics.computeForCredential(LockscreenCredential.createPin(pin));
+ }
+
@Test
public void testValidateCredential_pattern() {
PasswordMetrics adminMetrics = new PasswordMetrics(CREDENTIAL_TYPE_NONE);
diff --git a/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java b/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java
index ea5e7b6..fc72f61 100644
--- a/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java
+++ b/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java
@@ -137,4 +137,81 @@
fail("Timed out");
}
}
+
+ /**
+ * Create a database with one table with three columns.
+ */
+ private void createComplexDatabase() {
+ mDatabase.beginTransaction();
+ try {
+ mDatabase.execSQL("CREATE TABLE t1 (i int, d double, t text);");
+ mDatabase.setTransactionSuccessful();
+ } finally {
+ mDatabase.endTransaction();
+ }
+ }
+
+ /**
+ * A three-value insert for the complex database.
+ */
+ private String createComplexInsert() {
+ return "INSERT INTO t1 (i, d, t) VALUES (?1, ?2, ?3)";
+ }
+
+ @Test
+ public void testAutomaticCounters() {
+ final int size = 10;
+
+ createComplexDatabase();
+
+ // Put 10 lines in the database.
+ mDatabase.beginTransaction();
+ try {
+ try (SQLiteRawStatement s = mDatabase.createRawStatement(createComplexInsert())) {
+ for (int i = 0; i < size; i++) {
+ int vi = i * 3;
+ double vd = i * 2.5;
+ String vt = String.format("text%02dvalue", i);
+ s.bindInt(1, vi);
+ s.bindDouble(2, vd);
+ s.bindText(3, vt);
+ boolean r = s.step();
+ // No row is returned by this query.
+ assertFalse(r);
+ s.reset();
+ assertEquals(i + 1, mDatabase.getLastInsertRowId());
+ assertEquals(1, mDatabase.getLastChangedRowsCount());
+ assertEquals(i + 2, mDatabase.getTotalChangedRowsCount());
+ }
+ }
+ mDatabase.setTransactionSuccessful();
+ } finally {
+ mDatabase.endTransaction();
+ }
+
+ // Put a second 10 lines in the database.
+ mDatabase.beginTransaction();
+ try {
+ try (SQLiteRawStatement s = mDatabase.createRawStatement(createComplexInsert())) {
+ for (int i = 0; i < size; i++) {
+ int vi = i * 3;
+ double vd = i * 2.5;
+ String vt = String.format("text%02dvalue", i);
+ s.bindInt(1, vi);
+ s.bindDouble(2, vd);
+ s.bindText(3, vt);
+ boolean r = s.step();
+ // No row is returned by this query.
+ assertFalse(r);
+ s.reset();
+ assertEquals(size + i + 1, mDatabase.getLastInsertRowId());
+ assertEquals(1, mDatabase.getLastChangedRowsCount());
+ assertEquals(size + i + 2, mDatabase.getTotalChangedRowsCount());
+ }
+ }
+ mDatabase.setTransactionSuccessful();
+ } finally {
+ mDatabase.endTransaction();
+ }
+ }
}
diff --git a/core/tests/coretests/src/android/database/sqlite/SQLiteRawStatementTest.java b/core/tests/coretests/src/android/database/sqlite/SQLiteRawStatementTest.java
index 59a1643..36bb8e5 100644
--- a/core/tests/coretests/src/android/database/sqlite/SQLiteRawStatementTest.java
+++ b/core/tests/coretests/src/android/database/sqlite/SQLiteRawStatementTest.java
@@ -846,59 +846,6 @@
}
@Test
- public void testLastInsertRowId() {
- final int size = 10;
-
- createComplexDatabase();
-
- // Put 10 lines in the database.
- mDatabase.beginTransaction();
- try {
- try (SQLiteRawStatement s = mDatabase.createRawStatement(createComplexInsert())) {
- for (int i = 0; i < size; i++) {
- int vi = i * 3;
- double vd = i * 2.5;
- String vt = String.format("text%02dvalue", i);
- s.bindInt(1, vi);
- s.bindDouble(2, vd);
- s.bindText(3, vt);
- boolean r = s.step();
- // No row is returned by this query.
- assertFalse(r);
- s.reset();
- assertEquals(i + 1, mDatabase.getLastInsertRowId());
- }
- }
- mDatabase.setTransactionSuccessful();
- } finally {
- mDatabase.endTransaction();
- }
-
- // Put a second 10 lines in the database.
- mDatabase.beginTransaction();
- try {
- try (SQLiteRawStatement s = mDatabase.createRawStatement(createComplexInsert())) {
- for (int i = 0; i < size; i++) {
- int vi = i * 3;
- double vd = i * 2.5;
- String vt = String.format("text%02dvalue", i);
- s.bindInt(1, vi);
- s.bindDouble(2, vd);
- s.bindText(3, vt);
- boolean r = s.step();
- // No row is returned by this query.
- assertFalse(r);
- s.reset();
- assertEquals(size + i + 1, mDatabase.getLastInsertRowId());
- }
- }
- mDatabase.setTransactionSuccessful();
- } finally {
- mDatabase.endTransaction();
- }
- }
-
- @Test
public void testUnicode() {
// Create the t1 table and put some data in it.
mDatabase.beginTransaction();
diff --git a/core/tests/coretests/src/android/flags/FeatureFlagsTest.java b/core/tests/coretests/src/android/flags/FeatureFlagsTest.java
new file mode 100644
index 0000000..3fc9439
--- /dev/null
+++ b/core/tests/coretests/src/android/flags/FeatureFlagsTest.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.flags;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+
+@SmallTest
+@Presubmit
+public class FeatureFlagsTest {
+
+ IFeatureFlagsFake mIFeatureFlagsFake = new IFeatureFlagsFake();
+ FeatureFlags mFeatureFlags = new FeatureFlags(mIFeatureFlagsFake);
+
+ @Before
+ public void setup() {
+ FeatureFlags.setInstance(mFeatureFlags);
+ }
+
+ @Test
+ public void testFusedOff_Disabled() {
+ FusedOffFlag flag = FeatureFlags.fusedOffFlag("test", "a");
+ assertThat(mFeatureFlags.isEnabled(flag)).isFalse();
+ }
+
+ @Test
+ public void testFusedOn_Enabled() {
+ FusedOnFlag flag = FeatureFlags.fusedOnFlag("test", "a");
+ assertThat(mFeatureFlags.isEnabled(flag)).isTrue();
+ }
+
+ @Test
+ public void testBooleanFlag_DefaultDisabled() {
+ BooleanFlag flag = FeatureFlags.booleanFlag("test", "a", false);
+ assertThat(mFeatureFlags.isEnabled(flag)).isFalse();
+ }
+
+ @Test
+ public void testBooleanFlag_DefaultEnabled() {
+ BooleanFlag flag = FeatureFlags.booleanFlag("test", "a", true);
+ assertThat(mFeatureFlags.isEnabled(flag)).isTrue();
+ }
+
+ @Test
+ public void testDynamicBooleanFlag_DefaultDisabled() {
+ DynamicBooleanFlag flag = FeatureFlags.dynamicBooleanFlag("test", "a", false);
+ assertThat(mFeatureFlags.isCurrentlyEnabled(flag)).isFalse();
+ }
+
+ @Test
+ public void testDynamicBooleanFlag_DefaultEnabled() {
+ DynamicBooleanFlag flag = FeatureFlags.dynamicBooleanFlag("test", "a", true);
+ assertThat(mFeatureFlags.isCurrentlyEnabled(flag)).isTrue();
+ }
+
+ @Test
+ public void testBooleanFlag_OverrideBeforeRead() {
+ BooleanFlag flag = FeatureFlags.booleanFlag("test", "a", false);
+ SyncableFlag syncableFlag = new SyncableFlag(
+ flag.getNamespace(), flag.getName(), "true", false);
+
+ mIFeatureFlagsFake.setFlagOverrides(List.of(syncableFlag));
+
+ assertThat(mFeatureFlags.isEnabled(flag)).isTrue();
+ }
+
+ @Test
+ public void testFusedOffFlag_OverrideHasNoEffect() {
+ FusedOffFlag flag = FeatureFlags.fusedOffFlag("test", "a");
+ SyncableFlag syncableFlag = new SyncableFlag(
+ flag.getNamespace(), flag.getName(), "true", false);
+
+ mIFeatureFlagsFake.setFlagOverrides(List.of(syncableFlag));
+
+ assertThat(mFeatureFlags.isEnabled(flag)).isFalse();
+ }
+
+ @Test
+ public void testFusedOnFlag_OverrideHasNoEffect() {
+ FusedOnFlag flag = FeatureFlags.fusedOnFlag("test", "a");
+ SyncableFlag syncableFlag = new SyncableFlag(
+ flag.getNamespace(), flag.getName(), "false", false);
+
+ mIFeatureFlagsFake.setFlagOverrides(List.of(syncableFlag));
+
+ assertThat(mFeatureFlags.isEnabled(flag)).isTrue();
+ }
+
+ @Test
+ public void testDynamicFlag_OverrideBeforeRead() {
+ DynamicBooleanFlag flag = FeatureFlags.dynamicBooleanFlag("test", "a", false);
+ SyncableFlag syncableFlag = new SyncableFlag(
+ flag.getNamespace(), flag.getName(), "true", true);
+
+ mIFeatureFlagsFake.setFlagOverrides(List.of(syncableFlag));
+
+ // Changes to true
+ assertThat(mFeatureFlags.isCurrentlyEnabled(flag)).isTrue();
+ }
+
+ @Test
+ public void testDynamicFlag_OverrideAfterRead() {
+ DynamicBooleanFlag flag = FeatureFlags.dynamicBooleanFlag("test", "a", false);
+ SyncableFlag syncableFlag = new SyncableFlag(
+ flag.getNamespace(), flag.getName(), "true", true);
+
+ // Starts false
+ assertThat(mFeatureFlags.isCurrentlyEnabled(flag)).isFalse();
+
+ mIFeatureFlagsFake.setFlagOverrides(List.of(syncableFlag));
+
+ // Changes to true
+ assertThat(mFeatureFlags.isCurrentlyEnabled(flag)).isTrue();
+ }
+
+ @Test
+ public void testDynamicFlag_FiresListener() {
+ DynamicBooleanFlag flag = FeatureFlags.dynamicBooleanFlag("test", "a", false);
+ AtomicBoolean called = new AtomicBoolean(false);
+ FeatureFlags.ChangeListener listener = flag1 -> called.set(true);
+
+ mFeatureFlags.addChangeListener(listener);
+
+ SyncableFlag syncableFlag = new SyncableFlag(
+ flag.getNamespace(), flag.getName(), flag.getDefault().toString(), true);
+
+ mIFeatureFlagsFake.setFlagOverrides(List.of(syncableFlag));
+
+ // Fires listener.
+ assertThat(called.get()).isTrue();
+ }
+}
diff --git a/core/tests/coretests/src/android/flags/IFeatureFlagsFake.java b/core/tests/coretests/src/android/flags/IFeatureFlagsFake.java
new file mode 100644
index 0000000..bc5d8aa
--- /dev/null
+++ b/core/tests/coretests/src/android/flags/IFeatureFlagsFake.java
@@ -0,0 +1,113 @@
+/*
+ * 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.flags;
+
+import android.os.IBinder;
+import android.os.RemoteException;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+class IFeatureFlagsFake implements IFeatureFlags {
+
+ private final Set<IFeatureFlagsCallback> mCallbacks = new HashSet<>();
+
+ List<SyncableFlag> mOverrides;
+
+ @Override
+ public IBinder asBinder() {
+ return null;
+ }
+
+ @Override
+ public List<SyncableFlag> syncFlags(List<SyncableFlag> flagList) {
+ return mOverrides == null ? flagList : mOverrides;
+ }
+
+ @Override
+ public List<SyncableFlag> queryFlags(List<SyncableFlag> flagList) {
+ return mOverrides == null ? flagList : mOverrides; }
+
+ @Override
+ public void overrideFlag(SyncableFlag syncableFlag) {
+ SyncableFlag match = findFlag(syncableFlag);
+ if (match != null) {
+ mOverrides.remove(match);
+ }
+
+ mOverrides.add(syncableFlag);
+
+ for (IFeatureFlagsCallback cb : mCallbacks) {
+ try {
+ cb.onFlagChange(syncableFlag);
+ } catch (RemoteException e) {
+ // does not happen in fakes.
+ }
+ }
+ }
+
+ @Override
+ public void resetFlag(SyncableFlag syncableFlag) {
+ SyncableFlag match = findFlag(syncableFlag);
+ if (match != null) {
+ mOverrides.remove(match);
+ }
+
+ for (IFeatureFlagsCallback cb : mCallbacks) {
+ try {
+ cb.onFlagChange(syncableFlag);
+ } catch (RemoteException e) {
+ // does not happen in fakes.
+ }
+ }
+ }
+
+ private SyncableFlag findFlag(SyncableFlag syncableFlag) {
+ SyncableFlag match = null;
+ for (SyncableFlag sf : mOverrides) {
+ if (sf.getName().equals(syncableFlag.getName())
+ && sf.getNamespace().equals(syncableFlag.getNamespace())) {
+ match = sf;
+ break;
+ }
+ }
+
+ return match;
+ }
+ @Override
+ public void registerCallback(IFeatureFlagsCallback callback) {
+ mCallbacks.add(callback);
+ }
+
+ @Override
+ public void unregisterCallback(IFeatureFlagsCallback callback) {
+ mCallbacks.remove(callback);
+ }
+
+ public void setFlagOverrides(List<SyncableFlag> flagList) {
+ mOverrides = flagList;
+ for (SyncableFlag sf : flagList) {
+ for (IFeatureFlagsCallback cb : mCallbacks) {
+ try {
+ cb.onFlagChange(sf);
+ } catch (RemoteException e) {
+ // does not happen in fakes.
+ }
+ }
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/os/VibrationEffectTest.java b/core/tests/coretests/src/android/os/VibrationEffectTest.java
index 9107236..73954da 100644
--- a/core/tests/coretests/src/android/os/VibrationEffectTest.java
+++ b/core/tests/coretests/src/android/os/VibrationEffectTest.java
@@ -38,6 +38,8 @@
import android.hardware.vibrator.IVibrator;
import android.net.Uri;
import android.os.VibrationEffect.Composition.UnreachableAfterRepeatingIndefinitelyException;
+import android.os.vibrator.PrimitiveSegment;
+import android.os.vibrator.StepSegment;
import android.platform.test.annotations.Presubmit;
import androidx.test.InstrumentationRegistry;
@@ -634,6 +636,88 @@
.validate());
}
+ @Test
+ public void testResolveOneShot() {
+ VibrationEffect.Composed resolved = DEFAULT_ONE_SHOT.resolve(51);
+ assertEquals(0.2f, ((StepSegment) resolved.getSegments().get(0)).getAmplitude());
+
+ assertThrows(IllegalArgumentException.class, () -> DEFAULT_ONE_SHOT.resolve(1000));
+ }
+
+ @Test
+ public void testResolveWaveform() {
+ VibrationEffect.Composed resolved = TEST_WAVEFORM.resolve(102);
+ assertEquals(0.4f, ((StepSegment) resolved.getSegments().get(2)).getAmplitude());
+
+ assertThrows(IllegalArgumentException.class, () -> TEST_WAVEFORM.resolve(1000));
+ }
+
+ @Test
+ public void testResolvePrebaked() {
+ VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
+ assertEquals(effect, effect.resolve(51));
+ }
+
+ @Test
+ public void testResolveComposed() {
+ VibrationEffect effect = VibrationEffect.startComposition()
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 1)
+ .compose();
+ assertEquals(effect, effect.resolve(51));
+
+ VibrationEffect.Composed resolved = VibrationEffect.startComposition()
+ .addEffect(DEFAULT_ONE_SHOT)
+ .compose()
+ .resolve(51);
+ assertEquals(0.2f, ((StepSegment) resolved.getSegments().get(0)).getAmplitude());
+ }
+
+ @Test
+ public void testScaleOneShot() {
+ VibrationEffect.Composed scaledUp = TEST_ONE_SHOT.scale(1.5f);
+ assertTrue(100 / 255f < ((StepSegment) scaledUp.getSegments().get(0)).getAmplitude());
+
+ VibrationEffect.Composed scaledDown = TEST_ONE_SHOT.scale(0.5f);
+ assertTrue(100 / 255f > ((StepSegment) scaledDown.getSegments().get(0)).getAmplitude());
+ }
+
+ @Test
+ public void testScaleWaveform() {
+ VibrationEffect.Composed scaledUp = TEST_WAVEFORM.scale(1.5f);
+ assertEquals(1f, ((StepSegment) scaledUp.getSegments().get(0)).getAmplitude(), 1e-5f);
+
+ VibrationEffect.Composed scaledDown = TEST_WAVEFORM.scale(0.5f);
+ assertTrue(1f > ((StepSegment) scaledDown.getSegments().get(0)).getAmplitude());
+ }
+
+ @Test
+ public void testScalePrebaked() {
+ VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
+
+ VibrationEffect.Composed scaledUp = effect.scale(1.5f);
+ assertEquals(effect, scaledUp);
+
+ VibrationEffect.Composed scaledDown = effect.scale(0.5f);
+ assertEquals(effect, scaledDown);
+ }
+
+ @Test
+ public void testScaleComposed() {
+ VibrationEffect.Composed effect =
+ (VibrationEffect.Composed) VibrationEffect.startComposition()
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 0.5f, 1)
+ .addEffect(TEST_ONE_SHOT)
+ .compose();
+
+ VibrationEffect.Composed scaledUp = effect.scale(1.5f);
+ assertTrue(0.5f < ((PrimitiveSegment) scaledUp.getSegments().get(0)).getScale());
+ assertTrue(100 / 255f < ((StepSegment) scaledUp.getSegments().get(1)).getAmplitude());
+
+ VibrationEffect.Composed scaledDown = effect.scale(0.5f);
+ assertTrue(0.5f > ((PrimitiveSegment) scaledDown.getSegments().get(0)).getScale());
+ assertTrue(100 / 255f > ((StepSegment) scaledDown.getSegments().get(1)).getAmplitude());
+ }
+
private void doTestApplyRepeatingWithNonRepeatingOriginal(@NotNull VibrationEffect original) {
assertTrue(original.getDuration() != Long.MAX_VALUE);
int loopDelayMs = 123;
diff --git a/core/tests/coretests/src/android/os/vibrator/persistence/VibrationEffectXmlSerializationTest.java b/core/tests/coretests/src/android/os/vibrator/persistence/VibrationEffectXmlSerializationTest.java
index c0fdf9f..b31af89 100644
--- a/core/tests/coretests/src/android/os/vibrator/persistence/VibrationEffectXmlSerializationTest.java
+++ b/core/tests/coretests/src/android/os/vibrator/persistence/VibrationEffectXmlSerializationTest.java
@@ -29,10 +29,14 @@
import android.os.VibrationEffect;
import android.os.vibrator.PrebakedSegment;
import android.platform.test.annotations.Presubmit;
+import android.util.Xml;
+
+import com.android.modules.utils.TypedXmlPullParser;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
+import org.xmlpull.v1.XmlPullParser;
import java.io.IOException;
import java.io.StringReader;
@@ -65,6 +69,61 @@
}
@Test
+ public void testParseTag_succeedAndParserPointsToEndVibrationTag() throws Exception {
+ VibrationEffect effect = VibrationEffect.startComposition()
+ .addPrimitive(PRIMITIVE_CLICK)
+ .addPrimitive(PRIMITIVE_TICK, 0.2497f)
+ .compose();
+ String xml = "<vibration>"
+ + "<primitive-effect name=\"click\"/>"
+ + "<primitive-effect name=\"tick\" scale=\"0.2497\"/>"
+ + "</vibration>";
+ VibrationEffect effect2 = VibrationEffect.startComposition()
+ .addPrimitive(PRIMITIVE_LOW_TICK, 1f, 356)
+ .addPrimitive(PRIMITIVE_SPIN, 0.6364f, 7)
+ .compose();
+ String xml2 = "<vibration>"
+ + "<primitive-effect name=\"low_tick\" delayMs=\"356\"/>"
+ + "<primitive-effect name=\"spin\" scale=\"0.6364\" delayMs=\"7\"/>"
+ + "</vibration>";
+
+ TypedXmlPullParser parser = createXmlPullParser(xml);
+ assertParseTagSucceeds(parser, effect);
+ parser.next();
+ assertEndOfDocument(parser);
+
+ // Test no-issues when an end-tag follows the vibration XML.
+ // To test this, starting with the corresponding "start-tag" is necessary.
+ parser = createXmlPullParser("<next-tag>" + xml + "</next-tag>");
+ // Move the parser once to point to the "<vibration> tag.
+ parser.next();
+ assertParseTagSucceeds(parser, effect);
+ parser.next();
+ assertEndTag(parser, "next-tag");
+
+ parser = createXmlPullParser(xml + "<next-tag>");
+ assertParseTagSucceeds(parser, effect);
+ parser.next();
+ assertStartTag(parser, "next-tag");
+
+ parser = createXmlPullParser(xml + xml2);
+ assertParseTagSucceeds(parser, effect);
+ parser.next();
+ assertParseTagSucceeds(parser, effect2);
+ parser.next();
+ assertEndOfDocument(parser);
+ }
+
+ @Test
+ public void testParseTag_badXml_throwsException() throws Exception {
+ assertParseTagFails(
+ "<vibration>random text<primitive-effect name=\"click\"/></vibration>");
+ assertParseTagFails("<bad-tag><primitive-effect name=\"click\"/></vibration>");
+ assertParseTagFails("<primitive-effect name=\"click\"/></vibration>");
+ assertParseTagFails("<vibration><primitive-effect name=\"click\"/>");
+ }
+
+ @Test
public void testPrimitives_allSucceed() throws IOException {
VibrationEffect effect = VibrationEffect.startComposition()
.addPrimitive(PRIMITIVE_CLICK)
@@ -172,6 +231,41 @@
assertThat(parse(xml, /* flags= */ 0)).isEqualTo(effect);
}
+ private TypedXmlPullParser createXmlPullParser(String xml) throws Exception {
+ TypedXmlPullParser parser = Xml.newFastPullParser();
+ parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
+ parser.setInput(new StringReader(xml));
+ parser.next(); // read START_DOCUMENT
+ return parser;
+ }
+
+ /**
+ * Asserts parsing vibration from an open TypedXmlPullParser succeeds, and that the parser
+ * points to the end "vibration" tag.
+ */
+ private void assertParseTagSucceeds(
+ TypedXmlPullParser parser, VibrationEffect effect) throws Exception {
+ assertThat(parseTag(parser)).isEqualTo(effect);
+
+ assertThat(parser.getEventType()).isEqualTo(XmlPullParser.END_TAG);
+ assertThat(parser.getName()).isEqualTo("vibration");
+ }
+
+ private void assertEndTag(TypedXmlPullParser parser, String expectedTagName) throws Exception {
+ assertThat(parser.getName()).isEqualTo(expectedTagName);
+ assertThat(parser.getEventType()).isEqualTo(parser.END_TAG);
+ }
+
+ private void assertStartTag(TypedXmlPullParser parser, String expectedTagName)
+ throws Exception {
+ assertThat(parser.getName()).isEqualTo(expectedTagName);
+ assertThat(parser.getEventType()).isEqualTo(parser.START_TAG);
+ }
+
+ private void assertEndOfDocument(TypedXmlPullParser parser) throws Exception {
+ assertThat(parser.getEventType()).isEqualTo(parser.END_DOCUMENT);
+ }
+
private void assertHiddenApisParserSucceeds(String xml, VibrationEffect effect)
throws IOException {
assertThat(parse(xml, VibrationXmlParser.FLAG_ALLOW_HIDDEN_APIS)).isEqualTo(effect);
@@ -183,6 +277,12 @@
() -> serialize(effect, /* flags= */ 0));
}
+ private void assertParseTagFails(String xml) {
+ assertThrows("Expected parsing to fail for " + xml,
+ VibrationXmlParser.VibrationXmlParserException.class,
+ () -> parseTag(createXmlPullParser(xml)));
+ }
+
private void assertPublicApisSerializerSucceeds(VibrationEffect effect,
String... expectedSegments) throws IOException {
assertSerializationContainsSegments(serialize(effect, /* flags= */ 0), expectedSegments);
@@ -214,6 +314,11 @@
return VibrationXmlParser.parse(new StringReader(xml), flags);
}
+ private static VibrationEffect parseTag(TypedXmlPullParser parser)
+ throws IOException, VibrationXmlParser.VibrationXmlParserException {
+ return VibrationXmlParser.parseTag(parser, VibrationXmlParser.FLAG_ALLOW_HIDDEN_APIS);
+ }
+
private static String serialize(VibrationEffect effect, @VibrationXmlSerializer.Flags int flags)
throws IOException {
StringWriter writer = new StringWriter();
diff --git a/core/tests/coretests/src/android/view/ViewAttachTest.java b/core/tests/coretests/src/android/view/ViewAttachTest.java
index 1a8dd99..1da724a 100644
--- a/core/tests/coretests/src/android/view/ViewAttachTest.java
+++ b/core/tests/coretests/src/android/view/ViewAttachTest.java
@@ -92,4 +92,43 @@
assertFalse(shouldDrawRoundScrollbars);
}
}
+
+ /**
+ * Make sure that on any attached view, if the view is full-screen and hosted
+ * on a round device, the round scrollbars will be displayed even if the activity
+ * window is offset.
+ *
+ * @throws Throwable
+ */
+ @UiThreadTest
+ public void testRoundScrollbarsWithMargins() throws Throwable {
+ final ViewAttachTestActivity activity = getActivity();
+ final View rootView = activity.getWindow().getDecorView();
+ final WindowManager.LayoutParams params =
+ new WindowManager.LayoutParams(
+ rootView.getWidth(),
+ rootView.getHeight(),
+ 50, /* xPosition */
+ 0, /* yPosition */
+ WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY,
+ WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN,
+ PixelFormat.TRANSLUCENT);
+
+ rootView.setLayoutParams(params);
+
+ // Configure margins to make sure they don't cause issues configuring rounded scrollbars.
+ final ViewGroup.MarginLayoutParams lp = new ViewGroup.MarginLayoutParams(1, 1);
+ lp.setMargins(1, 2, 3, 4);
+ rootView.setLayoutParams(lp);
+
+ View contentView = activity.findViewById(R.id.view_attach_view);
+ boolean shouldDrawRoundScrollbars = contentView.shouldDrawRoundScrollbar();
+
+ if (activity.getResources().getConfiguration().isScreenRound()) {
+ assertTrue(shouldDrawRoundScrollbars);
+ } else {
+ // Never draw round scrollbars on non-round devices.
+ assertFalse(shouldDrawRoundScrollbars);
+ }
+ }
}
diff --git a/core/tests/coretests/src/android/widget/RemoteViewsAdapterTest.java b/core/tests/coretests/src/android/widget/RemoteViewsAdapterTest.java
index 184b9eac..4f722ce 100644
--- a/core/tests/coretests/src/android/widget/RemoteViewsAdapterTest.java
+++ b/core/tests/coretests/src/android/widget/RemoteViewsAdapterTest.java
@@ -353,6 +353,23 @@
public boolean isCreated() {
return false;
}
+
+ @Override
+ public RemoteViews.RemoteCollectionItems getRemoteCollectionItems() {
+ RemoteViews.RemoteCollectionItems.Builder itemsBuilder =
+ new RemoteViews.RemoteCollectionItems.Builder();
+ itemsBuilder.setHasStableIds(hasStableIds())
+ .setViewTypeCount(getViewTypeCount());
+ try {
+ for (int i = 0; i < mCount; i++) {
+ itemsBuilder.addItem(getItemId(i), getViewAt(i));
+ }
+ } catch (RemoteException e) {
+ // No-op
+ }
+
+ return itemsBuilder.build();
+ }
}
private static class DistinctIntent extends Intent {
diff --git a/core/tests/coretests/src/android/window/WindowContextControllerTest.java b/core/tests/coretests/src/android/window/WindowContextControllerTest.java
index a52d2e8..467d555 100644
--- a/core/tests/coretests/src/android/window/WindowContextControllerTest.java
+++ b/core/tests/coretests/src/android/window/WindowContextControllerTest.java
@@ -24,11 +24,13 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
+import android.app.servertransaction.WindowTokenClientController;
import android.os.Binder;
import android.platform.test.annotations.Presubmit;
@@ -56,6 +58,8 @@
public class WindowContextControllerTest {
private WindowContextController mController;
@Mock
+ private WindowTokenClientController mWindowTokenClientController;
+ @Mock
private WindowTokenClient mMockToken;
@Before
@@ -63,7 +67,9 @@
MockitoAnnotations.initMocks(this);
mController = new WindowContextController(mMockToken);
doNothing().when(mMockToken).onConfigurationChanged(any(), anyInt(), anyBoolean());
- doReturn(true).when(mMockToken).attachToDisplayArea(anyInt(), anyInt(), any());
+ WindowTokenClientController.overrideInstance(mWindowTokenClientController);
+ doReturn(true).when(mWindowTokenClientController).attachToDisplayArea(
+ eq(mMockToken), anyInt(), anyInt(), any());
}
@Test(expected = IllegalStateException.class)
@@ -78,7 +84,7 @@
public void testDetachIfNeeded_NotAttachedYet_DoNothing() {
mController.detachIfNeeded();
- verify(mMockToken, never()).detachFromWindowContainerIfNeeded();
+ verify(mWindowTokenClientController, never()).detachIfNeeded(any());
}
@Test
diff --git a/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigTest.java b/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigTest.java
index 0f30cfe..246a1e7 100644
--- a/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigTest.java
+++ b/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigTest.java
@@ -22,6 +22,7 @@
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import android.content.pm.PackagePartitions;
import android.os.FileUtils;
import android.os.SystemProperties;
import android.platform.test.annotations.Presubmit;
@@ -32,6 +33,7 @@
import com.android.frameworks.coretests.R;
import com.android.internal.content.om.OverlayConfig;
import com.android.internal.content.om.OverlayConfig.IdmapInvocation;
+import com.android.internal.content.om.OverlayConfigParser.OverlayPartition;
import com.android.internal.content.om.OverlayScanner;
import org.junit.Rule;
@@ -46,6 +48,7 @@
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
+import java.util.List;
@Presubmit
@RunWith(AndroidJUnit4.class)
@@ -88,6 +91,17 @@
assertEquals(configIndex, config.configIndex);
}
+ private String generatePartitionOrderString(List<OverlayPartition> partitions) {
+ StringBuilder partitionOrder = new StringBuilder();
+ for (int i = 0; i < partitions.size(); i++) {
+ partitionOrder.append(partitions.get(i).getName());
+ if (i < partitions.size() - 1) {
+ partitionOrder.append(", ");
+ }
+ }
+ return partitionOrder.toString();
+ }
+
@Test
public void testImmutableAfterNonImmutableFails() throws IOException {
mExpectedException.expect(IllegalStateException.class);
@@ -685,4 +699,122 @@
OverlayConfig.Configuration o3 = overlayConfig.getConfiguration("three");
assertNotNull(o3);
}
+
+ @Test
+ public void testSortPartitionsWithoutXml() throws IOException {
+ ArrayList<OverlayPartition> partitions = new ArrayList<>(
+ PackagePartitions.getOrderedPartitions(OverlayPartition::new));
+
+ final OverlayConfig overlayConfig = createConfigImpl();
+ String partitionOrderFilePath = String.format("%s/%s", mTestFolder.getRoot(),
+ "/product/overlay/partition_order.xml");
+ assertEquals(false, overlayConfig.sortPartitions(partitionOrderFilePath, partitions));
+ assertEquals("system, vendor, odm, oem, product, system_ext",
+ generatePartitionOrderString(partitions));
+ }
+
+ @Test
+ public void testSortPartitionsWithInvalidXmlRootElement() throws IOException {
+ ArrayList<OverlayPartition> partitions = new ArrayList<>(
+ PackagePartitions.getOrderedPartitions(OverlayPartition::new));
+ createFile("/product/overlay/partition_order.xml",
+ "<partition-list>\n"
+ + " <partition name=\"system_ext\"/>\n"
+ + " <partition name=\"vendor\"/>\n"
+ + " <partition name=\"oem\"/>\n"
+ + " <partition name=\"odm\"/>\n"
+ + " <partition name=\"product\"/>\n"
+ + " <partition name=\"system\"/>\n"
+ + "</partition-list>\n");
+ final OverlayConfig overlayConfig = createConfigImpl();
+ String partitionOrderFilePath = String.format("%s/%s", mTestFolder.getRoot(),
+ "/product/overlay/partition_order.xml");
+ assertEquals(false, overlayConfig.sortPartitions(partitionOrderFilePath, partitions));
+ assertEquals("system, vendor, odm, oem, product, system_ext",
+ generatePartitionOrderString(partitions));
+ }
+
+ @Test
+ public void testSortPartitionsWithInvalidPartition() throws IOException {
+ ArrayList<OverlayPartition> partitions = new ArrayList<>(
+ PackagePartitions.getOrderedPartitions(OverlayPartition::new));
+ createFile("/product/overlay/partition_order.xml",
+ "<partition-order>\n"
+ + " <partition name=\"INVALID\"/>\n"
+ + " <partition name=\"vendor\"/>\n"
+ + " <partition name=\"oem\"/>\n"
+ + " <partition name=\"odm\"/>\n"
+ + " <partition name=\"product\"/>\n"
+ + " <partition name=\"system\"/>\n"
+ + "</partition-order>\n");
+ final OverlayConfig overlayConfig = createConfigImpl();
+ String partitionOrderFilePath = String.format("%s/%s", mTestFolder.getRoot(),
+ "/product/overlay/partition_order.xml");
+ assertEquals(false, overlayConfig.sortPartitions(partitionOrderFilePath, partitions));
+ assertEquals("system, vendor, odm, oem, product, system_ext",
+ generatePartitionOrderString(partitions));
+ }
+
+ @Test
+ public void testSortPartitionsWithDuplicatePartition() throws IOException {
+ ArrayList<OverlayPartition> partitions = new ArrayList<>(
+ PackagePartitions.getOrderedPartitions(OverlayPartition::new));
+ createFile("/product/overlay/partition_order.xml",
+ "<partition-order>\n"
+ + " <partition name=\"system_ext\"/>\n"
+ + " <partition name=\"system\"/>\n"
+ + " <partition name=\"vendor\"/>\n"
+ + " <partition name=\"oem\"/>\n"
+ + " <partition name=\"odm\"/>\n"
+ + " <partition name=\"product\"/>\n"
+ + " <partition name=\"system\"/>\n"
+ + "</partition-order>\n");
+ final OverlayConfig overlayConfig = createConfigImpl();
+ String partitionOrderFilePath = String.format("%s/%s", mTestFolder.getRoot(),
+ "/product/overlay/partition_order.xml");
+ assertEquals(false, overlayConfig.sortPartitions(partitionOrderFilePath, partitions));
+ assertEquals("system, vendor, odm, oem, product, system_ext",
+ generatePartitionOrderString(partitions));
+ }
+
+ @Test
+ public void testSortPartitionsWithMissingPartition() throws IOException {
+ ArrayList<OverlayPartition> partitions = new ArrayList<>(
+ PackagePartitions.getOrderedPartitions(OverlayPartition::new));
+ createFile("/product/overlay/partition_order.xml",
+ "<partition-order>\n"
+ + " <partition name=\"vendor\"/>\n"
+ + " <partition name=\"oem\"/>\n"
+ + " <partition name=\"odm\"/>\n"
+ + " <partition name=\"product\"/>\n"
+ + " <partition name=\"system\"/>\n"
+ + "</partition-order>\n");
+ final OverlayConfig overlayConfig = createConfigImpl();
+ String partitionOrderFilePath = String.format("%s/%s", mTestFolder.getRoot(),
+ "/product/overlay/partition_order.xml");
+ assertEquals(false, overlayConfig.sortPartitions(partitionOrderFilePath, partitions));
+ assertEquals("system, vendor, odm, oem, product, system_ext",
+ generatePartitionOrderString(partitions));
+ }
+
+ @Test
+ public void testSortPartitionsWithCorrectPartitionOrderXml() throws IOException {
+ ArrayList<OverlayPartition> partitions = new ArrayList<>(
+ PackagePartitions.getOrderedPartitions(OverlayPartition::new));
+ createFile("/product/overlay/partition_order.xml",
+ "<partition-order>\n"
+ + " <partition name=\"system_ext\"/>\n"
+ + " <partition name=\"vendor\"/>\n"
+ + " <partition name=\"oem\"/>\n"
+ + " <partition name=\"odm\"/>\n"
+ + " <partition name=\"product\"/>\n"
+ + " <partition name=\"system\"/>\n"
+ + "</partition-order>\n");
+ final OverlayConfig overlayConfig = createConfigImpl();
+ String partitionOrderFilePath = String.format("%s/%s", mTestFolder.getRoot(),
+ "/product/overlay/partition_order.xml");
+ assertEquals(true, overlayConfig.sortPartitions(partitionOrderFilePath, partitions));
+ assertEquals("system_ext, vendor, oem, odm, product, system",
+ generatePartitionOrderString(partitions));
+ }
}
diff --git a/data/etc/platform.xml b/data/etc/platform.xml
index 2c85fe4..c4530f6 100644
--- a/data/etc/platform.xml
+++ b/data/etc/platform.xml
@@ -346,4 +346,8 @@
<!-- Allow IMS service entitlement app to schedule jobs to run when app in background. -->
<allow-in-power-save-except-idle package="com.android.imsserviceentitlement" />
+
+ <!-- Allow device lock controller app to schedule jobs and alarms when app in background,
+ otherwise, it may not be able to enforce provision for managed devices. -->
+ <allow-in-power-save package="com.android.devicelockcontroller" />
</permissions>
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreRSACipherSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreRSACipherSpi.java
index 3bb2564..2b1515a 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreRSACipherSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreRSACipherSpi.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.content.pm.PackageManager;
import android.hardware.security.keymint.KeyParameter;
import android.security.keymaster.KeymasterDefs;
import android.security.keystore.KeyProperties;
@@ -299,6 +300,12 @@
return false;
}
+ private static boolean hasKeyMintV2() {
+ PackageManager pm = android.app.AppGlobals.getInitialApplication().getPackageManager();
+ return pm.hasSystemFeature(PackageManager.FEATURE_HARDWARE_KEYSTORE, 200)
+ && !pm.hasSystemFeature(PackageManager.FEATURE_HARDWARE_KEYSTORE, 300);
+ }
+
@Override
protected final void addAlgorithmSpecificParametersToBegin(
@NonNull List<KeyParameter> parameters, Authorization[] keyCharacteristics) {
@@ -307,11 +314,12 @@
KeymasterDefs.KM_TAG_DIGEST, mKeymasterDigest
));
// Only add the KM_TAG_RSA_OAEP_MGF_DIGEST tag to begin() if the MGF Digest is
- // present in the key properties. Keys generated prior to Android 14 did not have
- // this tag (Keystore didn't add it) so specifying any MGF digest tag would cause
- // a begin() operation (on an Android 14 device) to fail (with a key that was generated
- // on Android 13 or below).
- if (isMgfDigestTagPresentInKeyProperties(keyCharacteristics)) {
+ // present in the key properties or KeyMint version is 200. Keys generated prior to
+ // Android 14 did not have this tag (Keystore didn't add it) and hence not present in
+ // imported key as well, so specifying any MGF digest tag would cause a begin()
+ // operation (on an Android 14 device) to fail (with a key that was generated on
+ // Android 13 or below).
+ if (isMgfDigestTagPresentInKeyProperties(keyCharacteristics) || hasKeyMintV2()) {
parameters.add(KeyStore2ParameterUtils.makeEnum(
KeymasterDefs.KM_TAG_RSA_OAEP_MGF_DIGEST, mKeymasterMgf1Digest
));
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java
index 76e0e1e..55eabb0 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java
@@ -48,7 +48,7 @@
// TODO(b/241126279) Introduce constants to better version functionality
@Override
public int getVendorApiLevel() {
- return 3;
+ return 4;
}
@NonNull
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java
index 18497ad..381e9d4 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java
@@ -32,7 +32,7 @@
*/
class SplitContainer {
@NonNull
- private final TaskFragmentContainer mPrimaryContainer;
+ private TaskFragmentContainer mPrimaryContainer;
@NonNull
private final TaskFragmentContainer mSecondaryContainer;
@NonNull
@@ -46,17 +46,35 @@
@NonNull
private final IBinder mToken;
+ /**
+ * Whether the selection of which container is primary can be changed at runtime. Runtime
+ * updates is currently possible only for {@link SplitPinContainer}
+ *
+ * @see SplitPinContainer
+ */
+ private final boolean mIsPrimaryContainerMutable;
+
SplitContainer(@NonNull TaskFragmentContainer primaryContainer,
@NonNull Activity primaryActivity,
@NonNull TaskFragmentContainer secondaryContainer,
@NonNull SplitRule splitRule,
@NonNull SplitAttributes splitAttributes) {
+ this(primaryContainer, primaryActivity, secondaryContainer, splitRule, splitAttributes,
+ false /* isPrimaryContainerMutable */);
+ }
+
+ SplitContainer(@NonNull TaskFragmentContainer primaryContainer,
+ @NonNull Activity primaryActivity,
+ @NonNull TaskFragmentContainer secondaryContainer,
+ @NonNull SplitRule splitRule,
+ @NonNull SplitAttributes splitAttributes, boolean isPrimaryContainerMutable) {
mPrimaryContainer = primaryContainer;
mSecondaryContainer = secondaryContainer;
mSplitRule = splitRule;
mDefaultSplitAttributes = splitRule.getDefaultSplitAttributes();
mCurrentSplitAttributes = splitAttributes;
mToken = new Binder("SplitContainer");
+ mIsPrimaryContainerMutable = isPrimaryContainerMutable;
if (shouldFinishPrimaryWithSecondary(splitRule)) {
if (mPrimaryContainer.getRunningActivityCount() == 1
@@ -74,6 +92,13 @@
}
}
+ void setPrimaryContainer(@NonNull TaskFragmentContainer primaryContainer) {
+ if (!mIsPrimaryContainerMutable) {
+ throw new IllegalStateException("Cannot update primary TaskFragmentContainer");
+ }
+ mPrimaryContainer = primaryContainer;
+ }
+
@NonNull
TaskFragmentContainer getPrimaryContainer() {
return mPrimaryContainer;
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 4cedd41..a2f75e0 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -213,6 +213,56 @@
}
@Override
+ public boolean pinTopActivityStack(int taskId, @NonNull SplitPinRule splitPinRule) {
+ synchronized (mLock) {
+ final TaskContainer task = getTaskContainer(taskId);
+ if (task == null) {
+ Log.e(TAG, "Cannot find the task for id: " + taskId);
+ return false;
+ }
+
+ final TaskFragmentContainer topContainer =
+ task.getTopNonFinishingTaskFragmentContainer();
+ // Cannot pin the TaskFragment if no other TaskFragment behind it.
+ if (topContainer == null || task.indexOf(topContainer) <= 0) {
+ Log.w(TAG, "Cannot find an ActivityStack to pin or split");
+ return false;
+ }
+ // Abort if the top container is already pinned.
+ if (task.getSplitPinContainer() != null) {
+ Log.w(TAG, "There is already a pinned ActivityStack.");
+ return false;
+ }
+
+ // Find a valid adjacent TaskFragmentContainer
+ final TaskFragmentContainer primaryContainer =
+ task.getNonFinishingTaskFragmentContainerBelow(topContainer);
+ if (primaryContainer == null) {
+ Log.w(TAG, "Cannot find another ActivityStack to split");
+ return false;
+ }
+
+ // Registers a Split
+ final SplitPinContainer splitPinContainer = new SplitPinContainer(primaryContainer,
+ topContainer, splitPinRule, splitPinRule.getDefaultSplitAttributes());
+ task.addSplitContainer(splitPinContainer);
+
+ // Updates the Split
+ final TransactionRecord transactionRecord = mTransactionManager.startNewTransaction();
+ final WindowContainerTransaction wct = transactionRecord.getTransaction();
+ mPresenter.updateSplitContainer(splitPinContainer, wct);
+ transactionRecord.apply(false /* shouldApplyIndependently */);
+ updateCallbackIfNecessary();
+ return true;
+ }
+ }
+
+ @Override
+ public void unpinTopActivityStack(int taskId){
+ // TODO
+ }
+
+ @Override
public void setSplitAttributesCalculator(
@NonNull Function<SplitAttributesCalculatorParams, SplitAttributes> calculator) {
synchronized (mLock) {
@@ -672,7 +722,7 @@
if (targetContainer == null) {
// When there is no embedding rule matched, try to place it in the top container
// like a normal launch.
- targetContainer = taskContainer.getTopTaskFragmentContainer();
+ targetContainer = taskContainer.getTopNonFinishingTaskFragmentContainer();
}
if (targetContainer == null) {
return;
@@ -791,7 +841,8 @@
final TaskFragmentContainer container = getContainerWithActivity(activity);
if (!isOnReparent && container != null
- && container.getTaskContainer().getTopTaskFragmentContainer() != container) {
+ && container.getTaskContainer().getTopNonFinishingTaskFragmentContainer()
+ != container) {
// Do not resolve if the launched activity is not the top-most container in the Task.
return true;
}
@@ -888,7 +939,8 @@
if (taskContainer == null) {
return;
}
- final TaskFragmentContainer targetContainer = taskContainer.getTopTaskFragmentContainer();
+ final TaskFragmentContainer targetContainer =
+ taskContainer.getTopNonFinishingTaskFragmentContainer();
if (targetContainer == null) {
return;
}
@@ -1213,11 +1265,13 @@
// 3. Whether the top activity (if any) should be split with the new activity intent.
final TaskContainer taskContainer = getTaskContainer(taskId);
- if (taskContainer == null || taskContainer.getTopTaskFragmentContainer() == null) {
+ if (taskContainer == null
+ || taskContainer.getTopNonFinishingTaskFragmentContainer() == null) {
// There is no other activity in the Task to check split with.
return null;
}
- final TaskFragmentContainer topContainer = taskContainer.getTopTaskFragmentContainer();
+ final TaskFragmentContainer topContainer =
+ taskContainer.getTopNonFinishingTaskFragmentContainer();
final Activity topActivity = topContainer.getTopNonFinishingActivity();
if (topActivity != null && topActivity != launchingActivity) {
final TaskFragmentContainer container = getSecondaryContainerForSplitIfAny(wct,
@@ -1567,6 +1621,12 @@
// background.
return;
}
+ final SplitContainer splitContainer = getActiveSplitForContainer(container);
+ if (splitContainer instanceof SplitPinContainer
+ && updateSplitContainerIfNeeded(splitContainer, wct, null /* splitAttributes */)) {
+ // A SplitPinContainer exists and is updated.
+ return;
+ }
if (launchPlaceholderIfNecessary(wct, container)) {
// Placeholder was launched, the positions will be updated when the activity is added
// to the secondary container.
@@ -1579,7 +1639,6 @@
// If the info is not available yet the task fragment will be expanded when it's ready
return;
}
- SplitContainer splitContainer = getActiveSplitForContainer(container);
if (splitContainer == null) {
return;
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPinContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPinContainer.java
new file mode 100644
index 0000000..03c77a0
--- /dev/null
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPinContainer.java
@@ -0,0 +1,47 @@
+/*
+ * 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 androidx.window.extensions.embedding;
+
+import androidx.annotation.NonNull;
+
+/**
+ * Client-side descriptor of a split that holds two containers while the secondary
+ * container is pinned on top of the Task and the primary container is the container that is
+ * currently below the secondary container. The primary container could be updated to
+ * another container whenever the existing primary container is removed or no longer
+ * be the container that's right behind the secondary container.
+ */
+class SplitPinContainer extends SplitContainer {
+
+ SplitPinContainer(@NonNull TaskFragmentContainer primaryContainer,
+ @NonNull TaskFragmentContainer secondaryContainer,
+ @NonNull SplitPinRule splitPinRule,
+ @NonNull SplitAttributes splitAttributes) {
+ super(primaryContainer, primaryContainer.getTopNonFinishingActivity(), secondaryContainer,
+ splitPinRule, splitAttributes, true /* isPrimaryContainerMutable */);
+ }
+
+ @Override
+ public String toString() {
+ return "SplitPinContainer{"
+ + " primaryContainer=" + getPrimaryContainer()
+ + " secondaryContainer=" + getSecondaryContainer()
+ + " splitPinRule=" + getSplitRule()
+ + " splitAttributes" + getCurrentSplitAttributes()
+ + "}";
+ }
+}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
index 53d39d9..4dafbd1 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -17,6 +17,7 @@
package androidx.window.extensions.embedding;
import static android.content.pm.PackageManager.MATCH_ALL;
+import static android.window.TaskFragmentOperation.OP_TYPE_REORDER_TO_FRONT;
import android.app.Activity;
import android.app.ActivityThread;
@@ -39,6 +40,7 @@
import android.view.WindowMetrics;
import android.window.TaskFragmentAnimationParams;
import android.window.TaskFragmentCreationParams;
+import android.window.TaskFragmentOperation;
import android.window.WindowContainerTransaction;
import androidx.annotation.IntDef;
@@ -336,10 +338,6 @@
// value.
final SplitRule rule = splitContainer.getSplitRule();
final TaskFragmentContainer primaryContainer = splitContainer.getPrimaryContainer();
- final Activity activity = primaryContainer.getTopNonFinishingActivity();
- if (activity == null) {
- return;
- }
final TaskContainer taskContainer = splitContainer.getTaskContainer();
final TaskProperties taskProperties = taskContainer.getTaskProperties();
final SplitAttributes splitAttributes = splitContainer.getCurrentSplitAttributes();
@@ -424,6 +422,16 @@
container.setLastRequestedBounds(fragmentOptions.getInitialRelativeBounds());
container.setLastRequestedWindowingMode(fragmentOptions.getWindowingMode());
super.createTaskFragment(wct, fragmentOptions);
+
+ // Reorders the pinned TaskFragment to front to ensure it is the front-most TaskFragment.
+ final SplitPinContainer pinnedContainer =
+ container.getTaskContainer().getSplitPinContainer();
+ if (pinnedContainer != null) {
+ final TaskFragmentOperation operation = new TaskFragmentOperation.Builder(
+ OP_TYPE_REORDER_TO_FRONT).build();
+ wct.addTaskFragmentOperation(
+ pinnedContainer.getSecondaryContainer().getTaskFragmentToken(), operation);
+ }
}
@Override
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
index 4580c98..969e3ed 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
@@ -57,6 +57,10 @@
@NonNull
private final List<SplitContainer> mSplitContainers = new ArrayList<>();
+ /** Active pin split pair in this Task. */
+ @Nullable
+ private SplitPinContainer mSplitPinContainer;
+
@NonNull
private final Configuration mConfiguration;
@@ -174,11 +178,28 @@
}
@Nullable
- TaskFragmentContainer getTopTaskFragmentContainer() {
- if (mContainers.isEmpty()) {
- return null;
+ TaskFragmentContainer getTopNonFinishingTaskFragmentContainer() {
+ for (int i = mContainers.size() - 1; i >= 0; i--) {
+ final TaskFragmentContainer container = mContainers.get(i);
+ if (!container.isFinished()) {
+ return container;
+ }
}
- return mContainers.get(mContainers.size() - 1);
+ return null;
+ }
+
+ /** Gets a non-finishing container below the given one. */
+ @Nullable
+ TaskFragmentContainer getNonFinishingTaskFragmentContainerBelow(
+ @NonNull TaskFragmentContainer current) {
+ final int index = mContainers.indexOf(current);
+ for (int i = index - 1; i >= 0; i--) {
+ final TaskFragmentContainer container = mContainers.get(i);
+ if (!container.isFinished()) {
+ return container;
+ }
+ }
+ return null;
}
@Nullable
@@ -217,31 +238,57 @@
}
void addSplitContainer(@NonNull SplitContainer splitContainer) {
+ if (splitContainer instanceof SplitPinContainer) {
+ mSplitPinContainer = (SplitPinContainer) splitContainer;
+ mSplitContainers.add(splitContainer);
+ return;
+ }
+
+ // Keeps the SplitPinContainer on the top of the list.
+ mSplitContainers.remove(mSplitPinContainer);
mSplitContainers.add(splitContainer);
+ if (mSplitPinContainer != null) {
+ mSplitContainers.add(mSplitPinContainer);
+ }
}
void removeSplitContainers(@NonNull List<SplitContainer> containers) {
mSplitContainers.removeAll(containers);
}
+ void removeSplitPinContainer() {
+ mSplitContainers.remove(mSplitPinContainer);
+ mSplitPinContainer = null;
+ }
+
+ @Nullable
+ SplitPinContainer getSplitPinContainer() {
+ return mSplitPinContainer;
+ }
+
void addTaskFragmentContainer(@NonNull TaskFragmentContainer taskFragmentContainer) {
mContainers.add(taskFragmentContainer);
+ onTaskFragmentContainerUpdated();
}
void addTaskFragmentContainer(int index, @NonNull TaskFragmentContainer taskFragmentContainer) {
mContainers.add(index, taskFragmentContainer);
+ onTaskFragmentContainerUpdated();
}
void removeTaskFragmentContainer(@NonNull TaskFragmentContainer taskFragmentContainer) {
mContainers.remove(taskFragmentContainer);
+ onTaskFragmentContainerUpdated();
}
void removeTaskFragmentContainers(@NonNull List<TaskFragmentContainer> taskFragmentContainer) {
mContainers.removeAll(taskFragmentContainer);
+ onTaskFragmentContainerUpdated();
}
void clearTaskFragmentContainer() {
mContainers.clear();
+ onTaskFragmentContainerUpdated();
}
/**
@@ -254,6 +301,34 @@
return mContainers;
}
+ private void onTaskFragmentContainerUpdated() {
+ if (mSplitPinContainer == null) {
+ return;
+ }
+
+ final TaskFragmentContainer pinnedContainer = mSplitPinContainer.getSecondaryContainer();
+ final int pinnedContainerIndex = mContainers.indexOf(pinnedContainer);
+ if (pinnedContainerIndex <= 0) {
+ removeSplitPinContainer();
+ return;
+ }
+
+ // Ensure the pinned container is top-most.
+ if (pinnedContainerIndex != mContainers.size() - 1) {
+ mContainers.remove(pinnedContainer);
+ mContainers.add(pinnedContainer);
+ }
+
+ // Update the primary container adjacent to the pinned container if needed.
+ final TaskFragmentContainer adjacentContainer =
+ getNonFinishingTaskFragmentContainerBelow(pinnedContainer);
+ if (adjacentContainer == null) {
+ removeSplitPinContainer();
+ } else if (mSplitPinContainer.getPrimaryContainer() != adjacentContainer) {
+ mSplitPinContainer.setPrimaryContainer(adjacentContainer);
+ }
+ }
+
/** Adds the descriptors of split states in this Task to {@code outSplitStates}. */
void getSplitStates(@NonNull List<SplitInfo> outSplitStates) {
for (SplitContainer container : mSplitContainers) {
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
index a45a8a1..ccf9552 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
@@ -98,14 +98,6 @@
mTaskFragmentOrganizer = taskFragmentOrganizer;
}
- /** Registers to listen to {@link CommonFoldingFeature} changes */
- public void addFoldingStateChangedCallback(
- java.util.function.Consumer<List<CommonFoldingFeature>> consumer) {
- synchronized (mLock) {
- mFoldingFeatureProducer.addDataChangedCallback(consumer);
- }
- }
-
/**
* Adds a listener interested in receiving updates to {@link WindowLayoutInfo}
*
@@ -304,7 +296,7 @@
* {@link IllegalArgumentException} since this can cause negative UI effects down stream.
*
* @param context a proxy for the {@link android.view.Window} that contains the
- * {@link DisplayFeature}.
+ * {@link DisplayFeature}.
* @return a {@link List} of {@link DisplayFeature}s that are within the
* {@link android.view.Window} of the {@link Activity}
*/
@@ -336,10 +328,32 @@
rotateRectToDisplayRotation(displayId, featureRect);
transformToWindowSpaceRect(windowConfiguration, featureRect);
- if (!isZero(featureRect)) {
+ if (isZero(featureRect)) {
// TODO(b/228641877): Remove guarding when fixed.
- features.add(new FoldingFeature(featureRect, baseFeature.getType(), state));
+ continue;
}
+ if (featureRect.left != 0 && featureRect.top != 0) {
+ throw new IllegalArgumentException("Bounding rectangle must start at the top or "
+ + "left of the window. BaseFeatureRect: " + baseFeature.getRect()
+ + ", FeatureRect: " + featureRect
+ + ", WindowConfiguration: " + windowConfiguration);
+
+ }
+ if (featureRect.left == 0
+ && featureRect.width() != windowConfiguration.getBounds().width()) {
+ throw new IllegalArgumentException("Horizontal FoldingFeature must have full width."
+ + " BaseFeatureRect: " + baseFeature.getRect()
+ + ", FeatureRect: " + featureRect
+ + ", WindowConfiguration: " + windowConfiguration);
+ }
+ if (featureRect.top == 0
+ && featureRect.height() != windowConfiguration.getBounds().height()) {
+ throw new IllegalArgumentException("Vertical FoldingFeature must have full height."
+ + " BaseFeatureRect: " + baseFeature.getRect()
+ + ", FeatureRect: " + featureRect
+ + ", WindowConfiguration: " + windowConfiguration);
+ }
+ features.add(new FoldingFeature(featureRect, baseFeature.getType(), state));
}
return features;
}
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 9e26472..9af1fe91 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
@@ -1462,6 +1462,51 @@
verify(testRecord).apply(eq(false));
}
+ @Test
+ public void testPinTopActivityStack() {
+ // Create two activities.
+ final Activity primaryActivity = createMockActivity();
+ final Activity secondaryActivity = createMockActivity();
+
+ // Unable to pin if not being embedded.
+ SplitPinRule splitPinRule = new SplitPinRule.Builder(new SplitAttributes.Builder().build(),
+ parentWindowMetrics -> true /* parentWindowMetricsPredicate */).build();
+ assertFalse(mSplitController.pinTopActivityStack(TASK_ID, splitPinRule));
+
+ // Split the two activities.
+ addSplitTaskFragments(primaryActivity, secondaryActivity);
+ final TaskFragmentContainer primaryContainer =
+ mSplitController.getContainerWithActivity(primaryActivity);
+ spyOn(primaryContainer);
+
+ // Unable to pin if no valid TaskFragment.
+ doReturn(true).when(primaryContainer).isFinished();
+ assertFalse(mSplitController.pinTopActivityStack(TASK_ID, splitPinRule));
+
+ // Otherwise, should pin successfully.
+ doReturn(false).when(primaryContainer).isFinished();
+ assertTrue(mSplitController.pinTopActivityStack(TASK_ID, splitPinRule));
+
+ // Unable to pin if there is already a pinned TaskFragment
+ assertFalse(mSplitController.pinTopActivityStack(TASK_ID, splitPinRule));
+
+ // Unable to pin on an unknown Task.
+ assertFalse(mSplitController.pinTopActivityStack(TASK_ID + 1, splitPinRule));
+
+ // Gets the current size of all the SplitContainers.
+ final TaskContainer taskContainer = mSplitController.getTaskContainer(TASK_ID);
+ final int splitContainerCount = taskContainer.getSplitContainers().size();
+
+ // Create another activity and split with primary activity.
+ final Activity thirdActivity = createMockActivity();
+ addSplitTaskFragments(primaryActivity, thirdActivity);
+
+ // Ensure another SplitContainer is added and the pinned TaskFragment still on top
+ assertTrue(taskContainer.getSplitContainers().size() == splitContainerCount + +1);
+ assertTrue(mSplitController.getTopActiveContainer(TASK_ID).getTopNonFinishingActivity()
+ == secondaryActivity);
+ }
+
/** Creates a mock activity in the organizer process. */
private Activity createMockActivity() {
return createMockActivity(TASK_ID);
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java
index 11af1d1..000c65a 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java
@@ -135,15 +135,15 @@
@Test
public void testGetTopTaskFragmentContainer() {
final TaskContainer taskContainer = createTestTaskContainer();
- assertNull(taskContainer.getTopTaskFragmentContainer());
+ assertNull(taskContainer.getTopNonFinishingTaskFragmentContainer());
final TaskFragmentContainer tf0 = new TaskFragmentContainer(null /* activity */,
new Intent(), taskContainer, mController, null /* pairedPrimaryContainer */);
- assertEquals(tf0, taskContainer.getTopTaskFragmentContainer());
+ assertEquals(tf0, taskContainer.getTopNonFinishingTaskFragmentContainer());
final TaskFragmentContainer tf1 = new TaskFragmentContainer(null /* activity */,
new Intent(), taskContainer, mController, null /* pairedPrimaryContainer */);
- assertEquals(tf1, taskContainer.getTopTaskFragmentContainer());
+ assertEquals(tf1, taskContainer.getTopNonFinishingTaskFragmentContainer());
}
@Test
diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp
index 35e88ed..a605e2b 100644
--- a/libs/WindowManager/Shell/Android.bp
+++ b/libs/WindowManager/Shell/Android.bp
@@ -115,8 +115,7 @@
name: "protolog.json.gz",
srcs: [":generate-wm_shell_protolog.json"],
out: ["wmshell.protolog.json.gz"],
- cmd: "$(location minigzip) -c < $(in) > $(out)",
- tools: ["minigzip"],
+ cmd: "gzip -c < $(in) > $(out)",
}
prebuilt_etc {
diff --git a/libs/WindowManager/Shell/res/values-watch/dimen.xml b/libs/WindowManager/Shell/res/values-watch/dimen.xml
new file mode 100644
index 0000000..362e72c
--- /dev/null
+++ b/libs/WindowManager/Shell/res/values-watch/dimen.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<resources>
+ <!-- The acceptable area ratio of fg icon area/bg icon area, i.e. (48 X 48) / (72 x 72) -->
+ <item type="dimen" format="float" name="splash_icon_enlarge_foreground_threshold">0.44</item>
+ <!-- Scaling factor applied to splash icons without provided background i.e. (60 / 48) -->
+ <item type="dimen" format="float" name="splash_icon_no_background_scale_factor">1.25</item>
+</resources>
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index 64fed1c..ac73e1d 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -421,4 +421,9 @@
<!-- 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 acceptable area ratio of fg icon area/bg icon area, i.e. (72 x 72) / (108 x 108) -->
+ <item type="dimen" format="float" name="splash_icon_enlarge_foreground_threshold">0.44</item>
+ <!-- Scaling factor applied to splash icons without provided background i.e. (192 / 160) -->
+ <item type="dimen" format="float" name="splash_icon_no_background_scale_factor">1.2</item>
</resources>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index 3eec513..e69825f2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -1079,9 +1079,9 @@
* <p>This is used by external callers (launcher).
*/
@VisibleForTesting
- public void expandStackAndSelectBubbleFromLauncher(String key, int bubbleBarXCoordinate,
- int bubbleBarYCoordinate) {
- mBubblePositioner.setBubbleBarPosition(bubbleBarXCoordinate, bubbleBarYCoordinate);
+ public void expandStackAndSelectBubbleFromLauncher(String key, int bubbleBarOffsetX,
+ int bubbleBarOffsetY) {
+ mBubblePositioner.setBubbleBarPosition(bubbleBarOffsetX, bubbleBarOffsetY);
if (BubbleOverflow.KEY.equals(key)) {
mBubbleData.setSelectedBubbleFromLauncher(mBubbleData.getOverflow());
@@ -2092,10 +2092,10 @@
}
@Override
- public void showBubble(String key, int bubbleBarXCoordinate, int bubbleBarYCoordinate) {
+ public void showBubble(String key, int bubbleBarOffsetX, int bubbleBarOffsetY) {
mMainExecutor.execute(
() -> mController.expandStackAndSelectBubbleFromLauncher(
- key, bubbleBarXCoordinate, bubbleBarYCoordinate));
+ key, bubbleBarOffsetX, bubbleBarOffsetY));
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
index 6a5e310..ee6996d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
@@ -718,9 +718,16 @@
mShowingInBubbleBar = showingInBubbleBar;
}
- /** Sets the position of the bubble bar in screen coordinates. */
- public void setBubbleBarPosition(int x, int y) {
- mBubbleBarPosition.set(x, y);
+ /**
+ * Sets the position of the bubble bar in screen coordinates.
+ *
+ * @param offsetX the offset of the bubble bar from the edge of the screen on the X axis
+ * @param offsetY the offset of the bubble bar from the edge of the screen on the Y axis
+ */
+ public void setBubbleBarPosition(int offsetX, int offsetY) {
+ mBubbleBarPosition.set(
+ getAvailableRect().width() - offsetX,
+ getAvailableRect().height() + mInsets.top + mInsets.bottom - offsetY);
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
index 59332f4..351319f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
@@ -29,8 +29,7 @@
oneway void unregisterBubbleListener(in IBubblesListener listener) = 2;
- oneway void showBubble(in String key, in int bubbleBarXCoordinate,
- in int bubbleBarYCoordinate) = 3;
+ oneway void showBubble(in String key, in int bubbleBarOffsetX, in int bubbleBarOffsetY) = 3;
oneway void removeBubble(in String key, in int reason) = 4;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleInfo.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleInfo.java
index 21355a3..24608d6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleInfo.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleInfo.java
@@ -129,6 +129,11 @@
return (mFlags & Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION) != 0;
}
+ /** Sets the flags for this bubble. */
+ public void setFlags(int flags) {
+ mFlags = flags;
+ }
+
@Override
public boolean equals(Object o) {
if (this == o) return true;
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 6d14440..f8d7b6b 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
@@ -202,17 +202,18 @@
}
/**
- * Moves a single task to freeform and sets the taskBounds to the passed in bounds,
- * startBounds
+ * The first part of the animated move to desktop transition. Applies the changes to move task
+ * to desktop mode and sets the taskBounds to the passed in bounds, startBounds. This is
+ * followed with a call to {@link finishMoveToDesktop} or {@link cancelMoveToDesktop}.
*/
- fun moveToFreeform(
+ fun startMoveToDesktop(
taskInfo: RunningTaskInfo,
startBounds: Rect,
dragToDesktopValueAnimator: MoveToDesktopAnimator
) {
KtProtoLog.v(
WM_SHELL_DESKTOP_MODE,
- "DesktopTasksController: moveToFreeform with bounds taskId=%d",
+ "DesktopTasksController: startMoveToDesktop taskId=%d",
taskInfo.taskId
)
val wct = WindowContainerTransaction()
@@ -221,18 +222,21 @@
wct.setBounds(taskInfo.token, startBounds)
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
- enterDesktopTaskTransitionHandler.startMoveToFreeformAnimation(wct,
- dragToDesktopValueAnimator, mOnAnimationFinishedCallback)
+ enterDesktopTaskTransitionHandler.startMoveToDesktop(wct, dragToDesktopValueAnimator,
+ mOnAnimationFinishedCallback)
} else {
shellTaskOrganizer.applyTransaction(wct)
}
}
- /** Brings apps to front and sets freeform task bounds */
- private fun moveToDesktopWithAnimation(taskInfo: RunningTaskInfo, freeformBounds: Rect) {
+ /**
+ * The second part of the animated move to desktop transition, called after
+ * {@link startMoveToDesktop}. Brings apps to front and sets freeform task bounds.
+ */
+ private fun finalizeMoveToDesktop(taskInfo: RunningTaskInfo, freeformBounds: Rect) {
KtProtoLog.v(
WM_SHELL_DESKTOP_MODE,
- "DesktopTasksController: moveToDesktop with animation taskId=%d",
+ "DesktopTasksController: finalizeMoveToDesktop taskId=%d",
taskInfo.taskId
)
val wct = WindowContainerTransaction()
@@ -241,8 +245,8 @@
wct.setBounds(taskInfo.token, freeformBounds)
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
- enterDesktopTaskTransitionHandler.startTransition(
- Transitions.TRANSIT_ENTER_DESKTOP_MODE, wct, mOnAnimationFinishedCallback)
+ enterDesktopTaskTransitionHandler.finalizeMoveToDesktop(wct,
+ mOnAnimationFinishedCallback)
} else {
shellTaskOrganizer.applyTransaction(wct)
releaseVisualIndicator()
@@ -272,13 +276,14 @@
}
/**
- * Move a task to fullscreen after being dragged from fullscreen and released back into
- * status bar area
+ * The second part of the animated move to desktop transition, called after
+ * {@link startMoveToDesktop}. Move a task to fullscreen after being dragged from fullscreen
+ * and released back into status bar area.
*/
- fun cancelMoveToFreeform(task: RunningTaskInfo, moveToDesktopAnimator: MoveToDesktopAnimator) {
+ fun cancelMoveToDesktop(task: RunningTaskInfo, moveToDesktopAnimator: MoveToDesktopAnimator) {
KtProtoLog.v(
WM_SHELL_DESKTOP_MODE,
- "DesktopTasksController: cancelMoveToFreeform taskId=%d",
+ "DesktopTasksController: cancelMoveToDesktop taskId=%d",
task.taskId
)
val wct = WindowContainerTransaction()
@@ -784,7 +789,7 @@
taskInfo: RunningTaskInfo,
freeformBounds: Rect
) {
- moveToDesktopWithAnimation(taskInfo, freeformBounds)
+ finalizeMoveToDesktop(taskInfo, freeformBounds)
}
private fun getStatusBarHeight(taskInfo: RunningTaskInfo): Int {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java
index 650cac5..1acf783 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java
@@ -79,7 +79,7 @@
* @param wct WindowContainerTransaction for transition
* @param onAnimationEndCallback to be called after animation
*/
- public void startTransition(@WindowManager.TransitionType int type,
+ private void startTransition(@WindowManager.TransitionType int type,
@NonNull WindowContainerTransaction wct,
Consumer<SurfaceControl.Transaction> onAnimationEndCallback) {
mOnAnimationFinishedCallback = onAnimationEndCallback;
@@ -88,17 +88,29 @@
}
/**
- * Starts Transition of type TRANSIT_ENTER_FREEFORM
+ * Starts Transition of type TRANSIT_START_MOVE_TO_DESKTOP_MODE
* @param wct WindowContainerTransaction for transition
* @param moveToDesktopAnimator Animator that shrinks and positions task during two part move
* to desktop animation
* @param onAnimationEndCallback to be called after animation
*/
- public void startMoveToFreeformAnimation(@NonNull WindowContainerTransaction wct,
+ public void startMoveToDesktop(@NonNull WindowContainerTransaction wct,
@NonNull MoveToDesktopAnimator moveToDesktopAnimator,
Consumer<SurfaceControl.Transaction> onAnimationEndCallback) {
mMoveToDesktopAnimator = moveToDesktopAnimator;
- startTransition(Transitions.TRANSIT_ENTER_FREEFORM, wct, onAnimationEndCallback);
+ startTransition(Transitions.TRANSIT_START_MOVE_TO_DESKTOP_MODE, wct,
+ onAnimationEndCallback);
+ }
+
+ /**
+ * Starts Transition of type TRANSIT_FINALIZE_MOVE_TO_DESKTOP_MODE
+ * @param wct WindowContainerTransaction for transition
+ * @param onAnimationEndCallback to be called after animation
+ */
+ public void finalizeMoveToDesktop(@NonNull WindowContainerTransaction wct,
+ Consumer<SurfaceControl.Transaction> onAnimationEndCallback) {
+ startTransition(Transitions.TRANSIT_FINALIZE_MOVE_TO_DESKTOP_MODE, wct,
+ onAnimationEndCallback);
}
/**
@@ -155,7 +167,7 @@
}
final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
- if (type == Transitions.TRANSIT_ENTER_FREEFORM
+ if (type == Transitions.TRANSIT_START_MOVE_TO_DESKTOP_MODE
&& taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
// Transitioning to freeform but keeping fullscreen bounds, so the crop is set
// to null and we don't require an animation
@@ -182,7 +194,7 @@
}
Rect endBounds = change.getEndAbsBounds();
- if (type == Transitions.TRANSIT_ENTER_DESKTOP_MODE
+ if (type == Transitions.TRANSIT_FINALIZE_MOVE_TO_DESKTOP_MODE
&& taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM
&& !endBounds.isEmpty()) {
// This Transition animates a task to freeform bounds after being dragged into freeform
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index b14c3c1..08da485 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -1927,6 +1927,7 @@
pw.println(innerPrefix + "mLeash=" + mLeash);
pw.println(innerPrefix + "mState=" + mPipTransitionState.getTransitionState());
pw.println(innerPrefix + "mPictureInPictureParams=" + mPictureInPictureParams);
+ mPipTransitionController.dump(pw, innerPrefix);
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index 73eb62a..e3d53fc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -72,6 +72,7 @@
import com.android.wm.shell.transition.Transitions;
import com.android.wm.shell.util.TransitionUtil;
+import java.io.PrintWriter;
import java.util.Optional;
/**
@@ -451,6 +452,9 @@
@Override
public void forceFinishTransition() {
+ // mFinishCallback might be null with an outdated mCurrentPipTaskToken
+ // for example, when app crashes while in PiP and exit transition has not started
+ mCurrentPipTaskToken = null;
if (mFinishCallback == null) return;
mFinishCallback.onTransitionFinished(null /* wct */, null /* callback */);
mFinishCallback = null;
@@ -1137,4 +1141,12 @@
PipMenuController.ALPHA_NO_CHANGE);
mPipMenuController.updateMenuBounds(destinationBounds);
}
+
+ @Override
+ public void dump(PrintWriter pw, String prefix) {
+ final String innerPrefix = prefix + " ";
+ pw.println(prefix + TAG);
+ pw.println(innerPrefix + "mCurrentPipTaskToken=" + mCurrentPipTaskToken);
+ pw.println(innerPrefix + "mFinishCallback=" + mFinishCallback);
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
index e1bcd70c..6362793 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
@@ -42,6 +42,7 @@
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
+import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
@@ -283,4 +284,9 @@
*/
void onPipTransitionCanceled(int direction);
}
+
+ /**
+ * Dumps internal states.
+ */
+ public void dump(PrintWriter pw, String prefix) {}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 0567c7f..7d62f58 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -740,12 +740,12 @@
mMainStage.activate(wct, false /* reparent */);
}
+ setSideStagePosition(splitPosition, wct);
mSplitLayout.setDivideRatio(splitRatio);
updateWindowBounds(mSplitLayout, wct);
wct.reorder(mRootTaskInfo.token, true);
setRootForceTranslucent(false, wct);
- setSideStagePosition(splitPosition, wct);
options1 = options1 != null ? options1 : new Bundle();
addActivityOptions(options1, mSideStage);
if (shortcutInfo1 != null) {
@@ -2564,6 +2564,9 @@
// so don't handle it.
Log.e(TAG, "Somehow removed the last task in a stage outside of a proper "
+ "transition.");
+ // This new transition would be merged to current one so we need to clear
+ // tile manually here.
+ clearSplitPairedInRecents(EXIT_REASON_APP_FINISHED);
final WindowContainerTransaction wct = new WindowContainerTransaction();
final int dismissTop = (dismissStages.size() == 1
&& getStageType(dismissStages.valueAt(0)) == STAGE_TYPE_MAIN)
@@ -2747,6 +2750,12 @@
Log.w(TAG, splitFailureMessage("startPendingEnterAnimation",
"launched 2 tasks in split, but didn't receive "
+ "2 tasks in transition. Possibly one of them failed to launch"));
+ if (mRecentTasks.isPresent() && mainChild != null) {
+ mRecentTasks.get().removeSplitPair(mainChild.getTaskInfo().taskId);
+ }
+ if (mRecentTasks.isPresent() && sideChild != null) {
+ mRecentTasks.get().removeSplitPair(sideChild.getTaskInfo().taskId);
+ }
mSplitUnsupportedToast.show();
return true;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
index dc91a11..84dcd4d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
@@ -112,28 +112,15 @@
*/
static final long MAX_ANIMATION_DURATION = MINIMAL_ANIMATION_DURATION + TIME_WINDOW_DURATION;
- // The acceptable area ratio of foreground_icon_area/background_icon_area, if there is an
- // icon which it's non-transparent foreground area is similar to it's background area, then
- // do not enlarge the foreground drawable.
- // For example, an icon with the foreground 108*108 opaque pixels and it's background
- // also 108*108 pixels, then do not enlarge this icon if only need to show foreground icon.
- private static final float ENLARGE_FOREGROUND_ICON_THRESHOLD = (72f * 72f) / (108f * 108f);
-
- /**
- * If the developer doesn't specify a background for the icon, we slightly scale it up.
- *
- * The background is either manually specified in the theme or the Adaptive Icon
- * background is used if it's different from the window background.
- */
- private static final float NO_BACKGROUND_SCALE = 192f / 160;
private final Context mContext;
private final HighResIconProvider mHighResIconProvider;
-
private int mIconSize;
private int mDefaultIconSize;
private int mBrandingImageWidth;
private int mBrandingImageHeight;
private int mMainWindowShiftLength;
+ private float mEnlargeForegroundIconThreshold;
+ private float mNoBackgroundScale;
private int mLastPackageContextConfigHash;
private final TransactionPool mTransactionPool;
private final SplashScreenWindowAttrs mTmpAttrs = new SplashScreenWindowAttrs();
@@ -336,6 +323,10 @@
com.android.wm.shell.R.dimen.starting_surface_brand_image_height);
mMainWindowShiftLength = mContext.getResources().getDimensionPixelSize(
com.android.wm.shell.R.dimen.starting_surface_exit_animation_window_shift_length);
+ mEnlargeForegroundIconThreshold = mContext.getResources().getFloat(
+ com.android.wm.shell.R.dimen.splash_icon_enlarge_foreground_threshold);
+ mNoBackgroundScale = mContext.getResources().getFloat(
+ com.android.wm.shell.R.dimen.splash_icon_no_background_scale_factor);
}
/**
@@ -604,14 +595,14 @@
// There is no background below the icon, so scale the icon up
if (mTmpAttrs.mIconBgColor == Color.TRANSPARENT
|| mTmpAttrs.mIconBgColor == mThemeColor) {
- mFinalIconSize *= NO_BACKGROUND_SCALE;
+ mFinalIconSize *= mNoBackgroundScale;
}
createIconDrawable(iconDrawable, false /* legacy */, false /* loadInDetail */);
} else {
final float iconScale = (float) mIconSize / (float) mDefaultIconSize;
final int densityDpi = mContext.getResources().getConfiguration().densityDpi;
final int scaledIconDpi =
- (int) (0.5f + iconScale * densityDpi * NO_BACKGROUND_SCALE);
+ (int) (0.5f + iconScale * densityDpi * mNoBackgroundScale);
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "getIcon");
iconDrawable = mHighResIconProvider.getIcon(
mActivityInfo, densityDpi, scaledIconDpi);
@@ -693,8 +684,8 @@
// Reference AdaptiveIcon description, outer is 108 and inner is 72, so we
// scale by 192/160 if we only draw adaptiveIcon's foreground.
final float noBgScale =
- iconColor.mFgNonTranslucentRatio < ENLARGE_FOREGROUND_ICON_THRESHOLD
- ? NO_BACKGROUND_SCALE : 1f;
+ iconColor.mFgNonTranslucentRatio < mEnlargeForegroundIconThreshold
+ ? mNoBackgroundScale : 1f;
// Using AdaptiveIconDrawable here can help keep the shape consistent with the
// current settings.
mFinalIconSize = (int) (0.5f + mIconSize * noBgScale);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellSharedConstants.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellSharedConstants.java
index 5f54f58..56c0d0e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellSharedConstants.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellSharedConstants.java
@@ -38,8 +38,6 @@
public static final String KEY_EXTRA_SHELL_RECENT_TASKS = "extra_shell_recent_tasks";
// See IBackAnimation.aidl
public static final String KEY_EXTRA_SHELL_BACK_ANIMATION = "extra_shell_back_animation";
- // See IFloatingTasks.aidl
- public static final String KEY_EXTRA_SHELL_FLOATING_TASKS = "extra_shell_floating_tasks";
// See IDesktopMode.aidl
public static final String KEY_EXTRA_SHELL_DESKTOP_MODE = "extra_shell_desktop_mode";
// See IDragAndDrop.aidl
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index 7565996..4ca383f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -149,11 +149,13 @@
/** Transition type for maximize to freeform transition. */
public static final int TRANSIT_RESTORE_FROM_MAXIMIZE = WindowManager.TRANSIT_FIRST_CUSTOM + 9;
- /** Transition type to freeform in desktop mode. */
- public static final int TRANSIT_ENTER_FREEFORM = WindowManager.TRANSIT_FIRST_CUSTOM + 10;
+ /** Transition type for starting the move to desktop mode. */
+ public static final int TRANSIT_START_MOVE_TO_DESKTOP_MODE =
+ WindowManager.TRANSIT_FIRST_CUSTOM + 10;
- /** Transition type to freeform in desktop mode. */
- public static final int TRANSIT_ENTER_DESKTOP_MODE = WindowManager.TRANSIT_FIRST_CUSTOM + 11;
+ /** Transition type for finalizing the move to desktop mode. */
+ public static final int TRANSIT_FINALIZE_MOVE_TO_DESKTOP_MODE =
+ WindowManager.TRANSIT_FIRST_CUSTOM + 11;
/** Transition type to fullscreen from desktop mode. */
public static final int TRANSIT_EXIT_DESKTOP_MODE = WindowManager.TRANSIT_FIRST_CUSTOM + 12;
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 14f2f9b..80cf96a 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
@@ -197,7 +197,7 @@
@NonNull TransitionInfo info,
@NonNull TransitionInfo.Change change) {
if (change.getMode() == WindowManager.TRANSIT_CHANGE
- && (info.getType() == Transitions.TRANSIT_ENTER_DESKTOP_MODE
+ && (info.getType() == Transitions.TRANSIT_FINALIZE_MOVE_TO_DESKTOP_MODE
|| info.getType() == Transitions.TRANSIT_CANCEL_ENTERING_DESKTOP_MODE
|| info.getType() == Transitions.TRANSIT_EXIT_DESKTOP_MODE
|| info.getType() == Transitions.TRANSIT_DESKTOP_MODE_TOGGLE_RESIZE)) {
@@ -616,7 +616,7 @@
} else if (mMoveToDesktopAnimator != null) {
relevantDecor.incrementRelayoutBlock();
mDesktopTasksController.ifPresent(
- c -> c.cancelMoveToFreeform(relevantDecor.mTaskInfo,
+ c -> c.cancelMoveToDesktop(relevantDecor.mTaskInfo,
mMoveToDesktopAnimator));
mMoveToDesktopAnimator = null;
return;
@@ -643,7 +643,7 @@
mDragToDesktopAnimationStartBounds, relevantDecor.mTaskInfo,
relevantDecor.mTaskSurface);
mDesktopTasksController.ifPresent(
- c -> c.moveToFreeform(relevantDecor.mTaskInfo,
+ c -> c.startMoveToDesktop(relevantDecor.mTaskInfo,
mDragToDesktopAnimationStartBounds,
mMoveToDesktopAnimator));
mMoveToDesktopAnimator.startAnimation();
@@ -799,6 +799,7 @@
return DesktopModeStatus.isProto2Enabled()
&& taskInfo.getWindowingMode() != WINDOWING_MODE_PINNED
&& taskInfo.getActivityType() == ACTIVITY_TYPE_STANDARD
+ && !taskInfo.configuration.windowConfiguration.isAlwaysOnTop()
&& mDisplayController.getDisplayContext(taskInfo.displayId)
.getResources().getConfiguration().smallestScreenWidthDp >= 600;
}
diff --git a/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml
index c8a9637..87b20ed 100644
--- a/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml
+++ b/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml
@@ -90,9 +90,9 @@
<option name="directory-keys"
value="/data/user/0/com.android.wm.shell.flicker.bubbles/files"/>
<option name="directory-keys"
- value="/data/user/0/com.android.server.wm.flicker.pip/files"/>
+ value="/data/user/0/com.android.wm.shell.flicker.pip/files"/>
<option name="directory-keys"
- value="/data/user/0/com.android.server.wm.flicker.splitscreen/files"/>
+ value="/data/user/0/com.android.wm.shell.flicker.splitscreen/files"/>
<option name="directory-keys"
value="/data/user/0/com.android.server.wm.flicker.service/files"/>
<option name="collect-on-run-ended-only" value="true"/>
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/BaseAppCompat.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/BaseAppCompat.kt
index 0f9579d..69c8ecd 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/BaseAppCompat.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/BaseAppCompat.kt
@@ -17,7 +17,6 @@
package com.android.wm.shell.flicker.appcompat
import android.content.Context
-import android.system.helpers.CommandsHelper
import android.tools.common.traces.component.ComponentNameMatcher
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.FlickerTestData
@@ -29,15 +28,18 @@
import com.android.wm.shell.flicker.appWindowIsVisibleAtStart
import com.android.wm.shell.flicker.appWindowKeepVisible
import com.android.wm.shell.flicker.layerKeepVisible
-import org.junit.After
+
import org.junit.Assume
import org.junit.Before
+import org.junit.Rule
abstract class BaseAppCompat(flicker: LegacyFlickerTest) : BaseTest(flicker) {
protected val context: Context = instrumentation.context
protected val letterboxApp = LetterboxAppHelper(instrumentation)
- lateinit var cmdHelper: CommandsHelper
- private lateinit var letterboxStyle: HashMap<String, String>
+
+ @JvmField
+ @Rule
+ val letterboxRule: LetterboxRule = LetterboxRule()
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit
@@ -52,50 +54,7 @@
@Before
fun before() {
- cmdHelper = CommandsHelper.getInstance(instrumentation)
- Assume.assumeTrue(tapl.isTablet && isIgnoreOrientationRequest())
- letterboxStyle = mapLetterboxStyle()
- resetLetterboxStyle()
- setLetterboxEducationEnabled(false)
- }
-
- @After
- fun after() {
- resetLetterboxStyle()
- }
-
- private fun mapLetterboxStyle(): HashMap<String, String> {
- val res = cmdHelper.executeShellCommand("wm get-letterbox-style")
- val lines = res.lines()
- val map = HashMap<String, String>()
- for (line in lines) {
- val keyValuePair = line.split(":")
- if (keyValuePair.size == 2) {
- val key = keyValuePair[0].trim()
- map[key] = keyValuePair[1].trim()
- }
- }
- return map
- }
-
- private fun getLetterboxStyle(): HashMap<String, String> {
- if (!::letterboxStyle.isInitialized) {
- letterboxStyle = mapLetterboxStyle()
- }
- return letterboxStyle
- }
-
- private fun resetLetterboxStyle() {
- cmdHelper.executeShellCommand("wm reset-letterbox-style")
- }
-
- private fun setLetterboxEducationEnabled(enabled: Boolean) {
- cmdHelper.executeShellCommand("wm set-letterbox-style --isEducationEnabled $enabled")
- }
-
- private fun isIgnoreOrientationRequest(): Boolean {
- val res = cmdHelper.executeShellCommand("wm get-ignore-orientation-request")
- return res != null && res.contains("true")
+ Assume.assumeTrue(tapl.isTablet && letterboxRule.isIgnoreOrientationRequest)
}
fun FlickerTestData.setStartRotation() = setRotation(flicker.scenario.startRotation)
@@ -115,7 +74,7 @@
/** Only run on tests with config_letterboxActivityCornersRadius != 0 in devices */
private fun assumeLetterboxRoundedCornersEnabled() {
- Assume.assumeTrue(getLetterboxStyle().getValue("Corner radius") != "0")
+ Assume.assumeTrue(letterboxRule.hasCornerRadius)
}
fun assertLetterboxAppVisibleAtStartAndEnd() {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/LetterboxRule.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/LetterboxRule.kt
new file mode 100644
index 0000000..5a1136f
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/LetterboxRule.kt
@@ -0,0 +1,108 @@
+/*
+ * 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.wm.shell.flicker.appcompat
+
+import android.app.Instrumentation
+import android.system.helpers.CommandsHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
+
+/**
+ * JUnit Rule to handle letterboxStyles and states
+ */
+class LetterboxRule(
+ private val withLetterboxEducationEnabled: Boolean = false,
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation(),
+ private val cmdHelper: CommandsHelper = CommandsHelper.getInstance(instrumentation)
+) : TestRule {
+
+ private val execAdb: (String) -> String = {cmd -> cmdHelper.executeShellCommand(cmd)}
+ private lateinit var _letterboxStyle: MutableMap<String, String>
+
+ val letterboxStyle: Map<String, String>
+ get() {
+ if (!::_letterboxStyle.isInitialized) {
+ _letterboxStyle = mapLetterboxStyle()
+ }
+ return _letterboxStyle
+ }
+
+ val cornerRadius: Int?
+ get() = asInt(letterboxStyle["Corner radius"])
+
+ val hasCornerRadius: Boolean
+ get() {
+ val radius = cornerRadius
+ return radius != null && radius > 0
+ }
+
+ val isIgnoreOrientationRequest: Boolean
+ get() = execAdb("wm get-ignore-orientation-request")?.contains("true") ?: false
+
+ override fun apply(base: Statement?, description: Description?): Statement {
+ resetLetterboxStyle()
+ _letterboxStyle = mapLetterboxStyle()
+ val isLetterboxEducationEnabled = _letterboxStyle.getValue("Is education enabled")
+ var hasLetterboxEducationStateChanged = false
+ if ("$withLetterboxEducationEnabled" != isLetterboxEducationEnabled) {
+ hasLetterboxEducationStateChanged = true
+ execAdb("wm set-letterbox-style --isEducationEnabled " +
+ withLetterboxEducationEnabled)
+ }
+ return try {
+ object : Statement() {
+ @Throws(Throwable::class)
+ override fun evaluate() {
+ base!!.evaluate()
+ }
+ }
+ } finally {
+ if (hasLetterboxEducationStateChanged) {
+ execAdb("wm set-letterbox-style --isEducationEnabled " +
+ isLetterboxEducationEnabled
+ )
+ }
+ resetLetterboxStyle()
+ }
+ }
+
+ private fun mapLetterboxStyle(): HashMap<String, String> {
+ val res = execAdb("wm get-letterbox-style")
+ val lines = res.lines()
+ val map = HashMap<String, String>()
+ for (line in lines) {
+ val keyValuePair = line.split(":")
+ if (keyValuePair.size == 2) {
+ val key = keyValuePair[0].trim()
+ map[key] = keyValuePair[1].trim()
+ }
+ }
+ return map
+ }
+
+ private fun resetLetterboxStyle() {
+ execAdb("wm reset-letterbox-style")
+ }
+
+ private fun asInt(str: String?): Int? = try {
+ str?.toInt()
+ } catch (e: NumberFormatException) {
+ null
+ }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenAppInSizeCompatModeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenAppInSizeCompatModeTest.kt
index a7bd258..67d5718 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenAppInSizeCompatModeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenAppInSizeCompatModeTest.kt
@@ -31,7 +31,7 @@
/**
* Test launching app in size compat mode.
*
- * To run this test: `atest WMShellFlickerTests:OpenAppInSizeCompatModeTest`
+ * To run this test: `atest WMShellFlickerTestsOther:OpenAppInSizeCompatModeTest`
*
* Actions:
* ```
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenTransparentActivityTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenTransparentActivityTest.kt
new file mode 100644
index 0000000..e6ca261
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenTransparentActivityTest.kt
@@ -0,0 +1,128 @@
+/*
+ * 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.wm.shell.flicker.appcompat
+
+import android.platform.test.annotations.Postsubmit
+import android.tools.common.Rotation
+import android.tools.common.flicker.assertions.FlickerTest
+import android.tools.common.traces.component.ComponentNameMatcher
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.RequiresDevice
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+/**
+ * Test launching app in size compat mode.
+ *
+ * To run this test: `atest WMShellFlickerTestsOther:OpenTransparentActivityTest`
+ *
+ * Actions:
+ * ```
+ * Launch a letteboxed app and then a transparent activity from it. We test the bounds
+ * are the same.
+ * ```
+ *
+ * Notes:
+ * ```
+ * Some default assertions (e.g., nav bar, status bar and screen covered)
+ * are inherited [BaseTest]
+ * ```
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+class OpenTransparentActivityTest(flicker: LegacyFlickerTest) : TransparentBaseAppCompat(flicker) {
+
+ /** {@inheritDoc} */
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ setup {
+ letterboxTranslucentLauncherApp.launchViaIntent(wmHelper)
+ }
+ transitions {
+ waitAndGetLaunchTransparent()?.click() ?: error("Launch Transparent not found")
+ }
+ teardown {
+ letterboxTranslucentApp.exit(wmHelper)
+ letterboxTranslucentLauncherApp.exit(wmHelper)
+ }
+ }
+
+ /**
+ * Checks the transparent activity is launched on top of the opaque one
+ */
+ @Postsubmit
+ @Test
+ fun translucentActivityIsLaunchedOnTopOfOpaqueActivity() {
+ flicker.assertWm {
+ this.isAppWindowOnTop(letterboxTranslucentLauncherApp)
+ .then()
+ .isAppWindowOnTop(letterboxTranslucentApp)
+ }
+ }
+
+ /**
+ * Checks that the activity is letterboxed
+ */
+ @Postsubmit
+ @Test
+ fun translucentActivityIsLetterboxed() {
+ flicker.assertLayers { isVisible(ComponentNameMatcher.LETTERBOX) }
+ }
+
+ /**
+ * Checks that the translucent activity inherits bounds from the opaque one.
+ */
+ @Postsubmit
+ @Test
+ fun translucentActivityInheritsBoundsFromOpaqueActivity() {
+ flicker.assertLayersEnd {
+ this.visibleRegion(letterboxTranslucentApp)
+ .coversExactly(visibleRegion(letterboxTranslucentLauncherApp).region)
+ }
+ }
+
+ /**
+ * Checks that the translucent activity has rounded corners
+ */
+ @Postsubmit
+ @Test
+ fun translucentActivityHasRoundedCorners() {
+ flicker.assertLayersEnd {
+ this.hasRoundedCorners(letterboxTranslucentApp)
+ }
+ }
+
+ companion object {
+ /**
+ * Creates the test configurations.
+ *
+ * See [FlickerTestFactory.rotationTests] for configuring screen orientation and
+ * navigation modes.
+ */
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<FlickerTest> {
+ return LegacyFlickerTestFactory
+ .nonRotationTests(supportedRotations = listOf(Rotation.ROTATION_90))
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RepositionFixedPortraitAppTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RepositionFixedPortraitAppTest.kt
index e875aae..68fa8d2 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RepositionFixedPortraitAppTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RepositionFixedPortraitAppTest.kt
@@ -32,7 +32,9 @@
/**
* Test launching a fixed portrait letterboxed app in landscape and repositioning to the right.
*
- * To run this test: `atest WMShellFlickerTests:RepositionFixedPortraitAppTest` Actions:
+ * To run this test: `atest WMShellFlickerTestsOther:RepositionFixedPortraitAppTest`
+ *
+ * Actions:
*
* ```
* Launch a fixed portrait app in landscape to letterbox app
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RestartAppInSizeCompatModeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RestartAppInSizeCompatModeTest.kt
index a18a144..fcb6931a 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RestartAppInSizeCompatModeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RestartAppInSizeCompatModeTest.kt
@@ -31,7 +31,7 @@
/**
* Test restarting app in size compat mode.
*
- * To run this test: `atest WMShellFlickerTests:RestartAppInSizeCompatModeTest`
+ * To run this test: `atest WMShellFlickerTestsOther:RestartAppInSizeCompatModeTest`
*
* Actions:
* ```
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/TransparentBaseAppCompat.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/TransparentBaseAppCompat.kt
new file mode 100644
index 0000000..ea0392c
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/TransparentBaseAppCompat.kt
@@ -0,0 +1,63 @@
+/*
+ * 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.wm.shell.flicker.appcompat
+
+import android.content.Context
+import android.tools.device.flicker.legacy.FlickerTestData
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.helpers.FIND_TIMEOUT
+import android.tools.device.traces.parsers.toFlickerComponent
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.UiObject2
+import androidx.test.uiautomator.Until
+import com.android.server.wm.flicker.helpers.LetterboxAppHelper
+import com.android.server.wm.flicker.testapp.ActivityOptions
+import com.android.wm.shell.flicker.BaseTest
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Rule
+
+abstract class TransparentBaseAppCompat(flicker: LegacyFlickerTest) : BaseTest(flicker) {
+ protected val context: Context = instrumentation.context
+ protected val letterboxTranslucentLauncherApp = LetterboxAppHelper(
+ instrumentation,
+ launcherName = ActivityOptions.LaunchTransparentActivity.LABEL,
+ component = ActivityOptions.LaunchTransparentActivity.COMPONENT.toFlickerComponent()
+ )
+ protected val letterboxTranslucentApp = LetterboxAppHelper(
+ instrumentation,
+ launcherName = ActivityOptions.TransparentActivity.LABEL,
+ component = ActivityOptions.TransparentActivity.COMPONENT.toFlickerComponent()
+ )
+
+ @JvmField
+ @Rule
+ val letterboxRule: LetterboxRule = LetterboxRule()
+
+ @Before
+ fun before() {
+ Assume.assumeTrue(tapl.isTablet && letterboxRule.isIgnoreOrientationRequest)
+ }
+
+ protected fun FlickerTestData.waitAndGetLaunchTransparent(): UiObject2? =
+ device.wait(
+ Until.findObject(By.text("Launch Transparent")),
+ FIND_TIMEOUT
+ )
+
+ protected fun FlickerTestData.goBack() = device.pressBack()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTest.kt
index 4771087..9ca7bf1 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTest.kt
@@ -20,6 +20,7 @@
import android.graphics.Point
import android.platform.test.annotations.Presubmit
import android.tools.common.flicker.subject.layers.LayersTraceSubject
+import android.tools.common.traces.component.ComponentNameMatcher
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
@@ -78,7 +79,8 @@
override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
flicker.assertLayers {
this.visibleLayersShownMoreThanOneConsecutiveEntry(
- LayersTraceSubject.VISIBLE_FOR_MORE_THAN_ONE_ENTRY_IGNORE_LAYERS + listOf(testApp)
+ LayersTraceSubject.VISIBLE_FOR_MORE_THAN_ONE_ENTRY_IGNORE_LAYERS +
+ listOf(testApp, ComponentNameMatcher(className = "Bubbles!#"))
)
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/CopyContentInSplitGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/CopyContentInSplitGesturalNavPortraitBenchmark.kt
deleted file mode 100644
index 92b6227..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/CopyContentInSplitGesturalNavPortraitBenchmark.kt
+++ /dev/null
@@ -1,32 +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.wm.shell.flicker.service.splitscreen.benchmark
-
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
-import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.CopyContentInSplit
-import org.junit.Test
-
-@RequiresDevice
-class CopyContentInSplitGesturalNavPortraitBenchmark : CopyContentInSplit(Rotation.ROTATION_0) {
- @PlatinumTest(focusArea = "sysui")
- @Presubmit
- @Test
- override fun copyContentInSplit() = super.copyContentInSplit()
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DragDividerToResizeGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DragDividerToResizeGesturalNavLandscapeBenchmark.kt
deleted file mode 100644
index 0495079..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DragDividerToResizeGesturalNavLandscapeBenchmark.kt
+++ /dev/null
@@ -1,32 +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.wm.shell.flicker.service.splitscreen.benchmark
-
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
-import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.DragDividerToResize
-import org.junit.Test
-
-@RequiresDevice
-class DragDividerToResizeGesturalNavLandscapeBenchmark : DragDividerToResize(Rotation.ROTATION_90) {
- @PlatinumTest(focusArea = "sysui")
- @Presubmit
- @Test
- override fun dragDividerToResize() = super.dragDividerToResize()
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBetweenSplitPairsGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBetweenSplitPairsGesturalNavLandscapeBenchmark.kt
deleted file mode 100644
index aab18a6..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBetweenSplitPairsGesturalNavLandscapeBenchmark.kt
+++ /dev/null
@@ -1,33 +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.wm.shell.flicker.service.splitscreen.benchmark
-
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
-import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBetweenSplitPairs
-import org.junit.Test
-
-@RequiresDevice
-class SwitchBetweenSplitPairsGesturalNavLandscapeBenchmark :
- SwitchBetweenSplitPairs(Rotation.ROTATION_90) {
- @PlatinumTest(focusArea = "sysui")
- @Presubmit
- @Test
- override fun switchBetweenSplitPairs() = super.switchBetweenSplitPairs()
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/UnlockKeyguardToSplitScreenGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/UnlockKeyguardToSplitScreenGesturalNavPortraitBenchmark.kt
deleted file mode 100644
index 840401c..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/UnlockKeyguardToSplitScreenGesturalNavPortraitBenchmark.kt
+++ /dev/null
@@ -1,34 +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.wm.shell.flicker.service.splitscreen.benchmark
-
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
-import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.UnlockKeyguardToSplitScreen
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.BlockJUnit4ClassRunner
-
-@RequiresDevice
-@RunWith(BlockJUnit4ClassRunner::class)
-class UnlockKeyguardToSplitScreenGesturalNavPortraitBenchmark : UnlockKeyguardToSplitScreen() {
- @PlatinumTest(focusArea = "sysui")
- @Presubmit
- @Test
- override fun unlockKeyguardToSplitScreen() = super.unlockKeyguardToSplitScreen()
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavLandscape.kt
deleted file mode 100644
index a5c5122..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavLandscape.kt
+++ /dev/null
@@ -1,40 +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.wm.shell.flicker.service.splitscreen.flicker
-
-import android.tools.common.Rotation
-import android.tools.common.flicker.FlickerConfig
-import android.tools.common.flicker.annotation.ExpectedScenarios
-import android.tools.common.flicker.annotation.FlickerConfigProvider
-import android.tools.common.flicker.config.FlickerConfig
-import android.tools.common.flicker.config.FlickerServiceConfig
-import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.CopyContentInSplit
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(FlickerServiceJUnit4ClassRunner::class)
-class CopyContentInSplitGesturalNavLandscape : CopyContentInSplit(Rotation.ROTATION_90) {
- @ExpectedScenarios([]) @Test override fun copyContentInSplit() = super.copyContentInSplit()
-
- companion object {
- @JvmStatic
- @FlickerConfigProvider
- fun flickerConfigProvider(): FlickerConfig =
- FlickerConfig().use(FlickerServiceConfig.DEFAULT)
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavPortrait.kt
deleted file mode 100644
index 092fb67..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavPortrait.kt
+++ /dev/null
@@ -1,40 +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.wm.shell.flicker.service.splitscreen.flicker
-
-import android.tools.common.Rotation
-import android.tools.common.flicker.FlickerConfig
-import android.tools.common.flicker.annotation.ExpectedScenarios
-import android.tools.common.flicker.annotation.FlickerConfigProvider
-import android.tools.common.flicker.config.FlickerConfig
-import android.tools.common.flicker.config.FlickerServiceConfig
-import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.CopyContentInSplit
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(FlickerServiceJUnit4ClassRunner::class)
-class CopyContentInSplitGesturalNavPortrait : CopyContentInSplit(Rotation.ROTATION_0) {
- @ExpectedScenarios([]) @Test override fun copyContentInSplit() = super.copyContentInSplit()
-
- companion object {
- @JvmStatic
- @FlickerConfigProvider
- fun flickerConfigProvider(): FlickerConfig =
- FlickerConfig().use(FlickerServiceConfig.DEFAULT)
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavLandscape.kt
deleted file mode 100644
index 8cb25fe..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavLandscape.kt
+++ /dev/null
@@ -1,44 +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.wm.shell.flicker.service.splitscreen.flicker
-
-import android.tools.common.Rotation
-import android.tools.common.flicker.FlickerConfig
-import android.tools.common.flicker.annotation.ExpectedScenarios
-import android.tools.common.flicker.annotation.FlickerConfigProvider
-import android.tools.common.flicker.config.FlickerConfig
-import android.tools.common.flicker.config.FlickerServiceConfig
-import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByDivider
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(FlickerServiceJUnit4ClassRunner::class)
-class DismissSplitScreenByDividerGesturalNavLandscape :
- DismissSplitScreenByDivider(Rotation.ROTATION_90) {
-
- @ExpectedScenarios(["SPLIT_SCREEN_EXIT"])
- @Test
- override fun dismissSplitScreenByDivider() = super.dismissSplitScreenByDivider()
-
- companion object {
- @JvmStatic
- @FlickerConfigProvider
- fun flickerConfigProvider(): FlickerConfig =
- FlickerConfig().use(FlickerServiceConfig.DEFAULT)
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavPortrait.kt
deleted file mode 100644
index fa1be63..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavPortrait.kt
+++ /dev/null
@@ -1,44 +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.wm.shell.flicker.service.splitscreen.flicker
-
-import android.tools.common.Rotation
-import android.tools.common.flicker.FlickerConfig
-import android.tools.common.flicker.annotation.ExpectedScenarios
-import android.tools.common.flicker.annotation.FlickerConfigProvider
-import android.tools.common.flicker.config.FlickerConfig
-import android.tools.common.flicker.config.FlickerServiceConfig
-import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByDivider
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(FlickerServiceJUnit4ClassRunner::class)
-class DismissSplitScreenByDividerGesturalNavPortrait :
- DismissSplitScreenByDivider(Rotation.ROTATION_0) {
-
- @ExpectedScenarios(["SPLIT_SCREEN_EXIT"])
- @Test
- override fun dismissSplitScreenByDivider() = super.dismissSplitScreenByDivider()
-
- companion object {
- @JvmStatic
- @FlickerConfigProvider
- fun flickerConfigProvider(): FlickerConfig =
- FlickerConfig().use(FlickerServiceConfig.DEFAULT)
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavLandscape.kt
deleted file mode 100644
index aa35237..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavLandscape.kt
+++ /dev/null
@@ -1,44 +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.wm.shell.flicker.service.splitscreen.flicker
-
-import android.tools.common.Rotation
-import android.tools.common.flicker.FlickerConfig
-import android.tools.common.flicker.annotation.ExpectedScenarios
-import android.tools.common.flicker.annotation.FlickerConfigProvider
-import android.tools.common.flicker.config.FlickerConfig
-import android.tools.common.flicker.config.FlickerServiceConfig
-import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByGoHome
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(FlickerServiceJUnit4ClassRunner::class)
-class DismissSplitScreenByGoHomeGesturalNavLandscape :
- DismissSplitScreenByGoHome(Rotation.ROTATION_90) {
-
- @ExpectedScenarios(["SPLIT_SCREEN_EXIT"])
- @Test
- override fun dismissSplitScreenByGoHome() = super.dismissSplitScreenByGoHome()
-
- companion object {
- @JvmStatic
- @FlickerConfigProvider
- fun flickerConfigProvider(): FlickerConfig =
- FlickerConfig().use(FlickerServiceConfig.DEFAULT)
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavPortrait.kt
deleted file mode 100644
index e195360..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavPortrait.kt
+++ /dev/null
@@ -1,44 +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.wm.shell.flicker.service.splitscreen.flicker
-
-import android.tools.common.Rotation
-import android.tools.common.flicker.FlickerConfig
-import android.tools.common.flicker.annotation.ExpectedScenarios
-import android.tools.common.flicker.annotation.FlickerConfigProvider
-import android.tools.common.flicker.config.FlickerConfig
-import android.tools.common.flicker.config.FlickerServiceConfig
-import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByGoHome
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(FlickerServiceJUnit4ClassRunner::class)
-class DismissSplitScreenByGoHomeGesturalNavPortrait :
- DismissSplitScreenByGoHome(Rotation.ROTATION_0) {
-
- @ExpectedScenarios(["SPLIT_SCREEN_EXIT"])
- @Test
- override fun dismissSplitScreenByGoHome() = super.dismissSplitScreenByGoHome()
-
- companion object {
- @JvmStatic
- @FlickerConfigProvider
- fun flickerConfigProvider(): FlickerConfig =
- FlickerConfig().use(FlickerServiceConfig.DEFAULT)
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavLandscape.kt
deleted file mode 100644
index c1b3aad..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavLandscape.kt
+++ /dev/null
@@ -1,43 +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.wm.shell.flicker.service.splitscreen.flicker
-
-import android.tools.common.Rotation
-import android.tools.common.flicker.FlickerConfig
-import android.tools.common.flicker.annotation.ExpectedScenarios
-import android.tools.common.flicker.annotation.FlickerConfigProvider
-import android.tools.common.flicker.config.FlickerConfig
-import android.tools.common.flicker.config.FlickerServiceConfig
-import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.DragDividerToResize
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(FlickerServiceJUnit4ClassRunner::class)
-class DragDividerToResizeGesturalNavLandscape : DragDividerToResize(Rotation.ROTATION_90) {
-
- @ExpectedScenarios(["SPLIT_SCREEN_RESIZE"])
- @Test
- override fun dragDividerToResize() = super.dragDividerToResize()
-
- companion object {
- @JvmStatic
- @FlickerConfigProvider
- fun flickerConfigProvider(): FlickerConfig =
- FlickerConfig().use(FlickerServiceConfig.DEFAULT)
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavPortrait.kt
deleted file mode 100644
index c6e2e85..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavPortrait.kt
+++ /dev/null
@@ -1,43 +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.wm.shell.flicker.service.splitscreen.flicker
-
-import android.tools.common.Rotation
-import android.tools.common.flicker.FlickerConfig
-import android.tools.common.flicker.annotation.ExpectedScenarios
-import android.tools.common.flicker.annotation.FlickerConfigProvider
-import android.tools.common.flicker.config.FlickerConfig
-import android.tools.common.flicker.config.FlickerServiceConfig
-import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.DragDividerToResize
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(FlickerServiceJUnit4ClassRunner::class)
-class DragDividerToResizeGesturalNavPortrait : DragDividerToResize(Rotation.ROTATION_0) {
-
- @ExpectedScenarios(["SPLIT_SCREEN_RESIZE"])
- @Test
- override fun dragDividerToResize() = super.dragDividerToResize()
-
- companion object {
- @JvmStatic
- @FlickerConfigProvider
- fun flickerConfigProvider(): FlickerConfig =
- FlickerConfig().use(FlickerServiceConfig.DEFAULT)
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt
deleted file mode 100644
index 5f771c7..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt
+++ /dev/null
@@ -1,44 +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.wm.shell.flicker.service.splitscreen.flicker
-
-import android.tools.common.Rotation
-import android.tools.common.flicker.FlickerConfig
-import android.tools.common.flicker.annotation.ExpectedScenarios
-import android.tools.common.flicker.annotation.FlickerConfigProvider
-import android.tools.common.flicker.config.FlickerConfig
-import android.tools.common.flicker.config.FlickerServiceConfig
-import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromAllApps
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(FlickerServiceJUnit4ClassRunner::class)
-class EnterSplitScreenByDragFromAllAppsGesturalNavLandscape :
- EnterSplitScreenByDragFromAllApps(Rotation.ROTATION_90) {
-
- @ExpectedScenarios(["SPLIT_SCREEN_ENTER"])
- @Test
- override fun enterSplitScreenByDragFromAllApps() = super.enterSplitScreenByDragFromAllApps()
-
- companion object {
- @JvmStatic
- @FlickerConfigProvider
- fun flickerConfigProvider(): FlickerConfig =
- FlickerConfig().use(FlickerServiceConfig.DEFAULT)
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt
deleted file mode 100644
index 729a401..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt
+++ /dev/null
@@ -1,44 +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.wm.shell.flicker.service.splitscreen.flicker
-
-import android.tools.common.Rotation
-import android.tools.common.flicker.FlickerConfig
-import android.tools.common.flicker.annotation.ExpectedScenarios
-import android.tools.common.flicker.annotation.FlickerConfigProvider
-import android.tools.common.flicker.config.FlickerConfig
-import android.tools.common.flicker.config.FlickerServiceConfig
-import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromAllApps
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(FlickerServiceJUnit4ClassRunner::class)
-class EnterSplitScreenByDragFromAllAppsGesturalNavPortrait :
- EnterSplitScreenByDragFromAllApps(Rotation.ROTATION_0) {
-
- @ExpectedScenarios(["SPLIT_SCREEN_ENTER"])
- @Test
- override fun enterSplitScreenByDragFromAllApps() = super.enterSplitScreenByDragFromAllApps()
-
- companion object {
- @JvmStatic
- @FlickerConfigProvider
- fun flickerConfigProvider(): FlickerConfig =
- FlickerConfig().use(FlickerServiceConfig.DEFAULT)
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt
deleted file mode 100644
index 6e4cf9f..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt
+++ /dev/null
@@ -1,45 +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.wm.shell.flicker.service.splitscreen.flicker
-
-import android.tools.common.Rotation
-import android.tools.common.flicker.FlickerConfig
-import android.tools.common.flicker.annotation.ExpectedScenarios
-import android.tools.common.flicker.annotation.FlickerConfigProvider
-import android.tools.common.flicker.config.FlickerConfig
-import android.tools.common.flicker.config.FlickerServiceConfig
-import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromNotification
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(FlickerServiceJUnit4ClassRunner::class)
-class EnterSplitScreenByDragFromNotificationGesturalNavLandscape :
- EnterSplitScreenByDragFromNotification(Rotation.ROTATION_90) {
-
- @ExpectedScenarios(["SPLIT_SCREEN_ENTER"])
- @Test
- override fun enterSplitScreenByDragFromNotification() =
- super.enterSplitScreenByDragFromNotification()
-
- companion object {
- @JvmStatic
- @FlickerConfigProvider
- fun flickerConfigProvider(): FlickerConfig =
- FlickerConfig().use(FlickerServiceConfig.DEFAULT)
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt
deleted file mode 100644
index cc28702..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt
+++ /dev/null
@@ -1,45 +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.wm.shell.flicker.service.splitscreen.flicker
-
-import android.tools.common.Rotation
-import android.tools.common.flicker.FlickerConfig
-import android.tools.common.flicker.annotation.ExpectedScenarios
-import android.tools.common.flicker.annotation.FlickerConfigProvider
-import android.tools.common.flicker.config.FlickerConfig
-import android.tools.common.flicker.config.FlickerServiceConfig
-import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromNotification
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(FlickerServiceJUnit4ClassRunner::class)
-class EnterSplitScreenByDragFromNotificationGesturalNavPortrait :
- EnterSplitScreenByDragFromNotification(Rotation.ROTATION_0) {
-
- @ExpectedScenarios(["SPLIT_SCREEN_ENTER"])
- @Test
- override fun enterSplitScreenByDragFromNotification() =
- super.enterSplitScreenByDragFromNotification()
-
- companion object {
- @JvmStatic
- @FlickerConfigProvider
- fun flickerConfigProvider(): FlickerConfig =
- FlickerConfig().use(FlickerServiceConfig.DEFAULT)
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt
deleted file mode 100644
index 736604f..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt
+++ /dev/null
@@ -1,44 +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.wm.shell.flicker.service.splitscreen.flicker
-
-import android.tools.common.Rotation
-import android.tools.common.flicker.FlickerConfig
-import android.tools.common.flicker.annotation.ExpectedScenarios
-import android.tools.common.flicker.annotation.FlickerConfigProvider
-import android.tools.common.flicker.config.FlickerConfig
-import android.tools.common.flicker.config.FlickerServiceConfig
-import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromShortcut
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(FlickerServiceJUnit4ClassRunner::class)
-class EnterSplitScreenByDragFromShortcutGesturalNavLandscape :
- EnterSplitScreenByDragFromShortcut(Rotation.ROTATION_90) {
-
- @ExpectedScenarios(["SPLIT_SCREEN_ENTER"])
- @Test
- override fun enterSplitScreenByDragFromShortcut() = super.enterSplitScreenByDragFromShortcut()
-
- companion object {
- @JvmStatic
- @FlickerConfigProvider
- fun flickerConfigProvider(): FlickerConfig =
- FlickerConfig().use(FlickerServiceConfig.DEFAULT)
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt
deleted file mode 100644
index 8df8dfa..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt
+++ /dev/null
@@ -1,44 +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.wm.shell.flicker.service.splitscreen.flicker
-
-import android.tools.common.Rotation
-import android.tools.common.flicker.FlickerConfig
-import android.tools.common.flicker.annotation.ExpectedScenarios
-import android.tools.common.flicker.annotation.FlickerConfigProvider
-import android.tools.common.flicker.config.FlickerConfig
-import android.tools.common.flicker.config.FlickerServiceConfig
-import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromShortcut
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(FlickerServiceJUnit4ClassRunner::class)
-class EnterSplitScreenByDragFromShortcutGesturalNavPortrait :
- EnterSplitScreenByDragFromShortcut(Rotation.ROTATION_0) {
-
- @ExpectedScenarios(["SPLIT_SCREEN_ENTER"])
- @Test
- override fun enterSplitScreenByDragFromShortcut() = super.enterSplitScreenByDragFromShortcut()
-
- companion object {
- @JvmStatic
- @FlickerConfigProvider
- fun flickerConfigProvider(): FlickerConfig =
- FlickerConfig().use(FlickerServiceConfig.DEFAULT)
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt
deleted file mode 100644
index 378f055..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt
+++ /dev/null
@@ -1,44 +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.wm.shell.flicker.service.splitscreen.flicker
-
-import android.tools.common.Rotation
-import android.tools.common.flicker.FlickerConfig
-import android.tools.common.flicker.annotation.ExpectedScenarios
-import android.tools.common.flicker.annotation.FlickerConfigProvider
-import android.tools.common.flicker.config.FlickerConfig
-import android.tools.common.flicker.config.FlickerServiceConfig
-import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromTaskbar
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(FlickerServiceJUnit4ClassRunner::class)
-class EnterSplitScreenByDragFromTaskbarGesturalNavLandscape :
- EnterSplitScreenByDragFromTaskbar(Rotation.ROTATION_90) {
-
- @ExpectedScenarios(["SPLIT_SCREEN_ENTER"])
- @Test
- override fun enterSplitScreenByDragFromTaskbar() = super.enterSplitScreenByDragFromTaskbar()
-
- companion object {
- @JvmStatic
- @FlickerConfigProvider
- fun flickerConfigProvider(): FlickerConfig =
- FlickerConfig().use(FlickerServiceConfig.DEFAULT)
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt
deleted file mode 100644
index b33d262..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt
+++ /dev/null
@@ -1,44 +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.wm.shell.flicker.service.splitscreen.flicker
-
-import android.tools.common.Rotation
-import android.tools.common.flicker.FlickerConfig
-import android.tools.common.flicker.annotation.ExpectedScenarios
-import android.tools.common.flicker.annotation.FlickerConfigProvider
-import android.tools.common.flicker.config.FlickerConfig
-import android.tools.common.flicker.config.FlickerServiceConfig
-import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromTaskbar
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(FlickerServiceJUnit4ClassRunner::class)
-class EnterSplitScreenByDragFromTaskbarGesturalNavPortrait :
- EnterSplitScreenByDragFromTaskbar(Rotation.ROTATION_0) {
-
- @ExpectedScenarios(["SPLIT_SCREEN_ENTER"])
- @Test
- override fun enterSplitScreenByDragFromTaskbar() = super.enterSplitScreenByDragFromTaskbar()
-
- companion object {
- @JvmStatic
- @FlickerConfigProvider
- fun flickerConfigProvider(): FlickerConfig =
- FlickerConfig().use(FlickerServiceConfig.DEFAULT)
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavLandscape.kt
deleted file mode 100644
index b1d3858..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavLandscape.kt
+++ /dev/null
@@ -1,44 +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.wm.shell.flicker.service.splitscreen.flicker
-
-import android.tools.common.Rotation
-import android.tools.common.flicker.FlickerConfig
-import android.tools.common.flicker.annotation.ExpectedScenarios
-import android.tools.common.flicker.annotation.FlickerConfigProvider
-import android.tools.common.flicker.config.FlickerConfig
-import android.tools.common.flicker.config.FlickerServiceConfig
-import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenFromOverview
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(FlickerServiceJUnit4ClassRunner::class)
-class EnterSplitScreenFromOverviewGesturalNavLandscape :
- EnterSplitScreenFromOverview(Rotation.ROTATION_90) {
-
- @ExpectedScenarios(["SPLIT_SCREEN_ENTER"])
- @Test
- override fun enterSplitScreenFromOverview() = super.enterSplitScreenFromOverview()
-
- companion object {
- @JvmStatic
- @FlickerConfigProvider
- fun flickerConfigProvider(): FlickerConfig =
- FlickerConfig().use(FlickerServiceConfig.DEFAULT)
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavPortrait.kt
deleted file mode 100644
index 6d824c7..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavPortrait.kt
+++ /dev/null
@@ -1,44 +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.wm.shell.flicker.service.splitscreen.flicker
-
-import android.tools.common.Rotation
-import android.tools.common.flicker.FlickerConfig
-import android.tools.common.flicker.annotation.ExpectedScenarios
-import android.tools.common.flicker.annotation.FlickerConfigProvider
-import android.tools.common.flicker.config.FlickerConfig
-import android.tools.common.flicker.config.FlickerServiceConfig
-import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenFromOverview
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(FlickerServiceJUnit4ClassRunner::class)
-class EnterSplitScreenFromOverviewGesturalNavPortrait :
- EnterSplitScreenFromOverview(Rotation.ROTATION_0) {
-
- @ExpectedScenarios(["SPLIT_SCREEN_ENTER"])
- @Test
- override fun enterSplitScreenFromOverview() = super.enterSplitScreenFromOverview()
-
- companion object {
- @JvmStatic
- @FlickerConfigProvider
- fun flickerConfigProvider(): FlickerConfig =
- FlickerConfig().use(FlickerServiceConfig.DEFAULT)
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt
deleted file mode 100644
index f1d3d0c..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt
+++ /dev/null
@@ -1,44 +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.wm.shell.flicker.service.splitscreen.flicker
-
-import android.tools.common.Rotation
-import android.tools.common.flicker.FlickerConfig
-import android.tools.common.flicker.annotation.ExpectedScenarios
-import android.tools.common.flicker.annotation.FlickerConfigProvider
-import android.tools.common.flicker.config.FlickerConfig
-import android.tools.common.flicker.config.FlickerServiceConfig
-import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchAppByDoubleTapDivider
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(FlickerServiceJUnit4ClassRunner::class)
-class SwitchAppByDoubleTapDividerGesturalNavLandscape :
- SwitchAppByDoubleTapDivider(Rotation.ROTATION_90) {
-
- @ExpectedScenarios([])
- @Test
- override fun switchAppByDoubleTapDivider() = super.switchAppByDoubleTapDivider()
-
- companion object {
- @JvmStatic
- @FlickerConfigProvider
- fun flickerConfigProvider(): FlickerConfig =
- FlickerConfig().use(FlickerServiceConfig.DEFAULT)
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt
deleted file mode 100644
index a867bac..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt
+++ /dev/null
@@ -1,44 +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.wm.shell.flicker.service.splitscreen.flicker
-
-import android.tools.common.Rotation
-import android.tools.common.flicker.FlickerConfig
-import android.tools.common.flicker.annotation.ExpectedScenarios
-import android.tools.common.flicker.annotation.FlickerConfigProvider
-import android.tools.common.flicker.config.FlickerConfig
-import android.tools.common.flicker.config.FlickerServiceConfig
-import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchAppByDoubleTapDivider
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(FlickerServiceJUnit4ClassRunner::class)
-class SwitchAppByDoubleTapDividerGesturalNavPortrait :
- SwitchAppByDoubleTapDivider(Rotation.ROTATION_0) {
-
- @ExpectedScenarios([])
- @Test
- override fun switchAppByDoubleTapDivider() = super.switchAppByDoubleTapDivider()
-
- companion object {
- @JvmStatic
- @FlickerConfigProvider
- fun flickerConfigProvider(): FlickerConfig =
- FlickerConfig().use(FlickerServiceConfig.DEFAULT)
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt
deleted file mode 100644
index 76247ba..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt
+++ /dev/null
@@ -1,44 +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.wm.shell.flicker.service.splitscreen.flicker
-
-import android.tools.common.Rotation
-import android.tools.common.flicker.FlickerConfig
-import android.tools.common.flicker.annotation.ExpectedScenarios
-import android.tools.common.flicker.annotation.FlickerConfigProvider
-import android.tools.common.flicker.config.FlickerConfig
-import android.tools.common.flicker.config.FlickerServiceConfig
-import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromAnotherApp
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(FlickerServiceJUnit4ClassRunner::class)
-class SwitchBackToSplitFromAnotherAppGesturalNavLandscape :
- SwitchBackToSplitFromAnotherApp(Rotation.ROTATION_90) {
-
- @ExpectedScenarios(["QUICKSWITCH"])
- @Test
- override fun switchBackToSplitFromAnotherApp() = super.switchBackToSplitFromAnotherApp()
-
- companion object {
- @JvmStatic
- @FlickerConfigProvider
- fun flickerConfigProvider(): FlickerConfig =
- FlickerConfig().use(FlickerServiceConfig.DEFAULT)
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt
deleted file mode 100644
index e179da8..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt
+++ /dev/null
@@ -1,44 +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.wm.shell.flicker.service.splitscreen.flicker
-
-import android.tools.common.Rotation
-import android.tools.common.flicker.FlickerConfig
-import android.tools.common.flicker.annotation.ExpectedScenarios
-import android.tools.common.flicker.annotation.FlickerConfigProvider
-import android.tools.common.flicker.config.FlickerConfig
-import android.tools.common.flicker.config.FlickerServiceConfig
-import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromAnotherApp
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(FlickerServiceJUnit4ClassRunner::class)
-class SwitchBackToSplitFromAnotherAppGesturalNavPortrait :
- SwitchBackToSplitFromAnotherApp(Rotation.ROTATION_0) {
-
- @ExpectedScenarios(["QUICKSWITCH"])
- @Test
- override fun switchBackToSplitFromAnotherApp() = super.switchBackToSplitFromAnotherApp()
-
- companion object {
- @JvmStatic
- @FlickerConfigProvider
- fun flickerConfigProvider(): FlickerConfig =
- FlickerConfig().use(FlickerServiceConfig.DEFAULT)
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavLandscape.kt
deleted file mode 100644
index 20f554f..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavLandscape.kt
+++ /dev/null
@@ -1,44 +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.wm.shell.flicker.service.splitscreen.flicker
-
-import android.tools.common.Rotation
-import android.tools.common.flicker.FlickerConfig
-import android.tools.common.flicker.annotation.ExpectedScenarios
-import android.tools.common.flicker.annotation.FlickerConfigProvider
-import android.tools.common.flicker.config.FlickerConfig
-import android.tools.common.flicker.config.FlickerServiceConfig
-import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromHome
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(FlickerServiceJUnit4ClassRunner::class)
-class SwitchBackToSplitFromHomeGesturalNavLandscape :
- SwitchBackToSplitFromHome(Rotation.ROTATION_90) {
-
- @ExpectedScenarios(["QUICKSWITCH"])
- @Test
- override fun switchBackToSplitFromHome() = super.switchBackToSplitFromHome()
-
- companion object {
- @JvmStatic
- @FlickerConfigProvider
- fun flickerConfigProvider(): FlickerConfig =
- FlickerConfig().use(FlickerServiceConfig.DEFAULT)
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavPortrait.kt
deleted file mode 100644
index f7776ee..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavPortrait.kt
+++ /dev/null
@@ -1,44 +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.wm.shell.flicker.service.splitscreen.flicker
-
-import android.tools.common.Rotation
-import android.tools.common.flicker.FlickerConfig
-import android.tools.common.flicker.annotation.ExpectedScenarios
-import android.tools.common.flicker.annotation.FlickerConfigProvider
-import android.tools.common.flicker.config.FlickerConfig
-import android.tools.common.flicker.config.FlickerServiceConfig
-import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromHome
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(FlickerServiceJUnit4ClassRunner::class)
-class SwitchBackToSplitFromHomeGesturalNavPortrait :
- SwitchBackToSplitFromHome(Rotation.ROTATION_0) {
-
- @ExpectedScenarios(["QUICKSWITCH"])
- @Test
- override fun switchBackToSplitFromHome() = super.switchBackToSplitFromHome()
-
- companion object {
- @JvmStatic
- @FlickerConfigProvider
- fun flickerConfigProvider(): FlickerConfig =
- FlickerConfig().use(FlickerServiceConfig.DEFAULT)
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavLandscape.kt
deleted file mode 100644
index 00f6073..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavLandscape.kt
+++ /dev/null
@@ -1,44 +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.wm.shell.flicker.service.splitscreen.flicker
-
-import android.tools.common.Rotation
-import android.tools.common.flicker.FlickerConfig
-import android.tools.common.flicker.annotation.ExpectedScenarios
-import android.tools.common.flicker.annotation.FlickerConfigProvider
-import android.tools.common.flicker.config.FlickerConfig
-import android.tools.common.flicker.config.FlickerServiceConfig
-import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromRecent
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(FlickerServiceJUnit4ClassRunner::class)
-class SwitchBackToSplitFromRecentGesturalNavLandscape :
- SwitchBackToSplitFromRecent(Rotation.ROTATION_90) {
-
- @ExpectedScenarios(["QUICKSWITCH"])
- @Test
- override fun switchBackToSplitFromRecent() = super.switchBackToSplitFromRecent()
-
- companion object {
- @JvmStatic
- @FlickerConfigProvider
- fun flickerConfigProvider(): FlickerConfig =
- FlickerConfig().use(FlickerServiceConfig.DEFAULT)
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavPortrait.kt
deleted file mode 100644
index b3340e7..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavPortrait.kt
+++ /dev/null
@@ -1,44 +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.wm.shell.flicker.service.splitscreen.flicker
-
-import android.tools.common.Rotation
-import android.tools.common.flicker.FlickerConfig
-import android.tools.common.flicker.annotation.ExpectedScenarios
-import android.tools.common.flicker.annotation.FlickerConfigProvider
-import android.tools.common.flicker.config.FlickerConfig
-import android.tools.common.flicker.config.FlickerServiceConfig
-import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromRecent
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(FlickerServiceJUnit4ClassRunner::class)
-class SwitchBackToSplitFromRecentGesturalNavPortrait :
- SwitchBackToSplitFromRecent(Rotation.ROTATION_0) {
-
- @ExpectedScenarios(["QUICKSWITCH"])
- @Test
- override fun switchBackToSplitFromRecent() = super.switchBackToSplitFromRecent()
-
- companion object {
- @JvmStatic
- @FlickerConfigProvider
- fun flickerConfigProvider(): FlickerConfig =
- FlickerConfig().use(FlickerServiceConfig.DEFAULT)
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavLandscape.kt
deleted file mode 100644
index 3da61e5..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavLandscape.kt
+++ /dev/null
@@ -1,43 +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.wm.shell.flicker.service.splitscreen.flicker
-
-import android.tools.common.Rotation
-import android.tools.common.flicker.FlickerConfig
-import android.tools.common.flicker.annotation.ExpectedScenarios
-import android.tools.common.flicker.annotation.FlickerConfigProvider
-import android.tools.common.flicker.config.FlickerConfig
-import android.tools.common.flicker.config.FlickerServiceConfig
-import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBetweenSplitPairs
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(FlickerServiceJUnit4ClassRunner::class)
-class SwitchBetweenSplitPairsGesturalNavLandscape : SwitchBetweenSplitPairs(Rotation.ROTATION_90) {
-
- @ExpectedScenarios(["QUICKSWITCH"])
- @Test
- override fun switchBetweenSplitPairs() = super.switchBetweenSplitPairs()
-
- companion object {
- @JvmStatic
- @FlickerConfigProvider
- fun flickerConfigProvider(): FlickerConfig =
- FlickerConfig().use(FlickerServiceConfig.DEFAULT)
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavPortrait.kt
deleted file mode 100644
index 627ae18..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavPortrait.kt
+++ /dev/null
@@ -1,43 +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.wm.shell.flicker.service.splitscreen.flicker
-
-import android.tools.common.Rotation
-import android.tools.common.flicker.FlickerConfig
-import android.tools.common.flicker.annotation.ExpectedScenarios
-import android.tools.common.flicker.annotation.FlickerConfigProvider
-import android.tools.common.flicker.config.FlickerConfig
-import android.tools.common.flicker.config.FlickerServiceConfig
-import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBetweenSplitPairs
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(FlickerServiceJUnit4ClassRunner::class)
-class SwitchBetweenSplitPairsGesturalNavPortrait : SwitchBetweenSplitPairs(Rotation.ROTATION_0) {
-
- @ExpectedScenarios(["QUICKSWITCH"])
- @Test
- override fun switchBetweenSplitPairs() = super.switchBetweenSplitPairs()
-
- companion object {
- @JvmStatic
- @FlickerConfigProvider
- fun flickerConfigProvider(): FlickerConfig =
- FlickerConfig().use(FlickerServiceConfig.DEFAULT)
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt
deleted file mode 100644
index 7cbc1c3..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt
+++ /dev/null
@@ -1,31 +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.wm.shell.flicker.service.splitscreen.flicker
-
-import android.tools.common.flicker.annotation.ExpectedScenarios
-import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.UnlockKeyguardToSplitScreen
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(FlickerServiceJUnit4ClassRunner::class)
-class UnlockKeyguardToSplitScreenGesturalNavLandscape : UnlockKeyguardToSplitScreen() {
-
- @ExpectedScenarios(["QUICKSWITCH"])
- @Test
- override fun unlockKeyguardToSplitScreen() = super.unlockKeyguardToSplitScreen()
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt
deleted file mode 100644
index 2eb81e0..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt
+++ /dev/null
@@ -1,31 +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.wm.shell.flicker.service.splitscreen.flicker
-
-import android.tools.common.flicker.annotation.ExpectedScenarios
-import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.UnlockKeyguardToSplitScreen
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(FlickerServiceJUnit4ClassRunner::class)
-class UnlockKeyguardToSplitScreenGesturalNavPortrait : UnlockKeyguardToSplitScreen() {
-
- @ExpectedScenarios(["QUICKSWITCH"])
- @Test
- override fun unlockKeyguardToSplitScreen() = super.unlockKeyguardToSplitScreen()
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/CopyContentInSplitGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavLandscape.kt
similarity index 81%
copy from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/CopyContentInSplitGesturalNavLandscapeBenchmark.kt
copy to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavLandscape.kt
index 566adec..2494054 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/CopyContentInSplitGesturalNavLandscapeBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavLandscape.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.CopyContentInSplit
import org.junit.Test
-@RequiresDevice
-class CopyContentInSplitGesturalNavLandscapeBenchmark : CopyContentInSplit(Rotation.ROTATION_90) {
+class CopyContentInSplitGesturalNavLandscape : CopyContentInSplit(Rotation.ROTATION_90) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/CopyContentInSplitGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavPortrait.kt
similarity index 81%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/CopyContentInSplitGesturalNavLandscapeBenchmark.kt
rename to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavPortrait.kt
index 566adec..57943ec 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/CopyContentInSplitGesturalNavLandscapeBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavPortrait.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.CopyContentInSplit
import org.junit.Test
-@RequiresDevice
-class CopyContentInSplitGesturalNavLandscapeBenchmark : CopyContentInSplit(Rotation.ROTATION_90) {
+class CopyContentInSplitGesturalNavPortrait : CopyContentInSplit(Rotation.ROTATION_0) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByDividerGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavLandscape.kt
similarity index 84%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByDividerGesturalNavLandscapeBenchmark.kt
rename to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavLandscape.kt
index e6d56b5..6f0e202 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByDividerGesturalNavLandscapeBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavLandscape.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByDivider
import org.junit.Test
-@RequiresDevice
-class DismissSplitScreenByDividerGesturalNavLandscapeBenchmark :
+class DismissSplitScreenByDividerGesturalNavLandscape :
DismissSplitScreenByDivider(Rotation.ROTATION_90) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByDividerGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavPortrait.kt
similarity index 84%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByDividerGesturalNavPortraitBenchmark.kt
rename to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavPortrait.kt
index 6752c58..dac8fa2 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByDividerGesturalNavPortraitBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavPortrait.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByDivider
import org.junit.Test
-@RequiresDevice
-class DismissSplitScreenByDividerGesturalNavPortraitBenchmark :
+class DismissSplitScreenByDividerGesturalNavPortrait :
DismissSplitScreenByDivider(Rotation.ROTATION_0) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByGoHomeGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavLandscape.kt
similarity index 84%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByGoHomeGesturalNavLandscapeBenchmark.kt
rename to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavLandscape.kt
index 7c9ab99..baecc16 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByGoHomeGesturalNavLandscapeBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavLandscape.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByGoHome
import org.junit.Test
-@RequiresDevice
-class DismissSplitScreenByGoHomeGesturalNavLandscapeBenchmark :
+class DismissSplitScreenByGoHomeGesturalNavLandscape :
DismissSplitScreenByGoHome(Rotation.ROTATION_90) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByGoHomeGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavPortrait.kt
similarity index 84%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByGoHomeGesturalNavPortraitBenchmark.kt
rename to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavPortrait.kt
index 4b79571..3063ea5 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByGoHomeGesturalNavPortraitBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavPortrait.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByGoHome
import org.junit.Test
-@RequiresDevice
-class DismissSplitScreenByGoHomeGesturalNavPortraitBenchmark :
+class DismissSplitScreenByGoHomeGesturalNavPortrait :
DismissSplitScreenByGoHome(Rotation.ROTATION_0) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DragDividerToResizeGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavLandscape.kt
similarity index 81%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DragDividerToResizeGesturalNavPortraitBenchmark.kt
rename to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavLandscape.kt
index 71ef48b..41660ba 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DragDividerToResizeGesturalNavPortraitBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavLandscape.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.DragDividerToResize
import org.junit.Test
-@RequiresDevice
-class DragDividerToResizeGesturalNavPortraitBenchmark : DragDividerToResize(Rotation.ROTATION_0) {
+class DragDividerToResizeGesturalNavLandscape : DragDividerToResize(Rotation.ROTATION_90) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DragDividerToResizeGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavPortrait.kt
similarity index 81%
copy from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DragDividerToResizeGesturalNavPortraitBenchmark.kt
copy to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavPortrait.kt
index 71ef48b..c41ffb7 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DragDividerToResizeGesturalNavPortraitBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavPortrait.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.DragDividerToResize
import org.junit.Test
-@RequiresDevice
-class DragDividerToResizeGesturalNavPortraitBenchmark : DragDividerToResize(Rotation.ROTATION_0) {
+class DragDividerToResizeGesturalNavPortrait : DragDividerToResize(Rotation.ROTATION_0) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt
similarity index 84%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsGesturalNavLandscapeBenchmark.kt
rename to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt
index c78729c..afde55b 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsGesturalNavLandscapeBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromAllApps
import org.junit.Test
-@RequiresDevice
-class EnterSplitScreenByDragFromAllAppsGesturalNavLandscapeBenchmark :
+class EnterSplitScreenByDragFromAllAppsGesturalNavLandscape :
EnterSplitScreenByDragFromAllApps(Rotation.ROTATION_90) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt
similarity index 84%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsGesturalNavPortraitBenchmark.kt
rename to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt
index 30bce2f6..3765fc4 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsGesturalNavPortraitBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromAllApps
import org.junit.Test
-@RequiresDevice
-class EnterSplitScreenByDragFromAllAppsGesturalNavPortraitBenchmark :
+class EnterSplitScreenByDragFromAllAppsGesturalNavPortrait :
EnterSplitScreenByDragFromAllApps(Rotation.ROTATION_0) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt
similarity index 88%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationGesturalNavLandscapeBenchmark.kt
rename to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt
index b33ea7c..1e128fd 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationGesturalNavLandscapeBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromNotification
import org.junit.Test
-@RequiresDevice
-class EnterSplitScreenByDragFromNotificationGesturalNavLandscapeBenchmark :
+class EnterSplitScreenByDragFromNotificationGesturalNavLandscape :
EnterSplitScreenByDragFromNotification(Rotation.ROTATION_90) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt
similarity index 84%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationGesturalNavPortraitBenchmark.kt
rename to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt
index 07a86a5..7767872 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationGesturalNavPortraitBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromNotification
import org.junit.Test
-@RequiresDevice
-class EnterSplitScreenByDragFromNotificationGesturalNavPortraitBenchmark :
+class EnterSplitScreenByDragFromNotificationGesturalNavPortrait :
EnterSplitScreenByDragFromNotification(Rotation.ROTATION_0) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt
similarity index 84%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutGesturalNavLandscapeBenchmark.kt
rename to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt
index 9a1d127..4ca4bd1 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutGesturalNavLandscapeBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromShortcut
import org.junit.Test
-@RequiresDevice
-class EnterSplitScreenByDragFromShortcutGesturalNavLandscapeBenchmark :
+class EnterSplitScreenByDragFromShortcutGesturalNavLandscape :
EnterSplitScreenByDragFromShortcut(Rotation.ROTATION_90) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt
similarity index 84%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutGesturalNavPortraitBenchmark.kt
rename to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt
index 266e268..2d9d258 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutGesturalNavPortraitBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromShortcut
import org.junit.Test
-@RequiresDevice
-class EnterSplitScreenByDragFromShortcutGesturalNavPortraitBenchmark :
+class EnterSplitScreenByDragFromShortcutGesturalNavPortrait :
EnterSplitScreenByDragFromShortcut(Rotation.ROTATION_0) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt
similarity index 84%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarGesturalNavLandscapeBenchmark.kt
rename to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt
index 83fc30b..c9282ac 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarGesturalNavLandscapeBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromTaskbar
import org.junit.Test
-@RequiresDevice
-class EnterSplitScreenByDragFromTaskbarGesturalNavLandscapeBenchmark :
+class EnterSplitScreenByDragFromTaskbarGesturalNavLandscape :
EnterSplitScreenByDragFromTaskbar(Rotation.ROTATION_90) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt
similarity index 84%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarGesturalNavPortraitBenchmark.kt
rename to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt
index b2f1929..68c6d6c 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarGesturalNavPortraitBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromTaskbar
import org.junit.Test
-@RequiresDevice
-class EnterSplitScreenByDragFromTaskbarGesturalNavPortraitBenchmark :
+class EnterSplitScreenByDragFromTaskbarGesturalNavPortrait :
EnterSplitScreenByDragFromTaskbar(Rotation.ROTATION_0) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenFromOverviewGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavLandscape.kt
similarity index 84%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenFromOverviewGesturalNavLandscapeBenchmark.kt
rename to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavLandscape.kt
index dae92dd..304529e 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenFromOverviewGesturalNavLandscapeBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavLandscape.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenFromOverview
import org.junit.Test
-@RequiresDevice
-class EnterSplitScreenFromOverviewGesturalNavLandscapeBenchmark :
+class EnterSplitScreenFromOverviewGesturalNavLandscape :
EnterSplitScreenFromOverview(Rotation.ROTATION_90) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenFromOverviewGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavPortrait.kt
similarity index 84%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenFromOverviewGesturalNavPortraitBenchmark.kt
rename to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavPortrait.kt
index 732047b..53a6b44 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenFromOverviewGesturalNavPortraitBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavPortrait.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenFromOverview
import org.junit.Test
-@RequiresDevice
-class EnterSplitScreenFromOverviewGesturalNavPortraitBenchmark :
+class EnterSplitScreenFromOverviewGesturalNavPortrait :
EnterSplitScreenFromOverview(Rotation.ROTATION_0) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchAppByDoubleTapDividerGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt
similarity index 84%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchAppByDoubleTapDividerGesturalNavLandscapeBenchmark.kt
rename to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt
index 1de7efd..a467830 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchAppByDoubleTapDividerGesturalNavLandscapeBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchAppByDoubleTapDivider
import org.junit.Test
-@RequiresDevice
-class SwitchAppByDoubleTapDividerGesturalNavLandscapeBenchmark :
+class SwitchAppByDoubleTapDividerGesturalNavLandscape :
SwitchAppByDoubleTapDivider(Rotation.ROTATION_90) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchAppByDoubleTapDividerGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt
similarity index 84%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchAppByDoubleTapDividerGesturalNavPortraitBenchmark.kt
rename to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt
index 1a046aa..1524233 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchAppByDoubleTapDividerGesturalNavPortraitBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchAppByDoubleTapDivider
import org.junit.Test
-@RequiresDevice
-class SwitchAppByDoubleTapDividerGesturalNavPortraitBenchmark :
+class SwitchAppByDoubleTapDividerGesturalNavPortrait :
SwitchAppByDoubleTapDivider(Rotation.ROTATION_0) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt
similarity index 84%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppGesturalNavLandscapeBenchmark.kt
rename to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt
index 6e88f0e..0389659 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppGesturalNavLandscapeBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromAnotherApp
import org.junit.Test
-@RequiresDevice
-class SwitchBackToSplitFromAnotherAppGesturalNavLandscapeBenchmark :
+class SwitchBackToSplitFromAnotherAppGesturalNavLandscape :
SwitchBackToSplitFromAnotherApp(Rotation.ROTATION_90) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt
similarity index 84%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppGesturalNavPortraitBenchmark.kt
rename to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt
index d26a29c..7fadf18 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppGesturalNavPortraitBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromAnotherApp
import org.junit.Test
-@RequiresDevice
-class SwitchBackToSplitFromAnotherAppGesturalNavPortraitBenchmark :
+class SwitchBackToSplitFromAnotherAppGesturalNavPortrait :
SwitchBackToSplitFromAnotherApp(Rotation.ROTATION_0) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromHomeGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavLandscape.kt
similarity index 84%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromHomeGesturalNavLandscapeBenchmark.kt
rename to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavLandscape.kt
index 4a552b0..148cc52 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromHomeGesturalNavLandscapeBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavLandscape.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromHome
import org.junit.Test
-@RequiresDevice
-class SwitchBackToSplitFromHomeGesturalNavLandscapeBenchmark :
+class SwitchBackToSplitFromHomeGesturalNavLandscape :
SwitchBackToSplitFromHome(Rotation.ROTATION_90) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromHomeGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavPortrait.kt
similarity index 84%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromHomeGesturalNavPortraitBenchmark.kt
rename to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavPortrait.kt
index b7376ea..0641fb0 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromHomeGesturalNavPortraitBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavPortrait.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromHome
import org.junit.Test
-@RequiresDevice
-class SwitchBackToSplitFromHomeGesturalNavPortraitBenchmark :
+class SwitchBackToSplitFromHomeGesturalNavPortrait :
SwitchBackToSplitFromHome(Rotation.ROTATION_0) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromRecentGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavLandscape.kt
similarity index 84%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromRecentGesturalNavLandscapeBenchmark.kt
rename to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavLandscape.kt
index b2d05e4..741f871 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromRecentGesturalNavLandscapeBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavLandscape.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromRecent
import org.junit.Test
-@RequiresDevice
-class SwitchBackToSplitFromRecentGesturalNavLandscapeBenchmark :
+class SwitchBackToSplitFromRecentGesturalNavLandscape :
SwitchBackToSplitFromRecent(Rotation.ROTATION_90) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromRecentGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavPortrait.kt
similarity index 84%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromRecentGesturalNavPortraitBenchmark.kt
rename to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavPortrait.kt
index 6de31b1..778e2d6 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromRecentGesturalNavPortraitBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavPortrait.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromRecent
import org.junit.Test
-@RequiresDevice
-class SwitchBackToSplitFromRecentGesturalNavPortraitBenchmark :
+class SwitchBackToSplitFromRecentGesturalNavPortrait :
SwitchBackToSplitFromRecent(Rotation.ROTATION_0) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBetweenSplitPairsGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavLandscape.kt
similarity index 80%
copy from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBetweenSplitPairsGesturalNavPortraitBenchmark.kt
copy to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavLandscape.kt
index b074f2c..dcdca70 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBetweenSplitPairsGesturalNavPortraitBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavLandscape.kt
@@ -14,18 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBetweenSplitPairs
import org.junit.Test
-@RequiresDevice
-class SwitchBetweenSplitPairsGesturalNavPortraitBenchmark :
- SwitchBetweenSplitPairs(Rotation.ROTATION_0) {
+class SwitchBetweenSplitPairsGesturalNavLandscape : SwitchBetweenSplitPairs(Rotation.ROTATION_90) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBetweenSplitPairsGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavPortrait.kt
similarity index 80%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBetweenSplitPairsGesturalNavPortraitBenchmark.kt
rename to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavPortrait.kt
index b074f2c..3c69311 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBetweenSplitPairsGesturalNavPortraitBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavPortrait.kt
@@ -14,18 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBetweenSplitPairs
import org.junit.Test
-@RequiresDevice
-class SwitchBetweenSplitPairsGesturalNavPortraitBenchmark :
- SwitchBetweenSplitPairs(Rotation.ROTATION_0) {
+class SwitchBetweenSplitPairsGesturalNavPortrait : SwitchBetweenSplitPairs(Rotation.ROTATION_0) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/UnlockKeyguardToSplitScreenGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt
similarity index 82%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/UnlockKeyguardToSplitScreenGesturalNavLandscapeBenchmark.kt
rename to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt
index c402aa4..c6566f5 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/UnlockKeyguardToSplitScreenGesturalNavLandscapeBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt
@@ -14,19 +14,17 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.UnlockKeyguardToSplitScreen
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.BlockJUnit4ClassRunner
-@RequiresDevice
@RunWith(BlockJUnit4ClassRunner::class)
-class UnlockKeyguardToSplitScreenGesturalNavLandscapeBenchmark : UnlockKeyguardToSplitScreen() {
+class UnlockKeyguardToSplitScreenGesturalNavLandscape : UnlockKeyguardToSplitScreen() {
@PlatinumTest(focusArea = "sysui")
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/UnlockKeyguardToSplitScreenGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt
similarity index 82%
copy from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/UnlockKeyguardToSplitScreenGesturalNavLandscapeBenchmark.kt
copy to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt
index c402aa4..bb1a502 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/UnlockKeyguardToSplitScreenGesturalNavLandscapeBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt
@@ -14,19 +14,17 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.UnlockKeyguardToSplitScreen
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.BlockJUnit4ClassRunner
-@RequiresDevice
@RunWith(BlockJUnit4ClassRunner::class)
-class UnlockKeyguardToSplitScreenGesturalNavLandscapeBenchmark : UnlockKeyguardToSplitScreen() {
+class UnlockKeyguardToSplitScreenGesturalNavPortrait : UnlockKeyguardToSplitScreen() {
@PlatinumTest(focusArea = "sysui")
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromAllApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromAllApps.kt
index 677aeb07..74f441d 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromAllApps.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromAllApps.kt
@@ -25,6 +25,7 @@
import com.android.launcher3.tapl.LauncherInstrumentation
import com.android.wm.shell.flicker.service.Utils
import org.junit.After
+import org.junit.Assume
import org.junit.Before
import org.junit.Ignore
import org.junit.Rule
@@ -45,6 +46,8 @@
@Before
fun setup() {
+ Assume.assumeTrue(tapl.isTablet)
+
tapl.setEnableRotation(true)
tapl.setExpectedRotation(rotation.value)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt
index f4f6878..8d93c0d 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt
@@ -25,6 +25,7 @@
import com.android.launcher3.tapl.LauncherInstrumentation
import com.android.wm.shell.flicker.service.Utils
import org.junit.After
+import org.junit.Assume
import org.junit.Before
import org.junit.Ignore
import org.junit.Rule
@@ -46,6 +47,8 @@
@Before
fun setup() {
+ Assume.assumeTrue(tapl.isTablet)
+
tapl.setEnableRotation(true)
tapl.setExpectedRotation(rotation.value)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt
index 322f711..ed08041 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt
@@ -25,6 +25,7 @@
import com.android.launcher3.tapl.LauncherInstrumentation
import com.android.wm.shell.flicker.service.Utils
import org.junit.After
+import org.junit.Assume
import org.junit.Before
import org.junit.Ignore
import org.junit.Rule
@@ -45,6 +46,8 @@
@Before
fun setup() {
+ Assume.assumeTrue(tapl.isTablet)
+
tapl.setEnableRotation(true)
tapl.setExpectedRotation(rotation.value)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt
index d44d177..9f9d4bb 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt
@@ -47,6 +47,7 @@
fun setup() {
tapl.setEnableRotation(true)
tapl.setExpectedRotation(rotation.value)
+ tapl.workspace.switchToOverview().dismissAllTasks()
SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandlerTest.java
index c6642f3..885ae38 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandlerTest.java
@@ -99,16 +99,17 @@
final int taskId = 1;
WindowContainerTransaction wct = new WindowContainerTransaction();
doReturn(mToken).when(mTransitions)
- .startTransition(Transitions.TRANSIT_ENTER_FREEFORM, wct,
+ .startTransition(Transitions.TRANSIT_START_MOVE_TO_DESKTOP_MODE, wct,
mEnterDesktopTaskTransitionHandler);
doReturn(taskId).when(mMoveToDesktopAnimator).getTaskId();
- mEnterDesktopTaskTransitionHandler.startMoveToFreeformAnimation(wct,
+ mEnterDesktopTaskTransitionHandler.startMoveToDesktop(wct,
mMoveToDesktopAnimator, null);
TransitionInfo.Change change =
createChange(WindowManager.TRANSIT_CHANGE, taskId, WINDOWING_MODE_FREEFORM);
- TransitionInfo info = createTransitionInfo(Transitions.TRANSIT_ENTER_FREEFORM, change);
+ TransitionInfo info = createTransitionInfo(Transitions.TRANSIT_START_MOVE_TO_DESKTOP_MODE,
+ change);
assertTrue(mEnterDesktopTaskTransitionHandler
@@ -120,17 +121,18 @@
@Test
public void testTransitEnterDesktopModeAnimation() throws Throwable {
- final int transitionType = Transitions.TRANSIT_ENTER_DESKTOP_MODE;
+ final int transitionType = Transitions.TRANSIT_FINALIZE_MOVE_TO_DESKTOP_MODE;
final int taskId = 1;
WindowContainerTransaction wct = new WindowContainerTransaction();
doReturn(mToken).when(mTransitions)
.startTransition(transitionType, wct, mEnterDesktopTaskTransitionHandler);
- mEnterDesktopTaskTransitionHandler.startTransition(transitionType, wct, null);
+ mEnterDesktopTaskTransitionHandler.finalizeMoveToDesktop(wct, null);
TransitionInfo.Change change =
createChange(WindowManager.TRANSIT_CHANGE, taskId, WINDOWING_MODE_FREEFORM);
change.setEndAbsBounds(new Rect(0, 0, 1, 1));
- TransitionInfo info = createTransitionInfo(Transitions.TRANSIT_ENTER_DESKTOP_MODE, change);
+ TransitionInfo info = createTransitionInfo(
+ Transitions.TRANSIT_FINALIZE_MOVE_TO_DESKTOP_MODE, change);
runOnUiThread(() -> {
try {
diff --git a/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightDreamManager.kt b/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightDreamManager.kt
index 96bfb78..d1dd8df 100644
--- a/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightDreamManager.kt
+++ b/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightDreamManager.kt
@@ -23,6 +23,7 @@
import android.util.Log
import com.android.dream.lowlight.dagger.LowLightDreamModule
import com.android.dream.lowlight.dagger.qualifiers.Application
+import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.TimeoutCancellationException
@@ -103,6 +104,11 @@
)
} catch (ex: TimeoutCancellationException) {
Log.e(TAG, "timed out while waiting for low light animation", ex)
+ } catch (ex: CancellationException) {
+ Log.w(TAG, "low light transition animation cancelled")
+ // Catch the cancellation so that we still set the system dream component if the
+ // animation is cancelled, such as by a user tapping to wake as the transition to
+ // low light happens.
}
dreamManager.setSystemDreamComponent(
if (shouldEnterLowLight) lowLightDreamComponent else null
diff --git a/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightTransitionCoordinator.kt b/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightTransitionCoordinator.kt
index 4736030..de1aee5 100644
--- a/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightTransitionCoordinator.kt
+++ b/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightTransitionCoordinator.kt
@@ -110,15 +110,5 @@
}
}
animator.addListener(listener)
- continuation.invokeOnCancellation {
- try {
- animator.removeListener(listener)
- animator.cancel()
- } catch (exception: IndexOutOfBoundsException) {
- // TODO(b/285666217): remove this try/catch once a proper fix is implemented.
- // Cancelling the animator can cause an exception since we may be removing a
- // listener during the cancellation. See b/285666217 for more details.
- }
- }
}
}
diff --git a/libs/dream/lowlight/src/com/android/dream/lowlight/util/TruncatedInterpolator.kt b/libs/dream/lowlight/src/com/android/dream/lowlight/util/TruncatedInterpolator.kt
new file mode 100644
index 0000000..f69c84d
--- /dev/null
+++ b/libs/dream/lowlight/src/com/android/dream/lowlight/util/TruncatedInterpolator.kt
@@ -0,0 +1,54 @@
+/*
+ * 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.dream.lowlight.util
+
+import android.view.animation.Interpolator
+
+/**
+ * Interpolator wrapper that shortens another interpolator from its original duration to a portion
+ * of that duration.
+ *
+ * For example, an `originalDuration` of 1000 and a `newDuration` of 200 results in an animation
+ * that when played for 200ms is the exact same as the first 200ms of a 1000ms animation if using
+ * the original interpolator.
+ *
+ * This is useful for the transition between the user dream and the low light clock as some
+ * animations are defined in the spec to be longer than the total duration of the animation. For
+ * example, the low light clock exit translation animation is defined to last >1s while the actual
+ * fade out of the low light clock is only 250ms, meaning the clock isn't visible anymore after
+ * 250ms.
+ *
+ * Since the dream framework currently only allows one dream to be visible and running, we use this
+ * interpolator to play just the first 250ms of the translation animation. Simply reducing the
+ * duration of the animation would result in the text exiting much faster than intended, so a custom
+ * interpolator is needed.
+ */
+class TruncatedInterpolator(
+ private val baseInterpolator: Interpolator,
+ originalDuration: Float,
+ newDuration: Float
+) : Interpolator {
+ private val scaleFactor: Float
+
+ init {
+ scaleFactor = newDuration / originalDuration
+ }
+
+ override fun getInterpolation(input: Float): Float {
+ return baseInterpolator.getInterpolation(input * scaleFactor)
+ }
+}
diff --git a/libs/dream/lowlight/tests/Android.bp b/libs/dream/lowlight/tests/Android.bp
index 2d79090..64b53cb 100644
--- a/libs/dream/lowlight/tests/Android.bp
+++ b/libs/dream/lowlight/tests/Android.bp
@@ -27,6 +27,7 @@
"androidx.test.runner",
"androidx.test.rules",
"androidx.test.ext.junit",
+ "animationlib",
"frameworks-base-testutils",
"junit",
"kotlinx_coroutines_test",
diff --git a/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightDreamManagerTest.kt b/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightDreamManagerTest.kt
index 2a886bc..de84adb 100644
--- a/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightDreamManagerTest.kt
+++ b/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightDreamManagerTest.kt
@@ -152,6 +152,21 @@
verify(mDreamManager).setSystemDreamComponent(DREAM_COMPONENT)
}
+ @Test
+ fun setAmbientLightMode_animationCancelled_SetsSystemDream() = testScope.runTest {
+ mLowLightDreamManager.setAmbientLightMode(LowLightDreamManager.AMBIENT_LIGHT_MODE_LOW_LIGHT)
+ runCurrent()
+ cancelEnterAnimations()
+ runCurrent()
+ // Animation never finishes, but we should still set the system dream
+ verify(mDreamManager).setSystemDreamComponent(DREAM_COMPONENT)
+ }
+
+ private fun cancelEnterAnimations() {
+ val listener = withArgCaptor { verify(mEnterAnimator).addListener(capture()) }
+ listener.onAnimationCancel(mEnterAnimator)
+ }
+
private fun completeEnterAnimations() {
val listener = withArgCaptor { verify(mEnterAnimator).addListener(capture()) }
listener.onAnimationEnd(mEnterAnimator)
diff --git a/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightTransitionCoordinatorTest.kt b/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightTransitionCoordinatorTest.kt
index 4c526a6..9ae304f 100644
--- a/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightTransitionCoordinatorTest.kt
+++ b/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightTransitionCoordinatorTest.kt
@@ -158,26 +158,6 @@
assertThat(job.isCancelled).isTrue()
}
- @Test
- fun shouldCancelAnimatorWhenJobCancelled() = testScope.runTest {
- whenever(mEnterListener.onBeforeEnterLowLight()).thenReturn(mAnimator)
- val coordinator = LowLightTransitionCoordinator()
- coordinator.setLowLightEnterListener(mEnterListener)
- val job = launch {
- coordinator.waitForLowLightTransitionAnimation(timeout = TIMEOUT, entering = true)
- }
- runCurrent()
- // Animator listener is added and the runnable is not run yet.
- verify(mAnimator).addListener(mAnimatorListenerCaptor.capture())
- verify(mAnimator, never()).cancel()
- assertThat(job.isCompleted).isFalse()
-
- job.cancel()
- // We should have removed the listener and cancelled the animator
- verify(mAnimator).removeListener(mAnimatorListenerCaptor.value)
- verify(mAnimator).cancel()
- }
-
companion object {
private val TIMEOUT = 1.toDuration(DurationUnit.SECONDS)
}
diff --git a/libs/dream/lowlight/tests/src/com/android/dream/lowlight/util/TruncatedInterpolatorTest.kt b/libs/dream/lowlight/tests/src/com/android/dream/lowlight/util/TruncatedInterpolatorTest.kt
new file mode 100644
index 0000000..190f02e
--- /dev/null
+++ b/libs/dream/lowlight/tests/src/com/android/dream/lowlight/util/TruncatedInterpolatorTest.kt
@@ -0,0 +1,53 @@
+/*
+ * 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.dream.lowlight.util
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.app.animation.Interpolators
+import com.google.common.truth.Truth
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class TruncatedInterpolatorTest {
+ @Test
+ fun truncatedInterpolator_matchesRegularInterpolator() {
+ val originalInterpolator = Interpolators.EMPHASIZED
+ val truncatedInterpolator =
+ TruncatedInterpolator(originalInterpolator, ORIGINAL_DURATION_MS, NEW_DURATION_MS)
+
+ // Both interpolators should start at the same value.
+ var animationPercent = 0f
+ Truth.assertThat(truncatedInterpolator.getInterpolation(animationPercent))
+ .isEqualTo(originalInterpolator.getInterpolation(animationPercent))
+
+ animationPercent = 1f
+ Truth.assertThat(truncatedInterpolator.getInterpolation(animationPercent))
+ .isEqualTo(originalInterpolator.getInterpolation(animationPercent * DURATION_RATIO))
+
+ animationPercent = 0.25f
+ Truth.assertThat(truncatedInterpolator.getInterpolation(animationPercent))
+ .isEqualTo(originalInterpolator.getInterpolation(animationPercent * DURATION_RATIO))
+ }
+
+ companion object {
+ private const val ORIGINAL_DURATION_MS: Float = 1000f
+ private const val NEW_DURATION_MS: Float = 200f
+ private const val DURATION_RATIO: Float = NEW_DURATION_MS / ORIGINAL_DURATION_MS
+ }
+}
diff --git a/libs/hwui/DisplayListOps.in b/libs/hwui/DisplayListOps.in
index a18ba1c..d21f07ef 100644
--- a/libs/hwui/DisplayListOps.in
+++ b/libs/hwui/DisplayListOps.in
@@ -14,7 +14,6 @@
* limitations under the License.
*/
-X(Flush)
X(Save)
X(Restore)
X(SaveLayer)
diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp
index d4d5898..3cd0e75 100644
--- a/libs/hwui/RecordingCanvas.cpp
+++ b/libs/hwui/RecordingCanvas.cpp
@@ -107,11 +107,6 @@
};
static_assert(sizeof(Op) == 4, "");
-struct Flush final : Op {
- static const auto kType = Type::Flush;
- void draw(SkCanvas* c, const SkMatrix&) const { c->flush(); }
-};
-
struct Save final : Op {
static const auto kType = Type::Save;
void draw(SkCanvas* c, const SkMatrix&) const { c->save(); }
@@ -751,10 +746,6 @@
}
}
-void DisplayListData::flush() {
- this->push<Flush>(0);
-}
-
void DisplayListData::save() {
this->push<Save>(0);
}
@@ -1046,10 +1037,6 @@
return nullptr;
}
-void RecordingCanvas::onFlush() {
- fDL->flush();
-}
-
void RecordingCanvas::willSave() {
mSaveCount++;
fDL->save();
diff --git a/libs/hwui/RecordingCanvas.h b/libs/hwui/RecordingCanvas.h
index 1f4ba5d..4f54ee2 100644
--- a/libs/hwui/RecordingCanvas.h
+++ b/libs/hwui/RecordingCanvas.h
@@ -127,8 +127,6 @@
private:
friend class RecordingCanvas;
- void flush();
-
void save();
void saveLayer(const SkRect*, const SkPaint*, const SkImageFilter*, SkCanvas::SaveLayerFlags);
void saveBehind(const SkRect*);
@@ -208,8 +206,6 @@
void willRestore() override;
bool onDoSaveBehind(const SkRect*) override;
- void onFlush() override;
-
void didConcat44(const SkM44&) override;
void didSetM44(const SkM44&) override;
void didScale(SkScalar, SkScalar) override;
diff --git a/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp b/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
index d7ac501..dbd9ef3 100644
--- a/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
+++ b/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
@@ -79,7 +79,7 @@
}
// flush will create a GrRenderTarget if not already present.
- canvas->flush();
+ directContext->flushAndSubmit();
GLuint fboID = 0;
SkISize fboSize;
@@ -167,7 +167,7 @@
// GL ops get inserted here if previous flush is missing, which could dirty the stencil
bool stencilWritten = SkAndroidFrameworkUtils::clipWithStencil(tmpCanvas);
- tmpCanvas->flush(); // need this flush for the single op that draws into the stencil
+ directContext->flushAndSubmit(); // need this flush for the single op that draws into the stencil
// ensure that the framebuffer that the webview will render into is bound before after we
// draw into the stencil
diff --git a/libs/hwui/pipeline/skia/ShaderCache.cpp b/libs/hwui/pipeline/skia/ShaderCache.cpp
index f71e728..b870023 100644
--- a/libs/hwui/pipeline/skia/ShaderCache.cpp
+++ b/libs/hwui/pipeline/skia/ShaderCache.cpp
@@ -88,6 +88,9 @@
mBlobCache.reset(new FileBlobCache(maxKeySize, maxValueSize, maxTotalSize, mFilename));
validateCache(identity, size);
mInitialized = true;
+ if (identity != nullptr && size > 0 && mIDHash.size()) {
+ set(&sIDKey, sizeof(sIDKey), mIDHash.data(), mIDHash.size());
+ }
}
}
@@ -96,11 +99,6 @@
mFilename = filename;
}
-BlobCache* ShaderCache::getBlobCacheLocked() {
- LOG_ALWAYS_FATAL_IF(!mInitialized, "ShaderCache has not been initialized");
- return mBlobCache.get();
-}
-
sk_sp<SkData> ShaderCache::load(const SkData& key) {
ATRACE_NAME("ShaderCache::load");
size_t keySize = key.size();
@@ -115,8 +113,7 @@
if (!valueBuffer) {
return nullptr;
}
- BlobCache* bc = getBlobCacheLocked();
- size_t valueSize = bc->get(key.data(), keySize, valueBuffer, mObservedBlobValueSize);
+ size_t valueSize = mBlobCache->get(key.data(), keySize, valueBuffer, mObservedBlobValueSize);
int maxTries = 3;
while (valueSize > mObservedBlobValueSize && maxTries > 0) {
mObservedBlobValueSize = std::min(valueSize, maxValueSize);
@@ -126,7 +123,7 @@
return nullptr;
}
valueBuffer = newValueBuffer;
- valueSize = bc->get(key.data(), keySize, valueBuffer, mObservedBlobValueSize);
+ valueSize = mBlobCache->get(key.data(), keySize, valueBuffer, mObservedBlobValueSize);
maxTries--;
}
if (!valueSize) {
@@ -143,16 +140,17 @@
return SkData::MakeFromMalloc(valueBuffer, valueSize);
}
-namespace {
-// Helper for BlobCache::set to trace the result.
-void set(BlobCache* cache, const void* key, size_t keySize, const void* value, size_t valueSize) {
- switch (cache->set(key, keySize, value, valueSize)) {
+void ShaderCache::set(const void* key, size_t keySize, const void* value, size_t valueSize) {
+ switch (mBlobCache->set(key, keySize, value, valueSize)) {
case BlobCache::InsertResult::kInserted:
// This is what we expect/hope. It means the cache is large enough.
return;
case BlobCache::InsertResult::kDidClean: {
ATRACE_FORMAT("ShaderCache: evicted an entry to fit {key: %lu value %lu}!", keySize,
valueSize);
+ if (mIDHash.size()) {
+ set(&sIDKey, sizeof(sIDKey), mIDHash.data(), mIDHash.size());
+ }
return;
}
case BlobCache::InsertResult::kNotEnoughSpace: {
@@ -172,15 +170,10 @@
}
}
}
-} // namespace
void ShaderCache::saveToDiskLocked() {
ATRACE_NAME("ShaderCache::saveToDiskLocked");
if (mInitialized && mBlobCache) {
- if (mIDHash.size()) {
- auto key = sIDKey;
- set(mBlobCache.get(), &key, sizeof(key), mIDHash.data(), mIDHash.size());
- }
// The most straightforward way to make ownership shared
mMutex.unlock();
mMutex.lock_shared();
@@ -209,11 +202,10 @@
const void* value = data.data();
- BlobCache* bc = getBlobCacheLocked();
if (mInStoreVkPipelineInProgress) {
if (mOldPipelineCacheSize == -1) {
// Record the initial pipeline cache size stored in the file.
- mOldPipelineCacheSize = bc->get(key.data(), keySize, nullptr, 0);
+ mOldPipelineCacheSize = mBlobCache->get(key.data(), keySize, nullptr, 0);
}
if (mNewPipelineCacheSize != -1 && mNewPipelineCacheSize == valueSize) {
// There has not been change in pipeline cache size. Stop trying to save.
@@ -228,7 +220,7 @@
mNewPipelineCacheSize = -1;
mTryToStorePipelineCache = true;
}
- set(bc, key.data(), keySize, value, valueSize);
+ set(key.data(), keySize, value, valueSize);
if (!mSavePending && mDeferredSaveDelayMs > 0) {
mSavePending = true;
diff --git a/libs/hwui/pipeline/skia/ShaderCache.h b/libs/hwui/pipeline/skia/ShaderCache.h
index c63a65a..6ccb212 100644
--- a/libs/hwui/pipeline/skia/ShaderCache.h
+++ b/libs/hwui/pipeline/skia/ShaderCache.h
@@ -97,20 +97,18 @@
void operator=(const ShaderCache&) = delete;
/**
- * "getBlobCacheLocked" returns the BlobCache object being used to store the
- * key/value blob pairs. If the BlobCache object has not yet been created,
- * this will do so, loading the serialized cache contents from disk if
- * possible.
- */
- BlobCache* getBlobCacheLocked() REQUIRES(mMutex);
-
- /**
* "validateCache" updates the cache to match the given identity. If the
* cache currently has the wrong identity, all entries in the cache are cleared.
*/
bool validateCache(const void* identity, ssize_t size) REQUIRES(mMutex);
/**
+ * Helper for BlobCache::set to trace the result and ensure the identity hash
+ * does not get evicted.
+ */
+ void set(const void* key, size_t keySize, const void* value, size_t valueSize) REQUIRES(mMutex);
+
+ /**
* "saveToDiskLocked" attempts to save the current contents of the cache to
* disk. If the identity hash exists, we will insert the identity hash into
* the cache for next validation.
@@ -128,11 +126,9 @@
bool mInitialized GUARDED_BY(mMutex) = false;
/**
- * "mBlobCache" is the cache in which the key/value blob pairs are stored. It
- * is initially NULL, and will be initialized by getBlobCacheLocked the
- * first time it's needed.
- * The blob cache contains the Android build number. We treat version mismatches as an empty
- * cache (logic implemented in BlobCache::unflatten).
+ * "mBlobCache" is the cache in which the key/value blob pairs are stored.
+ * The blob cache contains the Android build number. We treat version mismatches
+ * as an empty cache (logic implemented in BlobCache::unflatten).
*/
std::unique_ptr<FileBlobCache> mBlobCache GUARDED_BY(mMutex);
diff --git a/libs/hwui/renderthread/CacheManager.h b/libs/hwui/renderthread/CacheManager.h
index 5e43ac2..bcfa4f3 100644
--- a/libs/hwui/renderthread/CacheManager.h
+++ b/libs/hwui/renderthread/CacheManager.h
@@ -64,12 +64,13 @@
void unregisterCanvasContext(CanvasContext* context);
void onContextStopped(CanvasContext* context);
+ bool areAllContextsStopped();
+
private:
friend class RenderThread;
explicit CacheManager(RenderThread& thread);
void setupCacheLimits();
- bool areAllContextsStopped();
void checkUiHidden();
void scheduleDestroyContext();
void cancelDestroyContext();
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index a5518eb..2ef7802 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -125,6 +125,7 @@
, mRenderPipeline(std::move(renderPipeline))
, mHintSessionWrapper(uiThreadId, renderThreadId) {
mRenderThread.cacheManager().registerCanvasContext(this);
+ mRenderThread.renderState().registerContextCallback(this);
rootRenderNode->makeRoot();
mRenderNodes.emplace_back(rootRenderNode);
mProfiler.setDensity(DeviceInfo::getDensity());
@@ -137,6 +138,7 @@
}
mRenderNodes.clear();
mRenderThread.cacheManager().unregisterCanvasContext(this);
+ mRenderThread.renderState().removeContextCallback(this);
}
void CanvasContext::addRenderNode(RenderNode* node, bool placeFront) {
@@ -963,6 +965,10 @@
}
}
+void CanvasContext::onContextDestroyed() {
+ destroyHardwareResources();
+}
+
DeferredLayerUpdater* CanvasContext::createTextureLayer() {
return mRenderPipeline->createTextureLayer();
}
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index 32ac5af..3f02674 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -43,6 +43,7 @@
#include "Lighting.h"
#include "ReliableSurface.h"
#include "RenderNode.h"
+#include "renderstate/RenderState.h"
#include "renderthread/RenderTask.h"
#include "renderthread/RenderThread.h"
#include "utils/RingBuffer.h"
@@ -64,7 +65,7 @@
// This per-renderer class manages the bridge between the global EGL context
// and the render surface.
// TODO: Rename to Renderer or some other per-window, top-level manager
-class CanvasContext : public IFrameCallback {
+class CanvasContext : public IFrameCallback, public IGpuContextCallback {
public:
static CanvasContext* create(RenderThread& thread, bool translucent, RenderNode* rootRenderNode,
IContextFactory* contextFactory, pid_t uiThreadId,
@@ -154,6 +155,7 @@
void markLayerInUse(RenderNode* node);
void destroyHardwareResources();
+ void onContextDestroyed() override;
DeferredLayerUpdater* createTextureLayer();
diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp
index eb45707..f340945 100644
--- a/libs/hwui/renderthread/VulkanManager.cpp
+++ b/libs/hwui/renderthread/VulkanManager.cpp
@@ -41,7 +41,7 @@
namespace uirenderer {
namespace renderthread {
-static std::array<std::string_view, 19> sEnableExtensions{
+static std::array<std::string_view, 20> sEnableExtensions{
VK_KHR_BIND_MEMORY_2_EXTENSION_NAME,
VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME,
VK_KHR_EXTERNAL_MEMORY_CAPABILITIES_EXTENSION_NAME,
@@ -61,6 +61,7 @@
VK_EXT_QUEUE_FAMILY_FOREIGN_EXTENSION_NAME,
VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME,
VK_KHR_ANDROID_SURFACE_EXTENSION_NAME,
+ VK_EXT_GLOBAL_PRIORITY_EXTENSION_NAME,
};
static bool shouldEnableExtension(const std::string_view& extension) {
diff --git a/libs/hwui/tests/common/TestUtils.h b/libs/hwui/tests/common/TestUtils.h
index 81ecfe5..7ef82a7 100644
--- a/libs/hwui/tests/common/TestUtils.h
+++ b/libs/hwui/tests/common/TestUtils.h
@@ -307,13 +307,21 @@
int destroyed = 0;
int removeOverlays = 0;
int glesDraw = 0;
+ int vkInitialize = 0;
+ int vkDraw = 0;
+ int vkPostDraw = 0;
};
static void expectOnRenderThread(const std::string_view& function = "unknown") {
EXPECT_EQ(gettid(), TestUtils::getRenderThreadTid()) << "Called on wrong thread: " << function;
}
- static WebViewFunctorCallbacks createMockFunctor(RenderMode mode) {
+ static int createMockFunctor() {
+ const auto renderMode = WebViewFunctor_queryPlatformRenderMode();
+ return WebViewFunctor_create(nullptr, createMockFunctorCallbacks(renderMode), renderMode);
+ }
+
+ static WebViewFunctorCallbacks createMockFunctorCallbacks(RenderMode mode) {
auto callbacks = WebViewFunctorCallbacks{
.onSync =
[](int functor, void* client_data, const WebViewSyncData& data) {
@@ -345,9 +353,22 @@
sMockFunctorCounts[functor].glesDraw++;
};
break;
- default:
- ADD_FAILURE();
- return WebViewFunctorCallbacks{};
+ case RenderMode::Vulkan:
+ callbacks.vk.initialize = [](int functor, void* data,
+ const VkFunctorInitParams& params) {
+ expectOnRenderThread("initialize");
+ sMockFunctorCounts[functor].vkInitialize++;
+ };
+ callbacks.vk.draw = [](int functor, void* data, const VkFunctorDrawParams& params,
+ const WebViewOverlayData& overlayParams) {
+ expectOnRenderThread("draw");
+ sMockFunctorCounts[functor].vkDraw++;
+ };
+ callbacks.vk.postDraw = [](int functor, void* data) {
+ expectOnRenderThread("postDraw");
+ sMockFunctorCounts[functor].vkPostDraw++;
+ };
+ break;
}
return callbacks;
}
diff --git a/libs/hwui/tests/macrobench/main.cpp b/libs/hwui/tests/macrobench/main.cpp
index f3f32eb..e227999 100644
--- a/libs/hwui/tests/macrobench/main.cpp
+++ b/libs/hwui/tests/macrobench/main.cpp
@@ -14,30 +14,32 @@
* limitations under the License.
*/
-#include "tests/common/LeakChecker.h"
-#include "tests/common/TestScene.h"
-
-#include "Properties.h"
-#include "hwui/Typeface.h"
-#include "HardwareBitmapUploader.h"
-#include "renderthread/RenderProxy.h"
-
+#include <android-base/parsebool.h>
#include <benchmark/benchmark.h>
+#include <errno.h>
+#include <fcntl.h>
#include <fnmatch.h>
#include <getopt.h>
#include <pthread.h>
#include <stdio.h>
+#include <sys/stat.h>
+#include <sys/types.h>
#include <unistd.h>
+
+#include <regex>
#include <string>
#include <unordered_map>
#include <vector>
-#include <errno.h>
-#include <fcntl.h>
-#include <sys/stat.h>
-#include <sys/types.h>
+#include "HardwareBitmapUploader.h"
+#include "Properties.h"
+#include "hwui/Typeface.h"
+#include "renderthread/RenderProxy.h"
+#include "tests/common/LeakChecker.h"
+#include "tests/common/TestScene.h"
using namespace android;
+using namespace android::base;
using namespace android::uirenderer;
using namespace android::uirenderer::test;
@@ -69,6 +71,9 @@
--onscreen Render tests on device screen. By default tests
are offscreen rendered
--benchmark_format Set output format. Possible values are tabular, json, csv
+ --benchmark_list_tests Lists the tests that would run but does not run them
+ --benchmark_filter=<regex> Filters the test set to the given regex. If prefixed with `-` and test
+ that doesn't match the given regex is run
--renderer=TYPE Sets the render pipeline to use. May be skiagl or skiavk
--skip-leak-check Skips the memory leak check
--report-gpu-memory[=verbose] Dumps the GPU memory usage after each test run
@@ -140,6 +145,9 @@
if (!strcmp(format, "tabular")) {
gBenchmarkReporter.reset(new benchmark::ConsoleReporter());
} else if (!strcmp(format, "json")) {
+ // We cannot print the leak check if outputing to JSON as that will break
+ // JSON parsers since it's not JSON-formatted
+ gRunLeakCheck = false;
gBenchmarkReporter.reset(new benchmark::JSONReporter());
} else {
fprintf(stderr, "Unknown format '%s'\n", format);
@@ -160,6 +168,24 @@
return true;
}
+static void addTestsThatMatchFilter(std::string spec) {
+ if (spec.empty() || spec == "all") {
+ spec = "."; // Regexp that matches all benchmarks
+ }
+ bool isNegativeFilter = false;
+ if (spec[0] == '-') {
+ spec.replace(0, 1, "");
+ isNegativeFilter = true;
+ }
+ std::regex re(spec, std::regex_constants::extended);
+ for (auto& iter : TestScene::testMap()) {
+ if ((isNegativeFilter && !std::regex_search(iter.first, re)) ||
+ (!isNegativeFilter && std::regex_search(iter.first, re))) {
+ gRunTests.push_back(iter.second);
+ }
+ }
+}
+
// For options that only exist in long-form. Anything in the
// 0-255 range is reserved for short options (which just use their ASCII value)
namespace LongOpts {
@@ -170,6 +196,8 @@
ReportFrametime,
CpuSet,
BenchmarkFormat,
+ BenchmarkListTests,
+ BenchmarkFilter,
Onscreen,
Offscreen,
Renderer,
@@ -179,14 +207,16 @@
}
static const struct option LONG_OPTIONS[] = {
- {"frames", required_argument, nullptr, 'f'},
- {"repeat", required_argument, nullptr, 'r'},
+ {"count", required_argument, nullptr, 'c'},
+ {"runs", required_argument, nullptr, 'r'},
{"help", no_argument, nullptr, 'h'},
{"list", no_argument, nullptr, LongOpts::List},
{"wait-for-gpu", no_argument, nullptr, LongOpts::WaitForGpu},
{"report-frametime", optional_argument, nullptr, LongOpts::ReportFrametime},
{"cpuset", required_argument, nullptr, LongOpts::CpuSet},
{"benchmark_format", required_argument, nullptr, LongOpts::BenchmarkFormat},
+ {"benchmark_list_tests", optional_argument, nullptr, LongOpts::BenchmarkListTests},
+ {"benchmark_filter", required_argument, nullptr, LongOpts::BenchmarkFilter},
{"onscreen", no_argument, nullptr, LongOpts::Onscreen},
{"offscreen", no_argument, nullptr, LongOpts::Offscreen},
{"renderer", required_argument, nullptr, LongOpts::Renderer},
@@ -197,8 +227,12 @@
static const char* SHORT_OPTIONS = "c:r:h";
void parseOptions(int argc, char* argv[]) {
+ benchmark::BenchmarkReporter::Context::executable_name = (argc > 0) ? argv[0] : "unknown";
+
int c;
bool error = false;
+ bool listTestsOnly = false;
+ bool testsAreFiltered = false;
opterr = 0;
while (true) {
@@ -272,6 +306,21 @@
}
break;
+ case LongOpts::BenchmarkListTests:
+ if (!optarg || ParseBool(optarg) == ParseBoolResult::kTrue) {
+ listTestsOnly = true;
+ }
+ break;
+
+ case LongOpts::BenchmarkFilter:
+ if (!optarg) {
+ error = true;
+ break;
+ }
+ addTestsThatMatchFilter(optarg);
+ testsAreFiltered = true;
+ break;
+
case LongOpts::Renderer:
if (!optarg) {
error = true;
@@ -346,11 +395,18 @@
}
}
} while (optind < argc);
- } else {
+ } else if (gRunTests.empty() && !testsAreFiltered) {
for (auto& iter : TestScene::testMap()) {
gRunTests.push_back(iter.second);
}
}
+
+ if (listTestsOnly) {
+ for (auto& iter : gRunTests) {
+ std::cout << iter.name << std::endl;
+ }
+ exit(EXIT_SUCCESS);
+ }
}
int main(int argc, char* argv[]) {
diff --git a/libs/hwui/tests/unit/CanvasContextTests.cpp b/libs/hwui/tests/unit/CanvasContextTests.cpp
index 9e376e3..47a4105 100644
--- a/libs/hwui/tests/unit/CanvasContextTests.cpp
+++ b/libs/hwui/tests/unit/CanvasContextTests.cpp
@@ -19,6 +19,7 @@
#include "AnimationContext.h"
#include "IContextFactory.h"
#include "renderthread/CanvasContext.h"
+#include "renderthread/VulkanManager.h"
#include "tests/common/TestUtils.h"
using namespace android;
@@ -42,3 +43,38 @@
canvasContext->destroy();
}
+
+RENDERTHREAD_TEST(CanvasContext, buildLayerDoesntLeak) {
+ auto node = TestUtils::createNode(0, 0, 200, 400, [](RenderProperties& props, Canvas& canvas) {
+ canvas.drawColor(0xFFFF0000, SkBlendMode::kSrc);
+ });
+ ASSERT_TRUE(node->isValid());
+ EXPECT_EQ(LayerType::None, node->stagingProperties().effectiveLayerType());
+ node->mutateStagingProperties().mutateLayerProperties().setType(LayerType::RenderLayer);
+
+ auto& cacheManager = renderThread.cacheManager();
+ EXPECT_TRUE(cacheManager.areAllContextsStopped());
+ ContextFactory contextFactory;
+ std::unique_ptr<CanvasContext> canvasContext(
+ CanvasContext::create(renderThread, false, node.get(), &contextFactory, 0, 0));
+ canvasContext->buildLayer(node.get());
+ EXPECT_TRUE(node->hasLayer());
+ if (Properties::getRenderPipelineType() == RenderPipelineType::SkiaVulkan) {
+ auto instance = VulkanManager::peekInstance();
+ if (instance) {
+ EXPECT_TRUE(instance->hasVkContext());
+ } else {
+ ADD_FAILURE() << "VulkanManager wasn't initialized to buildLayer?";
+ }
+ }
+ renderThread.destroyRenderingContext();
+ EXPECT_FALSE(node->hasLayer()) << "Node still has a layer after rendering context destroyed";
+
+ if (Properties::getRenderPipelineType() == RenderPipelineType::SkiaVulkan) {
+ auto instance = VulkanManager::peekInstance();
+ if (instance) {
+ ADD_FAILURE() << "VulkanManager still exists";
+ EXPECT_FALSE(instance->hasVkContext());
+ }
+ }
+}
diff --git a/libs/hwui/tests/unit/RenderNodeTests.cpp b/libs/hwui/tests/unit/RenderNodeTests.cpp
index 80796f4..8273524 100644
--- a/libs/hwui/tests/unit/RenderNodeTests.cpp
+++ b/libs/hwui/tests/unit/RenderNodeTests.cpp
@@ -231,8 +231,7 @@
}
TEST(RenderNode, releasedCallback) {
- int functor = WebViewFunctor_create(
- nullptr, TestUtils::createMockFunctor(RenderMode::OpenGL_ES), RenderMode::OpenGL_ES);
+ int functor = TestUtils::createMockFunctor();
auto node = TestUtils::createNode(0, 0, 200, 400, [&](RenderProperties& props, Canvas& canvas) {
canvas.drawWebViewFunctor(functor);
diff --git a/libs/hwui/tests/unit/SkiaDisplayListTests.cpp b/libs/hwui/tests/unit/SkiaDisplayListTests.cpp
index f825d7c..1a1ce1e 100644
--- a/libs/hwui/tests/unit/SkiaDisplayListTests.cpp
+++ b/libs/hwui/tests/unit/SkiaDisplayListTests.cpp
@@ -48,8 +48,7 @@
SkCanvas dummyCanvas;
RenderNodeDrawable drawable(nullptr, &dummyCanvas);
skiaDL->mChildNodes.emplace_back(nullptr, &dummyCanvas);
- int functor1 = WebViewFunctor_create(
- nullptr, TestUtils::createMockFunctor(RenderMode::OpenGL_ES), RenderMode::OpenGL_ES);
+ int functor1 = TestUtils::createMockFunctor();
GLFunctorDrawable functorDrawable{functor1, &dummyCanvas};
WebViewFunctor_release(functor1);
skiaDL->mChildFunctors.push_back(&functorDrawable);
@@ -101,8 +100,7 @@
SkCanvas dummyCanvas;
- int functor1 = WebViewFunctor_create(
- nullptr, TestUtils::createMockFunctor(RenderMode::OpenGL_ES), RenderMode::OpenGL_ES);
+ int functor1 = TestUtils::createMockFunctor();
auto& counts = TestUtils::countsForFunctor(functor1);
skiaDL.mChildFunctors.push_back(
skiaDL.allocateDrawable<GLFunctorDrawable>(functor1, &dummyCanvas));
diff --git a/libs/hwui/tests/unit/WebViewFunctorManagerTests.cpp b/libs/hwui/tests/unit/WebViewFunctorManagerTests.cpp
index e1fb8b7..5e8f13d 100644
--- a/libs/hwui/tests/unit/WebViewFunctorManagerTests.cpp
+++ b/libs/hwui/tests/unit/WebViewFunctorManagerTests.cpp
@@ -26,9 +26,15 @@
using namespace android;
using namespace android::uirenderer;
+#define ASSUME_GLES() \
+ if (WebViewFunctor_queryPlatformRenderMode() != RenderMode::OpenGL_ES) \
+ GTEST_SKIP() << "Not in GLES, skipping test"
+
TEST(WebViewFunctor, createDestroyGLES) {
+ ASSUME_GLES();
int functor = WebViewFunctor_create(
- nullptr, TestUtils::createMockFunctor(RenderMode::OpenGL_ES), RenderMode::OpenGL_ES);
+ nullptr, TestUtils::createMockFunctorCallbacks(RenderMode::OpenGL_ES),
+ RenderMode::OpenGL_ES);
ASSERT_NE(-1, functor);
WebViewFunctor_release(functor);
TestUtils::runOnRenderThreadUnmanaged([](renderthread::RenderThread&) {
@@ -41,8 +47,10 @@
}
TEST(WebViewFunctor, createSyncHandleGLES) {
+ ASSUME_GLES();
int functor = WebViewFunctor_create(
- nullptr, TestUtils::createMockFunctor(RenderMode::OpenGL_ES), RenderMode::OpenGL_ES);
+ nullptr, TestUtils::createMockFunctorCallbacks(RenderMode::OpenGL_ES),
+ RenderMode::OpenGL_ES);
ASSERT_NE(-1, functor);
auto handle = WebViewFunctorManager::instance().handleFor(functor);
ASSERT_TRUE(handle);
@@ -82,8 +90,10 @@
}
TEST(WebViewFunctor, createSyncDrawGLES) {
+ ASSUME_GLES();
int functor = WebViewFunctor_create(
- nullptr, TestUtils::createMockFunctor(RenderMode::OpenGL_ES), RenderMode::OpenGL_ES);
+ nullptr, TestUtils::createMockFunctorCallbacks(RenderMode::OpenGL_ES),
+ RenderMode::OpenGL_ES);
ASSERT_NE(-1, functor);
auto handle = WebViewFunctorManager::instance().handleFor(functor);
ASSERT_TRUE(handle);
@@ -108,9 +118,11 @@
EXPECT_EQ(1, counts.destroyed);
}
-TEST(WebViewFunctor, contextDestroyed) {
+TEST(WebViewFunctor, contextDestroyedGLES) {
+ ASSUME_GLES();
int functor = WebViewFunctor_create(
- nullptr, TestUtils::createMockFunctor(RenderMode::OpenGL_ES), RenderMode::OpenGL_ES);
+ nullptr, TestUtils::createMockFunctorCallbacks(RenderMode::OpenGL_ES),
+ RenderMode::OpenGL_ES);
ASSERT_NE(-1, functor);
auto handle = WebViewFunctorManager::instance().handleFor(functor);
ASSERT_TRUE(handle);
diff --git a/media/java/android/media/MediaRoute2Info.java b/media/java/android/media/MediaRoute2Info.java
index 3def1d0..91fa873 100644
--- a/media/java/android/media/MediaRoute2Info.java
+++ b/media/java/android/media/MediaRoute2Info.java
@@ -450,27 +450,27 @@
public static final String FEATURE_REMOTE_GROUP_PLAYBACK =
"android.media.route.feature.REMOTE_GROUP_PLAYBACK";
- final String mId;
- final CharSequence mName;
- final List<String> mFeatures;
+ private final String mId;
+ private final CharSequence mName;
+ private final List<String> mFeatures;
@Type
- final int mType;
- final boolean mIsSystem;
- final Uri mIconUri;
- final CharSequence mDescription;
+ private final int mType;
+ private final boolean mIsSystem;
+ private final Uri mIconUri;
+ private final CharSequence mDescription;
@ConnectionState
- final int mConnectionState;
- final String mClientPackageName;
- final String mPackageName;
- final int mVolumeHandling;
- final int mVolumeMax;
- final int mVolume;
- final String mAddress;
- final Set<String> mDeduplicationIds;
- final Bundle mExtras;
- final String mProviderId;
- final boolean mIsVisibilityRestricted;
- final Set<String> mAllowedPackages;
+ private final int mConnectionState;
+ private final String mClientPackageName;
+ private final String mPackageName;
+ private final int mVolumeHandling;
+ private final int mVolumeMax;
+ private final int mVolume;
+ private final String mAddress;
+ private final Set<String> mDeduplicationIds;
+ private final Bundle mExtras;
+ private final String mProviderId;
+ private final boolean mIsVisibilityRestricted;
+ private final Set<String> mAllowedPackages;
MediaRoute2Info(@NonNull Builder builder) {
mId = builder.mId;
@@ -949,28 +949,28 @@
* Builder for {@link MediaRoute2Info media route info}.
*/
public static final class Builder {
- final String mId;
- final CharSequence mName;
- final List<String> mFeatures;
+ private final String mId;
+ private final CharSequence mName;
+ private final List<String> mFeatures;
@Type
- int mType = TYPE_UNKNOWN;
- boolean mIsSystem;
- Uri mIconUri;
- CharSequence mDescription;
+ private int mType = TYPE_UNKNOWN;
+ private boolean mIsSystem;
+ private Uri mIconUri;
+ private CharSequence mDescription;
@ConnectionState
- int mConnectionState;
- String mClientPackageName;
- String mPackageName;
- int mVolumeHandling = PLAYBACK_VOLUME_FIXED;
- int mVolumeMax;
- int mVolume;
- String mAddress;
- Set<String> mDeduplicationIds;
- Bundle mExtras;
- String mProviderId;
- boolean mIsVisibilityRestricted;
- Set<String> mAllowedPackages;
+ private int mConnectionState;
+ private String mClientPackageName;
+ private String mPackageName;
+ private int mVolumeHandling = PLAYBACK_VOLUME_FIXED;
+ private int mVolumeMax;
+ private int mVolume;
+ private String mAddress;
+ private Set<String> mDeduplicationIds;
+ private Bundle mExtras;
+ private String mProviderId;
+ private boolean mIsVisibilityRestricted;
+ private Set<String> mAllowedPackages;
/**
* Constructor for builder to create {@link MediaRoute2Info}.
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiver.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiver.java
index 9b33704..eccf604 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiver.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiver.java
@@ -191,6 +191,8 @@
&& isPendingIntentValid(intent, SlicePurchaseController.EXTRA_INTENT_REQUEST_FAILED)
&& isPendingIntentValid(intent,
SlicePurchaseController.EXTRA_INTENT_NOT_DEFAULT_DATA_SUBSCRIPTION)
+ && isPendingIntentValid(intent,
+ SlicePurchaseController.EXTRA_INTENT_NOTIFICATIONS_DISABLED)
&& isPendingIntentValid(intent, SlicePurchaseController.EXTRA_INTENT_SUCCESS)
&& isPendingIntentValid(intent,
SlicePurchaseController.EXTRA_INTENT_NOTIFICATION_SHOWN);
@@ -276,6 +278,8 @@
case SlicePurchaseController.EXTRA_INTENT_REQUEST_FAILED: return "request failed";
case SlicePurchaseController.EXTRA_INTENT_NOT_DEFAULT_DATA_SUBSCRIPTION:
return "not default data subscription";
+ case SlicePurchaseController.EXTRA_INTENT_NOTIFICATIONS_DISABLED:
+ return "notifications disabled";
case SlicePurchaseController.EXTRA_INTENT_SUCCESS: return "success";
case SlicePurchaseController.EXTRA_INTENT_NOTIFICATION_SHOWN:
return "notification shown";
@@ -321,26 +325,45 @@
}
private void onDisplayPerformanceBoostNotification(@NonNull Context context,
- @NonNull Intent intent, boolean repeat) {
- if (!repeat && !isIntentValid(intent)) {
+ @NonNull Intent intent, boolean localeChanged) {
+ if (!localeChanged && !isIntentValid(intent)) {
sendSlicePurchaseAppResponse(intent,
SlicePurchaseController.EXTRA_INTENT_REQUEST_FAILED);
return;
}
Resources res = getResources(context);
- NotificationChannel channel = new NotificationChannel(
- PERFORMANCE_BOOST_NOTIFICATION_CHANNEL_ID,
- res.getString(R.string.performance_boost_notification_channel),
- NotificationManager.IMPORTANCE_DEFAULT);
- // CarrierDefaultApp notifications are unblockable by default. Make this channel blockable
- // to allow users to disable notifications posted to this channel without affecting other
- // notifications in this application.
- channel.setBlockable(true);
- context.getSystemService(NotificationManager.class).createNotificationChannel(channel);
+ NotificationManager notificationManager =
+ context.getSystemService(NotificationManager.class);
+ NotificationChannel channel = notificationManager.getNotificationChannel(
+ PERFORMANCE_BOOST_NOTIFICATION_CHANNEL_ID);
+ if (channel == null) {
+ channel = new NotificationChannel(
+ PERFORMANCE_BOOST_NOTIFICATION_CHANNEL_ID,
+ res.getString(R.string.performance_boost_notification_channel),
+ NotificationManager.IMPORTANCE_DEFAULT);
+ // CarrierDefaultApp notifications are unblockable by default.
+ // Make this channel blockable to allow users to disable notifications posted to this
+ // channel without affecting other notifications in this application.
+ channel.setBlockable(true);
+ context.getSystemService(NotificationManager.class).createNotificationChannel(channel);
+ } else if (localeChanged) {
+ // If the channel already exists but the locale has changed, update the channel name.
+ channel.setName(res.getString(R.string.performance_boost_notification_channel));
+ }
+
+ boolean channelNotificationsDisabled =
+ channel.getImportance() == NotificationManager.IMPORTANCE_NONE;
+ if (channelNotificationsDisabled || !notificationManager.areNotificationsEnabled()) {
+ // If notifications are disabled for the app or channel, fail the purchase request.
+ logd("Purchase request failed because notifications are disabled for the "
+ + (channelNotificationsDisabled ? "channel." : "application."));
+ sendSlicePurchaseAppResponse(intent,
+ SlicePurchaseController.EXTRA_INTENT_NOTIFICATIONS_DISABLED);
+ return;
+ }
String carrier = intent.getStringExtra(SlicePurchaseController.EXTRA_CARRIER);
-
Notification notification =
new Notification.Builder(context, PERFORMANCE_BOOST_NOTIFICATION_CHANNEL_ID)
.setContentTitle(res.getString(
@@ -369,11 +392,12 @@
int capability = intent.getIntExtra(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY,
SlicePurchaseController.PREMIUM_CAPABILITY_INVALID);
- logd((repeat ? "Update" : "Display") + " the performance boost notification for capability "
+ logd((localeChanged ? "Update" : "Display")
+ + " the performance boost notification for capability "
+ TelephonyManager.convertPremiumCapabilityToString(capability));
context.getSystemService(NotificationManager.class).notifyAsUser(
PERFORMANCE_BOOST_NOTIFICATION_TAG, capability, notification, UserHandle.ALL);
- if (!repeat) {
+ if (!localeChanged) {
sIntents.put(capability, intent);
sendSlicePurchaseAppResponse(intent,
SlicePurchaseController.EXTRA_INTENT_NOTIFICATION_SHOWN);
diff --git a/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiverTest.java b/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiverTest.java
index 61847b5..3c8ef6e 100644
--- a/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiverTest.java
+++ b/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiverTest.java
@@ -32,6 +32,7 @@
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
+import android.annotation.NonNull;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
@@ -72,6 +73,7 @@
@Mock PendingIntent mContentIntent1;
@Mock PendingIntent mContentIntent2;
@Mock PendingIntent mNotificationShownIntent;
+ @Mock PendingIntent mNotificationsDisabledIntent;
@Mock Context mContext;
@Mock Resources mResources;
@Mock Configuration mConfiguration;
@@ -90,6 +92,7 @@
doReturn("").when(mResources).getString(anyInt());
doReturn(mNotificationManager).when(mContext)
.getSystemService(eq(NotificationManager.class));
+ doReturn(true).when(mNotificationManager).areNotificationsEnabled();
doReturn(mApplicationInfo).when(mContext).getApplicationInfo();
doReturn(mPackageManager).when(mContext).getPackageManager();
doReturn(mSpiedResources).when(mContext).getResources();
@@ -221,12 +224,10 @@
doReturn(true).when(mPendingIntent).isBroadcast();
doReturn(mPendingIntent).when(mIntent).getParcelableExtra(
anyString(), eq(PendingIntent.class));
- doReturn(TelephonyManager.PHONE_PROCESS_NAME).when(mNotificationShownIntent)
- .getCreatorPackage();
- doReturn(true).when(mNotificationShownIntent).isBroadcast();
- doReturn(mNotificationShownIntent).when(mIntent).getParcelableExtra(
- eq(SlicePurchaseController.EXTRA_INTENT_NOTIFICATION_SHOWN),
- eq(PendingIntent.class));
+ createValidPendingIntent(mNotificationShownIntent,
+ SlicePurchaseController.EXTRA_INTENT_NOTIFICATION_SHOWN);
+ createValidPendingIntent(mNotificationsDisabledIntent,
+ SlicePurchaseController.EXTRA_INTENT_NOTIFICATIONS_DISABLED);
// spy notification intents to prevent PendingIntent issues
doReturn(mContentIntent1).when(mSlicePurchaseBroadcastReceiver).createContentIntent(
@@ -253,6 +254,12 @@
mSlicePurchaseBroadcastReceiver.onReceive(mContext, mIntent);
}
+ private void createValidPendingIntent(@NonNull PendingIntent intent, @NonNull String extra) {
+ doReturn(TelephonyManager.PHONE_PROCESS_NAME).when(intent).getCreatorPackage();
+ doReturn(true).when(intent).isBroadcast();
+ doReturn(intent).when(mIntent).getParcelableExtra(eq(extra), eq(PendingIntent.class));
+ }
+
@Test
public void testNotificationCanceled() {
// send ACTION_NOTIFICATION_CANCELED
@@ -335,4 +342,22 @@
clearInvocations(mConfiguration);
return captor.getValue();
}
+
+ @Test
+ public void testNotificationsDisabled() throws Exception {
+ doReturn(false).when(mNotificationManager).areNotificationsEnabled();
+
+ displayPerformanceBoostNotification();
+
+ // verify notification was not shown
+ verify(mNotificationManager, never()).notifyAsUser(
+ eq(SlicePurchaseBroadcastReceiver.PERFORMANCE_BOOST_NOTIFICATION_TAG),
+ eq(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY),
+ any(),
+ eq(UserHandle.ALL));
+ verify(mNotificationShownIntent, never()).send();
+
+ // verify SlicePurchaseController was notified that notifications are disabled
+ verify(mNotificationsDisabledIntent).send();
+ }
}
diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java
index e1eb36a..25ac3c9 100644
--- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java
+++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java
@@ -419,12 +419,20 @@
mDynSystem.remove();
}
+ private boolean isDsuSlotLocked() {
+ // Slot names ending with ".lock" are a customized installation.
+ // We expect the client app to provide custom UI to enter/exit DSU mode.
+ // We will ignore the ACTION_REBOOT_TO_NORMAL command and will not show
+ // notifications in this case.
+ return mDynSystem.getActiveDsuSlot().endsWith(".lock");
+ }
+
private void executeRebootToNormalCommand() {
if (!isInDynamicSystem()) {
Log.e(TAG, "It's already running in normal system.");
return;
}
- if (mDynSystem.getActiveDsuSlot().endsWith(".lock")) {
+ if (isDsuSlotLocked()) {
Log.e(TAG, "Ignore the reboot intent for a locked DSU slot");
return;
}
@@ -449,13 +457,13 @@
private void executeNotifyIfInUseCommand() {
switch (getStatus()) {
case STATUS_IN_USE:
- if (!mHideNotification) {
+ if (!mHideNotification && !isDsuSlotLocked()) {
startForeground(NOTIFICATION_ID,
buildNotification(STATUS_IN_USE, CAUSE_NOT_SPECIFIED));
}
break;
case STATUS_READY:
- if (!mHideNotification) {
+ if (!mHideNotification && !isDsuSlotLocked()) {
startForeground(NOTIFICATION_ID,
buildNotification(STATUS_READY, CAUSE_NOT_SPECIFIED));
}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt
index a0ff216..ddb92b1 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt
@@ -27,14 +27,17 @@
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
-import androidx.compose.material3.Divider
+import androidx.compose.material3.HorizontalDivider
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
+import com.android.settingslib.development.DevelopmentSettingsEnabler
import com.android.settingslib.spa.framework.compose.rememberDrawablePainter
import com.android.settingslib.spa.framework.theme.SettingsDimension
import com.android.settingslib.spa.widget.ui.SettingsBody
@@ -80,11 +83,22 @@
}
@Composable
- fun FooterAppVersion() {
+ fun FooterAppVersion(showPackageName: Boolean = rememberIsDevelopmentSettingsEnabled()) {
if (packageInfo.versionName == null) return
- Divider()
- Box(modifier = Modifier.padding(SettingsDimension.itemPadding)) {
+ HorizontalDivider()
+ Column(modifier = Modifier.padding(SettingsDimension.itemPadding)) {
SettingsBody(stringResource(R.string.version_text, packageInfo.versionNameBidiWrapped))
+ if (showPackageName) {
+ SettingsBody(packageInfo.packageName)
+ }
+ }
+ }
+
+ @Composable
+ private fun rememberIsDevelopmentSettingsEnabled(): Boolean {
+ val context = LocalContext.current
+ return remember {
+ DevelopmentSettingsEnabler.isDevelopmentSettingsEnabled(context)
}
}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListSwitchItem.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListSwitchItem.kt
index e846687..155905b 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListSwitchItem.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListSwitchItem.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * 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.
@@ -19,26 +19,26 @@
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import com.android.settingslib.spa.framework.theme.SettingsDimension
+import com.android.settingslib.spa.widget.preference.SwitchPreference
import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
-import com.android.settingslib.spa.widget.preference.TwoTargetSwitchPreference
import com.android.settingslib.spaprivileged.model.app.AppRecord
@Composable
fun <T : AppRecord> AppListItemModel<T>.AppListSwitchItem(
- onClick: () -> Unit,
checked: State<Boolean?>,
changeable: State<Boolean>,
onCheckedChange: ((newChecked: Boolean) -> Unit)?,
) {
- TwoTargetSwitchPreference(
+ SwitchPreference(
model = object : SwitchPreferenceModel {
override val title = label
override val summary = this@AppListSwitchItem.summary
+ override val icon = @Composable {
+ AppIcon(record.app, SettingsDimension.appIconItemSize)
+ }
override val checked = checked
override val changeable = changeable
override val onCheckedChange = onCheckedChange
},
- icon = { AppIcon(record.app, SettingsDimension.appIconItemSize) },
- onClick = onClick,
)
}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListTwoTargetSwitchItem.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListTwoTargetSwitchItem.kt
new file mode 100644
index 0000000..99d3840
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListTwoTargetSwitchItem.kt
@@ -0,0 +1,44 @@
+/*
+ * 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.settingslib.spaprivileged.template.app
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.State
+import com.android.settingslib.spa.framework.theme.SettingsDimension
+import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
+import com.android.settingslib.spa.widget.preference.TwoTargetSwitchPreference
+import com.android.settingslib.spaprivileged.model.app.AppRecord
+
+@Composable
+fun <T : AppRecord> AppListItemModel<T>.AppListTwoTargetSwitchItem(
+ onClick: () -> Unit,
+ checked: State<Boolean?>,
+ changeable: State<Boolean>,
+ onCheckedChange: ((newChecked: Boolean) -> Unit)?,
+) {
+ TwoTargetSwitchPreference(
+ model = object : SwitchPreferenceModel {
+ override val title = label
+ override val summary = this@AppListTwoTargetSwitchItem.summary
+ override val checked = checked
+ override val changeable = changeable
+ override val onCheckedChange = onCheckedChange
+ },
+ icon = { AppIcon(record.app, SettingsDimension.appIconItemSize) },
+ onClick = onClick,
+ )
+}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppInfoTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppInfoTest.kt
index bb56c10..6831e56 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppInfoTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppInfoTest.kt
@@ -93,6 +93,7 @@
val packageInfo = PackageInfo().apply {
applicationInfo = APP
versionName = VERSION_NAME
+ packageName = PACKAGE_NAME
}
val appInfoProvider = AppInfoProvider(packageInfo)
@@ -105,9 +106,45 @@
composeTestRule.onNodeWithText("version $VERSION_NAME").assertIsDisplayed()
}
+ @Test
+ fun footerAppVersion_developmentEnabled_packageNameIsDisplayed() {
+ val packageInfo = PackageInfo().apply {
+ applicationInfo = APP
+ versionName = VERSION_NAME
+ packageName = PACKAGE_NAME
+ }
+ val appInfoProvider = AppInfoProvider(packageInfo)
+
+ composeTestRule.setContent {
+ CompositionLocalProvider(LocalContext provides context) {
+ appInfoProvider.FooterAppVersion(true)
+ }
+ }
+ composeTestRule.onNodeWithText(PACKAGE_NAME).assertIsDisplayed()
+ }
+
+
+ @Test
+ fun footerAppVersion_developmentDisabled_packageNameDoesNotExist() {
+ val packageInfo = PackageInfo().apply {
+ applicationInfo = APP
+ versionName = VERSION_NAME
+ packageName = PACKAGE_NAME
+ }
+ val appInfoProvider = AppInfoProvider(packageInfo)
+
+ composeTestRule.setContent {
+ CompositionLocalProvider(LocalContext provides context) {
+ appInfoProvider.FooterAppVersion(false)
+ }
+ }
+ composeTestRule.onNodeWithText(PACKAGE_NAME).assertDoesNotExist()
+ }
+
private companion object {
const val LABEL = "Label"
const val VERSION_NAME = "VersionName"
+ const val PACKAGE_NAME = "package.name"
val APP = object : ApplicationInfo() {
override fun loadLabel(pm: PackageManager) = LABEL
}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListSwitchItemTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListSwitchItemTest.kt
index abdcd3b..2fd1b10 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListSwitchItemTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListSwitchItemTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * 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.
@@ -43,7 +43,6 @@
fun appLabel_displayed() {
composeTestRule.setContent {
ITEM_MODEL.AppListSwitchItem(
- onClick = {},
checked = stateOf(null),
changeable = stateOf(false),
onCheckedChange = {},
@@ -57,7 +56,6 @@
fun summary_displayed() {
composeTestRule.setContent {
ITEM_MODEL.AppListSwitchItem(
- onClick = {},
checked = stateOf(null),
changeable = stateOf(false),
onCheckedChange = {},
@@ -68,27 +66,9 @@
}
@Test
- fun title_onClick() {
- var titleClicked = false
- composeTestRule.setContent {
- ITEM_MODEL.AppListSwitchItem(
- onClick = { titleClicked = true },
- checked = stateOf(false),
- changeable = stateOf(false),
- onCheckedChange = {},
- )
- }
-
- composeTestRule.onNodeWithText(LABEL).performClick()
-
- assertThat(titleClicked).isTrue()
- }
-
- @Test
fun switch_checkIsNull() {
composeTestRule.setContent {
ITEM_MODEL.AppListSwitchItem(
- onClick = {},
checked = stateOf(null),
changeable = stateOf(false),
onCheckedChange = {},
@@ -102,7 +82,6 @@
fun switch_checked() {
composeTestRule.setContent {
ITEM_MODEL.AppListSwitchItem(
- onClick = {},
checked = stateOf(true),
changeable = stateOf(false),
onCheckedChange = {},
@@ -116,7 +95,6 @@
fun switch_notChecked() {
composeTestRule.setContent {
ITEM_MODEL.AppListSwitchItem(
- onClick = {},
checked = stateOf(false),
changeable = stateOf(false),
onCheckedChange = {},
@@ -130,7 +108,6 @@
fun switch_changeable() {
composeTestRule.setContent {
ITEM_MODEL.AppListSwitchItem(
- onClick = {},
checked = stateOf(false),
changeable = stateOf(true),
onCheckedChange = {},
@@ -144,7 +121,6 @@
fun switch_notChangeable() {
composeTestRule.setContent {
ITEM_MODEL.AppListSwitchItem(
- onClick = {},
checked = stateOf(false),
changeable = stateOf(false),
onCheckedChange = {},
@@ -159,7 +135,6 @@
var switchClicked = false
composeTestRule.setContent {
ITEM_MODEL.AppListSwitchItem(
- onClick = {},
checked = stateOf(false),
changeable = stateOf(true),
onCheckedChange = { switchClicked = true },
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTwoTargetSwitchItemTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTwoTargetSwitchItemTest.kt
new file mode 100644
index 0000000..6e7fc8e
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTwoTargetSwitchItemTest.kt
@@ -0,0 +1,189 @@
+/*
+ * 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.settingslib.spaprivileged.template.app
+
+import android.content.pm.ApplicationInfo
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsEnabled
+import androidx.compose.ui.test.assertIsNotEnabled
+import androidx.compose.ui.test.assertIsOff
+import androidx.compose.ui.test.assertIsOn
+import androidx.compose.ui.test.isToggleable
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.performClick
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.framework.compose.stateOf
+import com.android.settingslib.spaprivileged.model.app.AppRecord
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class AppListTwoTargetSwitchItemTest {
+ @get:Rule
+ val composeTestRule = createComposeRule()
+
+ @Test
+ fun appLabel_displayed() {
+ composeTestRule.setContent {
+ ITEM_MODEL.AppListTwoTargetSwitchItem(
+ onClick = {},
+ checked = stateOf(null),
+ changeable = stateOf(false),
+ onCheckedChange = {},
+ )
+ }
+
+ composeTestRule.onNodeWithText(LABEL).assertIsDisplayed()
+ }
+
+ @Test
+ fun summary_displayed() {
+ composeTestRule.setContent {
+ ITEM_MODEL.AppListTwoTargetSwitchItem(
+ onClick = {},
+ checked = stateOf(null),
+ changeable = stateOf(false),
+ onCheckedChange = {},
+ )
+ }
+
+ composeTestRule.onNodeWithText(SUMMARY).assertIsDisplayed()
+ }
+
+ @Test
+ fun title_onClick() {
+ var titleClicked = false
+ composeTestRule.setContent {
+ ITEM_MODEL.AppListTwoTargetSwitchItem(
+ onClick = { titleClicked = true },
+ checked = stateOf(false),
+ changeable = stateOf(false),
+ onCheckedChange = {},
+ )
+ }
+
+ composeTestRule.onNodeWithText(LABEL).performClick()
+
+ assertThat(titleClicked).isTrue()
+ }
+
+ @Test
+ fun switch_checkIsNull() {
+ composeTestRule.setContent {
+ ITEM_MODEL.AppListTwoTargetSwitchItem(
+ onClick = {},
+ checked = stateOf(null),
+ changeable = stateOf(false),
+ onCheckedChange = {},
+ )
+ }
+
+ composeTestRule.onNode(isToggleable()).assertDoesNotExist()
+ }
+
+ @Test
+ fun switch_checked() {
+ composeTestRule.setContent {
+ ITEM_MODEL.AppListTwoTargetSwitchItem(
+ onClick = {},
+ checked = stateOf(true),
+ changeable = stateOf(false),
+ onCheckedChange = {},
+ )
+ }
+
+ composeTestRule.onNode(isToggleable()).assertIsOn()
+ }
+
+ @Test
+ fun switch_notChecked() {
+ composeTestRule.setContent {
+ ITEM_MODEL.AppListTwoTargetSwitchItem(
+ onClick = {},
+ checked = stateOf(false),
+ changeable = stateOf(false),
+ onCheckedChange = {},
+ )
+ }
+
+ composeTestRule.onNode(isToggleable()).assertIsOff()
+ }
+
+ @Test
+ fun switch_changeable() {
+ composeTestRule.setContent {
+ ITEM_MODEL.AppListTwoTargetSwitchItem(
+ onClick = {},
+ checked = stateOf(false),
+ changeable = stateOf(true),
+ onCheckedChange = {},
+ )
+ }
+
+ composeTestRule.onNode(isToggleable()).assertIsEnabled()
+ }
+
+ @Test
+ fun switch_notChangeable() {
+ composeTestRule.setContent {
+ ITEM_MODEL.AppListTwoTargetSwitchItem(
+ onClick = {},
+ checked = stateOf(false),
+ changeable = stateOf(false),
+ onCheckedChange = {},
+ )
+ }
+
+ composeTestRule.onNode(isToggleable()).assertIsNotEnabled()
+ }
+
+ @Test
+ fun switch_onClick() {
+ var switchClicked = false
+ composeTestRule.setContent {
+ ITEM_MODEL.AppListTwoTargetSwitchItem(
+ onClick = {},
+ checked = stateOf(false),
+ changeable = stateOf(true),
+ onCheckedChange = { switchClicked = true },
+ )
+ }
+
+ composeTestRule.onNode(isToggleable()).performClick()
+
+ assertThat(switchClicked).isTrue()
+ }
+
+ private companion object {
+ const val PACKAGE_NAME = "package.name"
+ const val LABEL = "Label"
+ const val SUMMARY = "Summary"
+ val APP = ApplicationInfo().apply {
+ packageName = PACKAGE_NAME
+ }
+ val ITEM_MODEL = AppListItemModel(
+ record = object : AppRecord {
+ override val app = APP
+ },
+ label = LABEL,
+ summary = stateOf(SUMMARY),
+ )
+ }
+}
diff --git a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/Tile.java b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/Tile.java
index 612a928..1a938d6 100644
--- a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/Tile.java
+++ b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/Tile.java
@@ -179,7 +179,8 @@
* Check whether tile has order.
*/
public boolean hasOrder() {
- return mMetaData.containsKey(META_DATA_KEY_ORDER)
+ return mMetaData != null
+ && mMetaData.containsKey(META_DATA_KEY_ORDER)
&& mMetaData.get(META_DATA_KEY_ORDER) instanceof Integer;
}
@@ -204,7 +205,7 @@
CharSequence title = null;
ensureMetadataNotStale(context);
final PackageManager packageManager = context.getPackageManager();
- if (mMetaData.containsKey(META_DATA_PREFERENCE_TITLE)) {
+ if (mMetaData != null && mMetaData.containsKey(META_DATA_PREFERENCE_TITLE)) {
if (mMetaData.containsKey(META_DATA_PREFERENCE_TITLE_URI)) {
// If has as uri to provide dynamic title, skip loading here. UI will later load
// at tile binding time.
diff --git a/packages/SettingsLib/res/values-eu/arrays.xml b/packages/SettingsLib/res/values-eu/arrays.xml
index 3b980c5..17d5e1d 100644
--- a/packages/SettingsLib/res/values-eu/arrays.xml
+++ b/packages/SettingsLib/res/values-eu/arrays.xml
@@ -208,7 +208,7 @@
<item msgid="2675263395797191850">"Animazioa desaktibatuta"</item>
<item msgid="5790132543372767872">"Animazio-eskala: 0,5x"</item>
<item msgid="2529692189302148746">"Animazio-eskala: 1×"</item>
- <item msgid="8072785072237082286">"Animazio-eskala: 1,5x"</item>
+ <item msgid="8072785072237082286">"Animazio-eskala: 1,5×"</item>
<item msgid="3531560925718232560">"Animazio-eskala 2x"</item>
<item msgid="4542853094898215187">"Animazio-eskala: 5x"</item>
<item msgid="5643881346223901195">"Animazio-eskala: 10x"</item>
@@ -217,7 +217,7 @@
<item msgid="3376676813923486384">"Animazioa desaktibatuta"</item>
<item msgid="753422683600269114">"Animazio-eskala: 0,5x"</item>
<item msgid="3695427132155563489">"Animazio-eskala: 1×"</item>
- <item msgid="9032615844198098981">"Animazio-eskala: 1,5x"</item>
+ <item msgid="9032615844198098981">"Animazio-eskala: 1,5×"</item>
<item msgid="8473868962499332073">"Animazio-eskala: 2x"</item>
<item msgid="4403482320438668316">"Animazio-eskala: 5x"</item>
<item msgid="169579387974966641">"Animazio-eskala: 10x"</item>
@@ -226,7 +226,7 @@
<item msgid="6416998593844817378">"Animazioa desaktibatuta"</item>
<item msgid="875345630014338616">"Animazio-eskala: 0,5x"</item>
<item msgid="2753729231187104962">"Animazio-eskala: 1×"</item>
- <item msgid="1368370459723665338">"Animazio-eskala: 1,5x"</item>
+ <item msgid="1368370459723665338">"Animazio-eskala: 1,5×"</item>
<item msgid="5768005350534383389">"Animazio-eskala: 2x"</item>
<item msgid="3728265127284005444">"Animazio-eskala: 5x"</item>
<item msgid="2464080977843960236">"Animazio-eskala: 10x"</item>
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
index 832131c..bff51e3 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
@@ -66,11 +66,14 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
+import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Function;
import java.util.stream.Collectors;
+import java.util.stream.Stream;
/** InfoMediaManager provide interface to get InfoMediaDevice list. */
@RequiresApi(Build.VERSION_CODES.R)
@@ -493,16 +496,6 @@
return info.getName();
}
- boolean shouldDisableMediaOutput(String packageName) {
- if (TextUtils.isEmpty(packageName)) {
- Log.w(TAG, "shouldDisableMediaOutput() package name is null or empty!");
- return true;
- }
-
- // Disable when there is no transferable route
- return getTransferableRoutes(packageName).isEmpty();
- }
-
@TargetApi(Build.VERSION_CODES.R)
boolean shouldEnableVolumeSeekBar(RoutingSessionInfo sessionInfo) {
return sessionInfo.isSystemSession() // System sessions are not remote
@@ -693,25 +686,62 @@
return filteredInfos;
}
+ /**
+ * Returns an ordered list of available devices based on the provided {@code
+ * routeListingPreferenceItems}.
+ *
+ * <p>The result has the following order:
+ *
+ * <ol>
+ * <li>Selected routes.
+ * <li>Not-selected system routes.
+ * <li>Not-selected, non-system, available routes sorted by route listing preference.
+ * </ol>
+ *
+ * @param selectedRoutes List of currently selected routes.
+ * @param availableRoutes List of available routes that match the app's requested route
+ * features.
+ * @param routeListingPreferenceItems Ordered list of {@link RouteListingPreference.Item} to
+ * sort routes with.
+ */
@DoNotInline
static List<MediaRoute2Info> arrangeRouteListByPreference(
- List<MediaRoute2Info> selectedRouteInfos, List<MediaRoute2Info> infolist,
- List<RouteListingPreference.Item> preferenceRouteListing) {
- final List<MediaRoute2Info> sortedInfoList = new ArrayList<>(selectedRouteInfos);
- infolist.removeAll(selectedRouteInfos);
- sortedInfoList.addAll(infolist.stream().filter(
- MediaRoute2Info::isSystemRoute).collect(Collectors.toList()));
- for (RouteListingPreference.Item item : preferenceRouteListing) {
- for (MediaRoute2Info info : infolist) {
- if (item.getRouteId().equals(info.getId())
- && !selectedRouteInfos.contains(info)
- && !info.isSystemRoute()) {
- sortedInfoList.add(info);
- break;
- }
+ List<MediaRoute2Info> selectedRoutes,
+ List<MediaRoute2Info> availableRoutes,
+ List<RouteListingPreference.Item> routeListingPreferenceItems) {
+ Set<String> sortedRouteIds = new LinkedHashSet<>();
+
+ // Add selected routes first.
+ for (MediaRoute2Info selectedRoute : selectedRoutes) {
+ sortedRouteIds.add(selectedRoute.getId());
+ }
+
+ // Add not-yet-added system routes.
+ for (MediaRoute2Info availableRoute : availableRoutes) {
+ if (availableRoute.isSystemRoute()) {
+ sortedRouteIds.add(availableRoute.getId());
}
}
- return sortedInfoList;
+
+ // Create a mapping from id to route to avoid a quadratic search.
+ Map<String, MediaRoute2Info> idToRouteMap =
+ Stream.concat(selectedRoutes.stream(), availableRoutes.stream())
+ .collect(
+ Collectors.toMap(
+ MediaRoute2Info::getId,
+ Function.identity(),
+ (route1, route2) -> route1));
+
+ // Add not-selected routes that match RLP items. All system routes have already been
+ // added at this point.
+ for (RouteListingPreference.Item item : routeListingPreferenceItems) {
+ MediaRoute2Info route = idToRouteMap.get(item.getRouteId());
+ if (route != null) {
+ sortedRouteIds.add(route.getId());
+ }
+ }
+
+ return sortedRouteIds.stream().map(idToRouteMap::get).collect(Collectors.toList());
}
@DoNotInline
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
index 8479df1..fd5ca84 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
@@ -453,13 +453,6 @@
}
/**
- * Returns {@code true} if needed to disable media output, otherwise returns {@code false}.
- */
- public boolean shouldDisableMediaOutput(String packageName) {
- return mInfoMediaManager.shouldDisableMediaOutput(packageName);
- }
-
- /**
* Returns {@code true} if needed to enable volume seekbar, otherwise returns {@code false}.
*/
public boolean shouldEnableVolumeSeekBar(RoutingSessionInfo sessionInfo) {
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
index 45b5de4..866ef9d 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
@@ -1083,51 +1083,4 @@
assertThat(device.getState()).isEqualTo(STATE_SELECTED);
assertThat(mInfoMediaManager.getCurrentConnectedDevice()).isEqualTo(device);
}
-
- @Test
- public void shouldDisableMediaOutput_infosIsEmpty_returnsTrue() {
- mShadowRouter2Manager.setTransferableRoutes(new ArrayList<>());
-
- assertThat(mInfoMediaManager.shouldDisableMediaOutput("test")).isTrue();
- }
-
- @Test
- public void shouldDisableMediaOutput_infosSizeEqual1_returnsFalse() {
- final MediaRoute2Info info = mock(MediaRoute2Info.class);
- final List<MediaRoute2Info> infos = new ArrayList<>();
- infos.add(info);
- mShadowRouter2Manager.setTransferableRoutes(infos);
-
- when(info.getType()).thenReturn(TYPE_REMOTE_SPEAKER);
-
- assertThat(mInfoMediaManager.shouldDisableMediaOutput("test")).isFalse();
- }
-
- @Test
- public void shouldDisableMediaOutput_infosSizeEqual1AndNotCastDevice_returnsFalse() {
- final MediaRoute2Info info = mock(MediaRoute2Info.class);
- final List<MediaRoute2Info> infos = new ArrayList<>();
- infos.add(info);
- mShadowRouter2Manager.setTransferableRoutes(infos);
-
- when(info.getType()).thenReturn(TYPE_BUILTIN_SPEAKER);
-
- assertThat(mInfoMediaManager.shouldDisableMediaOutput("test")).isFalse();
- }
-
-
- @Test
- public void shouldDisableMediaOutput_infosSizeOverThan1_returnsFalse() {
- final MediaRoute2Info info = mock(MediaRoute2Info.class);
- final MediaRoute2Info info2 = mock(MediaRoute2Info.class);
- final List<MediaRoute2Info> infos = new ArrayList<>();
- infos.add(info);
- infos.add(info2);
- mShadowRouter2Manager.setTransferableRoutes(infos);
-
- when(info.getType()).thenReturn(TYPE_REMOTE_SPEAKER);
- when(info2.getType()).thenReturn(TYPE_REMOTE_SPEAKER);
-
- assertThat(mInfoMediaManager.shouldDisableMediaOutput("test")).isFalse();
- }
}
diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml
index 447cd7b..fe51ed5 100644
--- a/packages/SettingsProvider/res/values/defaults.xml
+++ b/packages/SettingsProvider/res/values/defaults.xml
@@ -321,4 +321,7 @@
<!-- Whether vibrate icon is shown in the status bar by default. -->
<integer name="def_statusBarVibrateIconEnabled">0</integer>
+
+ <!-- Whether predictive back animation is enabled by default. -->
+ <bool name="def_enable_back_animation">false</bool>
</resources>
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
index 6a5535d..6b0a906 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
@@ -61,6 +61,7 @@
Settings.System.TTY_MODE,
Settings.System.MASTER_MONO,
Settings.System.MASTER_BALANCE,
+ Settings.System.STAY_AWAKE_ON_FOLD,
Settings.System.SOUND_EFFECTS_ENABLED,
Settings.System.HAPTIC_FEEDBACK_ENABLED,
Settings.System.POWER_SOUNDS_ENABLED, // moved to global
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
index 061bdeb..0dd8569 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
@@ -218,6 +218,7 @@
VALIDATORS.put(System.WIFI_STATIC_DNS1, LENIENT_IP_ADDRESS_VALIDATOR);
VALIDATORS.put(System.WIFI_STATIC_DNS2, LENIENT_IP_ADDRESS_VALIDATOR);
VALIDATORS.put(System.SHOW_BATTERY_PERCENT, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(System.STAY_AWAKE_ON_FOLD, BOOLEAN_VALIDATOR);
VALIDATORS.put(System.NOTIFICATION_LIGHT_PULSE, BOOLEAN_VALIDATOR);
VALIDATORS.put(System.WEAR_ACCESSIBILITY_GESTURE_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(System.WEAR_ACCESSIBILITY_GESTURE_ENABLED_DURING_OOBE, BOOLEAN_VALIDATOR);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 7a60507..d1d745f 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -246,6 +246,7 @@
public static final String RESULT_ROWS_DELETED = "result_rows_deleted";
public static final String RESULT_SETTINGS_LIST = "result_settings_list";
+ public static final String SETTINGS_PROVIDER_JOBS_NS = "SettingsProviderJobsNamespace";
// Used for scheduling jobs to make a copy for the settings files
public static final int WRITE_FALLBACK_SETTINGS_FILES_JOB_ID = 1;
public static final long ONE_DAY_INTERVAL_MILLIS = 24 * 60 * 60 * 1000L;
@@ -2785,12 +2786,13 @@
*/
public void scheduleWriteFallbackFilesJob() {
final Context context = getContext();
- final JobScheduler jobScheduler =
+ JobScheduler jobScheduler =
(JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
if (jobScheduler == null) {
// Might happen: SettingsProvider is created before JobSchedulerService in system server
return;
}
+ jobScheduler = jobScheduler.forNamespace(SETTINGS_PROVIDER_JOBS_NS);
// Check if the job is already scheduled. If so, skip scheduling another one
if (jobScheduler.getPendingJob(WRITE_FALLBACK_SETTINGS_FILES_JOB_ID) != null) {
return;
@@ -3773,7 +3775,7 @@
}
private final class UpgradeController {
- private static final int SETTINGS_VERSION = 220;
+ private static final int SETTINGS_VERSION = 221;
private final int mUserId;
@@ -5867,6 +5869,21 @@
currentVersion = 220;
}
+ if (currentVersion == 220) {
+ final SettingsState globalSettings = getGlobalSettingsLocked();
+ final Setting enableBackAnimation =
+ globalSettings.getSettingLocked(Global.ENABLE_BACK_ANIMATION);
+ if (enableBackAnimation.isNull()) {
+ final boolean defEnableBackAnimation =
+ getContext()
+ .getResources()
+ .getBoolean(R.bool.def_enable_back_animation);
+ initGlobalSettingsDefaultValLocked(
+ Settings.Global.ENABLE_BACK_ANIMATION, defEnableBackAnimation);
+ }
+ currentVersion = 221;
+ }
+
// vXXX: Add new settings above this point.
if (currentVersion != newVersion) {
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/WriteFallbackSettingsFilesJobService.java b/packages/SettingsProvider/src/com/android/providers/settings/WriteFallbackSettingsFilesJobService.java
index 66aa7ba..91e8bf8 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/WriteFallbackSettingsFilesJobService.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/WriteFallbackSettingsFilesJobService.java
@@ -16,6 +16,7 @@
package com.android.providers.settings;
+import static com.android.providers.settings.SettingsProvider.SETTINGS_PROVIDER_JOBS_NS;
import static com.android.providers.settings.SettingsProvider.TABLE_CONFIG;
import static com.android.providers.settings.SettingsProvider.TABLE_GLOBAL;
import static com.android.providers.settings.SettingsProvider.TABLE_SECURE;
@@ -35,7 +36,8 @@
public class WriteFallbackSettingsFilesJobService extends JobService {
@Override
public boolean onStartJob(final JobParameters params) {
- if (params.getJobId() != WRITE_FALLBACK_SETTINGS_FILES_JOB_ID) {
+ if (!SETTINGS_PROVIDER_JOBS_NS.equals(params.getJobNamespace())
+ || params.getJobId() != WRITE_FALLBACK_SETTINGS_FILES_JOB_ID) {
return false;
}
final List<String> settingsFiles = new ArrayList<>();
diff --git a/packages/Shell/res/values-gl/strings.xml b/packages/Shell/res/values-gl/strings.xml
index 912dc85..9d4f7de 100644
--- a/packages/Shell/res/values-gl/strings.xml
+++ b/packages/Shell/res/values-gl/strings.xml
@@ -20,7 +20,7 @@
<string name="bugreport_notification_channel" msgid="2574150205913861141">"Informes de erros"</string>
<string name="bugreport_in_progress_title" msgid="4311705936714972757">"Estase xerando o informe de erros <xliff:g id="ID">#%d</xliff:g>"</string>
<string name="bugreport_finished_title" msgid="4429132808670114081">"Rexistrouse o informe de erros <xliff:g id="ID">#%d</xliff:g>"</string>
- <string name="bugreport_updating_title" msgid="4423539949559634214">"Engadindo detalles ao informe de erro"</string>
+ <string name="bugreport_updating_title" msgid="4423539949559634214">"Engadindo detalles ao informe de erros"</string>
<string name="bugreport_updating_wait" msgid="3322151947853929470">"Agarda..."</string>
<string name="bugreport_finished_text" product="watch" msgid="1223616207145252689">"O informe de erros aparecerá no teléfono en breve"</string>
<string name="bugreport_finished_text" product="tv" msgid="5758325479058638893">"Selecciona para compartir o teu informe de erros"</string>
@@ -32,7 +32,7 @@
<string name="bugreport_confirm_dont_repeat" msgid="6179945398364357318">"Non mostrar outra vez"</string>
<string name="bugreport_storage_title" msgid="5332488144740527109">"Informes de erros"</string>
<string name="bugreport_unreadable_text" msgid="586517851044535486">"Non se puido ler o ficheiro de informe de erros"</string>
- <string name="bugreport_add_details_to_zip_failed" msgid="1302931926486712371">"Non se puideron engadir os detalles do informe de erro ao ficheiro zip"</string>
+ <string name="bugreport_add_details_to_zip_failed" msgid="1302931926486712371">"Non se puideron engadir os detalles do informe de erros ao ficheiro zip"</string>
<string name="bugreport_unnamed" msgid="2800582406842092709">"sen nome"</string>
<string name="bugreport_info_action" msgid="2158204228510576227">"Detalles"</string>
<string name="bugreport_screenshot_action" msgid="8677781721940614995">"Captura de pantalla"</string>
diff --git a/packages/SoundPicker/Android.bp b/packages/SoundPicker/Android.bp
index 1ac9bbb..235e672 100644
--- a/packages/SoundPicker/Android.bp
+++ b/packages/SoundPicker/Android.bp
@@ -34,4 +34,13 @@
platform_apis: true,
certificate: "media",
privileged: true,
+
+ optimize: {
+ enabled: true,
+ optimize: true,
+ shrink: true,
+ shrink_resources: true,
+ obfuscate: false,
+ proguard_compatibility: false,
+ },
}
diff --git a/packages/SoundPicker/res/layout/fragment_sound_picker.xml b/packages/SoundPicker/res/layout/fragment_ringtone_picker.xml
similarity index 100%
rename from packages/SoundPicker/res/layout/fragment_sound_picker.xml
rename to packages/SoundPicker/res/layout/fragment_ringtone_picker.xml
diff --git a/packages/SoundPicker/res/layout/fragment_vibration_picker.xml b/packages/SoundPicker/res/layout/fragment_vibration_picker.xml
deleted file mode 100644
index 34d95aa2..0000000
--- a/packages/SoundPicker/res/layout/fragment_vibration_picker.xml
+++ /dev/null
@@ -1,32 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- 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.
--->
-<androidx.constraintlayout.widget.ConstraintLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto"
- android:layout_width="match_parent"
- android:layout_height="match_parent">
-
- <TextView
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:text="@string/empty_list"
- android:textColor="?android:attr/colorAccent"
- android:textAppearance="?android:attr/textAppearanceMedium"
- android:gravity="center"
- />
-
-</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/packages/SoundPicker/src/com/android/soundpicker/BasePickerFragment.java b/packages/SoundPicker/src/com/android/soundpicker/BasePickerFragment.java
new file mode 100644
index 0000000..4fc2a86
--- /dev/null
+++ b/packages/SoundPicker/src/com/android/soundpicker/BasePickerFragment.java
@@ -0,0 +1,312 @@
+/*
+ * 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.soundpicker;
+
+import android.app.Activity;
+import android.content.ContentProvider;
+import android.content.Intent;
+import android.graphics.drawable.Drawable;
+import android.media.RingtoneManager;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.MediaStore;
+import android.util.Log;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+import androidx.fragment.app.Fragment;
+import androidx.lifecycle.ViewModelProvider;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import dagger.hilt.android.AndroidEntryPoint;
+
+import java.util.Objects;
+
+/**
+ * Base class for generic picker fragments.
+ *
+ * <p>This fragment displays a recycler view that is populated by a {@link RingtoneListViewAdapter}
+ * with data provided by a {@link RingtoneListHandler}. Each item can be selected on click,
+ * which also triggers a ringtone preview performed by the shared {@link RingtonePickerViewModel}.
+ * The ringtone preview uses the selection state of all picker fragments (e.g. sound selected by
+ * one fragment and vibration selected by another).
+ */
+@AndroidEntryPoint(Fragment.class)
+public abstract class BasePickerFragment extends Hilt_BasePickerFragment implements
+ RingtoneListViewAdapter.Callbacks {
+
+ private static final String TAG = "BasePickerFragment";
+ private static final String COLUMN_LABEL = MediaStore.Audio.Media.TITLE;
+ private boolean mIsManagedProfile;
+ private Drawable mWorkIconDrawable;
+
+ protected RingtoneListViewAdapter mRingtoneListViewAdapter;
+ protected RecyclerView mRecyclerView;
+ protected RingtonePickerViewModel.Config mPickerConfig;
+ protected RingtonePickerViewModel mRingtonePickerViewModel;
+ protected RingtoneListHandler.Config mRingtoneListConfig;
+ protected RingtoneListHandler mRingtoneListHandler;
+
+ public BasePickerFragment() {
+ super(R.layout.fragment_ringtone_picker);
+ }
+
+ @Override
+ public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+ mRingtonePickerViewModel = new ViewModelProvider(requireActivity()).get(
+ RingtonePickerViewModel.class);
+ mRingtoneListHandler = getRingtoneListHandler();
+ mRecyclerView = view.requireViewById(R.id.recycler_view);
+
+ mPickerConfig = mRingtonePickerViewModel.getPickerConfig();
+ mRingtoneListConfig = mRingtoneListHandler.getRingtoneListConfig();
+
+ mIsManagedProfile = UserManager.get(requireActivity()).isManagedProfile(
+ mPickerConfig.userId);
+
+ mRingtoneListViewAdapter = createRingtoneListViewAdapter();
+ mRecyclerView.setHasFixedSize(true);
+ mRecyclerView.setAdapter(mRingtoneListViewAdapter);
+ mRecyclerView.setLayoutManager(new LinearLayoutManager(requireActivity()));
+ setSelectedItem(mRingtoneListHandler.getSelectedItemPosition());
+ prepareRecyclerView(mRecyclerView);
+ }
+
+ @Override
+ public boolean isWorkRingtone(int position) {
+ if (!mIsManagedProfile) {
+ return false;
+ }
+
+ /*
+ * Display the work icon if the ringtone belongs to a work profile. We
+ * can tell that a ringtone belongs to a work profile if the picker user
+ * is a managed profile, the ringtone Uri is in external storage, and
+ * either the uri has no user id or has the id of the picker user
+ */
+ Uri currentUri = mRingtoneListHandler.getRingtoneUri(position);
+ int uriUserId = ContentProvider.getUserIdFromUri(currentUri,
+ mPickerConfig.userId);
+ Uri uriWithoutUserId = ContentProvider.getUriWithoutUserId(currentUri);
+
+ return uriUserId == mPickerConfig.userId
+ && uriWithoutUserId.toString().startsWith(
+ MediaStore.Audio.Media.EXTERNAL_CONTENT_URI.toString());
+ }
+
+ @Override
+ public Drawable getWorkIconDrawable() {
+ if (mWorkIconDrawable == null) {
+ mWorkIconDrawable = requireActivity().getPackageManager()
+ .getUserBadgeForDensityNoBackground(
+ UserHandle.of(mPickerConfig.userId), /* density= */ -1);
+ }
+
+ return mWorkIconDrawable;
+ }
+
+ @Override
+ public void onRingtoneSelected(int position) {
+ setSelectedItem(position);
+
+ // In the buttonless (watch-only) version, preemptively set our result since
+ // we won't have another chance to do so before the activity closes.
+ if (!mPickerConfig.showOkCancelButtons) {
+ setSuccessResultWithSelectedRingtone();
+ }
+
+ // Play clip
+ mRingtonePickerViewModel.playRingtone();
+ }
+
+ @Override
+ public void onAddRingtoneSelected() {
+ addRingtoneAsync();
+ }
+
+ /**
+ * Sets up the list by adding fixed items to the top and bottom, if required. And sets the
+ * selected item in the list.
+ * @param recyclerView The recyclerview that contains the list of displayed items.
+ */
+ protected void prepareRecyclerView(@NonNull RecyclerView recyclerView) {
+ // Reset the static item count, as this method can be called multiple times
+ mRingtoneListHandler.resetFixedItems();
+
+ if (mRingtoneListConfig.hasDefaultItem) {
+ int defaultItemPos = addDefaultRingtoneItem();
+
+ if (getSelectedItem() < 0
+ && RingtoneManager.isDefault(mRingtoneListConfig.initialSelectedUri)) {
+ setSelectedItem(defaultItemPos);
+ }
+ }
+
+ if (mRingtoneListConfig.hasSilentItem) {
+ int silentItemPos = addSilentItem();
+
+ // The 'Silent' item should use a null Uri
+ if (getSelectedItem() < 0
+ && mRingtoneListConfig.initialSelectedUri == null) {
+ setSelectedItem(silentItemPos);
+ }
+ }
+
+ if (getSelectedItem() < 0) {
+ setSelectedItem(mRingtoneListHandler.getRingtonePosition(
+ mRingtoneListConfig.initialSelectedUri));
+ }
+
+ // In the buttonless (watch-only) version, preemptively set our result since we won't
+ // have another chance to do so before the activity closes.
+ if (!mPickerConfig.showOkCancelButtons) {
+ setSuccessResultWithSelectedRingtone();
+ }
+
+ addNewRingtoneItem();
+
+ // Enable context menu in ringtone items
+ registerForContextMenu(recyclerView);
+ }
+
+ /**
+ * Returns the fragment's sound/vibration list handler.
+ * @return The ringtone list handler.
+ */
+ protected abstract RingtoneListHandler getRingtoneListHandler();
+
+ /**
+ * Starts the process to add a new ringtone to the list of ringtones asynchronously.
+ * Currently, only works for adding sound files.
+ */
+ protected abstract void addRingtoneAsync();
+
+ /**
+ * Adds an item to the end of the list that can be used to add new ringtones to the list.
+ * Currently, only works for adding sound files.
+ */
+ protected abstract void addNewRingtoneItem();
+
+ protected int getSelectedItem() {
+ return mRingtoneListHandler.getSelectedItemPosition();
+ }
+
+ /**
+ * Returns the selected URI to the caller activity.
+ */
+ protected void setSuccessResultWithSelectedRingtone() {
+ requireActivity().setResult(Activity.RESULT_OK,
+ new Intent().putExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI,
+ mRingtonePickerViewModel.getSelectedRingtoneUri()));
+ }
+
+ /**
+ * Creates a ringtone recyclerview adapter using the ringtone manager cursor.
+ * @return The created RingtoneListViewAdapter.
+ */
+ protected RingtoneListViewAdapter createRingtoneListViewAdapter() {
+ LocalizedCursor cursor = new LocalizedCursor(
+ mRingtoneListHandler.getRingtoneCursor(), getResources(), COLUMN_LABEL);
+ return new RingtoneListViewAdapter(cursor, /* RingtoneListViewAdapterCallbacks= */ this);
+ }
+
+ /**
+ * Sets the selected item in the list and scroll to the position in the recyclerview.
+ * @param pos the position of the selected item in the list.
+ */
+ protected void setSelectedItem(int pos) {
+ Objects.requireNonNull(mRingtoneListViewAdapter);
+ mRingtoneListHandler.setSelectedItemPosition(pos);
+ mRingtoneListViewAdapter.setSelectedItem(pos);
+ mRingtoneListHandler.setSelectedItemId(mRingtoneListViewAdapter.getItemId(pos));
+ mRecyclerView.scrollToPosition(pos);
+ }
+
+ /**
+ * Adds a fixed item to the fixed items list . A fixed item is one that is not from
+ * the RingtoneManager.
+ *
+ * @param textResId The resource ID of the text for the item.
+ * @return The index of the inserted fixed item in the adapter.
+ */
+ protected int addFixedItem(int textResId) {
+ return mRingtoneListViewAdapter.addTitleForFixedItem(textResId);
+ }
+
+ /**
+ * Re-query RingtoneManager for the most recent set of installed ringtones. May move the
+ * selected item position to match the new position of the chosen ringtone.
+ * <p>
+ * This should only need to happen after adding or removing a ringtone.
+ */
+ protected void requeryForAdapter() {
+ mRingtonePickerViewModel.reinit();
+ // Refresh and set a new cursor, and closing the old one.
+ mRingtoneListViewAdapter = createRingtoneListViewAdapter();
+ mRecyclerView.setAdapter(mRingtoneListViewAdapter);
+ prepareRecyclerView(mRecyclerView);
+
+ // Update selected item location.
+ for (int i = 0; i < mRingtoneListViewAdapter.getItemCount(); i++) {
+ if (mRingtoneListViewAdapter.getItemId(i)
+ == mRingtoneListHandler.getSelectedItemId()) {
+ setSelectedItem(i);
+ return;
+ }
+ }
+
+ // If selected item is still unknown, then set it to the default item, if available.
+ // If it's not available, then attempt to set it to the silent item in the list.
+ int selectedPosition = mRingtoneListHandler.getDefaultItemPosition();
+
+ if (selectedPosition < 0) {
+ selectedPosition = mRingtoneListHandler.getSilentItemPosition();
+ }
+
+ setSelectedItem(selectedPosition);
+ }
+
+ private int addDefaultRingtoneItem() {
+ int defaultItemPosInAdapter = addFixedItem(
+ RingtonePickerViewModel.getDefaultRingtoneItemTextByType(
+ mPickerConfig.ringtoneType));
+ int defaultItemPosInListHandler = mRingtoneListHandler.addDefaultItem();
+
+ if (defaultItemPosInAdapter != defaultItemPosInListHandler) {
+ Log.wtf(TAG, "Default item position in adapter and list handler must match.");
+ return RingtoneListHandler.ITEM_POSITION_UNKNOWN;
+ }
+
+ return defaultItemPosInListHandler;
+ }
+
+ private int addSilentItem() {
+ int silentItemPosInAdapter = addFixedItem(com.android.internal.R.string.ringtone_silent);
+ int silentItemPosInListHandler = mRingtoneListHandler.addSilentItem();
+
+ if (silentItemPosInAdapter != silentItemPosInListHandler) {
+ Log.wtf(TAG, "Silent item position in adapter and list handler must match.");
+ return RingtoneListHandler.ITEM_POSITION_UNKNOWN;
+ }
+
+ return silentItemPosInListHandler;
+ }
+}
diff --git a/packages/SoundPicker/src/com/android/soundpicker/RingtoneFactory.java b/packages/SoundPicker/src/com/android/soundpicker/RingtoneFactory.java
index 0a8a73b..cb41eab 100644
--- a/packages/SoundPicker/src/com/android/soundpicker/RingtoneFactory.java
+++ b/packages/SoundPicker/src/com/android/soundpicker/RingtoneFactory.java
@@ -17,8 +17,8 @@
package com.android.soundpicker;
import android.content.Context;
+import android.media.AudioAttributes;
import android.media.Ringtone;
-import android.media.RingtoneManager;
import android.net.Uri;
import dagger.hilt.android.qualifiers.ApplicationContext;
@@ -40,12 +40,23 @@
}
/**
- * Returns a {@link Ringtone} based on the provided URI.
+ * Returns a {@link Ringtone} built from the provided URI and audio attributes flags.
*
- * @param uri The URI used to get the {@link Ringtone}
- * @return a {@link Ringtone}
+ * @param uri The URI used to build the {@link Ringtone}.
+ * @param audioAttributesFlags A combination of audio attribute flags that affect the volume
+ * and settings when playing the ringtone.
+ * @return the built {@link Ringtone}.
*/
- public Ringtone create(Uri uri) {
- return RingtoneManager.getRingtone(mApplicationContext, uri);
+ public Ringtone create(Uri uri, int audioAttributesFlags) {
+ AudioAttributes audioAttributes = new AudioAttributes.Builder()
+ .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
+ .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
+ .setFlags(audioAttributesFlags)
+ .build();
+ // TODO: We are currently only using MEDIA_SOUND for enabledMedia. This will change once we
+ // start playing sound and/or vibration.
+ return new Ringtone.Builder(mApplicationContext, Ringtone.MEDIA_SOUND, audioAttributes)
+ .setUri(uri)
+ .build();
}
}
diff --git a/packages/SoundPicker/src/com/android/soundpicker/RingtoneListHandler.java b/packages/SoundPicker/src/com/android/soundpicker/RingtoneListHandler.java
new file mode 100644
index 0000000..bb38e0e
--- /dev/null
+++ b/packages/SoundPicker/src/com/android/soundpicker/RingtoneListHandler.java
@@ -0,0 +1,222 @@
+/*
+ * 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.soundpicker;
+
+import static java.util.Objects.requireNonNull;
+
+import android.annotation.Nullable;
+import android.database.Cursor;
+import android.media.RingtoneManager;
+import android.net.Uri;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import javax.inject.Inject;
+
+/**
+ * Handles ringtone list state and actions. This includes keeping track of the selected item,
+ * ringtone manager cursor and added items to the list.
+ */
+public class RingtoneListHandler {
+
+ // TODO: We're using an empty URI instead of null, because null URIs still produce a sound,
+ // while empty ones don't (Potentially this might be due to empty URIs being perceived as
+ // malformed ones). We will switch to using the official silent URIs (SOUND_OFF, VIBRATION_OFF)
+ // once they become available.
+ static final Uri SILENT_URI = Uri.EMPTY;
+ static final int ITEM_POSITION_UNKNOWN = -1;
+
+ private static final String TAG = "RingtoneListHandler";
+
+ /** The position in the list of the 'Silent' item. */
+ private int mSilentItemPosition = ITEM_POSITION_UNKNOWN;
+ /** The position in the list of the 'Default' item. */
+ private int mDefaultItemPosition = ITEM_POSITION_UNKNOWN;
+ /** The number of fixed items in the list. */
+ private int mFixedItemCount;
+ /**
+ * Stable ID for the ringtone that is currently selected (may be -1 if no ringtone is selected).
+ */
+ private long mSelectedItemId = -1;
+ private int mSelectedItemPosition = ITEM_POSITION_UNKNOWN;
+
+ private RingtoneManager mRingtoneManager;
+ private Config mRingtoneListConfig;
+ private Cursor mRingtoneCursor;
+
+ /**
+ * Holds immutable info on the ringtone list that is displayed.
+ */
+ static final class Config {
+ /**
+ * Whether this list has the 'Default' item.
+ */
+ public final boolean hasDefaultItem;
+ /**
+ * The Uri to play when the 'Default' item is clicked.
+ */
+ public final Uri uriForDefaultItem;
+ /**
+ * Whether this list has the 'Silent' item.
+ */
+ public final boolean hasSilentItem;
+ /**
+ * The initially selected uri in the list.
+ */
+ public final Uri initialSelectedUri;
+
+ Config(boolean hasDefaultItem, Uri uriForDefaultItem, boolean hasSilentItem,
+ Uri initialSelectedUri) {
+ this.hasDefaultItem = hasDefaultItem;
+ this.uriForDefaultItem = uriForDefaultItem;
+ this.hasSilentItem = hasSilentItem;
+ this.initialSelectedUri = initialSelectedUri;
+ }
+ }
+
+ @Inject
+ RingtoneListHandler() {
+ }
+
+ void init(@NonNull Config ringtoneListConfig,
+ @NonNull RingtoneManager ringtoneManager, @NonNull Cursor ringtoneCursor) {
+ mRingtoneManager = requireNonNull(ringtoneManager);
+ mRingtoneListConfig = requireNonNull(ringtoneListConfig);
+ mRingtoneCursor = requireNonNull(ringtoneCursor);
+ }
+
+ Config getRingtoneListConfig() {
+ return mRingtoneListConfig;
+ }
+
+ Cursor getRingtoneCursor() {
+ requireInitCalled();
+ return mRingtoneCursor;
+ }
+
+ Uri getRingtoneUri(int position) {
+ if (position < 0) {
+ Log.w(TAG, "Selected item position is unknown.");
+ // When the selected item is ITEM_POSITION_UNKNOWN, it is not the case we expected.
+ // We return SILENT_URI for this case.
+ return SILENT_URI;
+ } else if (position == mDefaultItemPosition) {
+ // Use the default Uri that they originally gave us.
+ return mRingtoneListConfig.uriForDefaultItem;
+ } else if (position == mSilentItemPosition) {
+ // Use SILENT_URI for the 'Silent' item.
+ return SILENT_URI;
+ } else {
+ requireInitCalled();
+ return mRingtoneManager.getRingtoneUri(mapListPositionToRingtonePosition(position));
+ }
+ }
+
+ int getRingtonePosition(Uri uri) {
+ requireInitCalled();
+ return mapRingtonePositionToListPosition(mRingtoneManager.getRingtonePosition(uri));
+ }
+
+ void resetFixedItems() {
+ mFixedItemCount = 0;
+ mDefaultItemPosition = ITEM_POSITION_UNKNOWN;
+ mSilentItemPosition = ITEM_POSITION_UNKNOWN;
+ }
+
+ int addDefaultItem() {
+ if (mDefaultItemPosition < 0) {
+ mDefaultItemPosition = addFixedItem();
+ }
+ return mDefaultItemPosition;
+ }
+
+ int getDefaultItemPosition() {
+ return mDefaultItemPosition;
+ }
+
+ int addSilentItem() {
+ if (mSilentItemPosition < 0) {
+ mSilentItemPosition = addFixedItem();
+ }
+ return mSilentItemPosition;
+ }
+
+ public int getSilentItemPosition() {
+ return mSilentItemPosition;
+ }
+
+ int getSelectedItemPosition() {
+ return mSelectedItemPosition;
+ }
+
+ void setSelectedItemPosition(int selectedItemPosition) {
+ mSelectedItemPosition = selectedItemPosition;
+ }
+
+ void setSelectedItemId(long selectedItemId) {
+ mSelectedItemId = selectedItemId;
+ }
+
+ long getSelectedItemId() {
+ return mSelectedItemId;
+ }
+
+ @Nullable
+ Uri getSelectedRingtoneUri() {
+ return getRingtoneUri(mSelectedItemPosition);
+ }
+
+ /**
+ * Maps the item position in the list, to its equivalent position in the RingtoneManager.
+ *
+ * @param itemPosition the position of item in the list.
+ * @return position of the item in the RingtoneManager.
+ */
+ private int mapListPositionToRingtonePosition(int itemPosition) {
+ // If the manager position is less than add items, then return that.
+ if (itemPosition < mFixedItemCount) return itemPosition;
+
+ return itemPosition - mFixedItemCount;
+ }
+
+ /**
+ * Maps the item position in the RingtoneManager, to its equivalent position in the list.
+ *
+ * @param itemPosition the position of the item in the RingtoneManager.
+ * @return position of the item in the list.
+ */
+ private int mapRingtonePositionToListPosition(int itemPosition) {
+ // If the manager position is less than add items, then return that.
+ if (itemPosition < 0) return itemPosition;
+
+ return itemPosition + mFixedItemCount;
+ }
+
+ /**
+ * Increments the number of added fixed items and returns the index of the newest added item.
+ * @return index of the newest added fixed item.
+ */
+ private int addFixedItem() {
+ return mFixedItemCount++;
+ }
+
+ private void requireInitCalled() {
+ requireNonNull(mRingtoneManager);
+ requireNonNull(mRingtoneCursor);
+ }
+}
diff --git a/packages/SoundPicker/src/com/android/soundpicker/RingtoneAdapter.java b/packages/SoundPicker/src/com/android/soundpicker/RingtoneListViewAdapter.java
similarity index 82%
rename from packages/SoundPicker/src/com/android/soundpicker/RingtoneAdapter.java
rename to packages/SoundPicker/src/com/android/soundpicker/RingtoneListViewAdapter.java
index 1f33aa2..4ca8943 100644
--- a/packages/SoundPicker/src/com/android/soundpicker/RingtoneAdapter.java
+++ b/packages/SoundPicker/src/com/android/soundpicker/RingtoneListViewAdapter.java
@@ -51,36 +51,30 @@
* but not selected and its position will never change.
* </ul>
*/
-final class RingtoneAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
+final class RingtoneListViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private static final int VIEW_TYPE_FIXED_ITEM = 0;
private static final int VIEW_TYPE_DYNAMIC_ITEM = 1;
private static final int VIEW_TYPE_ADD_RINGTONE_ITEM = 2;
private final Cursor mCursor;
private final List<Integer> mFixedItemTitles;
- private final WorkRingtoneProvider mWorkRingtoneProvider;
- private final RingtoneSelectionListener mRingtoneSelectionListener;
+ private final Callbacks mCallbacks;
private final int mRowIDColumn;
private int mSelectedItem = -1;
@StringRes private Integer mAddRingtoneItemTitle;
- /** Listener for ringtone selections. */
- interface RingtoneSelectionListener {
+ /** Provides callbacks for the adapter. */
+ interface Callbacks {
void onRingtoneSelected(int position);
void onAddRingtoneSelected();
- }
- /** Provides a mean to detect work ringtones. */
- interface WorkRingtoneProvider {
boolean isWorkRingtone(int position);
-
Drawable getWorkIconDrawable();
}
- RingtoneAdapter(Cursor cursor, RingtoneSelectionListener ringtoneSelectionListener,
- WorkRingtoneProvider workRingtoneProvider) {
+ RingtoneListViewAdapter(Cursor cursor,
+ Callbacks callbacks) {
mCursor = cursor;
- mRingtoneSelectionListener = ringtoneSelectionListener;
- mWorkRingtoneProvider = workRingtoneProvider;
+ mCallbacks = callbacks;
mFixedItemTitles = new ArrayList<>();
mRowIDColumn = mCursor != null ? mCursor.getColumnIndex("_id") : -1;
setHasStableIds(true);
@@ -92,9 +86,15 @@
notifyItemChanged(mSelectedItem);
}
- void addTitleForFixedItem(@StringRes int textResId) {
+ /**
+ * Adds title to the fixed items list and returns the index of the newest added item.
+ * @param textResId the title to add to the fixed items list.
+ * @return The index of the newest added item in the fixed items list.
+ */
+ int addTitleForFixedItem(@StringRes int textResId) {
mFixedItemTitles.add(textResId);
notifyItemInserted(mFixedItemTitles.size() - 1);
+ return mFixedItemTitles.size() - 1;
}
void addTitleForAddRingtoneItem(@StringRes int textResId) {
@@ -112,18 +112,19 @@
com.android.internal.R.layout.select_dialog_singlechoice_material, parent,
false);
- return new FixedItemViewHolder(fixedItemView, mRingtoneSelectionListener);
+ return new FixedItemViewHolder(fixedItemView, mCallbacks);
}
if (viewType == VIEW_TYPE_ADD_RINGTONE_ITEM) {
View addRingtoneItemView = inflater.inflate(R.layout.add_new_sound_item, parent, false);
- return new AddRingtoneItemViewHolder(addRingtoneItemView, mRingtoneSelectionListener);
+ return new AddRingtoneItemViewHolder(addRingtoneItemView,
+ mCallbacks);
}
View view = inflater.inflate(R.layout.radio_with_work_badge, parent, false);
- return new DynamicItemViewHolder(view, mRingtoneSelectionListener);
+ return new DynamicItemViewHolder(view, mCallbacks);
}
@Override
@@ -152,9 +153,9 @@
throw new IllegalStateException("Could not move cursor to position: " + pos);
}
- Drawable workIcon = (mWorkRingtoneProvider != null)
- && mWorkRingtoneProvider.isWorkRingtone(position)
- ? mWorkRingtoneProvider.getWorkIconDrawable() : null;
+ Drawable workIcon = (mCallbacks != null)
+ && mCallbacks.isWorkRingtone(position)
+ ? mCallbacks.getWorkIconDrawable() : null;
viewHolder.onBind(mCursor.getString(RingtoneManager.TITLE_COLUMN_INDEX),
/* isChecked= */ position == mSelectedItem, workIcon);
@@ -207,18 +208,15 @@
private final CheckedTextView mTitleTextView;
private final ImageView mWorkIcon;
- DynamicItemViewHolder(View itemView, RingtoneSelectionListener listener) {
+ DynamicItemViewHolder(View itemView, Callbacks listener) {
super(itemView);
- mTitleTextView = itemView.findViewById(R.id.checked_text_view);
- mWorkIcon = itemView.findViewById(R.id.work_icon);
+ mTitleTextView = itemView.requireViewById(R.id.checked_text_view);
+ mWorkIcon = itemView.requireViewById(R.id.work_icon);
itemView.setOnClickListener(v -> listener.onRingtoneSelected(this.getLayoutPosition()));
}
void onBind(String title, boolean isChecked, Drawable workIcon) {
- Objects.requireNonNull(mTitleTextView);
- Objects.requireNonNull(mWorkIcon);
-
mTitleTextView.setText(title);
mTitleTextView.setChecked(isChecked);
@@ -234,7 +232,7 @@
private static class FixedItemViewHolder extends RecyclerView.ViewHolder {
private final CheckedTextView mTitleTextView;
- FixedItemViewHolder(View itemView, RingtoneSelectionListener listener) {
+ FixedItemViewHolder(View itemView, Callbacks listener) {
super(itemView);
mTitleTextView = (CheckedTextView) itemView;
@@ -252,16 +250,14 @@
private static class AddRingtoneItemViewHolder extends RecyclerView.ViewHolder {
private final TextView mTitleTextView;
- AddRingtoneItemViewHolder(View itemView, RingtoneSelectionListener listener) {
+ AddRingtoneItemViewHolder(View itemView, Callbacks listener) {
super(itemView);
- mTitleTextView = itemView.findViewById(R.id.add_new_sound_text);
+ mTitleTextView = itemView.requireViewById(R.id.add_new_sound_text);
itemView.setOnClickListener(v -> listener.onAddRingtoneSelected());
}
void onBind(@StringRes int title) {
- Objects.requireNonNull(mTitleTextView);
-
mTitleTextView.setText(title);
}
}
diff --git a/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerActivity.java b/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerActivity.java
index 4d7cf1c..90a14f9 100644
--- a/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerActivity.java
+++ b/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerActivity.java
@@ -40,12 +40,15 @@
public final class RingtonePickerActivity extends Hilt_RingtonePickerActivity {
private static final String TAG = "RingtonePickerActivity";
- // TODO: Use the extra key from RingtoneManager once it's added.
+ // TODO: Use the extra keys from RingtoneManager once they're added.
private static final String EXTRA_RINGTONE_PICKER_CATEGORY = "EXTRA_RINGTONE_PICKER_CATEGORY";
+ private static final String EXTRA_VIBRATION_SHOW_DEFAULT = "EXTRA_VIBRATION_SHOW_DEFAULT";
+ private static final String EXTRA_VIBRATION_DEFAULT_URI = "EXTRA_VIBRATION_DEFAULT_URI";
+ private static final String EXTRA_VIBRATION_SHOW_SILENT = "EXTRA_VIBRATION_SHOW_SILENT";
+ private static final String EXTRA_VIBRATION_EXISTING_URI = "EXTRA_VIBRATION_EXISTING_URI";
private static final boolean RINGTONE_PICKER_CATEGORY_FEATURE_ENABLED = false;
private RingtonePickerViewModel mRingtonePickerViewModel;
-
private int mAttributesFlags;
@Override
@@ -65,19 +68,6 @@
int ringtoneType = intent.getIntExtra(RingtoneManager.EXTRA_RINGTONE_TYPE,
RingtonePickerViewModel.RINGTONE_TYPE_UNKNOWN);
- // Get whether to show the 'Default' item, and the URI to play when the default is clicked
- boolean hasDefaultItem =
- intent.getBooleanExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true);
- // The Uri to play when the 'Default' item is clicked.
- Uri uriForDefaultItem =
- intent.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_DEFAULT_URI);
- if (uriForDefaultItem == null) {
- uriForDefaultItem = RingtonePickerViewModel.getDefaultItemUriByType(ringtoneType);
- }
-
- // Get whether this list has the 'Silent' item.
- boolean hasSilentItem =
- intent.getBooleanExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, true);
// AudioAttributes flags
mAttributesFlags |= intent.getIntExtra(
RingtoneManager.EXTRA_RINGTONE_AUDIO_ATTRIBUTES_FLAGS,
@@ -85,10 +75,6 @@
boolean showOkCancelButtons = getResources().getBoolean(R.bool.config_showOkCancelButtons);
- // Get the URI whose list item should have a checkmark
- Uri existingUri = intent
- .getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI);
-
String title = intent.getStringExtra(RingtoneManager.EXTRA_RINGTONE_TITLE);
if (title == null) {
title = getString(RingtonePickerViewModel.getTitleByType(ringtoneType));
@@ -97,12 +83,15 @@
RingtonePickerViewModel.PickerType pickerType = mapCategoryToPickerType(
ringtonePickerCategory);
- mRingtonePickerViewModel.init(new RingtonePickerViewModel.PickerConfig(title, pickerUserId,
- ringtoneType, hasDefaultItem, uriForDefaultItem, hasSilentItem,
- mAttributesFlags, existingUri, showOkCancelButtons, pickerType));
+ RingtoneListHandler.Config soundListConfig = getSoundListConfig(pickerType, intent,
+ ringtoneType);
+ RingtoneListHandler.Config vibrationListConfig = getVibrationListConfig(pickerType, intent);
- // The volume keys will control the stream that we are choosing a ringtone for
- setVolumeControlStream(mRingtonePickerViewModel.getRingtoneStreamType());
+ RingtonePickerViewModel.Config pickerConfig =
+ new RingtonePickerViewModel.Config(title, pickerUserId, ringtoneType,
+ showOkCancelButtons, mAttributesFlags, pickerType);
+
+ mRingtonePickerViewModel.init(pickerConfig, soundListConfig, vibrationListConfig);
if (savedInstanceState == null) {
TabbedDialogFragment dialogFragment = new TabbedDialogFragment();
@@ -115,8 +104,73 @@
ft.addToBackStack(null);
dialogFragment.show(ft, TabbedDialogFragment.TAG);
}
+
+ // The volume keys will control the stream that we are choosing a ringtone for
+ setVolumeControlStream(mRingtonePickerViewModel.getRingtoneStreamType());
}
+ private RingtoneListHandler.Config getSoundListConfig(
+ RingtonePickerViewModel.PickerType pickerType, Intent intent, int ringtoneType) {
+ if (pickerType != RingtonePickerViewModel.PickerType.SOUND_PICKER
+ && pickerType != RingtonePickerViewModel.PickerType.RINGTONE_PICKER) {
+ // This ringtone picker does not require a sound picker.
+ return null;
+ }
+
+ // Get whether to show the 'Default' sound item, and the URI to play when it's clicked
+ boolean hasDefaultSoundItem =
+ intent.getBooleanExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true);
+
+ // The Uri to play when the 'Default' sound item is clicked.
+ Uri uriForDefaultSoundItem =
+ intent.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_DEFAULT_URI);
+ if (uriForDefaultSoundItem == null) {
+ uriForDefaultSoundItem = RingtonePickerViewModel.getDefaultItemUriByType(ringtoneType);
+ }
+
+ // Get whether this list has the 'Silent' sound item.
+ boolean hasSilentSoundItem =
+ intent.getBooleanExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, true);
+
+ // AudioAttributes flags
+ mAttributesFlags |= intent.getIntExtra(
+ RingtoneManager.EXTRA_RINGTONE_AUDIO_ATTRIBUTES_FLAGS,
+ 0 /*defaultValue == no flags*/);
+
+ // Get the sound URI whose list item should have a checkmark
+ Uri existingSoundUri = intent
+ .getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI);
+
+ return new RingtoneListHandler.Config(hasDefaultSoundItem,
+ uriForDefaultSoundItem, hasSilentSoundItem, existingSoundUri);
+ }
+
+ private RingtoneListHandler.Config getVibrationListConfig(
+ RingtonePickerViewModel.PickerType pickerType, Intent intent) {
+ if (pickerType != RingtonePickerViewModel.PickerType.VIBRATION_PICKER
+ && pickerType != RingtonePickerViewModel.PickerType.RINGTONE_PICKER) {
+ // This ringtone picker does not require a vibration picker.
+ return null;
+ }
+
+ // Get whether to show the 'Default' vibration item, and the URI to play when it's clicked
+ boolean hasDefaultVibrationItem =
+ intent.getBooleanExtra(EXTRA_VIBRATION_SHOW_DEFAULT, false);
+
+ // The Uri to play when the 'Default' vibration item is clicked.
+ Uri uriForDefaultVibrationItem = intent.getParcelableExtra(EXTRA_VIBRATION_DEFAULT_URI);
+
+ // Get whether this list has the 'Silent' vibration item.
+ boolean hasSilentVibrationItem =
+ intent.getBooleanExtra(EXTRA_VIBRATION_SHOW_SILENT, true);
+
+ // Get the vibration URI whose list item should have a checkmark
+ Uri existingVibrationUri = intent.getParcelableExtra(EXTRA_VIBRATION_EXISTING_URI);
+
+ return new RingtoneListHandler.Config(
+ hasDefaultVibrationItem, uriForDefaultVibrationItem, hasSilentVibrationItem,
+ existingVibrationUri);
+ }
@Override
public void onDestroy() {
diff --git a/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerViewModel.java b/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerViewModel.java
index 914f16a..2c09711 100644
--- a/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerViewModel.java
+++ b/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerViewModel.java
@@ -20,8 +20,6 @@
import android.annotation.Nullable;
import android.annotation.StringRes;
-import android.database.Cursor;
-import android.media.AudioAttributes;
import android.media.Ringtone;
import android.media.RingtoneManager;
import android.net.Uri;
@@ -45,7 +43,8 @@
import javax.inject.Inject;
/**
- * View model for {@link RingtonePickerActivity}.
+ * A view model which holds immutable info about the picker state and means to retrieve and play
+ * currently selected ringtones.
*/
@HiltViewModel
public final class RingtonePickerViewModel extends ViewModel {
@@ -58,39 +57,25 @@
*/
@VisibleForTesting
static Ringtone sPlayingRingtone;
+
private static final String TAG = "RingtonePickerViewModel";
- private static final String RINGTONE_MANAGER_NULL_MESSAGE =
- "RingtoneManager must not be null. Did you forget to call "
- + "RingtonePickerViewModel#initRingtoneManager?";
- private static final int ITEM_POSITION_UNKNOWN = -1;
private final RingtoneManagerFactory mRingtoneManagerFactory;
private final RingtoneFactory mRingtoneFactory;
+ private final RingtoneListHandler mSoundListHandler;
+ private final RingtoneListHandler mVibrationListHandler;
private final ListeningExecutorService mListeningExecutorService;
- /** The position in the list of the 'Silent' item. */
- private int mSilentItemPosition = ITEM_POSITION_UNKNOWN;
- /** The position in the list of the ringtone to sample. */
- private int mSampleItemPosition = ITEM_POSITION_UNKNOWN;
- /** The position in the list of the 'Default' item. */
- private int mDefaultItemPosition = ITEM_POSITION_UNKNOWN;
- /** The number of fixed items in the list. */
- private int mFixedItemCount;
- private ListenableFuture<Uri> mAddCustomRingtoneFuture;
private RingtoneManager mRingtoneManager;
/**
- * Stable ID for the ringtone that is currently selected (may be -1 if no ringtone is selected).
- */
- private long mSelectedItemId = -1;
- private int mSelectedItemPosition = ITEM_POSITION_UNKNOWN;
-
- /**
* The ringtone that's currently playing.
*/
private Ringtone mCurrentRingtone;
- private PickerConfig mPickerConfig;
+ private Config mPickerConfig;
+
+ private ListenableFuture<Uri> mAddCustomRingtoneFuture;
public enum PickerType {
RINGTONE_PICKER,
@@ -101,7 +86,7 @@
/**
* Holds immutable info on the picker that should be displayed.
*/
- static final class PickerConfig {
+ static final class Config {
public final String title;
/**
* Id of the user to which the ringtone picker should list the ringtones.
@@ -112,45 +97,23 @@
*/
public final int ringtoneType;
/**
- * Whether this list has the 'Default' item.
- */
- public final boolean hasDefaultItem;
- /**
- * The Uri to play when the 'Default' item is clicked.
- */
- public final Uri uriForDefaultItem;
- /**
- * Whether this list has the 'Silent' item.
- */
- public final boolean hasSilentItem;
- /**
* AudioAttributes flags.
*/
public final int audioAttributesFlags;
/**
- * The Uri to place a checkmark next to.
- */
- public final Uri existingUri;
- /**
* In the buttonless (watch-only) version we don't show the OK/Cancel buttons.
*/
public final boolean showOkCancelButtons;
public final PickerType mPickerType;
- PickerConfig(String title, int userId, int ringtoneType,
- boolean hasDefaultItem, Uri uriForDefaultItem, boolean hasSilentItem,
- int audioAttributesFlags, Uri existingUri, boolean showOkCancelButtons,
- PickerType pickerType) {
+ Config(String title, int userId, int ringtoneType, boolean showOkCancelButtons,
+ int audioAttributesFlags, PickerType pickerType) {
this.title = title;
this.userId = userId;
this.ringtoneType = ringtoneType;
- this.hasDefaultItem = hasDefaultItem;
- this.uriForDefaultItem = uriForDefaultItem;
- this.hasSilentItem = hasSilentItem;
- this.audioAttributesFlags = audioAttributesFlags;
- this.existingUri = existingUri;
this.showOkCancelButtons = showOkCancelButtons;
+ this.audioAttributesFlags = audioAttributesFlags;
this.mPickerType = pickerType;
}
}
@@ -158,17 +121,14 @@
@Inject
RingtonePickerViewModel(RingtoneManagerFactory ringtoneManagerFactory,
RingtoneFactory ringtoneFactory,
- ListeningExecutorServiceFactory listeningExecutorServiceFactory) {
+ ListeningExecutorServiceFactory listeningExecutorServiceFactory,
+ RingtoneListHandler soundListHandler,
+ RingtoneListHandler vibrationListHandler) {
mRingtoneManagerFactory = ringtoneManagerFactory;
mRingtoneFactory = ringtoneFactory;
mListeningExecutorService = listeningExecutorServiceFactory.createSingleThreadExecutor();
- }
-
- @NonNull
- PickerConfig getPickerConfig() {
- return requireNonNull(mPickerConfig,
- "PickerConfig was never set. Did you forget to call "
- + "RingtonePickerViewModel#init?");
+ mSoundListHandler = soundListHandler;
+ mVibrationListHandler = vibrationListHandler;
}
@StringRes
@@ -218,129 +178,68 @@
}
}
- void init(@NonNull PickerConfig pickerConfig) {
+ void init(@NonNull Config pickerConfig,
+ RingtoneListHandler.Config soundListConfig,
+ RingtoneListHandler.Config vibrationListConfig) {
mRingtoneManager = mRingtoneManagerFactory.create();
mPickerConfig = pickerConfig;
- if (pickerConfig.ringtoneType != RINGTONE_TYPE_UNKNOWN) {
- mRingtoneManager.setType(pickerConfig.ringtoneType);
+ if (mPickerConfig.ringtoneType != RINGTONE_TYPE_UNKNOWN) {
+ mRingtoneManager.setType(mPickerConfig.ringtoneType);
+ }
+ if (soundListConfig != null) {
+ mSoundListHandler.init(soundListConfig, mRingtoneManager,
+ mRingtoneManager.getCursor());
+ }
+ if (vibrationListConfig != null) {
+ // TODO: Switch to the vibration cursor, once the API is made available.
+ mVibrationListHandler.init(vibrationListConfig, mRingtoneManager,
+ mRingtoneManager.getCursor());
}
}
/**
- * Adds an audio file to the list of ringtones asynchronously.
- * Any previous async tasks are canceled before start the new one.
+ * Re-initializes the view model which is required after updating any of the picker lists.
+ * This could happen when adding a custom ringtone.
+ */
+ void reinit() {
+ init(mPickerConfig, mSoundListHandler.getRingtoneListConfig(),
+ mVibrationListHandler.getRingtoneListConfig());
+ }
+
+ @NonNull
+ Config getPickerConfig() {
+ requireInitCalled();
+ return mPickerConfig;
+ }
+
+ @NonNull
+ RingtoneListHandler getSoundListHandler() {
+ return mSoundListHandler;
+ }
+
+ @NonNull
+ RingtoneListHandler getVibrationListHandler() {
+ return mVibrationListHandler;
+ }
+
+ /**
+ * Combined the currently selected sound and vibration URIs and returns a unified URI. If the
+ * picker does not show either sound or vibration, that portion of the URI will be null.
*
- * @param uri Uri of the file to be added as ringtone. Must be a media file.
- * @param type The type of the ringtone to be added.
- * @param callback The callback to invoke when the task is completed.
- * @param executor The executor to run the callback on when the task completes.
+ * Currently only the sound URI is returned, since we don't have the API to retrieve vibrations
+ * yet.
+ * @return Combined sound and vibration URI.
*/
- void addRingtoneAsync(Uri uri, int type, FutureCallback<Uri> callback, Executor executor) {
- // Cancel any currently running add ringtone tasks before starting a new one
- cancelPendingAsyncTasks();
- mAddCustomRingtoneFuture = mListeningExecutorService.submit(() -> addRingtone(uri, type));
- Futures.addCallback(mAddCustomRingtoneFuture, callback, executor);
- }
-
- /**
- * Cancels all pending async tasks.
- */
- void cancelPendingAsyncTasks() {
- if (mAddCustomRingtoneFuture != null && !mAddCustomRingtoneFuture.isDone()) {
- mAddCustomRingtoneFuture.cancel(/* mayInterruptIfRunning= */ true);
- }
+ Uri getSelectedRingtoneUri() {
+ // TODO: Combine sound and vibration URIs before returning.
+ return mSoundListHandler.getSelectedRingtoneUri();
}
int getRingtoneStreamType() {
- requireNonNull(mRingtoneManager, RINGTONE_MANAGER_NULL_MESSAGE);
+ requireInitCalled();
return mRingtoneManager.inferStreamType();
}
- Cursor getRingtoneCursor() {
- requireNonNull(mRingtoneManager, RINGTONE_MANAGER_NULL_MESSAGE);
- return mRingtoneManager.getCursor();
- }
-
- Uri getRingtoneUri(int position) {
- requireNonNull(mRingtoneManager, RINGTONE_MANAGER_NULL_MESSAGE);
- return mRingtoneManager.getRingtoneUri(mapListPositionToRingtonePosition(position));
- }
-
- int getRingtonePosition(Uri uri) {
- requireNonNull(mRingtoneManager, RINGTONE_MANAGER_NULL_MESSAGE);
- return mapRingtonePositionToListPosition(mRingtoneManager.getRingtonePosition(uri));
- }
-
- /**
- * Maps the item position in the list, to its equivalent position in the RingtoneManager.
- *
- * @param itemPosition the position of item in the list.
- * @return position of the item in the RingtoneManager.
- */
- private int mapListPositionToRingtonePosition(int itemPosition) {
- // If the manager position is -1 (for not found), then return that.
- if (itemPosition < 0) return itemPosition;
-
- return itemPosition - mFixedItemCount;
- }
-
- /**
- * Maps the item position in the RingtoneManager, to its equivalent position in the list.
- *
- * @param itemPosition the position of the item in the RingtoneManager.
- * @return position of the item in the list.
- */
- private int mapRingtonePositionToListPosition(int itemPosition) {
- // If the manager position is -1 (for not found), then return that.
- if (itemPosition < 0) return itemPosition;
-
- return itemPosition + mFixedItemCount;
- }
-
- void resetFixedItemCount() {
- mFixedItemCount = 0;
- }
-
- int incrementAndGetFixedItemCount() {
- return mFixedItemCount++;
- }
-
- void setDefaultItemPosition(int defaultItemPosition) {
- mDefaultItemPosition = defaultItemPosition;
- }
-
- int getSilentItemPosition() {
- return mSilentItemPosition;
- }
-
- void setSilentItemPosition(int silentItemPosition) {
- mSilentItemPosition = silentItemPosition;
- }
-
- public int getSampleItemPosition() {
- return mSampleItemPosition;
- }
-
- public void setSampleItemPosition(int sampleItemPosition) {
- mSampleItemPosition = sampleItemPosition;
- }
-
- public int getSelectedItemPosition() {
- return mSelectedItemPosition;
- }
-
- public void setSelectedItemPosition(int selectedItemPosition) {
- mSelectedItemPosition = selectedItemPosition;
- }
-
- public void setSelectedItemId(long selectedItemId) {
- mSelectedItemId = selectedItemId;
- }
-
- public long getSelectedItemId() {
- return mSelectedItemId;
- }
-
void onPause(boolean isChangingConfigurations) {
if (!isChangingConfigurations) {
stopAnyPlayingRingtone();
@@ -355,51 +254,50 @@
}
}
- @Nullable
- Uri getCurrentlySelectedRingtoneUri() {
- if (mSelectedItemPosition == ITEM_POSITION_UNKNOWN) {
- // When the selected item is POS_UNKNOWN, it is not the case we expected.
- // We return null for this case.
- return null;
- } else if (mSelectedItemPosition == mDefaultItemPosition) {
- // Use the default Uri that they originally gave us.
- return mPickerConfig.uriForDefaultItem;
- } else if (mSelectedItemPosition == mSilentItemPosition) {
- // Use a null Uri for the 'Silent' item.
- return null;
- } else {
- return getRingtoneUri(mSelectedItemPosition);
+ /**
+ * Plays a ringtone which is created using the currently selected sound and vibration URIs. If
+ * this is a sound or vibration only picker, then the other portion of the URI will be empty
+ * and should not affect the played ringtone.
+ *
+ * Currently, we only use the sound URI to create the ringtone, since we still don't have the
+ * API to retrieve the available vibrations list.
+ */
+ void playRingtone() {
+ requireInitCalled();
+ stopAnyPlayingRingtone();
+
+ mCurrentRingtone = mRingtoneFactory.create(getSelectedRingtoneUri(),
+ mPickerConfig.audioAttributesFlags);
+
+ if (mCurrentRingtone != null) {
+ mCurrentRingtone.play();
}
}
- void playRingtone(int position, Uri uriForDefaultItem, int attributesFlags) {
- requireNonNull(mRingtoneManager, RINGTONE_MANAGER_NULL_MESSAGE);
- stopAnyPlayingRingtone();
- if (mSampleItemPosition == mSilentItemPosition) {
- return;
+ /**
+ * Cancels all pending async tasks.
+ */
+ void cancelPendingAsyncTasks() {
+ if (mAddCustomRingtoneFuture != null && !mAddCustomRingtoneFuture.isDone()) {
+ mAddCustomRingtoneFuture.cancel(/* mayInterruptIfRunning= */ true);
}
+ }
- if (mSampleItemPosition == mDefaultItemPosition) {
- mCurrentRingtone = mRingtoneFactory.create(uriForDefaultItem);
- /*
- * Stream type of mDefaultRingtone is not set explicitly here. It should be set in
- * accordance with mRingtoneManager of this Activity.
- */
- if (mCurrentRingtone != null) {
- mCurrentRingtone.setStreamType(mRingtoneManager.inferStreamType());
- }
- } else {
- mCurrentRingtone = mRingtoneManager.getRingtone(
- mapListPositionToRingtonePosition(position));
- }
-
- if (mCurrentRingtone != null) {
- if (attributesFlags != 0) {
- mCurrentRingtone.setAudioAttributes(new AudioAttributes.Builder(
- mCurrentRingtone.getAudioAttributes()).setFlags(attributesFlags).build());
- }
- mCurrentRingtone.play();
- }
+ /**
+ * Adds an audio file to the list of ringtones asynchronously.
+ * Any previous async tasks are canceled before start the new one.
+ *
+ * @param uri Uri of the file to be added as ringtone. Must be a media file.
+ * @param type The type of the ringtone to be added.
+ * @param callback The callback to invoke when the task is completed.
+ * @param executor The executor to run the callback on when the task completes.
+ */
+ void addSoundRingtoneAsync(Uri uri, int type, FutureCallback<Uri> callback, Executor executor) {
+ // Cancel any currently running add ringtone tasks before starting a new one
+ cancelPendingAsyncTasks();
+ mAddCustomRingtoneFuture = mListeningExecutorService.submit(
+ () -> addRingtone(uri, type));
+ Futures.addCallback(mAddCustomRingtoneFuture, callback, executor);
}
/**
@@ -412,7 +310,7 @@
*/
@Nullable
private Uri addRingtone(Uri uri, int type) throws IOException {
- requireNonNull(mRingtoneManager, RINGTONE_MANAGER_NULL_MESSAGE);
+ requireInitCalled();
return mRingtoneManager.addCustomExternalRingtone(uri, type);
}
@@ -434,4 +332,9 @@
}
mCurrentRingtone = null;
}
+
+ private void requireInitCalled() {
+ requireNonNull(mRingtoneManager);
+ requireNonNull(mPickerConfig);
+ }
}
diff --git a/packages/SoundPicker/src/com/android/soundpicker/SoundPickerFragment.java b/packages/SoundPicker/src/com/android/soundpicker/SoundPickerFragment.java
index e07d109..a37191f 100644
--- a/packages/SoundPicker/src/com/android/soundpicker/SoundPickerFragment.java
+++ b/packages/SoundPicker/src/com/android/soundpicker/SoundPickerFragment.java
@@ -17,16 +17,10 @@
package com.android.soundpicker;
import android.app.Activity;
-import android.content.ContentProvider;
import android.content.Intent;
-import android.graphics.drawable.Drawable;
-import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.provider.MediaStore;
import android.util.Log;
import android.view.View;
import android.widget.Toast;
@@ -35,74 +29,20 @@
import androidx.activity.result.ActivityResultCallback;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
-import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
-import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
-import androidx.recyclerview.widget.LinearLayoutManager;
-import androidx.recyclerview.widget.RecyclerView;
import com.google.common.util.concurrent.FutureCallback;
-import dagger.hilt.android.AndroidEntryPoint;
-
import org.jetbrains.annotations.NotNull;
-import java.util.Objects;
-
/**
- * A fragment that will display a picker used to select sound or silent. It also includes the
+ * A fragment that displays a picker used to select sound or silent. It also includes the
* ability to add custom sounds.
*/
-@AndroidEntryPoint(Fragment.class)
-public class SoundPickerFragment extends Hilt_SoundPickerFragment {
+public class SoundPickerFragment extends BasePickerFragment {
private static final String TAG = "SoundPickerFragment";
- private static final String COLUMN_LABEL = MediaStore.Audio.Media.TITLE;
- private static final int POS_UNKNOWN = -1;
-
- private RingtonePickerViewModel.PickerConfig mPickerConfig;
- private boolean mIsManagedProfile;
- private RingtonePickerViewModel mRingtonePickerViewModel;
- private RingtoneAdapter mRingtoneAdapter;
- private RecyclerView mSoundRecyclerView;
-
- private final RingtoneAdapter.WorkRingtoneProvider mWorkRingtoneProvider =
- new RingtoneAdapter.WorkRingtoneProvider() {
- private Drawable mWorkIconDrawable;
- @Override
- public boolean isWorkRingtone(int position) {
- if (mIsManagedProfile) {
- /*
- * Display the w ork icon if the ringtone belongs to a work profile. We
- * can tell that a ringtone belongs to a work profile if the picker user
- * is a managed profile, the ringtone Uri is in external storage, and
- * either the uri has no user id or has the id of the picker user
- */
- Uri currentUri = mRingtonePickerViewModel.getRingtoneUri(position);
- int uriUserId = ContentProvider.getUserIdFromUri(currentUri,
- mPickerConfig.userId);
- Uri uriWithoutUserId = ContentProvider.getUriWithoutUserId(currentUri);
-
- return uriUserId == mPickerConfig.userId
- && uriWithoutUserId.toString().startsWith(
- MediaStore.Audio.Media.EXTERNAL_CONTENT_URI.toString());
- }
-
- return false;
- }
-
- @Override
- public Drawable getWorkIconDrawable() {
- if (mWorkIconDrawable == null) {
- mWorkIconDrawable = requireActivity().getPackageManager()
- .getUserBadgeForDensityNoBackground(
- UserHandle.of(mPickerConfig.userId), /* density= */ -1);
- }
-
- return mWorkIconDrawable;
- }
- };
private final FutureCallback<Uri> mAddCustomRingtoneCallback = new FutureCallback<>() {
@Override
@@ -127,7 +67,7 @@
if (result.getResultCode() == Activity.RESULT_OK) {
// There are no request codes
Intent data = result.getData();
- mRingtonePickerViewModel.addRingtoneAsync(data.getData(),
+ mRingtonePickerViewModel.addSoundRingtoneAsync(data.getData(),
mPickerConfig.ringtoneType,
mAddCustomRingtoneCallback,
// Causes the callback to be executed on the main thread.
@@ -137,130 +77,34 @@
}
});
- private final RingtoneAdapter.RingtoneSelectionListener mRingtoneSelectionListener =
- new RingtoneAdapter.RingtoneSelectionListener() {
- @Override
- public void onRingtoneSelected(int position) {
- SoundPickerFragment.this.setSelectedItem(position);
-
- // In the buttonless (watch-only) version, preemptively set our result since
- // we won't have another chance to do so before the activity closes.
- if (!mPickerConfig.showOkCancelButtons) {
- setSuccessResultWithRingtone(
- mRingtonePickerViewModel.getCurrentlySelectedRingtoneUri());
- }
-
- // Play clip
- playRingtone(position);
- }
-
- @Override
- public void onAddRingtoneSelected() {
- // The "Add new ringtone" item was clicked. Start a file picker intent to
- // select only audio files (MIME type "audio/*")
- final Intent chooseFile = getMediaFilePickerIntent();
- mActivityResultLauncher.launch(chooseFile);
- }
- };
-
- public SoundPickerFragment() {
- super(R.layout.fragment_sound_picker);
+ @Override
+ public void onViewCreated(@NotNull View view, Bundle savedInstanceState) {
+ mRingtonePickerViewModel = new ViewModelProvider(requireActivity()).get(
+ RingtonePickerViewModel.class);
+ super.onViewCreated(view, savedInstanceState);
}
@Override
- public void onViewCreated(@NotNull View view, Bundle savedInstanceState) {
- super.onViewCreated(view, savedInstanceState);
- mRingtonePickerViewModel = new ViewModelProvider(requireActivity()).get(
- RingtonePickerViewModel.class);
- mSoundRecyclerView = view.findViewById(R.id.recycler_view);
- Objects.requireNonNull(mSoundRecyclerView);
-
- mPickerConfig = mRingtonePickerViewModel.getPickerConfig();
- mIsManagedProfile = UserManager.get(requireActivity()).isManagedProfile(
- mPickerConfig.userId);
-
- mRingtoneAdapter = createRingtoneAdapter();
- mSoundRecyclerView.setHasFixedSize(true);
- mSoundRecyclerView.setAdapter(mRingtoneAdapter);
- mSoundRecyclerView.setLayoutManager(new LinearLayoutManager(requireActivity()));
- setSelectedItem(mRingtonePickerViewModel.getSelectedItemPosition());
- prepareRecyclerView(mSoundRecyclerView);
+ protected RingtoneListHandler getRingtoneListHandler() {
+ return mRingtonePickerViewModel.getSoundListHandler();
}
- private void prepareRecyclerView(@NonNull RecyclerView recyclerView) {
- // Reset the static item count, as this method can be called multiple times
- mRingtonePickerViewModel.resetFixedItemCount();
+ @Override
+ protected void addRingtoneAsync() {
+ // The "Add new ringtone" item was clicked. Start a file picker intent to
+ // select only audio files (MIME type "audio/*")
+ final Intent chooseFile = getMediaFilePickerIntent();
+ mActivityResultLauncher.launch(chooseFile);
+ }
- if (mPickerConfig.hasDefaultItem) {
- int defaultItemPos = addDefaultRingtoneItem();
-
- if (getSelectedItem() == POS_UNKNOWN
- && RingtoneManager.isDefault(mPickerConfig.existingUri)) {
- setSelectedItem(defaultItemPos);
- }
- }
-
- if (mPickerConfig.hasSilentItem) {
- int silentItemPos = addSilentItem();
-
- // The 'Silent' item should use a null Uri
- if (getSelectedItem() == POS_UNKNOWN && mPickerConfig.existingUri == null) {
- setSelectedItem(silentItemPos);
- }
- }
-
- if (getSelectedItem() == POS_UNKNOWN) {
- setSelectedItem(
- mRingtonePickerViewModel.getRingtonePosition(mPickerConfig.existingUri));
- }
-
- // In the buttonless (watch-only) version, preemptively set our result since we won't
- // have another chance to do so before the activity closes.
- if (!mPickerConfig.showOkCancelButtons) {
- setSuccessResultWithRingtone(
- mRingtonePickerViewModel.getCurrentlySelectedRingtoneUri());
- }
+ @Override
+ protected void addNewRingtoneItem() {
// If external storage is available, add a button to install sounds from storage.
if (resolvesMediaFilePicker()
&& Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
- addNewSoundItem();
+ mRingtoneListViewAdapter.addTitleForAddRingtoneItem(
+ RingtonePickerViewModel.getAddNewItemTextByType(mPickerConfig.ringtoneType));
}
-
- // Enable context menu in ringtone items
- registerForContextMenu(recyclerView);
- }
-
- /**
- * Re-query RingtoneManager for the most recent set of installed ringtones. May move the
- * selected item position to match the new position of the chosen sound.
- * <p>
- * This should only need to happen after adding or removing a ringtone.
- */
- private void requeryForAdapter() {
- // Refresh and set a new cursor, closing the old one.
- mRingtonePickerViewModel.init(mPickerConfig);
- mRingtoneAdapter = createRingtoneAdapter();
- mSoundRecyclerView.setAdapter(mRingtoneAdapter);
- prepareRecyclerView(mSoundRecyclerView);
-
- // Update selected item location.
- int selectedPosition = POS_UNKNOWN;
- for (int i = 0; i < mRingtoneAdapter.getItemCount(); i++) {
- if (mRingtoneAdapter.getItemId(i) == mRingtonePickerViewModel.getSelectedItemId()) {
- selectedPosition = i;
- break;
- }
- }
- if (mPickerConfig.hasSilentItem && selectedPosition == POS_UNKNOWN) {
- selectedPosition = mRingtonePickerViewModel.getSilentItemPosition();
- }
- setSelectedItem(selectedPosition);
- }
-
- private void playRingtone(int position) {
- mRingtonePickerViewModel.setSampleItemPosition(position);
- mRingtonePickerViewModel.playRingtone(mRingtonePickerViewModel.getSampleItemPosition(),
- mPickerConfig.uriForDefaultItem, mPickerConfig.audioAttributesFlags);
}
private boolean resolvesMediaFilePicker() {
@@ -275,58 +119,4 @@
new String[]{"audio/*", "application/ogg"});
return chooseFile;
}
-
- private void setSuccessResultWithRingtone(Uri ringtoneUri) {
- requireActivity().setResult(Activity.RESULT_OK,
- new Intent().putExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI, ringtoneUri));
- }
-
- private int getSelectedItem() {
- return mRingtonePickerViewModel.getSelectedItemPosition();
- }
-
- private void setSelectedItem(int pos) {
- Objects.requireNonNull(mRingtoneAdapter);
- mRingtonePickerViewModel.setSelectedItemPosition(pos);
- mRingtoneAdapter.setSelectedItem(pos);
- mRingtonePickerViewModel.setSelectedItemId(mRingtoneAdapter.getItemId(pos));
- mSoundRecyclerView.scrollToPosition(pos);
- }
-
- /**
- * Adds a fixed item to the fixed items list . A fixed item is one that is not from
- * the RingtoneManager.
- *
- * @param textResId The resource ID of the text for the item.
- * @return The position of the inserted item.
- */
- private int addFixedItem(int textResId) {
- mRingtoneAdapter.addTitleForFixedItem(textResId);
- return mRingtonePickerViewModel.incrementAndGetFixedItemCount();
- }
-
- private int addDefaultRingtoneItem() {
- int defaultRingtoneItemPos = addFixedItem(
- RingtonePickerViewModel.getDefaultRingtoneItemTextByType(
- mPickerConfig.ringtoneType));
- mRingtonePickerViewModel.setDefaultItemPosition(defaultRingtoneItemPos);
- return defaultRingtoneItemPos;
- }
-
- private int addSilentItem() {
- int silentItemPos = addFixedItem(com.android.internal.R.string.ringtone_silent);
- mRingtonePickerViewModel.setSilentItemPosition(silentItemPos);
- return silentItemPos;
- }
-
- private void addNewSoundItem() {
- mRingtoneAdapter.addTitleForAddRingtoneItem(
- RingtonePickerViewModel.getAddNewItemTextByType(mPickerConfig.ringtoneType));
- }
-
- private RingtoneAdapter createRingtoneAdapter() {
- LocalizedCursor cursor = new LocalizedCursor(
- mRingtonePickerViewModel.getRingtoneCursor(), getResources(), COLUMN_LABEL);
- return new RingtoneAdapter(cursor, mRingtoneSelectionListener, mWorkRingtoneProvider);
- }
}
diff --git a/packages/SoundPicker/src/com/android/soundpicker/TabbedDialogFragment.java b/packages/SoundPicker/src/com/android/soundpicker/TabbedDialogFragment.java
index 63140d2..50ea9d7 100644
--- a/packages/SoundPicker/src/com/android/soundpicker/TabbedDialogFragment.java
+++ b/packages/SoundPicker/src/com/android/soundpicker/TabbedDialogFragment.java
@@ -23,7 +23,6 @@
import android.content.DialogInterface;
import android.content.Intent;
import android.media.RingtoneManager;
-import android.net.Uri;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
@@ -42,8 +41,6 @@
import org.jetbrains.annotations.NotNull;
-import java.util.Objects;
-
/**
* A dialog fragment with a sound and/or vibration tab based on the picker type.
* <ul>
@@ -59,6 +56,17 @@
private RingtonePickerViewModel mRingtonePickerViewModel;
+ private final ViewPager2.OnPageChangeCallback mOnPageChangeCallback =
+ new ViewPager2.OnPageChangeCallback() {
+ @Override
+ public void onPageScrollStateChanged(int state) {
+ super.onPageScrollStateChanged(state);
+ if (state == ViewPager2.SCROLL_STATE_IDLE) {
+ mRingtonePickerViewModel.onStop(/* isChangingConfigurations= */ false);
+ }
+ }
+ };
+
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -77,8 +85,7 @@
dialogBuilder
.setPositiveButton(getString(com.android.internal.R.string.ok),
(dialog, whichButton) -> {
- setSuccessResultWithRingtone(
- mRingtonePickerViewModel.getCurrentlySelectedRingtoneUri());
+ setSuccessResultWithSelectedRingtone();
requireActivity().finish();
})
.setNegativeButton(getString(com.android.internal.R.string.cancel),
@@ -110,9 +117,10 @@
}
}
- private void setSuccessResultWithRingtone(Uri ringtoneUri) {
+ private void setSuccessResultWithSelectedRingtone() {
requireActivity().setResult(Activity.RESULT_OK,
- new Intent().putExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI, ringtoneUri));
+ new Intent().putExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI,
+ mRingtonePickerViewModel.getSelectedRingtoneUri()));
}
/**
@@ -123,10 +131,8 @@
*/
private View buildTabbedView(@NonNull LayoutInflater inflater) {
View view = inflater.inflate(R.layout.fragment_tabbed_dialog, null, false);
- TabLayout tabLayout = view.findViewById(R.id.tabLayout);
- ViewPager2 viewPager = view.findViewById(R.id.masterViewPager);
- Objects.requireNonNull(tabLayout);
- Objects.requireNonNull(viewPager);
+ TabLayout tabLayout = view.requireViewById(R.id.tabLayout);
+ ViewPager2 viewPager = view.requireViewById(R.id.masterViewPager);
ViewPagerAdapter adapter = new ViewPagerAdapter(requireActivity());
addFragments(adapter);
@@ -137,6 +143,7 @@
}
viewPager.setAdapter(adapter);
+ viewPager.registerOnPageChangeCallback(mOnPageChangeCallback);
new TabLayoutMediator(tabLayout, viewPager,
(tab, position) -> tab.setText(adapter.getTitle(position))).attach();
diff --git a/packages/SoundPicker/src/com/android/soundpicker/VibrationPickerFragment.java b/packages/SoundPicker/src/com/android/soundpicker/VibrationPickerFragment.java
index 356b9ae..7412c19 100644
--- a/packages/SoundPicker/src/com/android/soundpicker/VibrationPickerFragment.java
+++ b/packages/SoundPicker/src/com/android/soundpicker/VibrationPickerFragment.java
@@ -16,14 +16,37 @@
package com.android.soundpicker;
-import androidx.fragment.app.Fragment;
+import android.os.Bundle;
+import android.view.View;
+
+import androidx.lifecycle.ViewModelProvider;
+
+import org.jetbrains.annotations.NotNull;
/**
- * A fragment that will display a picker used to select vibration.
+ * A fragment that displays a picker used to select vibration or silent (no vibration).
*/
-public class VibrationPickerFragment extends Fragment {
+public class VibrationPickerFragment extends BasePickerFragment {
- public VibrationPickerFragment() {
- super(R.layout.fragment_vibration_picker);
+ @Override
+ public void onViewCreated(@NotNull View view, Bundle savedInstanceState) {
+ mRingtonePickerViewModel = new ViewModelProvider(requireActivity()).get(
+ RingtonePickerViewModel.class);
+ super.onViewCreated(view, savedInstanceState);
+ }
+
+ @Override
+ protected RingtoneListHandler getRingtoneListHandler() {
+ return mRingtonePickerViewModel.getVibrationListHandler();
+ }
+
+ @Override
+ protected void addRingtoneAsync() {
+ // no-op
+ }
+
+ @Override
+ protected void addNewRingtoneItem() {
+ // no-op
}
}
diff --git a/packages/SoundPicker/tests/Android.bp b/packages/SoundPicker/tests/Android.bp
index dcd7b98..c38426f 100644
--- a/packages/SoundPicker/tests/Android.bp
+++ b/packages/SoundPicker/tests/Android.bp
@@ -27,6 +27,7 @@
"androidx.test.core",
"androidx.test.rules",
"androidx.test.ext.junit",
+ "androidx.test.ext.truth",
"mockito-target-minus-junit4",
"guava-android-testlib",
"SoundPickerLib",
diff --git a/packages/SoundPicker/tests/src/com/android/soundpicker/RingtoneListHandlerTest.java b/packages/SoundPicker/tests/src/com/android/soundpicker/RingtoneListHandlerTest.java
new file mode 100644
index 0000000..80e71e200
--- /dev/null
+++ b/packages/SoundPicker/tests/src/com/android/soundpicker/RingtoneListHandlerTest.java
@@ -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.soundpicker;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.when;
+
+import android.database.Cursor;
+import android.media.RingtoneManager;
+import android.net.Uri;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+public class RingtoneListHandlerTest {
+
+ private static final Uri DEFAULT_URI = Uri.parse("media://custom/ringtone/default_uri");
+ private static final Uri RINGTONE_URI = Uri.parse("media://custom/ringtone/uri");
+ private static final int SILENT_RINGTONE_POSITION = 0;
+ private static final int DEFAULT_RINGTONE_POSITION = 1;
+ private static final int RINGTONE_POSITION = 2;
+
+ @Mock
+ private RingtoneManager mMockRingtoneManager;
+ @Mock
+ private Cursor mMockCursor;
+
+ private RingtoneListHandler mRingtoneListHandler;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ RingtoneListHandler.Config mRingtoneListConfig = createRingtoneListConfig();
+
+ mRingtoneListHandler = new RingtoneListHandler();
+
+ // Add silent and default options to the list.
+ mRingtoneListHandler.addSilentItem();
+ mRingtoneListHandler.addDefaultItem();
+
+ mRingtoneListHandler.init(mRingtoneListConfig, mMockRingtoneManager, mMockCursor);
+ }
+
+ @Test
+ public void testGetRingtoneCursor_returnsTheCorrectRingtoneCursor() {
+ assertThat(mRingtoneListHandler.getRingtoneCursor()).isEqualTo(mMockCursor);
+ }
+
+ @Test
+ public void testGetRingtoneUri_returnsTheCorrectRingtoneUri() {
+ Uri expectedUri = RINGTONE_URI;
+ when(mMockRingtoneManager.getRingtoneUri(eq(0))).thenReturn(expectedUri);
+
+ // Request 3rd item from list.
+ Uri actualUri = mRingtoneListHandler.getRingtoneUri(RINGTONE_POSITION);
+ assertThat(actualUri).isEqualTo(expectedUri);
+ }
+
+ @Test
+ public void testGetRingtoneUri_withSelectedItemUnknown_returnsTheCorrectRingtoneUri() {
+ Uri uri = mRingtoneListHandler.getRingtoneUri(RingtoneListHandler.ITEM_POSITION_UNKNOWN);
+ assertThat(uri).isEqualTo(RingtoneListHandler.SILENT_URI);
+ }
+
+ @Test
+ public void testGetRingtoneUri_withSelectedItemDefaultPosition_returnsTheCorrectRingtoneUri() {
+ Uri actualUri = mRingtoneListHandler.getRingtoneUri(DEFAULT_RINGTONE_POSITION);
+ assertThat(actualUri).isEqualTo(DEFAULT_URI);
+ }
+
+ @Test
+ public void testGetRingtoneUri_withSelectedItemSilentPosition_returnsTheCorrectRingtoneUri() {
+ Uri uri = mRingtoneListHandler.getRingtoneUri(SILENT_RINGTONE_POSITION);
+ assertThat(uri).isEqualTo(RingtoneListHandler.SILENT_URI);
+ }
+
+ @Test
+ public void testGetCurrentlySelectedRingtoneUri_returnsTheCorrectRingtoneUri() {
+ mRingtoneListHandler.setSelectedItemPosition(RingtoneListHandler.ITEM_POSITION_UNKNOWN);
+ Uri actualUri = mRingtoneListHandler.getSelectedRingtoneUri();
+ assertThat(actualUri).isEqualTo(RingtoneListHandler.SILENT_URI);
+
+ mRingtoneListHandler.setSelectedItemPosition(DEFAULT_RINGTONE_POSITION);
+ actualUri = mRingtoneListHandler.getSelectedRingtoneUri();
+ assertThat(actualUri).isEqualTo(DEFAULT_URI);
+
+ mRingtoneListHandler.setSelectedItemPosition(SILENT_RINGTONE_POSITION);
+ actualUri = mRingtoneListHandler.getSelectedRingtoneUri();
+ assertThat(actualUri).isEqualTo(RingtoneListHandler.SILENT_URI);
+
+ when(mMockRingtoneManager.getRingtoneUri(eq(0))).thenReturn(RINGTONE_URI);
+ mRingtoneListHandler.setSelectedItemPosition(RINGTONE_POSITION);
+ actualUri = mRingtoneListHandler.getSelectedRingtoneUri();
+ assertThat(actualUri).isEqualTo(RINGTONE_URI);
+ }
+
+ @Test
+ public void testGetRingtonePosition_returnsTheCorrectRingtonePosition() {
+ when(mMockRingtoneManager.getRingtonePosition(RINGTONE_URI)).thenReturn(0);
+
+ int actualPosition = mRingtoneListHandler.getRingtonePosition(RINGTONE_URI);
+
+ assertThat(actualPosition).isEqualTo(RINGTONE_POSITION);
+
+ }
+
+ @Test
+ public void testFixedItems_onlyAddsItemsOnceAndInOrder() {
+ // Clear fixed items before testing the add methods.
+ mRingtoneListHandler.resetFixedItems();
+
+ assertThat(mRingtoneListHandler.getSilentItemPosition()).isEqualTo(
+ RingtoneListHandler.ITEM_POSITION_UNKNOWN);
+ assertThat(mRingtoneListHandler.getDefaultItemPosition()).isEqualTo(
+ RingtoneListHandler.ITEM_POSITION_UNKNOWN);
+
+ mRingtoneListHandler.addSilentItem();
+ mRingtoneListHandler.addDefaultItem();
+ mRingtoneListHandler.addSilentItem();
+ mRingtoneListHandler.addDefaultItem();
+
+ assertThat(mRingtoneListHandler.getSilentItemPosition()).isEqualTo(
+ SILENT_RINGTONE_POSITION);
+ assertThat(mRingtoneListHandler.getDefaultItemPosition()).isEqualTo(
+ DEFAULT_RINGTONE_POSITION);
+ }
+
+ @Test
+ public void testResetFixedItems_resetsSilentAndDefaultItemPositions() {
+ assertThat(mRingtoneListHandler.getSilentItemPosition()).isEqualTo(
+ SILENT_RINGTONE_POSITION);
+ assertThat(mRingtoneListHandler.getDefaultItemPosition()).isEqualTo(
+ DEFAULT_RINGTONE_POSITION);
+
+ mRingtoneListHandler.resetFixedItems();
+
+ assertThat(mRingtoneListHandler.getSilentItemPosition()).isEqualTo(
+ RingtoneListHandler.ITEM_POSITION_UNKNOWN);
+ assertThat(mRingtoneListHandler.getDefaultItemPosition()).isEqualTo(
+ RingtoneListHandler.ITEM_POSITION_UNKNOWN);
+ }
+
+ private RingtoneListHandler.Config createRingtoneListConfig() {
+ return new RingtoneListHandler.Config(/* hasDefaultItem= */ true,
+ /* uriForDefaultItem= */ DEFAULT_URI, /* hasSilentItem= */ true,
+ /* existingUri= */ DEFAULT_URI);
+ }
+}
diff --git a/packages/SoundPicker/tests/src/com/android/soundpicker/RingtonePickerViewModelTest.java b/packages/SoundPicker/tests/src/com/android/soundpicker/RingtonePickerViewModelTest.java
index 659aae8..cde6c76 100644
--- a/packages/SoundPicker/tests/src/com/android/soundpicker/RingtonePickerViewModelTest.java
+++ b/packages/SoundPicker/tests/src/com/android/soundpicker/RingtonePickerViewModelTest.java
@@ -17,12 +17,14 @@
package com.android.soundpicker;
import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
@@ -54,13 +56,10 @@
@RunWith(AndroidJUnit4.class)
public class RingtonePickerViewModelTest {
- private static final Uri DEFAULT_URI = Uri.parse("https://www.google.com/login.html");
+ private static final Uri DEFAULT_URI = Uri.parse("media://custom/ringtone/default_uri");
+ private static final Uri RINGTONE_URI = Uri.parse("media://custom/ringtone/uri");
private static final int RINGTONE_TYPE_UNKNOWN = -1;
- private static final int POS_UNKNOWN = -1;
- private static final int NO_ATTRIBUTES_FLAGS = 0;
- private static final int SILENT_RINGTONE_POSITION = 0;
private static final int DEFAULT_RINGTONE_POSITION = 1;
- private static final int RINGTONE_POSITION = 2;
@Mock
private RingtoneManagerFactory mMockRingtoneManagerFactory;
@@ -69,35 +68,54 @@
@Mock
private RingtoneManager mMockRingtoneManager;
@Mock
- private Cursor mMockCursor;
- @Mock
private ListeningExecutorServiceFactory mMockListeningExecutorServiceFactory;
+ @Mock
+ private Cursor mMockCursor;
+ private RingtoneListHandler mSoundListHandler;
+ private RingtoneListHandler mVibrationListHandler;
private ExecutorService mMainThreadExecutor;
private ListeningExecutorService mBackgroundThreadExecutor;
private Ringtone mMockDefaultRingtone;
private Ringtone mMockRingtone;
private RingtonePickerViewModel mViewModel;
+ private RingtoneListHandler.Config mSoundListConfig;
+ private RingtoneListHandler.Config mVibrationListConfig;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- when(mMockRingtoneManagerFactory.create()).thenReturn(mMockRingtoneManager);
+ mSoundListHandler = new RingtoneListHandler();
+ mVibrationListHandler = new RingtoneListHandler();
+ mSoundListConfig = createRingtoneListConfig();
+ mVibrationListConfig = createRingtoneListConfig();
mMockDefaultRingtone = createMockRingtone();
mMockRingtone = createMockRingtone();
- when(mMockRingtoneFactory.create(DEFAULT_URI)).thenReturn(mMockDefaultRingtone);
- when(mMockRingtoneManager.getRingtone(anyInt())).thenReturn(mMockRingtone);
+ when(mMockRingtoneManagerFactory.create()).thenReturn(mMockRingtoneManager);
+ when(mMockRingtoneFactory.create(DEFAULT_URI,
+ AudioAttributes.FLAG_AUDIBILITY_ENFORCED)).thenReturn(mMockDefaultRingtone);
+ when(mMockRingtoneManager.getRingtoneUri(anyInt())).thenReturn(RINGTONE_URI);
+ when(mMockRingtoneManager.getCursor()).thenReturn(mMockCursor);
mMainThreadExecutor = TestingExecutors.sameThreadScheduledExecutor();
mBackgroundThreadExecutor = TestingExecutors.sameThreadScheduledExecutor();
when(mMockListeningExecutorServiceFactory.createSingleThreadExecutor()).thenReturn(
mBackgroundThreadExecutor);
mViewModel = new RingtonePickerViewModel(mMockRingtoneManagerFactory, mMockRingtoneFactory,
- mMockListeningExecutorServiceFactory);
- mViewModel.setSilentItemPosition(SILENT_RINGTONE_POSITION);
- mViewModel.setDefaultItemPosition(DEFAULT_RINGTONE_POSITION);
- mViewModel.setSampleItemPosition(RINGTONE_POSITION);
+ mMockListeningExecutorServiceFactory, mSoundListHandler,
+ mVibrationListHandler);
+
+ // Add silent and default options to the sound list.
+ mSoundListHandler.addSilentItem();
+ mSoundListHandler.addDefaultItem();
+
+ // Add silent and default options to the vibration list.
+ mVibrationListHandler.addSilentItem();
+ mVibrationListHandler.addDefaultItem();
+
+ mSoundListHandler.setSelectedItemPosition(DEFAULT_RINGTONE_POSITION);
+ mVibrationListHandler.setSelectedItemPosition(DEFAULT_RINGTONE_POSITION);
}
@After
@@ -112,59 +130,86 @@
@Test
public void testInitRingtoneManager_whenTypeIsUnknown_createManagerButDoNotSetType() {
- mViewModel.init(createPickerConfig(RINGTONE_TYPE_UNKNOWN));
+ mViewModel.init(createPickerConfig(RINGTONE_TYPE_UNKNOWN), mSoundListConfig,
+ mVibrationListConfig);
verify(mMockRingtoneManagerFactory).create();
verify(mMockRingtoneManager, never()).setType(anyInt());
+ assertNotNull(mViewModel.getSoundListHandler().getRingtoneListConfig());
+ assertNotNull(mViewModel.getVibrationListHandler().getRingtoneListConfig());
}
@Test
public void testInitRingtoneManager_whenTypeIsNotUnknown_createManagerAndSetType() {
- mViewModel.init(createPickerConfig(RingtoneManager.TYPE_NOTIFICATION));
+ mViewModel.init(createPickerConfig(RingtoneManager.TYPE_NOTIFICATION), mSoundListConfig,
+ mVibrationListConfig);
verify(mMockRingtoneManagerFactory).create();
verify(mMockRingtoneManager).setType(RingtoneManager.TYPE_NOTIFICATION);
+ assertNotNull(mViewModel.getSoundListHandler().getRingtoneListConfig());
+ assertNotNull(mViewModel.getVibrationListHandler().getRingtoneListConfig());
+ }
+
+ @Test
+ public void testInitRingtoneManager_bothListConfigsAreNull_onlyRecreateRingtoneManager() {
+ mViewModel.init(
+ createPickerConfig(RingtoneManager.TYPE_NOTIFICATION),
+ /* soundListConfig= */ null, /* vibrationListConfig= */ null);
+
+ verify(mMockRingtoneManagerFactory).create();
+ verify(mMockRingtoneManager).setType(RingtoneManager.TYPE_NOTIFICATION);
+ assertNull(mViewModel.getSoundListHandler().getRingtoneListConfig());
+ assertNull(mViewModel.getVibrationListHandler().getRingtoneListConfig());
+ }
+
+ @Test
+ public void testReinitialize_bothListConfigsInitialized_recreateManagerAndReinitHandlers() {
+ mViewModel.init(createPickerConfig(RingtoneManager.TYPE_NOTIFICATION), mSoundListConfig,
+ mVibrationListConfig);
+ mViewModel.reinit();
+
+ verify(mMockRingtoneManagerFactory, times(2)).create();
+ verify(mMockRingtoneManager, times(2)).setType(RingtoneManager.TYPE_NOTIFICATION);
+ assertNotNull(mViewModel.getSoundListHandler().getRingtoneListConfig());
+ assertNotNull(mViewModel.getVibrationListHandler().getRingtoneListConfig());
+ }
+
+ @Test
+ public void testReinitialize_bothListConfigsAlreadyNull_onlyRecreateRingtoneManager() {
+ mViewModel.init(
+ createPickerConfig(RingtoneManager.TYPE_NOTIFICATION),
+ /* soundListConfig= */ null, /* vibrationListConfig= */ null);
+ mViewModel.reinit();
+
+ verify(mMockRingtoneManagerFactory, times(2)).create();
+ verify(mMockRingtoneManager, times(2)).setType(RingtoneManager.TYPE_NOTIFICATION);
+ assertNull(mViewModel.getSoundListHandler().getRingtoneListConfig());
+ assertNull(mViewModel.getVibrationListHandler().getRingtoneListConfig());
}
@Test
public void testGetStreamType_returnsTheCorrectStreamType() {
when(mMockRingtoneManager.inferStreamType()).thenReturn(AudioManager.STREAM_ALARM);
- mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE));
+ mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE), mSoundListConfig,
+ mVibrationListConfig);
assertEquals(mViewModel.getRingtoneStreamType(), AudioManager.STREAM_ALARM);
}
@Test
- public void testGetRingtoneCursor_returnsTheCorrectRingtoneCursor() {
- when(mMockRingtoneManager.getCursor()).thenReturn(mMockCursor);
- mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE));
- assertEquals(mViewModel.getRingtoneCursor(), mMockCursor);
- }
-
- @Test
- public void testGetRingtoneUri_returnsTheCorrectRingtoneUri() {
- Uri expectedUri = DEFAULT_URI;
- when(mMockRingtoneManager.getRingtoneUri(anyInt())).thenReturn(expectedUri);
- mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE));
- Uri actualUri = mViewModel.getRingtoneUri(DEFAULT_RINGTONE_POSITION);
- assertEquals(actualUri, expectedUri);
- }
-
- @Test
public void testOnPause_withChangingConfigurationTrue_doNotStopPlayingRingtone() {
- mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE));
- mViewModel.playRingtone(RINGTONE_POSITION, DEFAULT_URI,
- AudioAttributes.FLAG_AUDIBILITY_ENFORCED);
- verifyRingtonePlayCalledAndMockPlayingState(mMockRingtone);
+ mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE), mSoundListConfig,
+ mVibrationListConfig);
+ mViewModel.playRingtone();
+ verifyRingtonePlayCalledAndMockPlayingState(mMockDefaultRingtone);
mViewModel.onPause(/* isChangingConfigurations= */ true);
- verify(mMockRingtone, never()).stop();
+ verify(mMockDefaultRingtone, never()).stop();
}
@Test
public void testOnPause_withChangingConfigurationFalse_stopPlayingRingtone() {
- mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE));
- mViewModel.setSampleItemPosition(DEFAULT_RINGTONE_POSITION);
- mViewModel.playRingtone(DEFAULT_RINGTONE_POSITION, DEFAULT_URI,
- AudioAttributes.FLAG_AUDIBILITY_ENFORCED);
+ mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE), mSoundListConfig,
+ mVibrationListConfig);
+ mViewModel.playRingtone();
verifyRingtonePlayCalledAndMockPlayingState(mMockDefaultRingtone);
mViewModel.onPause(/* isChangingConfigurations= */ false);
verify(mMockDefaultRingtone).stop();
@@ -172,24 +217,24 @@
@Test
public void testOnViewModelRecreated_previousRingtoneCanStillBeStopped() {
- mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE));
- mViewModel.setSampleItemPosition(RINGTONE_POSITION);
+ mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE), mSoundListConfig,
+ mVibrationListConfig);
Ringtone mockRingtone1 = createMockRingtone();
Ringtone mockRingtone2 = createMockRingtone();
- when(mMockRingtoneManager.getRingtone(anyInt())).thenReturn(mockRingtone1, mockRingtone2);
- mViewModel.playRingtone(DEFAULT_RINGTONE_POSITION, DEFAULT_URI,
- AudioAttributes.FLAG_AUDIBILITY_ENFORCED);
+
+ when(mMockRingtoneFactory.create(any(), anyInt())).thenReturn(mockRingtone1, mockRingtone2);
+ mViewModel.playRingtone();
verifyRingtonePlayCalledAndMockPlayingState(mockRingtone1);
// Fake a scenario where the activity is destroyed and recreated due to a config change.
// This will result in a new view model getting created.
mViewModel.onStop(/* isChangingConfigurations= */ true);
verify(mockRingtone1, never()).stop();
mViewModel = new RingtonePickerViewModel(mMockRingtoneManagerFactory, mMockRingtoneFactory,
- mMockListeningExecutorServiceFactory);
- mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE));
- mViewModel.setSampleItemPosition(RINGTONE_POSITION);
- mViewModel.playRingtone(DEFAULT_RINGTONE_POSITION, DEFAULT_URI,
- AudioAttributes.FLAG_AUDIBILITY_ENFORCED);
+ mMockListeningExecutorServiceFactory, mSoundListHandler,
+ mVibrationListHandler);
+ mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE), mSoundListConfig,
+ mVibrationListConfig);
+ mViewModel.playRingtone();
verifyRingtonePlayCalledAndMockPlayingState(mockRingtone2);
verify(mockRingtone1).stop();
verify(mockRingtone2, never()).stop();
@@ -197,10 +242,9 @@
@Test
public void testOnStop_withChangingConfigurationTrueAndDefaultRingtonePlaying_saveRingtone() {
- mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE));
- mViewModel.setSampleItemPosition(DEFAULT_RINGTONE_POSITION);
- mViewModel.playRingtone(DEFAULT_RINGTONE_POSITION, DEFAULT_URI,
- AudioAttributes.FLAG_AUDIBILITY_ENFORCED);
+ mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE), mSoundListConfig,
+ mVibrationListConfig);
+ mViewModel.playRingtone();
verifyRingtonePlayCalledAndMockPlayingState(mMockDefaultRingtone);
mViewModel.onStop(/* isChangingConfigurations= */ true);
assertEquals(RingtonePickerViewModel.sPlayingRingtone, mMockDefaultRingtone);
@@ -208,239 +252,66 @@
@Test
public void testOnStop_withChangingConfigurationTrueAndCurrentRingtonePlaying_saveRingtone() {
- mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE));
- mViewModel.setSampleItemPosition(RINGTONE_POSITION);
- mViewModel.playRingtone(RINGTONE_POSITION, DEFAULT_URI,
- AudioAttributes.FLAG_AUDIBILITY_ENFORCED);
- verifyRingtonePlayCalledAndMockPlayingState(mMockRingtone);
+ mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE), mSoundListConfig,
+ mVibrationListConfig);
+ mViewModel.playRingtone();
+ verifyRingtonePlayCalledAndMockPlayingState(mMockDefaultRingtone);
mViewModel.onStop(/* isChangingConfigurations= */ true);
- assertEquals(RingtonePickerViewModel.sPlayingRingtone, mMockRingtone);
+ assertEquals(RingtonePickerViewModel.sPlayingRingtone, mMockDefaultRingtone);
}
@Test
public void testOnStop_withChangingConfigurationTrueAndNoPlayingRingtone_saveNothing() {
- mViewModel.setSampleItemPosition(DEFAULT_RINGTONE_POSITION);
mViewModel.onStop(/* isChangingConfigurations= */ true);
assertNull(RingtonePickerViewModel.sPlayingRingtone);
}
@Test
public void testOnStop_withChangingConfigurationFalse_stopPlayingRingtone() {
- mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE));
- mViewModel.setSampleItemPosition(DEFAULT_RINGTONE_POSITION);
- mViewModel.playRingtone(DEFAULT_RINGTONE_POSITION, DEFAULT_URI,
- AudioAttributes.FLAG_AUDIBILITY_ENFORCED);
+ mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE), mSoundListConfig,
+ mVibrationListConfig);
+
+ mViewModel.playRingtone();
verifyRingtonePlayCalledAndMockPlayingState(mMockDefaultRingtone);
mViewModel.onStop(/* isChangingConfigurations= */ false);
verify(mMockDefaultRingtone).stop();
}
@Test
- public void testGetCurrentlySelectedRingtoneUri_checkedItemIsUnknown_returnsNull() {
- mViewModel.setSelectedItemPosition(POS_UNKNOWN);
- Uri uri = mViewModel.getCurrentlySelectedRingtoneUri();
- assertNull(uri);
+ public void testGetCurrentlySelectedRingtoneUri_returnsTheCorrectRingtoneUri() {
+ mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE), mSoundListConfig,
+ mVibrationListConfig);
+
+ assertEquals(DEFAULT_URI, mViewModel.getSelectedRingtoneUri());
}
@Test
- public void testGetCurrentlySelectedRingtoneUri_checkedItemIsDefaultPos_returnsDefaultUri() {
- Uri expectedUri = DEFAULT_URI;
- mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE));
- mViewModel.setSelectedItemPosition(DEFAULT_RINGTONE_POSITION);
- Uri actualUri = mViewModel.getCurrentlySelectedRingtoneUri();
- assertEquals(actualUri, expectedUri);
- }
+ public void testPlayRingtone_playTheCorrectRingtone() {
+ mSoundListHandler.setSelectedItemPosition(DEFAULT_RINGTONE_POSITION);
+ mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE), mSoundListConfig,
+ mVibrationListConfig);
- @Test
- public void testGetCurrentlySelectedRingtoneUri_checkedItemIsSilentPos_returnsNull() {
- mViewModel.setSelectedItemPosition(SILENT_RINGTONE_POSITION);
- Uri uri = mViewModel.getCurrentlySelectedRingtoneUri();
- assertNull(uri);
- }
-
- @Test
- public void testCancelPendingAsyncTasks_correctlyCancelsPendingTasks()
- throws IOException {
- FutureCallback<Uri> mockCallback = mock(FutureCallback.class);
-
- when(mMockListeningExecutorServiceFactory.createSingleThreadExecutor()).thenReturn(
- TestingExecutors.noOpScheduledExecutor());
- when(mMockRingtoneManager.addCustomExternalRingtone(DEFAULT_URI,
- RingtoneManager.TYPE_NOTIFICATION)).thenReturn(DEFAULT_URI);
- mViewModel = new RingtonePickerViewModel(mMockRingtoneManagerFactory, mMockRingtoneFactory,
- mMockListeningExecutorServiceFactory);
- mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE));
- mViewModel.addRingtoneAsync(DEFAULT_URI, RingtoneManager.TYPE_NOTIFICATION, mockCallback,
- mMainThreadExecutor);
- verify(mockCallback, never()).onFailure(any());
- // Calling cancelPendingAsyncTasks should cancel the pending task. Cancelling an async
- // task invokes the onFailure method in the callable.
- mViewModel.cancelPendingAsyncTasks();
- verify(mockCallback).onFailure(any());
- verify(mockCallback, never()).onSuccess(any());
-
- }
-
- @Test
- public void testAddRingtoneAsync_cancelPreviousTaskBeforeStartingNewOne()
- throws IOException {
- FutureCallback<Uri> mockCallback1 = mock(FutureCallback.class);
- FutureCallback<Uri> mockCallback2 = mock(FutureCallback.class);
-
- when(mMockListeningExecutorServiceFactory.createSingleThreadExecutor()).thenReturn(
- TestingExecutors.noOpScheduledExecutor());
- when(mMockRingtoneManager.addCustomExternalRingtone(DEFAULT_URI,
- RingtoneManager.TYPE_NOTIFICATION)).thenReturn(DEFAULT_URI);
- mViewModel = new RingtonePickerViewModel(mMockRingtoneManagerFactory, mMockRingtoneFactory,
- mMockListeningExecutorServiceFactory);
- mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE));
- mViewModel.addRingtoneAsync(DEFAULT_URI, RingtoneManager.TYPE_NOTIFICATION, mockCallback1,
- mMainThreadExecutor);
- verify(mockCallback1, never()).onFailure(any());
- // We call addRingtoneAsync again to cancel the previous task and start a new one.
- // Cancelling an async task invokes the onFailure method in the callable.
- mViewModel.addRingtoneAsync(DEFAULT_URI, RingtoneManager.TYPE_NOTIFICATION, mockCallback2,
- mMainThreadExecutor);
- verify(mockCallback1).onFailure(any());
- verify(mockCallback1, never()).onSuccess(any());
- verifyNoMoreInteractions(mockCallback2);
- }
-
- @Test
- public void testAddRingtoneAsync_whenAddRingtoneIsSuccessful_successCallbackIsInvoked()
- throws IOException {
- Uri expectedUri = DEFAULT_URI;
- FutureCallback<Uri> mockCallback = mock(FutureCallback.class);
-
- when(mMockRingtoneManager.addCustomExternalRingtone(DEFAULT_URI,
- RingtoneManager.TYPE_NOTIFICATION)).thenReturn(expectedUri);
-
- mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE));
- mViewModel.addRingtoneAsync(DEFAULT_URI, RingtoneManager.TYPE_NOTIFICATION, mockCallback,
- mMainThreadExecutor);
-
- verify(mMockRingtoneManager).addCustomExternalRingtone(DEFAULT_URI,
- RingtoneManager.TYPE_NOTIFICATION);
- verify(mockCallback).onSuccess(expectedUri);
- verify(mockCallback, never()).onFailure(any());
- }
-
- @Test
- public void testAddRingtoneAsync_whenAddRingtoneFailed_failureCallbackIsInvoked()
- throws IOException {
- FutureCallback<Uri> mockCallback = mock(FutureCallback.class);
-
- when(mMockRingtoneManager.addCustomExternalRingtone(any(), anyInt())).thenThrow(
- IOException.class);
-
- mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE));
- mViewModel.addRingtoneAsync(DEFAULT_URI, RingtoneManager.TYPE_NOTIFICATION, mockCallback,
- mMainThreadExecutor);
-
- verify(mockCallback).onFailure(any(IOException.class));
- verify(mockCallback, never()).onSuccess(any());
- }
-
- @Test
- public void testGetCurrentlySelectedRingtoneUri_checkedItemRingtonePos_returnsTheCorrectUri() {
- Uri expectedUri = DEFAULT_URI;
- when(mMockRingtoneManager.getRingtoneUri(RINGTONE_POSITION)).thenReturn(expectedUri);
- mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE));
- mViewModel.setSelectedItemPosition(RINGTONE_POSITION);
- Uri actualUri = mViewModel.getCurrentlySelectedRingtoneUri();
-
- verify(mMockRingtoneManager).getRingtoneUri(RINGTONE_POSITION);
- assertEquals(actualUri, expectedUri);
+ mViewModel.playRingtone();
+ verifyRingtonePlayCalledAndMockPlayingState(mMockDefaultRingtone);
}
@Test
public void testPlayRingtone_stopsPreviouslyRunningRingtone() {
// Start playing the first ringtone
- mViewModel.setSampleItemPosition(DEFAULT_RINGTONE_POSITION);
- mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE));
- mViewModel.playRingtone(DEFAULT_RINGTONE_POSITION, DEFAULT_URI,
- AudioAttributes.FLAG_AUDIBILITY_ENFORCED);
+ mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE), mSoundListConfig,
+ mVibrationListConfig);
+ mViewModel.playRingtone();
verifyRingtonePlayCalledAndMockPlayingState(mMockDefaultRingtone);
// Start playing the second ringtone
- mViewModel.setSampleItemPosition(RINGTONE_POSITION);
- mViewModel.playRingtone(RINGTONE_POSITION, DEFAULT_URI,
- AudioAttributes.FLAG_AUDIBILITY_ENFORCED);
+ when(mMockRingtoneFactory.create(DEFAULT_URI,
+ AudioAttributes.FLAG_AUDIBILITY_ENFORCED)).thenReturn(mMockRingtone);
+ mViewModel.playRingtone();
verifyRingtonePlayCalledAndMockPlayingState(mMockRingtone);
verify(mMockDefaultRingtone).stop();
}
@Test
- public void testPlayRingtone_samplePosEqualToSilentPos_onlyStopPlayingRingtone() {
- mViewModel.setSampleItemPosition(DEFAULT_RINGTONE_POSITION);
- mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE));
- mViewModel.playRingtone(DEFAULT_RINGTONE_POSITION, DEFAULT_URI,
- AudioAttributes.FLAG_AUDIBILITY_ENFORCED);
- verifyRingtonePlayCalledAndMockPlayingState(mMockDefaultRingtone);
-
- mViewModel.setSampleItemPosition(SILENT_RINGTONE_POSITION);
- mViewModel.playRingtone(RINGTONE_POSITION, DEFAULT_URI,
- AudioAttributes.FLAG_AUDIBILITY_ENFORCED);
- verify(mMockDefaultRingtone).stop();
- // This will be invoked on the first ringtone we play, but not on the second one.
- verify(mMockRingtoneFactory).create(any());
- verify(mMockRingtoneManager, never()).getRingtone(anyInt());
- verify(mMockRingtone, never()).play();
-
- }
-
- @Test
- public void testPlayRingtone_samplePosEqualToDefaultPos_playDefaultRingtone() {
- mViewModel.setSampleItemPosition(DEFAULT_RINGTONE_POSITION);
- mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE));
-
- when(mMockRingtoneManager.inferStreamType()).thenReturn(AudioManager.STREAM_ALARM);
-
- mViewModel.playRingtone(DEFAULT_RINGTONE_POSITION, DEFAULT_URI,
- AudioAttributes.FLAG_AUDIBILITY_ENFORCED);
- verifyRingtonePlayCalledAndMockPlayingState(mMockDefaultRingtone);
-
- verify(mMockDefaultRingtone).setStreamType(AudioManager.STREAM_ALARM);
- verify(mMockDefaultRingtone).play();
- }
-
- @Test
- public void testPlayRingtone_samplePosNotEqualToDefaultPos_playRingtone() {
- mViewModel.setSampleItemPosition(RINGTONE_POSITION);
- mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE));
-
- mViewModel.playRingtone(RINGTONE_POSITION, DEFAULT_URI,
- AudioAttributes.FLAG_AUDIBILITY_ENFORCED);
- verifyRingtonePlayCalledAndMockPlayingState(mMockRingtone);
- verify(mMockRingtone).setAudioAttributes(
- audioAttributes(AudioAttributes.USAGE_NOTIFICATION_RINGTONE,
- AudioAttributes.FLAG_AUDIBILITY_ENFORCED));
- verify(mMockRingtone).play();
- }
-
- @Test
- public void testPlayRingtone_withNoAttributeFlags_doNotUpdateRingtoneAttributesFlags() {
- mViewModel.setSampleItemPosition(RINGTONE_POSITION);
- mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE));
-
- mViewModel.playRingtone(RINGTONE_POSITION, DEFAULT_URI,
- NO_ATTRIBUTES_FLAGS);
- verifyRingtonePlayCalledAndMockPlayingState(mMockRingtone);
- verify(mMockRingtone, never()).setAudioAttributes(any());
- verify(mMockRingtone).play();
- }
-
- @Test
- public void testGetRingtonePosition_returnsTheCorrectRingtonePosition() {
- int expectedPosition = 1;
- when(mMockRingtoneManager.getRingtonePosition(any())).thenReturn(expectedPosition);
- mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE));
- int actualPosition = mViewModel.getRingtonePosition(DEFAULT_URI);
-
- assertEquals(actualPosition, expectedPosition);
- }
-
- @Test
public void testDefaultItemUri_withNotificationIntent_returnDefaultNotificationUri() {
Uri uri = RingtonePickerViewModel.getDefaultItemUriByType(
RingtoneManager.TYPE_NOTIFICATION);
@@ -537,6 +408,89 @@
assertEquals(R.string.ringtone_default, defaultRingtoneItemText);
}
+ @Test
+ public void testCancelPendingAsyncTasks_correctlyCancelsPendingTasks()
+ throws IOException {
+ FutureCallback<Uri> mockCallback = mock(FutureCallback.class);
+
+ when(mMockListeningExecutorServiceFactory.createSingleThreadExecutor()).thenReturn(
+ TestingExecutors.noOpScheduledExecutor());
+
+ mViewModel = new RingtonePickerViewModel(mMockRingtoneManagerFactory, mMockRingtoneFactory,
+ mMockListeningExecutorServiceFactory, mSoundListHandler,
+ mVibrationListHandler);
+ mViewModel.addSoundRingtoneAsync(DEFAULT_URI, RingtoneManager.TYPE_NOTIFICATION,
+ mockCallback, mMainThreadExecutor);
+ verify(mockCallback, never()).onFailure(any());
+ // Calling cancelPendingAsyncTasks should cancel the pending task. Cancelling an async
+ // task invokes the onFailure method in the callable.
+ mViewModel.cancelPendingAsyncTasks();
+ verify(mockCallback).onFailure(any());
+ verify(mockCallback, never()).onSuccess(any());
+
+ }
+
+ @Test
+ public void testAddRingtoneAsync_cancelPreviousTaskBeforeStartingNewOne()
+ throws IOException {
+ FutureCallback<Uri> mockCallback1 = mock(FutureCallback.class);
+ FutureCallback<Uri> mockCallback2 = mock(FutureCallback.class);
+
+ when(mMockListeningExecutorServiceFactory.createSingleThreadExecutor()).thenReturn(
+ TestingExecutors.noOpScheduledExecutor());
+
+ mViewModel = new RingtonePickerViewModel(mMockRingtoneManagerFactory, mMockRingtoneFactory,
+ mMockListeningExecutorServiceFactory, mSoundListHandler,
+ mVibrationListHandler);
+ mViewModel.addSoundRingtoneAsync(DEFAULT_URI, RingtoneManager.TYPE_NOTIFICATION,
+ mockCallback1, mMainThreadExecutor);
+ verify(mockCallback1, never()).onFailure(any());
+ // We call addRingtoneAsync again to cancel the previous task and start a new one.
+ // Cancelling an async task invokes the onFailure method in the callable.
+ mViewModel.addSoundRingtoneAsync(DEFAULT_URI, RingtoneManager.TYPE_NOTIFICATION,
+ mockCallback2, mMainThreadExecutor);
+ verify(mockCallback1).onFailure(any());
+ verify(mockCallback1, never()).onSuccess(any());
+ verifyNoMoreInteractions(mockCallback2);
+ }
+
+ @Test
+ public void testAddRingtoneAsync_whenAddRingtoneIsSuccessful_successCallbackIsInvoked()
+ throws IOException {
+ Uri expectedUri = DEFAULT_URI;
+ FutureCallback<Uri> mockCallback = mock(FutureCallback.class);
+
+ when(mMockRingtoneManager.addCustomExternalRingtone(DEFAULT_URI,
+ RingtoneManager.TYPE_NOTIFICATION)).thenReturn(expectedUri);
+
+ mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE), mSoundListConfig,
+ mVibrationListConfig);
+
+ mViewModel.addSoundRingtoneAsync(DEFAULT_URI, RingtoneManager.TYPE_NOTIFICATION,
+ mockCallback, mMainThreadExecutor);
+
+ verify(mockCallback).onSuccess(expectedUri);
+ verify(mockCallback, never()).onFailure(any());
+ }
+
+ @Test
+ public void testAddRingtoneAsync_whenAddRingtoneFailed_failureCallbackIsInvoked()
+ throws IOException {
+ FutureCallback<Uri> mockCallback = mock(FutureCallback.class);
+
+ when(mMockRingtoneManager.addCustomExternalRingtone(any(), anyInt())).thenThrow(
+ IOException.class);
+
+ mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE), mSoundListConfig,
+ mVibrationListConfig);
+
+ mViewModel.addSoundRingtoneAsync(DEFAULT_URI, RingtoneManager.TYPE_NOTIFICATION,
+ mockCallback, mMainThreadExecutor);
+
+ verify(mockCallback).onFailure(any(IOException.class));
+ verify(mockCallback, never()).onSuccess(any());
+ }
+
private Ringtone createMockRingtone() {
Ringtone mockRingtone = mock(Ringtone.class);
when(mockRingtone.getAudioAttributes()).thenReturn(
@@ -558,12 +512,23 @@
.build();
}
- private RingtonePickerViewModel.PickerConfig createPickerConfig(int ringtoneType) {
- return new RingtonePickerViewModel.PickerConfig("Phone ringtone", /* userId= */ 1,
- ringtoneType, /* hasDefaultItem= */ true,
- /* uriForDefaultItem= */ DEFAULT_URI, /* hasSilentItem= */ true,
- /* audioAttributesFlags= */0, /* existingUri= */ Uri.parse(""),
- /* showOkCancelButtons= */ true,
+ private RingtonePickerViewModel.Config createPickerConfig(int ringtoneType,
+ int audioAttributes) {
+ return new RingtonePickerViewModel.Config("Phone ringtone", /* userId= */ 1,
+ ringtoneType, /* showOkCancelButtons= */ true,
+ audioAttributes, RingtonePickerViewModel.PickerType.RINGTONE_PICKER);
+ }
+
+ private RingtonePickerViewModel.Config createPickerConfig(int ringtoneType) {
+ return new RingtonePickerViewModel.Config("Phone ringtone", /* userId= */ 1,
+ ringtoneType, /* showOkCancelButtons= */ true,
+ AudioAttributes.FLAG_AUDIBILITY_ENFORCED,
RingtonePickerViewModel.PickerType.RINGTONE_PICKER);
}
+
+ private RingtoneListHandler.Config createRingtoneListConfig() {
+ return new RingtoneListHandler.Config(/* hasDefaultItem= */ true,
+ /* uriForDefaultItem= */ DEFAULT_URI, /* hasSilentItem= */ true,
+ /* existingUri= */ Uri.parse(""));
+ }
}
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 4633a65..7be6043 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -123,6 +123,7 @@
],
static_libs: [
"SystemUISharedLib",
+ "SystemUICustomizationLib",
"SettingsLib",
"androidx.leanback_leanback",
"androidx.slice_slice-core",
diff --git a/packages/SystemUI/OWNERS b/packages/SystemUI/OWNERS
index 1ce3472..bba926d 100644
--- a/packages/SystemUI/OWNERS
+++ b/packages/SystemUI/OWNERS
@@ -23,6 +23,7 @@
brycelee@google.com
brzezinski@google.com
caitlinshk@google.com
+cameronyee@google.com
chandruis@google.com
chrisgollner@google.com
cinek@google.com
@@ -44,6 +45,7 @@
jjaggi@google.com
jonmiranda@google.com
joshtrask@google.com
+juansmartinez@google.com
juliacr@google.com
juliatuttle@google.com
justinkoh@google.com
@@ -74,7 +76,6 @@
pixel@google.com
pomini@google.com
rahulbanerjee@google.com
-rasheedlewis@google.com
roosa@google.com
saff@google.com
santie@google.com
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
index 8e79e3c..38b99cc 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
@@ -70,6 +70,9 @@
* If a new layout change happens while an animation is already in progress, the animation
* is updated to continue from the current values to the new end state.
*
+ * A set of [excludedViews] can be passed. If any dependent view from [rootView] matches an
+ * entry in this set, changes to that view will not be animated.
+ *
* The animator continues to respond to layout changes until [stopAnimating] is called.
*
* Successive calls to this method override the previous settings ([interpolator] and
@@ -82,9 +85,16 @@
fun animate(
rootView: View,
interpolator: Interpolator = DEFAULT_INTERPOLATOR,
- duration: Long = DEFAULT_DURATION
+ duration: Long = DEFAULT_DURATION,
+ excludedViews: Set<View> = emptySet()
): Boolean {
- return animate(rootView, interpolator, duration, ephemeral = false)
+ return animate(
+ rootView,
+ interpolator,
+ duration,
+ ephemeral = false,
+ excludedViews = excludedViews
+ )
}
/**
@@ -95,16 +105,24 @@
fun animateNextUpdate(
rootView: View,
interpolator: Interpolator = DEFAULT_INTERPOLATOR,
- duration: Long = DEFAULT_DURATION
+ duration: Long = DEFAULT_DURATION,
+ excludedViews: Set<View> = emptySet()
): Boolean {
- return animate(rootView, interpolator, duration, ephemeral = true)
+ return animate(
+ rootView,
+ interpolator,
+ duration,
+ ephemeral = true,
+ excludedViews = excludedViews
+ )
}
private fun animate(
rootView: View,
interpolator: Interpolator,
duration: Long,
- ephemeral: Boolean
+ ephemeral: Boolean,
+ excludedViews: Set<View> = emptySet()
): Boolean {
if (
!occupiesSpace(
@@ -119,7 +137,7 @@
}
val listener = createUpdateListener(interpolator, duration, ephemeral)
- addListener(rootView, listener, recursive = true)
+ addListener(rootView, listener, recursive = true, excludedViews = excludedViews)
return true
}
@@ -921,8 +939,11 @@
private fun addListener(
view: View,
listener: View.OnLayoutChangeListener,
- recursive: Boolean = false
+ recursive: Boolean = false,
+ excludedViews: Set<View> = emptySet()
) {
+ if (excludedViews.contains(view)) return
+
// Make sure that only one listener is active at a time.
val previousListener = view.getTag(R.id.tag_layout_listener)
if (previousListener != null && previousListener is View.OnLayoutChangeListener) {
@@ -933,7 +954,12 @@
view.setTag(R.id.tag_layout_listener, listener)
if (view is ViewGroup && recursive) {
for (i in 0 until view.childCount) {
- addListener(view.getChildAt(i), listener, recursive = true)
+ addListener(
+ view.getChildAt(i),
+ listener,
+ recursive = true,
+ excludedViews = excludedViews
+ )
}
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
index ca91b8a..38b751c 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
@@ -17,15 +17,19 @@
package com.android.systemui.notifications.ui.composable
+import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
import androidx.compose.ui.unit.dp
@Composable
@@ -34,10 +38,26 @@
) {
// TODO(b/272779828): implement.
Column(
- modifier = modifier.fillMaxWidth().defaultMinSize(minHeight = 300.dp).padding(4.dp),
+ modifier =
+ modifier
+ .fillMaxWidth()
+ .defaultMinSize(minHeight = 300.dp)
+ .clip(RoundedCornerShape(32.dp))
+ .background(MaterialTheme.colorScheme.surface)
+ .padding(16.dp),
) {
- Text("Notifications", modifier = Modifier.align(Alignment.CenterHorizontally))
+ Text(
+ text = "Notifications",
+ modifier = Modifier.align(Alignment.CenterHorizontally),
+ style = MaterialTheme.typography.titleLarge,
+ color = MaterialTheme.colorScheme.onSurface,
+ )
Spacer(modifier = Modifier.weight(1f))
- Text("Shelf", modifier = Modifier.align(Alignment.CenterHorizontally))
+ Text(
+ text = "Shelf",
+ modifier = Modifier.align(Alignment.CenterHorizontally),
+ style = MaterialTheme.typography.titleSmall,
+ color = MaterialTheme.colorScheme.onSurface,
+ )
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/QuickSettings.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/QuickSettings.kt
index 665d6dd..1bb341c 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/QuickSettings.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/QuickSettings.kt
@@ -17,15 +17,19 @@
package com.android.systemui.qs.footer.ui.compose
+import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
import androidx.compose.ui.unit.dp
@Composable
@@ -34,10 +38,26 @@
) {
// TODO(b/272780058): implement.
Column(
- modifier = modifier.fillMaxWidth().defaultMinSize(minHeight = 300.dp).padding(4.dp),
+ modifier =
+ modifier
+ .fillMaxWidth()
+ .defaultMinSize(minHeight = 300.dp)
+ .clip(RoundedCornerShape(32.dp))
+ .background(MaterialTheme.colorScheme.primary)
+ .padding(16.dp),
) {
- Text("Quick settings", modifier = Modifier.align(Alignment.CenterHorizontally))
+ Text(
+ text = "Quick settings",
+ modifier = Modifier.align(Alignment.CenterHorizontally),
+ style = MaterialTheme.typography.titleLarge,
+ color = MaterialTheme.colorScheme.onPrimary,
+ )
Spacer(modifier = Modifier.weight(1f))
- Text("QS footer actions", modifier = Modifier.align(Alignment.CenterHorizontally))
+ Text(
+ text = "QS footer actions",
+ modifier = Modifier.align(Alignment.CenterHorizontally),
+ style = MaterialTheme.typography.titleSmall,
+ color = MaterialTheme.colorScheme.onPrimary,
+ )
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
index 20e1751..27358f5 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
@@ -16,18 +16,19 @@
package com.android.systemui.shade.ui.composable
+import androidx.compose.foundation.clickable
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.material3.Button
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Text
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.notifications.ui.composable.Notifications
+import com.android.systemui.qs.footer.ui.compose.QuickSettings
import com.android.systemui.scene.shared.model.Direction
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
@@ -62,12 +63,7 @@
override fun Content(
containerName: String,
modifier: Modifier,
- ) {
- ShadeScene(
- viewModel = viewModel,
- modifier = modifier,
- )
- }
+ ) = ShadeScene(viewModel, modifier)
private fun destinationScenes(
up: SceneKey,
@@ -84,23 +80,15 @@
viewModel: ShadeSceneViewModel,
modifier: Modifier = Modifier,
) {
- // TODO(b/280887022): implement the real UI.
-
- Box(modifier = modifier) {
- Column(
- horizontalAlignment = Alignment.CenterHorizontally,
- modifier = Modifier.align(Alignment.Center)
- ) {
- Text("Shade", style = MaterialTheme.typography.headlineMedium)
- Row(
- horizontalArrangement = Arrangement.spacedBy(8.dp),
- ) {
- Button(
- onClick = { viewModel.onContentClicked() },
- ) {
- Text("Open some content")
- }
- }
- }
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = Arrangement.spacedBy(16.dp),
+ modifier =
+ Modifier.fillMaxSize()
+ .clickable(onClick = { viewModel.onContentClicked() })
+ .padding(horizontal = 16.dp, vertical = 48.dp)
+ ) {
+ QuickSettings(modifier = modifier.height(160.dp))
+ Notifications(modifier = modifier.weight(1f))
}
}
diff --git a/packages/SystemUI/customization/res/values-h800dp/dimens.xml b/packages/SystemUI/customization/res/values-h800dp/dimens.xml
index 60afc8a..cb49945 100644
--- a/packages/SystemUI/customization/res/values-h800dp/dimens.xml
+++ b/packages/SystemUI/customization/res/values-h800dp/dimens.xml
@@ -17,4 +17,7 @@
<resources>
<!-- Large clock maximum font size (dp is intentional, to prevent any further scaling) -->
<dimen name="large_clock_text_size">200dp</dimen>
+
+ <!-- With the large clock, move up slightly from the center -->
+ <dimen name="keyguard_large_clock_top_margin">-112dp</dimen>
</resources>
diff --git a/packages/SystemUI/customization/res/values/dimens.xml b/packages/SystemUI/customization/res/values/dimens.xml
index ba8f284..8eb8132 100644
--- a/packages/SystemUI/customization/res/values/dimens.xml
+++ b/packages/SystemUI/customization/res/values/dimens.xml
@@ -24,4 +24,12 @@
<item name="keyguard_clock_line_spacing_scale" type="dimen" format="float">.7</item>
<!-- Burmese line spacing multiplier between hours and minutes of the keyguard clock -->
<item name="keyguard_clock_line_spacing_scale_burmese" type="dimen" format="float">1</item>
+
+ <!-- With the large clock, move up slightly from the center -->
+ <dimen name="keyguard_large_clock_top_margin">-60dp</dimen>
+
+ <!-- additional offset for clock switch area items -->
+ <dimen name="small_clock_height">114dp</dimen>
+ <dimen name="small_clock_padding_top">28dp</dimen>
+ <dimen name="clock_padding_start">28dp</dimen>
</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/ktfmt_includes.txt b/packages/SystemUI/ktfmt_includes.txt
index 18753fd..006fc09 100644
--- a/packages/SystemUI/ktfmt_includes.txt
+++ b/packages/SystemUI/ktfmt_includes.txt
@@ -422,9 +422,6 @@
-packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallFlags.kt
-packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallLogger.kt
-packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherContainer.kt
--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/model/ConnectivitySlots.kt
--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt
-packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryStateNotifier.kt
-packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsController.kt
-packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImpl.kt
@@ -696,7 +693,6 @@
-packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallChronometerTest.kt
-packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
-packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallLoggerTest.kt
--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
-packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryStateNotifierTest.kt
-packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ClockTest.kt
-packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImplTest.kt
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
index c0b69c1..25f77ea 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
@@ -172,7 +172,6 @@
public String expandedAccessibilityClassName;
public SlashState slash;
public boolean handlesLongClick = true;
- public boolean showRippleEffect = true;
@Nullable
public Drawable sideViewCustomDrawable;
public String spec;
@@ -217,7 +216,6 @@
|| !Objects.equals(other.dualTarget, dualTarget)
|| !Objects.equals(other.slash, slash)
|| !Objects.equals(other.handlesLongClick, handlesLongClick)
- || !Objects.equals(other.showRippleEffect, showRippleEffect)
|| !Objects.equals(other.sideViewCustomDrawable, sideViewCustomDrawable);
other.spec = spec;
other.icon = icon;
@@ -234,7 +232,6 @@
other.isTransient = isTransient;
other.slash = slash != null ? slash.copy() : null;
other.handlesLongClick = handlesLongClick;
- other.showRippleEffect = showRippleEffect;
other.sideViewCustomDrawable = sideViewCustomDrawable;
return changed;
}
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_message_area.xml b/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_message_area.xml
index 57b3acd..66c54f2 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_message_area.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_message_area.xml
@@ -21,6 +21,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/keyguard_lock_padding"
+ android:importantForAccessibility="no"
android:ellipsize="marquee"
android:focusable="true"
android:gravity="center"
diff --git a/packages/SystemUI/res-keyguard/layout/status_bar_mobile_signal_group_inner.xml b/packages/SystemUI/res-keyguard/layout/status_bar_mobile_signal_group_inner.xml
index c85449d0..8f1323d 100644
--- a/packages/SystemUI/res-keyguard/layout/status_bar_mobile_signal_group_inner.xml
+++ b/packages/SystemUI/res-keyguard/layout/status_bar_mobile_signal_group_inner.xml
@@ -30,12 +30,13 @@
<FrameLayout
android:id="@+id/inout_container"
- android:layout_height="17dp"
+ android:layout_height="@dimen/status_bar_mobile_inout_container_size"
android:layout_width="wrap_content"
android:layout_gravity="center_vertical">
<ImageView
android:id="@+id/mobile_in"
- android:layout_height="wrap_content"
+ android:layout_height="@dimen/status_bar_mobile_signal_size"
+ android:adjustViewBounds="true"
android:layout_width="wrap_content"
android:src="@drawable/ic_activity_down"
android:visibility="gone"
@@ -43,7 +44,8 @@
/>
<ImageView
android:id="@+id/mobile_out"
- android:layout_height="wrap_content"
+ android:layout_height="@dimen/status_bar_mobile_signal_size"
+ android:adjustViewBounds="true"
android:layout_width="wrap_content"
android:src="@drawable/ic_activity_up"
android:paddingEnd="2dp"
@@ -52,11 +54,12 @@
</FrameLayout>
<ImageView
android:id="@+id/mobile_type"
- android:layout_height="wrap_content"
+ android:layout_height="@dimen/status_bar_mobile_signal_size"
android:layout_width="wrap_content"
android:layout_gravity="center_vertical"
- android:paddingStart="2.5dp"
- android:paddingEnd="1dp"
+ android:adjustViewBounds="true"
+ android:paddingStart="2.5sp"
+ android:paddingEnd="1sp"
android:visibility="gone" />
<Space
android:id="@+id/mobile_roaming_space"
@@ -70,14 +73,14 @@
android:layout_gravity="center_vertical">
<com.android.systemui.statusbar.AnimatedImageView
android:id="@+id/mobile_signal"
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
+ android:layout_height="@dimen/status_bar_mobile_signal_size"
+ android:layout_width="@dimen/status_bar_mobile_signal_size"
systemui:hasOverlappingRendering="false"
/>
<ImageView
android:id="@+id/mobile_roaming"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
+ android:layout_width="@dimen/status_bar_mobile_signal_size"
+ android:layout_height="@dimen/status_bar_mobile_signal_size"
android:layout_gravity="top|start"
android:src="@drawable/stat_sys_roaming"
android:contentDescription="@string/data_connection_roaming"
diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml
index 39dd90e..8c81733 100644
--- a/packages/SystemUI/res-keyguard/values/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values/dimens.xml
@@ -95,9 +95,6 @@
<dimen name="num_pad_key_margin_end">12dp</dimen>
<!-- additional offset for clock switch area items -->
- <dimen name="small_clock_height">114dp</dimen>
- <dimen name="small_clock_padding_top">28dp</dimen>
- <dimen name="clock_padding_start">28dp</dimen>
<dimen name="below_clock_padding_start">32dp</dimen>
<dimen name="below_clock_padding_end">16dp</dimen>
<dimen name="below_clock_padding_start_icons">28dp</dimen>
diff --git a/core/res/res/color/letterbox_background.xml b/packages/SystemUI/res/color/brightness_slider_overlay_color.xml
similarity index 69%
copy from core/res/res/color/letterbox_background.xml
copy to packages/SystemUI/res/color/brightness_slider_overlay_color.xml
index 955948a..a8abd79 100644
--- a/core/res/res/color/letterbox_background.xml
+++ b/packages/SystemUI/res/color/brightness_slider_overlay_color.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- ~ Copyright (C) 2022 The Android Open Source Project
+ ~ 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.
@@ -14,6 +14,9 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
+
<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:color="@color/system_neutral1_500" android:lStar="5" />
-</selector>
+ <item android:state_pressed="true" android:color="?attr/onShadeActive" android:alpha="0.12" />
+ <item android:state_hovered="true" android:color="?attr/onShadeActive" android:alpha="0.09" />
+ <item android:color="@color/transparent" />
+</selector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/brightness_progress_full_drawable.xml b/packages/SystemUI/res/drawable/brightness_progress_full_drawable.xml
index 2ea90c7..a9e7adf 100644
--- a/packages/SystemUI/res/drawable/brightness_progress_full_drawable.xml
+++ b/packages/SystemUI/res/drawable/brightness_progress_full_drawable.xml
@@ -26,6 +26,13 @@
<corners android:radius="@dimen/rounded_slider_corner_radius"/>
</shape>
</item>
+ <item>
+ <shape>
+ <corners android:radius="@dimen/rounded_slider_corner_radius" />
+ <size android:height="@dimen/rounded_slider_height" />
+ <solid android:color="@color/brightness_slider_overlay_color" />
+ </shape>
+ </item>
<item
android:id="@+id/slider_icon"
android:gravity="center_vertical|right"
diff --git a/packages/SystemUI/res/drawable/keyguard_bottom_affordance_bg.xml b/packages/SystemUI/res/drawable/keyguard_bottom_affordance_bg.xml
index 87b5a4c..32dc4b3 100644
--- a/packages/SystemUI/res/drawable/keyguard_bottom_affordance_bg.xml
+++ b/packages/SystemUI/res/drawable/keyguard_bottom_affordance_bg.xml
@@ -20,7 +20,7 @@
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
android:shape="rectangle">
- <solid android:color="?androidprv:attr/colorSurface"/>
+ <solid android:color="?androidprv:attr/materialColorSurfaceContainerHigh"/>
<size
android:width="@dimen/keyguard_affordance_fixed_width"
android:height="@dimen/keyguard_affordance_fixed_height"/>
diff --git a/packages/SystemUI/res/layout/activity_rear_display_education.xml b/packages/SystemUI/res/layout/activity_rear_display_education.xml
index c295cfe..1b6247f 100644
--- a/packages/SystemUI/res/layout/activity_rear_display_education.xml
+++ b/packages/SystemUI/res/layout/activity_rear_display_education.xml
@@ -28,7 +28,7 @@
app:cardCornerRadius="28dp"
app:cardBackgroundColor="@color/rear_display_overlay_animation_background_color">
- <com.airbnb.lottie.LottieAnimationView
+ <com.android.systemui.reardisplay.RearDisplayEducationLottieViewWrapper
android:id="@+id/rear_display_folded_animation"
android:importantForAccessibility="no"
android:layout_width="@dimen/rear_display_animation_width"
diff --git a/packages/SystemUI/res/layout/activity_rear_display_education_opened.xml b/packages/SystemUI/res/layout/activity_rear_display_education_opened.xml
index 0e6b281..bded012 100644
--- a/packages/SystemUI/res/layout/activity_rear_display_education_opened.xml
+++ b/packages/SystemUI/res/layout/activity_rear_display_education_opened.xml
@@ -29,7 +29,7 @@
app:cardCornerRadius="28dp"
app:cardBackgroundColor="@color/rear_display_overlay_animation_background_color">
- <com.airbnb.lottie.LottieAnimationView
+ <com.android.systemui.reardisplay.RearDisplayEducationLottieViewWrapper
android:id="@+id/rear_display_folded_animation"
android:importantForAccessibility="no"
android:layout_width="@dimen/rear_display_animation_width_opened"
diff --git a/packages/SystemUI/res/layout/auth_biometric_contents.xml b/packages/SystemUI/res/layout/auth_biometric_contents.xml
index efc661a..50b3bec 100644
--- a/packages/SystemUI/res/layout/auth_biometric_contents.xml
+++ b/packages/SystemUI/res/layout/auth_biometric_contents.xml
@@ -57,7 +57,7 @@
<include layout="@layout/auth_biometric_icon"/>
- <com.airbnb.lottie.LottieAnimationView
+ <com.android.systemui.biometrics.BiometricPromptLottieViewWrapper
android:id="@+id/biometric_icon_overlay"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
diff --git a/packages/SystemUI/res/layout/biometric_prompt_layout.xml b/packages/SystemUI/res/layout/biometric_prompt_layout.xml
index 05ff1b1..ecb0bfa 100644
--- a/packages/SystemUI/res/layout/biometric_prompt_layout.xml
+++ b/packages/SystemUI/res/layout/biometric_prompt_layout.xml
@@ -39,6 +39,7 @@
android:singleLine="true"
android:marqueeRepeatLimit="1"
android:ellipsize="marquee"
+ android:importantForAccessibility="no"
style="@style/TextAppearance.AuthCredential.Subtitle"/>
<TextView
@@ -59,7 +60,7 @@
android:layout_height="wrap_content"
android:layout_gravity="center">
- <com.airbnb.lottie.LottieAnimationView
+ <com.android.systemui.biometrics.BiometricPromptLottieViewWrapper
android:id="@+id/biometric_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@@ -67,7 +68,7 @@
android:contentDescription="@null"
android:scaleType="fitXY" />
- <com.airbnb.lottie.LottieAnimationView
+ <com.android.systemui.biometrics.BiometricPromptLottieViewWrapper
android:id="@+id/biometric_icon_overlay"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
diff --git a/packages/SystemUI/res/layout/combined_qs_header.xml b/packages/SystemUI/res/layout/combined_qs_header.xml
index 665c612..f3a6bbe 100644
--- a/packages/SystemUI/res/layout/combined_qs_header.xml
+++ b/packages/SystemUI/res/layout/combined_qs_header.xml
@@ -121,10 +121,12 @@
<LinearLayout
android:id="@+id/shade_header_system_icons"
android:layout_width="wrap_content"
- app:layout_constraintHeight_min="@dimen/large_screen_shade_header_min_height"
- android:layout_height="@dimen/large_screen_shade_header_min_height"
+ android:layout_height="@dimen/shade_header_system_icons_height"
android:clickable="true"
android:orientation="horizontal"
+ android:gravity="center_vertical"
+ android:paddingStart="@dimen/shade_header_system_icons_padding_start"
+ android:paddingEnd="@dimen/shade_header_system_icons_padding_end"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@id/privacy_container"
app:layout_constraintTop_toTopOf="@id/clock">
@@ -132,13 +134,13 @@
<com.android.systemui.statusbar.phone.StatusIconContainer
android:id="@+id/statusIcons"
android:layout_width="wrap_content"
- android:layout_height="match_parent"
+ android:layout_height="wrap_content"
android:paddingEnd="@dimen/signal_cluster_battery_padding" />
<com.android.systemui.battery.BatteryMeterView
android:id="@+id/batteryRemainingIcon"
android:layout_width="wrap_content"
- android:layout_height="match_parent"
+ android:layout_height="wrap_content"
app:textAppearance="@style/TextAppearance.QS.Status" />
</LinearLayout>
diff --git a/packages/SystemUI/res/layout/heads_up_status_bar_layout.xml b/packages/SystemUI/res/layout/heads_up_status_bar_layout.xml
index bacb5c1..49744e7 100644
--- a/packages/SystemUI/res/layout/heads_up_status_bar_layout.xml
+++ b/packages/SystemUI/res/layout/heads_up_status_bar_layout.xml
@@ -27,8 +27,8 @@
view. -->
<Space
android:id="@+id/icon_placeholder"
- android:layout_width="@dimen/status_bar_icon_drawing_size"
- android:layout_height="@dimen/status_bar_icon_drawing_size"
+ android:layout_width="@dimen/status_bar_icon_size_sp"
+ android:layout_height="@dimen/status_bar_icon_size_sp"
android:layout_gravity="center_vertical"
/>
<TextView
diff --git a/packages/SystemUI/res/layout/media_smartspace_recommendations.xml b/packages/SystemUI/res/layout/media_smartspace_recommendations.xml
deleted file mode 100644
index 9304ff7..0000000
--- a/packages/SystemUI/res/layout/media_smartspace_recommendations.xml
+++ /dev/null
@@ -1,136 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2019 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
- -->
-
-<!-- Layout for media recommendations inside QSPanel carousel -->
-<!-- See media_recommendation_expanded.xml and media_recommendation_collapsed.xml for the
- constraints. -->
-<com.android.systemui.util.animation.TransitionLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto"
- android:id="@+id/media_recommendations"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:clipChildren="false"
- android:clipToPadding="false"
- android:forceHasOverlappingRendering="false"
- android:background="@drawable/qs_media_background"
- android:theme="@style/MediaPlayer">
-
- <!-- This view just ensures the full media player is a certain height. -->
- <View
- android:id="@+id/sizing_view"
- android:layout_width="match_parent"
- android:layout_height="@dimen/qs_media_session_height_expanded" />
-
- <com.android.internal.widget.CachingIconView
- android:id="@+id/recommendation_card_icon"
- android:layout_width="@dimen/qs_media_app_icon_size"
- android:layout_height="@dimen/qs_media_app_icon_size"
- android:minWidth="@dimen/qs_media_app_icon_size"
- android:minHeight="@dimen/qs_media_app_icon_size"
- android:layout_marginStart="@dimen/qs_media_padding"
- android:layout_marginTop="@dimen/qs_media_rec_icon_top_margin"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toTopOf="parent" />
-
- <FrameLayout
- android:id="@+id/media_cover1_container"
- style="@style/MediaPlayer.Recommendation.AlbumContainer"
- >
- <ImageView
- android:id="@+id/media_cover1"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:minWidth="@dimen/qs_media_rec_album_size"
- android:minHeight="@dimen/qs_media_rec_album_size"
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintBottom_toBottomOf="parent"
- android:adjustViewBounds="true"
- android:background="@drawable/bg_smartspace_media_item"
- style="@style/MediaPlayer.Recommendation.Album"
- android:clipToOutline="true"
- android:scaleType="centerCrop"/>
- </FrameLayout>
-
- <TextView
- android:id="@+id/media_title1"
- style="@style/MediaPlayer.Recommendation.Text.Title"
- />
-
- <TextView
- android:id="@+id/media_subtitle1"
- style="@style/MediaPlayer.Recommendation.Text.Subtitle"
- />
-
- <FrameLayout
- android:id="@+id/media_cover2_container"
- style="@style/MediaPlayer.Recommendation.AlbumContainer"
- >
- <ImageView
- android:id="@+id/media_cover2"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:minWidth="@dimen/qs_media_rec_album_size"
- android:minHeight="@dimen/qs_media_rec_album_size"
- android:adjustViewBounds="true"
- android:background="@drawable/bg_smartspace_media_item"
- style="@style/MediaPlayer.Recommendation.Album"
- android:clipToOutline="true"
- android:scaleType="centerCrop"/>
- </FrameLayout>
-
- <TextView
- android:id="@+id/media_title2"
- style="@style/MediaPlayer.Recommendation.Text.Title"
- />
-
- <TextView
- android:id="@+id/media_subtitle2"
- style="@style/MediaPlayer.Recommendation.Text.Subtitle"
- />
-
- <FrameLayout
- android:id="@+id/media_cover3_container"
- style="@style/MediaPlayer.Recommendation.AlbumContainer"
- >
- <ImageView
- android:id="@+id/media_cover3"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:minWidth="@dimen/qs_media_rec_album_size"
- android:minHeight="@dimen/qs_media_rec_album_size"
- android:adjustViewBounds="true"
- android:background="@drawable/bg_smartspace_media_item"
- style="@style/MediaPlayer.Recommendation.Album"
- android:clipToOutline="true"
- android:scaleType="centerCrop"/>
- </FrameLayout>
-
- <TextView
- android:id="@+id/media_title3"
- style="@style/MediaPlayer.Recommendation.Text.Title"
- />
-
- <TextView
- android:id="@+id/media_subtitle3"
- style="@style/MediaPlayer.Recommendation.Text.Subtitle"
- />
-
- <include
- layout="@layout/media_long_press_menu" />
-
-</com.android.systemui.util.animation.TransitionLayout>
diff --git a/packages/SystemUI/res/layout/quick_settings_brightness_dialog.xml b/packages/SystemUI/res/layout/quick_settings_brightness_dialog.xml
index e95c6a7..91550b3 100644
--- a/packages/SystemUI/res/layout/quick_settings_brightness_dialog.xml
+++ b/packages/SystemUI/res/layout/quick_settings_brightness_dialog.xml
@@ -34,5 +34,6 @@
android:paddingEnd="0dp"
android:progressDrawable="@drawable/brightness_progress_drawable"
android:splitTrack="false"
+ android:clickable="true"
/>
</com.android.systemui.settings.brightness.BrightnessSliderView>
diff --git a/packages/SystemUI/res/layout/sidefps_view.xml b/packages/SystemUI/res/layout/sidefps_view.xml
index 73050c2..4d95220 100644
--- a/packages/SystemUI/res/layout/sidefps_view.xml
+++ b/packages/SystemUI/res/layout/sidefps_view.xml
@@ -14,7 +14,7 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<com.airbnb.lottie.LottieAnimationView
+<com.android.systemui.biometrics.SideFpsLottieViewWrapper
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/sidefps_animation"
diff --git a/packages/SystemUI/res/layout/status_bar.xml b/packages/SystemUI/res/layout/status_bar.xml
index 6b8f3cf..909f19f 100644
--- a/packages/SystemUI/res/layout/status_bar.xml
+++ b/packages/SystemUI/res/layout/status_bar.xml
@@ -32,7 +32,7 @@
<ImageView
android:id="@+id/notification_lights_out"
- android:layout_width="@dimen/status_bar_icon_size"
+ android:layout_width="@dimen/status_bar_icon_size_sp"
android:layout_height="match_parent"
android:paddingStart="@dimen/status_bar_padding_start"
android:paddingBottom="2dip"
diff --git a/packages/SystemUI/res/layout/status_bar_notification_footer.xml b/packages/SystemUI/res/layout/status_bar_notification_footer.xml
index db94c92..909048e 100644
--- a/packages/SystemUI/res/layout/status_bar_notification_footer.xml
+++ b/packages/SystemUI/res/layout/status_bar_notification_footer.xml
@@ -17,10 +17,9 @@
<!-- Extends Framelayout -->
<com.android.systemui.statusbar.notification.row.FooterView
xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:paddingStart="16dp"
- android:paddingEnd="16dp"
android:visibility="gone">
<com.android.systemui.statusbar.AlphaOptimizedFrameLayout
android:id="@+id/content"
@@ -37,31 +36,45 @@
android:visibility="gone"
android:textAppearance="?android:attr/textAppearanceButton"
android:text="@string/unlock_to_see_notif_text"/>
- <com.android.systemui.statusbar.notification.row.FooterViewButton
- style="@style/TextAppearance.NotificationSectionHeaderButton"
- android:id="@+id/manage_text"
- android:layout_width="wrap_content"
- android:layout_height="48dp"
- android:layout_marginTop="12dp"
- android:layout_gravity="start"
- android:background="@drawable/notif_footer_btn_background"
- android:focusable="true"
- android:textColor="@color/notif_pill_text"
- android:contentDescription="@string/manage_notifications_history_text"
- android:text="@string/manage_notifications_history_text"
- />
- <com.android.systemui.statusbar.notification.row.FooterViewButton
- style="@style/TextAppearance.NotificationSectionHeaderButton"
- android:id="@+id/dismiss_text"
- android:layout_width="wrap_content"
- android:layout_height="48dp"
- android:layout_marginTop="12dp"
- android:layout_gravity="end"
- android:background="@drawable/notif_footer_btn_background"
- android:focusable="true"
- android:textColor="@color/notif_pill_text"
- android:contentDescription="@string/accessibility_clear_all"
- android:text="@string/clear_all_notifications_text"
- />
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ >
+ <com.android.systemui.statusbar.notification.row.FooterViewButton
+ style="@style/TextAppearance.NotificationSectionHeaderButton"
+ android:id="@+id/manage_text"
+ android:layout_width="wrap_content"
+ android:layout_height="48dp"
+ android:layout_marginTop="12dp"
+ android:layout_marginStart="16dp"
+ app:layout_constraintVertical_bias="0.0"
+ app:layout_constraintHorizontal_chainStyle="spread_inside"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintEnd_toStartOf="@id/dismiss_text"
+ android:background="@drawable/notif_footer_btn_background"
+ android:focusable="true"
+ android:textColor="@color/notif_pill_text"
+ android:contentDescription="@string/manage_notifications_history_text"
+ android:text="@string/manage_notifications_history_text"
+ />
+ <com.android.systemui.statusbar.notification.row.FooterViewButton
+ style="@style/TextAppearance.NotificationSectionHeaderButton"
+ android:id="@+id/dismiss_text"
+ android:layout_width="wrap_content"
+ android:layout_height="48dp"
+ android:layout_marginTop="12dp"
+ android:layout_marginEnd="16dp"
+ app:layout_constraintVertical_bias="1.0"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintStart_toEndOf="@id/manage_text"
+ android:background="@drawable/notif_footer_btn_background"
+ android:focusable="true"
+ android:textColor="@color/notif_pill_text"
+ android:contentDescription="@string/accessibility_clear_all"
+ android:text="@string/clear_all_notifications_text"
+ />
+ </androidx.constraintlayout.widget.ConstraintLayout>
</com.android.systemui.statusbar.AlphaOptimizedFrameLayout>
</com.android.systemui.statusbar.notification.row.FooterView>
diff --git a/packages/SystemUI/res/layout/status_bar_wifi_group.xml b/packages/SystemUI/res/layout/status_bar_wifi_group.xml
deleted file mode 100644
index 6cb6993b..0000000
--- a/packages/SystemUI/res/layout/status_bar_wifi_group.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-**
-** Copyright 2018, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-** http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-<com.android.systemui.statusbar.StatusBarWifiView
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/wifi_combo"
- android:layout_width="wrap_content"
- android:layout_height="match_parent"
- android:gravity="center_vertical" >
-
- <include layout="@layout/status_bar_wifi_group_inner" />
-
-</com.android.systemui.statusbar.StatusBarWifiView>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/status_bar_wifi_group_inner.xml b/packages/SystemUI/res/layout/status_bar_wifi_group_inner.xml
index 0ea0653..4c5cd7d 100644
--- a/packages/SystemUI/res/layout/status_bar_wifi_group_inner.xml
+++ b/packages/SystemUI/res/layout/status_bar_wifi_group_inner.xml
@@ -24,16 +24,17 @@
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center_vertical"
- android:layout_marginStart="2.5dp"
+ android:layout_marginStart="2.5sp"
>
<FrameLayout
android:id="@+id/inout_container"
- android:layout_height="17dp"
+ android:layout_height="@dimen/status_bar_wifi_inout_container_size"
android:layout_width="wrap_content"
android:gravity="center_vertical" >
<ImageView
android:id="@+id/wifi_in"
- android:layout_height="wrap_content"
+ android:layout_height="@dimen/status_bar_wifi_signal_size"
+ android:adjustViewBounds="true"
android:layout_width="wrap_content"
android:src="@drawable/ic_activity_down"
android:visibility="gone"
@@ -41,7 +42,8 @@
/>
<ImageView
android:id="@+id/wifi_out"
- android:layout_height="wrap_content"
+ android:layout_height="@dimen/status_bar_wifi_signal_size"
+ android:adjustViewBounds="true"
android:layout_width="wrap_content"
android:src="@drawable/ic_activity_up"
android:paddingEnd="2dp"
@@ -75,7 +77,7 @@
<View
android:id="@+id/wifi_airplane_spacer"
android:layout_width="@dimen/status_bar_airplane_spacer_width"
- android:layout_height="4dp"
+ android:layout_height="wrap_content"
android:visibility="gone"
/>
</com.android.keyguard.AlphaOptimizedLinearLayout>
diff --git a/packages/SystemUI/res/layout/super_notification_shade.xml b/packages/SystemUI/res/layout/super_notification_shade.xml
index 2fde947..a336252 100644
--- a/packages/SystemUI/res/layout/super_notification_shade.xml
+++ b/packages/SystemUI/res/layout/super_notification_shade.xml
@@ -76,6 +76,13 @@
android:layout_height="match_parent"
android:visibility="invisible" />
+ <!-- Shared container for the notification stack. Can be positioned by either
+ the keyguard_root_view or notification_panel -->
+ <com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
+ android:id="@+id/shared_notification_container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+
<include layout="@layout/brightness_mirror_container" />
<com.android.systemui.scrim.ScrimView
diff --git a/packages/SystemUI/res/layout/udfps_keyguard_preview.xml b/packages/SystemUI/res/layout/udfps_keyguard_preview.xml
index c068b7b..0964a21 100644
--- a/packages/SystemUI/res/layout/udfps_keyguard_preview.xml
+++ b/packages/SystemUI/res/layout/udfps_keyguard_preview.xml
@@ -24,7 +24,7 @@
android:background="@drawable/fingerprint_bg">
<!-- LockScreen fingerprint icon from 0 stroke width to full width -->
- <com.airbnb.lottie.LottieAnimationView
+ <com.android.systemui.keyguard.ui.view.UdfpsLottieViewWrapper
android:layout_width="0dp"
android:layout_height="0dp"
android:scaleType="centerCrop"
diff --git a/packages/SystemUI/res/layout/udfps_keyguard_view_internal.xml b/packages/SystemUI/res/layout/udfps_keyguard_view_internal.xml
index 191158e..1d6147c 100644
--- a/packages/SystemUI/res/layout/udfps_keyguard_view_internal.xml
+++ b/packages/SystemUI/res/layout/udfps_keyguard_view_internal.xml
@@ -32,7 +32,7 @@
<!-- Fingerprint -->
<!-- AOD dashed fingerprint icon with moving dashes -->
- <com.airbnb.lottie.LottieAnimationView
+ <com.android.systemui.keyguard.ui.view.UdfpsLottieViewWrapper
android:id="@+id/udfps_aod_fp"
android:layout_width="match_parent"
android:layout_height="match_parent"
@@ -43,7 +43,7 @@
app:lottie_rawRes="@raw/udfps_aod_fp"/>
<!-- LockScreen fingerprint icon from 0 stroke width to full width -->
- <com.airbnb.lottie.LottieAnimationView
+ <com.android.systemui.keyguard.ui.view.UdfpsLottieViewWrapper
android:id="@+id/udfps_lockscreen_fp"
android:layout_width="match_parent"
android:layout_height="match_parent"
diff --git a/packages/SystemUI/res/layout/zen_mode_condition.xml b/packages/SystemUI/res/layout/zen_mode_condition.xml
index ab52465..3baae33 100644
--- a/packages/SystemUI/res/layout/zen_mode_condition.xml
+++ b/packages/SystemUI/res/layout/zen_mode_condition.xml
@@ -15,6 +15,7 @@
limitations under the License.
-->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:theme="@style/Theme.SystemUI.QuickSettings"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipChildren="false"
diff --git a/packages/SystemUI/res/values-af/strings.xml b/packages/SystemUI/res/values-af/strings.xml
index 5bd2184..e0c25e3 100644
--- a/packages/SystemUI/res/values-af/strings.xml
+++ b/packages/SystemUI/res/values-af/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Tik om te bekyk"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Kon nie skermopname stoor nie"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Kon nie skermopname begin nie"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Bekyk tans volskerm"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Swiep van bo af as jy wil uitgaan."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Het dit"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Terug"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Tuis"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Kieslys"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Wanneer jy deel, opneem of uitsaai, het Android toegang tot enigiets wat op jou skerm sigbaar is of op jou toestel gespeel word. Wees dus versigtig met dinge soos wagwoorde, betalingbesonderhede, boodskappe, foto’s, en oudio en video."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Wanneer jy ’n app deel, opneem of uitsaai, het Android toegang tot enigiets wat in daardie app gewys of gespeel word. Wees dus versigtig met dinge soos wagwoorde, betalingbesonderhede, boodskappe, foto’s, en oudio en video."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Begin"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Deur jou IT-admin geblokkeer"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Skermskote is deur toestelbeleid gedeaktiveer"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Vee alles uit"</string>
diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml
index f2eb766..b8ac5fc 100644
--- a/packages/SystemUI/res/values-am/strings.xml
+++ b/packages/SystemUI/res/values-am/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"ለመመልከት መታ ያድርጉ"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"የማያ ገጽ ቀረጻን ማስቀመጥ ላይ ስህተት"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"የማያ ገፅ ቀረጻን መጀመር ላይ ስህተት"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"ሙሉ ገፅ በማሳየት ላይ"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"ለመውጣት፣ ከላይ ወደታች ጠረግ ያድርጉ።"</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"ገባኝ"</string>
<string name="accessibility_back" msgid="6530104400086152611">"ተመለስ"</string>
<string name="accessibility_home" msgid="5430449841237966217">"መነሻ"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"ምናሌ"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"እርስዎ ሲያጋሩ፣ ሲቀርጹ ወይም cast ሲያደርጉ Android በማያ ገጽዎ ላይ ለሚታይ ወይም በመሣሪያዎ ላይ ለሚጫወት ማንኛውም ነገር መዳረሻ አለው። ስለዚህ እንደ የይለፍ ቃላት፣ የክፍያ ዝርዝሮች፣ መልዕክቶች፣ ፎቶዎች እና ኦዲዮ እና ቪድዮ ላሉ ነገሮች ጥንቃቄ ያድርጉ።"</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"እርስዎ ሲያጋሩ፣ ሲቀርጹ ወይም cast ሲያደርጉ Android በማያ ገጽዎ ላይ ለሚታይ ወይም በመሣሪያዎ ላይ ለሚጫወት ማንኛውም ነገር መዳረሻ አለው። ስለዚህ እንደ የይለፍ ቃላት፣ የክፍያ ዝርዝሮች፣ መልዕክቶች፣ ፎቶዎች እና ኦዲዮ እና ቪድዮ ላሉ ነገሮች ጥንቃቄ ያድርጉ።"</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"ጀምር"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"በእርስዎ የአይቲ አስተዳዳሪ ታግዷል"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"የማያ ገፅ ቀረጻ በመሣሪያ መመሪያ ተሰናክሏል"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"ሁሉንም አጽዳ"</string>
diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml
index 9a74144..e465773 100644
--- a/packages/SystemUI/res/values-ar/strings.xml
+++ b/packages/SystemUI/res/values-ar/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"انقر لعرض التسجيل."</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"حدث خطأ أثناء حفظ تسجيل محتوى الشاشة."</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"حدث خطأ في بدء تسجيل الشاشة"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"جارٍ العرض بملء الشاشة"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"للخروج، مرر بسرعة من أعلى إلى أسفل."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"حسنًا"</string>
<string name="accessibility_back" msgid="6530104400086152611">"رجوع"</string>
<string name="accessibility_home" msgid="5430449841237966217">"الرئيسية"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"القائمة"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"أثناء المشاركة أو التسجيل أو البثّ، يمكن لنظام Android الوصول إلى كل المحتوى المعروض على شاشتك أو الذي يتم تشغيله على جهازك، لذا يُرجى توخي الحذر بشأن المعلومات، مثل كلمات المرور وتفاصيل الدفع والرسائل والصور وملفات الصوت والفيديو."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"أثناء مشاركة محتوى تطبيق أو تسجيله أو بثّه، يمكن لنظام Android الوصول إلى كل المحتوى المعروض أو الذي يتم تشغيله في ذلك التطبيق، لذا يُرجى توخي الحذر بشأن المعلومات مثل كلمات المرور وتفاصيل الدفع والرسائل والصور وملفات الصوت والفيديو."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"بدء"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"حظر مشرف تكنولوجيا المعلومات هذه الميزة"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"ميزة \"تصوير الشاشة\" غير مفعَّلة بسبب سياسة الجهاز."</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"محو الكل"</string>
diff --git a/packages/SystemUI/res/values-as/strings.xml b/packages/SystemUI/res/values-as/strings.xml
index 856989c..372cab8 100644
--- a/packages/SystemUI/res/values-as/strings.xml
+++ b/packages/SystemUI/res/values-as/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"চাবলৈ টিপক"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"ৰেকৰ্ড কৰা স্ক্ৰীন ছেভ কৰোঁতে আসোঁৱাহ হৈছে"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"স্ক্রীন ৰেকৰ্ড কৰা আৰম্ভ কৰোঁতে আসোঁৱাহ হৈছে"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"পূৰ্ণ স্ক্ৰীনত চাই আছে"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"বাহিৰ হ’বলৈ ওপৰৰ পৰা তললৈ ছোৱাইপ কৰক।"</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"বুজি পালোঁ"</string>
<string name="accessibility_back" msgid="6530104400086152611">"উভতি যাওক"</string>
<string name="accessibility_home" msgid="5430449841237966217">"গৃহ পৃষ্ঠাৰ বুটাম"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"মেনু"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"আপুনি শ্বেয়াৰ কৰা, ৰেকৰ্ড কৰা অথবা কাষ্ট কৰাৰ সময়ত, আপোনাৰ স্ক্ৰীনখনত দৃশ্যমান হোৱা যিকোনো বস্তু অথবা আপোনাৰ ডিভাইচত প্লে’ কৰা যিকোনো সমললৈ Androidৰ এক্সেছ থাকে। গতিকে, পাছৱৰ্ড, পৰিশোধৰ সবিশেষ, বাৰ্তা, ফট’ আৰু অডিঅ’ আৰু ভিডিঅ’ৰ ক্ষেত্ৰত সাৱধান হওক।"</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"আপুনি শ্বেয়াৰ কৰা, ৰেকৰ্ড কৰা অথবা কাষ্ট কৰাৰ সময়ত, সেইটো এপত দৃশ্যমান যিকোনো বস্তু অথবা আপোনাৰ ডিভাইচত প্লে’ কৰা যিকোনো সমললৈ Androidৰ এক্সেছ থাকে। গতিকে, পাছৱৰ্ড, পৰিশোধৰ সবিশেষ, বাৰ্তা, ফট’ আৰু অডিঅ’ আৰু ভিডিঅ’ৰ ক্ষেত্ৰত সাৱধান হওক।"</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"আৰম্ভ কৰক"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"আপোনাৰ আইটি প্ৰশাসকে অৱৰোধ কৰিছে"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"ডিভাইচ সম্পৰ্কীয় নীতিয়ে স্ক্ৰীন কেপশ্বাৰ কৰাটো অক্ষম কৰিছে"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"আটাইবোৰ মচক"</string>
diff --git a/packages/SystemUI/res/values-az/strings.xml b/packages/SystemUI/res/values-az/strings.xml
index 1c14b04e..ba87178 100644
--- a/packages/SystemUI/res/values-az/strings.xml
+++ b/packages/SystemUI/res/values-az/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Baxmaq üçün toxunun"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Ekran çəkimini yadda saxlayarkən xəta oldu"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Ekranın yazılması ilə bağlı xəta"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Tam ekrana baxış"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Çıxmaq üçün yuxarıdan aşağı sürüşdürün."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Anladım"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Geri"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Ana səhifə"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Menyu"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Paylaşım, qeydəalma və ya yayım zamanı Android-in ekranda görünən, yaxud cihazda oxudulan məlumatlara girişi olur. Parol, ödəniş detalları, mesaj, foto, habelə audio və video kimi məlumatlarla bağlı diqqətli olun."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Tətbiq paylaşdıqda, qeydə aldıqda və ya yayımladıqda Android-in həmin tətbiqdə göstərilən, yaxud oxudulan məlumatlara girişi olur. Parol, ödəniş detalları, mesaj, foto, habelə audio və video kimi məlumatlarla bağlı diqqətli olun."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Başlayın"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"İT admininiz tərəfindən bloklanıb"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Ekran çəkimi cihaz siyasəti ilə deaktiv edilib"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Hamısını silin"</string>
diff --git a/packages/SystemUI/res/values-b+sr+Latn/strings.xml b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
index c00cd6a..258ae1d 100644
--- a/packages/SystemUI/res/values-b+sr+Latn/strings.xml
+++ b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Dodirnite da biste pregledali"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Greška pri čuvanju snimka ekrana"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Greška pri pokretanju snimanja ekrana"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Prikazuje se ceo ekran"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Da biste izašli, prevucite nadole odozgo."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Važi"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Nazad"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Početna"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Meni"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Kada delite, snimate ili prebacujete, Android ima pristup kompletnom sadržaju koji je vidljiv na ekranu ili se pušta na uređaju. Zato budite pažljivi sa lozinkama, informacijama o plaćanju, porukama, slikama i audio i video snimcima."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Kada delite, snimate ili prebacujete aplikaciju, Android ima pristup kompletnom sadržaju koji je vidljiv ili se pušta u toj aplikaciji. Zato budite pažljivi sa lozinkama, informacijama o plaćanju, porukama, slikama i audio i video snimcima."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Pokreni"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blokira IT administrator"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Snimanje ekrana je onemogućeno smernicama za uređaj"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Obriši sve"</string>
diff --git a/packages/SystemUI/res/values-be/strings.xml b/packages/SystemUI/res/values-be/strings.xml
index 95eebad..5f9d63c 100644
--- a/packages/SystemUI/res/values-be/strings.xml
+++ b/packages/SystemUI/res/values-be/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Націсніце для прагляду"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Памылка захавання запісу экрана"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Памылка пачатку запісу экрана"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Прагляд у поўнаэкранным рэжыме"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Для выхаду правядзіце зверху ўніз."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Зразумела"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Назад"</string>
<string name="accessibility_home" msgid="5430449841237966217">"На Галоўную старонку"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Меню"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Калі адбываецца абагульванне, запіс ці трансляцыя, Android мае доступ да ўсяго змесціва, якое паказваецца на экране ці прайграецца на прыладзе. Таму прадухіліце паказ пароляў, плацежных рэквізітаў, паведамленняў, фота, відэа і аўдыя."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Калі адбываецца абагульванне, запіс ці трансляцыя змесціва праграмы, Android мае доступ да ўсяго змесціва, якое паказваецца ці прайграецца ў праграме. Таму прадухіліце паказ пароляў, плацежных рэквізітаў, паведамленняў, фота, відэа і аўдыя."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Пачаць"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Заблакіравана вашым ІТ-адміністратарам"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Здыманне экрана адключана згодна з палітыкай прылады"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Ачысціць усё"</string>
diff --git a/packages/SystemUI/res/values-bg/strings.xml b/packages/SystemUI/res/values-bg/strings.xml
index 567a22b..f2a8dbf 100644
--- a/packages/SystemUI/res/values-bg/strings.xml
+++ b/packages/SystemUI/res/values-bg/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Докоснете за преглед"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Грешка при запазването на записа на екрана"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"При стартирането на записа на екрана възникна грешка"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Изглед на цял екран"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"За изход плъзнете пръст надолу от горната част."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Разбрах"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Назад"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Начало"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Меню"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Когато споделяте, записвате или предавате, Android има достъп до всичко, което се вижда на екрана ви или се възпроизвежда на устройството ви. Затова бъдете внимателни с неща като пароли, подробности за начини на плащане, съобщения, снимки, аудио и видео."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Когато споделяте, записвате или предавате дадено приложение, Android има достъп до всичко, което се показва или възпроизвежда в него. Затова бъдете внимателни с неща като пароли, подробности за начини на плащане, съобщения, снимки, аудио и видео."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Стартиране"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Блокирано от системния ви администратор"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Заснемането на екрана е деактивирано от правило за устройството"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Изчистване на всички"</string>
diff --git a/packages/SystemUI/res/values-bn/strings.xml b/packages/SystemUI/res/values-bn/strings.xml
index 38308e2..836b0ed 100644
--- a/packages/SystemUI/res/values-bn/strings.xml
+++ b/packages/SystemUI/res/values-bn/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"দেখতে ট্যাপ করুন"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"স্ক্রিন রেকর্ডিং সেভ করার সময় কোনও সমস্যা হয়েছে"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"স্ক্রিন রেকর্ডিং শুরু করার সময় সমস্যা হয়েছে"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"ফুল-স্ক্রিনে দেখা হচ্ছে"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"বেরিয়ে যেতে উপর থেকে নিচের দিকে সোয়াইপ করুন।"</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"বুঝেছি"</string>
<string name="accessibility_back" msgid="6530104400086152611">"ফিরুন"</string>
<string name="accessibility_home" msgid="5430449841237966217">"হোম"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"মেনু"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"আপনি শেয়ার, রেকর্ড বা কাস্ট করার সময়, স্ক্রিনে দৃশ্যমান বা ডিভাইসে চালানো সব কিছুই Android অ্যাক্সেস করতে পারবে। তাই পাসওয়ার্ড, পেমেন্টের বিবরণ, মেসেজ, ফটো এবং অডিও ও ভিডিওর মতো বিষয়ে সতর্ক থাকুন।"</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"আপনি কোনও অ্যাপ শেয়ার, রেকর্ড বা কাস্ট করার সময়, সেই অ্যাপে দেখা যায় বা চালানো হয় এমন সব কিছু Android অ্যাক্সেস করতে পারবে। তাই পাসওয়ার্ড, পেমেন্টের বিবরণ, মেসেজ, ফটো এবং অডিও ও ভিডিওর মতো বিষয়ে সতর্ক থাকুন।"</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"শুরু করুন"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"আপনার আইটি অ্যাডমিন ব্লক করেছেন"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"ডিভাইস নীতির কারণে স্ক্রিন ক্যাপচার করার প্রসেস বন্ধ করা আছে"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"সব মুছে দিন"</string>
diff --git a/packages/SystemUI/res/values-bs/strings.xml b/packages/SystemUI/res/values-bs/strings.xml
index eaa0d9d..db902ef 100644
--- a/packages/SystemUI/res/values-bs/strings.xml
+++ b/packages/SystemUI/res/values-bs/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Dodirnite da vidite"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Greška prilikom pohranjivanja snimka ekrana"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Greška pri pokretanju snimanja ekrana"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Prikazuje se cijeli ekran"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Da izađete, prevucite odozgo nadolje."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Razumijem"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Nazad"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Dugme za početnu stranicu"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Dugme Meni"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Kada dijelite, snimate ili emitirate, Android ima pristup svemu što je vidljivo na ekranu ili što se reproducira na uređaju. Stoga budite oprezni s informacijama kao što su lozinke, podaci o plaćanju, poruke, fotografije, zvukovi i videozapisi."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Kada dijelite, snimate ili emitirate aplikaciju, Android ima pristup svemu što se prikazuje ili reproducira u toj aplikaciji. Stoga budite oprezni s informacijama kao što su lozinke, podaci o plaćanju, poruke, fotografije, zvukovi i videozapisi."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Pokreni"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blokirao je vaš IT administrator"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Snimanje ekrana je onemogućeno pravilima uređaja"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Očisti sve"</string>
diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml
index 6416543..7eefa4f 100644
--- a/packages/SystemUI/res/values-ca/strings.xml
+++ b/packages/SystemUI/res/values-ca/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Toca per veure-la"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"S\'ha produït un error en desar la gravació de la pantalla"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"S\'ha produït un error en iniciar la gravació de pantalla"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Mode de pantalla completa"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Per sortir, llisca cap avall des de la part superior."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Entesos"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Enrere"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Inici"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Menú"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Quan comparteixes, graves o emets contingut, Android té accés a qualsevol cosa que es vegi a la pantalla o que es reprodueixi al dispositiu. Per aquest motiu, ves amb compte amb les contrasenyes, les dades de pagament, els missatges, les fotos i l\'àudio i el vídeo."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Quan comparteixes, graves o emets contingut, Android té accés a qualsevol cosa que es vegi a la pantalla o que es reprodueixi a l\'aplicació. Per aquest motiu, ves amb compte amb les contrasenyes, les dades de pagament, els missatges, les fotos i l\'àudio i el vídeo."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Inicia"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Bloquejat per l\'administrador de TI"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Les captures de pantalla estan desactivades per la política de dispositius"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Esborra-ho tot"</string>
diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml
index 56ae4b8..4a070fb 100644
--- a/packages/SystemUI/res/values-cs/strings.xml
+++ b/packages/SystemUI/res/values-cs/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Klepnutím nahrávku zobrazíte"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Při ukládání záznamu obrazovky došlo k chybě"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Při spouštění nahrávání obrazovky došlo k chybě"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Zobrazení celé obrazovky"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Režim ukončíte přejetím prstem shora dolů."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Rozumím"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Zpět"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Domů"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Menu"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Během sdílení, nahrávání nebo odesílání má Android přístup k veškerému obsahu, který je viditelný na obrazovce nebo se přehrává v zařízení. Buďte proto opatrní s věcmi, jako jsou hesla, platební údaje, zprávy, fotografie, zvuk a video."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Během sdílení, nahrávání nebo odesílání aplikace má Android přístup k veškerému obsahu, který je v dané aplikaci zobrazen nebo přehráván. Buďte proto opatrní s věcmi, jako jsou hesla, platební údaje, zprávy, fotografie, zvuk a video."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Začít"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blokováno administrátorem IT"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Záznam obrazovky je zakázán zásadami zařízení"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Smazat vše"</string>
diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml
index 0ce9932..2893b1e 100644
--- a/packages/SystemUI/res/values-da/strings.xml
+++ b/packages/SystemUI/res/values-da/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Tryk for at se"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Skærmoptagelsen kunne ikke gemmes"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Skærmoptagelsen kunne ikke startes"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Visning i fuld skærm"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Stryg ned fra toppen for at afslutte."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"OK, det er forstået"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Tilbage"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Hjem"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Menu"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Når du deler, optager eller caster, har Android adgang til alt, der er synligt på din skærm eller afspilles på din enhed. Vær derfor forsigtig med ting såsom adgangskoder, betalingsoplysninger, beskeder, billeder, lyd og video."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Når du deler, optager eller caster en app, har Android adgang til alt, der vises eller afspilles i den pågældende app. Vær derfor forsigtig med ting såsom adgangskoder, betalingsoplysninger, beskeder, billeder, lyd og video."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Start"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blokeret af din it-administrator"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Screenshots er deaktiveret af enhedspolitikken"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Ryd alle"</string>
diff --git a/packages/SystemUI/res/values-de/strings.xml b/packages/SystemUI/res/values-de/strings.xml
index ef76528..b527541 100644
--- a/packages/SystemUI/res/values-de/strings.xml
+++ b/packages/SystemUI/res/values-de/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Zum Ansehen tippen"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Fehler beim Speichern der Bildschirmaufzeichnung"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Fehler beim Start der Bildschirmaufzeichnung"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Vollbildmodus wird aktiviert"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Zum Beenden von oben nach unten wischen"</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Ok"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Zurück"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Startbildschirm"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Menü"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Beim Teilen, Aufnehmen oder Streamen hat Android Zugriff auf alle Inhalte, die auf deinem Bildschirm sichtbar sind oder von deinem Gerät wiedergegeben werden. Sei also vorsichtig mit Informationen wie Passwörtern, Zahlungsdetails, Nachrichten, Fotos sowie Audio- und Videoinhalten."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Beim Teilen, Aufnehmen oder Streamen einer App hat Android Zugriff auf alle Inhalte, die in dieser App sichtbar sind oder von ihr wiedergegeben werden. Sei also vorsichtig mit Informationen wie Passwörtern, Zahlungsdetails, Nachrichten, Fotos sowie Audio- und Videoinhalten."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Starten"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Vom IT-Administrator blockiert"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Bildschirmaufnahme ist durch die Geräterichtlinien deaktiviert"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Alle löschen"</string>
diff --git a/packages/SystemUI/res/values-el/strings.xml b/packages/SystemUI/res/values-el/strings.xml
index 2c4b332..e23cbb8 100644
--- a/packages/SystemUI/res/values-el/strings.xml
+++ b/packages/SystemUI/res/values-el/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Πατήστε για προβολή"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Σφάλμα κατά την αποθήκευση της εγγραφής οθόνης"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Σφάλμα κατά την έναρξη της εγγραφής οθόνης"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Προβολή σε πλήρη οθόνη"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Για έξοδο, σύρετε προς τα κάτω από το επάνω μέρος."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Το κατάλαβα"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Πίσω"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Αρχική οθόνη"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Μενού"</string>
@@ -412,6 +415,10 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Όταν κάνετε κοινή χρήση, εγγραφή ή μετάδοση, το Android έχει πρόσβαση σε οτιδήποτε είναι ορατό στην οθόνη σας ή αναπαράγεται στη συσκευή σας. Επομένως, να είστε προσεκτικοί με τους κωδικούς πρόσβασης, τα στοιχεία πληρωμής, τα μηνύματα, τις φωτογραφίες, τον ήχο και το βίντεο."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Όταν κάνετε κοινή χρήση, εγγραφή ή μετάδοση μιας εφαρμογής, το Android έχει πρόσβαση σε οτιδήποτε είναι ορατό ή αναπαράγεται στη συγκεκριμένη εφαρμογή. Επομένως, να είστε προσεκτικοί με τους κωδικούς πρόσβασης, τα στοιχεία πληρωμής, τα μηνύματα, τις φωτογραφίες, τον ήχο και το βίντεο."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Έναρξη"</string>
+ <string name="media_projection_task_switcher_text" msgid="590885489897412359">"Η κοινοποίηση τίθεται σε παύση κατά την εναλλαγή μεταξύ εφαρμογών"</string>
+ <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"Εναλλακτικά, κοινοποιήστε την εφαρμογή"</string>
+ <string name="media_projection_task_switcher_action_back" msgid="5324164224147845282">"Επιστροφή"</string>
+ <string name="media_projection_task_switcher_notification_channel" msgid="7613206306777814253">"Εναλλαγή μεταξύ εφαρμογών"</string>
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Αποκλείστηκε από τον διαχειριστή IT"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Η καταγραφή οθόνης έχει απενεργοποιηθεί από την πολιτική χρήσης συσκευής."</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Διαγραφή όλων"</string>
diff --git a/packages/SystemUI/res/values-en-rAU/strings.xml b/packages/SystemUI/res/values-en-rAU/strings.xml
index 03d2a51..8098739 100644
--- a/packages/SystemUI/res/values-en-rAU/strings.xml
+++ b/packages/SystemUI/res/values-en-rAU/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Tap to view"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Error saving screen recording"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Error starting screen recording"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Viewing full screen"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"To exit, swipe down from the top."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Got it"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Back"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Home"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Menu"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"When you’re sharing, recording or casting, Android has access to anything visible on your screen or played on your device. So be careful with things like passwords, payment details, messages, photos, audio and video."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"When you’re sharing, recording or casting an app, Android has access to anything shown or played on that app. So be careful with things like passwords, payment details, messages, photos, audio and video."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Start"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blocked by your IT admin"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Screen capturing is disabled by device policy"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Clear all"</string>
diff --git a/packages/SystemUI/res/values-en-rCA/strings.xml b/packages/SystemUI/res/values-en-rCA/strings.xml
index f328508..d208382 100644
--- a/packages/SystemUI/res/values-en-rCA/strings.xml
+++ b/packages/SystemUI/res/values-en-rCA/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Tap to view"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Error saving screen recording"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Error starting screen recording"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Viewing full screen"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"To exit, swipe down from the top."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Got it"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Back"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Home"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Menu"</string>
@@ -412,6 +415,10 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"When you’re sharing, recording, or casting, Android has access to anything visible on your screen or played on your device. So be careful with things like passwords, payment details, messages, photos, and audio and video."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"When you’re sharing, recording, or casting an app, Android has access to anything shown or played on that app. So be careful with things like passwords, payment details, messages, photos, and audio and video."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Start"</string>
+ <string name="media_projection_task_switcher_text" msgid="590885489897412359">"Sharing pauses when you switch apps"</string>
+ <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"Share this app instead"</string>
+ <string name="media_projection_task_switcher_action_back" msgid="5324164224147845282">"Switch back"</string>
+ <string name="media_projection_task_switcher_notification_channel" msgid="7613206306777814253">"App switch"</string>
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blocked by your IT admin"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Screen capturing is disabled by device policy"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Clear all"</string>
diff --git a/packages/SystemUI/res/values-en-rGB/strings.xml b/packages/SystemUI/res/values-en-rGB/strings.xml
index 03d2a51..8098739 100644
--- a/packages/SystemUI/res/values-en-rGB/strings.xml
+++ b/packages/SystemUI/res/values-en-rGB/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Tap to view"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Error saving screen recording"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Error starting screen recording"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Viewing full screen"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"To exit, swipe down from the top."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Got it"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Back"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Home"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Menu"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"When you’re sharing, recording or casting, Android has access to anything visible on your screen or played on your device. So be careful with things like passwords, payment details, messages, photos, audio and video."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"When you’re sharing, recording or casting an app, Android has access to anything shown or played on that app. So be careful with things like passwords, payment details, messages, photos, audio and video."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Start"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blocked by your IT admin"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Screen capturing is disabled by device policy"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Clear all"</string>
diff --git a/packages/SystemUI/res/values-en-rIN/strings.xml b/packages/SystemUI/res/values-en-rIN/strings.xml
index 03d2a51..8098739 100644
--- a/packages/SystemUI/res/values-en-rIN/strings.xml
+++ b/packages/SystemUI/res/values-en-rIN/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Tap to view"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Error saving screen recording"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Error starting screen recording"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Viewing full screen"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"To exit, swipe down from the top."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Got it"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Back"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Home"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Menu"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"When you’re sharing, recording or casting, Android has access to anything visible on your screen or played on your device. So be careful with things like passwords, payment details, messages, photos, audio and video."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"When you’re sharing, recording or casting an app, Android has access to anything shown or played on that app. So be careful with things like passwords, payment details, messages, photos, audio and video."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Start"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blocked by your IT admin"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Screen capturing is disabled by device policy"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Clear all"</string>
diff --git a/packages/SystemUI/res/values-en-rXC/strings.xml b/packages/SystemUI/res/values-en-rXC/strings.xml
index ed958d8..8a321e2 100644
--- a/packages/SystemUI/res/values-en-rXC/strings.xml
+++ b/packages/SystemUI/res/values-en-rXC/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Tap to view"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Error saving screen recording"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Error starting screen recording"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Viewing full screen"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"To exit, swipe down from the top."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Got it"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Back"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Home"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Menu"</string>
@@ -412,6 +415,10 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"When you’re sharing, recording, or casting, Android has access to anything visible on your screen or played on your device. So be careful with things like passwords, payment details, messages, photos, and audio and video."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"When you’re sharing, recording, or casting an app, Android has access to anything shown or played on that app. So be careful with things like passwords, payment details, messages, photos, and audio and video."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Start"</string>
+ <string name="media_projection_task_switcher_text" msgid="590885489897412359">"Sharing pauses when you switch apps"</string>
+ <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"Share this app instead"</string>
+ <string name="media_projection_task_switcher_action_back" msgid="5324164224147845282">"Switch back"</string>
+ <string name="media_projection_task_switcher_notification_channel" msgid="7613206306777814253">"App switch"</string>
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blocked by your IT admin"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Screen capturing is disabled by device policy"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Clear all"</string>
diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml
index 91b6be6..a6edfd5 100644
--- a/packages/SystemUI/res/values-es-rUS/strings.xml
+++ b/packages/SystemUI/res/values-es-rUS/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Presiona para ver"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Se produjo un error al guardar la grabación de pantalla"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Error al iniciar la grabación de pantalla"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Visualización en pantalla completa"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Para salir, desliza el dedo hacia abajo desde la parte superior."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Entendido"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Atrás"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Página principal"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Menú"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Cuando compartas, grabes o transmitas contenido, Android podrá acceder a todo lo que sea visible en la pantalla o que reproduzcas en el dispositivo. Por lo tanto, debes tener cuidado con contraseñas, detalles de pagos, mensajes, fotos, audios y videos."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Cuando compartas, grabes o transmitas una app, Android podrá acceder a todo el contenido que se muestre o que reproduzcas en ella. Por lo tanto, debes tener cuidado con contraseñas, detalles de pagos, mensajes, fotos, audios y videos."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Iniciar"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Bloqueada por tu administrador de TI"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"La captura de pantalla está inhabilitada debido a la política del dispositivo"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Cerrar todo"</string>
diff --git a/packages/SystemUI/res/values-es/strings.xml b/packages/SystemUI/res/values-es/strings.xml
index 178c8b1..2fb76cd 100644
--- a/packages/SystemUI/res/values-es/strings.xml
+++ b/packages/SystemUI/res/values-es/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Toca para verla"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"No se ha podido guardar la grabación de pantalla"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"No se ha podido empezar a grabar la pantalla"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Modo de pantalla completa"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Para salir, desliza el dedo de arriba abajo."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Entendido"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Atrás"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Inicio"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Menú"</string>
@@ -412,6 +415,10 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Cuando compartes, grabas o envías contenido, Android puede acceder a todo lo que se muestre en la pantalla o se reproduzca en tu dispositivo. Debes tener cuidado con elementos como contraseñas, detalles de pagos, mensajes, fotos, audio y vídeo."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Cuando compartes, grabas o envías una aplicación, Android puede acceder a todo lo que se muestre o se reproduzca en ella. Debes tener cuidado con elementos como contraseñas, detalles de pagos, mensajes, fotos, audio y vídeo."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Empezar"</string>
+ <string name="media_projection_task_switcher_text" msgid="590885489897412359">"El uso compartido se detiene al cambiar de aplicación"</string>
+ <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"Compartir esta aplicación"</string>
+ <string name="media_projection_task_switcher_action_back" msgid="5324164224147845282">"Volver"</string>
+ <string name="media_projection_task_switcher_notification_channel" msgid="7613206306777814253">"Cambio de aplicación"</string>
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Bloqueado por tu administrador de TI"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Las capturas de pantalla están inhabilitadas debido a la política de dispositivos"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Borrar todo"</string>
@@ -1030,7 +1037,7 @@
<string name="status_before_loading" msgid="1500477307859631381">"El contenido se mostrará en breve"</string>
<string name="missed_call" msgid="4228016077700161689">"Llamada perdida"</string>
<string name="messages_count_overflow_indicator" msgid="7850934067082006043">"<xliff:g id="NUMBER">%d</xliff:g>+"</string>
- <string name="people_tile_description" msgid="8154966188085545556">"Consulta los mensajes recientes, las llamadas perdidas y los cambios de estado"</string>
+ <string name="people_tile_description" msgid="8154966188085545556">"Consulta mensajes recientes, llamadas perdidas y cambios de estado"</string>
<string name="people_tile_title" msgid="6589377493334871272">"Conversación"</string>
<string name="paused_by_dnd" msgid="7856941866433556428">"Pausado por No molestar"</string>
<string name="new_notification_text_content_description" msgid="2915029960094389291">"<xliff:g id="NAME">%1$s</xliff:g> ha enviado un mensaje: <xliff:g id="NOTIFICATION">%2$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-et/strings.xml b/packages/SystemUI/res/values-et/strings.xml
index b2470f7..b300e78 100644
--- a/packages/SystemUI/res/values-et/strings.xml
+++ b/packages/SystemUI/res/values-et/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Puudutage kuvamiseks"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Viga ekraanisalvestise salvestamisel"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Viga ekraanikuva salvestamise alustamisel"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Kuvamine täisekraanil"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Väljumiseks pühkige ülevalt alla."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Selge"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Tagasi"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Kodu"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Menüü"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Kui jagate, salvestate või kannate üle, on Androidil juurdepääs kõigele, mis on teie ekraanikuval nähtaval või mida teie seadmes esitatakse. Seega olge ettevaatlik selliste andmetega nagu paroolid, makseteave, sõnumid, fotod ning heli ja video."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Kui jagate, salvestate või kannate rakendust üle, on Androidil juurdepääs kõigele, mida selles rakenduses kuvatakse või esitatakse. Seega olge paroolide, makseteabe, sõnumite, fotode, heli ja videoga ettevaatlik."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Alusta"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blokeeris teie IT-administraator"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Ekraanikuva jäädvustamine on seadmereeglitega keelatud"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Tühjenda kõik"</string>
diff --git a/packages/SystemUI/res/values-eu/strings.xml b/packages/SystemUI/res/values-eu/strings.xml
index 33fee8d..8fd391c 100644
--- a/packages/SystemUI/res/values-eu/strings.xml
+++ b/packages/SystemUI/res/values-eu/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Sakatu ikusteko"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Errore bat gertatu da pantaila-grabaketa gordetzean"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Errore bat gertatu da pantaila grabatzen hastean"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Pantaila osoko ikuspegia"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Irteteko, pasatu hatza goitik behera."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Ados"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Atzera"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Hasiera"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Menua"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Edukia partekatzen, grabatzen edo igortzen ari zarenean, pantailan ikusgai dagoen edo gailuan erreproduzitzen ari den guztia atzi dezake Android-ek. Beraz, kontuz ibili pasahitzekin, ordainketen xehetasunekin, mezuekin, argazkiekin, audioekin eta bideoekin, besteak beste."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Aplikazio bat partekatzen, grabatzen edo igortzen ari zarenean, aplikazio horretan ikusgai dagoen edo bertan erreproduzitzen ari den guztia atzi dezake Android-ek. Beraz, kontuz ibili pasahitzekin, ordainketen xehetasunekin, mezuekin, argazkiekin, audioekin eta bideoekin, besteak beste."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Hasi"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"IKT saileko administratzaileak blokeatu du"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Pantaila-kapturak egiteko aukera desgaituta dago, gailu-gidalerroei jarraikiz"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Garbitu guztiak"</string>
diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml
index 97f6930..4d3b3f0 100644
--- a/packages/SystemUI/res/values-fa/strings.xml
+++ b/packages/SystemUI/res/values-fa/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"برای مشاهده ضربه بزنید"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"خطا در ذخیرهسازی ضبط صفحهنمایش"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"خطا هنگام شروع ضبط صفحهنمایش"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"درحال مشاهده در حالت تمامصفحه"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"برای خروج، از بالای صفحه تند بهپایین بکشید."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"متوجهام"</string>
<string name="accessibility_back" msgid="6530104400086152611">"برگشت"</string>
<string name="accessibility_home" msgid="5430449841237966217">"صفحهٔ اصلی"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"منو"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"وقتی درحال همرسانی، ضبط، یا پخش محتوا هستید، Android به همه محتوایی که در صفحهتان نمایان است یا در دستگاهتان پخش میشود دسترسی دارد. درنتیجه مراقب چیزهایی مثل گذرواژهها، جزئیات پرداخت، پیامها، عکسها، و صدا و تصویر باشید."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"وقتی درحال همرسانی، ضبط، یا پخش محتوای برنامهای هستید، Android به همه محتوایی که در آن برنامه نمایان است یا پخش میشود دسترسی دارد. درنتیجه مراقب چیزهایی مثل گذرواژهها، جزئیات پرداخت، پیامها، عکسها، و صدا و تصویر باشید."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"شروع"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"سرپرست فناوری اطلاعات آن را مسدود کرده است"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"«ضبط صفحهنمایش» بهدلیل خطمشی دستگاه غیرفعال است"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"پاک کردن همه موارد"</string>
diff --git a/packages/SystemUI/res/values-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml
index abf52fe..f3b95de 100644
--- a/packages/SystemUI/res/values-fi/strings.xml
+++ b/packages/SystemUI/res/values-fi/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Napauta näyttääksesi"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Virhe näyttötallenteen tallentamisessa"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Virhe näytön tallennuksen aloituksessa"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Koko näytön tilassa"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Sulje palkki pyyhkäisemällä alas ruudun ylälaidasta."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Selvä"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Takaisin"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Aloitus"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Valikko"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Kun jaat, tallennat tai striimaat, Android saa pääsyn kaikkeen näytölläsi näkyvään tai laitteellasi toistettuun sisältöön. Ole siis varovainen, kun lisäät salasanoja, maksutietoja, viestejä, kuvia, audiota tai videoita."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Kun jaat, tallennat tai striimaat, Android saa pääsyn kaikkeen sovelluksella näkyvään tai toistettuun sisältöön. Ole siis varovainen, kun lisäät salasanoja, maksutietoja, viestejä, kuvia, audiota tai videoita."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Aloita"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"IT-järjestelmänvalvojasi estämä"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Kuvakaappaus on poistettu käytöstä laitekäytännön perusteella"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Tyhjennä kaikki"</string>
diff --git a/packages/SystemUI/res/values-fr-rCA/strings.xml b/packages/SystemUI/res/values-fr-rCA/strings.xml
index 4f94d1f..8a74f8a 100644
--- a/packages/SystemUI/res/values-fr-rCA/strings.xml
+++ b/packages/SystemUI/res/values-fr-rCA/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Touchez pour afficher"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Erreur d\'enregistrement de l\'écran"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Une erreur s\'est produite lors du démarrage de l\'enregistrement d\'écran"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Affichage plein écran"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Pour quitter, balayez vers le bas à partir du haut."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"OK"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Précédent"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Domicile"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Menu"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Lorsque vous partagez, enregistrez ou diffusez, Android a accès à tout ce qui est visible sur votre écran ou lu sur votre appareil. Par conséquent, soyez prudent avec les mots de passe, les détails du paiement, les messages, les photos et les contenus audio et vidéo."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Lorsque vous partagez, enregistrez ou diffusez une application, Android a accès à tout ce qui est visible sur votre écran ou lu sur cette application. Par conséquent, soyez prudent avec les mots de passe, les détails du paiement, les messages, les photos et les contenus audio et vidéo."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Commencer"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Bloquée par votre administrateur informatique"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"La fonctionnalité de capture d\'écran est désactivée par l\'application Device Policy"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Tout effacer"</string>
diff --git a/packages/SystemUI/res/values-fr/strings.xml b/packages/SystemUI/res/values-fr/strings.xml
index 0ede09a..5fe8f55 100644
--- a/packages/SystemUI/res/values-fr/strings.xml
+++ b/packages/SystemUI/res/values-fr/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Appuyez pour afficher"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Erreur lors de l\'enregistrement de l\'écran"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Erreur lors du démarrage de l\'enregistrement de l\'écran"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Affichage en plein écran"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Pour quitter, balayez l\'écran du haut vers le bas."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"OK"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Retour"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Accueil"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Menu"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Lorsque vous partagez, enregistrez ou castez, Android a accès à tout ce qui est visible sur votre écran ou lu sur votre appareil. Faites donc attention aux éléments tels que les mots de passe, détails de mode de paiement, messages, photos et contenus audio et vidéo."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Lorsque vous partagez, enregistrez ou castez une appli, Android a accès à tout ce qui est visible sur votre écran ou lu sur votre appareil. Faites donc attention aux éléments tels que les mots de passe, détails de mode de paiement, messages et contenus audio et vidéo."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Commencer"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Bloqué par votre administrateur informatique"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"La capture d\'écran est désactivée conformément aux règles relatives à l\'appareil"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Tout effacer"</string>
diff --git a/packages/SystemUI/res/values-gl/strings.xml b/packages/SystemUI/res/values-gl/strings.xml
index 1446755..804aeca 100644
--- a/packages/SystemUI/res/values-gl/strings.xml
+++ b/packages/SystemUI/res/values-gl/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Toca para ver o contido"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Produciuse un erro ao gardar a gravación da pantalla"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Produciuse un erro ao iniciar a gravación da pantalla"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Vendo pantalla completa"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Para saír, pasa o dedo cara abaixo desde a parte superior."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Entendido"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Volver"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Inicio"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Menú"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Cando compartes, gravas ou emites contido, Android ten acceso a todo o que se vexa na pantalla ou se reproduza no teu dispositivo. Polo tanto, debes ter coidado con determinada información, como os contrasinais, os detalles de pago, as mensaxes e as fotos, así como o contido de audio e de vídeo."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Cando compartes, gravas ou emites unha aplicación, Android ten acceso a todo o que se vexa ou se reproduza nela. Polo tanto, debes ter coidado con determinada información, como os contrasinais, os detalles de pago, as mensaxes e as fotos, así como o contido de audio e de vídeo."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Iniciar"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"O teu administrador de TI bloqueou esta aplicación"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"A política do dispositivo desactivou a opción de capturar a pantalla"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Eliminar todo"</string>
@@ -1040,7 +1051,7 @@
<string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Produciuse un problema ao ler o medidor da batería"</string>
<string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Toca para obter máis información"</string>
<string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Sen alarmas postas"</string>
- <string name="accessibility_bouncer" msgid="5896923685673320070">"introducir bloqueo de pantalla"</string>
+ <string name="accessibility_bouncer" msgid="5896923685673320070">"introducir o bloqueo de pantalla"</string>
<string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Sensor de impresión dixital"</string>
<string name="accessibility_authenticate_hint" msgid="798914151813205721">"autenticar"</string>
<string name="accessibility_enter_hint" msgid="2617864063504824834">"poñer o dispositivo"</string>
diff --git a/packages/SystemUI/res/values-gu/strings.xml b/packages/SystemUI/res/values-gu/strings.xml
index 89390b1..cfd5a36 100644
--- a/packages/SystemUI/res/values-gu/strings.xml
+++ b/packages/SystemUI/res/values-gu/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"જોવા માટે ટૅપ કરો"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"સ્ક્રીન રેકોર્ડિંગ સાચવવામાં ભૂલ આવી"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"સ્ક્રીનને રેકૉર્ડ કરવાનું શરૂ કરવામાં ભૂલ"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"પૂર્ણ સ્ક્રીન પર જુઓ"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"બહાર નીકળવા માટે, ટોચ પરથી નીચે સ્વાઇપ કરો."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"સમજાઈ ગયું"</string>
<string name="accessibility_back" msgid="6530104400086152611">"પાછળ"</string>
<string name="accessibility_home" msgid="5430449841237966217">"હોમ"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"મેનુ"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"જ્યારે તમે શેર, રેકોર્ડ અથવા કાસ્ટ કરી રહ્યાં હો, ત્યારે તમારી સ્ક્રીન પર દેખાતી હોય કે તમારા ડિવાઇસ પર ચલાવવામાં આવતી હોય તેવી બધી વસ્તુનો ઍક્સેસ Android પાસે હોય છે. તેથી પાસવર્ડ, ચુકવણીની વિગતો, મેસેજ, ફોટા અને ડિવાઇસ પર વાગી રહેલા ઑડિયો તથા વીડિયો જેવી બાબતોને લઈને સાવચેત રહો."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"જ્યારે તમે કોઈ ઍપ શેર, રેકોર્ડ અથવા કાસ્ટ કરી રહ્યાં હો, ત્યારે તે ઍપ પર બતાવવામાં કે ચલાવવામાં આવતી હોય તેવી બધી વસ્તુનો ઍક્સેસ Android પાસે હોય છે. તેથી પાસવર્ડ, ચુકવણીની વિગતો, મેસેજ, ફોટા અને ડિવાઇસ પર વાગી રહેલા ઑડિયો તથા વીડિયો જેવી બાબતોને લઈને સાવચેત રહો."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"શરૂ કરો"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"તમારા IT ઍડમિન દ્વારા બ્લૉક કરાયેલી"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"ડિવાઇસ પૉલિસી અનુસાર સ્ક્રીન કૅપ્ચર કરવાની સુવિધા બંધ કરવામાં આવી છે"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"બધુ સાફ કરો"</string>
diff --git a/packages/SystemUI/res/values-h800dp/dimens.xml b/packages/SystemUI/res/values-h800dp/dimens.xml
index 3a71994..829ef98 100644
--- a/packages/SystemUI/res/values-h800dp/dimens.xml
+++ b/packages/SystemUI/res/values-h800dp/dimens.xml
@@ -15,9 +15,6 @@
-->
<resources>
- <!-- With the large clock, move up slightly from the center -->
- <dimen name="keyguard_large_clock_top_margin">-112dp</dimen>
-
<!-- Margin above the ambient indication container -->
<dimen name="ambient_indication_container_margin_top">20dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml
index fb017da..194321a 100644
--- a/packages/SystemUI/res/values-hi/strings.xml
+++ b/packages/SystemUI/res/values-hi/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"देखने के लिए टैप करें"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"स्क्रीन रिकॉर्डिंग सेव करते समय गड़बड़ी हुई"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"स्क्रीन को रिकॉर्ड करने में गड़बड़ी आ रही है"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"फ़ुल स्क्रीन मोड पर देखा जा रहा है"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"बाहर निकलने के लिए, सबसे ऊपर से नीचे की ओर स्वाइप करें."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"ठीक है"</string>
<string name="accessibility_back" msgid="6530104400086152611">"वापस जाएं"</string>
<string name="accessibility_home" msgid="5430449841237966217">"होम"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"मेन्यू"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"शेयर, रिकॉर्ड या कास्ट करते समय, Android के पास स्क्रीन पर दिख रहे कॉन्टेंट या डिवाइस पर चल रहे हर मीडिया का ऐक्सेस होता है. इसलिए, पासवर्ड, पेमेंट के तरीके की जानकारी, मैसेज, फ़ोटो, और डिवाइस पर चल रहे ऑडियो और वीडियो को लेकर सावधानी बरतें."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"किसी ऐप्लिकेशन को शेयर, रिकॉर्ड या कास्ट करते समय, Android के पास उस ऐप्लिकेशन पर दिख रहे कॉन्टेंट या उस पर चल रहे हर मीडिया का ऐक्सेस होता है. इसलिए, पासवर्ड, पेमेंट के तरीके की जानकारी, मैसेज, फ़ोटो, और डिवाइस पर चल रहे ऑडियो और वीडियो को लेकर सावधानी बरतें."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"शुरू करें"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"आपके आईटी एडमिन ने स्क्रीन कैप्चर करने की सुविधा पर रोक लगाई है"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"डिवाइस से जुड़ी नीति के तहत स्क्रीन कैप्चर करने की सुविधा बंद है"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"सभी को हटाएं"</string>
diff --git a/packages/SystemUI/res/values-hr/strings.xml b/packages/SystemUI/res/values-hr/strings.xml
index b7a7cdd..7fbc100 100644
--- a/packages/SystemUI/res/values-hr/strings.xml
+++ b/packages/SystemUI/res/values-hr/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Dodirnite za prikaz"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Pogreška prilikom spremanja snimke zaslona"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Pogreška prilikom pokretanja snimanja zaslona"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Gledanje preko cijelog zaslona"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Za izlaz prijeđite prstom od vrha prema dolje."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Shvaćam"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Natrag"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Početna"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Izbornik"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Kad dijelite, snimate ili emitirate, Android ima pristup svemu što je vidljivo na vašem zaslonu ili se reproducira na vašem uređaju. Stoga pazite na stvari kao što su zaporke, podaci o plaćanju, poruke, fotografije te audio i videozapisi."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Kad dijelite, snimate ili emitirate aplikaciju, Android ima pristup svemu što se prikazuje ili reproducira u toj aplikaciji. Stoga pazite na stvari kao što su zaporke, podaci o plaćanju, poruke, fotografije te audio i videozapisi."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Pokreni"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blokirao vaš IT administrator"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Snimanje zaslona onemogućeno je u skladu s pravilima za uređaje"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Izbriši sve"</string>
diff --git a/packages/SystemUI/res/values-hu/strings.xml b/packages/SystemUI/res/values-hu/strings.xml
index 6a96fcc..1c46c44 100644
--- a/packages/SystemUI/res/values-hu/strings.xml
+++ b/packages/SystemUI/res/values-hu/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Koppintson a megtekintéshez"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Hiba történt a képernyőrögzítés mentése során"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Hiba a képernyőrögzítés indításakor"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Megtekintése teljes képernyőn"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Kilépéshez csúsztassa ujját fentről lefelé."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Értem"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Vissza"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Főoldal"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Menü"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Amikor Ön megosztást, rögzítést vagy átküldést végez, az Android a képernyőn látható vagy az eszközön lejátszott minden tartalomhoz hozzáfér. Ezért legyen elővigyázatos a jelszavakkal, a fizetési adatokkal, az üzenetekkel, a fotókkal, valamint a hang- és videófelvételekkel."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Amikor Ön megoszt, rögzít vagy átküld egy alkalmazást, az Android az adott alkalmazásban látható vagy lejátszott minden tartalomhoz hozzáfér. Ezért legyen elővigyázatos a jelszavakkal, a fizetési adatokkal, az üzenetekkel, a fotókkal, valamint a hang- és videófelvételekkel."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Indítás"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Rendszergazda által letiltva"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"A képernyőfelvételt eszközszabályzat tiltja"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Az összes törlése"</string>
diff --git a/packages/SystemUI/res/values-hy/strings.xml b/packages/SystemUI/res/values-hy/strings.xml
index d91b3dd2..afd8b66 100644
--- a/packages/SystemUI/res/values-hy/strings.xml
+++ b/packages/SystemUI/res/values-hy/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Հպեք՝ դիտելու համար"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Չհաջողվեց պահել էկրանի տեսագրությունը"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Չհաջողվեց սկսել տեսագրումը"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Լիաէկրան դիտում"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Դուրս գալու համար վերևից սահահարվածեք դեպի ներքև:"</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Պարզ է"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Հետ"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Տուն"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Ցանկ"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Երբ դուք ցուցադրում, տեսագրում կամ հեռարձակում եք էկրանը, Android-ին հասանելի է լինում այն ամենը, ինչ տեսանելի է ձեր էկրանին և նվագարկվում է ձեր սարքում։ Ուստի ուշադիր եղեք այնպիսի բաների հետ, ինչպիսիք են գաղտնաբառերը, վճարային տվյալները, հաղորդագրությունները, լուսանկարները, աուդիո և վիդեո բովանդակությունը։"</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Երբ դուք ցուցադրում, տեսագրում կամ հեռարձակում եք որևէ հավելվածի էկրանը, Android-ին հասանելի է լինում այն ամենը, ինչ ցուցադրվում է կամ նվագարկվում այդ հավելվածում։ Ուստի ուշադիր եղեք այնպիսի բաների հետ, ինչպիսիք են գաղտնաբառերը, վճարային տվյալները, հաղորդագրությունները, լուսանկարները, աուդիո և վիդեո բովանդակությունը։"</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Սկսել"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Արգելափակվել է ձեր ՏՏ ադմինիստրատորի կողմից"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Էկրանի տեսագրումն անջատված է սարքի կանոնների համաձայն"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Մաքրել բոլորը"</string>
diff --git a/packages/SystemUI/res/values-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml
index 550b048..f5564ba 100644
--- a/packages/SystemUI/res/values-in/strings.xml
+++ b/packages/SystemUI/res/values-in/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Ketuk untuk melihat"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Terjadi error saat menyimpan rekaman layar"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Terjadi error saat memulai perekaman layar"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Melihat layar penuh"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Untuk keluar, geser layar ke bawah dari atas."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Mengerti"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Kembali"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Utama"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Menu"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Jika Anda membagikan, merekam, atau mentransmisikan, Android akan memiliki akses ke semua hal yang ditampilkan di layar atau yang diputar di perangkat Anda. Jadi, berhati-hatilah saat memasukkan sandi, detail pembayaran, pesan, foto, audio, dan video."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Jika Anda membagikan, merekam, atau mentransmisikan suatu aplikasi, Android akan memiliki akses ke semua hal yang ditampilkan atau yang diputar di aplikasi tersebut. Jadi, berhati-hatilah saat memasukkan sandi, detail pembayaran, pesan, foto, audio, dan video."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Mulai"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Diblokir oleh admin IT Anda"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Pengambilan screenshot dinonaktifkan oleh kebijakan perangkat"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Hapus semua"</string>
diff --git a/packages/SystemUI/res/values-is/strings.xml b/packages/SystemUI/res/values-is/strings.xml
index 86455c0..647d1c4 100644
--- a/packages/SystemUI/res/values-is/strings.xml
+++ b/packages/SystemUI/res/values-is/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Ýttu til að skoða"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Villa við að vista skjáupptöku"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Villa við að hefja upptöku skjás"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Notar allan skjáinn"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Strjúktu niður frá efri brún til að hætta."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Ég skil"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Til baka"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Heim"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Valmynd"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Þegar þú deilir, tekur upp eða varpar hefur Android aðgang að öllu sem sést á skjánum eða spilast í tækinu. Passaðu því upp á aðgangsorð, greiðsluupplýsingar, skilaboð, myndir, hljóð og myndskeið."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Þegar þú deilir, tekur upp eða varpar forriti hefur Android aðgang að öllu sem sést eða spilast í viðkomandi forriti. Passaðu því upp á aðgangsorð, greiðsluupplýsingar, skilaboð, myndir, hljóð og myndskeið."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Byrja"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Útilokað af kerfisstjóra"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Slökkt er á skjáupptöku í tækjareglum"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Hreinsa allt"</string>
diff --git a/packages/SystemUI/res/values-it/strings.xml b/packages/SystemUI/res/values-it/strings.xml
index 17ec328..3b60f42 100644
--- a/packages/SystemUI/res/values-it/strings.xml
+++ b/packages/SystemUI/res/values-it/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Tocca per visualizzare"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Errore durante il salvataggio della registrazione dello schermo"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Errore durante l\'avvio della registrazione dello schermo"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Visualizzazione a schermo intero"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Per uscire, scorri dall\'alto verso il basso."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"OK"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Indietro"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Home"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Menu"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Quando condividi, registri o trasmetti, Android ha accesso a qualsiasi elemento visibile sul tuo schermo o in riproduzione sul tuo dispositivo. Presta quindi attenzione a password, dettagli sui pagamenti, messaggi, foto, audio e video."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Quando condividi, registri o trasmetti un\'app, Android ha accesso a qualsiasi elemento visualizzato o riprodotto sull\'app. Presta quindi attenzione a password, dettagli sui pagamenti, messaggi, foto, audio e video."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Inizia"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Bloccata dall\'amministratore IT"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"L\'acquisizione schermo è disattivata dai criteri relativi ai dispositivi"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Cancella tutto"</string>
@@ -510,14 +521,14 @@
<string name="stream_accessibility" msgid="3873610336741987152">"Accessibilità"</string>
<string name="volume_ringer_status_normal" msgid="1339039682222461143">"Attiva suoneria"</string>
<string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Attiva vibrazione"</string>
- <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Disattiva suoneria"</string>
+ <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Silenzia"</string>
<string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Tocca per riattivare l\'audio."</string>
<string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Tocca per attivare la vibrazione. L\'audio dei servizi di accessibilità può essere disattivato."</string>
<string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Tocca per disattivare l\'audio. L\'audio dei servizi di accessibilità può essere disattivato."</string>
<string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Tocca per attivare la vibrazione."</string>
<string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Tocca per disattivare l\'audio."</string>
<string name="volume_ringer_change" msgid="3574969197796055532">"Tocca per cambiare la modalità della suoneria"</string>
- <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"disattiva l\'audio"</string>
+ <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"silenzia"</string>
<string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"riattiva l\'audio"</string>
<string name="volume_ringer_hint_vibrate" msgid="6211609047099337509">"vibrazione"</string>
<string name="volume_dialog_title" msgid="6502703403483577940">"Controlli del volume %s"</string>
diff --git a/packages/SystemUI/res/values-iw/strings.xml b/packages/SystemUI/res/values-iw/strings.xml
index 5b317cb..121e5be 100644
--- a/packages/SystemUI/res/values-iw/strings.xml
+++ b/packages/SystemUI/res/values-iw/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"יש להקיש כדי להציג"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"שגיאה בשמירה של הקלטת המסך"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"שגיאה בהפעלה של הקלטת המסך"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"צפייה במסך מלא"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"כדי לצאת, פשוט מחליקים אצבע מלמעלה למטה."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"הבנתי"</string>
<string name="accessibility_back" msgid="6530104400086152611">"חזרה"</string>
<string name="accessibility_home" msgid="5430449841237966217">"בית"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"תפריט"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"בזמן שיתוף, הקלטה או העברה (cast) תהיה ל-Android גישה לכל הפרטים שגלויים במסך שלך או מופעלים מהמכשיר שלך. מומלץ להיזהר עם סיסמאות, פרטי תשלום, הודעות, תמונות, אודיו וסרטונים."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"בזמן שיתוף, הקלטה או העברה (cast) של אפליקציה, תהיה ל-Android גישה לכל מה שגלוי באפליקציה או מופעל מהאפליקציה. מומלץ להיזהר עם סיסמאות, פרטי תשלום, הודעות, תמונות, אודיו וסרטונים."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"התחלה"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"האפשרות נחסמה על ידי אדמין ב-IT"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"צילום המסך מושבת בגלל מדיניות המכשיר"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"ניקוי הכול"</string>
diff --git a/packages/SystemUI/res/values-ja/strings.xml b/packages/SystemUI/res/values-ja/strings.xml
index 56a78e2..383d9f4 100644
--- a/packages/SystemUI/res/values-ja/strings.xml
+++ b/packages/SystemUI/res/values-ja/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"タップすると表示されます"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"画面の録画の保存中にエラーが発生しました"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"画面の録画中にエラーが発生しました"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"全画面表示"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"終了するには、上から下にスワイプします。"</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"OK"</string>
<string name="accessibility_back" msgid="6530104400086152611">"戻る"</string>
<string name="accessibility_home" msgid="5430449841237966217">"ホーム"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"メニュー"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"共有、録画、キャスト中は、画面に表示される内容やデバイスで再生される内容に Android がアクセスできるため、パスワード、お支払いの詳細、メッセージ、写真、音声、動画などの情報にご注意ください。"</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"アプリの共有、録画、キャスト中は、そのアプリで表示または再生される内容に Android がアクセスできるため、パスワード、お支払いの詳細、メッセージ、写真、音声、動画などの情報にご注意ください。"</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"開始"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"IT 管理者によりブロックされました"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"デバイス ポリシーに基づき、画面のキャプチャが無効になりました"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"すべて消去"</string>
diff --git a/packages/SystemUI/res/values-ka/strings.xml b/packages/SystemUI/res/values-ka/strings.xml
index f19b6b6..943b2885 100644
--- a/packages/SystemUI/res/values-ka/strings.xml
+++ b/packages/SystemUI/res/values-ka/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"შეეხეთ სანახავად"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"ეკრანის ჩანაწერის შენახვისას შეცდომა მოხდა"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"ეკრანის ჩაწერის დაწყებისას წარმოიქმნა შეცდომა"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"მიმდინარეობს სრულ ეკრანზე ნახვა"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"გასვლისთვის გადაფურცლეთ ზემოდან ქვემოთ."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"გასაგებია"</string>
<string name="accessibility_back" msgid="6530104400086152611">"უკან"</string>
<string name="accessibility_home" msgid="5430449841237966217">"საწყისი"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"მენიუ"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"გაზიარებისას, ჩაწერისას ან ტრანსლირებისას, Android-ს წვდომა აქვს ყველაფერზე, რაც ჩანს თქვენს ეკრანზე ან უკრავს თქვენს მოწყობილობაზე. ამიტომ იყავით ფრთხილად ისეთ ინფორმაციასთან, როგორიცაა პაროლები, გადახდის დეტალები, შეტყობინებები, ფოტოები, აუდიო და ვიდეო."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"აპის გაზიარებისას, ჩაწერისას ან ტრანსლირებისას, Android-ს წვდომა აქვს ყველაფერზე, რაც ჩანს ან იკვრება აპში. ამიტომ იყავით ფრთხილად ისეთ ინფორმაციასთან, როგორიცაა პაროლები, გადახდის დეტალები, შეტყობინებები, ფოტოები, აუდიო და ვიდეო."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"დაწყება"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"დაბლოკილია თქვენი IT-ადმინისტრატორის მიერ"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"ეკრანის აღბეჭდვა გამორთულია მოწყობილობის წესების თანახმად"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"ყველას გასუფთავება"</string>
diff --git a/packages/SystemUI/res/values-kk/strings.xml b/packages/SystemUI/res/values-kk/strings.xml
index c7d30e7..a80dcfb 100644
--- a/packages/SystemUI/res/values-kk/strings.xml
+++ b/packages/SystemUI/res/values-kk/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Көру үшін түртіңіз."</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Экран жазбасын сақтау кезінде қате шықты."</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Экрандағы бейнені жазу кезінде қате шықты."</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Толық экранда көру"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Шығу үшін жоғарыдан төмен қарай сырғытыңыз."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Түсінікті"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Артқа"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Үй"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Mәзір"</string>
@@ -301,8 +304,8 @@
<string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Күн шыққанға дейін"</string>
<string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"Қосылу уақыты: <xliff:g id="TIME">%s</xliff:g>"</string>
<string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"<xliff:g id="TIME">%s</xliff:g> дейін"</string>
- <string name="quick_settings_dark_mode_secondary_label_on_at_bedtime" msgid="2274300599408864897">"Ұйқы уақытында"</string>
- <string name="quick_settings_dark_mode_secondary_label_until_bedtime_ends" msgid="1790772410777123685">"Ұйқы уақыты аяқталғанға дейін"</string>
+ <string name="quick_settings_dark_mode_secondary_label_on_at_bedtime" msgid="2274300599408864897">"Ұйқы режимінде"</string>
+ <string name="quick_settings_dark_mode_secondary_label_until_bedtime_ends" msgid="1790772410777123685">"Ұйқы режимі аяқталғанға дейін"</string>
<string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
<string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC өшірулі"</string>
<string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC қосулы"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Бөлісу, жазу не трансляциялау кезінде Android жүйесі экраныңызда көрінетін не құрылғыңызда ойнатылатын барлық нәрсені пайдалана алады. Сондықтан құпия сөздерді, төлем туралы мәліметті, хабарларды немесе басқа құпия ақпаратты енгізген кезде сақ болыңыз."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Қолданба экранын бөлісу, жазу не трансляциялау кезінде Android жүйесі онда көрінетін не ойнатылатын барлық нәрсені пайдалана алады. Сондықтан құпия сөздерді, төлем туралы мәліметті, хабарларды немесе басқа құпия ақпаратты енгізген кезде сақ болыңыз."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Бастау"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Әкімшіңіз бөгеген"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Құрылғы саясатына байланысты экранды түсіру өшірілді."</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Барлығын тазарту"</string>
diff --git a/packages/SystemUI/res/values-km/strings.xml b/packages/SystemUI/res/values-km/strings.xml
index 375b79d..702b9cb 100644
--- a/packages/SystemUI/res/values-km/strings.xml
+++ b/packages/SystemUI/res/values-km/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"ចុចដើម្បីមើល"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"មានបញ្ហាក្នុងការរក្សាទុកការថតវីដេអូអេក្រង់"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"មានបញ្ហាក្នុងការចាប់ផ្ដើមថតអេក្រង់"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"កំពុងមើលពេញអេក្រង់"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"ដើម្បីចាកចេញ សូមអូសពីលើចុះក្រោម។"</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"យល់ហើយ"</string>
<string name="accessibility_back" msgid="6530104400086152611">"ថយក្រោយ"</string>
<string name="accessibility_home" msgid="5430449841237966217">"គេហទំព័រ"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"ម៉ឺនុយ"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"នៅពេលអ្នកកំពុងចែករំលែក ថត ឬភ្ជាប់, Android មានសិទ្ធិចូលប្រើអ្វីៗដែលអាចមើលឃើញនៅលើអេក្រង់របស់អ្នក ឬចាក់នៅលើឧបករណ៍របស់អ្នក។ ដូច្នេះ សូមប្រុងប្រយ័ត្នចំពោះអ្វីៗដូចជា ពាក្យសម្ងាត់ ព័ត៌មានលម្អិតអំពីការទូទាត់ប្រាក់ សារ រូបថត ព្រមទាំងសំឡេង និងវីដេអូ។"</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"នៅពេលអ្នកកំពុងចែករំលែក ថត ឬភ្ជាប់កម្មវិធី, Android មានសិទ្ធិចូលប្រើអ្វីៗដែលបង្ហាញ ឬចាក់នៅលើកម្មវិធីនោះ។ ដូច្នេះ សូមប្រុងប្រយ័ត្នចំពោះអ្វីៗដូចជា ពាក្យសម្ងាត់ ព័ត៌មានលម្អិតអំពីការទូទាត់ប្រាក់ សារ រូបថត ព្រមទាំងសំឡេង និងវីដេអូ។"</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"ចាប់ផ្ដើម"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"បានទប់ស្កាត់ដោយអ្នកគ្រប់គ្រងផ្នែកព័ត៌មានវិទ្យារបស់អ្នក"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"ការថតអេក្រង់ត្រូវបានបិទដោយគោលការណ៍ឧបករណ៍"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"សម្អាតទាំងអស់"</string>
diff --git a/packages/SystemUI/res/values-kn/strings.xml b/packages/SystemUI/res/values-kn/strings.xml
index 437f406..544af6a 100644
--- a/packages/SystemUI/res/values-kn/strings.xml
+++ b/packages/SystemUI/res/values-kn/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"ವೀಕ್ಷಿಸಲು ಟ್ಯಾಪ್ ಮಾಡಿ"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"ಸ್ಕ್ರೀನ್ ರೆಕಾರ್ಡಿಂಗ್ ಸೇವ್ ಮಾಡುವಾಗ ದೋಷ ಎದುರಾಗಿದೆ"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"ಸ್ಕ್ರೀನ್ ರೆಕಾರ್ಡಿಂಗ್ ಪ್ರಾರಂಭಿಸುವಾಗ ದೋಷ ಕಂಡುಬಂದಿದೆ"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"ಪೂರ್ಣ ಸ್ಕ್ರೀನ್ನಲ್ಲಿ ವೀಕ್ಷಿಸಲಾಗುತ್ತಿದೆ"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"ನಿರ್ಗಮಿಸಲು, ಮೇಲಿನಿಂದ ಕೆಳಕ್ಕೆ ಸ್ವೈಪ್ ಮಾಡಿ."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"ಅರ್ಥವಾಯಿತು"</string>
<string name="accessibility_back" msgid="6530104400086152611">"ಹಿಂದೆ"</string>
<string name="accessibility_home" msgid="5430449841237966217">"ಮುಖಪುಟ"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"ಮೆನು"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"ನೀವು ಹಂಚಿಕೊಳ್ಳುತ್ತಿರುವಾಗ, ರೆಕಾರ್ಡಿಂಗ್ ಮಾಡುತ್ತಿರುವಾಗ ಅಥವಾ ಕ್ಯಾಸ್ಟ್ ಮಾಡುತ್ತಿರುವಾಗ, ನಿಮ್ಮ ಸ್ಕ್ರೀನ್ ಮೇಲೆ ಕಾಣಿಸುವ ಅಥವಾ ನಿಮ್ಮ ಸಾಧನದಲ್ಲಿ ಪ್ಲೇ ಮಾಡುವ ಯಾವುದೇ ವಿಷಯಕ್ಕೆ Android ಆ್ಯಕ್ಸೆಸ್ ಅನ್ನು ಹೊಂದಿರುತ್ತದೆ. ಆದ್ದರಿಂದ ಪಾಸ್ವರ್ಡ್ಗಳು, ಪಾವತಿ ವಿವರಗಳು, ಸಂದೇಶಗಳು, ಫೋಟೋಗಳು ಹಾಗೂ ಆಡಿಯೊ ಮತ್ತು ವೀಡಿಯೊದಂತಹ ವಿಷಯಗಳ ಕುರಿತು ಜಾಗರೂಕರಾಗಿರಿ."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"ನೀವು ಆ್ಯಪ್ ಅನ್ನು ಹಂಚಿಕೊಳ್ಳುತ್ತಿರುವಾಗ, ರೆಕಾರ್ಡಿಂಗ್ ಮಾಡುತ್ತಿರುವಾಗ ಅಥವಾ ಕ್ಯಾಸ್ಟ್ ಮಾಡುತ್ತಿರುವಾಗ, ಆ ಆ್ಯಪ್ನಲ್ಲಿ ತೋರಿಸುವ ಅಥವಾ ಪ್ಲೇ ಮಾಡುವ ಯಾವುದೇ ವಿಷಯಕ್ಕೆ Android ಆ್ಯಕ್ಸೆಸ್ ಅನ್ನು ಹೊಂದಿರುತ್ತದೆ. ಆದ್ದರಿಂದ ಪಾಸ್ವರ್ಡ್ಗಳು, ಪಾವತಿ ವಿವರಗಳು, ಸಂದೇಶಗಳು, ಫೋಟೋಗಳು ಹಾಗೂ ಆಡಿಯೊ ಮತ್ತು ವೀಡಿಯೊದಂತಹ ವಿಷಯಗಳ ಕುರಿತು ಜಾಗರೂಕರಾಗಿರಿ."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"ಪ್ರಾರಂಭಿಸಿ"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"ನಿಮ್ಮ IT ನಿರ್ವಾಹಕರು ನಿರ್ಬಂಧಿಸಿದ್ದಾರೆ"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"ಸಾಧನ ನೀತಿಯಿಂದ ಸ್ಕ್ರೀನ್ ಕ್ಯಾಪ್ಚರಿಂಗ್ ಅನ್ನು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"ಎಲ್ಲವನ್ನೂ ತೆರವುಗೊಳಿಸಿ"</string>
diff --git a/packages/SystemUI/res/values-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml
index 9916951..bd58c0f 100644
--- a/packages/SystemUI/res/values-ko/strings.xml
+++ b/packages/SystemUI/res/values-ko/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"탭하여 보기"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"화면 녹화 저장 중에 오류가 발생했습니다."</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"화면 녹화 시작 중 오류 발생"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"전체 화면 모드"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"종료하려면 위에서 아래로 스와이프합니다."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"확인"</string>
<string name="accessibility_back" msgid="6530104400086152611">"뒤로"</string>
<string name="accessibility_home" msgid="5430449841237966217">"홈"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"메뉴"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"공유, 녹화 또는 전송 중에 Android가 화면에 표시되거나 기기에서 재생되는 모든 항목에 액세스할 수 있습니다. 따라서 비밀번호, 결제 세부정보, 메시지, 사진, 오디오 및 동영상 등이 노출되지 않도록 주의하세요."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"앱을 공유, 녹화 또는 전송할 때는 Android가 해당 앱에 표시되거나 재생되는 모든 항목에 액세스할 수 있으므로 비밀번호, 결제 세부정보, 메시지, 사진, 오디오 및 동영상 등이 노출되지 않도록 주의하세요."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"시작"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"IT 관리자에 의해 차단됨"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"기기 정책에 의해 화면 캡처가 사용 중지되었습니다."</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"모두 지우기"</string>
diff --git a/packages/SystemUI/res/values-ky/strings.xml b/packages/SystemUI/res/values-ky/strings.xml
index aaec2cd..054aa2e 100644
--- a/packages/SystemUI/res/values-ky/strings.xml
+++ b/packages/SystemUI/res/values-ky/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Көрүү үчүн таптаңыз"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Экран тартылган жок"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Экранды жаздырууну баштоодо ката кетти"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Толук экран режимин көрүү"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Чыгуу үчүн экранды ылдый сүрүп коюңуз."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Түшүндүм"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Артка"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Үйгө"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Меню"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Бөлүшүп, жаздырып же тышкы экранга чыгарып жатканда Android экраныңыздагы бардык маалыматты же түзмөктө ойнотулуп жаткан нерселерди көрө алат. Андыктан сырсөздөрдү, төлөмдүн чоо-жайын, билдирүүлөрдү, сүрөттөрдү, аудио жана видеону көрсөтүп албаңыз."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Колдонмону бөлүшүп, жаздырып же тышкы экранга чыгарганда Android ал колдонмодо көрсөтүлүп жана ойнотулуп жаткан нерселерди көрө алат. Андыктан сырсөздөрдү, төлөмдүн чоо-жайын, билдирүүлөрдү, сүрөттөрдү, аудио жана видеону көрсөтүп албаңыз."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Баштоо"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"IT администраторуңуз бөгөттөп койгон"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Түзмөк саясаты экрандагыны тартып алууну өчүрүп койгон"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Баарын тазалап салуу"</string>
diff --git a/packages/SystemUI/res/values-lo/strings.xml b/packages/SystemUI/res/values-lo/strings.xml
index 0306fc9..66bd9e3 100644
--- a/packages/SystemUI/res/values-lo/strings.xml
+++ b/packages/SystemUI/res/values-lo/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"ແຕະເພື່ອເບິ່ງ"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"ເກີດຂໍ້ຜິດພາດໃນການບັນທຶກໜ້າຈໍ"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"ເກີດຄວາມຜິດພາດໃນການບັນທຶກໜ້າຈໍ"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"ກຳລັງເບິ່ງເຕັມຈໍ"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"ເພື່ອອອກ, ໃຫ້ປັດລົງຈາກເທິງສຸດ."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"ເຂົ້າໃຈແລ້ວ"</string>
<string name="accessibility_back" msgid="6530104400086152611">"ກັບຄືນ"</string>
<string name="accessibility_home" msgid="5430449841237966217">"ໜ້າທຳອິດ"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"ເມນູ"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"ເມື່ອທ່ານກຳລັງແບ່ງປັນ, ບັນທຶກ ຫຼື ສົ່ງສັນຍານ, Android ຈະມີສິດເຂົ້າເຖິງທຸກສິ່ງທີ່ປາກົດຢູ່ໜ້າຈໍຂອງທ່ານ ຫຼື ຫຼິ້ນຢູ່ອຸປະກອນຂອງທ່ານ. ດັ່ງນັ້ນ, ໃຫ້ລະມັດລະວັງສິ່ງຕ່າງໆ ເຊັ່ນ: ລະຫັດຜ່ານ, ລາຍລະອຽດການຈ່າຍເງິນ, ຂໍ້ຄວາມ, ຮູບພາບ ພ້ອມທັງສຽງ ແລະ ວິດີໂອ."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"ເມື່ອທ່ານກຳລັງແບ່ງປັນ, ບັນທຶກ ຫຼື ສົ່ງສັນຍານແອັບ, Android ຈະມີສິດເຂົ້າເຖິງທຸກສິ່ງທີ່ສະແດງ ຫຼື ຫຼິ້ນຢູ່ແອັບດັ່ງກ່າວ. ດັ່ງນັ້ນ, ໃຫ້ລະມັດລະວັງສິ່ງຕ່າງໆ ເຊັ່ນ: ລະຫັດຜ່ານ, ລາຍລະອຽດການຈ່າຍເງິນ, ຂໍ້ຄວາມ, ຮູບພາບ ພ້ອມທັງສຽງ ແລະ ວິດີໂອ."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"ເລີ່ມ"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"ຖືກບລັອກໄວ້ໂດຍຜູ້ເບິ່ງແຍງລະບົບໄອທີຂອງທ່ານ"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"ການຖ່າຍຮູບໜ້າຈໍຖືກປິດການນຳໃຊ້ໄວ້ໂດຍນະໂຍບາຍອຸປະກອນ"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"ລຶບລ້າງທັງໝົດ"</string>
diff --git a/packages/SystemUI/res/values-lt/strings.xml b/packages/SystemUI/res/values-lt/strings.xml
index 8548a24..c93506f 100644
--- a/packages/SystemUI/res/values-lt/strings.xml
+++ b/packages/SystemUI/res/values-lt/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Palieskite, kad peržiūrėtumėte"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Išsaugant ekrano įrašą įvyko klaida"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Pradedant ekrano vaizdo įrašymą iškilo problema"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Peržiūrima viso ekrano režimu"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Jei norite išeiti, perbraukite žemyn iš viršaus."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Supratau"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Atgal"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Pagrindinis"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Meniu"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Kai bendrinate, įrašote ar perduodate turinį, „Android“ gali pasiekti viską, kas rodoma ekrane ar leidžiama įrenginyje. Todėl būkite atsargūs naudodami slaptažodžius, išsamią mokėjimo metodo informaciją, pranešimus, nuotraukas ir garso bei vaizdo įrašus."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Kai bendrinate, įrašote ar perduodate programą, „Android“ gali pasiekti viską, kas rodoma ar leidžiama programoje. Todėl būkite atsargūs naudodami slaptažodžius, išsamią mokėjimo metodo informaciją, pranešimus, nuotraukas ir garso bei vaizdo įrašus."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Pradėti"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Užblokavo jūsų IT administratorius"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Ekrano fiksavimo funkcija išjungta vadovaujantis įrenginio politika"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Viską išvalyti"</string>
diff --git a/packages/SystemUI/res/values-lv/strings.xml b/packages/SystemUI/res/values-lv/strings.xml
index 79f73b6..dcd54c4 100644
--- a/packages/SystemUI/res/values-lv/strings.xml
+++ b/packages/SystemUI/res/values-lv/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Pieskarieties, lai skatītu"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Saglabājot ekrāna ierakstu, radās kļūda."</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Sākot ierakstīt ekrāna saturu, radās kļūda."</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Skatīšanās pilnekrāna režīmā"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Lai izietu, no augšdaļas velciet lejup."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Labi"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Atpakaļ"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Sākums"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Izvēlne"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Kopīgošanas, ierakstīšanas vai apraides laikā Android var piekļūt visam, kas tiek rādīts jūsu ekrānā vai atskaņots jūsu ierīcē. Tāpēc piesardzīgi apejieties ar parolēm, maksājumu informāciju, ziņojumiem, fotoattēliem un audio un video saturu."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Lietotnes kopīgošanas, ierakstīšanas vai apraides laikā Android var piekļūt visam, kas tiek rādīts vai atskaņots attiecīgajā lietotnē. Tāpēc piesardzīgi apejieties ar parolēm, maksājumu informāciju, ziņojumiem, fotoattēliem un audio un video saturu."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Sākt"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Bloķējis jūsu IT administrators"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Ierīces politika ir atspējojusi ekrānuzņēmumu izveidi"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Dzēst visu"</string>
diff --git a/packages/SystemUI/res/values-mk/strings.xml b/packages/SystemUI/res/values-mk/strings.xml
index b8829c6..e83bf81 100644
--- a/packages/SystemUI/res/values-mk/strings.xml
+++ b/packages/SystemUI/res/values-mk/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Допрете за прегледување"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Грешка при зачувувањето на снимката од екранот"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Грешка при почетокот на снимањето на екранот"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Се прикажува на цел екран"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"За да излезете, повлечете одозгора надолу."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Сфатив"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Назад"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Почетна страница"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Мени"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Кога споделувате, снимате или емитувате, Android има пристап до сѐ што е видливо на вашиот екран или пуштено на вашиот уред. Затоа, бидете внимателни со работи како лозинки, детали за плаќање, пораки, фотографии и аудио и видео."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Кога споделувате, снимате или емитувате апликација, Android има пристап до сѐ што се прикажува или пушта на таа апликација. Затоа, бидете внимателни со работи како лозинки, детали за плаќање, пораки, фотографии и аудио и видео."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Започни"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Блокирано од IT-администраторот"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Снимањето на екранот е оневозможено со правила на уредот"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Избриши сѐ"</string>
diff --git a/packages/SystemUI/res/values-ml/strings.xml b/packages/SystemUI/res/values-ml/strings.xml
index f8c14e6..c8fbde8 100644
--- a/packages/SystemUI/res/values-ml/strings.xml
+++ b/packages/SystemUI/res/values-ml/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"കാണാൻ ടാപ്പ് ചെയ്യുക"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"സ്ക്രീൻ റെക്കോർഡിംഗ് സംരക്ഷിക്കുന്നതിൽ പിശക്"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"സ്ക്രീൻ റെക്കോർഡിംഗ് ആരംഭിക്കുന്നതിൽ പിശക്"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"പൂർണ്ണ സ്ക്രീനിൽ കാണുന്നു"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"പുറത്തുകടക്കാൻ, മുകളിൽ നിന്ന് താഴോട്ട് സ്വൈപ്പ് ചെയ്യുക."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"മനസ്സിലായി"</string>
<string name="accessibility_back" msgid="6530104400086152611">"മടങ്ങുക"</string>
<string name="accessibility_home" msgid="5430449841237966217">"ഹോം"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"മെനു"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"പങ്കിടുമ്പോൾ, റെക്കോർഡ് ചെയ്യുമ്പോൾ അല്ലെങ്കിൽ കാസ്റ്റ് ചെയ്യുമ്പോൾ, Android-ന് നിങ്ങളുടെ സ്ക്രീനിൽ ദൃശ്യമാകുന്നതോ ഉപകരണത്തിൽ പ്ലേ ചെയ്യുന്നതോ ആയ ഏത് കാര്യത്തിലേക്കും ആക്സസ് ഉണ്ട്. അതിനാൽ പാസ്വേഡുകൾ, പേയ്മെന്റ് വിശദാംശങ്ങൾ, സന്ദേശങ്ങൾ, ഫോട്ടോകൾ, ഓഡിയോ, വീഡിയോ എന്നിവ പോലുള്ള കാര്യങ്ങൾ നൽകുമ്പോൾ സൂക്ഷിക്കുക."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"ഒരു ആപ്പ് പങ്കിടുമ്പോൾ, റെക്കോർഡ് ചെയ്യുമ്പോൾ അല്ലെങ്കിൽ കാസ്റ്റ് ചെയ്യുമ്പോൾ, Android-ന് ആ ആപ്പിൽ കാണിക്കുന്ന അല്ലെങ്കിൽ പ്ലേ ചെയ്യുന്ന എല്ലാത്തിലേക്കും ആക്സസ് ഉണ്ട്. അതിനാൽ പാസ്വേഡുകൾ, പേയ്മെന്റ് വിശദാംശങ്ങൾ, സന്ദേശങ്ങൾ, ഫോട്ടോകൾ, ഓഡിയോ, വീഡിയോ എന്നിവ പോലുള്ള കാര്യങ്ങൾ നൽകുമ്പോൾ സൂക്ഷിക്കുക."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"ആരംഭിക്കുക"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"നിങ്ങളുടെ ഐടി അഡ്മിൻ ബ്ലോക്ക് ചെയ്തു"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"ഉപകരണ നയം, സ്ക്രീൻ ക്യാപ്ചർ ചെയ്യൽ പ്രവർത്തനരഹിതമാക്കി"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"എല്ലാം മായ്ക്കുക"</string>
diff --git a/packages/SystemUI/res/values-mn/strings.xml b/packages/SystemUI/res/values-mn/strings.xml
index ceb8782..15745ff 100644
--- a/packages/SystemUI/res/values-mn/strings.xml
+++ b/packages/SystemUI/res/values-mn/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Харахын тулд товшино уу"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Дэлгэцийн бичлэгийг хадгалахад алдаа гарлаа"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Дэлгэцийн бичлэгийг эхлүүлэхэд алдаа гарлаа"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Бүтэн дэлгэцээр үзэж байна"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Гарахаар бол дээрээс нь доош нь чирнэ үү."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Ойлголоо"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Буцах"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Гэрийн"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Цэс"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Таныг хуваалцаж, бичлэг хийж эсвэл дамжуулж байх үед Android таны дэлгэцэд харуулсан эсвэл төхөөрөмжид тань тоглуулсан аливаа зүйлд хандах эрхтэй. Тиймээс нууц үг, төлбөрийн дэлгэрэнгүй, мессеж, зураг, аудио болон видео зэрэг зүйлд болгоомжтой хандаарай."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Таныг хуваалцаж, бичлэг хийж эсвэл дамжуулж байх үед Android тухайн аппад харуулсан эсвэл тоглуулсан аливаа зүйлд хандах эрхтэй. Тиймээс нууц үг, төлбөрийн дэлгэрэнгүй, мессеж, зураг, аудио болон видео зэрэг бусад зүйлд болгоомжтой хандаарай."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Эхлүүлэх"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Таны IT админ блоклосон"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Төхөөрөмжийн бодлогоор дэлгэцийн зураг авахыг идэвхгүй болгосон"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Бүгдийг арилгах"</string>
diff --git a/packages/SystemUI/res/values-mr/strings.xml b/packages/SystemUI/res/values-mr/strings.xml
index ba45d53..a52d217 100644
--- a/packages/SystemUI/res/values-mr/strings.xml
+++ b/packages/SystemUI/res/values-mr/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"पाहण्यासाठी टॅप करा"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"स्क्रीन रेकॉर्डिंग सेव्ह करताना एरर आली"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"स्क्रीन रेकॉर्डिंग सुरू करताना एरर आली"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"पूर्ण स्क्रीनवर पाहत आहात"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"बाहेर पडण्यासाठी, वरून खाली स्वाइप करा."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"समजले"</string>
<string name="accessibility_back" msgid="6530104400086152611">"मागे"</string>
<string name="accessibility_home" msgid="5430449841237966217">"होम"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"मेनू"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"तुम्ही शेअर, रेकॉर्ड किंवा कास्ट करत असताना, Android ला तुमच्या स्क्रीनवर दाखवलेल्या किंवा डिव्हाइसवर प्ले केलेल्या कोणत्याही गोष्टीचा अॅक्सेस असतो. त्यामुळे पासवर्ड, पेमेंट तपशील, मेसेज, फोटो आणि ऑडिओ व व्हिडिओ यांसारख्या गोष्टींबाबत सावधगिरी बाळगा."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"तुम्ही एखादे अॅप शेअर, रेकॉर्ड किंवा कास्ट करत असताना, Android ला त्या अॅपवर दाखवलेल्या किंवा प्ले केलेल्या कोणत्याही गोष्टीचा अॅक्सेस असतो. त्यामुळे पासवर्ड, पेमेंट तपशील, मेसेज, फोटो आणि ऑडिओ व व्हिडिओ यांसारख्या गोष्टींबाबत सावधगिरी बाळगा."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"सुरुवात करा"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"तुमच्या आयटी ॲडमिनने ब्लॉक केले आहे"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"डिव्हाइस धोरणाने स्क्रीन कॅप्चर करणे बंद केले आहे"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"सर्व साफ करा"</string>
diff --git a/packages/SystemUI/res/values-ms/strings.xml b/packages/SystemUI/res/values-ms/strings.xml
index e28ea01..3da42a5 100644
--- a/packages/SystemUI/res/values-ms/strings.xml
+++ b/packages/SystemUI/res/values-ms/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Ketik untuk lihat"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Ralat semasa menyimpan rakaman skrin"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Ralat semasa memulakan rakaman skrin"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Melihat skrin penuh"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Untuk keluar, leret ke bawah dari bahagian atas."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"OK"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Kembali"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Rumah"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Menu"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Apabila anda membuat perkongsian, rakaman atau penghantaran, Android boleh mengakses apa-apa sahaja yang boleh dilihat pada skrin anda atau dimainkan pada peranti anda. Oleh hal yang demikian, berhati-hati dengan perkara seperti kata laluan, butiran pembayaran, mesej, foto dan audio serta video."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Apabila anda berkongsi, merakam atau menghantar apl, Android boleh mengakses apa-apa sahaja yang ditunjukan atau dimainkan pada apl tersebut. Oleh hal yang demikian, berhati-hati dengan perkara seperti kata laluan, butiran pembayaran, mesej, foto dan audio serta video."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Mula"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Disekat oleh pentadbir IT anda"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Tangkapan skrin dilumpuhkan oleh dasar peranti"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Kosongkan semua"</string>
diff --git a/packages/SystemUI/res/values-my/strings.xml b/packages/SystemUI/res/values-my/strings.xml
index 5bcd5a0..c258862 100644
--- a/packages/SystemUI/res/values-my/strings.xml
+++ b/packages/SystemUI/res/values-my/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"ကြည့်ရှုရန် တို့ပါ"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"ဖန်သားပြင်ရိုက်ကူးမှုကို သိမ်းရာတွင် အမှားရှိသည်"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"ဖန်သားပြင် ရိုက်ကူးမှု စတင်ရာတွင် အမှားအယွင်းရှိနေသည်"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"ဖန်သားပြင်အပြည့် ကြည့်နေသည်"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"ထွက်ရန် အပေါ်မှ အောက်သို့ ပွတ်ဆွဲပါ။"</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"နားလည်ပြီ"</string>
<string name="accessibility_back" msgid="6530104400086152611">"နောက်သို့"</string>
<string name="accessibility_home" msgid="5430449841237966217">"ပင်မစာမျက်နှာ"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"မီနူး"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"မျှဝေ၊ ရုပ်သံဖမ်း (သို့) ကာစ်လုပ်သည့်အခါ Android သည် သင့်ဖန်သားပြင်တွင် မြင်နိုင်သည့် (သို့) သင့်စက်တွင် ဖွင့်ထားသည့် အရာအားလုံးကို တွေ့နိုင်သည်။ စကားဝှက်၊ ငွေပေးချေမှု အချက်အလက်၊ မက်ဆေ့ဂျ်၊ ဓာတ်ပုံ၊ အသံနှင့် ဗီဒီယိုကဲ့သို့ အရာများကို ဂရုစိုက်ပါ။"</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"အက်ပ်တစ်ခုဖြင့် မျှဝေ၊ ရုပ်သံဖမ်း (သို့) ကာစ်လုပ်သည့်အခါ Android သည် ယင်းအက်ပ်တွင် ပြထားသည့် (သို့) ဖွင့်ထားသည့် အရာအားလုံးကို တွေ့နိုင်သည်။ ထို့ကြောင့် စကားဝှက်၊ ငွေပေးချေမှု အချက်အလက်၊ မက်ဆေ့ဂျ်၊ ဓာတ်ပုံ၊ အသံနှင့် ဗီဒီယိုကဲ့သို့အရာများကို ဂရုစိုက်ပါ။"</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"စတင်ရန်"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"သင်၏ IT စီမံခန့်ခွဲသူက ပိတ်ထားသည်"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"ကိရိယာ မူဝါဒက ဖန်သားပြင်ပုံဖမ်းခြင်းကို ပိတ်ထားသည်"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"အားလုံးရှင်းရန်"</string>
diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml
index 755ee81..9277d52 100644
--- a/packages/SystemUI/res/values-nb/strings.xml
+++ b/packages/SystemUI/res/values-nb/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Trykk for å se"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Feil ved lagring av skjermopptaket"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Feil ved start av skjermopptaket"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Visning i fullskjerm"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Sveip ned fra toppen for å avslutte."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Skjønner"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Tilbake"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Startside"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Meny"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Når du deler, tar opp eller caster noe, har Android tilgang til alt som vises på skjermen eller spilles av på enheten. Derfor bør du være forsiktig med for eksempel passord, betalingsopplysninger, meldinger, bilder, lyd og video."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Når du deler, tar opp eller caster en app, har Android tilgang til alt som vises eller spilles av i den aktuelle appen. Derfor bør du være forsiktig med for eksempel passord, betalingsopplysninger, meldinger, bilder, lyd og video."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Begynn"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blokkert av IT-administratoren"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Skjermdumper er deaktivert av enhetsinnstillingene"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Fjern alt"</string>
diff --git a/packages/SystemUI/res/values-ne/strings.xml b/packages/SystemUI/res/values-ne/strings.xml
index d1445e1..9f9bf85 100644
--- a/packages/SystemUI/res/values-ne/strings.xml
+++ b/packages/SystemUI/res/values-ne/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"हेर्नका लागि ट्याप गर्नुहोस्"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"स्क्रिन रेकर्डिङ सेभ गर्ने क्रममा त्रुटि भयो"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"स्क्रिन रेकर्ड गर्न थाल्ने क्रममा त्रुटि भयो"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"पूरा पर्दा हेर्दै"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"बाहिर निस्कन, माथिबाट तल स्वाइप गर्नुहोस्।"</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"बुझेँ"</string>
<string name="accessibility_back" msgid="6530104400086152611">"पछाडि"</string>
<string name="accessibility_home" msgid="5430449841237966217">"गृह"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"मेनु"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"तपाईंले सेयर गर्दा, रेकर्ड गर्दा वा कास्ट गर्दा Android ले तपाईंको स्क्रिनमा देखिने वा डिभाइसमा प्ले गरिने सबै कुरा हेर्न तथा प्रयोग गर्न सक्छ। त्यसैले पासवर्ड, भुक्तानीसम्बन्धी विवरण, म्यासेज, फोटो र अडियो तथा भिडियो जस्ता कुरा हेर्दा वा प्ले गर्दा सावधानी अपनाउनुहोला।"</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"तपाईंले कुनै एप सेयर गर्दा, रेकर्ड गर्दा वा कास्ट गर्दा Android ले उक्त एपमा देखाइने वा प्ले गरिने सबै कुरा हेर्न तथा प्रयोग गर्न सक्छ। त्यसैले पासवर्ड, भुक्तानीसम्बन्धी विवरण, म्यासेज, फोटो र अडियो तथा भिडियो जस्ता कुरा हेर्दा वा प्ले गर्दा सावधानी अपनाउनुहोला।"</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"सुरु गर्नुहोस्"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"तपाईंका IT एड्मिनले ब्लक गर्नुभएको छ"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"डिभाइसको नीतिका कारण स्क्रिन क्याप्चर गर्ने सुविधा अफ गरिएको छ"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"सबै हटाउनुहोस्"</string>
diff --git a/packages/SystemUI/res/values-nl/strings.xml b/packages/SystemUI/res/values-nl/strings.xml
index 932889b..5c8ba84 100644
--- a/packages/SystemUI/res/values-nl/strings.xml
+++ b/packages/SystemUI/res/values-nl/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Tik om te bekijken"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Fout bij opslaan van schermopname"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Fout bij starten van schermopname"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Volledig scherm wordt getoond"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Swipe omlaag vanaf de bovenkant van het scherm om af te sluiten."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"OK"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Terug"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Startscherm"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Menu"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Als je deelt, opneemt of cast, heeft Android toegang tot alles dat zichtbaar is op je scherm of wordt afgespeeld op je apparaat. Wees daarom voorzichtig met bijvoorbeeld wachtwoorden, betalingsgegevens, berichten, foto\'s, en audio en video."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Als je deelt, opneemt of cast, heeft Android toegang tot alles dat wordt getoond of afgespeeld in die app. Wees daarom voorzichtig met bijvoorbeeld wachtwoorden, betalingsgegevens, berichten, foto\'s, en audio en video."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Starten"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Geblokkeerd door je IT-beheerder"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Schermopname staat uit vanwege apparaatbeleid"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Alles wissen"</string>
diff --git a/packages/SystemUI/res/values-or/strings.xml b/packages/SystemUI/res/values-or/strings.xml
index 8061fd6..e41e7c2 100644
--- a/packages/SystemUI/res/values-or/strings.xml
+++ b/packages/SystemUI/res/values-or/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"ଦେଖିବାକୁ ଟାପ୍ କରନ୍ତୁ"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"ସ୍କ୍ରିନ ରେକର୍ଡିଂ ସେଭ କରିବାରେ ତ୍ରୁଟି"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"ସ୍କ୍ରିନ୍ ରେକର୍ଡିଂ ଆରମ୍ଭ କରିବାରେ ତ୍ରୁଟି"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"ପୂର୍ଣ୍ଣ ସ୍କ୍ରିନରେ ଦେଖିବା"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"ବାହାରି ଯିବା ପାଇଁ, ଶୀର୍ଷରୁ ତଳକୁ ସ୍ୱାଇପ କରନ୍ତୁ।"</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"ବୁଝିଗଲି"</string>
<string name="accessibility_back" msgid="6530104400086152611">"ଫେରନ୍ତୁ"</string>
<string name="accessibility_home" msgid="5430449841237966217">"ହୋମ"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"ମେନୁ"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"ଆପଣ ସେୟାର, ରେକର୍ଡ କିମ୍ବା କାଷ୍ଟ କରିବା ସମୟରେ, ଆପଣଙ୍କ ସ୍କ୍ରିନରେ ଦେଖାଯାଉଥିବା କିମ୍ବା ଆପଣଙ୍କ ଡିଭାଇସରେ ପ୍ଲେ ହେଉଥିବା ସବୁକିଛିକୁ Androidର ଆକ୍ସେସ ଅଛି। ତେଣୁ ପାସୱାର୍ଡ, ପେମେଣ୍ଟ ବିବରଣୀ, ମେସେଜ, ଫଟୋ ଏବଂ ଅଡିଓ ଓ ଭିଡିଓ ପରି ବିଷୟଗୁଡ଼ିକ ପ୍ରତି ସତର୍କ ରୁହନ୍ତୁ।"</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"ଆପଣ ଏକ ଆପ ସେୟାର, ରେକର୍ଡ କିମ୍ବା କାଷ୍ଟ କରିବା ସମୟରେ, ସେହି ଆପରେ ଦେଖାଯାଉଥିବା କିମ୍ବା ପ୍ଲେ ହେଉଥିବା ସବୁକିଛିକୁ Androidର ଆକ୍ସେସ ଅଛି। ତେଣୁ ପାସୱାର୍ଡ, ପେମେଣ୍ଟ ବିବରଣୀ, ମେସେଜ, ଫଟୋ ଏବଂ ଅଡିଓ ଓ ଭିଡିଓ ପରି ବିଷୟଗୁଡ଼ିକ ପ୍ରତି ସତର୍କ ରୁହନ୍ତୁ।"</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"ଆରମ୍ଭ କରନ୍ତୁ"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"ଆପଣଙ୍କ IT ଆଡମିନଙ୍କ ଦ୍ୱାରା ବ୍ଲକ କରାଯାଇଛି"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"ଡିଭାଇସ ନୀତି ଦ୍ୱାରା ସ୍କ୍ରିନ କେପଚରିଂକୁ ଅକ୍ଷମ କରାଯାଇଛି"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"ସବୁ ଖାଲି କରନ୍ତୁ"</string>
diff --git a/packages/SystemUI/res/values-pa/strings.xml b/packages/SystemUI/res/values-pa/strings.xml
index 0f3ef6a..cc63775 100644
--- a/packages/SystemUI/res/values-pa/strings.xml
+++ b/packages/SystemUI/res/values-pa/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"ਦੇਖਣ ਲਈ ਟੈਪ ਕਰੋ"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"ਸਕ੍ਰੀਨ ਰਿਕਾਰਡਿੰਗ ਨੂੰ ਰੱਖਿਅਤ ਕਰਨ ਵੇਲੇ ਗੜਬੜ ਹੋ ਗਈ"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"ਸਕ੍ਰੀਨ ਰਿਕਾਰਡਿੰਗ ਨੂੰ ਸ਼ੁਰੂ ਕਰਨ ਵੇਲੇ ਗੜਬੜ ਹੋਈ"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"ਪੂਰੀ ਸਕ੍ਰੀਨ \'ਤੇ ਦੇਖੋ"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"ਬਾਹਰ ਜਾਣ ਲਈ, ਉਪਰੋਂ ਹੇਠਾਂ ਸਵਾਈਪ ਕਰੋ।"</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"ਸਮਝ ਲਿਆ"</string>
<string name="accessibility_back" msgid="6530104400086152611">"ਪਿੱਛੇ"</string>
<string name="accessibility_home" msgid="5430449841237966217">"ਘਰ"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"ਮੀਨੂ"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"ਤੁਹਾਡੇ ਵੱਲੋਂ ਸਾਂਝਾ ਕਰਨ, ਰਿਕਾਰਡ ਕਰਨ, ਜਾਂ ਕਾਸਟ ਕਰਨ \'ਤੇ, Android ਕੋਲ ਤੁਹਾਡੀ ਸਕ੍ਰੀਨ \'ਤੇ ਦਿਸਦੀ ਜਾਂ ਤੁਹਾਡੇ ਡੀਵਾਈਸ \'ਤੇ ਚਲਾਈ ਗਈ ਹਰੇਕ ਚੀਜ਼ ਤੱਕ ਪਹੁੰਚ ਹੁੰਦੀ ਹੈ। ਇਸ ਲਈ ਪਾਸਵਰਡਾਂ, ਭੁਗਤਾਨ ਵੇਰਵਿਆਂ, ਸੁਨੇਹਿਆਂ, ਫ਼ੋਟੋਆਂ ਅਤੇ ਆਡੀਓ ਅਤੇ ਵੀਡੀਓ ਵਰਗੀਆਂ ਚੀਜ਼ਾਂ ਵਾਸਤੇ ਸਾਵਧਾਨ ਰਹੋ।"</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"ਤੁਹਾਡੇ ਵੱਲੋਂ ਸਾਂਝਾ ਕਰਨ, ਰਿਕਾਰਡ ਕਰਨ, ਜਾਂ ਕਾਸਟ ਕਰਨ \'ਤੇ, Android ਕੋਲ ਉਸ ਐਪ \'ਤੇ ਦਿਖਾਈ ਗਈ ਜਾਂ ਚਲਾਈ ਗਈ ਹਰੇਕ ਚੀਜ਼ ਤੱਕ ਪਹੁੰਚ ਹੁੰਦੀ ਹੈ। ਇਸ ਲਈ ਪਾਸਵਰਡਾਂ, ਭੁਗਤਾਨ ਵੇਰਵਿਆਂ, ਸੁਨੇਹਿਆਂ, ਫ਼ੋਟੋਆਂ ਅਤੇ ਆਡੀਓ ਅਤੇ ਵੀਡੀਓ ਵਰਗੀਆਂ ਚੀਜ਼ਾਂ ਸੰਬੰਧੀ ਸਾਵਧਾਨ ਰਹੋ।"</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"ਸ਼ੁਰੂ ਕਰੋ"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"ਤੁਹਾਡੇ ਆਈ.ਟੀ. ਪ੍ਰਸ਼ਾਸਕ ਵੱਲੋਂ ਬਲਾਕ ਕੀਤਾ ਗਿਆ"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"ਡੀਵਾਈਸ ਨੀਤੀ ਦੇ ਕਾਰਨ ਸਕ੍ਰੀਨ ਕੈਪਚਰ ਕਰਨਾ ਬੰਦ ਹੈ"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"ਸਭ ਕਲੀਅਰ ਕਰੋ"</string>
diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml
index 757ffcd..21018f9 100644
--- a/packages/SystemUI/res/values-pl/strings.xml
+++ b/packages/SystemUI/res/values-pl/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Kliknij, aby wyświetlić"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Podczas zapisywania nagrania ekranu wystąpił błąd"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Błąd podczas rozpoczynania rejestracji zawartości ekranu"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Włączony pełny ekran"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Aby wyjść, przesuń palcem z góry na dół."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"OK"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Wróć"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Ekran główny"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Menu"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Podczas udostępniania, nagrywania lub przesyłania treści Android ma dostęp do wszystkiego, co jest widoczne na ekranie lub odtwarzane na urządzeniu. Dlatego zachowaj ostrożność w zakresie haseł, danych do płatności, wiadomości, zdjęć, audio i filmów."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Podczas udostępniania, nagrywania lub przesyłania treści Android ma dostęp do wszystkiego, co jest w niej wyświetlane lub odtwarzane. Dlatego zachowaj ostrożność w zakresie haseł, danych do płatności, wiadomości, zdjęć, audio i filmów."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Rozpocznij"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Zablokowane przez administratora IT"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Zrzuty ekranu są wyłączone zgodnie z zasadami dotyczącymi urządzeń"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Usuń wszystkie"</string>
diff --git a/packages/SystemUI/res/values-pt-rBR/strings.xml b/packages/SystemUI/res/values-pt-rBR/strings.xml
index 4ee6bd2..82015ea 100644
--- a/packages/SystemUI/res/values-pt-rBR/strings.xml
+++ b/packages/SystemUI/res/values-pt-rBR/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Toque para ver"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Erro ao salvar a gravação da tela"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Erro ao iniciar a gravação de tela"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Visualização em tela cheia"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Para sair, deslize de cima para baixo."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Entendi"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Voltar"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Página inicial"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Menu"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Quando você compartilha, grava ou transmite a tela, o Android tem acesso a todas as informações visíveis nela ou reproduzidas no dispositivo. Portanto, tenha cuidado com senhas, detalhes de pagamento, mensagens, fotos, áudios e vídeos."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Quando você compartilha, grava ou transmite um app, o Android tem acesso a todas as informações visíveis ou reproduzidas nele. Tenha cuidado com senhas, detalhes de pagamento, mensagens, fotos, áudios e vídeos."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Início"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Ação bloqueada pelo administrador de TI"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"A captura de tela foi desativada pela política do dispositivo"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Remover tudo"</string>
diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml
index f16b027..45853b9 100644
--- a/packages/SystemUI/res/values-pt-rPT/strings.xml
+++ b/packages/SystemUI/res/values-pt-rPT/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Toque para ver"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Erro ao guardar a gravação de ecrã"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Ocorreu um erro ao iniciar a gravação do ecrã."</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Visualização de ecrã inteiro"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Para sair, deslize rapidamente para baixo a partir da parte superior."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"OK"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Anterior"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Página inicial"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Menu"</string>
@@ -412,6 +415,10 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Quando está a partilhar, gravar ou transmitir conteúdo, o Android tem acesso a tudo o que está visível no seu ecrã ou é reproduzido no seu dispositivo. Por isso, tenha cuidado com, por exemplo, palavras-passe, detalhes de pagamento, mensagens, fotos, áudio e vídeo."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Quando está a partilhar, gravar ou transmitir uma app, o Android tem acesso a tudo o que é apresentado ou reproduzido nessa app. Por isso, tenha cuidado com, por exemplo, palavras-passe, detalhes de pagamento, mensagens, fotos, áudio e vídeo."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Iniciar"</string>
+ <string name="media_projection_task_switcher_text" msgid="590885489897412359">"A partilha é pausada quando muda de app"</string>
+ <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"Partilhar antes esta app"</string>
+ <string name="media_projection_task_switcher_action_back" msgid="5324164224147845282">"Voltar"</string>
+ <string name="media_projection_task_switcher_notification_channel" msgid="7613206306777814253">"Mudança de app"</string>
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Bloqueado pelo administrador de TI"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"A captura de ecrã está desativada pela política do dispositivo"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Limpar tudo"</string>
diff --git a/packages/SystemUI/res/values-pt/strings.xml b/packages/SystemUI/res/values-pt/strings.xml
index 4ee6bd2..82015ea 100644
--- a/packages/SystemUI/res/values-pt/strings.xml
+++ b/packages/SystemUI/res/values-pt/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Toque para ver"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Erro ao salvar a gravação da tela"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Erro ao iniciar a gravação de tela"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Visualização em tela cheia"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Para sair, deslize de cima para baixo."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Entendi"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Voltar"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Página inicial"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Menu"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Quando você compartilha, grava ou transmite a tela, o Android tem acesso a todas as informações visíveis nela ou reproduzidas no dispositivo. Portanto, tenha cuidado com senhas, detalhes de pagamento, mensagens, fotos, áudios e vídeos."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Quando você compartilha, grava ou transmite um app, o Android tem acesso a todas as informações visíveis ou reproduzidas nele. Tenha cuidado com senhas, detalhes de pagamento, mensagens, fotos, áudios e vídeos."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Início"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Ação bloqueada pelo administrador de TI"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"A captura de tela foi desativada pela política do dispositivo"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Remover tudo"</string>
diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml
index e57eb5d..bc6a5fd 100644
--- a/packages/SystemUI/res/values-ro/strings.xml
+++ b/packages/SystemUI/res/values-ro/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Atinge pentru a afișa"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Eroare la salvarea înregistrării ecranului"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Eroare la începerea înregistrării ecranului"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Vizualizare pe ecran complet"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Pentru a ieși, glisează de sus în jos."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"OK"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Înapoi"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Ecranul de pornire"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Meniu"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Când permiți accesul, înregistrezi sau proiectezi, Android are acces la orice este vizibil pe ecran sau se redă pe dispozitiv. Prin urmare, ai grijă cu parolele, detaliile de plată, mesajele, fotografiile și conținutul audio și video."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Când permiți accesul, înregistrezi sau proiectezi o aplicație, Android are acces la orice se afișează pe ecran sau se redă în aplicație. Prin urmare, ai grijă cu informații cum ar fi parolele, detaliile de plată, mesajele, fotografiile și conținutul audio și video."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Începe"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blocată de administratorul IT"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Capturile de ecran sunt dezactivate de politica privind dispozitivele"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Șterge toate notificările"</string>
diff --git a/packages/SystemUI/res/values-ru/strings.xml b/packages/SystemUI/res/values-ru/strings.xml
index 70f6e15..80b0d0f 100644
--- a/packages/SystemUI/res/values-ru/strings.xml
+++ b/packages/SystemUI/res/values-ru/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Нажмите, чтобы посмотреть."</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Не удалось сохранить запись видео с экрана."</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Не удалось начать запись видео с экрана."</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Полноэкранный режим"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Чтобы выйти, проведите по экрану сверху вниз."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"ОК"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Назад"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Главный экран"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Меню"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Когда вы демонстрируете, транслируете экран или записываете видео с него, система Android получает доступ ко всему, что видно или воспроизводится на устройстве. Поэтому будьте осторожны с паролями, сведениями о способах оплаты, сообщениями, фотографиями, аудио- и видеозаписями."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Когда вы демонстрируете, записываете или транслируете экран приложения, система Android получает доступ ко всему, что видно или воспроизводится в нем. Поэтому будьте осторожны с паролями, сведениями о способах оплаты, сообщениями, фотографиями, аудио- и видеозаписями."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Начать"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Заблокировано вашим администратором"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Запись экрана отключена в соответствии с правилами для устройства."</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Очистить все"</string>
diff --git a/packages/SystemUI/res/values-si/strings.xml b/packages/SystemUI/res/values-si/strings.xml
index b4dc0f2..8989b7e 100644
--- a/packages/SystemUI/res/values-si/strings.xml
+++ b/packages/SystemUI/res/values-si/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"බැලීමට තට්ටු කරන්න"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"තිර පටිගත කිරීම සුරැකීමේ දෝෂයකි"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"තිර පටිගත කිරීම ආරම්භ කිරීමේ දෝෂයකි"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"මුළු තිරය බලමින්"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"ඉවත් වීමට, ඉහළ සිට පහළට ස්වයිප් කරන්න"</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"වැටහුණි"</string>
<string name="accessibility_back" msgid="6530104400086152611">"ආපසු"</string>
<string name="accessibility_home" msgid="5430449841237966217">"මුල් පිටුව"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"මෙනුව"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"ඔබ බෙදා ගන්නා විට, පටිගත කරන විට, හෝ විකාශය කරන විට, Android හට ඔබේ තිරයේ පෙනෙන හෝ ඔබේ උපාංගයේ වාදනය වන ඕනෑම දෙයකට ප්රවේශය ඇත. ඒ නිසා මුරපද, ගෙවීම් විස්තර, පණිවුඩ, ඡායාරූප, සහ ශ්රව්ය සහ දෘශ්ය වැනි දේවල් පිළිබඳ ප්රවේශම් වන්න."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"ඔබ යෙදුමක් බෙදා ගන්නා විට, පටිගත කරන විට හෝ විකාශය කරන විට, Android හට එම යෙදුමේ පෙන්වන හෝ වාදනය කරන ඕනෑම දෙයකට ප්රවේශය ඇත. ඒ නිසා මුරපද, ගෙවීම් විස්තර, පණිවුඩ, ඡායාරූප, සහ ශ්රව්ය සහ දෘශ්ය වැනි දේවල් පිළිබඳ ප්රවේශම් වන්න."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"අරඹන්න"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"ඔබේ IT පරිපාලක විසින් අවහිර කර ඇත"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"උපාංග ප්රතිපත්තිය මගින් තිර ග්රහණය කිරීම අබල කර ඇත"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"සියල්ල හිස් කරන්න"</string>
diff --git a/packages/SystemUI/res/values-sk/strings.xml b/packages/SystemUI/res/values-sk/strings.xml
index 802bd7a..e743d56 100644
--- a/packages/SystemUI/res/values-sk/strings.xml
+++ b/packages/SystemUI/res/values-sk/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Zobrazte klepnutím"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Pri ukladaní nahrávky obrazovky sa vyskytla chyba"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Pri spustení nahrávania obrazovky sa vyskytla chyba"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Zobrazenie na celú obrazovku"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Ukončíte potiahnutím zhora nadol."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Dobre"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Späť"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Plocha"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Menu"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Počas zdieľania, nahrávania alebo prenosu bude mať Android prístup k všetkému, čo sa zobrazuje na obrazovke alebo prehráva v zariadení. Preto zvýšte pozornosť v prípade položiek, ako sú heslá, platobné údaje, správy, fotky a zvuk či video."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Počas zdieľania, nahrávania alebo prenosu v aplikácii bude mať Android prístup k všetkému zobrazovanému alebo prehrávaného obsahu v danej aplikácii. Preto zvýšte pozornosť v prípade položiek, ako sú heslá, platobné údaje, správy, fotky a zvuk či video."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Začať"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blokované vaším správcom IT"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Snímanie obrazovky je zakázané pravidlami pre zariadenie"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Vymazať všetko"</string>
diff --git a/packages/SystemUI/res/values-sl/strings.xml b/packages/SystemUI/res/values-sl/strings.xml
index a303e20..90d62cb 100644
--- a/packages/SystemUI/res/values-sl/strings.xml
+++ b/packages/SystemUI/res/values-sl/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Dotaknite se za ogled."</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Napaka pri shranjevanju posnetka zaslona"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Napaka pri začenjanju snemanja zaslona"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Vklopljen je celozaslonski način."</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Zaprete ga tako, da z vrha s prstom povlečete navzdol."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Razumem"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Nazaj"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Začetni zaslon"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Meni"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Pri deljenju, snemanju ali predvajanju ima Android dostop do vsega, kar je prikazano na zaslonu ali se predvaja v napravi. Zato bodite previdni z gesli, podatki za plačilo, sporočili, fotografijami ter z zvokom in videom."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Pri deljenju, snemanju ali predvajanju aplikacije ima Android dostop do vsega, kar je prikazano ali predvajano v tej aplikaciji, zato bodite previdni z gesli, podatki za plačilo, sporočili, fotografijami ter z zvokom in videom."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Začni"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blokiral skrbnik za IT"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Zajemanje zaslonske slike je onemogočil pravilnik za naprave."</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Izbriši vse"</string>
diff --git a/packages/SystemUI/res/values-sq/strings.xml b/packages/SystemUI/res/values-sq/strings.xml
index 4d0dcef..3677bdee 100644
--- a/packages/SystemUI/res/values-sq/strings.xml
+++ b/packages/SystemUI/res/values-sq/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Trokit për të parë"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Gabim gjatë ruajtjes së regjistrimit të ekranit"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Gabim gjatë nisjes së regjistrimit të ekranit"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Po shikon ekranin e plotë"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Për të dalë, rrëshqit nga lart poshtë."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"E kuptova"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Prapa"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Faqja bazë"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Menyja"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Kur ti ndan, regjistron ose transmeton, Android ka qasje te çdo gjë e dukshme në ekranin tënd ose që po luhet në pajisjen tënde. Prandaj, ki kujdes me gjërat si fjalëkalimet, detajet e pagesave, mesazhet, fotografitë, si dhe audion dhe videon."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Kur ti ndan, regjistron ose transmeton një aplikacion, Android ka qasje te çdo gjë e dukshme në ekranin tënd ose që po luhet në atë aplikacion. Prandaj, ki kujdes me gjërat si fjalëkalimet, detajet e pagesave, mesazhet, fotografitë, si dhe audion dhe videon."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Nis"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"U bllokua nga administratori yt i teknologjisë së informacionit"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Regjistrimi i ekranit është çaktivizuar nga politika e pajisjes."</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Pastroji të gjitha"</string>
diff --git a/packages/SystemUI/res/values-sr/strings.xml b/packages/SystemUI/res/values-sr/strings.xml
index 6227d5d..19d3065 100644
--- a/packages/SystemUI/res/values-sr/strings.xml
+++ b/packages/SystemUI/res/values-sr/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Додирните да бисте прегледали"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Грешка при чувању снимка екрана"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Грешка при покретању снимања екрана"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Приказује се цео екран"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Да бисте изашли, превуците надоле одозго."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Важи"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Назад"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Почетна"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Мени"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Када делите, снимате или пребацујете, Android има приступ комплетном садржају који је видљив на екрану или се пушта на уређају. Зато будите пажљиви са лозинкама, информацијама о плаћању, порукама, сликама и аудио и видео снимцима."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Када делите, снимате или пребацујете апликацију, Android има приступ комплетном садржају који је видљив или се пушта у тој апликацији. Зато будите пажљиви са лозинкама, информацијама о плаћању, порукама, сликама и аудио и видео снимцима."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Покрени"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Блокира ИТ администратор"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Снимање екрана је онемогућено смерницама за уређај"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Обриши све"</string>
diff --git a/packages/SystemUI/res/values-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml
index 7cf096f4..ae199e9 100644
--- a/packages/SystemUI/res/values-sv/strings.xml
+++ b/packages/SystemUI/res/values-sv/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Tryck för att visa"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Det gick inte att spara skärminspelningen"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Det gick inte att starta skärminspelningen"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Visar på fullskärm"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Svep nedåt från skärmens överkant för att avsluta."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"OK"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Tillbaka"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Startsida"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Meny"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"När du delar, spelar in eller castar har Android åtkomst till allt som visas på skärmen eller spelas upp på enheten. Var försiktig med sådant som lösenord, betalningsuppgifter, meddelanden, foton och ljud och video."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"När du delar, spelar in eller castar en app har Android åtkomst till allt som visas eller spelas upp i appen. Var försiktig med sådant som lösenord, betalningsuppgifter, meddelanden, foton och ljud och video."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Börja"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blockeras av IT-administratören"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Skärminspelning är inaktiverat av enhetspolicyn"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Rensa alla"</string>
diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml
index 892d369..abed50d 100644
--- a/packages/SystemUI/res/values-sw/strings.xml
+++ b/packages/SystemUI/res/values-sw/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Gusa ili uangalie"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Hitilafu imetokea wakati wa kuhifadhi rekodi ya skrini"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Hitilafu imetokea wakati wa kuanza kurekodi skrini"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Unatazama kwenye skrini nzima"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Ili uondoke, telezesha kidole kutoka juu hadi chini."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Nimeelewa"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Nyuma"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Nyumbani"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Menyu"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Unaposhiriki, kurekodi au kutuma, Android inaweza kufikia kitu chochote kitakachoonekana kwenye skrini yako au kuchezwa kwenye kifaa chako. Kwa hivyo kuwa mwangalifu na vitu kama vile manenosiri, maelezo ya malipo, ujumbe, picha na sauti na video."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Unaposhiriki, kurekodi au kutuma programu, Android inaweza kufikia kitu chochote kitakachoonekana au kuchezwa kwenye programu hiyo. Kwa hivyo kuwa mwangalifu na vitu kama vile manenosiri, maelezo ya malipo, ujumbe, picha na sauti na video."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Anza"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Umezuiwa na msimamizi wako wa TEHAMA"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Mchakato wa kurekodi skrini umezimwa na sera ya kifaa"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Futa zote"</string>
diff --git a/packages/SystemUI/res/values-sw600dp/dimens.xml b/packages/SystemUI/res/values-sw600dp/dimens.xml
index 65a08b9..2b1d9d6 100644
--- a/packages/SystemUI/res/values-sw600dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp/dimens.xml
@@ -77,6 +77,9 @@
<dimen name="large_dialog_width">472dp</dimen>
<dimen name="large_screen_shade_header_height">42dp</dimen>
+ <!-- start padding is smaller to account for status icon margins coming from drawable itself -->
+ <dimen name="shade_header_system_icons_padding_start">11dp</dimen>
+ <dimen name="shade_header_system_icons_padding_end">12dp</dimen>
<!-- Lockscreen shade transition values -->
<dimen name="lockscreen_shade_transition_by_tap_distance">200dp</dimen>
diff --git a/packages/SystemUI/res/values-sw720dp/dimens.xml b/packages/SystemUI/res/values-sw720dp/dimens.xml
index d053a7a..de913ac 100644
--- a/packages/SystemUI/res/values-sw720dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw720dp/dimens.xml
@@ -20,13 +20,16 @@
<dimen name="status_bar_icons_padding_start">10dp</dimen>
<!-- gap on either side of status bar notification icons -->
- <dimen name="status_bar_icon_horizontal_margin">1dp</dimen>
+ <dimen name="status_bar_icon_horizontal_margin">1sp</dimen>
<dimen name="controls_header_horizontal_padding">28dp</dimen>
<dimen name="controls_content_margin_horizontal">40dp</dimen>
<dimen name="large_screen_shade_header_height">56dp</dimen>
+ <!-- it's a bit smaller on 720dp to account for status_bar_icon_horizontal_margin -->
+ <dimen name="shade_header_system_icons_padding_start">10dp</dimen>
+
<!-- Biometric Auth pattern view size, better to align keyguard_security_width -->
<dimen name="biometric_auth_pattern_view_size">348dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values-ta/strings.xml b/packages/SystemUI/res/values-ta/strings.xml
index 3378e13..8d930a5 100644
--- a/packages/SystemUI/res/values-ta/strings.xml
+++ b/packages/SystemUI/res/values-ta/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"பார்க்கத் தட்டவும்"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"ஸ்கிரீன் ரெக்கார்டிங்கைச் சேமிப்பதில் பிழை ஏற்பட்டது"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"ஸ்கிரீன் ரெக்கார்டிங்கைத் தொடங்குவதில் பிழை"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"முழுத் திரையில் காட்டுகிறது"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"வெளியேற, மேலிருந்து கீழே ஸ்வைப் செய்யவும்"</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"புரிந்தது"</string>
<string name="accessibility_back" msgid="6530104400086152611">"பின்செல்"</string>
<string name="accessibility_home" msgid="5430449841237966217">"முகப்பு"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"மெனு"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"நீங்கள் பகிரும்போதோ ரெக்கார்டு செய்யும்போதோ அலைபரப்பும்போதோ உங்கள் திரையில் காட்டப்படுகின்ற அல்லது சாதனத்தில் பிளே செய்யப்படுகின்ற அனைத்தையும் Android அணுக முடியும். எனவே கடவுச்சொற்கள், பேமெண்ட் விவரங்கள், மெசேஜ்கள், படங்கள், ஆடியோ, வீடியோ போன்றவை குறித்துக் கவனத்துடன் இருங்கள்."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"நீங்கள் ஓர் ஆப்ஸைப் பகிரும்போதோ ரெக்கார்டு செய்யும்போதோ அலைபரப்பும்போதோ அந்த ஆப்ஸில் காட்டப்படுகின்ற அல்லது பிளே செய்யப்படுகின்ற அனைத்தையும் Android அணுக முடியும். எனவே கடவுச்சொற்கள், பேமெண்ட் விவரங்கள், மெசேஜ்கள், படங்கள், ஆடியோ, வீடியோ போன்றவை குறித்துக் கவனத்துடன் இருங்கள்."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"தொடங்கு"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"உங்கள் IT நிர்வாகி தடுத்துள்ளார்"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"\'திரையைப் படமெடுத்தல்\' சாதனக் கொள்கையின்படி முடக்கப்பட்டுள்ளது"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"எல்லாவற்றையும் அழி"</string>
diff --git a/packages/SystemUI/res/values-te/strings.xml b/packages/SystemUI/res/values-te/strings.xml
index 9e50548..f90a435 100644
--- a/packages/SystemUI/res/values-te/strings.xml
+++ b/packages/SystemUI/res/values-te/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"చూడటానికి ట్యాప్ చేయండి"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"స్క్రీన్ రికార్డింగ్ను సేవ్ చేయడంలో ఎర్రర్ ఏర్పడింది"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"స్క్రీన్ రికార్డింగ్ ప్రారంభించడంలో ఎర్రర్ ఏర్పడింది"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"ఫుల్ స్క్రీన్లో చూస్తున్నారు"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"నిష్క్రమించడానికి, పై నుండి కిందికి స్వైప్ చేయండి."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"సరే"</string>
<string name="accessibility_back" msgid="6530104400086152611">"వెనుకకు"</string>
<string name="accessibility_home" msgid="5430449841237966217">"హోమ్"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"మెనూ"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"మీరు షేర్ చేస్తున్నప్పుడు, రికార్డ్ చేస్తున్నప్పుడు, లేదా ప్రసారం చేస్తున్నప్పుడు, మీ స్క్రీన్పై కనిపించే దేనికైనా లేదా మీ పరికరంలో ప్లే అయిన దేనికైనా Androidకు యాక్సెస్ ఉంటుంది. కాబట్టి పాస్వర్డ్లు, పేమెంట్ వివరాలు, మెసేజ్లు, ఫోటోలు, ఆడియో, ఇంకా వీడియో వంటి విషయాల్లో జాగ్రత్త వహించండి."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"మీరు ఏదైనా యాప్ను షేర్ చేస్తున్నప్పుడు, రికార్డ్ చేస్తున్నప్పుడు, లేదా ప్రసారం చేస్తున్నప్పుడు, ఆ యాప్లో చూపబడిన దేనికైనా లేదా ప్లే అయిన దేనికైనా Androidకు యాక్సెస్ ఉంటుంది. కాబట్టి పాస్వర్డ్లు, పేమెంట్ వివరాలు, మెసేజ్లు, ఫోటోలు, ఆడియో, ఇంకా వీడియో వంటి విషయాల్లో జాగ్రత్త వహించండి."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"ప్రారంభించండి"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"మీ IT అడ్మిన్ ద్వారా బ్లాక్ చేయబడింది"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"పరికర పాలసీ ద్వారా స్క్రీన్ క్యాప్చర్ చేయడం డిజేబుల్ చేయబడింది"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"అన్నీ క్లియర్ చేయండి"</string>
diff --git a/packages/SystemUI/res/values-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml
index 554324a..8043792 100644
--- a/packages/SystemUI/res/values-th/strings.xml
+++ b/packages/SystemUI/res/values-th/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"แตะเพื่อดู"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"เกิดข้อผิดพลาดในการบันทึกหน้าจอ"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"เกิดข้อผิดพลาดขณะเริ่มบันทึกหน้าจอ"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"กำลังดูแบบเต็มหน้าจอ"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"หากต้องการออก ให้เลื่อนลงจากด้านบน"</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"รับทราบ"</string>
<string name="accessibility_back" msgid="6530104400086152611">"กลับ"</string>
<string name="accessibility_home" msgid="5430449841237966217">"หน้าแรก"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"เมนู"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"เมื่อกำลังแชร์ บันทึก หรือแคสต์ Android จะมีสิทธิ์เข้าถึงทุกสิ่งที่ปรากฏบนหน้าจอหรือเล่นอยู่ในอุปกรณ์ ดังนั้นโปรดระวังสิ่งต่างๆ อย่างเช่นรหัสผ่าน รายละเอียดการชำระเงิน ข้อความ รูปภาพ รวมถึงเสียงและวิดีโอ"</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"เมื่อกำลังแชร์ บันทึก หรือแคสต์แอป Android จะมีสิทธิ์เข้าถึงทุกสิ่งที่แสดงหรือเล่นอยู่ในแอปดังกล่าว ดังนั้นโปรดระวังสิ่งต่างๆ อย่างเช่นรหัสผ่าน รายละเอียดการชำระเงิน ข้อความ รูปภาพ รวมถึงเสียงและวิดีโอ"</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"เริ่ม"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"ผู้ดูแลระบบไอทีบล็อกไว้"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"การจับภาพหน้าจอปิดใช้โดยนโยบายด้านอุปกรณ์"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"ล้างทั้งหมด"</string>
diff --git a/packages/SystemUI/res/values-tl/strings.xml b/packages/SystemUI/res/values-tl/strings.xml
index 2c632ec..835c864 100644
--- a/packages/SystemUI/res/values-tl/strings.xml
+++ b/packages/SystemUI/res/values-tl/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"I-tap para tingnan"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Nagka-error sa pag-save ng recording ng screen"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Nagkaroon ng error sa pagsisimula ng pag-record ng screen"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Panonood sa full screen"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Upang lumabas, mag-swipe mula sa itaas pababa."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Nakuha ko"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Bumalik"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Home"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Menu"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Kapag nagbabahagi, nagre-record, o nagka-cast ka, may access ang Android sa kahit anong nakikita sa iyong screen o pine-play sa device mo. Kaya mag-ingat sa mga bagay-bagay tulad ng mga password, detalye ng pagbabayad, mensahe, larawan, at audio at video."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Kapag nagbabahagi, nagre-record, o nagka-cast ka ng app, may access ang Android sa kahit anong ipinapakita o pine-play sa app na iyon. Kaya mag-ingat sa mga bagay-bagay tulad ng mga password, detalye ng pagbabayad, mensahe, larawan, at audio at video."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Simulan"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Na-block ng iyong IT admin"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Naka-disable ang pag-screen capture ayon sa patakaran ng device"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"I-clear lahat"</string>
diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml
index 8430771..3f05efa 100644
--- a/packages/SystemUI/res/values-tr/strings.xml
+++ b/packages/SystemUI/res/values-tr/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Görüntülemek için dokunun"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Ekran kaydı saklanırken hata oluştu"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Ekran kaydı başlatılırken hata oluştu"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Tam ekran olarak görüntüleme"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Çıkmak için yukarıdan aşağıya doğru hızlıca kaydırın."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Anladım"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Geri"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Ana sayfa"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Menü"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Paylaşma, kaydetme veya yayınlama özelliğini kullandığınızda Android, ekranınızda gösterilen veya cihazınızda oynatılan her şeye erişebilir. Bu nedenle şifre, ödeme ayrıntıları, mesaj, fotoğraf, ses ve video gibi öğeler konusunda dikkatli olun."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Bir uygulamayı paylaştığınızda, kaydettiğinizde veya yayınladığınızda Android, söz konusu uygulamada gösterilen veya oynatılan her şeye erişebilir. Bu nedenle şifre, ödeme ayrıntıları, mesaj, fotoğraf, ses ve video gibi öğeler konusunda dikkatli olun."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Başlat"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"BT yöneticiniz tarafından engellendi"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Ekran görüntüsü alma, cihaz politikası tarafından devre dışı bırakıldı"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Tümünü temizle"</string>
diff --git a/packages/SystemUI/res/values-uk/strings.xml b/packages/SystemUI/res/values-uk/strings.xml
index b3ee948..d4b9c34 100644
--- a/packages/SystemUI/res/values-uk/strings.xml
+++ b/packages/SystemUI/res/values-uk/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Натисніть, щоб переглянути"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Не вдалося зберегти запис відео з екрана"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Не вдалося почати запис екрана"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Перегляд на весь екран"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Щоб вийти, проведіть пальцем зверху вниз."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"OK"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Назад"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Головна"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Меню"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Коли ви показуєте, записуєте або транслюєте екран, ОС Android отримує доступ до всього, що відображається на ньому чи відтворюється на пристрої. Тому будьте уважні з паролями, повідомленнями, фотографіями, аудіо, відео, платіжною інформацією тощо."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Коли ви показуєте, записуєте або транслюєте додаток, ОС Android отримує доступ до всього, що відображається або відтворюється в ньому. Тому будьте уважні з паролями, повідомленнями, фотографіями, аудіо, відео, платіжною інформацією тощо."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Почати"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Заблоковано адміністратором"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Запис екрана вимкнено згідно з правилами для пристрою"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Очистити все"</string>
diff --git a/packages/SystemUI/res/values-ur/strings.xml b/packages/SystemUI/res/values-ur/strings.xml
index 3f47187..acf1357 100644
--- a/packages/SystemUI/res/values-ur/strings.xml
+++ b/packages/SystemUI/res/values-ur/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"دیکھنے کے لیے تھپتھپائیں"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"اسکرین ریکارڈنگ محفوظ کرنے میں خرابی"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"اسکرین ریکارڈنگ شروع کرنے میں خرابی"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"فُل اسکرین میں دیکھ رہے ہیں"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"باہر نکلنے کیلئے اوپر سے نیچے کی طرف سوائپ کریں۔"</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"سمجھ آ گئی"</string>
<string name="accessibility_back" msgid="6530104400086152611">"واپس جائیں"</string>
<string name="accessibility_home" msgid="5430449841237966217">"ہوم"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"مینیو"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"جب آپ اشتراک، ریکارڈنگ یا کاسٹ کر رہے ہوتے ہیں تو Android کو آپ کی اسکرین پر دکھائی دینے والی یا آپ کے آلے پر چلائی گئی ہر چیز تک رسائی حاصل ہوتی ہے۔ لہذا، پاس ورڈز، ادائیگی کی تفصیلات، پیغامات، تصاویر، ساتھ ہی آڈیو اور ویڈیو جیسی چیزوں کے سلسلے میں محتاط رہیں۔"</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"جب آپ اشتراک، ریکارڈنگ یا کسی ایپ کو کاسٹ کر رہے ہوتے ہیں تو Android کو اس ایپ پر دکھائی گئی یا چلائی گئی ہر چیز تک رسائی حاصل ہوتی ہے۔ لہذا، پاس ورڈز، ادائیگی کی تفصیلات، پیغامات، تصاویر، ساتھ ہی آڈیو اور ویڈیو جیسی چیزوں کے سلسلے میں محتاط رہیں۔"</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"شروع کریں"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"آپ کے IT منتظم نے مسدود کر دیا"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"اسکرین کو کیپچر کرنا آلہ کی پالیسی کے ذریعے غیر فعال ہے"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"سبھی کو صاف کریں"</string>
diff --git a/packages/SystemUI/res/values-uz/strings.xml b/packages/SystemUI/res/values-uz/strings.xml
index 499eb1b..2af9e92 100644
--- a/packages/SystemUI/res/values-uz/strings.xml
+++ b/packages/SystemUI/res/values-uz/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Koʻrish uchun bosing"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Ekran yozuvi saqlanmadi"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Ekranni yozib olish boshlanmadi"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Butun ekranli rejim"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Chiqish uchun tepadan pastga torting."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"OK"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Orqaga"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Uyga"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Menyu"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Ulashish, yozib olish va translatsiya qilish vaqtida Android ekranda chiqadigan yoki qurilmada ijro qilinadigan kontentni koʻra oladi. Shu sababli parollar, toʻlov tafsilotlari, xabarlar, suratlar, audio va video chiqmasligi uchun ehtiyot boʻling."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Ilovani ulashish, yozib olish yoki translatsiya qilayotganingizda Android ekranda chiqadigan yoki qurilmada ijro qilinadigan kontentni koʻra oladi. Shu sababli parollar, toʻlov tafsilotlari, xabarlar, suratlar, audio va video chiqmasligi uchun ehtiyot boʻling."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Boshlash"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"AT administratoringiz tomonidan bloklangan"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Ekranni tasvirga olish qurilmadan foydalanish tartibi tomonidan faolsizlantirilgan"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Hammasini tozalash"</string>
diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml
index 1d96b1e..791337d 100644
--- a/packages/SystemUI/res/values-vi/strings.xml
+++ b/packages/SystemUI/res/values-vi/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Nhấn để xem"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Có lỗi xảy ra khi lưu video ghi màn hình"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Lỗi khi bắt đầu ghi màn hình"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Xem toàn màn hình"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Để thoát, hãy vuốt từ trên cùng xuống dưới."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"OK"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Quay lại"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Trang chủ"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Menu"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Khi bạn chia sẻ, ghi hoặc truyền, Android sẽ có quyền truy cập vào mọi nội dung xuất hiện trên màn hình hoặc phát trên thiết bị của bạn. Vì vậy, hãy thận trọng để không làm lộ thông tin như mật khẩu, thông tin thanh toán, tin nhắn, ảnh, âm thanh và video."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Khi bạn chia sẻ, ghi hoặc truyền ứng dụng, Android sẽ có quyền truy cập vào mọi nội dung xuất hiện hoặc phát trên ứng dụng đó. Vì vậy, hãy thận trọng để không làm lộ các thông tin như mật khẩu, thông tin thanh toán, tin nhắn, ảnh, âm thanh và video."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Bắt đầu"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Bị quản trị viên CNTT chặn"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Tính năng chụp ảnh màn hình đã bị tắt theo chính sách thiết bị"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Xóa tất cả"</string>
diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml
index 0699551..bc289ee 100644
--- a/packages/SystemUI/res/values-zh-rCN/strings.xml
+++ b/packages/SystemUI/res/values-zh-rCN/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"点按即可查看"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"保存屏幕录制内容时出错"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"启动屏幕录制时出错"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"目前处于全屏模式"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"要退出,请从顶部向下滑动。"</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"知道了"</string>
<string name="accessibility_back" msgid="6530104400086152611">"返回"</string>
<string name="accessibility_home" msgid="5430449841237966217">"主屏幕"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"菜单"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"在分享内容时,Android 可以访问屏幕上显示或设备中播放的所有内容。因此,请务必小心操作,谨防密码、付款信息、消息、照片、音频和视频等内容遭到泄露。"</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"在分享、录制或投放内容时,Android 可以访问通过此应用显示或播放的所有内容。因此,请务必小心操作,谨防密码、付款信息、消息、照片、音频和视频等内容遭到泄露。"</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"开始"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"已被 IT 管理员禁止"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"设备政策已停用屏幕截图功能"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"全部清除"</string>
diff --git a/packages/SystemUI/res/values-zh-rHK/strings.xml b/packages/SystemUI/res/values-zh-rHK/strings.xml
index 7160b56..8db6dbd 100644
--- a/packages/SystemUI/res/values-zh-rHK/strings.xml
+++ b/packages/SystemUI/res/values-zh-rHK/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"輕按即可查看"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"儲存螢幕錄影時發生錯誤"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"開始錄影畫面時發生錯誤"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"開啟全螢幕"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"由頂部向下滑動即可退出。"</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"知道了"</string>
<string name="accessibility_back" msgid="6530104400086152611">"返回"</string>
<string name="accessibility_home" msgid="5430449841237966217">"首頁"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"選單"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"當你分享、錄影或投放時,Android 可存取顯示在螢幕畫面上或在裝置上播放的所有內容。因此,請謹慎處理密碼、付款資料、訊息、相片、音訊和影片等。"</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"當你分享、錄影或投放應用程式時,Android 可存取顯示在該應用程式中顯示或播放的所有內容。因此,請謹慎處理密碼、付款資料、訊息、相片、音訊和影片等。"</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"開始"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"已被你的 IT 管理員封鎖"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"螢幕截圖功能因裝置政策而停用"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"全部清除"</string>
diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml
index 0deba33..9ba2aa9 100644
--- a/packages/SystemUI/res/values-zh-rTW/strings.xml
+++ b/packages/SystemUI/res/values-zh-rTW/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"輕觸即可查看"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"儲存螢幕錄影內容時發生錯誤"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"開始錄製螢幕畫面時發生錯誤"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"以全螢幕檢視"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"如要退出,請從畫面頂端向下滑動。"</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"知道了"</string>
<string name="accessibility_back" msgid="6530104400086152611">"返回"</string>
<string name="accessibility_home" msgid="5430449841237966217">"主畫面"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"選單"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"當你分享、錄製或投放內容時,Android 將可存取畫面上顯示的任何資訊或裝置播放的任何內容。因此,請謹慎處理密碼、付款資料、訊息、相片和影音內容等資訊。"</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"當你分享、錄製或投放內容時,Android 可存取應用程式中顯示的任何資訊或播放的任何內容。因此,請謹慎處理密碼、付款資料、訊息、相片和影音內容等資訊。"</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"開始"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"IT 管理員已封鎖這項操作"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"根據裝置政策規定,螢幕畫面擷取功能已停用"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"全部清除"</string>
diff --git a/packages/SystemUI/res/values-zu/strings.xml b/packages/SystemUI/res/values-zu/strings.xml
index eb8ee45..0e6bde2 100644
--- a/packages/SystemUI/res/values-zu/strings.xml
+++ b/packages/SystemUI/res/values-zu/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Thepha ukuze ubuke"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Iphutha lokulondoloza okokuqopha iskrini"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Iphutha lokuqala ukurekhoda isikrini"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Ukubuka isikrini esigcwele"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Ukuze uphume, swayiphela phansi kusuka phezulu."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Ngiyezwa"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Emuva"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Ekhaya"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Imenyu"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Uma wabelana, ukurekhoda, noma ukusakaza, i-Android inokufinyelela kunoma yini ebonakala esikrinini sakho noma okudlalwayo kudivayisi yakho. Ngakho-ke qaphela ngezinto ezifana namaphasiwedi, imininingwane yokukhokha, imilayezo, izithombe, nomsindo nevidiyo."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Uma wabelana, ukurekhoda, noma ukusakaza ku-app, i-Android inokufinyelela kunoma yini eboniswayo noma edlalwa kuleyo app. Ngakho-ke qaphela ngezinto ezfana namaphasiwedi, imininingwane yokukhokha, imilayezo, izithombe, nomsindo nevidiyo."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Qala"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Kuvinjelwe ngumlawuli wakho we-IT"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Ukuthwebula isikrini kukhutshazwe yinqubomgomo yedivayisi"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Sula konke"</string>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index b102102..3366f4f 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -121,24 +121,26 @@
<dimen name="navigation_edge_cancelled_arrow_height">0dp</dimen>
<dimen name="navigation_edge_cancelled_edge_corners">6dp</dimen>
- <!-- Height of notification icons in the status bar -->
+ <!-- New sp height of notification icons in the status bar -->
+ <dimen name="status_bar_icon_size_sp">@*android:dimen/status_bar_icon_size_sp</dimen>
+ <!-- Original dp height of notification icons in the status bar -->
<dimen name="status_bar_icon_size">@*android:dimen/status_bar_icon_size</dimen>
<!-- Default horizontal drawable padding for status bar icons. -->
- <dimen name="status_bar_horizontal_padding">2.5dp</dimen>
+ <dimen name="status_bar_horizontal_padding">2.5sp</dimen>
<!-- Height of the battery icon in the status bar. -->
- <dimen name="status_bar_battery_icon_height">13.0dp</dimen>
+ <dimen name="status_bar_battery_icon_height">13.0sp</dimen>
<!-- Width of the battery icon in the status bar. The battery drawable assumes a 12x20 canvas,
- so the width of the icon should be 13.0dp * (12.0 / 20.0) -->
- <dimen name="status_bar_battery_icon_width">7.8dp</dimen>
+ so the width of the icon should be 13.0sp * (12.0 / 20.0) -->
+ <dimen name="status_bar_battery_icon_width">7.8sp</dimen>
- <!-- The battery icon is 13dp tall, but the other system icons are 15dp tall (see
+ <!-- The battery icon is 13sp tall, but the other system icons are 15sp tall (see
@*android:dimen/status_bar_system_icon_size) with some top and bottom padding embedded in
the drawables themselves. So, the battery icon may need an extra 1dp of spacing so that its
bottom still aligns with the bottom of all the other system icons. See b/258672854. -->
- <dimen name="status_bar_battery_extra_vertical_spacing">1dp</dimen>
+ <dimen name="status_bar_battery_extra_vertical_spacing">1sp</dimen>
<!-- The font size for the clock in the status bar. -->
<dimen name="status_bar_clock_size">14sp</dimen>
@@ -153,19 +155,26 @@
<dimen name="status_bar_left_clock_starting_padding">0dp</dimen>
<!-- End padding for left-aligned status bar clock -->
- <dimen name="status_bar_left_clock_end_padding">2dp</dimen>
+ <dimen name="status_bar_left_clock_end_padding">2sp</dimen>
<!-- Spacing after the wifi signals that is present if there are any icons following it. -->
- <dimen name="status_bar_wifi_signal_spacer_width">2.5dp</dimen>
+ <dimen name="status_bar_wifi_signal_spacer_width">2.5sp</dimen>
+ <!-- Size of the view displaying the wifi inout icon in the status bar. -->
+ <dimen name="status_bar_wifi_inout_container_size">17sp</dimen>
<!-- Size of the view displaying the wifi signal icon in the status bar. -->
- <dimen name="status_bar_wifi_signal_size">@*android:dimen/status_bar_system_icon_size</dimen>
+ <dimen name="status_bar_wifi_signal_size">13sp</dimen>
+
+ <!-- Size of the view displaying the mobile inout icon in the status bar. -->
+ <dimen name="status_bar_mobile_inout_container_size">17sp</dimen>
+ <!-- Size of the view displaying the mobile signal icon in the status bar. -->
+ <dimen name="status_bar_mobile_signal_size">13sp</dimen>
<!-- Spacing before the airplane mode icon if there are any icons preceding it. -->
- <dimen name="status_bar_airplane_spacer_width">4dp</dimen>
+ <dimen name="status_bar_airplane_spacer_width">4sp</dimen>
<!-- Spacing between system icons. -->
- <dimen name="status_bar_system_icon_spacing">0dp</dimen>
+ <dimen name="status_bar_system_icon_spacing">0sp</dimen>
<!-- The amount to scale each of the status bar icons by. A value of 1 means no scaling. -->
<item name="status_bar_icon_scale_factor" format="float" type="dimen">1.0</item>
@@ -338,7 +347,7 @@
<dimen name="status_bar_icons_padding_top">8dp</dimen>
<!-- gap on either side of status bar notification icons -->
- <dimen name="status_bar_icon_horizontal_margin">0dp</dimen>
+ <dimen name="status_bar_icon_horizontal_margin">0sp</dimen>
<!-- the padding on the start of the statusbar -->
<dimen name="status_bar_padding_start">8dp</dimen>
@@ -350,10 +359,10 @@
<dimen name="status_bar_padding_top">0dp</dimen>
<!-- the radius of the overflow dot in the status bar -->
- <dimen name="overflow_dot_radius">2dp</dimen>
+ <dimen name="overflow_dot_radius">2sp</dimen>
<!-- the padding between dots in the icon overflow -->
- <dimen name="overflow_icon_dot_padding">3dp</dimen>
+ <dimen name="overflow_icon_dot_padding">3sp</dimen>
<!-- Dimensions related to screenshots -->
@@ -473,6 +482,10 @@
<dimen name="large_screen_shade_header_height">48dp</dimen>
<dimen name="large_screen_shade_header_min_height">@dimen/qs_header_row_min_height</dimen>
<dimen name="large_screen_shade_header_left_padding">@dimen/qs_horizontal_margin</dimen>
+ <dimen name="shade_header_system_icons_height">@dimen/large_screen_shade_header_min_height</dimen>
+ <dimen name="shade_header_system_icons_height_large_screen">32dp</dimen>
+ <dimen name="shade_header_system_icons_padding_start">0dp</dimen>
+ <dimen name="shade_header_system_icons_padding_end">0dp</dimen>
<!-- The top margin of the panel that holds the list of notifications.
On phones it's always 0dp but it's overridden in Car UI
@@ -650,9 +663,6 @@
<dimen name="restricted_padlock_pading">4dp</dimen>
- <!-- How far the expanded QS panel peeks from the header in collapsed state. -->
- <dimen name="qs_peek_height">0dp</dimen>
-
<!-- Padding between subtitles and the following text in the QSFooter dialog -->
<dimen name="qs_footer_dialog_subtitle_padding">20dp</dimen>
@@ -734,8 +744,6 @@
<dimen name="keyguard_clock_switch_y_shift">14dp</dimen>
<!-- When large clock is showing, offset the smartspace by this amount -->
<dimen name="keyguard_smartspace_top_offset">12dp</dimen>
- <!-- With the large clock, move up slightly from the center -->
- <dimen name="keyguard_large_clock_top_margin">-60dp</dimen>
<dimen name="notification_scrim_corner_radius">32dp</dimen>
@@ -841,7 +849,7 @@
<!-- Padding between the mobile signal indicator and the start icon when the roaming icon
is displayed in the upper left corner. -->
- <dimen name="roaming_icon_start_padding">2dp</dimen>
+ <dimen name="roaming_icon_start_padding">2sp</dimen>
<!-- Extra padding between the mobile data type icon and the strength indicator when the data
type icon is wide for the tile in quick settings. -->
@@ -1067,7 +1075,7 @@
<!-- Margin between icons of Ongoing App Ops chip -->
<dimen name="ongoing_appops_chip_icon_margin">4dp</dimen>
<!-- Icon size of Ongoing App Ops chip -->
- <dimen name="ongoing_appops_chip_icon_size">16dp</dimen>
+ <dimen name="ongoing_appops_chip_icon_size">16sp</dimen>
<!-- Radius of Ongoing App Ops chip corners -->
<dimen name="ongoing_appops_chip_bg_corner_radius">28dp</dimen>
<!-- One or two privacy items -->
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index 134a7a9..3a2177a 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -211,6 +211,7 @@
<item type="id" name="keyguard_indication_area" />
<item type="id" name="keyguard_indication_text" />
<item type="id" name="keyguard_indication_text_bottom" />
+ <item type="id" name="nssl_guideline" />
<item type="id" name="lock_icon" />
<item type="id" name="lock_icon_bg" />
</resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 81012a75..f8c13b0 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1114,6 +1114,16 @@
<!-- System sharing media projection permission button to continue. [CHAR LIMIT=60] -->
<string name="media_projection_entry_generic_permission_dialog_continue">Start</string>
+ <!-- Task switcher notification -->
+ <!-- Task switcher notification text. [CHAR LIMIT=100] -->
+ <string name="media_projection_task_switcher_text">Sharing pauses when you switch apps</string>
+ <!-- The action for switching to the foreground task. [CHAR LIMIT=40] -->
+ <string name="media_projection_task_switcher_action_switch">Share this app instead</string>
+ <!-- The action for switching back to the projected task. [CHAR LIMIT=40] -->
+ <string name="media_projection_task_switcher_action_back">Switch back</string>
+ <!-- Task switcher notification channel name. [CHAR LIMIT=40] -->
+ <string name="media_projection_task_switcher_notification_channel">App switch</string>
+
<!-- Title for the dialog that is shown when screen capturing is disabled by enterprise policy. [CHAR LIMIT=100] -->
<string name="screen_capturing_disabled_by_policy_dialog_title">Blocked by your IT admin</string>
diff --git a/packages/SystemUI/res/xml/large_screen_shade_header.xml b/packages/SystemUI/res/xml/large_screen_shade_header.xml
index 39f4c81..cb2c3a1 100644
--- a/packages/SystemUI/res/xml/large_screen_shade_header.xml
+++ b/packages/SystemUI/res/xml/large_screen_shade_header.xml
@@ -56,7 +56,7 @@
<Constraint android:id="@+id/shade_header_system_icons">
<Layout
android:layout_width="wrap_content"
- android:layout_height="@dimen/large_screen_shade_header_min_height"
+ android:layout_height="@dimen/shade_header_system_icons_height_large_screen"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/privacy_container"
app:layout_constraintTop_toTopOf="parent"
diff --git a/packages/SystemUI/res/xml/media_recommendation_collapsed.xml b/packages/SystemUI/res/xml/media_recommendation_collapsed.xml
deleted file mode 100644
index b7d4b3a..0000000
--- a/packages/SystemUI/res/xml/media_recommendation_collapsed.xml
+++ /dev/null
@@ -1,101 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2020 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License
- -->
-<ConstraintSet
- xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto" >
-
- <Constraint
- android:id="@+id/sizing_view"
- android:layout_width="match_parent"
- android:layout_height="@dimen/qs_media_session_height_collapsed"
- />
-
- <!-- Only the constraintBottom and marginBottom are different. The rest of the constraints are
- the same as the constraints in media_recommendations_expanded.xml. But, due to how
- ConstraintSets work, all the constraints need to be in the same place. So, the shared
- constraints can't be put in the shared layout file media_smartspace_recommendations.xml and
- the constraints are instead duplicated between here and media_recommendations_expanded.xml.
- Ditto for the other cover containers. -->
- <Constraint
- android:id="@+id/media_cover1_container"
- app:layout_constraintBottom_toBottomOf="parent"
- android:layout_marginBottom="@dimen/qs_media_padding"
- style="@style/MediaPlayer.Recommendation.AlbumContainer"
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintEnd_toStartOf="@id/media_cover2_container"
- android:layout_marginEnd="@dimen/qs_media_rec_album_side_margin"
- app:layout_constraintHorizontal_chainStyle="packed"
- app:layout_constraintHorizontal_bias="1.0"
- app:layout_constraintVertical_bias="0.5"
- />
-
- <Constraint
- android:id="@+id/media_title1"
- android:visibility="gone"
- />
-
- <Constraint
- android:id="@+id/media_subtitle1"
- android:visibility="gone"
- />
-
- <Constraint
- android:id="@+id/media_cover2_container"
- app:layout_constraintBottom_toBottomOf="parent"
- android:layout_marginBottom="@dimen/qs_media_padding"
- style="@style/MediaPlayer.Recommendation.AlbumContainer"
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintStart_toEndOf="@id/media_cover1_container"
- app:layout_constraintEnd_toStartOf="@id/media_cover3_container"
- android:layout_marginEnd="@dimen/qs_media_rec_album_side_margin"
- app:layout_constraintVertical_bias="0.5"
- />
-
- <Constraint
- android:id="@+id/media_title2"
- android:visibility="gone"
- />
-
- <Constraint
- android:id="@+id/media_subtitle2"
- android:visibility="gone"
- />
-
- <Constraint
- android:id="@+id/media_cover3_container"
- app:layout_constraintBottom_toBottomOf="parent"
- android:layout_marginBottom="@dimen/qs_media_padding"
- style="@style/MediaPlayer.Recommendation.AlbumContainer"
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintStart_toEndOf="@id/media_cover2_container"
- app:layout_constraintEnd_toEndOf="parent"
- android:layout_marginEnd="@dimen/qs_media_padding"
- app:layout_constraintVertical_bias="0.5"
- />
-
- <Constraint
- android:id="@+id/media_title3"
- android:visibility="gone"
- />
-
- <Constraint
- android:id="@+id/media_subtitle3"
- android:visibility="gone"
- />
-
-</ConstraintSet>
diff --git a/packages/SystemUI/res/xml/media_recommendation_expanded.xml b/packages/SystemUI/res/xml/media_recommendation_expanded.xml
deleted file mode 100644
index ce25a7d..0000000
--- a/packages/SystemUI/res/xml/media_recommendation_expanded.xml
+++ /dev/null
@@ -1,123 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2020 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License
- -->
-<ConstraintSet
- xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto"
- >
-
- <Constraint
- android:id="@+id/sizing_view"
- android:layout_width="match_parent"
- android:layout_height="@dimen/qs_media_session_height_expanded"
- />
-
- <Constraint
- android:id="@+id/media_cover1_container"
- style="@style/MediaPlayer.Recommendation.AlbumContainer"
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintBottom_toTopOf="@+id/media_title1"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintEnd_toStartOf="@id/media_cover2_container"
- android:layout_marginEnd="@dimen/qs_media_rec_album_side_margin"
- app:layout_constraintHorizontal_chainStyle="packed"
- app:layout_constraintVertical_chainStyle="packed"
- app:layout_constraintHorizontal_bias="1.0"
- app:layout_constraintVertical_bias="0.4"
- />
-
- <Constraint
- android:id="@+id/media_title1"
- style="@style/MediaPlayer.Recommendation.Text.Title"
- app:layout_constraintStart_toStartOf="@+id/media_cover1_container"
- app:layout_constraintEnd_toEndOf="@+id/media_cover1_container"
- app:layout_constraintTop_toBottomOf="@+id/media_cover1_container"
- app:layout_constraintBottom_toTopOf="@+id/media_subtitle1"
- />
-
- <Constraint
- android:id="@+id/media_subtitle1"
- style="@style/MediaPlayer.Recommendation.Text.Subtitle"
- app:layout_constraintStart_toStartOf="@+id/media_cover1_container"
- app:layout_constraintEnd_toEndOf="@+id/media_cover1_container"
- app:layout_constraintTop_toBottomOf="@+id/media_title1"
- app:layout_constraintBottom_toBottomOf="parent"
- android:layout_marginBottom="@dimen/qs_media_padding"
- />
-
- <Constraint
- android:id="@+id/media_cover2_container"
- style="@style/MediaPlayer.Recommendation.AlbumContainer"
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintBottom_toTopOf="@id/media_title2"
- app:layout_constraintStart_toEndOf="@id/media_cover1_container"
- app:layout_constraintEnd_toStartOf="@id/media_cover3_container"
- android:layout_marginEnd="@dimen/qs_media_rec_album_side_margin"
- app:layout_constraintVertical_chainStyle="packed"
- app:layout_constraintVertical_bias="0.4"
- />
-
- <Constraint
- android:id="@+id/media_title2"
- style="@style/MediaPlayer.Recommendation.Text.Title"
- app:layout_constraintStart_toStartOf="@+id/media_cover2_container"
- app:layout_constraintEnd_toEndOf="@+id/media_cover2_container"
- app:layout_constraintTop_toBottomOf="@+id/media_cover2_container"
- app:layout_constraintBottom_toTopOf="@+id/media_subtitle2"
- />
-
- <Constraint
- android:id="@+id/media_subtitle2"
- style="@style/MediaPlayer.Recommendation.Text.Subtitle"
- app:layout_constraintStart_toStartOf="@+id/media_cover2_container"
- app:layout_constraintEnd_toEndOf="@+id/media_cover2_container"
- app:layout_constraintTop_toBottomOf="@+id/media_title2"
- app:layout_constraintBottom_toBottomOf="parent"
- android:layout_marginBottom="@dimen/qs_media_padding"
- />
-
- <Constraint
- android:id="@+id/media_cover3_container"
- style="@style/MediaPlayer.Recommendation.AlbumContainer"
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintBottom_toTopOf="@id/media_title3"
- app:layout_constraintStart_toEndOf="@id/media_cover2_container"
- app:layout_constraintEnd_toEndOf="parent"
- android:layout_marginEnd="@dimen/qs_media_padding"
- app:layout_constraintVertical_chainStyle="packed"
- app:layout_constraintVertical_bias="0.4"
- />
-
- <Constraint
- android:id="@+id/media_title3"
- style="@style/MediaPlayer.Recommendation.Text.Title"
- app:layout_constraintStart_toStartOf="@+id/media_cover3_container"
- app:layout_constraintEnd_toEndOf="@+id/media_cover3_container"
- app:layout_constraintTop_toBottomOf="@+id/media_cover3_container"
- app:layout_constraintBottom_toTopOf="@+id/media_subtitle3"
- />
-
- <Constraint
- android:id="@+id/media_subtitle3"
- style="@style/MediaPlayer.Recommendation.Text.Subtitle"
- app:layout_constraintStart_toStartOf="@+id/media_cover3_container"
- app:layout_constraintEnd_toEndOf="@+id/media_cover3_container"
- app:layout_constraintTop_toBottomOf="@+id/media_title3"
- app:layout_constraintBottom_toBottomOf="parent"
- android:layout_marginBottom="@dimen/qs_media_padding"
- />
-
-</ConstraintSet>
diff --git a/packages/SystemUI/res/xml/media_recommendations_view_collapsed.xml b/packages/SystemUI/res/xml/media_recommendations_collapsed.xml
similarity index 100%
rename from packages/SystemUI/res/xml/media_recommendations_view_collapsed.xml
rename to packages/SystemUI/res/xml/media_recommendations_collapsed.xml
diff --git a/packages/SystemUI/res/xml/media_recommendations_view_expanded.xml b/packages/SystemUI/res/xml/media_recommendations_expanded.xml
similarity index 100%
rename from packages/SystemUI/res/xml/media_recommendations_view_expanded.xml
rename to packages/SystemUI/res/xml/media_recommendations_expanded.xml
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSampler.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSampler.kt
index 0fbeb1a..f3296f0 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSampler.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSampler.kt
@@ -32,7 +32,7 @@
@JvmOverloads
constructor(
val sampledView: View,
- mainExecutor: Executor?,
+ val mainExecutor: Executor?,
val bgExecutor: Executor?,
val regionSamplingEnabled: Boolean,
val isLockscreen: Boolean = false,
@@ -166,7 +166,7 @@
if (isLockscreen) WallpaperManager.FLAG_LOCK
else WallpaperManager.FLAG_SYSTEM
)
- onColorsChanged(sampledRegionWithOffset, initialSampling)
+ mainExecutor?.execute { onColorsChanged(sampledRegionWithOffset, initialSampling) }
}
)
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
index ca064ef..4b14d3cf 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
@@ -42,8 +42,6 @@
"com.google.android.apps.nexuslauncher.NexusLauncherActivity";
public static final String KEY_EXTRA_SYSUI_PROXY = "extra_sysui_proxy";
- public static final String KEY_EXTRA_WINDOW_CORNER_RADIUS = "extra_window_corner_radius";
- public static final String KEY_EXTRA_SUPPORTS_WINDOW_CORNERS = "extra_supports_window_corners";
public static final String KEY_EXTRA_UNFOLD_ANIMATION_FORWARDER = "extra_unfold_animation";
// See ISysuiUnlockAnimationController.aidl
public static final String KEY_EXTRA_UNLOCK_ANIMATION_CONTROLLER = "unlock_animation";
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
deleted file mode 100644
index 74c325d..0000000
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
+++ /dev/null
@@ -1,541 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.shared.system;
-
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
-import static android.view.WindowManager.TRANSIT_CHANGE;
-import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_LOCKED;
-import static android.view.WindowManager.TRANSIT_SLEEP;
-
-import android.annotation.SuppressLint;
-import android.app.ActivityManager;
-import android.app.ActivityTaskManager;
-import android.app.IApplicationThread;
-import android.graphics.Rect;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.ArrayMap;
-import android.util.Log;
-import android.view.IRecentsAnimationController;
-import android.view.RemoteAnimationTarget;
-import android.view.SurfaceControl;
-import android.window.IRemoteTransition;
-import android.window.IRemoteTransitionFinishedCallback;
-import android.window.PictureInPictureSurfaceTransaction;
-import android.window.RemoteTransition;
-import android.window.TaskSnapshot;
-import android.window.TransitionInfo;
-import android.window.WindowContainerToken;
-import android.window.WindowContainerTransaction;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.wm.shell.util.TransitionUtil;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-
-/**
- * Helper class to build {@link RemoteTransition} objects
- */
-public class RemoteTransitionCompat {
- private static final String TAG = "RemoteTransitionCompat";
-
- /** Constructor specifically for recents animation */
- public static RemoteTransition newRemoteTransition(RecentsAnimationListener recents,
- IApplicationThread appThread) {
- IRemoteTransition remote = new IRemoteTransition.Stub() {
- final RecentsControllerWrap mRecentsSession = new RecentsControllerWrap();
- IBinder mToken = null;
-
- @Override
- public void startAnimation(IBinder transition, TransitionInfo info,
- SurfaceControl.Transaction t,
- IRemoteTransitionFinishedCallback finishedCallback) {
- // TODO(b/177438007): Move this set-up logic into launcher's animation impl.
- mToken = transition;
- mRecentsSession.start(recents, mToken, info, t, finishedCallback);
- }
-
- @Override
- public void mergeAnimation(IBinder transition, TransitionInfo info,
- SurfaceControl.Transaction t, IBinder mergeTarget,
- IRemoteTransitionFinishedCallback finishedCallback) {
- if (mergeTarget.equals(mToken) && mRecentsSession.merge(info, t)) {
- try {
- finishedCallback.onTransitionFinished(null /* wct */, null /* sct */);
- } catch (RemoteException e) {
- Log.e(TAG, "Error merging transition.", e);
- }
- // commit taskAppeared after merge transition finished.
- mRecentsSession.commitTasksAppearedIfNeeded();
- } else {
- t.close();
- info.releaseAllSurfaces();
- }
- }
- };
- return new RemoteTransition(remote, appThread, "Recents");
- }
-
- /**
- * Wrapper to hook up parts of recents animation to shell transition.
- * TODO(b/177438007): Remove this once Launcher handles shell transitions directly.
- */
- @VisibleForTesting
- static class RecentsControllerWrap extends IRecentsAnimationController.Default {
- private RecentsAnimationListener mListener = null;
- private IRemoteTransitionFinishedCallback mFinishCB = null;
-
- /**
- * List of tasks that we are switching away from via this transition. Upon finish, these
- * pausing tasks will become invisible.
- * These need to be ordered since the order must be restored if there is no task-switch.
- */
- private ArrayList<TaskState> mPausingTasks = null;
-
- /**
- * List of tasks that we are switching to. Upon finish, these will remain visible and
- * on top.
- */
- private ArrayList<TaskState> mOpeningTasks = null;
-
- private WindowContainerToken mPipTask = null;
- private WindowContainerToken mRecentsTask = null;
- private int mRecentsTaskId = 0;
- private TransitionInfo mInfo = null;
- private boolean mOpeningSeparateHome = false;
- private ArrayMap<SurfaceControl, SurfaceControl> mLeashMap = null;
- private PictureInPictureSurfaceTransaction mPipTransaction = null;
- private IBinder mTransition = null;
- private boolean mKeyguardLocked = false;
- private RemoteAnimationTarget[] mAppearedTargets;
- private boolean mWillFinishToHome = false;
-
- /** The animation is idle, waiting for the user to choose a task to switch to. */
- private static final int STATE_NORMAL = 0;
-
- /** The user chose a new task to switch to and the animation is animating to it. */
- private static final int STATE_NEW_TASK = 1;
-
- /** The latest state that the recents animation is operating in. */
- private int mState = STATE_NORMAL;
-
- void start(RecentsAnimationListener listener,
- IBinder transition, TransitionInfo info, SurfaceControl.Transaction t,
- IRemoteTransitionFinishedCallback finishedCallback) {
- if (mInfo != null) {
- throw new IllegalStateException("Trying to run a new recents animation while"
- + " recents is already active.");
- }
- mListener = listener;
- mInfo = info;
- mFinishCB = finishedCallback;
- mPausingTasks = new ArrayList<>();
- mOpeningTasks = new ArrayList<>();
- mPipTask = null;
- mRecentsTask = null;
- mRecentsTaskId = -1;
- mLeashMap = new ArrayMap<>();
- mTransition = transition;
- mKeyguardLocked = (info.getFlags() & TRANSIT_FLAG_KEYGUARD_LOCKED) != 0;
- mState = STATE_NORMAL;
-
- final ArrayList<RemoteAnimationTarget> apps = new ArrayList<>();
- final ArrayList<RemoteAnimationTarget> wallpapers = new ArrayList<>();
- TransitionUtil.LeafTaskFilter leafTaskFilter = new TransitionUtil.LeafTaskFilter();
- // About layering: we divide up the "layer space" into 3 regions (each the size of
- // the change count). This lets us categorize things into above/below/between
- // while maintaining their relative ordering.
- for (int i = 0; i < info.getChanges().size(); ++i) {
- final TransitionInfo.Change change = info.getChanges().get(i);
- final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
- if (TransitionUtil.isWallpaper(change)) {
- final RemoteAnimationTarget target = TransitionUtil.newTarget(change,
- // wallpapers go into the "below" layer space
- info.getChanges().size() - i, info, t, mLeashMap);
- wallpapers.add(target);
- // Make all the wallpapers opaque since we want them visible from the start
- t.setAlpha(target.leash, 1);
- } else if (leafTaskFilter.test(change)) {
- // start by putting everything into the "below" layer space.
- final RemoteAnimationTarget target = TransitionUtil.newTarget(change,
- info.getChanges().size() - i, info, t, mLeashMap);
- apps.add(target);
- if (TransitionUtil.isClosingType(change.getMode())) {
- // raise closing (pausing) task to "above" layer so it isn't covered
- t.setLayer(target.leash, info.getChanges().size() * 3 - i);
- mPausingTasks.add(new TaskState(change, target.leash));
- if (taskInfo.pictureInPictureParams != null
- && taskInfo.pictureInPictureParams.isAutoEnterEnabled()) {
- mPipTask = taskInfo.token;
- }
- } else if (taskInfo != null
- && taskInfo.topActivityType == ACTIVITY_TYPE_RECENTS) {
- // There's a 3p launcher, so make sure recents goes above that.
- t.setLayer(target.leash, info.getChanges().size() * 3 - i);
- mRecentsTask = taskInfo.token;
- mRecentsTaskId = taskInfo.taskId;
- } else if (taskInfo != null && taskInfo.topActivityType == ACTIVITY_TYPE_HOME) {
- mRecentsTask = taskInfo.token;
- mRecentsTaskId = taskInfo.taskId;
- } else if (TransitionUtil.isOpeningType(change.getMode())) {
- mOpeningTasks.add(new TaskState(change, target.leash));
- }
- }
- }
- t.apply();
- mListener.onAnimationStart(new RecentsAnimationControllerCompat(this),
- apps.toArray(new RemoteAnimationTarget[apps.size()]),
- wallpapers.toArray(new RemoteAnimationTarget[wallpapers.size()]),
- new Rect(0, 0, 0, 0), new Rect());
- }
-
- @SuppressLint("NewApi")
- boolean merge(TransitionInfo info, SurfaceControl.Transaction t) {
- if (info.getType() == TRANSIT_SLEEP) {
- // A sleep event means we need to stop animations immediately, so cancel here.
- mListener.onAnimationCanceled(new HashMap<>());
- finish(mWillFinishToHome, false /* userLeaveHint */);
- return false;
- }
- ArrayList<TransitionInfo.Change> openingTasks = null;
- ArrayList<TransitionInfo.Change> closingTasks = null;
- mAppearedTargets = null;
- mOpeningSeparateHome = false;
- TransitionInfo.Change recentsOpening = null;
- boolean foundRecentsClosing = false;
- boolean hasChangingApp = false;
- final TransitionUtil.LeafTaskFilter leafTaskFilter =
- new TransitionUtil.LeafTaskFilter();
- for (int i = 0; i < info.getChanges().size(); ++i) {
- final TransitionInfo.Change change = info.getChanges().get(i);
- final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
- final boolean isLeafTask = leafTaskFilter.test(change);
- if (TransitionUtil.isOpeningType(change.getMode())) {
- if (mRecentsTask.equals(change.getContainer())) {
- recentsOpening = change;
- } else if (isLeafTask) {
- if (taskInfo.topActivityType == ACTIVITY_TYPE_HOME) {
- // This is usually a 3p launcher
- mOpeningSeparateHome = true;
- }
- if (openingTasks == null) {
- openingTasks = new ArrayList<>();
- }
- openingTasks.add(change);
- }
- } else if (TransitionUtil.isClosingType(change.getMode())) {
- if (mRecentsTask.equals(change.getContainer())) {
- foundRecentsClosing = true;
- } else if (isLeafTask) {
- if (closingTasks == null) {
- closingTasks = new ArrayList<>();
- }
- closingTasks.add(change);
- }
- } else if (change.getMode() == TRANSIT_CHANGE) {
- // Finish recents animation if the display is changed, so the default
- // transition handler can play the animation such as rotation effect.
- if (change.hasFlags(TransitionInfo.FLAG_IS_DISPLAY)) {
- mListener.onSwitchToScreenshot(() -> finish(false /* toHome */,
- false /* userLeaveHint */));
- return false;
- }
- hasChangingApp = true;
- }
- }
- if (hasChangingApp && foundRecentsClosing) {
- // This happens when a visible app is expanding (usually PiP). In this case,
- // that transition probably has a special-purpose animation, so finish recents
- // now and let it do its animation (since recents is going to be occluded).
- if (!mListener.onSwitchToScreenshot(
- () -> finish(true /* toHome */, false /* userLeaveHint */))) {
- Log.w(TAG, "Recents callback doesn't support support switching to screenshot"
- + ", there might be a flicker.");
- finish(true /* toHome */, false /* userLeaveHint */);
- }
- return false;
- }
- if (recentsOpening != null) {
- // the recents task re-appeared. This happens if the user gestures before the
- // task-switch (NEW_TASK) animation finishes.
- if (mState == STATE_NORMAL) {
- Log.e(TAG, "Returning to recents while recents is already idle.");
- }
- if (closingTasks == null || closingTasks.size() == 0) {
- Log.e(TAG, "Returning to recents without closing any opening tasks.");
- }
- // Setup may hide it initially since it doesn't know that overview was still active.
- t.show(recentsOpening.getLeash());
- t.setAlpha(recentsOpening.getLeash(), 1.f);
- mState = STATE_NORMAL;
- }
- boolean didMergeThings = false;
- if (closingTasks != null) {
- // Cancelling a task-switch. Move the tasks back to mPausing from mOpening
- for (int i = 0; i < closingTasks.size(); ++i) {
- final TransitionInfo.Change change = closingTasks.get(i);
- int openingIdx = TaskState.indexOf(mOpeningTasks, change);
- if (openingIdx < 0) {
- Log.e(TAG, "Back to existing recents animation from an unrecognized "
- + "task: " + change.getTaskInfo().taskId);
- continue;
- }
- mPausingTasks.add(mOpeningTasks.remove(openingIdx));
- didMergeThings = true;
- }
- }
- if (openingTasks != null && openingTasks.size() > 0) {
- // Switching to some new tasks, add to mOpening and remove from mPausing. Also,
- // enter NEW_TASK state since this will start the switch-to animation.
- final int layer = mInfo.getChanges().size() * 3;
- final RemoteAnimationTarget[] targets =
- new RemoteAnimationTarget[openingTasks.size()];
- for (int i = 0; i < openingTasks.size(); ++i) {
- final TransitionInfo.Change change = openingTasks.get(i);
- int pausingIdx = TaskState.indexOf(mPausingTasks, change);
- if (pausingIdx >= 0) {
- // Something is showing/opening a previously-pausing app.
- targets[i] = TransitionUtil.newTarget(change, layer,
- mPausingTasks.get(pausingIdx).mLeash);
- mOpeningTasks.add(mPausingTasks.remove(pausingIdx));
- // Setup hides opening tasks initially, so make it visible again (since we
- // are already showing it).
- t.show(change.getLeash());
- t.setAlpha(change.getLeash(), 1.f);
- } else {
- // We are receiving new opening tasks, so convert to onTasksAppeared.
- targets[i] = TransitionUtil.newTarget(change, layer, info, t, mLeashMap);
- // reparent into the original `mInfo` since that's where we are animating.
- final int rootIdx = TransitionUtil.rootIndexFor(change, mInfo);
- t.reparent(targets[i].leash, mInfo.getRoot(rootIdx).getLeash());
- t.setLayer(targets[i].leash, layer);
- mOpeningTasks.add(new TaskState(change, targets[i].leash));
- }
- }
- didMergeThings = true;
- mState = STATE_NEW_TASK;
- mAppearedTargets = targets;
- }
- if (!didMergeThings) {
- // Didn't recognize anything in incoming transition so don't merge it.
- Log.w(TAG, "Don't know how to merge this transition.");
- return false;
- }
- t.apply();
- // not using the incoming anim-only surfaces
- info.releaseAnimSurfaces();
- return true;
- }
-
- private void commitTasksAppearedIfNeeded() {
- if (mAppearedTargets != null) {
- mListener.onTasksAppeared(mAppearedTargets);
- mAppearedTargets = null;
- }
- }
-
- @Override public TaskSnapshot screenshotTask(int taskId) {
- try {
- return ActivityTaskManager.getService().takeTaskSnapshot(taskId,
- true /* updateCache */);
- } catch (RemoteException e) {
- Log.e(TAG, "Failed to screenshot task", e);
- }
- return null;
- }
-
- @Override public void setInputConsumerEnabled(boolean enabled) {
- if (!enabled) return;
- // transient launches don't receive focus automatically. Since we are taking over
- // the gesture now, take focus explicitly.
- // This also moves recents back to top if the user gestured before a switch
- // animation finished.
- try {
- ActivityTaskManager.getService().setFocusedTask(mRecentsTaskId);
- } catch (RemoteException e) {
- Log.e(TAG, "Failed to set focused task", e);
- }
- }
-
- @Override public void setAnimationTargetsBehindSystemBars(boolean behindSystemBars) {
- }
-
- @Override public void setFinishTaskTransaction(int taskId,
- PictureInPictureSurfaceTransaction finishTransaction, SurfaceControl overlay) {
- mPipTransaction = finishTransaction;
- }
-
- @Override
- @SuppressLint("NewApi")
- public void finish(boolean toHome, boolean sendUserLeaveHint) {
- if (mFinishCB == null) {
- Log.e(TAG, "Duplicate call to finish", new RuntimeException());
- return;
- }
- final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
- final WindowContainerTransaction wct = new WindowContainerTransaction();
-
- if (mKeyguardLocked && mRecentsTask != null) {
- if (toHome) wct.reorder(mRecentsTask, true /* toTop */);
- else wct.restoreTransientOrder(mRecentsTask);
- }
- if (!toHome && !mWillFinishToHome && mPausingTasks != null && mState == STATE_NORMAL) {
- // The gesture is returning to the pausing-task(s) rather than continuing with
- // recents, so end the transition by moving the app back to the top (and also
- // re-showing it's task).
- for (int i = mPausingTasks.size() - 1; i >= 0; --i) {
- // reverse order so that index 0 ends up on top
- wct.reorder(mPausingTasks.get(i).mToken, true /* onTop */);
- t.show(mPausingTasks.get(i).mTaskSurface);
- }
- if (!mKeyguardLocked && mRecentsTask != null) {
- wct.restoreTransientOrder(mRecentsTask);
- }
- } else if (toHome && mOpeningSeparateHome && mPausingTasks != null) {
- // Special situation where 3p launcher was changed during recents (this happens
- // during tapltests...). Here we get both "return to home" AND "home opening".
- // This is basically going home, but we have to restore the recents and home order.
- for (int i = 0; i < mOpeningTasks.size(); ++i) {
- final TaskState state = mOpeningTasks.get(i);
- if (state.mTaskInfo.topActivityType == ACTIVITY_TYPE_HOME) {
- // Make sure it is on top.
- wct.reorder(state.mToken, true /* onTop */);
- }
- t.show(state.mTaskSurface);
- }
- for (int i = mPausingTasks.size() - 1; i >= 0; --i) {
- t.hide(mPausingTasks.get(i).mTaskSurface);
- }
- if (!mKeyguardLocked && mRecentsTask != null) {
- wct.restoreTransientOrder(mRecentsTask);
- }
- } else {
- // The general case: committing to recents, going home, or switching tasks.
- for (int i = 0; i < mOpeningTasks.size(); ++i) {
- t.show(mOpeningTasks.get(i).mTaskSurface);
- }
- for (int i = 0; i < mPausingTasks.size(); ++i) {
- if (!sendUserLeaveHint) {
- // This means recents is not *actually* finishing, so of course we gotta
- // do special stuff in WMCore to accommodate.
- wct.setDoNotPip(mPausingTasks.get(i).mToken);
- }
- // Since we will reparent out of the leashes, pre-emptively hide the child
- // surface to match the leash. Otherwise, there will be a flicker before the
- // visibility gets committed in Core when using split-screen (in splitscreen,
- // the leaf-tasks are not "independent" so aren't hidden by normal setup).
- t.hide(mPausingTasks.get(i).mTaskSurface);
- }
- if (mPipTask != null && mPipTransaction != null && sendUserLeaveHint) {
- t.show(mInfo.getChange(mPipTask).getLeash());
- PictureInPictureSurfaceTransaction.apply(mPipTransaction,
- mInfo.getChange(mPipTask).getLeash(), t);
- mPipTask = null;
- mPipTransaction = null;
- }
- }
- try {
- mFinishCB.onTransitionFinished(wct.isEmpty() ? null : wct, t);
- } catch (RemoteException e) {
- Log.e(TAG, "Failed to call animation finish callback", e);
- t.apply();
- }
- // Only release the non-local created surface references. The animator is responsible
- // for releasing the leashes created by local.
- mInfo.releaseAllSurfaces();
- // Reset all members.
- mListener = null;
- mFinishCB = null;
- mPausingTasks = null;
- mOpeningTasks = null;
- mAppearedTargets = null;
- mInfo = null;
- mOpeningSeparateHome = false;
- mLeashMap = null;
- mTransition = null;
- mState = STATE_NORMAL;
- }
-
- @Override public void setDeferCancelUntilNextTransition(boolean defer, boolean screenshot) {
- }
-
- @Override public void cleanupScreenshot() {
- }
-
- @Override public void setWillFinishToHome(boolean willFinishToHome) {
- mWillFinishToHome = willFinishToHome;
- }
-
- /**
- * @see IRecentsAnimationController#removeTask
- */
- @Override public boolean removeTask(int taskId) {
- return false;
- }
-
- /**
- * @see IRecentsAnimationController#detachNavigationBarFromApp
- */
- @Override public void detachNavigationBarFromApp(boolean moveHomeToTop) {
- try {
- ActivityTaskManager.getService().detachNavigationBarFromApp(mTransition);
- } catch (RemoteException e) {
- Log.e(TAG, "Failed to detach the navigation bar from app", e);
- }
- }
-
- /**
- * @see IRecentsAnimationController#animateNavigationBarToApp(long)
- */
- @Override public void animateNavigationBarToApp(long duration) {
- }
- }
-
- /** Utility class to track the state of a task as-seen by recents. */
- private static class TaskState {
- WindowContainerToken mToken;
- ActivityManager.RunningTaskInfo mTaskInfo;
-
- /** The surface/leash of the task provided by Core. */
- SurfaceControl mTaskSurface;
-
- /** The (local) animation-leash created for this task. */
- SurfaceControl mLeash;
-
- TaskState(TransitionInfo.Change change, SurfaceControl leash) {
- mToken = change.getContainer();
- mTaskInfo = change.getTaskInfo();
- mTaskSurface = change.getLeash();
- mLeash = leash;
- }
-
- static int indexOf(ArrayList<TaskState> list, TransitionInfo.Change change) {
- for (int i = list.size() - 1; i >= 0; --i) {
- if (list.get(i).mToken.equals(change.getContainer())) {
- return i;
- }
- }
- return -1;
- }
-
- public String toString() {
- return "" + mToken + " : " + mLeash;
- }
- }
-}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/tracing/FrameProtoTracer.java b/packages/SystemUI/shared/src/com/android/systemui/shared/tracing/FrameProtoTracer.java
deleted file mode 100644
index 98212e1..0000000
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/tracing/FrameProtoTracer.java
+++ /dev/null
@@ -1,192 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.shared.tracing;
-
-import android.os.Trace;
-import android.util.Log;
-import android.view.Choreographer;
-
-import com.android.internal.util.TraceBuffer;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.util.ArrayDeque;
-import java.util.ArrayList;
-import java.util.Queue;
-import java.util.function.Consumer;
-
-/**
- * A proto tracer implementation that can be updated directly (upon state change), or on the next
- * scheduled frame.
- *
- * @param <P> The class type of the proto provider
- * @param <S> The proto class type of the encapsulating proto
- * @param <T> The proto class type of the individual proto entries in the buffer
- * @param <R> The proto class type of the entry root proto in the buffer
- */
-public class FrameProtoTracer<P, S extends P, T extends P, R>
- implements Choreographer.FrameCallback {
-
- private static final String TAG = "FrameProtoTracer";
- private static final int BUFFER_CAPACITY = 1024 * 1024;
-
- private final Object mLock = new Object();
- private final TraceBuffer<P, S, T> mBuffer;
- private final File mTraceFile;
- private final ProtoTraceParams<P, S, T, R> mParams;
- private Choreographer mChoreographer;
- private final Queue<T> mPool = new ArrayDeque<>();
- private final ArrayList<ProtoTraceable<R>> mTraceables = new ArrayList<>();
- private final ArrayList<ProtoTraceable<R>> mTmpTraceables = new ArrayList<>();
-
- private volatile boolean mEnabled;
- private boolean mFrameScheduled;
-
- private final TraceBuffer.ProtoProvider<P, S, T> mProvider =
- new TraceBuffer.ProtoProvider<P, S, T>() {
- @Override
- public int getItemSize(P proto) {
- return mParams.getProtoSize(proto);
- }
-
- @Override
- public byte[] getBytes(P proto) {
- return mParams.getProtoBytes(proto);
- }
-
- @Override
- public void write(S encapsulatingProto, Queue<T> buffer, OutputStream os)
- throws IOException {
- os.write(mParams.serializeEncapsulatingProto(encapsulatingProto, buffer));
- }
- };
-
- public interface ProtoTraceParams<P, S, T, R> {
- File getTraceFile();
- S getEncapsulatingTraceProto();
- T updateBufferProto(T reuseObj, ArrayList<ProtoTraceable<R>> traceables);
- byte[] serializeEncapsulatingProto(S encapsulatingProto, Queue<T> buffer);
- byte[] getProtoBytes(P proto);
- int getProtoSize(P proto);
- }
-
- public FrameProtoTracer(ProtoTraceParams<P, S, T, R> params) {
- mParams = params;
- mBuffer = new TraceBuffer<>(BUFFER_CAPACITY, mProvider, new Consumer<T>() {
- @Override
- public void accept(T t) {
- onProtoDequeued(t);
- }
- });
- mTraceFile = params.getTraceFile();
- }
-
- public void start() {
- synchronized (mLock) {
- if (mEnabled) {
- return;
- }
- mBuffer.resetBuffer();
- mEnabled = true;
- }
- logState();
- }
-
- public void stop() {
- synchronized (mLock) {
- if (!mEnabled) {
- return;
- }
- mEnabled = false;
- }
- writeToFile();
- }
-
- public boolean isEnabled() {
- return mEnabled;
- }
-
- public void add(ProtoTraceable<R> traceable) {
- synchronized (mLock) {
- mTraceables.add(traceable);
- }
- }
-
- public void remove(ProtoTraceable<R> traceable) {
- synchronized (mLock) {
- mTraceables.remove(traceable);
- }
- }
-
- public void scheduleFrameUpdate() {
- if (!mEnabled || mFrameScheduled) {
- return;
- }
-
- // Schedule an update on the next frame
- if (mChoreographer == null) {
- mChoreographer = Choreographer.getMainThreadInstance();
- }
- mChoreographer.postFrameCallback(this);
- mFrameScheduled = true;
- }
-
- public void update() {
- if (!mEnabled) {
- return;
- }
-
- logState();
- }
-
- public float getBufferUsagePct() {
- return (float) mBuffer.getBufferSize() / BUFFER_CAPACITY;
- }
-
- @Override
- public void doFrame(long frameTimeNanos) {
- logState();
- }
-
- private void onProtoDequeued(T proto) {
- mPool.add(proto);
- }
-
- private void logState() {
- synchronized (mLock) {
- mTmpTraceables.addAll(mTraceables);
- }
-
- mBuffer.add(mParams.updateBufferProto(mPool.poll(), mTmpTraceables));
- mTmpTraceables.clear();
- mFrameScheduled = false;
- }
-
- private void writeToFile() {
- try {
- Trace.beginSection("ProtoTracer.writeToFile");
- mBuffer.writeTraceToFile(mTraceFile, mParams.getEncapsulatingTraceProto());
- } catch (IOException e) {
- Log.e(TAG, "Unable to write buffer to file", e);
- } finally {
- Trace.endSection();
- }
- }
-}
-
-
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/tracing/ProtoTraceable.java b/packages/SystemUI/shared/src/com/android/systemui/shared/tracing/ProtoTraceable.java
deleted file mode 100644
index e05b0b0..0000000
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/tracing/ProtoTraceable.java
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
- * except in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
- */
-
-package com.android.systemui.shared.tracing;
-
-/**
- * @see FrameProtoTracer
- */
-public interface ProtoTraceable<T> {
-
- /**
- * NOTE: Implementations should update all fields in this proto.
- */
- void writeToProto(T proto);
-}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/util/TraceUtils.kt b/packages/SystemUI/shared/src/com/android/systemui/util/TraceUtils.kt
index e02e592..96a974d 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/util/TraceUtils.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/util/TraceUtils.kt
@@ -67,7 +67,7 @@
* under a single track.
*/
inline fun <T> traceAsync(method: String, block: () -> T): T =
- traceAsync(method, "AsyncTraces", block)
+ traceAsync("AsyncTraces", method, block)
/**
* Creates an async slice in a track with [trackName] while [block] runs.
@@ -76,7 +76,7 @@
* [trackName] of the track. The track is one of the rows visible in a perfetto trace inside
* SystemUI process.
*/
- inline fun <T> traceAsync(method: String, trackName: String, block: () -> T): T {
+ inline fun <T> traceAsync(trackName: String, method: String, block: () -> T): T {
val cookie = lastCookie.incrementAndGet()
Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_APP, trackName, method, cookie)
try {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockFrame.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardClockFrame.kt
index 635f0fa..50e5466 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockFrame.kt
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockFrame.kt
@@ -12,6 +12,10 @@
) : FrameLayout(context, attrs) {
private var drawAlpha: Int = 255
+ init {
+ setLayerType(View.LAYER_TYPE_SOFTWARE, null)
+ }
+
protected override fun onSetAlpha(alpha: Int): Boolean {
drawAlpha = alpha
return true
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
index 2377057..d9b7bde 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
@@ -69,7 +69,7 @@
(long) (125 * KeyguardPatternView.DISAPPEAR_MULTIPLIER_LOCKED),
0.6f /* translationScale */,
0.45f /* delayScale */, AnimationUtils.loadInterpolator(
- mContext, android.R.interpolator.fast_out_linear_in));
+ mContext, android.R.interpolator.fast_out_linear_in));
mDisappearYTranslation = getResources().getDimensionPixelSize(
R.dimen.disappear_y_translation);
mYTrans = getResources().getDimensionPixelSize(R.dimen.pin_view_trans_y_entry);
@@ -82,8 +82,10 @@
}
void onDevicePostureChanged(@DevicePostureInt int posture) {
- mLastDevicePosture = posture;
- updateMargins();
+ if (mLastDevicePosture != posture) {
+ mLastDevicePosture = posture;
+ updateMargins();
+ }
}
@Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java
index 38c07dc..2bdf46e 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java
@@ -104,8 +104,10 @@
}
void onDevicePostureChanged(@DevicePostureInt int posture) {
- mLastDevicePosture = posture;
- updateMargins();
+ if (mLastDevicePosture != posture) {
+ mLastDevicePosture = posture;
+ updateMargins();
+ }
}
private void updateMargins() {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
index 794e694..b3e08c0 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
@@ -165,15 +165,18 @@
* Responsible for identifying if PIN hinting is to be enabled or not
*/
private boolean isPinHinting() {
- return mLockPatternUtils.getPinLength(KeyguardUpdateMonitor.getCurrentUser())
- == DEFAULT_PIN_LENGTH;
+ return mPinLength == DEFAULT_PIN_LENGTH;
}
/**
- * Responsible for identifying if auto confirm is enabled or not in Settings
+ * Responsible for identifying if auto confirm is enabled or not in Settings and
+ * a valid PIN_LENGTH is stored on the device (though the latter check is only to make it more
+ * robust since we only allow enabling PIN confirmation if the user has a valid PIN length
+ * saved on device)
*/
private boolean isAutoPinConfirmEnabledInSettings() {
//Checks if user has enabled the auto confirm in Settings
- return mLockPatternUtils.isAutoPinConfirmEnabled(KeyguardUpdateMonitor.getCurrentUser());
+ return mLockPatternUtils.isAutoPinConfirmEnabled(KeyguardUpdateMonitor.getCurrentUser())
+ && mPinLength != LockPatternUtils.PIN_LENGTH_UNAVAILABLE;
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index 6853f81..42a4e72 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -793,8 +793,6 @@
void reloadColors() {
mViewMode.reloadColors();
- setBackgroundColor(Utils.getColorAttrDefaultColor(getContext(),
- com.android.internal.R.attr.materialColorSurface));
}
/** Handles density or font scale changes. */
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 458ca2b..f952337 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -79,17 +79,25 @@
import com.android.systemui.log.SessionTracker;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.FalsingManager;
+import com.android.systemui.scene.domain.interactor.SceneInteractor;
+import com.android.systemui.scene.shared.model.SceneContainerNames;
+import com.android.systemui.scene.shared.model.SceneKey;
import com.android.systemui.shared.system.SysUiStatsLog;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.UserSwitcherController;
+import com.android.systemui.user.domain.interactor.UserInteractor;
import com.android.systemui.util.ViewController;
+import com.android.systemui.util.kotlin.JavaAdapter;
import com.android.systemui.util.settings.GlobalSettings;
import java.io.File;
import java.util.Optional;
import javax.inject.Inject;
+import javax.inject.Provider;
+
+import kotlinx.coroutines.Job;
/** Controller for {@link KeyguardSecurityContainer} */
@KeyguardBouncerScope
@@ -378,6 +386,10 @@
showPrimarySecurityScreen(false);
}
};
+ private final UserInteractor mUserInteractor;
+ private final Provider<SceneInteractor> mSceneInteractor;
+ private final Provider<JavaAdapter> mJavaAdapter;
+ @Nullable private Job mSceneTransitionCollectionJob;
@Inject
public KeyguardSecurityContainerController(KeyguardSecurityContainer view,
@@ -402,7 +414,10 @@
ViewMediatorCallback viewMediatorCallback,
AudioManager audioManager,
KeyguardFaceAuthInteractor keyguardFaceAuthInteractor,
- BouncerMessageInteractor bouncerMessageInteractor
+ BouncerMessageInteractor bouncerMessageInteractor,
+ Provider<JavaAdapter> javaAdapter,
+ UserInteractor userInteractor,
+ Provider<SceneInteractor> sceneInteractor
) {
super(view);
mLockPatternUtils = lockPatternUtils;
@@ -429,6 +444,9 @@
mAudioManager = audioManager;
mKeyguardFaceAuthInteractor = keyguardFaceAuthInteractor;
mBouncerMessageInteractor = bouncerMessageInteractor;
+ mUserInteractor = userInteractor;
+ mSceneInteractor = sceneInteractor;
+ mJavaAdapter = javaAdapter;
}
@Override
@@ -451,6 +469,24 @@
mView.setOnKeyListener(mOnKeyListener);
showPrimarySecurityScreen(false);
+
+ if (mFeatureFlags.isEnabled(Flags.SCENE_CONTAINER)) {
+ // When the scene framework transitions from bouncer to gone, we dismiss the keyguard.
+ mSceneTransitionCollectionJob = mJavaAdapter.get().alwaysCollectFlow(
+ mSceneInteractor.get().sceneTransitions(SceneContainerNames.SYSTEM_UI_DEFAULT),
+ sceneTransitionModel -> {
+ if (sceneTransitionModel != null
+ && sceneTransitionModel.getFrom() == SceneKey.Bouncer.INSTANCE
+ && sceneTransitionModel.getTo() == SceneKey.Gone.INSTANCE) {
+ final int selectedUserId = mUserInteractor.getSelectedUserId();
+ showNextSecurityScreenOrFinish(
+ /* authenticated= */ true,
+ selectedUserId,
+ /* bypassSecondaryLockScreen= */ true,
+ mSecurityModel.getSecurityMode(selectedUserId));
+ }
+ });
+ }
}
@Override
@@ -459,6 +495,11 @@
mConfigurationController.removeCallback(mConfigurationListener);
mView.removeMotionEventListener(mGlobalTouchListener);
mUserSwitcherController.removeUserSwitchCallback(mUserSwitchCallback);
+
+ if (mSceneTransitionCollectionJob != null) {
+ mSceneTransitionCollectionJob.cancel(null);
+ mSceneTransitionCollectionJob = null;
+ }
}
/** */
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 5e84a57..35198de 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -164,13 +164,13 @@
import com.android.systemui.keyguard.domain.interactor.FaceAuthenticationListener;
import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
import com.android.systemui.keyguard.shared.constants.TrustAgentUiEvent;
-import com.android.systemui.keyguard.shared.model.AcquiredAuthenticationStatus;
-import com.android.systemui.keyguard.shared.model.AuthenticationStatus;
-import com.android.systemui.keyguard.shared.model.DetectionStatus;
-import com.android.systemui.keyguard.shared.model.ErrorAuthenticationStatus;
-import com.android.systemui.keyguard.shared.model.FailedAuthenticationStatus;
-import com.android.systemui.keyguard.shared.model.HelpAuthenticationStatus;
-import com.android.systemui.keyguard.shared.model.SuccessAuthenticationStatus;
+import com.android.systemui.keyguard.shared.model.AcquiredFaceAuthenticationStatus;
+import com.android.systemui.keyguard.shared.model.ErrorFaceAuthenticationStatus;
+import com.android.systemui.keyguard.shared.model.FaceAuthenticationStatus;
+import com.android.systemui.keyguard.shared.model.FaceDetectionStatus;
+import com.android.systemui.keyguard.shared.model.FailedFaceAuthenticationStatus;
+import com.android.systemui.keyguard.shared.model.HelpFaceAuthenticationStatus;
+import com.android.systemui.keyguard.shared.model.SuccessFaceAuthenticationStatus;
import com.android.systemui.keyguard.shared.model.SysUiFaceAuthenticateOptions;
import com.android.systemui.log.SessionTracker;
import com.android.systemui.plugins.WeatherData;
@@ -1471,27 +1471,31 @@
private FaceAuthenticationListener mFaceAuthenticationListener =
new FaceAuthenticationListener() {
@Override
- public void onAuthenticationStatusChanged(@NonNull AuthenticationStatus status) {
- if (status instanceof AcquiredAuthenticationStatus) {
+ public void onAuthenticationStatusChanged(
+ @NonNull FaceAuthenticationStatus status
+ ) {
+ if (status instanceof AcquiredFaceAuthenticationStatus) {
handleFaceAcquired(
- ((AcquiredAuthenticationStatus) status).getAcquiredInfo());
- } else if (status instanceof ErrorAuthenticationStatus) {
- ErrorAuthenticationStatus error = (ErrorAuthenticationStatus) status;
+ ((AcquiredFaceAuthenticationStatus) status).getAcquiredInfo());
+ } else if (status instanceof ErrorFaceAuthenticationStatus) {
+ ErrorFaceAuthenticationStatus error =
+ (ErrorFaceAuthenticationStatus) status;
handleFaceError(error.getMsgId(), error.getMsg());
- } else if (status instanceof FailedAuthenticationStatus) {
+ } else if (status instanceof FailedFaceAuthenticationStatus) {
handleFaceAuthFailed();
- } else if (status instanceof HelpAuthenticationStatus) {
- HelpAuthenticationStatus helpMsg = (HelpAuthenticationStatus) status;
+ } else if (status instanceof HelpFaceAuthenticationStatus) {
+ HelpFaceAuthenticationStatus helpMsg =
+ (HelpFaceAuthenticationStatus) status;
handleFaceHelp(helpMsg.getMsgId(), helpMsg.getMsg());
- } else if (status instanceof SuccessAuthenticationStatus) {
+ } else if (status instanceof SuccessFaceAuthenticationStatus) {
FaceManager.AuthenticationResult result =
- ((SuccessAuthenticationStatus) status).getSuccessResult();
+ ((SuccessFaceAuthenticationStatus) status).getSuccessResult();
handleFaceAuthenticated(result.getUserId(), result.isStrongBiometric());
}
}
@Override
- public void onDetectionStatusChanged(@NonNull DetectionStatus status) {
+ public void onDetectionStatusChanged(@NonNull FaceDetectionStatus status) {
handleFaceAuthenticated(status.getUserId(), status.isStrongBiometric());
}
};
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewModule.java b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewModule.java
index a7d4455..8762769 100644
--- a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewModule.java
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewModule.java
@@ -20,6 +20,7 @@
import com.android.systemui.R;
import com.android.systemui.battery.BatteryMeterView;
import com.android.systemui.statusbar.phone.KeyguardStatusBarView;
+import com.android.systemui.statusbar.phone.StatusBarLocation;
import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer;
import dagger.Module;
@@ -44,6 +45,13 @@
/** */
@Provides
@KeyguardStatusBarViewScope
+ static StatusBarLocation getStatusBarLocation() {
+ return StatusBarLocation.KEYGUARD;
+ }
+
+ /** */
+ @Provides
+ @KeyguardStatusBarViewScope
static StatusBarUserSwitcherContainer getUserSwitcherContainer(KeyguardStatusBarView view) {
return view.findViewById(R.id.user_switcher_container);
}
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index 1f1b154..04acd0b 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -123,7 +123,6 @@
import com.android.systemui.statusbar.policy.ZenModeController;
import com.android.systemui.statusbar.window.StatusBarWindowController;
import com.android.systemui.telephony.TelephonyListenerManager;
-import com.android.systemui.tracing.ProtoTracer;
import com.android.systemui.tuner.TunablePadding.TunablePaddingService;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.util.DeviceConfigProxy;
@@ -335,7 +334,6 @@
@Inject Lazy<IWallpaperManager> mWallpaperManager;
@Inject Lazy<CommandQueue> mCommandQueue;
@Inject Lazy<RecordingController> mRecordingController;
- @Inject Lazy<ProtoTracer> mProtoTracer;
@Inject Lazy<MediaOutputDialogFactory> mMediaOutputDialogFactory;
@Inject Lazy<DeviceConfigProxy> mDeviceConfigProxy;
@Inject Lazy<TelephonyListenerManager> mTelephonyListenerManager;
@@ -528,7 +526,6 @@
mProviders.put(DozeParameters.class, mDozeParameters::get);
mProviders.put(IWallpaperManager.class, mWallpaperManager::get);
mProviders.put(CommandQueue.class, mCommandQueue::get);
- mProviders.put(ProtoTracer.class, mProtoTracer::get);
mProviders.put(DeviceConfigProxy.class, mDeviceConfigProxy::get);
mProviders.put(TelephonyListenerManager.class, mTelephonyListenerManager::get);
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
index 0b25184..a977966 100644
--- a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
@@ -14,25 +14,39 @@
* limitations under the License.
*/
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
package com.android.systemui.authentication.data.repository
+import com.android.internal.widget.LockPatternChecker
import com.android.internal.widget.LockPatternUtils
+import com.android.internal.widget.LockscreenCredential
import com.android.keyguard.KeyguardSecurityModel
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.authentication.shared.model.AuthenticationResultModel
+import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.keyguard.data.repository.KeyguardRepository
import com.android.systemui.user.data.repository.UserRepository
+import com.android.systemui.util.time.SystemClock
import dagger.Binds
import dagger.Module
import java.util.function.Function
import javax.inject.Inject
+import kotlin.coroutines.resume
+import kotlin.coroutines.suspendCoroutine
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.withContext
@@ -50,18 +64,29 @@
val isUnlocked: StateFlow<Boolean>
/**
- * Whether lock screen bypass is enabled. When enabled, the lock screen will be automatically
- * dismisses once the authentication challenge is completed. For example, completing a biometric
- * authentication challenge via face unlock or fingerprint sensor can automatically bypass the
- * lock screen.
+ * Whether the auto confirm feature is enabled for the currently-selected user.
+ *
+ * Note that the length of the PIN is also important to take into consideration, please see
+ * [hintedPinLength].
*/
- val isBypassEnabled: StateFlow<Boolean>
+ val isAutoConfirmEnabled: StateFlow<Boolean>
/**
- * Number of consecutively failed authentication attempts. This resets to `0` when
- * authentication succeeds.
+ * The exact length a PIN should be for us to enable PIN length hinting.
+ *
+ * A PIN that's shorter or longer than this is not eligible for the UI to render hints showing
+ * how many digits the current PIN is, even if [isAutoConfirmEnabled] is enabled.
+ *
+ * Note that PIN length hinting is only available if the PIN auto confirmation feature is
+ * available.
*/
- val failedAuthenticationAttempts: StateFlow<Int>
+ val hintedPinLength: Int
+
+ /** Whether the pattern should be visible for the currently-selected user. */
+ val isPatternVisible: StateFlow<Boolean>
+
+ /** The current throttling state, as cached via [setThrottling]. */
+ val throttling: StateFlow<AuthenticationThrottlingModel>
/**
* Returns the currently-configured authentication method. This determines how the
@@ -69,11 +94,45 @@
*/
suspend fun getAuthenticationMethod(): AuthenticationMethodModel
- /** See [isBypassEnabled]. */
- fun setBypassEnabled(isBypassEnabled: Boolean)
+ /** Returns the length of the PIN or `0` if the current auth method is not PIN. */
+ suspend fun getPinLength(): Int
- /** See [failedAuthenticationAttempts]. */
- fun setFailedAuthenticationAttempts(failedAuthenticationAttempts: Int)
+ /**
+ * Returns whether the lockscreen is enabled.
+ *
+ * When the lockscreen is not enabled, it shouldn't show in cases when the authentication method
+ * is considered not secure (for example, "swipe" is considered to be "none").
+ */
+ suspend fun isLockscreenEnabled(): Boolean
+
+ /** Reports an authentication attempt. */
+ suspend fun reportAuthenticationAttempt(isSuccessful: Boolean)
+
+ /** Returns the current number of failed authentication attempts. */
+ suspend fun getFailedAuthenticationAttemptCount(): Int
+
+ /**
+ * Returns the timestamp for when the current throttling will end, allowing the user to attempt
+ * authentication again.
+ *
+ * Note that this is in milliseconds and it matches [SystemClock.elapsedRealtime].
+ */
+ suspend fun getThrottlingEndTimestamp(): Long
+
+ /** Sets the cached throttling state, updating the [throttling] flow. */
+ fun setThrottling(throttlingModel: AuthenticationThrottlingModel)
+
+ /**
+ * Sets the throttling timeout duration (time during which the user should not be allowed to
+ * attempt authentication).
+ */
+ suspend fun setThrottleDuration(durationMs: Int)
+
+ /**
+ * Checks the given [LockscreenCredential] to see if it's correct, returning an
+ * [AuthenticationResultModel] representing what happened.
+ */
+ suspend fun checkCredential(credential: LockscreenCredential): AuthenticationResultModel
}
class AuthenticationRepositoryImpl
@@ -83,65 +142,139 @@
private val getSecurityMode: Function<Int, KeyguardSecurityModel.SecurityMode>,
@Background private val backgroundDispatcher: CoroutineDispatcher,
private val userRepository: UserRepository,
- private val lockPatternUtils: LockPatternUtils,
keyguardRepository: KeyguardRepository,
+ private val lockPatternUtils: LockPatternUtils,
) : AuthenticationRepository {
- override val isUnlocked: StateFlow<Boolean> =
- keyguardRepository.isKeyguardUnlocked.stateIn(
- scope = applicationScope,
- started = SharingStarted.WhileSubscribed(),
- initialValue = false,
- )
+ override val isUnlocked = keyguardRepository.isKeyguardUnlocked
- private val _isBypassEnabled = MutableStateFlow(false)
- override val isBypassEnabled: StateFlow<Boolean> = _isBypassEnabled.asStateFlow()
+ override suspend fun isLockscreenEnabled(): Boolean {
+ return withContext(backgroundDispatcher) {
+ val selectedUserId = userRepository.selectedUserId
+ !lockPatternUtils.isLockPatternEnabled(selectedUserId)
+ }
+ }
- private val _failedAuthenticationAttempts = MutableStateFlow(0)
- override val failedAuthenticationAttempts: StateFlow<Int> =
- _failedAuthenticationAttempts.asStateFlow()
+ override val isAutoConfirmEnabled: StateFlow<Boolean> =
+ userRepository.selectedUserInfo
+ .map { it.id }
+ .flatMapLatest { userId ->
+ flow { emit(lockPatternUtils.isAutoPinConfirmEnabled(userId)) }
+ .flowOn(backgroundDispatcher)
+ }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = false,
+ )
+
+ override val hintedPinLength: Int = 6
+
+ override val isPatternVisible: StateFlow<Boolean> =
+ userRepository.selectedUserInfo
+ .map { it.id }
+ .flatMapLatest { userId ->
+ flow { emit(lockPatternUtils.isVisiblePatternEnabled(userId)) }
+ .flowOn(backgroundDispatcher)
+ }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = true,
+ )
+
+ private val _throttling = MutableStateFlow(AuthenticationThrottlingModel())
+ override val throttling: StateFlow<AuthenticationThrottlingModel> = _throttling.asStateFlow()
+
+ private val UserRepository.selectedUserId: Int
+ get() = getSelectedUserInfo().id
override suspend fun getAuthenticationMethod(): AuthenticationMethodModel {
return withContext(backgroundDispatcher) {
- val selectedUserId = userRepository.getSelectedUserInfo().id
+ val selectedUserId = userRepository.selectedUserId
when (getSecurityMode.apply(selectedUserId)) {
KeyguardSecurityModel.SecurityMode.PIN,
- KeyguardSecurityModel.SecurityMode.SimPin ->
- AuthenticationMethodModel.Pin(
- code = listOf(1, 2, 3, 4), // TODO(b/280883900): remove this
- autoConfirm = lockPatternUtils.isAutoPinConfirmEnabled(selectedUserId),
- )
- KeyguardSecurityModel.SecurityMode.Password,
- KeyguardSecurityModel.SecurityMode.SimPuk ->
- AuthenticationMethodModel.Password(
- password = "password", // TODO(b/280883900): remove this
- )
- KeyguardSecurityModel.SecurityMode.Pattern ->
- AuthenticationMethodModel.Pattern(
- coordinates =
- listOf(
- AuthenticationMethodModel.Pattern.PatternCoordinate(2, 0),
- AuthenticationMethodModel.Pattern.PatternCoordinate(2, 1),
- AuthenticationMethodModel.Pattern.PatternCoordinate(2, 2),
- AuthenticationMethodModel.Pattern.PatternCoordinate(1, 1),
- AuthenticationMethodModel.Pattern.PatternCoordinate(0, 0),
- AuthenticationMethodModel.Pattern.PatternCoordinate(0, 1),
- AuthenticationMethodModel.Pattern.PatternCoordinate(0, 2),
- ), // TODO(b/280883900): remove this
- )
+ KeyguardSecurityModel.SecurityMode.SimPin,
+ KeyguardSecurityModel.SecurityMode.SimPuk -> AuthenticationMethodModel.Pin
+ KeyguardSecurityModel.SecurityMode.Password -> AuthenticationMethodModel.Password
+ KeyguardSecurityModel.SecurityMode.Pattern -> AuthenticationMethodModel.Pattern
KeyguardSecurityModel.SecurityMode.None -> AuthenticationMethodModel.None
KeyguardSecurityModel.SecurityMode.Invalid -> error("Invalid security mode!")
- null -> error("Invalid security is null!")
}
}
}
- override fun setBypassEnabled(isBypassEnabled: Boolean) {
- _isBypassEnabled.value = isBypassEnabled
+ override suspend fun getPinLength(): Int {
+ return withContext(backgroundDispatcher) {
+ val selectedUserId = userRepository.selectedUserId
+ lockPatternUtils.getPinLength(selectedUserId)
+ }
}
- override fun setFailedAuthenticationAttempts(failedAuthenticationAttempts: Int) {
- _failedAuthenticationAttempts.value = failedAuthenticationAttempts
+ override suspend fun reportAuthenticationAttempt(isSuccessful: Boolean) {
+ val selectedUserId = userRepository.selectedUserId
+ withContext(backgroundDispatcher) {
+ if (isSuccessful) {
+ lockPatternUtils.reportSuccessfulPasswordAttempt(selectedUserId)
+ } else {
+ lockPatternUtils.reportFailedPasswordAttempt(selectedUserId)
+ }
+ }
+ }
+
+ override suspend fun getFailedAuthenticationAttemptCount(): Int {
+ return withContext(backgroundDispatcher) {
+ val selectedUserId = userRepository.selectedUserId
+ lockPatternUtils.getCurrentFailedPasswordAttempts(selectedUserId)
+ }
+ }
+
+ override suspend fun getThrottlingEndTimestamp(): Long {
+ return withContext(backgroundDispatcher) {
+ val selectedUserId = userRepository.selectedUserId
+ lockPatternUtils.getLockoutAttemptDeadline(selectedUserId)
+ }
+ }
+
+ override fun setThrottling(throttlingModel: AuthenticationThrottlingModel) {
+ _throttling.value = throttlingModel
+ }
+
+ override suspend fun setThrottleDuration(durationMs: Int) {
+ withContext(backgroundDispatcher) {
+ lockPatternUtils.setLockoutAttemptDeadline(
+ userRepository.selectedUserId,
+ durationMs,
+ )
+ }
+ }
+
+ override suspend fun checkCredential(
+ credential: LockscreenCredential
+ ): AuthenticationResultModel {
+ return suspendCoroutine { continuation ->
+ LockPatternChecker.checkCredential(
+ lockPatternUtils,
+ credential,
+ userRepository.selectedUserId,
+ object : LockPatternChecker.OnCheckCallback {
+ override fun onChecked(matched: Boolean, throttleTimeoutMs: Int) {
+ continuation.resume(
+ AuthenticationResultModel(
+ isSuccessful = matched,
+ throttleDurationMs = throttleTimeoutMs,
+ )
+ )
+ }
+
+ override fun onCancelled() {
+ continuation.resume(AuthenticationResultModel(isSuccessful = false))
+ }
+
+ override fun onEarlyMatched() = Unit
+ }
+ )
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
index 15e579d..b482977 100644
--- a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
@@ -16,25 +16,44 @@
package com.android.systemui.authentication.domain.interactor
-import android.app.admin.DevicePolicyManager
+import com.android.internal.widget.LockPatternView
+import com.android.internal.widget.LockscreenCredential
import com.android.systemui.authentication.data.repository.AuthenticationRepository
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import com.android.systemui.user.data.repository.UserRepository
+import com.android.systemui.util.time.SystemClock
import javax.inject.Inject
+import kotlin.math.max
+import kotlin.time.Duration.Companion.seconds
+import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.async
+import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
/** Hosts application business logic related to authentication. */
@SysUISingleton
class AuthenticationInteractor
@Inject
constructor(
- @Application applicationScope: CoroutineScope,
+ @Application private val applicationScope: CoroutineScope,
private val repository: AuthenticationRepository,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
+ private val userRepository: UserRepository,
+ private val keyguardRepository: KeyguardRepository,
+ private val clock: SystemClock,
) {
/**
* Whether the device is unlocked.
@@ -59,26 +78,67 @@
initialValue = true,
)
- /**
- * Whether lock screen bypass is enabled. When enabled, the lock screen will be automatically
- * dismisses once the authentication challenge is completed. For example, completing a biometric
- * authentication challenge via face unlock or fingerprint sensor can automatically bypass the
- * lock screen.
- */
- val isBypassEnabled: StateFlow<Boolean> = repository.isBypassEnabled
+ /** The current authentication throttling state, only meaningful if [isThrottled] is `true`. */
+ val throttling: StateFlow<AuthenticationThrottlingModel> = repository.throttling
/**
- * Number of consecutively failed authentication attempts. This resets to `0` when
- * authentication succeeds.
+ * Whether currently throttled and the user has to wait before being able to try another
+ * authentication attempt.
*/
- val failedAuthenticationAttempts: StateFlow<Int> = repository.failedAuthenticationAttempts
+ val isThrottled: StateFlow<Boolean> =
+ throttling
+ .map { it.remainingMs > 0 }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.Eagerly,
+ initialValue = throttling.value.remainingMs > 0,
+ )
+
+ /** The length of the hinted PIN, or `null` if pin length hint should not be shown. */
+ val hintedPinLength: StateFlow<Int?> =
+ repository.isAutoConfirmEnabled
+ .map { isAutoConfirmEnabled ->
+ repository.getPinLength().takeIf {
+ isAutoConfirmEnabled && it == repository.hintedPinLength
+ }
+ }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.Eagerly,
+ initialValue = null,
+ )
+
+ /** Whether the auto confirm feature is enabled for the currently-selected user. */
+ val isAutoConfirmEnabled: StateFlow<Boolean> = repository.isAutoConfirmEnabled
+
+ /** Whether the pattern should be visible for the currently-selected user. */
+ val isPatternVisible: StateFlow<Boolean> = repository.isPatternVisible
+
+ private var throttlingCountdownJob: Job? = null
+
+ init {
+ applicationScope.launch {
+ userRepository.selectedUserInfo
+ .map { it.id }
+ .distinctUntilChanged()
+ .collect { onSelectedUserChanged() }
+ }
+ }
/**
* Returns the currently-configured authentication method. This determines how the
* authentication challenge is completed in order to unlock an otherwise locked device.
*/
suspend fun getAuthenticationMethod(): AuthenticationMethodModel {
- return repository.getAuthenticationMethod()
+ val authMethod = repository.getAuthenticationMethod()
+ return if (
+ authMethod is AuthenticationMethodModel.None && repository.isLockscreenEnabled()
+ ) {
+ // We treat "None" as "Swipe" when the lockscreen is enabled.
+ AuthenticationMethodModel.Swipe
+ } else {
+ authMethod
+ }
}
/**
@@ -90,6 +150,16 @@
}
/**
+ * Whether lock screen bypass is enabled. When enabled, the lock screen will be automatically
+ * dismisses once the authentication challenge is completed. For example, completing a biometric
+ * authentication challenge via face unlock or fingerprint sensor can automatically bypass the
+ * lock screen.
+ */
+ fun isBypassEnabled(): Boolean {
+ return keyguardRepository.isBypassEnabled()
+ }
+
+ /**
* Attempts to authenticate the user and unlock the device.
*
* If [tryAutoConfirm] is `true`, authentication is attempted if and only if the auth method
@@ -104,84 +174,115 @@
* authentication failed, `null` if the check was not performed.
*/
suspend fun authenticate(input: List<Any>, tryAutoConfirm: Boolean = false): Boolean? {
- val authMethod = getAuthenticationMethod()
- if (tryAutoConfirm) {
- if ((authMethod as? AuthenticationMethodModel.Pin)?.autoConfirm != true) {
- // Do not attempt to authenticate unless the PIN lock is set to auto-confirm.
- return null
- }
-
- if (input.size < authMethod.code.size) {
- // Do not attempt to authenticate if the PIN has not yet the required amount of
- // digits. This intentionally only skip for shorter PINs; if the PIN is longer, the
- // layer above might have throttled this check, and the PIN should be rejected via
- // the auth code below.
- return null
- }
+ if (input.isEmpty()) {
+ throw IllegalArgumentException("Input was empty!")
}
- val isSuccessful =
- when (authMethod) {
- is AuthenticationMethodModel.Pin -> input.asCode() == authMethod.code
- is AuthenticationMethodModel.Password -> input.asPassword() == authMethod.password
- is AuthenticationMethodModel.Pattern -> input.asPattern() == authMethod.coordinates
- else -> true
+ val skipCheck =
+ when {
+ // We're being throttled, the UI layer should not have called this; skip the
+ // attempt.
+ isThrottled.value -> true
+ // Auto-confirm attempt when the feature is not enabled; skip the attempt.
+ tryAutoConfirm && !isAutoConfirmEnabled.value -> true
+ // Auto-confirm should skip the attempt if the pin entered is too short.
+ tryAutoConfirm && input.size < repository.getPinLength() -> true
+ else -> false
}
+ if (skipCheck) {
+ return null
+ }
- if (isSuccessful) {
- repository.setFailedAuthenticationAttempts(0)
- } else {
- repository.setFailedAuthenticationAttempts(
- repository.failedAuthenticationAttempts.value + 1
+ // Attempt to authenticate:
+ val authMethod = getAuthenticationMethod()
+ val credential = authMethod.createCredential(input) ?: return null
+ val authenticationResult = repository.checkCredential(credential)
+ credential.zeroize()
+
+ if (authenticationResult.isSuccessful || !tryAutoConfirm) {
+ repository.reportAuthenticationAttempt(
+ isSuccessful = authenticationResult.isSuccessful,
)
}
- return isSuccessful
+ // Check if we need to throttle and, if so, kick off the throttle countdown:
+ if (!authenticationResult.isSuccessful && authenticationResult.throttleDurationMs > 0) {
+ repository.setThrottleDuration(
+ durationMs = authenticationResult.throttleDurationMs,
+ )
+ startThrottlingCountdown()
+ }
+
+ if (authenticationResult.isSuccessful) {
+ // Since authentication succeeded, we should refresh throttling to make sure that our
+ // state is completely reflecting the upstream source of truth.
+ refreshThrottling()
+ }
+
+ return authenticationResult.isSuccessful
}
- /** See [isBypassEnabled]. */
- fun toggleBypassEnabled() {
- repository.setBypassEnabled(!repository.isBypassEnabled.value)
- }
-
- companion object {
- /**
- * Returns a PIN code from the given list. It's assumed the given list elements are all
- * [Int] in the range [0-9].
- */
- private fun List<Any>.asCode(): List<Int>? {
- if (isEmpty() || size > DevicePolicyManager.MAX_PASSWORD_LENGTH) {
- return null
- }
-
- return map {
- require(it is Int && it in 0..9) {
- "Pin is required to be Int in range [0..9], but got $it"
+ /** Starts refreshing the throttling state every second. */
+ private suspend fun startThrottlingCountdown() {
+ cancelCountdown()
+ throttlingCountdownJob =
+ applicationScope.launch {
+ while (refreshThrottling() > 0) {
+ delay(1.seconds.inWholeMilliseconds)
}
- it
}
- }
+ }
- /**
- * Returns a password from the given list. It's assumed the given list elements are all
- * [Char].
- */
- private fun List<Any>.asPassword(): String {
- val anyList = this
- return buildString { anyList.forEach { append(it as Char) } }
- }
+ /** Cancels any throttling state countdown started in [startThrottlingCountdown]. */
+ private fun cancelCountdown() {
+ throttlingCountdownJob?.cancel()
+ throttlingCountdownJob = null
+ }
- /**
- * Returns a list of [AuthenticationMethodModel.Pattern.PatternCoordinate] from the given
- * list. It's assumed the given list elements are all
- * [AuthenticationMethodModel.Pattern.PatternCoordinate].
- */
- private fun List<Any>.asPattern():
- List<AuthenticationMethodModel.Pattern.PatternCoordinate> {
- val anyList = this
- return buildList {
- anyList.forEach { add(it as AuthenticationMethodModel.Pattern.PatternCoordinate) }
- }
+ /** Notifies that the currently-selected user has changed. */
+ private suspend fun onSelectedUserChanged() {
+ cancelCountdown()
+ if (refreshThrottling() > 0) {
+ startThrottlingCountdown()
+ }
+ }
+
+ /**
+ * Refreshes the throttling state, hydrating the repository with the latest state.
+ *
+ * @return The remaining time for the current throttling countdown, in milliseconds or `0` if
+ * not being throttled.
+ */
+ private suspend fun refreshThrottling(): Long {
+ return withContext(backgroundDispatcher) {
+ val failedAttemptCount = async { repository.getFailedAuthenticationAttemptCount() }
+ val deadline = async { repository.getThrottlingEndTimestamp() }
+ val remainingMs = max(0, deadline.await() - clock.elapsedRealtime())
+ repository.setThrottling(
+ AuthenticationThrottlingModel(
+ failedAttemptCount = failedAttemptCount.await(),
+ remainingMs = remainingMs.toInt(),
+ ),
+ )
+ remainingMs
+ }
+ }
+
+ private fun AuthenticationMethodModel.createCredential(
+ input: List<Any>
+ ): LockscreenCredential? {
+ return when (this) {
+ is AuthenticationMethodModel.Pin ->
+ LockscreenCredential.createPin(input.joinToString(""))
+ is AuthenticationMethodModel.Password ->
+ LockscreenCredential.createPassword(input.joinToString(""))
+ is AuthenticationMethodModel.Pattern ->
+ LockscreenCredential.createPattern(
+ input
+ .map { it as AuthenticationMethodModel.Pattern.PatternCoordinate }
+ .map { LockPatternView.Cell.of(it.y, it.x) }
+ )
+ else -> null
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationMethodModel.kt b/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationMethodModel.kt
index 1016b6b..97c6697 100644
--- a/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationMethodModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationMethodModel.kt
@@ -16,8 +16,6 @@
package com.android.systemui.authentication.shared.model
-import androidx.annotation.VisibleForTesting
-
/** Enumerates all known authentication methods. */
sealed class AuthenticationMethodModel(
/**
@@ -34,30 +32,11 @@
/** The most basic authentication method. The lock screen can be swiped away when displayed. */
object Swipe : AuthenticationMethodModel(isSecure = false)
- /**
- * Authentication method using a PIN.
- *
- * In practice, a pin is restricted to 16 decimal digits , see
- * [android.app.admin.DevicePolicyManager.MAX_PASSWORD_LENGTH]
- */
- data class Pin(val code: List<Int>, val autoConfirm: Boolean) :
- AuthenticationMethodModel(isSecure = true) {
+ object Pin : AuthenticationMethodModel(isSecure = true)
- /** Convenience constructor for tests only. */
- @VisibleForTesting
- constructor(
- code: Long,
- autoConfirm: Boolean = false
- ) : this(code.toString(10).map { it - '0' }, autoConfirm) {}
- }
+ object Password : AuthenticationMethodModel(isSecure = true)
- data class Password(val password: String) : AuthenticationMethodModel(isSecure = true)
-
- data class Pattern(
- val coordinates: List<PatternCoordinate>,
- val isPatternVisible: Boolean = true,
- ) : AuthenticationMethodModel(isSecure = true) {
-
+ object Pattern : AuthenticationMethodModel(isSecure = true) {
data class PatternCoordinate(
val x: Int,
val y: Int,
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationResultModel.kt b/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationResultModel.kt
new file mode 100644
index 0000000..f2a3e74
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationResultModel.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright 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.authentication.shared.model
+
+/** Models the result of an authentication attempt. */
+data class AuthenticationResultModel(
+ /** Whether authentication was successful. */
+ val isSuccessful: Boolean = false,
+ /** If [isSuccessful] is `false`, how long the user must wait before trying again. */
+ val throttleDurationMs: Int = 0,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationThrottlingModel.kt b/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationThrottlingModel.kt
new file mode 100644
index 0000000..d0d398e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationThrottlingModel.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright 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.authentication.shared.model
+
+/** Models a state for throttling the next authentication attempt. */
+data class AuthenticationThrottlingModel(
+
+ /** Number of failed authentication attempts so far. If not throttling this will be `0`. */
+ val failedAttemptCount: Int = 0,
+
+ /**
+ * Remaining amount of time, in milliseconds, before another authentication attempt can be done.
+ * If not throttling this will be `0`.
+ *
+ * This number is changed throughout the timeout.
+ */
+ val remainingMs: Int = 0,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/battery/AccessorizedBatteryDrawable.kt b/packages/SystemUI/src/com/android/systemui/battery/AccessorizedBatteryDrawable.kt
index b52ddc1..b34f1b4 100644
--- a/packages/SystemUI/src/com/android/systemui/battery/AccessorizedBatteryDrawable.kt
+++ b/packages/SystemUI/src/com/android/systemui/battery/AccessorizedBatteryDrawable.kt
@@ -87,6 +87,10 @@
}
var displayShield: Boolean = false
+ set(value) {
+ field = value
+ postInvalidate()
+ }
private fun updateSizes() {
val b = bounds
@@ -204,4 +208,11 @@
val shieldPathString = context.resources.getString(R.string.config_batterymeterShieldPath)
shieldPath.set(PathParser.createPathFromPathData(shieldPathString))
}
+
+ private val invalidateRunnable: () -> Unit = { invalidateSelf() }
+
+ private fun postInvalidate() {
+ unscheduleSelf(invalidateRunnable)
+ scheduleSelf(invalidateRunnable, 0)
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
index f5f47d0..234795f 100644
--- a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
+++ b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
@@ -466,9 +466,11 @@
public void dump(PrintWriter pw, String[] args) {
String powerSave = mDrawable == null ? null : mDrawable.getPowerSaveEnabled() + "";
+ String displayShield = mDrawable == null ? null : mDrawable.getDisplayShield() + "";
CharSequence percent = mBatteryPercentView == null ? null : mBatteryPercentView.getText();
pw.println(" BatteryMeterView:");
pw.println(" mDrawable.getPowerSave: " + powerSave);
+ pw.println(" mDrawable.getDisplayShield: " + displayShield);
pw.println(" mBatteryPercentView.getText(): " + percent);
pw.println(" mTextColor: #" + Integer.toHexString(mTextColor));
pw.println(" mBatteryStateUnknown: " + mBatteryStateUnknown);
diff --git a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterViewController.java b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterViewController.java
index f6a10bd..6a5749c 100644
--- a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterViewController.java
@@ -30,16 +30,18 @@
import androidx.annotation.NonNull;
+import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.phone.StatusBarIconController;
+import com.android.systemui.statusbar.phone.StatusBarLocation;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.util.ViewController;
+import java.io.PrintWriter;
+
import javax.inject.Inject;
/** Controller for {@link BatteryMeterView}. **/
@@ -53,6 +55,7 @@
private final String mSlotBattery;
private final SettingObserver mSettingObserver;
private final UserTracker mUserTracker;
+ private final StatusBarLocation mLocation;
private final ConfigurationController.ConfigurationListener mConfigurationListener =
new ConfigurationController.ConfigurationListener() {
@@ -94,6 +97,13 @@
public void onIsBatteryDefenderChanged(boolean isBatteryDefender) {
mView.onIsBatteryDefenderChanged(isBatteryDefender);
}
+
+ @Override
+ public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
+ pw.print(super.toString());
+ pw.println(" location=" + mLocation);
+ mView.dump(pw, args);
+ }
};
private final UserTracker.Callback mUserChangedCallback =
@@ -113,14 +123,15 @@
@Inject
public BatteryMeterViewController(
BatteryMeterView view,
+ StatusBarLocation location,
UserTracker userTracker,
ConfigurationController configurationController,
TunerService tunerService,
@Main Handler mainHandler,
ContentResolver contentResolver,
- FeatureFlags featureFlags,
BatteryController batteryController) {
super(view);
+ mLocation = location;
mUserTracker = userTracker;
mConfigurationController = configurationController;
mTunerService = tunerService;
@@ -129,7 +140,8 @@
mBatteryController = batteryController;
mView.setBatteryEstimateFetcher(mBatteryController::getEstimatedTimeRemainingString);
- mView.setDisplayShieldEnabled(featureFlags.isEnabled(Flags.BATTERY_SHIELD_ICON));
+ mView.setDisplayShieldEnabled(
+ getContext().getResources().getBoolean(R.bool.flag_battery_shield_icon));
mSlotBattery = getResources().getString(com.android.internal.R.string.status_bar_battery);
mSettingObserver = new SettingObserver(mMainHandler);
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index 7a2f244..9df56fc 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -250,7 +250,7 @@
.setMessage(messageBody)
.setPositiveButton(android.R.string.ok, null)
.create();
- alertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL);
+ alertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
alertDialog.show();
}
@@ -263,7 +263,7 @@
.setOnDismissListener(
dialog -> animateAway(AuthDialogCallback.DISMISSED_ERROR))
.create();
- alertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL);
+ alertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
alertDialog.show();
}
diff --git a/services/core/java/com/android/server/biometrics/BiometricSensorPrivacy.java b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricPromptLottieViewWrapper.kt
similarity index 66%
copy from services/core/java/com/android/server/biometrics/BiometricSensorPrivacy.java
copy to packages/SystemUI/src/com/android/systemui/biometrics/BiometricPromptLottieViewWrapper.kt
index 6727fbc..e48e6e2 100644
--- a/services/core/java/com/android/server/biometrics/BiometricSensorPrivacy.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricPromptLottieViewWrapper.kt
@@ -13,13 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package com.android.systemui.biometrics
-package com.android.server.biometrics;
+import android.content.Context
+import android.util.AttributeSet
+import com.android.systemui.util.wrapper.LottieViewWrapper
-/**
- * Interface for biometric operations to get camera privacy state.
- */
-public interface BiometricSensorPrivacy {
- /* Returns true if privacy is enabled and camera access is disabled. */
- boolean isCameraPrivacyEnabled();
-}
+class BiometricPromptLottieViewWrapper
+@JvmOverloads
+constructor(context: Context, attrs: AttributeSet? = null) : LottieViewWrapper(context, attrs)
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
index 9d0cde1..083e21f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
@@ -17,12 +17,7 @@
import android.app.ActivityTaskManager
import android.content.Context
-import android.content.res.Configuration
-import android.graphics.Color
import android.graphics.PixelFormat
-import android.graphics.PorterDuff
-import android.graphics.PorterDuffColorFilter
-import android.graphics.Rect
import android.hardware.biometrics.BiometricOverlayConstants
import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_KEYGUARD
import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_SETTINGS
@@ -33,27 +28,23 @@
import android.hardware.fingerprint.ISidefpsController
import android.os.Handler
import android.util.Log
-import android.util.RotationUtils
import android.view.Display
import android.view.DisplayInfo
import android.view.Gravity
import android.view.LayoutInflater
import android.view.Surface
import android.view.View
-import android.view.View.AccessibilityDelegate
import android.view.ViewPropertyAnimator
import android.view.WindowManager
import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION
import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY
-import android.view.accessibility.AccessibilityEvent
-import androidx.annotation.RawRes
import com.airbnb.lottie.LottieAnimationView
-import com.airbnb.lottie.LottieProperty
-import com.airbnb.lottie.model.KeyPath
import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.Dumpable
import com.android.systemui.R
import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
+import com.android.systemui.biometrics.ui.binder.SideFpsOverlayViewBinder
+import com.android.systemui.biometrics.ui.viewmodel.SideFpsOverlayViewModel
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
@@ -64,6 +55,7 @@
import com.android.systemui.util.traceSection
import java.io.PrintWriter
import javax.inject.Inject
+import javax.inject.Provider
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
@@ -86,6 +78,7 @@
@Main private val mainExecutor: DelayableExecutor,
@Main private val handler: Handler,
private val alternateBouncerInteractor: AlternateBouncerInteractor,
+ private val sideFpsOverlayViewModelFactory: Provider<SideFpsOverlayViewModel>,
@Application private val scope: CoroutineScope,
dumpManager: DumpManager
) : Dumpable {
@@ -117,6 +110,8 @@
private var overlayView: View? = null
set(value) {
field?.let { oldView ->
+ val lottie = oldView.findViewById(R.id.sidefps_animation) as LottieAnimationView
+ lottie.pauseAnimation()
windowManager.removeView(oldView)
orientationListener.disable()
}
@@ -193,7 +188,9 @@
requests.add(request)
mainExecutor.execute {
if (overlayView == null) {
- traceSection("SideFpsController#show(request=${request.name}, reason=$reason") {
+ traceSection(
+ "SideFpsController#show(request=${request.name}, reason=$reason)"
+ ) {
createOverlayForDisplay(reason)
}
} else {
@@ -208,7 +205,7 @@
requests.remove(request)
mainExecutor.execute {
if (requests.isEmpty()) {
- traceSection("SideFpsController#hide(${request.name}") { overlayView = null }
+ traceSection("SideFpsController#hide(${request.name})") { overlayView = null }
}
}
}
@@ -246,105 +243,15 @@
private fun createOverlayForDisplay(@BiometricOverlayConstants.ShowReason reason: Int) {
val view = layoutInflater.inflate(R.layout.sidefps_view, null, false)
overlayView = view
- val display = context.display!!
- // b/284098873 `context.display.rotation` may not up-to-date, we use displayInfo.rotation
- display.getDisplayInfo(displayInfo)
- val offsets =
- sensorProps.getLocation(display.uniqueId).let { location ->
- if (location == null) {
- Log.w(TAG, "No location specified for display: ${display.uniqueId}")
- }
- location ?: sensorProps.location
- }
- overlayOffsets = offsets
-
- val lottie = view.findViewById(R.id.sidefps_animation) as LottieAnimationView
- view.rotation =
- display.asSideFpsAnimationRotation(
- offsets.isYAligned(),
- getRotationFromDefault(displayInfo.rotation)
- )
- lottie.setAnimation(
- display.asSideFpsAnimation(
- offsets.isYAligned(),
- getRotationFromDefault(displayInfo.rotation)
- )
+ SideFpsOverlayViewBinder.bind(
+ view = view,
+ viewModel = sideFpsOverlayViewModelFactory.get(),
+ overlayViewParams = overlayViewParams,
+ reason = reason,
+ context = context,
)
- lottie.addLottieOnCompositionLoadedListener {
- // Check that view is not stale, and that overlayView has not been hidden/removed
- if (overlayView != null && overlayView == view) {
- updateOverlayParams(display, it.bounds)
- }
- }
orientationReasonListener.reason = reason
- lottie.addOverlayDynamicColor(context, reason)
-
- /**
- * Intercepts TYPE_WINDOW_STATE_CHANGED accessibility event, preventing Talkback from
- * speaking @string/accessibility_fingerprint_label twice when sensor location indicator is
- * in focus
- */
- view.setAccessibilityDelegate(
- object : AccessibilityDelegate() {
- override fun dispatchPopulateAccessibilityEvent(
- host: View,
- event: AccessibilityEvent
- ): Boolean {
- return if (
- event.getEventType() === AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED
- ) {
- true
- } else {
- super.dispatchPopulateAccessibilityEvent(host, event)
- }
- }
- }
- )
}
-
- @VisibleForTesting
- fun updateOverlayParams(display: Display, bounds: Rect) {
- val isNaturalOrientation = display.isNaturalOrientation()
- val isDefaultOrientation =
- if (isReverseDefaultRotation) !isNaturalOrientation else isNaturalOrientation
- val size = windowManager.maximumWindowMetrics.bounds
-
- val displayWidth = if (isDefaultOrientation) size.width() else size.height()
- val displayHeight = if (isDefaultOrientation) size.height() else size.width()
- val boundsWidth = if (isDefaultOrientation) bounds.width() else bounds.height()
- val boundsHeight = if (isDefaultOrientation) bounds.height() else bounds.width()
-
- val sensorBounds =
- if (overlayOffsets.isYAligned()) {
- Rect(
- displayWidth - boundsWidth,
- overlayOffsets.sensorLocationY,
- displayWidth,
- overlayOffsets.sensorLocationY + boundsHeight
- )
- } else {
- Rect(
- overlayOffsets.sensorLocationX,
- 0,
- overlayOffsets.sensorLocationX + boundsWidth,
- boundsHeight
- )
- }
-
- RotationUtils.rotateBounds(
- sensorBounds,
- Rect(0, 0, displayWidth, displayHeight),
- getRotationFromDefault(display.rotation)
- )
-
- overlayViewParams.x = sensorBounds.left
- overlayViewParams.y = sensorBounds.top
-
- windowManager.updateViewLayout(overlayView, overlayViewParams)
- }
-
- private fun getRotationFromDefault(rotation: Int): Int =
- if (isReverseDefaultRotation) (rotation + 1) % 4 else rotation
}
private val FingerprintManager?.sideFpsSensorProperties: FingerprintSensorPropertiesInternal?
@@ -369,89 +276,12 @@
private fun ActivityTaskManager.topClass(): String =
getTasks(1).firstOrNull()?.topActivity?.className ?: ""
-@RawRes
-private fun Display.asSideFpsAnimation(yAligned: Boolean, rotationFromDefault: Int): Int =
- when (rotationFromDefault) {
- Surface.ROTATION_0 -> if (yAligned) R.raw.sfps_pulse else R.raw.sfps_pulse_landscape
- Surface.ROTATION_180 -> if (yAligned) R.raw.sfps_pulse else R.raw.sfps_pulse_landscape
- else -> if (yAligned) R.raw.sfps_pulse_landscape else R.raw.sfps_pulse
- }
-
-private fun Display.asSideFpsAnimationRotation(yAligned: Boolean, rotationFromDefault: Int): Float =
- when (rotationFromDefault) {
- Surface.ROTATION_90 -> if (yAligned) 0f else 180f
- Surface.ROTATION_180 -> 180f
- Surface.ROTATION_270 -> if (yAligned) 180f else 0f
- else -> 0f
- }
-
private fun SensorLocationInternal.isYAligned(): Boolean = sensorLocationY != 0
private fun Display.isNaturalOrientation(): Boolean =
rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180
-private fun LottieAnimationView.addOverlayDynamicColor(
- context: Context,
- @BiometricOverlayConstants.ShowReason reason: Int
-) {
- fun update() {
- val isKeyguard = reason == REASON_AUTH_KEYGUARD
- if (isKeyguard) {
- val color =
- com.android.settingslib.Utils.getColorAttrDefaultColor(
- context,
- com.android.internal.R.attr.materialColorPrimaryFixed
- )
- val outerRimColor =
- com.android.settingslib.Utils.getColorAttrDefaultColor(
- context,
- com.android.internal.R.attr.materialColorPrimaryFixedDim
- )
- val chevronFill =
- com.android.settingslib.Utils.getColorAttrDefaultColor(
- context,
- com.android.internal.R.attr.materialColorOnPrimaryFixed
- )
- addValueCallback(KeyPath(".blue600", "**"), LottieProperty.COLOR_FILTER) {
- PorterDuffColorFilter(color, PorterDuff.Mode.SRC_ATOP)
- }
- addValueCallback(KeyPath(".blue400", "**"), LottieProperty.COLOR_FILTER) {
- PorterDuffColorFilter(outerRimColor, PorterDuff.Mode.SRC_ATOP)
- }
- addValueCallback(KeyPath(".black", "**"), LottieProperty.COLOR_FILTER) {
- PorterDuffColorFilter(chevronFill, PorterDuff.Mode.SRC_ATOP)
- }
- } else {
- if (!isDarkMode(context)) {
- addValueCallback(KeyPath(".black", "**"), LottieProperty.COLOR_FILTER) {
- PorterDuffColorFilter(Color.WHITE, PorterDuff.Mode.SRC_ATOP)
- }
- }
- for (key in listOf(".blue600", ".blue400")) {
- addValueCallback(KeyPath(key, "**"), LottieProperty.COLOR_FILTER) {
- PorterDuffColorFilter(
- context.getColor(R.color.settingslib_color_blue400),
- PorterDuff.Mode.SRC_ATOP
- )
- }
- }
- }
- }
-
- if (composition != null) {
- update()
- } else {
- addLottieOnCompositionLoadedListener { update() }
- }
-}
-
-private fun isDarkMode(context: Context): Boolean {
- val darkMode = context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
- return darkMode == Configuration.UI_MODE_NIGHT_YES
-}
-
-@VisibleForTesting
-class OrientationReasonListener(
+public class OrientationReasonListener(
context: Context,
displayManager: DisplayManager,
handler: Handler,
diff --git a/services/core/java/com/android/server/biometrics/BiometricSensorPrivacy.java b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsLottieViewWrapper.kt
similarity index 66%
copy from services/core/java/com/android/server/biometrics/BiometricSensorPrivacy.java
copy to packages/SystemUI/src/com/android/systemui/biometrics/SideFpsLottieViewWrapper.kt
index 6727fbc..e98f6db 100644
--- a/services/core/java/com/android/server/biometrics/BiometricSensorPrivacy.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsLottieViewWrapper.kt
@@ -13,13 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package com.android.systemui.biometrics
-package com.android.server.biometrics;
+import android.content.Context
+import android.util.AttributeSet
+import com.android.systemui.util.wrapper.LottieViewWrapper
-/**
- * Interface for biometric operations to get camera privacy state.
- */
-public interface BiometricSensorPrivacy {
- /* Returns true if privacy is enabled and camera access is disabled. */
- boolean isCameraPrivacyEnabled();
-}
+class SideFpsLottieViewWrapper
+@JvmOverloads
+constructor(context: Context, attrs: AttributeSet? = null) : LottieViewWrapper(context, attrs)
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt
index c43722f..efbde4c 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt
@@ -16,8 +16,11 @@
package com.android.systemui.biometrics.data.repository
+import android.hardware.biometrics.ComponentInfoInternal
import android.hardware.biometrics.SensorLocationInternal
+import android.hardware.biometrics.SensorProperties
import android.hardware.fingerprint.FingerprintManager
+import android.hardware.fingerprint.FingerprintSensorProperties
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback
import com.android.systemui.biometrics.shared.model.FingerprintSensorType
@@ -30,10 +33,8 @@
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.shareIn
/**
@@ -43,22 +44,17 @@
*/
interface FingerprintPropertyRepository {
- /**
- * If the repository is initialized or not. Other properties are defaults until this is true.
- */
- val isInitialized: Flow<Boolean>
-
/** The id of fingerprint sensor. */
- val sensorId: StateFlow<Int>
+ val sensorId: Flow<Int>
/** The security strength of sensor (convenience, weak, strong). */
- val strength: StateFlow<SensorStrength>
+ val strength: Flow<SensorStrength>
/** The types of fingerprint sensor (rear, ultrasonic, optical, etc.). */
- val sensorType: StateFlow<FingerprintSensorType>
+ val sensorType: Flow<FingerprintSensorType>
/** The sensor location relative to each physical display. */
- val sensorLocations: StateFlow<Map<String, SensorLocationInternal>>
+ val sensorLocations: Flow<Map<String, SensorLocationInternal>>
}
@SysUISingleton
@@ -66,10 +62,10 @@
@Inject
constructor(
@Application private val applicationScope: CoroutineScope,
- private val fingerprintManager: FingerprintManager
+ private val fingerprintManager: FingerprintManager?
) : FingerprintPropertyRepository {
- override val isInitialized: Flow<Boolean> =
+ private val props: Flow<FingerprintSensorPropertiesInternal> =
conflatedCallbackFlow {
val callback =
object : IFingerprintAuthenticatorsRegisteredCallback.Stub() {
@@ -77,45 +73,47 @@
sensors: List<FingerprintSensorPropertiesInternal>
) {
if (sensors.isNotEmpty()) {
- setProperties(sensors[0])
- trySendWithFailureLogging(true, TAG, "initialize properties")
+ trySendWithFailureLogging(sensors[0], TAG, "initialize properties")
+ } else {
+ trySendWithFailureLogging(
+ DEFAULT_PROPS,
+ TAG,
+ "initialize with default properties"
+ )
}
}
}
- fingerprintManager.addAuthenticatorsRegisteredCallback(callback)
- trySendWithFailureLogging(false, TAG, "initial value defaulting to false")
+ fingerprintManager?.addAuthenticatorsRegisteredCallback(callback)
+ trySendWithFailureLogging(DEFAULT_PROPS, TAG, "initialize with default properties")
awaitClose {}
}
.shareIn(scope = applicationScope, started = SharingStarted.Eagerly, replay = 1)
- private val _sensorId: MutableStateFlow<Int> = MutableStateFlow(-1)
- override val sensorId: StateFlow<Int> = _sensorId.asStateFlow()
-
- private val _strength: MutableStateFlow<SensorStrength> =
- MutableStateFlow(SensorStrength.CONVENIENCE)
- override val strength = _strength.asStateFlow()
-
- private val _sensorType: MutableStateFlow<FingerprintSensorType> =
- MutableStateFlow(FingerprintSensorType.UNKNOWN)
- override val sensorType = _sensorType.asStateFlow()
-
- private val _sensorLocations: MutableStateFlow<Map<String, SensorLocationInternal>> =
- MutableStateFlow(mapOf("" to SensorLocationInternal.DEFAULT))
- override val sensorLocations: StateFlow<Map<String, SensorLocationInternal>> =
- _sensorLocations.asStateFlow()
-
- private fun setProperties(prop: FingerprintSensorPropertiesInternal) {
- _sensorId.value = prop.sensorId
- _strength.value = sensorStrengthIntToObject(prop.sensorStrength)
- _sensorType.value = sensorTypeIntToObject(prop.sensorType)
- _sensorLocations.value =
- prop.allLocations.associateBy { sensorLocationInternal ->
+ override val sensorId: Flow<Int> = props.map { it.sensorId }
+ override val strength: Flow<SensorStrength> =
+ props.map { sensorStrengthIntToObject(it.sensorStrength) }
+ override val sensorType: Flow<FingerprintSensorType> =
+ props.map { sensorTypeIntToObject(it.sensorType) }
+ override val sensorLocations: Flow<Map<String, SensorLocationInternal>> =
+ props.map {
+ it.allLocations.associateBy { sensorLocationInternal ->
sensorLocationInternal.displayId
}
- }
+ }
companion object {
private const val TAG = "FingerprintPropertyRepositoryImpl"
+ private val DEFAULT_PROPS =
+ FingerprintSensorPropertiesInternal(
+ -1 /* sensorId */,
+ SensorProperties.STRENGTH_CONVENIENCE,
+ 0 /* maxEnrollmentsPerUser */,
+ listOf<ComponentInfoInternal>(),
+ FingerprintSensorProperties.TYPE_UNKNOWN,
+ false /* halControlsIllumination */,
+ true /* resetLockoutRequiresHardwareAuthToken */,
+ listOf<SensorLocationInternal>(SensorLocationInternal.DEFAULT)
+ )
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractor.kt
index aa85e5f3..37f39cb 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractor.kt
@@ -17,16 +17,24 @@
package com.android.systemui.biometrics.domain.interactor
import android.hardware.biometrics.SensorLocationInternal
-import android.util.Log
import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository
import com.android.systemui.dagger.SysUISingleton
import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.combine
/** Business logic for SideFps overlay offsets. */
interface SideFpsOverlayInteractor {
+ /** The displayId of the current display. */
+ val displayId: Flow<String>
- /** Get the corresponding offsets based on different displayId. */
- fun getOverlayOffsets(displayId: String): SensorLocationInternal
+ /** The corresponding offsets based on different displayId. */
+ val overlayOffsets: Flow<SensorLocationInternal>
+
+ /** Update the displayId. */
+ fun changeDisplay(displayId: String?)
}
@SysUISingleton
@@ -35,14 +43,16 @@
constructor(private val fingerprintPropertyRepository: FingerprintPropertyRepository) :
SideFpsOverlayInteractor {
- override fun getOverlayOffsets(displayId: String): SensorLocationInternal {
- val offsets = fingerprintPropertyRepository.sensorLocations.value
- return if (offsets.containsKey(displayId)) {
- offsets[displayId]!!
- } else {
- Log.w(TAG, "No location specified for display: $displayId")
- offsets[""]!!
+ private val _displayId: MutableStateFlow<String> = MutableStateFlow("")
+ override val displayId: Flow<String> = _displayId.asStateFlow()
+
+ override val overlayOffsets: Flow<SensorLocationInternal> =
+ combine(displayId, fingerprintPropertyRepository.sensorLocations) { displayId, offsets ->
+ offsets[displayId] ?: SensorLocationInternal.DEFAULT
}
+
+ override fun changeDisplay(displayId: String?) {
+ _displayId.value = displayId ?: ""
}
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricModality.kt b/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/BiometricModality.kt
similarity index 95%
rename from packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricModality.kt
rename to packages/SystemUI/src/com/android/systemui/biometrics/shared/model/BiometricModality.kt
index 3197c09..fb580ca 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricModality.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/BiometricModality.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.biometrics.domain.model
+package com.android.systemui.biometrics.shared.model
import android.hardware.biometrics.BiometricAuthenticator
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
index 64df6a0..e5a4d1a 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
@@ -46,9 +46,9 @@
import com.android.systemui.biometrics.AuthPanelController
import com.android.systemui.biometrics.Utils
import com.android.systemui.biometrics.domain.model.BiometricModalities
-import com.android.systemui.biometrics.domain.model.BiometricModality
-import com.android.systemui.biometrics.domain.model.asBiometricModality
+import com.android.systemui.biometrics.shared.model.BiometricModality
import com.android.systemui.biometrics.shared.model.PromptKind
+import com.android.systemui.biometrics.shared.model.asBiometricModality
import com.android.systemui.biometrics.ui.BiometricPromptLayout
import com.android.systemui.biometrics.ui.viewmodel.FingerprintStartMode
import com.android.systemui.biometrics.ui.viewmodel.PromptMessage
@@ -396,7 +396,6 @@
private var lifecycleScope: CoroutineScope? = null
private var modalities: BiometricModalities = BiometricModalities()
- private var faceFailedAtLeastOnce = false
private var legacyCallback: Callback? = null
override var legacyIconController: AuthIconController? = null
@@ -476,19 +475,15 @@
viewModel.ensureFingerprintHasStarted(isDelayed = true)
applicationScope.launch {
- val suppress =
- modalities.hasFaceAndFingerprint &&
- (failedModality == BiometricModality.Face) &&
- faceFailedAtLeastOnce
- if (failedModality == BiometricModality.Face) {
- faceFailedAtLeastOnce = true
- }
-
viewModel.showTemporaryError(
failureReason,
messageAfterError = modalities.asDefaultHelpMessage(applicationContext),
authenticateAfterError = modalities.hasFingerprint,
- suppressIfErrorShowing = suppress,
+ suppressIf = { currentMessage ->
+ modalities.hasFaceAndFingerprint &&
+ failedModality == BiometricModality.Face &&
+ currentMessage.isError
+ },
failedModality = failedModality,
)
}
@@ -501,11 +496,10 @@
}
applicationScope.launch {
- val suppress =
- modalities.hasFaceAndFingerprint && (errorModality == BiometricModality.Face)
viewModel.showTemporaryError(
error,
- suppressIfErrorShowing = suppress,
+ messageAfterError = modalities.asDefaultHelpMessage(applicationContext),
+ authenticateAfterError = modalities.hasFingerprint,
)
delay(BiometricPrompt.HIDE_DIALOG_DELAY.toLong())
legacyCallback?.onAction(Callback.ACTION_ERROR)
@@ -522,6 +516,7 @@
viewModel.showTemporaryError(
help,
messageAfterError = modalities.asDefaultHelpMessage(applicationContext),
+ authenticateAfterError = modalities.hasFingerprint,
hapticFeedback = false,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt
new file mode 100644
index 0000000..0409519
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt
@@ -0,0 +1,168 @@
+/*
+ * 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.biometrics.ui.binder
+
+import android.content.Context
+import android.content.res.Configuration
+import android.graphics.Color
+import android.graphics.PorterDuff
+import android.graphics.PorterDuffColorFilter
+import android.hardware.biometrics.BiometricOverlayConstants
+import android.view.View
+import android.view.WindowManager
+import android.view.accessibility.AccessibilityEvent
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.airbnb.lottie.LottieAnimationView
+import com.airbnb.lottie.LottieProperty
+import com.airbnb.lottie.model.KeyPath
+import com.android.systemui.R
+import com.android.systemui.biometrics.ui.viewmodel.SideFpsOverlayViewModel
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.lifecycle.repeatWhenAttached
+import kotlinx.coroutines.launch
+
+/** Sub-binder for SideFpsOverlayView. */
+object SideFpsOverlayViewBinder {
+
+ /** Bind the view. */
+ @JvmStatic
+ fun bind(
+ view: View,
+ viewModel: SideFpsOverlayViewModel,
+ overlayViewParams: WindowManager.LayoutParams,
+ @BiometricOverlayConstants.ShowReason reason: Int,
+ @Application context: Context
+ ) {
+ val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
+
+ val lottie = view.findViewById(R.id.sidefps_animation) as LottieAnimationView
+
+ viewModel.changeDisplay()
+
+ view.repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ launch {
+ viewModel.sideFpsAnimationRotation.collect { rotation ->
+ view.rotation = rotation
+ }
+ }
+
+ launch {
+ // TODO(b/221037350, wenhuiy): Create a separate ViewBinder for sideFpsAnimation
+ // in order to add scuba tests in the future.
+ viewModel.sideFpsAnimation.collect { animation ->
+ lottie.setAnimation(animation)
+ }
+ }
+
+ launch {
+ viewModel.sensorBounds.collect { sensorBounds ->
+ overlayViewParams.x = sensorBounds.left
+ overlayViewParams.y = sensorBounds.top
+
+ windowManager.updateViewLayout(view, overlayViewParams)
+ }
+ }
+
+ launch {
+ viewModel.overlayOffsets.collect { overlayOffsets ->
+ lottie.addLottieOnCompositionLoadedListener {
+ viewModel.updateSensorBounds(
+ it.bounds,
+ windowManager.maximumWindowMetrics.bounds,
+ overlayOffsets
+ )
+ }
+ }
+ }
+ }
+ }
+
+ lottie.addOverlayDynamicColor(context, reason)
+
+ /**
+ * Intercepts TYPE_WINDOW_STATE_CHANGED accessibility event, preventing Talkback from
+ * speaking @string/accessibility_fingerprint_label twice when sensor location indicator is
+ * in focus
+ */
+ view.accessibilityDelegate =
+ object : View.AccessibilityDelegate() {
+ override fun dispatchPopulateAccessibilityEvent(
+ host: View,
+ event: AccessibilityEvent
+ ): Boolean {
+ return if (
+ event.getEventType() === AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED
+ ) {
+ true
+ } else {
+ super.dispatchPopulateAccessibilityEvent(host, event)
+ }
+ }
+ }
+ }
+}
+
+private fun LottieAnimationView.addOverlayDynamicColor(
+ context: Context,
+ @BiometricOverlayConstants.ShowReason reason: Int
+) {
+ fun update() {
+ val isKeyguard = reason == BiometricOverlayConstants.REASON_AUTH_KEYGUARD
+ if (isKeyguard) {
+ val color = context.getColor(R.color.numpad_key_color_secondary) // match bouncer color
+ val chevronFill =
+ com.android.settingslib.Utils.getColorAttrDefaultColor(
+ context,
+ android.R.attr.textColorPrimaryInverse
+ )
+ for (key in listOf(".blue600", ".blue400")) {
+ addValueCallback(KeyPath(key, "**"), LottieProperty.COLOR_FILTER) {
+ PorterDuffColorFilter(color, PorterDuff.Mode.SRC_ATOP)
+ }
+ }
+ addValueCallback(KeyPath(".black", "**"), LottieProperty.COLOR_FILTER) {
+ PorterDuffColorFilter(chevronFill, PorterDuff.Mode.SRC_ATOP)
+ }
+ } else if (!isDarkMode(context)) {
+ addValueCallback(KeyPath(".black", "**"), LottieProperty.COLOR_FILTER) {
+ PorterDuffColorFilter(Color.WHITE, PorterDuff.Mode.SRC_ATOP)
+ }
+ } else if (isDarkMode(context)) {
+ for (key in listOf(".blue600", ".blue400")) {
+ addValueCallback(KeyPath(key, "**"), LottieProperty.COLOR_FILTER) {
+ PorterDuffColorFilter(
+ context.getColor(R.color.settingslib_color_blue400),
+ PorterDuff.Mode.SRC_ATOP
+ )
+ }
+ }
+ }
+ }
+
+ if (composition != null) {
+ update()
+ } else {
+ addLottieOnCompositionLoadedListener { update() }
+ }
+}
+
+private fun isDarkMode(context: Context): Boolean {
+ val darkMode = context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
+ return darkMode == Configuration.UI_MODE_NIGHT_YES
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptAuthState.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptAuthState.kt
index 444082c..2f9557f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptAuthState.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptAuthState.kt
@@ -16,7 +16,7 @@
package com.android.systemui.biometrics.ui.viewmodel
-import com.android.systemui.biometrics.domain.model.BiometricModality
+import com.android.systemui.biometrics.shared.model.BiometricModality
/**
* The authenticated state with the [authenticatedModality] (when [isAuthenticated]) with an
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptMessage.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptMessage.kt
index 219da71..50f4911 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptMessage.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptMessage.kt
@@ -33,9 +33,9 @@
else -> ""
}
- /** If this is an [Error] or [Help] message. */
- val isErrorOrHelp: Boolean
- get() = this is Error || this is Help
+ /** If this is an [Error]. */
+ val isError: Boolean
+ get() = this is Error
/** An error message. */
data class Error(val errorMessage: String) : PromptMessage
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
index d63bf57..8a2e405 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
@@ -20,7 +20,7 @@
import com.android.systemui.biometrics.AuthBiometricView
import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor
import com.android.systemui.biometrics.domain.model.BiometricModalities
-import com.android.systemui.biometrics.domain.model.BiometricModality
+import com.android.systemui.biometrics.shared.model.BiometricModality
import com.android.systemui.biometrics.shared.model.PromptKind
import com.android.systemui.statusbar.VibratorHelper
import javax.inject.Inject
@@ -210,35 +210,33 @@
* Show a temporary error [message] associated with an optional [failedModality] and play
* [hapticFeedback].
*
- * An optional [messageAfterError] will be shown via [showAuthenticating] when
- * [authenticateAfterError] is set (or via [showHelp] when not set) after the error is
- * dismissed.
+ * The [messageAfterError] will be shown via [showAuthenticating] when [authenticateAfterError]
+ * is set (or via [showHelp] when not set) after the error is dismissed.
*
- * The error is ignored if the user has already authenticated or if [suppressIfErrorShowing] is
- * set and an error message is already showing.
+ * The error is ignored if the user has already authenticated or if [suppressIf] is true given
+ * the currently showing [PromptMessage].
*/
suspend fun showTemporaryError(
message: String,
+ messageAfterError: String,
+ authenticateAfterError: Boolean,
+ suppressIf: (PromptMessage) -> Boolean = { false },
hapticFeedback: Boolean = true,
- messageAfterError: String = "",
- authenticateAfterError: Boolean = false,
- suppressIfErrorShowing: Boolean = false,
failedModality: BiometricModality = BiometricModality.None,
) = coroutineScope {
if (_isAuthenticated.value.isAuthenticated) {
return@coroutineScope
}
- if (_message.value.isErrorOrHelp && suppressIfErrorShowing) {
- if (_isAuthenticated.value.isNotAuthenticated) {
- _canTryAgainNow.value = supportsRetry(failedModality)
- }
+
+ _canTryAgainNow.value = supportsRetry(failedModality)
+
+ if (suppressIf(_message.value)) {
return@coroutineScope
}
_isAuthenticating.value = false
_isAuthenticated.value = PromptAuthState(false)
_forceMediumSize.value = true
- _canTryAgainNow.value = supportsRetry(failedModality)
_message.value = PromptMessage.Error(message)
_legacyState.value = AuthBiometricView.STATE_ERROR
@@ -374,7 +372,9 @@
AuthBiometricView.STATE_AUTHENTICATED
}
- vibrator.success(modality)
+ if (!needsUserConfirmation) {
+ vibrator.success(modality)
+ }
messageJob?.cancel()
messageJob = null
@@ -420,6 +420,8 @@
_message.value = PromptMessage.Empty
_legacyState.value = AuthBiometricView.STATE_AUTHENTICATED
+ vibrator.success(authState.authenticatedModality)
+
messageJob?.cancel()
messageJob = null
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModel.kt
new file mode 100644
index 0000000..e938b4e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModel.kt
@@ -0,0 +1,148 @@
+/*
+ * 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.biometrics.ui.viewmodel
+
+import android.content.Context
+import android.graphics.Rect
+import android.hardware.biometrics.SensorLocationInternal
+import android.util.RotationUtils
+import android.view.Display
+import android.view.DisplayInfo
+import android.view.Surface
+import androidx.annotation.RawRes
+import com.android.systemui.R
+import com.android.systemui.biometrics.domain.interactor.SideFpsOverlayInteractor
+import com.android.systemui.dagger.qualifiers.Application
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.map
+
+/** View-model for SideFpsOverlayView. */
+class SideFpsOverlayViewModel
+@Inject
+constructor(
+ @Application private val context: Context,
+ private val sideFpsOverlayInteractor: SideFpsOverlayInteractor,
+) {
+
+ private val isReverseDefaultRotation =
+ context.resources.getBoolean(com.android.internal.R.bool.config_reverseDefaultRotation)
+
+ private val _sensorBounds: MutableStateFlow<Rect> = MutableStateFlow(Rect())
+ val sensorBounds = _sensorBounds.asStateFlow()
+
+ val overlayOffsets: Flow<SensorLocationInternal> = sideFpsOverlayInteractor.overlayOffsets
+
+ /** Update the displayId. */
+ fun changeDisplay() {
+ sideFpsOverlayInteractor.changeDisplay(context.display!!.uniqueId)
+ }
+
+ /** Determine the rotation of the sideFps animation given the overlay offsets. */
+ val sideFpsAnimationRotation: Flow<Float> =
+ overlayOffsets.map { overlayOffsets ->
+ val display = context.display!!
+ val displayInfo: DisplayInfo = DisplayInfo()
+ // b/284098873 `context.display.rotation` may not up-to-date, we use
+ // displayInfo.rotation
+ display.getDisplayInfo(displayInfo)
+ val yAligned: Boolean = overlayOffsets.isYAligned()
+ when (getRotationFromDefault(displayInfo.rotation)) {
+ Surface.ROTATION_90 -> if (yAligned) 0f else 180f
+ Surface.ROTATION_180 -> 180f
+ Surface.ROTATION_270 -> if (yAligned) 180f else 0f
+ else -> 0f
+ }
+ }
+
+ /** Populate the sideFps animation from the overlay offsets. */
+ @RawRes
+ val sideFpsAnimation: Flow<Int> =
+ overlayOffsets.map { overlayOffsets ->
+ val display = context.display!!
+ val displayInfo: DisplayInfo = DisplayInfo()
+ // b/284098873 `context.display.rotation` may not up-to-date, we use
+ // displayInfo.rotation
+ display.getDisplayInfo(displayInfo)
+ val yAligned: Boolean = overlayOffsets.isYAligned()
+ when (getRotationFromDefault(displayInfo.rotation)) {
+ Surface.ROTATION_0 -> if (yAligned) R.raw.sfps_pulse else R.raw.sfps_pulse_landscape
+ Surface.ROTATION_180 ->
+ if (yAligned) R.raw.sfps_pulse else R.raw.sfps_pulse_landscape
+ else -> if (yAligned) R.raw.sfps_pulse_landscape else R.raw.sfps_pulse
+ }
+ }
+
+ /**
+ * Calculate and update the bounds of the sensor based on the bounds of the overlay view, the
+ * maximum bounds of the window, and the offsets of the sensor location.
+ */
+ fun updateSensorBounds(
+ bounds: Rect,
+ maximumWindowBounds: Rect,
+ offsets: SensorLocationInternal
+ ) {
+ val isNaturalOrientation = context.display!!.isNaturalOrientation()
+ val isDefaultOrientation =
+ if (isReverseDefaultRotation) !isNaturalOrientation else isNaturalOrientation
+
+ val displayWidth =
+ if (isDefaultOrientation) maximumWindowBounds.width() else maximumWindowBounds.height()
+ val displayHeight =
+ if (isDefaultOrientation) maximumWindowBounds.height() else maximumWindowBounds.width()
+ val boundsWidth = if (isDefaultOrientation) bounds.width() else bounds.height()
+ val boundsHeight = if (isDefaultOrientation) bounds.height() else bounds.width()
+
+ val sensorBounds =
+ if (offsets.isYAligned()) {
+ Rect(
+ displayWidth - boundsWidth,
+ offsets.sensorLocationY,
+ displayWidth,
+ offsets.sensorLocationY + boundsHeight
+ )
+ } else {
+ Rect(
+ offsets.sensorLocationX,
+ 0,
+ offsets.sensorLocationX + boundsWidth,
+ boundsHeight
+ )
+ }
+
+ val displayInfo: DisplayInfo = DisplayInfo()
+ context.display!!.getDisplayInfo(displayInfo)
+
+ RotationUtils.rotateBounds(
+ sensorBounds,
+ Rect(0, 0, displayWidth, displayHeight),
+ getRotationFromDefault(displayInfo.rotation)
+ )
+
+ _sensorBounds.value = sensorBounds
+ }
+
+ private fun getRotationFromDefault(rotation: Int): Int =
+ if (isReverseDefaultRotation) (rotation + 1) % 4 else rotation
+}
+
+private fun Display.isNaturalOrientation(): Boolean =
+ rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180
+
+private fun SensorLocationInternal.isYAligned(): Boolean = sensorLocationY != 0
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/BouncerRepository.kt b/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/BouncerRepository.kt
index ff896fa..943216e 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/BouncerRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/BouncerRepository.kt
@@ -16,7 +16,6 @@
package com.android.systemui.bouncer.data.repository
-import com.android.systemui.bouncer.shared.model.AuthenticationThrottledModel
import com.android.systemui.dagger.SysUISingleton
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
@@ -30,15 +29,7 @@
/** The user-facing message to show in the bouncer. */
val message: StateFlow<String?> = _message.asStateFlow()
- private val _throttling = MutableStateFlow<AuthenticationThrottledModel?>(null)
- /** The current authentication throttling state. If `null`, there's no throttling. */
- val throttling: StateFlow<AuthenticationThrottledModel?> = _throttling.asStateFlow()
-
fun setMessage(message: String?) {
_message.value = message
}
-
- fun setThrottling(throttling: AuthenticationThrottledModel?) {
- _throttling.value = throttling
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt
index 2abdb84..e3e9b3a 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt
@@ -16,10 +16,10 @@
package com.android.systemui.bouncer.domain.interactor
+import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
-import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.time.SystemClock
@@ -35,8 +35,8 @@
private val keyguardStateController: KeyguardStateController,
private val bouncerRepository: KeyguardBouncerRepository,
private val biometricSettingsRepository: BiometricSettingsRepository,
- private val deviceEntryFingerprintAuthRepository: DeviceEntryFingerprintAuthRepository,
private val systemClock: SystemClock,
+ private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
) {
var receivedDownTouch = false
val isVisible: Flow<Boolean> = bouncerRepository.alternateBouncerVisible
@@ -78,7 +78,7 @@
biometricSettingsRepository.isFingerprintEnrolled.value &&
biometricSettingsRepository.isStrongBiometricAllowed.value &&
biometricSettingsRepository.isFingerprintEnabledByDevicePolicy.value &&
- !deviceEntryFingerprintAuthRepository.isLockedOut.value &&
+ !keyguardUpdateMonitor.isFingerprintLockedOut &&
!keyguardStateController.isUnlocked &&
!statusBarStateController.isDozing
}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
index 5dd24b2..8e14237 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
@@ -14,30 +14,32 @@
* limitations under the License.
*/
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
package com.android.systemui.bouncer.domain.interactor
import android.content.Context
-import androidx.annotation.VisibleForTesting
import com.android.systemui.R
import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel
import com.android.systemui.bouncer.data.repository.BouncerRepository
-import com.android.systemui.bouncer.shared.model.AuthenticationThrottledModel
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.util.kotlin.pairwise
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
+import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.delay
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.flatMapLatest
-import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
@@ -50,6 +52,7 @@
private val repository: BouncerRepository,
private val authenticationInteractor: AuthenticationInteractor,
private val sceneInteractor: SceneInteractor,
+ featureFlags: FeatureFlags,
@Assisted private val containerName: String,
) {
@@ -57,9 +60,10 @@
val message: StateFlow<String?> =
combine(
repository.message,
- repository.throttling,
- ) { message, throttling ->
- messageOrThrottlingMessage(message, throttling)
+ authenticationInteractor.isThrottled,
+ authenticationInteractor.throttling,
+ ) { message, isThrottled, throttling ->
+ messageOrThrottlingMessage(message, isThrottled, throttling)
}
.stateIn(
scope = applicationScope,
@@ -67,36 +71,39 @@
initialValue =
messageOrThrottlingMessage(
repository.message.value,
- repository.throttling.value,
+ authenticationInteractor.isThrottled.value,
+ authenticationInteractor.throttling.value,
)
)
- /** The current authentication throttling state. If `null`, there's no throttling. */
- val throttling: StateFlow<AuthenticationThrottledModel?> = repository.throttling
+ /** The current authentication throttling state, only meaningful if [isThrottled] is `true`. */
+ val throttling: StateFlow<AuthenticationThrottlingModel> = authenticationInteractor.throttling
+
+ /**
+ * Whether currently throttled and the user has to wait before being able to try another
+ * authentication attempt.
+ */
+ val isThrottled: StateFlow<Boolean> = authenticationInteractor.isThrottled
+
+ /** Whether the auto confirm feature is enabled for the currently-selected user. */
+ val isAutoConfirmEnabled: StateFlow<Boolean> = authenticationInteractor.isAutoConfirmEnabled
+
+ /** The length of the hinted PIN, or `null`, if pin length hint should not be shown. */
+ val hintedPinLength: StateFlow<Int?> = authenticationInteractor.hintedPinLength
+
+ /** Whether the pattern should be visible for the currently-selected user. */
+ val isPatternVisible: StateFlow<Boolean> = authenticationInteractor.isPatternVisible
init {
- // UNLOCKING SHOWS Gone.
- //
- // Move to the gone scene if the device becomes unlocked while on the bouncer scene.
- applicationScope.launch {
- sceneInteractor
- .currentScene(containerName)
- .flatMapLatest { currentScene ->
- if (currentScene.key == SceneKey.Bouncer) {
- authenticationInteractor.isUnlocked
- } else {
- flowOf(false)
+ if (featureFlags.isEnabled(Flags.SCENE_CONTAINER)) {
+ // Clear the message if moved from throttling to no-longer throttling.
+ applicationScope.launch {
+ isThrottled.pairwise().collect { (wasThrottled, currentlyThrottled) ->
+ if (wasThrottled && !currentlyThrottled) {
+ clearMessage()
}
}
- .distinctUntilChanged()
- .collect { isUnlocked ->
- if (isUnlocked) {
- sceneInteractor.setCurrentScene(
- containerName = containerName,
- scene = SceneModel(SceneKey.Gone),
- )
- }
- }
+ }
}
}
@@ -168,41 +175,16 @@
input: List<Any>,
tryAutoConfirm: Boolean = false,
): Boolean? {
- if (repository.throttling.value != null) {
- return false
- }
-
val isAuthenticated =
authenticationInteractor.authenticate(input, tryAutoConfirm) ?: return null
- val failedAttempts = authenticationInteractor.failedAuthenticationAttempts.value
- when {
- isAuthenticated -> {
- repository.setThrottling(null)
- sceneInteractor.setCurrentScene(
- containerName = containerName,
- scene = SceneModel(SceneKey.Gone),
- )
- }
- failedAttempts >= THROTTLE_AGGRESSIVELY_AFTER || failedAttempts % THROTTLE_EVERY == 0 ->
- applicationScope.launch {
- var remainingDurationSec = THROTTLE_DURATION_SEC
- while (remainingDurationSec > 0) {
- repository.setThrottling(
- AuthenticationThrottledModel(
- failedAttemptCount = failedAttempts,
- totalDurationSec = THROTTLE_DURATION_SEC,
- remainingDurationSec = remainingDurationSec,
- )
- )
- remainingDurationSec--
- delay(1000)
- }
-
- repository.setThrottling(null)
- clearMessage()
- }
- else -> repository.setMessage(errorMessage(getAuthenticationMethod()))
+ if (isAuthenticated) {
+ sceneInteractor.setCurrentScene(
+ containerName = containerName,
+ scene = SceneModel(SceneKey.Gone),
+ )
+ } else {
+ repository.setMessage(errorMessage(getAuthenticationMethod()))
}
return isAuthenticated
@@ -233,13 +215,14 @@
private fun messageOrThrottlingMessage(
message: String?,
- throttling: AuthenticationThrottledModel?,
+ isThrottled: Boolean,
+ throttlingModel: AuthenticationThrottlingModel,
): String {
return when {
- throttling != null ->
+ isThrottled ->
applicationContext.getString(
com.android.internal.R.string.lockscreen_too_many_failed_attempts_countdown,
- throttling.remainingDurationSec,
+ throttlingModel.remainingMs.milliseconds.inWholeSeconds,
)
message != null -> message
else -> ""
@@ -252,10 +235,4 @@
containerName: String,
): BouncerInteractor
}
-
- companion object {
- @VisibleForTesting const val THROTTLE_DURATION_SEC = 30
- @VisibleForTesting const val THROTTLE_AGGRESSIVELY_AFTER = 15
- @VisibleForTesting const val THROTTLE_EVERY = 5
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/shared/model/AuthenticationThrottledModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/shared/model/AuthenticationThrottledModel.kt
deleted file mode 100644
index cbea635..0000000
--- a/packages/SystemUI/src/com/android/systemui/bouncer/shared/model/AuthenticationThrottledModel.kt
+++ /dev/null
@@ -1,30 +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.bouncer.shared.model
-
-/**
- * Models application state for when further authentication attempts are being throttled due to too
- * many consecutive failed authentication attempts.
- */
-data class AuthenticationThrottledModel(
- /** Total number of failed attempts so far. */
- val failedAttemptCount: Int,
- /** Total amount of time the user has to wait before attempting again. */
- val totalDurationSec: Int,
- /** Remaining amount of time the user has to wait before attempting again. */
- val remainingDurationSec: Int,
-)
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
index db6ca0b..a4ef5ce 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
@@ -14,19 +14,24 @@
* limitations under the License.
*/
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
package com.android.systemui.bouncer.ui.viewmodel
import android.content.Context
import com.android.systemui.R
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
-import com.android.systemui.bouncer.shared.model.AuthenticationThrottledModel
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.util.kotlin.pairwise
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
+import kotlin.math.ceil
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
@@ -46,17 +51,18 @@
@Application private val applicationContext: Context,
@Application private val applicationScope: CoroutineScope,
interactorFactory: BouncerInteractor.Factory,
+ featureFlags: FeatureFlags,
@Assisted containerName: String,
) {
private val interactor: BouncerInteractor = interactorFactory.create(containerName)
private val isInputEnabled: StateFlow<Boolean> =
- interactor.throttling
- .map { it == null }
+ interactor.isThrottled
+ .map { !it }
.stateIn(
scope = applicationScope,
started = SharingStarted.WhileSubscribed(),
- initialValue = interactor.throttling.value == null,
+ initialValue = !interactor.isThrottled.value,
)
private val pin: PinBouncerViewModel by lazy {
@@ -99,15 +105,48 @@
)
init {
- applicationScope.launch {
- _authMethod.subscriptionCount
- .pairwise()
- .map { (previousCount, currentCount) -> currentCount > previousCount }
- .collect { subscriberAdded ->
- if (subscriberAdded) {
- reloadAuthMethod()
+ if (featureFlags.isEnabled(Flags.SCENE_CONTAINER)) {
+ applicationScope.launch {
+ interactor.isThrottled
+ .map { isThrottled ->
+ if (isThrottled) {
+ when (interactor.getAuthenticationMethod()) {
+ is AuthenticationMethodModel.Pin ->
+ R.string.kg_too_many_failed_pin_attempts_dialog_message
+ is AuthenticationMethodModel.Password ->
+ R.string.kg_too_many_failed_password_attempts_dialog_message
+ is AuthenticationMethodModel.Pattern ->
+ R.string.kg_too_many_failed_pattern_attempts_dialog_message
+ else -> null
+ }?.let { stringResourceId ->
+ applicationContext.getString(
+ stringResourceId,
+ interactor.throttling.value.failedAttemptCount,
+ ceil(interactor.throttling.value.remainingMs / 1000f).toInt(),
+ )
+ }
+ } else {
+ null
+ }
}
- }
+ .distinctUntilChanged()
+ .collect { dialogMessageOrNull ->
+ if (dialogMessageOrNull != null) {
+ _throttlingDialogMessage.value = dialogMessageOrNull
+ }
+ }
+ }
+
+ applicationScope.launch {
+ _authMethod.subscriptionCount
+ .pairwise()
+ .map { (previousCount, currentCount) -> currentCount > previousCount }
+ .collect { subscriberAdded ->
+ if (subscriberAdded) {
+ reloadAuthMethod()
+ }
+ }
+ }
}
}
@@ -115,9 +154,9 @@
val message: StateFlow<MessageViewModel> =
combine(
interactor.message,
- interactor.throttling,
- ) { message, throttling ->
- toMessageViewModel(message, throttling)
+ interactor.isThrottled,
+ ) { message, isThrottled ->
+ toMessageViewModel(message, isThrottled)
}
.stateIn(
scope = applicationScope,
@@ -125,7 +164,7 @@
initialValue =
toMessageViewModel(
message = interactor.message.value,
- throttling = interactor.throttling.value,
+ isThrottled = interactor.isThrottled.value,
),
)
@@ -141,37 +180,6 @@
*/
val throttlingDialogMessage: StateFlow<String?> = _throttlingDialogMessage.asStateFlow()
- init {
- applicationScope.launch {
- interactor.throttling
- .map { model ->
- model?.let {
- when (interactor.getAuthenticationMethod()) {
- is AuthenticationMethodModel.Pin ->
- R.string.kg_too_many_failed_pin_attempts_dialog_message
- is AuthenticationMethodModel.Password ->
- R.string.kg_too_many_failed_password_attempts_dialog_message
- is AuthenticationMethodModel.Pattern ->
- R.string.kg_too_many_failed_pattern_attempts_dialog_message
- else -> null
- }?.let { stringResourceId ->
- applicationContext.getString(
- stringResourceId,
- model.failedAttemptCount,
- model.totalDurationSec,
- )
- }
- }
- }
- .distinctUntilChanged()
- .collect { dialogMessageOrNull ->
- if (dialogMessageOrNull != null) {
- _throttlingDialogMessage.value = dialogMessageOrNull
- }
- }
- }
- }
-
/** Notifies that the emergency services button was clicked. */
fun onEmergencyServicesButtonClicked() {
// TODO(b/280877228): implement this
@@ -184,11 +192,11 @@
private fun toMessageViewModel(
message: String?,
- throttling: AuthenticationThrottledModel?,
+ isThrottled: Boolean,
): MessageViewModel {
return MessageViewModel(
text = message ?: "",
- isUpdateAnimated = throttling == null,
+ isUpdateAnimated = !isThrottled,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt
index 5efa6f0..4be539d 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt
@@ -29,7 +29,6 @@
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
@@ -70,19 +69,7 @@
val dots: StateFlow<List<PatternDotViewModel>> = _dots.asStateFlow()
/** Whether the pattern itself should be rendered visibly. */
- val isPatternVisible: StateFlow<Boolean> =
- flow {
- emit(null)
- emit(interactor.getAuthenticationMethod())
- }
- .map { authMethod ->
- (authMethod as? AuthenticationMethodModel.Pattern)?.isPatternVisible ?: false
- }
- .stateIn(
- scope = applicationScope,
- started = SharingStarted.Eagerly,
- initialValue = false,
- )
+ val isPatternVisible: StateFlow<Boolean> = interactor.isPatternVisible
/** Notifies that the UI has been shown to the user. */
fun onShown() {
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
index 641e863..1b14acc 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
@@ -18,13 +18,12 @@
import android.content.Context
import com.android.keyguard.PinShapeAdapter
-import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
@@ -45,26 +44,18 @@
private val mutablePinEntries = MutableStateFlow<List<EnteredKey>>(emptyList())
val pinEntries: StateFlow<List<EnteredKey>> = mutablePinEntries
- /** The length of the hinted PIN, or `null` if pin length hint should not be shown. */
- val hintedPinLength: StateFlow<Int?> =
- flow { emit(interactor.getAuthenticationMethod()) }
- .map { authMethod ->
- // Hinting is enabled for 6-digit codes only
- autoConfirmPinLength(authMethod).takeIf { it == HINTING_PASSCODE_LENGTH }
- }
- .stateIn(
- scope = applicationScope,
- started = SharingStarted.Eagerly,
- initialValue = null,
- )
+ /** The length of the PIN for which we should show a hint. */
+ val hintedPinLength: StateFlow<Int?> = interactor.hintedPinLength
/** Appearance of the backspace button. */
val backspaceButtonAppearance: StateFlow<ActionButtonAppearance> =
- mutablePinEntries
- .map { mutablePinEntries ->
+ combine(
+ mutablePinEntries,
+ interactor.isAutoConfirmEnabled,
+ ) { mutablePinEntries, isAutoConfirmEnabled ->
computeBackspaceButtonAppearance(
- interactor.getAuthenticationMethod(),
- mutablePinEntries
+ enteredPin = mutablePinEntries,
+ isAutoConfirmEnabled = isAutoConfirmEnabled,
)
}
.stateIn(
@@ -75,11 +66,14 @@
/** Appearance of the confirm button. */
val confirmButtonAppearance: StateFlow<ActionButtonAppearance> =
- flow {
- emit(null)
- emit(interactor.getAuthenticationMethod())
+ interactor.isAutoConfirmEnabled
+ .map {
+ if (it) {
+ ActionButtonAppearance.Hidden
+ } else {
+ ActionButtonAppearance.Shown
+ }
}
- .map { authMethod -> computeConfirmButtonAppearance(authMethod) }
.stateIn(
scope = applicationScope,
started = SharingStarted.Eagerly,
@@ -134,21 +128,10 @@
}
}
- private fun isAutoConfirmEnabled(authMethodModel: AuthenticationMethodModel?): Boolean {
- return (authMethodModel as? AuthenticationMethodModel.Pin)?.autoConfirm == true
- }
-
- private fun autoConfirmPinLength(authMethodModel: AuthenticationMethodModel?): Int? {
- if (!isAutoConfirmEnabled(authMethodModel)) return null
-
- return (authMethodModel as? AuthenticationMethodModel.Pin)?.code?.size
- }
-
private fun computeBackspaceButtonAppearance(
- authMethodModel: AuthenticationMethodModel,
- enteredPin: List<EnteredKey>
+ enteredPin: List<EnteredKey>,
+ isAutoConfirmEnabled: Boolean,
): ActionButtonAppearance {
- val isAutoConfirmEnabled = isAutoConfirmEnabled(authMethodModel)
val isEmpty = enteredPin.isEmpty()
return when {
@@ -157,15 +140,6 @@
else -> ActionButtonAppearance.Shown
}
}
- private fun computeConfirmButtonAppearance(
- authMethodModel: AuthenticationMethodModel?
- ): ActionButtonAppearance {
- return if (isAutoConfirmEnabled(authMethodModel)) {
- ActionButtonAppearance.Hidden
- } else {
- ActionButtonAppearance.Shown
- }
- }
}
/** Appearance of pin-pad action buttons. */
@@ -178,9 +152,6 @@
Shown,
}
-/** Auto-confirm passcodes of exactly 6 digits show a length hint, see http://shortn/_IXlmSNbDh6 */
-private const val HINTING_PASSCODE_LENGTH = 6
-
private var nextSequenceNumber = 1
/**
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/PackageUpdateMonitor.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/PackageUpdateMonitor.kt
index 1973b62..840225e 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/PackageUpdateMonitor.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/PackageUpdateMonitor.kt
@@ -52,7 +52,7 @@
/** Start monitoring for package updates. No-op if already monitoring. */
fun startMonitoring() {
if (monitoring.compareAndSet(/* expected */ false, /* new */ true)) {
- register(context, user, false, bgHandler)
+ register(context, user, bgHandler)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSysUIComponent.java
index 5493cea..94b2ab1 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSysUIComponent.java
@@ -19,6 +19,7 @@
import com.android.systemui.globalactions.ShutdownUiModule;
import com.android.systemui.keyguard.CustomizationProvider;
import com.android.systemui.scene.startable.SceneContainerStartableModule;
+import com.android.systemui.shade.ShadeModule;
import com.android.systemui.statusbar.NotificationInsetsModule;
import com.android.systemui.statusbar.QsFrameTranslateModule;
@@ -33,6 +34,7 @@
DependencyProvider.class,
NotificationInsetsModule.class,
QsFrameTranslateModule.class,
+ ShadeModule.class,
ShutdownUiModule.class,
SceneContainerStartableModule.class,
SystemUIBinder.class,
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
index f68bd49..35cf4a1 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
@@ -71,6 +71,7 @@
import com.android.systemui.statusbar.policy.SensorPrivacyController;
import com.android.systemui.statusbar.policy.SensorPrivacyControllerImpl;
import com.android.systemui.volume.dagger.VolumeModule;
+import com.android.systemui.wallpapers.dagger.WallpaperModule;
import dagger.Binds;
import dagger.Module;
@@ -106,6 +107,7 @@
StatusBarEventsModule.class,
StartCentralSurfacesModule.class,
VolumeModule.class,
+ WallpaperModule.class,
KeyboardShortcutsModule.class
})
public abstract class ReferenceSystemUIModule {
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
index b1f513d..a560acc 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
@@ -51,6 +51,7 @@
import com.android.systemui.statusbar.notification.InstantAppNotifier
import com.android.systemui.statusbar.phone.KeyguardLiftController
import com.android.systemui.statusbar.phone.LockscreenWallpaper
+import com.android.systemui.statusbar.phone.ScrimController
import com.android.systemui.stylus.StylusUsiPowerStartable
import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
import com.android.systemui.theme.ThemeOverlayController
@@ -59,6 +60,7 @@
import com.android.systemui.util.NotificationChannels
import com.android.systemui.util.StartBinderLoggerModule
import com.android.systemui.volume.VolumeUI
+import com.android.systemui.wallpapers.dagger.WallpaperModule
import com.android.systemui.wmshell.WMShell
import dagger.Binds
import dagger.Module
@@ -72,6 +74,7 @@
MultiUserUtilsModule::class,
StartControlsStartableModule::class,
StartBinderLoggerModule::class,
+ WallpaperModule::class,
])
abstract class SystemUICoreStartableModule {
/** Inject into AuthController. */
@@ -316,4 +319,9 @@
@IntoMap
@ClassKey(LockscreenWallpaper::class)
abstract fun bindLockscreenWallpaper(impl: LockscreenWallpaper): CoreStartable
+
+ @Binds
+ @IntoMap
+ @ClassKey(ScrimController::class)
+ abstract fun bindScrimController(impl: ScrimController): CoreStartable
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index eef8508..18b5612 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -81,7 +81,6 @@
import com.android.systemui.security.data.repository.SecurityRepositoryModule;
import com.android.systemui.settings.DisplayTracker;
import com.android.systemui.shade.ShadeController;
-import com.android.systemui.shade.ShadeModule;
import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
import com.android.systemui.shade.transition.LargeScreenShadeInterpolatorImpl;
import com.android.systemui.shared.condition.Monitor;
@@ -198,7 +197,6 @@
SecurityRepositoryModule.class,
ScreenRecordModule.class,
SettingsUtilModule.class,
- ShadeModule.class,
SmartRepliesInflationModule.class,
SmartspaceModule.class,
StatusBarPipelineModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
index ee046c2..484bf3d 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
@@ -26,6 +26,7 @@
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.android.app.animation.Interpolators
+import com.android.dream.lowlight.util.TruncatedInterpolator
import com.android.systemui.R
import com.android.systemui.complication.ComplicationHostViewController
import com.android.systemui.complication.ComplicationLayoutParams
@@ -204,31 +205,28 @@
translationYAnimator(
from = 0f,
to = -mDreamInTranslationYDistance.toFloat(),
- durationMs = mDreamInTranslationYDurationMs,
+ durationMs = mDreamInComplicationsAnimDurationMs,
delayMs = 0,
- interpolator = Interpolators.EMPHASIZED
+ // Truncate the animation from the full duration to match the alpha
+ // animation so that the whole animation ends at the same time.
+ interpolator =
+ TruncatedInterpolator(
+ Interpolators.EMPHASIZED,
+ /*originalDuration=*/ mDreamInTranslationYDurationMs.toFloat(),
+ /*newDuration=*/ mDreamInComplicationsAnimDurationMs.toFloat()
+ )
),
alphaAnimator(
- from =
- mCurrentAlphaAtPosition.getOrDefault(
- key = POSITION_BOTTOM,
- defaultValue = 1f
- ),
- to = 0f,
- durationMs = mDreamInComplicationsAnimDurationMs,
- delayMs = 0,
- positions = POSITION_BOTTOM
- )
- .apply {
- doOnEnd {
- // The logical end of the animation is once the alpha and blur
- // animations finish, end the animation so that any listeners are
- // notified. The Y translation animation is much longer than all of
- // the other animations due to how the spec is defined, but is not
- // expected to run to completion.
- mAnimator?.end()
- }
- },
+ from =
+ mCurrentAlphaAtPosition.getOrDefault(
+ key = POSITION_BOTTOM,
+ defaultValue = 1f
+ ),
+ to = 0f,
+ durationMs = mDreamInComplicationsAnimDurationMs,
+ delayMs = 0,
+ positions = POSITION_BOTTOM
+ ),
alphaAnimator(
from =
mCurrentAlphaAtPosition.getOrDefault(
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 5d5fb55..0670ec3 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -61,10 +61,6 @@
// TODO(b/254512538): Tracking Bug
val INSTANT_VOICE_REPLY = unreleasedFlag(111, "instant_voice_reply")
- // TODO(b/279735475): Tracking Bug
- @JvmField
- val NEW_LIGHT_BAR_LOGIC = releasedFlag(279735475, "new_light_bar_logic")
-
/**
* This flag is server-controlled and should stay as [unreleasedFlag] since we never want to
* enable it on release builds.
@@ -72,9 +68,6 @@
val NOTIFICATION_MEMORY_LOGGING_ENABLED =
unreleasedFlag(119, "notification_memory_logging_enabled")
- // TODO(b/257315550): Tracking Bug
- val NO_HUN_FOR_OLD_WHEN = releasedFlag(118, "no_hun_for_old_when")
-
// TODO(b/260335638): Tracking Bug
@JvmField
val NOTIFICATION_INLINE_REPLY_ANIMATION =
@@ -91,6 +84,11 @@
val NOTIFICATION_SHELF_REFACTOR =
unreleasedFlag(271161129, "notification_shelf_refactor", teamfood = true)
+ // TODO(b/290787599): Tracking Bug
+ @JvmField
+ val NOTIFICATION_ICON_CONTAINER_REFACTOR =
+ unreleasedFlag(278765923, "notification_icon_container_refactor")
+
// TODO(b/288326013): Tracking Bug
@JvmField
val NOTIFICATION_ASYNC_HYBRID_VIEW_INFLATION =
@@ -258,6 +256,16 @@
@JvmField
val MIGRATE_INDICATION_AREA = unreleasedFlag(236, "migrate_indication_area", teamfood = true)
+ /**
+ * Migrate the bottom area to the new keyguard root view.
+ * Because there is no such thing as a "bottom area" after this, this also breaks it up into
+ * many smaller, modular pieces.
+ */
+ // TODO(b/290652751): Tracking bug.
+ @JvmField
+ val MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA =
+ unreleasedFlag(290652751, "migrate_split_keyguard_bottom_area")
+
/** Whether to listen for fingerprint authentication over keyguard occluding activities. */
// TODO(b/283260512): Tracking bug.
@JvmField
@@ -275,7 +283,16 @@
/** Migrate the lock icon view to the new keyguard root view. */
// TODO(b/286552209): Tracking bug.
@JvmField
- val MIGRATE_LOCK_ICON = unreleasedFlag(240, "migrate_lock_icon")
+ val MIGRATE_LOCK_ICON = unreleasedFlag(240, "migrate_lock_icon", teamfood = true)
+
+ // TODO(b/288276738): Tracking bug.
+ @JvmField
+ val WIDGET_ON_KEYGUARD = unreleasedFlag(241, "widget_on_keyguard")
+
+ /** Migrate the NSSL to the a sibling to both the panel and keyguard root view. */
+ // TODO(b/288074305): Tracking bug.
+ @JvmField
+ val MIGRATE_NSSL = unreleasedFlag(242, "migrate_nssl")
// 300 - power menu
// TODO(b/254512600): Tracking Bug
@@ -348,22 +365,10 @@
// TODO(b/256614753): Tracking Bug
val NEW_STATUS_BAR_MOBILE_ICONS = releasedFlag(606, "new_status_bar_mobile_icons")
- // TODO(b/256614210): Tracking Bug
- val NEW_STATUS_BAR_WIFI_ICON = releasedFlag(607, "new_status_bar_wifi_icon")
-
// TODO(b/256614751): Tracking Bug
val NEW_STATUS_BAR_MOBILE_ICONS_BACKEND =
unreleasedFlag(608, "new_status_bar_mobile_icons_backend", teamfood = true)
- // TODO(b/256613548): Tracking Bug
- val NEW_STATUS_BAR_WIFI_ICON_BACKEND =
- unreleasedFlag(609, "new_status_bar_wifi_icon_backend", teamfood = true)
-
- // TODO(b/256623670): Tracking Bug
- @JvmField
- val BATTERY_SHIELD_ICON =
- resourceBooleanFlag(610, R.bool.flag_battery_shield_icon, "battery_shield_icon")
-
// TODO(b/260881289): Tracking Bug
val NEW_STATUS_BAR_ICONS_DEBUG_COLORING =
unreleasedFlag(611, "new_status_bar_icons_debug_coloring")
@@ -433,10 +438,6 @@
// TODO(b/266157412): Tracking Bug
val MEDIA_RETAIN_SESSIONS = unreleasedFlag(913, "media_retain_sessions")
- // TODO(b/266739309): Tracking Bug
- @JvmField
- val MEDIA_RECOMMENDATION_CARD_UPDATE = releasedFlag(914, "media_recommendation_card_update")
-
// TODO(b/267007629): Tracking Bug
val MEDIA_RESUME_PROGRESS = releasedFlag(915, "media_resume_progress")
@@ -694,11 +695,11 @@
// TODO(b/283071711): Tracking bug
@JvmField
val TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK =
- releasedFlag(2401, "trim_resources_with_background_trim_on_lock")
+ unreleasedFlag(2401, "trim_resources_with_background_trim_on_lock")
// TODO:(b/283203305): Tracking bug
@JvmField
- val TRIM_FONT_CACHES_AT_UNLOCK = releasedFlag(2402, "trim_font_caches_on_unlock")
+ val TRIM_FONT_CACHES_AT_UNLOCK = unreleasedFlag(2402, "trim_font_caches_on_unlock")
// 2700 - unfold transitions
// TODO(b/265764985): Tracking Bug
@@ -770,9 +771,21 @@
val ENABLE_NEW_PRIVACY_DIALOG =
unreleasedFlag(283740863, "enable_new_privacy_dialog", teamfood = false)
+ // TODO(b/289573946): Tracking Bug
+ @JvmField
+ val PRECOMPUTED_TEXT =
+ unreleasedFlag(289573946, "precomputed_text")
+
// 2900 - CentralSurfaces-related flags
// TODO(b/285174336): Tracking Bug
@JvmField
val USE_REPOS_FOR_BOUNCER_SHOWING = unreleasedFlag(2900, "use_repos_for_bouncer_showing")
+
+ // 3100 - Haptic interactions
+
+ // TODO(b/290213663): Tracking Bug
+ @JvmField
+ val ONE_WAY_HAPTICS_API_MIGRATION =
+ unreleasedFlag(3100, "oneway_haptics_api_migration")
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
index f59ad90..23f6fa6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
@@ -31,6 +31,9 @@
import com.android.systemui.keyguard.ui.viewmodel.KeyguardIndicationAreaViewModel
import com.android.systemui.shade.NotificationShadeWindowView
import com.android.systemui.statusbar.KeyguardIndicationController
+import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
+import com.android.systemui.statusbar.notification.stack.ui.viewbinder.SharedNotificationContainerBinder
+import com.android.systemui.statusbar.notification.stack.ui.viewmodel.SharedNotificationContainerViewModel
import javax.inject.Inject
import kotlinx.coroutines.DisposableHandle
@@ -40,7 +43,9 @@
@Inject
constructor(
private val keyguardRootView: KeyguardRootView,
+ private val sharedNotificationContainer: SharedNotificationContainer,
private val keyguardIndicationAreaViewModel: KeyguardIndicationAreaViewModel,
+ private val sharedNotificationContainerViewModel: SharedNotificationContainerViewModel,
private val notificationShadeWindowView: NotificationShadeWindowView,
private val featureFlags: FeatureFlags,
private val indicationController: KeyguardIndicationController,
@@ -55,10 +60,28 @@
notificationShadeWindowView.requireViewById(R.id.notification_panel) as ViewGroup
bindIndicationArea(notificationPanel)
bindLockIconView(notificationPanel)
+ setupNotificationStackScrollLayout(notificationPanel)
+
keyguardLayoutManager.layoutViews()
keyguardLayoutManagerCommandListener.start()
}
+ fun setupNotificationStackScrollLayout(legacyParent: ViewGroup) {
+ if (featureFlags.isEnabled(Flags.MIGRATE_NSSL)) {
+ // This moves the existing NSSL view to a different parent, as the controller is a
+ // singleton and recreating it has other bad side effects
+ val nssl =
+ legacyParent.requireViewById<View>(R.id.notification_stack_scroller).also {
+ (it.getParent() as ViewGroup).removeView(it)
+ }
+ sharedNotificationContainer.addNotificationStackScrollLayout(nssl)
+ SharedNotificationContainerBinder.bind(
+ sharedNotificationContainer,
+ sharedNotificationContainerViewModel
+ )
+ }
+ }
+
fun bindIndicationArea(legacyParent: ViewGroup) {
indicationAreaHandle?.dispose()
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 9a32e94..ddd4a2b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -163,9 +163,11 @@
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.UserSwitcherController;
import com.android.systemui.util.DeviceConfigProxy;
+import com.android.systemui.util.kotlin.JavaAdapter;
import com.android.systemui.util.settings.SecureSettings;
import com.android.systemui.util.settings.SystemSettings;
import com.android.systemui.util.time.SystemClock;
+import com.android.systemui.wallpapers.data.repository.WallpaperRepository;
import com.android.wm.shell.keyguard.KeyguardTransitions;
import dagger.Lazy;
@@ -290,6 +292,8 @@
public static final String SYS_BOOT_REASON_PROP = "sys.boot.reason.last";
public static final String REBOOT_MAINLINE_UPDATE = "reboot,mainline_update";
private final DreamOverlayStateController mDreamOverlayStateController;
+ private final JavaAdapter mJavaAdapter;
+ private final WallpaperRepository mWallpaperRepository;
/** The stream type that the lock sounds are tied to. */
private int mUiSoundsStreamType;
@@ -1323,6 +1327,8 @@
KeyguardTransitions keyguardTransitions,
InteractionJankMonitor interactionJankMonitor,
DreamOverlayStateController dreamOverlayStateController,
+ JavaAdapter javaAdapter,
+ WallpaperRepository wallpaperRepository,
Lazy<ShadeController> shadeControllerLazy,
Lazy<NotificationShadeWindowController> notificationShadeWindowControllerLazy,
Lazy<ActivityLaunchAnimator> activityLaunchAnimator,
@@ -1384,6 +1390,8 @@
mScreenOffAnimationController = screenOffAnimationController;
mInteractionJankMonitor = interactionJankMonitor;
mDreamOverlayStateController = dreamOverlayStateController;
+ mJavaAdapter = javaAdapter;
+ mWallpaperRepository = wallpaperRepository;
mActivityLaunchAnimator = activityLaunchAnimator;
mScrimControllerLazy = scrimControllerLazy;
@@ -1487,6 +1495,10 @@
com.android.internal.R.anim.lock_screen_behind_enter);
mWorkLockController = new WorkLockActivityController(mContext, mUserTracker);
+
+ mJavaAdapter.alwaysCollectFlow(
+ mWallpaperRepository.getWallpaperSupportsAmbientMode(),
+ this::setWallpaperSupportsAmbientMode);
}
// TODO(b/273443374) remove, temporary util to get a feature flag
@@ -3464,7 +3476,7 @@
* In case it does support it, we have to fade in the incoming app, otherwise we'll reveal it
* with the light reveal scrim.
*/
- public void setWallpaperSupportsAmbientMode(boolean supportsAmbientMode) {
+ private void setWallpaperSupportsAmbientMode(boolean supportsAmbientMode) {
mWallpaperSupportsAmbientMode = supportsAmbientMode;
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index 1487408..a5ac7c7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -67,9 +67,11 @@
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.UserSwitcherController;
import com.android.systemui.util.DeviceConfigProxy;
+import com.android.systemui.util.kotlin.JavaAdapter;
import com.android.systemui.util.settings.SecureSettings;
import com.android.systemui.util.settings.SystemSettings;
import com.android.systemui.util.time.SystemClock;
+import com.android.systemui.wallpapers.data.repository.WallpaperRepository;
import com.android.wm.shell.keyguard.KeyguardTransitions;
import dagger.Lazy;
@@ -131,6 +133,8 @@
KeyguardTransitions keyguardTransitions,
InteractionJankMonitor interactionJankMonitor,
DreamOverlayStateController dreamOverlayStateController,
+ JavaAdapter javaAdapter,
+ WallpaperRepository wallpaperRepository,
Lazy<ShadeController> shadeController,
Lazy<NotificationShadeWindowController> notificationShadeWindowController,
Lazy<ActivityLaunchAnimator> activityLaunchAnimator,
@@ -172,6 +176,8 @@
keyguardTransitions,
interactionJankMonitor,
dreamOverlayStateController,
+ javaAdapter,
+ wallpaperRepository,
shadeController,
notificationShadeWindowController,
activityLaunchAnimator,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
index a3d1abe..6edf40f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
@@ -38,13 +38,13 @@
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.shared.model.AcquiredAuthenticationStatus
-import com.android.systemui.keyguard.shared.model.AuthenticationStatus
-import com.android.systemui.keyguard.shared.model.DetectionStatus
-import com.android.systemui.keyguard.shared.model.ErrorAuthenticationStatus
-import com.android.systemui.keyguard.shared.model.FailedAuthenticationStatus
-import com.android.systemui.keyguard.shared.model.HelpAuthenticationStatus
-import com.android.systemui.keyguard.shared.model.SuccessAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.AcquiredFaceAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.ErrorFaceAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.FaceAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.FaceDetectionStatus
+import com.android.systemui.keyguard.shared.model.FailedFaceAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.HelpFaceAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.SuccessFaceAuthenticationStatus
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.log.FaceAuthenticationLogger
import com.android.systemui.log.SessionTracker
@@ -88,10 +88,10 @@
val canRunFaceAuth: StateFlow<Boolean>
/** Provide the current status of face authentication. */
- val authenticationStatus: Flow<AuthenticationStatus>
+ val authenticationStatus: Flow<FaceAuthenticationStatus>
/** Provide the current status of face detection. */
- val detectionStatus: Flow<DetectionStatus>
+ val detectionStatus: Flow<FaceDetectionStatus>
/** Current state of whether face authentication is locked out or not. */
val isLockedOut: StateFlow<Boolean>
@@ -151,13 +151,13 @@
private var cancelNotReceivedHandlerJob: Job? = null
private var halErrorRetryJob: Job? = null
- private val _authenticationStatus: MutableStateFlow<AuthenticationStatus?> =
+ private val _authenticationStatus: MutableStateFlow<FaceAuthenticationStatus?> =
MutableStateFlow(null)
- override val authenticationStatus: Flow<AuthenticationStatus>
+ override val authenticationStatus: Flow<FaceAuthenticationStatus>
get() = _authenticationStatus.filterNotNull()
- private val _detectionStatus = MutableStateFlow<DetectionStatus?>(null)
- override val detectionStatus: Flow<DetectionStatus>
+ private val _detectionStatus = MutableStateFlow<FaceDetectionStatus?>(null)
+ override val detectionStatus: Flow<FaceDetectionStatus>
get() = _detectionStatus.filterNotNull()
private val _isLockedOut = MutableStateFlow(false)
@@ -396,18 +396,18 @@
private val faceAuthCallback =
object : FaceManager.AuthenticationCallback() {
override fun onAuthenticationFailed() {
- _authenticationStatus.value = FailedAuthenticationStatus
+ _authenticationStatus.value = FailedFaceAuthenticationStatus
_isAuthenticated.value = false
faceAuthLogger.authenticationFailed()
onFaceAuthRequestCompleted()
}
override fun onAuthenticationAcquired(acquireInfo: Int) {
- _authenticationStatus.value = AcquiredAuthenticationStatus(acquireInfo)
+ _authenticationStatus.value = AcquiredFaceAuthenticationStatus(acquireInfo)
}
override fun onAuthenticationError(errorCode: Int, errString: CharSequence?) {
- val errorStatus = ErrorAuthenticationStatus(errorCode, errString.toString())
+ val errorStatus = ErrorFaceAuthenticationStatus(errorCode, errString.toString())
if (errorStatus.isLockoutError()) {
_isLockedOut.value = true
}
@@ -433,11 +433,11 @@
if (faceAcquiredInfoIgnoreList.contains(code)) {
return
}
- _authenticationStatus.value = HelpAuthenticationStatus(code, helpStr.toString())
+ _authenticationStatus.value = HelpFaceAuthenticationStatus(code, helpStr.toString())
}
override fun onAuthenticationSucceeded(result: FaceManager.AuthenticationResult) {
- _authenticationStatus.value = SuccessAuthenticationStatus(result)
+ _authenticationStatus.value = SuccessFaceAuthenticationStatus(result)
_isAuthenticated.value = true
faceAuthLogger.faceAuthSuccess(result)
onFaceAuthRequestCompleted()
@@ -482,7 +482,7 @@
private val detectionCallback =
FaceManager.FaceDetectionCallback { sensorId, userId, isStrong ->
faceAuthLogger.faceDetected()
- _detectionStatus.value = DetectionStatus(sensorId, userId, isStrong)
+ _detectionStatus.value = FaceDetectionStatus(sensorId, userId, isStrong)
}
private var cancellationInProgress = false
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt
index 52234b3..616ce39 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt
@@ -21,27 +21,29 @@
import android.hardware.biometrics.BiometricSourceType
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardUpdateMonitorCallback
-import com.android.systemui.Dumpable
import com.android.systemui.biometrics.AuthController
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.dump.DumpManager
-import java.io.PrintWriter
+import com.android.systemui.keyguard.shared.model.AcquiredFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.ErrorFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.FailedFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.FingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.HelpFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.stateIn
/** Encapsulates state about device entry fingerprint auth mechanism. */
interface DeviceEntryFingerprintAuthRepository {
/** Whether the device entry fingerprint auth is locked out. */
- val isLockedOut: StateFlow<Boolean>
+ val isLockedOut: Flow<Boolean>
/**
* Whether the fingerprint sensor is currently listening, this doesn't mean that the user is
@@ -53,6 +55,9 @@
* Fingerprint sensor type present on the device, null if fingerprint sensor is not available.
*/
val availableFpSensorType: Flow<BiometricType?>
+
+ /** Provide the current status of fingerprint authentication. */
+ val authenticationStatus: Flow<FingerprintAuthenticationStatus>
}
/**
@@ -69,16 +74,7 @@
val authController: AuthController,
val keyguardUpdateMonitor: KeyguardUpdateMonitor,
@Application scope: CoroutineScope,
- dumpManager: DumpManager,
-) : DeviceEntryFingerprintAuthRepository, Dumpable {
-
- init {
- dumpManager.registerDumpable(this)
- }
-
- override fun dump(pw: PrintWriter, args: Array<String?>) {
- pw.println("isLockedOut=${isLockedOut.value}")
- }
+) : DeviceEntryFingerprintAuthRepository {
override val availableFpSensorType: Flow<BiometricType?>
get() {
@@ -114,7 +110,7 @@
else if (authController.isRearFpsSupported) BiometricType.REAR_FINGERPRINT else null
}
- override val isLockedOut: StateFlow<Boolean> =
+ override val isLockedOut: Flow<Boolean> =
conflatedCallbackFlow {
val sendLockoutUpdate =
fun() {
@@ -138,7 +134,7 @@
sendLockoutUpdate()
awaitClose { keyguardUpdateMonitor.removeCallback(callback) }
}
- .stateIn(scope, started = SharingStarted.Eagerly, initialValue = false)
+ .stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = false)
override val isRunning: Flow<Boolean>
get() = conflatedCallbackFlow {
@@ -166,6 +162,93 @@
awaitClose { keyguardUpdateMonitor.removeCallback(callback) }
}
+ override val authenticationStatus: Flow<FingerprintAuthenticationStatus>
+ get() = conflatedCallbackFlow {
+ val callback =
+ object : KeyguardUpdateMonitorCallback() {
+ override fun onBiometricAuthenticated(
+ userId: Int,
+ biometricSourceType: BiometricSourceType,
+ isStrongBiometric: Boolean,
+ ) {
+
+ sendUpdateIfFingerprint(
+ biometricSourceType,
+ SuccessFingerprintAuthenticationStatus(
+ userId,
+ isStrongBiometric,
+ ),
+ )
+ }
+
+ override fun onBiometricError(
+ msgId: Int,
+ errString: String?,
+ biometricSourceType: BiometricSourceType,
+ ) {
+ sendUpdateIfFingerprint(
+ biometricSourceType,
+ ErrorFingerprintAuthenticationStatus(
+ msgId,
+ errString,
+ ),
+ )
+ }
+
+ override fun onBiometricHelp(
+ msgId: Int,
+ helpString: String?,
+ biometricSourceType: BiometricSourceType,
+ ) {
+ sendUpdateIfFingerprint(
+ biometricSourceType,
+ HelpFingerprintAuthenticationStatus(
+ msgId,
+ helpString,
+ ),
+ )
+ }
+
+ override fun onBiometricAuthFailed(
+ biometricSourceType: BiometricSourceType,
+ ) {
+ sendUpdateIfFingerprint(
+ biometricSourceType,
+ FailedFingerprintAuthenticationStatus,
+ )
+ }
+
+ override fun onBiometricAcquired(
+ biometricSourceType: BiometricSourceType,
+ acquireInfo: Int,
+ ) {
+ sendUpdateIfFingerprint(
+ biometricSourceType,
+ AcquiredFingerprintAuthenticationStatus(
+ acquireInfo,
+ ),
+ )
+ }
+
+ private fun sendUpdateIfFingerprint(
+ biometricSourceType: BiometricSourceType,
+ authenticationStatus: FingerprintAuthenticationStatus
+ ) {
+ if (biometricSourceType != BiometricSourceType.FINGERPRINT) {
+ return
+ }
+
+ trySendWithFailureLogging(
+ authenticationStatus,
+ TAG,
+ "new fingerprint authentication status"
+ )
+ }
+ }
+ keyguardUpdateMonitor.registerCallback(callback)
+ awaitClose { keyguardUpdateMonitor.removeCallback(callback) }
+ }
+
companion object {
const val TAG = "DeviceEntryFingerprintAuthRepositoryImpl"
}
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 e7704d6..7475c42 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
@@ -31,26 +31,31 @@
import com.android.systemui.doze.DozeTransitionCallback
import com.android.systemui.doze.DozeTransitionListener
import com.android.systemui.dreams.DreamOverlayCallbackController
+import com.android.systemui.keyguard.ScreenLifecycle
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
import com.android.systemui.keyguard.shared.model.DozeStateModel
import com.android.systemui.keyguard.shared.model.DozeTransitionModel
+import com.android.systemui.keyguard.shared.model.ScreenModel
import com.android.systemui.keyguard.shared.model.StatusBarState
import com.android.systemui.keyguard.shared.model.WakefulnessModel
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.phone.BiometricUnlockController
import com.android.systemui.statusbar.phone.BiometricUnlockController.WakeAndUnlockMode
import com.android.systemui.statusbar.phone.DozeParameters
+import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.statusbar.policy.KeyguardStateController
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOn
@@ -87,7 +92,7 @@
val isKeyguardShowing: Flow<Boolean>
/** Is the keyguard in a unlocked state? */
- val isKeyguardUnlocked: Flow<Boolean>
+ val isKeyguardUnlocked: StateFlow<Boolean>
/** Is an activity showing over the keyguard? */
val isKeyguardOccluded: Flow<Boolean>
@@ -147,6 +152,9 @@
/** Observable for device wake/sleep state */
val wakefulness: StateFlow<WakefulnessModel>
+ /** Observable for device screen state */
+ val screenModel: StateFlow<ScreenModel>
+
/** Observable for biometric unlock modes */
val biometricUnlockState: Flow<BiometricUnlockModel>
@@ -162,6 +170,9 @@
/** Whether quick settings or quick-quick settings is visible. */
val isQuickSettingsVisible: Flow<Boolean>
+ /** Receive an event for doze time tick */
+ val dozeTimeTick: Flow<Unit>
+
/**
* Returns `true` if the keyguard is showing; `false` otherwise.
*
@@ -171,6 +182,14 @@
*/
fun isKeyguardShowing(): Boolean
+ /**
+ * Whether lock screen bypass is enabled. When enabled, the lock screen will be automatically
+ * dismissed once the authentication challenge is completed. For example, completing a biometric
+ * authentication challenge via face unlock or fingerprint sensor can automatically bypass the
+ * lock screen.
+ */
+ fun isBypassEnabled(): Boolean
+
/** Sets whether the bottom area UI should animate the transition out of doze state. */
fun setAnimateDozingTransitions(animate: Boolean)
@@ -195,6 +214,8 @@
fun setIsDozing(isDozing: Boolean)
fun setIsActiveDreamLockscreenHosted(isLockscreenHosted: Boolean)
+
+ fun dozeTimeTick()
}
/** Encapsulates application state for the keyguard. */
@@ -204,8 +225,10 @@
constructor(
statusBarStateController: StatusBarStateController,
wakefulnessLifecycle: WakefulnessLifecycle,
+ screenLifecycle: ScreenLifecycle,
biometricUnlockController: BiometricUnlockController,
private val keyguardStateController: KeyguardStateController,
+ private val keyguardBypassController: KeyguardBypassController,
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
private val dozeTransitionListener: DozeTransitionListener,
private val dozeParameters: DozeParameters,
@@ -252,23 +275,17 @@
override val isAodAvailable: Flow<Boolean> =
conflatedCallbackFlow {
val callback =
- object : DozeParameters.Callback {
- override fun onAlwaysOnChange() {
- trySendWithFailureLogging(
- dozeParameters.getAlwaysOn(),
- TAG,
- "updated isAodAvailable"
- )
- }
+ DozeParameters.Callback {
+ trySendWithFailureLogging(
+ dozeParameters.alwaysOn,
+ TAG,
+ "updated isAodAvailable"
+ )
}
dozeParameters.addCallback(callback)
// Adding the callback does not send an initial update.
- trySendWithFailureLogging(
- dozeParameters.getAlwaysOn(),
- TAG,
- "initial isAodAvailable"
- )
+ trySendWithFailureLogging(dozeParameters.alwaysOn, TAG, "initial isAodAvailable")
awaitClose { dozeParameters.removeCallback(callback) }
}
@@ -299,7 +316,7 @@
}
.distinctUntilChanged()
- override val isKeyguardUnlocked: Flow<Boolean> =
+ override val isKeyguardUnlocked: StateFlow<Boolean> =
conflatedCallbackFlow {
val callback =
object : KeyguardStateController.Callback {
@@ -330,7 +347,11 @@
awaitClose { keyguardStateController.removeCallback(callback) }
}
- .distinctUntilChanged()
+ .stateIn(
+ scope = scope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = keyguardStateController.isUnlocked,
+ )
override val isKeyguardGoingAway: Flow<Boolean> = conflatedCallbackFlow {
val callback =
@@ -362,6 +383,13 @@
_isDozing.value = isDozing
}
+ private val _dozeTimeTick = MutableSharedFlow<Unit>()
+ override val dozeTimeTick = _dozeTimeTick.asSharedFlow()
+
+ override fun dozeTimeTick() {
+ _dozeTimeTick.tryEmit(Unit)
+ }
+
private val _lastDozeTapToWakePosition = MutableStateFlow<Point?>(null)
override val lastDozeTapToWakePosition = _lastDozeTapToWakePosition.asStateFlow()
@@ -460,6 +488,10 @@
return keyguardStateController.isShowing
}
+ override fun isBypassEnabled(): Boolean {
+ return keyguardBypassController.bypassEnabled
+ }
+
override val statusBarState: Flow<StatusBarState> = conflatedCallbackFlow {
val callback =
object : StatusBarStateController.StateListener {
@@ -547,6 +579,42 @@
initialValue = WakefulnessModel.fromWakefulnessLifecycle(wakefulnessLifecycle),
)
+ override val screenModel: StateFlow<ScreenModel> =
+ conflatedCallbackFlow {
+ val observer =
+ object : ScreenLifecycle.Observer {
+ override fun onScreenTurningOn() {
+ dispatchNewState()
+ }
+ override fun onScreenTurnedOn() {
+ dispatchNewState()
+ }
+ override fun onScreenTurningOff() {
+ dispatchNewState()
+ }
+ override fun onScreenTurnedOff() {
+ dispatchNewState()
+ }
+
+ private fun dispatchNewState() {
+ trySendWithFailureLogging(
+ ScreenModel.fromScreenLifecycle(screenLifecycle),
+ TAG,
+ "updated screen state",
+ )
+ }
+ }
+
+ screenLifecycle.addObserver(observer)
+ awaitClose { screenLifecycle.removeObserver(observer) }
+ }
+ .stateIn(
+ scope,
+ // Use Eagerly so that we're always listening and never miss an event.
+ SharingStarted.Eagerly,
+ initialValue = ScreenModel.fromScreenLifecycle(screenLifecycle),
+ )
+
override val fingerprintSensorLocation: Flow<Point?> = conflatedCallbackFlow {
fun sendFpLocation() {
trySendWithFailureLogging(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/NoopDeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/NoopDeviceEntryFaceAuthRepository.kt
index abe59b7..27e3a74 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/NoopDeviceEntryFaceAuthRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/NoopDeviceEntryFaceAuthRepository.kt
@@ -18,8 +18,8 @@
import com.android.keyguard.FaceAuthUiEvent
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.shared.model.AuthenticationStatus
-import com.android.systemui.keyguard.shared.model.DetectionStatus
+import com.android.systemui.keyguard.shared.model.FaceAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.FaceDetectionStatus
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
@@ -40,10 +40,10 @@
private val _canRunFaceAuth = MutableStateFlow(false)
override val canRunFaceAuth: StateFlow<Boolean> = _canRunFaceAuth
- override val authenticationStatus: Flow<AuthenticationStatus>
+ override val authenticationStatus: Flow<FaceAuthenticationStatus>
get() = emptyFlow()
- override val detectionStatus: Flow<DetectionStatus>
+ override val detectionStatus: Flow<FaceDetectionStatus>
get() = emptyFlow()
private val _isLockedOut = MutableStateFlow(false)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DozeInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DozeInteractor.kt
index 2efcd0c..0c898be 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DozeInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DozeInteractor.kt
@@ -35,4 +35,8 @@
fun setLastTapToWakePosition(position: Point) {
keyguardRepository.setLastDozeTapToWakePosition(position)
}
+
+ fun dozeTimeTick() {
+ keyguardRepository.dozeTimeTick()
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt
index 74ef7a5..141b130 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt
@@ -16,8 +16,8 @@
package com.android.systemui.keyguard.domain.interactor
-import com.android.systemui.keyguard.shared.model.AuthenticationStatus
-import com.android.systemui.keyguard.shared.model.DetectionStatus
+import com.android.systemui.keyguard.shared.model.FaceAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.FaceDetectionStatus
import kotlinx.coroutines.flow.Flow
/**
@@ -27,10 +27,10 @@
interface KeyguardFaceAuthInteractor {
/** Current authentication status */
- val authenticationStatus: Flow<AuthenticationStatus>
+ val authenticationStatus: Flow<FaceAuthenticationStatus>
/** Current detection status */
- val detectionStatus: Flow<DetectionStatus>
+ val detectionStatus: Flow<FaceDetectionStatus>
/** Can face auth be run right now */
fun canFaceAuthRun(): Boolean
@@ -72,8 +72,8 @@
*/
interface FaceAuthenticationListener {
/** Receive face authentication status updates */
- fun onAuthenticationStatusChanged(status: AuthenticationStatus)
+ fun onAuthenticationStatusChanged(status: FaceAuthenticationStatus)
/** Receive status updates whenever face detection runs */
- fun onDetectionStatusChanged(status: DetectionStatus)
+ fun onDetectionStatusChanged(status: FaceDetectionStatus)
}
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 7fae752..1553525 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
@@ -70,6 +70,8 @@
val dozeAmount: Flow<Float> = repository.linearDozeAmount
/** Whether the system is in doze mode. */
val isDozing: Flow<Boolean> = repository.isDozing
+ /** Receive an event for doze time tick */
+ val dozeTimeTick: Flow<Unit> = repository.dozeTimeTick
/** Whether Always-on Display mode is available. */
val isAodAvailable: Flow<Boolean> = repository.isAodAvailable
/** Doze transition information. */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
index 320b2a4..324d443 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
@@ -414,7 +414,7 @@
}
private suspend fun isFeatureDisabledByDevicePolicy(): Boolean =
- traceAsync("isFeatureDisabledByDevicePolicy", TAG) {
+ traceAsync(TAG, "isFeatureDisabledByDevicePolicy") {
withContext(backgroundDispatcher) {
devicePolicyManager.areKeyguardShortcutsDisabled(userId = userTracker.userId)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenSceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenSceneInteractor.kt
index c8f7efb..1c200b0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenSceneInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenSceneInteractor.kt
@@ -20,20 +20,14 @@
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.scene.domain.interactor.SceneInteractor
-import com.android.systemui.scene.shared.model.SceneKey
-import com.android.systemui.scene.shared.model.SceneModel
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
-import kotlinx.coroutines.launch
/** Hosts business and application state accessing logic for the lockscreen scene. */
class LockscreenSceneInteractor
@@ -42,7 +36,6 @@
@Application applicationScope: CoroutineScope,
private val authenticationInteractor: AuthenticationInteractor,
bouncerInteractorFactory: BouncerInteractor.Factory,
- private val sceneInteractor: SceneInteractor,
@Assisted private val containerName: String,
) {
private val bouncerInteractor: BouncerInteractor =
@@ -72,46 +65,6 @@
initialValue = false,
)
- init {
- // LOCKING SHOWS Lockscreen.
- //
- // Move to the lockscreen scene if the device becomes locked while in any scene.
- applicationScope.launch {
- authenticationInteractor.isUnlocked
- .map { !it }
- .distinctUntilChanged()
- .collect { isLocked ->
- if (isLocked) {
- sceneInteractor.setCurrentScene(
- containerName = containerName,
- scene = SceneModel(SceneKey.Lockscreen),
- )
- }
- }
- }
-
- // BYPASS UNLOCK.
- //
- // Moves to the gone scene if bypass is enabled and the device becomes unlocked while in the
- // lockscreen scene.
- applicationScope.launch {
- combine(
- authenticationInteractor.isBypassEnabled,
- authenticationInteractor.isUnlocked,
- sceneInteractor.currentScene(containerName),
- ::Triple,
- )
- .collect { (isBypassEnabled, isUnlocked, currentScene) ->
- if (isBypassEnabled && isUnlocked && currentScene.key == SceneKey.Lockscreen) {
- sceneInteractor.setCurrentScene(
- containerName = containerName,
- scene = SceneModel(SceneKey.Gone),
- )
- }
- }
- }
- }
-
/** Attempts to dismiss the lockscreen. This will cause the bouncer to show, if needed. */
fun dismissLockscreen() {
bouncerInteractor.showOrUnlockDevice(containerName = containerName)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NoopKeyguardFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NoopKeyguardFaceAuthInteractor.kt
index 5005b6c..10dd900 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NoopKeyguardFaceAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NoopKeyguardFaceAuthInteractor.kt
@@ -17,8 +17,8 @@
package com.android.systemui.keyguard.domain.interactor
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.shared.model.AuthenticationStatus
-import com.android.systemui.keyguard.shared.model.DetectionStatus
+import com.android.systemui.keyguard.shared.model.FaceAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.FaceDetectionStatus
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.emptyFlow
@@ -31,9 +31,9 @@
*/
@SysUISingleton
class NoopKeyguardFaceAuthInteractor @Inject constructor() : KeyguardFaceAuthInteractor {
- override val authenticationStatus: Flow<AuthenticationStatus>
+ override val authenticationStatus: Flow<FaceAuthenticationStatus>
get() = emptyFlow()
- override val detectionStatus: Flow<DetectionStatus>
+ override val detectionStatus: Flow<FaceDetectionStatus>
get() = emptyFlow()
override fun canFaceAuthRun(): Boolean = false
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt
index d467225..6e7a20b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt
@@ -30,8 +30,8 @@
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.data.repository.DeviceEntryFaceAuthRepository
-import com.android.systemui.keyguard.shared.model.AuthenticationStatus
-import com.android.systemui.keyguard.shared.model.ErrorAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.ErrorFaceAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.FaceAuthenticationStatus
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.log.FaceAuthenticationLogger
import com.android.systemui.util.kotlin.pairwise
@@ -165,10 +165,10 @@
repository.cancel()
}
- private val _authenticationStatusOverride = MutableStateFlow<AuthenticationStatus?>(null)
+ private val faceAuthenticationStatusOverride = MutableStateFlow<FaceAuthenticationStatus?>(null)
/** Provide the status of face authentication */
override val authenticationStatus =
- merge(_authenticationStatusOverride.filterNotNull(), repository.authenticationStatus)
+ merge(faceAuthenticationStatusOverride.filterNotNull(), repository.authenticationStatus)
/** Provide the status of face detection */
override val detectionStatus = repository.detectionStatus
@@ -176,13 +176,13 @@
private fun runFaceAuth(uiEvent: FaceAuthUiEvent, fallbackToDetect: Boolean) {
if (featureFlags.isEnabled(Flags.FACE_AUTH_REFACTOR)) {
if (repository.isLockedOut.value) {
- _authenticationStatusOverride.value =
- ErrorAuthenticationStatus(
+ faceAuthenticationStatusOverride.value =
+ ErrorFaceAuthenticationStatus(
BiometricFaceConstants.FACE_ERROR_LOCKOUT_PERMANENT,
context.resources.getString(R.string.keyguard_face_unlock_unavailable)
)
} else {
- _authenticationStatusOverride.value = null
+ faceAuthenticationStatusOverride.value = null
applicationScope.launch {
faceAuthenticationLogger.authRequested(uiEvent)
repository.authenticate(uiEvent, fallbackToDetection = fallbackToDetect)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FaceAuthenticationModels.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FaceAuthenticationModels.kt
index b354cfd..0f6d82e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FaceAuthenticationModels.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FaceAuthenticationModels.kt
@@ -23,33 +23,35 @@
* Authentication status provided by
* [com.android.systemui.keyguard.data.repository.DeviceEntryFaceAuthRepository]
*/
-sealed class AuthenticationStatus
+sealed class FaceAuthenticationStatus
/** Success authentication status. */
-data class SuccessAuthenticationStatus(val successResult: FaceManager.AuthenticationResult) :
- AuthenticationStatus()
+data class SuccessFaceAuthenticationStatus(val successResult: FaceManager.AuthenticationResult) :
+ FaceAuthenticationStatus()
/** Face authentication help message. */
-data class HelpAuthenticationStatus(val msgId: Int, val msg: String?) : AuthenticationStatus()
+data class HelpFaceAuthenticationStatus(val msgId: Int, val msg: String?) :
+ FaceAuthenticationStatus()
/** Face acquired message. */
-data class AcquiredAuthenticationStatus(val acquiredInfo: Int) : AuthenticationStatus()
+data class AcquiredFaceAuthenticationStatus(val acquiredInfo: Int) : FaceAuthenticationStatus()
/** Face authentication failed message. */
-object FailedAuthenticationStatus : AuthenticationStatus()
+object FailedFaceAuthenticationStatus : FaceAuthenticationStatus()
/** Face authentication error message */
-data class ErrorAuthenticationStatus(
+data class ErrorFaceAuthenticationStatus(
val msgId: Int,
val msg: String? = null,
// present to break equality check if the same error occurs repeatedly.
val createdAt: Long = elapsedRealtime()
-) : AuthenticationStatus() {
+) : FaceAuthenticationStatus() {
/**
* Method that checks if [msgId] is a lockout error. A lockout error means that face
* authentication is locked out.
*/
- fun isLockoutError() = msgId == FaceManager.FACE_ERROR_LOCKOUT_PERMANENT
+ fun isLockoutError() =
+ msgId == FaceManager.FACE_ERROR_LOCKOUT_PERMANENT || msgId == FaceManager.FACE_ERROR_LOCKOUT
/**
* Method that checks if [msgId] is a cancellation error. This means that face authentication
@@ -64,4 +66,4 @@
}
/** Face detection success message. */
-data class DetectionStatus(val sensorId: Int, val userId: Int, val isStrongBiometric: Boolean)
+data class FaceDetectionStatus(val sensorId: Int, val userId: Int, val isStrongBiometric: Boolean)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FingerprintAuthenticationModels.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FingerprintAuthenticationModels.kt
new file mode 100644
index 0000000..5fb2cbf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FingerprintAuthenticationModels.kt
@@ -0,0 +1,52 @@
+/*
+ * 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.shared.model
+
+import android.os.SystemClock.elapsedRealtime
+
+/**
+ * Fingerprint authentication status provided by
+ * [com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository]
+ */
+sealed class FingerprintAuthenticationStatus
+
+/** Fingerprint authentication success status. */
+data class SuccessFingerprintAuthenticationStatus(
+ val userId: Int,
+ val isStrongBiometric: Boolean,
+) : FingerprintAuthenticationStatus()
+
+/** Fingerprint authentication help message. */
+data class HelpFingerprintAuthenticationStatus(
+ val msgId: Int,
+ val msg: String?,
+) : FingerprintAuthenticationStatus()
+
+/** Fingerprint acquired message. */
+data class AcquiredFingerprintAuthenticationStatus(val acquiredInfo: Int) :
+ FingerprintAuthenticationStatus()
+
+/** Fingerprint authentication failed message. */
+object FailedFingerprintAuthenticationStatus : FingerprintAuthenticationStatus()
+
+/** Fingerprint authentication error message */
+data class ErrorFingerprintAuthenticationStatus(
+ val msgId: Int,
+ val msg: String? = null,
+ // present to break equality check if the same error occurs repeatedly.
+ val createdAt: Long = elapsedRealtime(),
+) : FingerprintAuthenticationStatus()
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/ScreenModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/ScreenModel.kt
new file mode 100644
index 0000000..80a1b75
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/ScreenModel.kt
@@ -0,0 +1,29 @@
+/*
+ * 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.shared.model
+
+import com.android.systemui.keyguard.ScreenLifecycle
+
+/** Model device screen lifecycle states. */
+data class ScreenModel(
+ val state: ScreenState,
+) {
+ companion object {
+ fun fromScreenLifecycle(screenLifecycle: ScreenLifecycle): ScreenModel {
+ return ScreenModel(ScreenState.fromScreenLifecycleInt(screenLifecycle.getScreenState()))
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/ScreenState.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/ScreenState.kt
new file mode 100644
index 0000000..fe5d935
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/ScreenState.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.shared.model
+
+import com.android.systemui.keyguard.ScreenLifecycle
+
+enum class ScreenState {
+ /** Screen is fully off. */
+ SCREEN_OFF,
+ /** Signal that the screen is turning on. */
+ SCREEN_TURNING_ON,
+ /** Screen is fully on. */
+ SCREEN_ON,
+ /** Signal that the screen is turning off. */
+ SCREEN_TURNING_OFF;
+
+ companion object {
+ fun fromScreenLifecycleInt(value: Int): ScreenState {
+ return when (value) {
+ ScreenLifecycle.SCREEN_OFF -> SCREEN_OFF
+ ScreenLifecycle.SCREEN_TURNING_ON -> SCREEN_TURNING_ON
+ ScreenLifecycle.SCREEN_ON -> SCREEN_ON
+ ScreenLifecycle.SCREEN_TURNING_OFF -> SCREEN_TURNING_OFF
+ else -> throw IllegalArgumentException("Invalid screen value: $value")
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
index 7d14198..db84268 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
@@ -343,9 +343,9 @@
Utils.getColorAttrDefaultColor(
view.context,
if (viewModel.isActivated) {
- com.android.internal.R.attr.textColorPrimaryInverse
+ com.android.internal.R.attr.materialColorOnPrimaryFixed
} else {
- com.android.internal.R.attr.textColorPrimary
+ com.android.internal.R.attr.materialColorOnSurface
},
)
)
@@ -355,9 +355,9 @@
Utils.getColorAttr(
view.context,
if (viewModel.isActivated) {
- com.android.internal.R.attr.colorAccentPrimary
+ com.android.internal.R.attr.materialColorPrimaryFixed
} else {
- com.android.internal.R.attr.colorSurface
+ com.android.internal.R.attr.materialColorSurfaceContainerHigh
}
)
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsAodFingerprintViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsAodFingerprintViewBinder.kt
index 728dd39..9872d97 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsAodFingerprintViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsAodFingerprintViewBinder.kt
@@ -37,6 +37,7 @@
view: LottieAnimationView,
viewModel: UdfpsAodViewModel,
) {
+ view.alpha = 0f
view.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.STARTED) {
launch {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsBackgroundViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsBackgroundViewBinder.kt
index 26ef468..0113628 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsBackgroundViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsBackgroundViewBinder.kt
@@ -38,6 +38,7 @@
view: ImageView,
viewModel: BackgroundViewModel,
) {
+ view.alpha = 0f
view.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.STARTED) {
launch {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsFingerprintViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsFingerprintViewBinder.kt
index 0ab8e52..bab04f2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsFingerprintViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsFingerprintViewBinder.kt
@@ -42,6 +42,7 @@
view: LottieAnimationView,
viewModel: FingerprintViewModel,
) {
+ view.alpha = 0f
view.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.STARTED) {
launch {
diff --git a/services/core/java/com/android/server/biometrics/BiometricSensorPrivacy.java b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/UdfpsLottieViewWrapper.kt
similarity index 66%
copy from services/core/java/com/android/server/biometrics/BiometricSensorPrivacy.java
copy to packages/SystemUI/src/com/android/systemui/keyguard/ui/view/UdfpsLottieViewWrapper.kt
index 6727fbc..3a2c3c7 100644
--- a/services/core/java/com/android/server/biometrics/BiometricSensorPrivacy.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/UdfpsLottieViewWrapper.kt
@@ -13,13 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package com.android.systemui.keyguard.ui.view
-package com.android.server.biometrics;
+import android.content.Context
+import android.util.AttributeSet
+import com.android.systemui.util.wrapper.LottieViewWrapper
-/**
- * Interface for biometric operations to get camera privacy state.
- */
-public interface BiometricSensorPrivacy {
- /* Returns true if privacy is enabled and camera access is disabled. */
- boolean isCameraPrivacyEnabled();
-}
+class UdfpsLottieViewWrapper
+@JvmOverloads
+constructor(context: Context, attrs: AttributeSet? = null) : LottieViewWrapper(context, attrs)
diff --git a/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt b/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt
index 68cdfb6..373f705 100644
--- a/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt
@@ -4,7 +4,7 @@
import android.hardware.face.FaceSensorPropertiesInternal
import com.android.keyguard.FaceAuthUiEvent
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.shared.model.ErrorAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.ErrorFaceAuthenticationStatus
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.log.core.LogLevel.DEBUG
import com.android.systemui.log.dagger.FaceAuthLog
@@ -240,7 +240,7 @@
)
}
- fun hardwareError(errorStatus: ErrorAuthenticationStatus) {
+ fun hardwareError(errorStatus: ErrorFaceAuthenticationStatus) {
logBuffer.log(
TAG,
DEBUG,
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/RecommendationViewHolder.kt b/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/RecommendationViewHolder.kt
index 0b33904..258284e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/RecommendationViewHolder.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/RecommendationViewHolder.kt
@@ -31,15 +31,12 @@
private const val TAG = "RecommendationViewHolder"
/** ViewHolder for a Smartspace media recommendation. */
-class RecommendationViewHolder private constructor(itemView: View, updatedView: Boolean) {
+class RecommendationViewHolder private constructor(itemView: View) {
val recommendations = itemView as TransitionLayout
// Recommendation screen
- lateinit var cardIcon: ImageView
- lateinit var mediaAppIcons: List<CachingIconView>
- lateinit var mediaProgressBars: List<SeekBar>
- lateinit var cardTitle: TextView
+ val cardTitle: TextView = itemView.requireViewById(R.id.media_rec_title)
val mediaCoverContainers =
listOf<ViewGroup>(
@@ -47,53 +44,25 @@
itemView.requireViewById(R.id.media_cover2_container),
itemView.requireViewById(R.id.media_cover3_container)
)
+ val mediaAppIcons: List<CachingIconView> =
+ mediaCoverContainers.map { it.requireViewById(R.id.media_rec_app_icon) }
val mediaTitles: List<TextView> =
- if (updatedView) {
- mediaCoverContainers.map { it.requireViewById(R.id.media_title) }
- } else {
- listOf(
- itemView.requireViewById(R.id.media_title1),
- itemView.requireViewById(R.id.media_title2),
- itemView.requireViewById(R.id.media_title3)
- )
- }
+ mediaCoverContainers.map { it.requireViewById(R.id.media_title) }
val mediaSubtitles: List<TextView> =
- if (updatedView) {
- mediaCoverContainers.map { it.requireViewById(R.id.media_subtitle) }
- } else {
- listOf(
- itemView.requireViewById(R.id.media_subtitle1),
- itemView.requireViewById(R.id.media_subtitle2),
- itemView.requireViewById(R.id.media_subtitle3)
- )
+ mediaCoverContainers.map { it.requireViewById(R.id.media_subtitle) }
+ val mediaProgressBars: List<SeekBar> =
+ mediaCoverContainers.map {
+ it.requireViewById<SeekBar?>(R.id.media_progress_bar).apply {
+ // Media playback is in the direction of tape, not time, so it stays LTR
+ layoutDirection = View.LAYOUT_DIRECTION_LTR
+ }
}
val mediaCoverItems: List<ImageView> =
- if (updatedView) {
- mediaCoverContainers.map { it.requireViewById(R.id.media_cover) }
- } else {
- listOf(
- itemView.requireViewById(R.id.media_cover1),
- itemView.requireViewById(R.id.media_cover2),
- itemView.requireViewById(R.id.media_cover3)
- )
- }
+ mediaCoverContainers.map { it.requireViewById(R.id.media_cover) }
val gutsViewHolder = GutsViewHolder(itemView)
init {
- if (updatedView) {
- mediaAppIcons = mediaCoverContainers.map { it.requireViewById(R.id.media_rec_app_icon) }
- cardTitle = itemView.requireViewById(R.id.media_rec_title)
- mediaProgressBars =
- mediaCoverContainers.map {
- it.requireViewById<SeekBar?>(R.id.media_progress_bar).apply {
- // Media playback is in the direction of tape, not time, so it stays LTR
- layoutDirection = View.LAYOUT_DIRECTION_LTR
- }
- }
- } else {
- cardIcon = itemView.requireViewById<ImageView>(R.id.recommendation_card_icon)
- }
(recommendations.background as IlluminationDrawable).let { background ->
mediaCoverContainers.forEach { background.registerLightSource(it) }
background.registerLightSource(gutsViewHolder.cancel)
@@ -114,63 +83,31 @@
* @param parent Parent of inflated view.
*/
@JvmStatic
- fun create(
- inflater: LayoutInflater,
- parent: ViewGroup,
- updatedView: Boolean,
- ): RecommendationViewHolder {
+ fun create(inflater: LayoutInflater, parent: ViewGroup): RecommendationViewHolder {
val itemView =
- if (updatedView) {
- inflater.inflate(
- R.layout.media_recommendations,
- parent,
- false /* attachToRoot */
- )
- } else {
- inflater.inflate(
- R.layout.media_smartspace_recommendations,
- parent,
- false /* attachToRoot */
- )
- }
+ inflater.inflate(R.layout.media_recommendations, parent, false /* attachToRoot */)
// Because this media view (a TransitionLayout) is used to measure and layout the views
// in various states before being attached to its parent, we can't depend on the default
// LAYOUT_DIRECTION_INHERIT to correctly resolve the ltr direction.
itemView.layoutDirection = View.LAYOUT_DIRECTION_LOCALE
- return RecommendationViewHolder(itemView, updatedView)
+ return RecommendationViewHolder(itemView)
}
// Res Ids for the control components on the recommendation view.
val controlsIds =
setOf(
- R.id.recommendation_card_icon,
R.id.media_rec_title,
- R.id.media_cover1,
- R.id.media_cover2,
- R.id.media_cover3,
R.id.media_cover,
R.id.media_cover1_container,
R.id.media_cover2_container,
R.id.media_cover3_container,
- R.id.media_title1,
- R.id.media_title2,
- R.id.media_title3,
R.id.media_title,
- R.id.media_subtitle1,
- R.id.media_subtitle2,
- R.id.media_subtitle3,
R.id.media_subtitle,
)
val mediaTitlesAndSubtitlesIds =
setOf(
- R.id.media_title1,
- R.id.media_title2,
- R.id.media_title3,
R.id.media_title,
- R.id.media_subtitle1,
- R.id.media_subtitle2,
- R.id.media_subtitle3,
R.id.media_subtitle,
)
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
index 70b5e75..398dcf2 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
@@ -744,11 +744,7 @@
val newRecs = mediaControlPanelFactory.get()
newRecs.attachRecommendation(
- RecommendationViewHolder.create(
- LayoutInflater.from(context),
- mediaContent,
- mediaFlags.isRecommendationCardUpdateEnabled()
- )
+ RecommendationViewHolder.create(LayoutInflater.from(context), mediaContent)
)
newRecs.mediaViewController.sizeChangedListener = this::updateCarouselDimensions
val lp =
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
index a978b92..a12bc2c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
@@ -784,14 +784,7 @@
contentDescription =
mRecommendationViewHolder.getGutsViewHolder().getGutsText().getText();
} else if (data != null) {
- if (mFeatureFlags.isEnabled(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE)) {
- contentDescription = mContext.getString(
- R.string.controls_media_smartspace_rec_header);
- } else {
- contentDescription = mContext.getString(
- R.string.controls_media_smartspace_rec_description,
- data.getAppName(mContext));
- }
+ contentDescription = mContext.getString(R.string.controls_media_smartspace_rec_header);
} else {
contentDescription = null;
}
@@ -1377,10 +1370,6 @@
PackageManager packageManager = mContext.getPackageManager();
// Set up media source app's logo.
Drawable icon = packageManager.getApplicationIcon(applicationInfo);
- if (!mFeatureFlags.isEnabled(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE)) {
- ImageView headerLogoImageView = mRecommendationViewHolder.getCardIcon();
- headerLogoImageView.setImageDrawable(icon);
- }
fetchAndUpdateRecommendationColors(icon);
// Set up media rec card's tap action if applicable.
@@ -1401,16 +1390,7 @@
// Set up media item cover.
ImageView mediaCoverImageView = mediaCoverItems.get(itemIndex);
- if (mFeatureFlags.isEnabled(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE)) {
- bindRecommendationArtwork(
- recommendation,
- data.getPackageName(),
- itemIndex
- );
- } else {
- mediaCoverImageView.post(
- () -> mediaCoverImageView.setImageIcon(recommendation.getIcon()));
- }
+ bindRecommendationArtwork(recommendation, data.getPackageName(), itemIndex);
// Set up the media item's click listener if applicable.
ViewGroup mediaCoverContainer = mediaCoverContainers.get(itemIndex);
@@ -1455,21 +1435,18 @@
subtitleView.setText(subtitle);
// Set up progress bar
- if (mFeatureFlags.isEnabled(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE)) {
- SeekBar mediaProgressBar =
- mRecommendationViewHolder.getMediaProgressBars().get(itemIndex);
- TextView mediaSubtitle =
- mRecommendationViewHolder.getMediaSubtitles().get(itemIndex);
- // show progress bar if the recommended album is played.
- Double progress = MediaDataUtils.getDescriptionProgress(recommendation.getExtras());
- if (progress == null || progress <= 0.0) {
- mediaProgressBar.setVisibility(View.GONE);
- mediaSubtitle.setVisibility(View.VISIBLE);
- } else {
- mediaProgressBar.setProgress((int) (progress * 100));
- mediaProgressBar.setVisibility(View.VISIBLE);
- mediaSubtitle.setVisibility(View.GONE);
- }
+ SeekBar mediaProgressBar =
+ mRecommendationViewHolder.getMediaProgressBars().get(itemIndex);
+ TextView mediaSubtitle = mRecommendationViewHolder.getMediaSubtitles().get(itemIndex);
+ // show progress bar if the recommended album is played.
+ Double progress = MediaDataUtils.getDescriptionProgress(recommendation.getExtras());
+ if (progress == null || progress <= 0.0) {
+ mediaProgressBar.setVisibility(View.GONE);
+ mediaSubtitle.setVisibility(View.VISIBLE);
+ } else {
+ mediaProgressBar.setProgress((int) (progress * 100));
+ mediaProgressBar.setVisibility(View.VISIBLE);
+ mediaSubtitle.setVisibility(View.GONE);
}
}
mSmartspaceMediaItemsCount = NUM_REQUIRED_RECOMMENDATIONS;
@@ -1588,9 +1565,7 @@
int textPrimaryColor = MediaColorSchemesKt.textPrimaryFromScheme(colorScheme);
int textSecondaryColor = MediaColorSchemesKt.textSecondaryFromScheme(colorScheme);
- if (mFeatureFlags.isEnabled(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE)) {
- mRecommendationViewHolder.getCardTitle().setTextColor(textPrimaryColor);
- }
+ mRecommendationViewHolder.getCardTitle().setTextColor(textPrimaryColor);
mRecommendationViewHolder.getRecommendations()
.setBackgroundTintList(ColorStateList.valueOf(backgroundColor));
@@ -1598,12 +1573,9 @@
(title) -> title.setTextColor(textPrimaryColor));
mRecommendationViewHolder.getMediaSubtitles().forEach(
(subtitle) -> subtitle.setTextColor(textSecondaryColor));
- if (mFeatureFlags.isEnabled(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE)) {
- mRecommendationViewHolder.getMediaProgressBars().forEach(
- (progressBar) -> progressBar.setProgressTintList(
- ColorStateList.valueOf(textPrimaryColor))
- );
- }
+ mRecommendationViewHolder.getMediaProgressBars().forEach(
+ (progressBar) -> progressBar.setProgressTintList(
+ ColorStateList.valueOf(textPrimaryColor)));
mRecommendationViewHolder.getGutsViewHolder().setColors(colorScheme);
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
index 4bca778..1dd969f 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
@@ -655,13 +655,8 @@
expandedLayout.load(context, R.xml.media_session_expanded)
}
TYPE.RECOMMENDATION -> {
- if (mediaFlags.isRecommendationCardUpdateEnabled()) {
- collapsedLayout.load(context, R.xml.media_recommendations_view_collapsed)
- expandedLayout.load(context, R.xml.media_recommendations_view_expanded)
- } else {
- collapsedLayout.load(context, R.xml.media_recommendation_collapsed)
- expandedLayout.load(context, R.xml.media_recommendation_expanded)
- }
+ collapsedLayout.load(context, R.xml.media_recommendations_collapsed)
+ expandedLayout.load(context, R.xml.media_recommendations_expanded)
}
}
refreshState()
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
index 01f047c..09aef88 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
@@ -49,10 +49,6 @@
*/
fun isRetainingPlayersEnabled() = featureFlags.isEnabled(Flags.MEDIA_RETAIN_SESSIONS)
- /** Check whether we show the updated recommendation card. */
- fun isRecommendationCardUpdateEnabled() =
- featureFlags.isEnabled(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE)
-
/** Check whether to get progress information for resume players */
fun isResumeProgressEnabled() = featureFlags.isEnabled(Flags.MEDIA_RESUME_PROGRESS)
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinator.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinator.kt
index a4f4076..a437139 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinator.kt
@@ -16,15 +16,19 @@
package com.android.systemui.mediaprojection.taskswitcher.ui
+import android.app.Notification
+import android.app.NotificationChannel
+import android.app.NotificationManager
import android.content.Context
import android.util.Log
-import android.widget.Toast
+import com.android.systemui.R
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.mediaprojection.taskswitcher.ui.model.TaskSwitcherNotificationUiState.NotShowing
import com.android.systemui.mediaprojection.taskswitcher.ui.model.TaskSwitcherNotificationUiState.Showing
import com.android.systemui.mediaprojection.taskswitcher.ui.viewmodel.TaskSwitcherNotificationViewModel
+import com.android.systemui.util.NotificationChannels
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
@@ -37,38 +41,69 @@
@Inject
constructor(
private val context: Context,
+ private val notificationManager: NotificationManager,
@Application private val applicationScope: CoroutineScope,
@Main private val mainDispatcher: CoroutineDispatcher,
private val viewModel: TaskSwitcherNotificationViewModel,
) {
-
fun start() {
applicationScope.launch {
viewModel.uiState.flowOn(mainDispatcher).collect { uiState ->
Log.d(TAG, "uiState -> $uiState")
when (uiState) {
- is Showing -> showNotification(uiState)
+ is Showing -> showNotification()
is NotShowing -> hideNotification()
}
}
}
}
- private fun showNotification(uiState: Showing) {
- val text =
- """
- Sharing pauses when you switch apps.
- Share this app instead.
- Switch back.
- """
- .trimIndent()
- // TODO(b/286201515): Create actual notification.
- Toast.makeText(context, text, Toast.LENGTH_SHORT).show()
+ private fun showNotification() {
+ notificationManager.notify(TAG, NOTIFICATION_ID, createNotification())
}
- private fun hideNotification() {}
+ private fun createNotification(): Notification {
+ // TODO(b/286201261): implement actions
+ val actionSwitch =
+ Notification.Action.Builder(
+ /* icon = */ null,
+ context.getString(R.string.media_projection_task_switcher_action_switch),
+ /* intent = */ null
+ )
+ .build()
+
+ val actionBack =
+ Notification.Action.Builder(
+ /* icon = */ null,
+ context.getString(R.string.media_projection_task_switcher_action_back),
+ /* intent = */ null
+ )
+ .build()
+
+ val channel =
+ NotificationChannel(
+ NotificationChannels.HINTS,
+ context.getString(R.string.media_projection_task_switcher_notification_channel),
+ NotificationManager.IMPORTANCE_HIGH
+ )
+ notificationManager.createNotificationChannel(channel)
+ return Notification.Builder(context, channel.id)
+ .setSmallIcon(R.drawable.qs_screen_record_icon_on)
+ .setAutoCancel(true)
+ .setContentText(context.getString(R.string.media_projection_task_switcher_text))
+ .addAction(actionSwitch)
+ .addAction(actionBack)
+ .setPriority(Notification.PRIORITY_HIGH)
+ .setDefaults(Notification.DEFAULT_VIBRATE)
+ .build()
+ }
+
+ private fun hideNotification() {
+ notificationManager.cancel(NOTIFICATION_ID)
+ }
companion object {
private const val TAG = "TaskSwitchNotifCoord"
+ private const val NOTIFICATION_ID = 5566
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index a8af67a..b21b001 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -88,11 +88,7 @@
import com.android.systemui.shared.system.SysUiStatsLog;
import com.android.systemui.shared.system.TaskStackChangeListener;
import com.android.systemui.shared.system.TaskStackChangeListeners;
-import com.android.systemui.shared.tracing.ProtoTraceable;
import com.android.systemui.statusbar.phone.LightBarController;
-import com.android.systemui.tracing.ProtoTracer;
-import com.android.systemui.tracing.nano.EdgeBackGestureHandlerProto;
-import com.android.systemui.tracing.nano.SystemUiTraceProto;
import com.android.systemui.util.Assert;
import com.android.wm.shell.back.BackAnimation;
import com.android.wm.shell.desktopmode.DesktopMode;
@@ -115,8 +111,7 @@
/**
* Utility class to handle edge swipes for back gesture
*/
-public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBackPlugin>,
- ProtoTraceable<SystemUiTraceProto> {
+public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBackPlugin> {
private static final String TAG = "EdgeBackGestureHandler";
private static final int MAX_LONG_PRESS_TIMEOUT = SystemProperties.getInt(
@@ -192,7 +187,6 @@
private Consumer<Boolean> mButtonForcedVisibleCallback;
private final PluginManager mPluginManager;
- private final ProtoTracer mProtoTracer;
private final NavigationModeController mNavigationModeController;
private final BackPanelController.Factory mBackPanelControllerFactory;
private final ViewConfiguration mViewConfiguration;
@@ -402,7 +396,6 @@
@Main Handler handler,
@Background Executor backgroundExecutor,
UserTracker userTracker,
- ProtoTracer protoTracer,
NavigationModeController navigationModeController,
BackPanelController.Factory backPanelControllerFactory,
ViewConfiguration viewConfiguration,
@@ -425,7 +418,6 @@
mOverviewProxyService = overviewProxyService;
mSysUiState = sysUiState;
mPluginManager = pluginManager;
- mProtoTracer = protoTracer;
mNavigationModeController = navigationModeController;
mBackPanelControllerFactory = backPanelControllerFactory;
mViewConfiguration = viewConfiguration;
@@ -557,7 +549,6 @@
*/
public void onNavBarAttached() {
mIsAttached = true;
- mProtoTracer.add(this);
mOverviewProxyService.addCallback(mQuickSwitchListener);
mSysUiState.addCallback(mSysUiStateCallback);
if (mIsTrackpadGestureFeaturesEnabled) {
@@ -576,7 +567,6 @@
*/
public void onNavBarDetached() {
mIsAttached = false;
- mProtoTracer.remove(this);
mOverviewProxyService.removeCallback(mQuickSwitchListener);
mSysUiState.removeCallback(mSysUiStateCallback);
mInputManager.unregisterInputDeviceListener(mInputDeviceListener);
@@ -1135,8 +1125,6 @@
dispatchToBackAnimation(ev);
}
}
-
- mProtoTracer.scheduleFrameUpdate();
}
private boolean isButtonPressFromTrackpad(MotionEvent ev) {
@@ -1285,14 +1273,6 @@
return topActivity != null && mGestureBlockingActivities.contains(topActivity);
}
- @Override
- public void writeToProto(SystemUiTraceProto proto) {
- if (proto.edgeBackGestureHandler == null) {
- proto.edgeBackGestureHandler = new EdgeBackGestureHandlerProto();
- }
- proto.edgeBackGestureHandler.allowGesture = mAllowGesture;
- }
-
public void setBackAnimation(BackAnimation backAnimation) {
mBackAnimation = backAnimation;
updateBackAnimationThresholds();
@@ -1319,7 +1299,6 @@
private final Handler mHandler;
private final Executor mBackgroundExecutor;
private final UserTracker mUserTracker;
- private final ProtoTracer mProtoTracer;
private final NavigationModeController mNavigationModeController;
private final BackPanelController.Factory mBackPanelControllerFactory;
private final ViewConfiguration mViewConfiguration;
@@ -1343,7 +1322,6 @@
@Main Handler handler,
@Background Executor backgroundExecutor,
UserTracker userTracker,
- ProtoTracer protoTracer,
NavigationModeController navigationModeController,
BackPanelController.Factory backPanelControllerFactory,
ViewConfiguration viewConfiguration,
@@ -1365,7 +1343,6 @@
mHandler = handler;
mBackgroundExecutor = backgroundExecutor;
mUserTracker = userTracker;
- mProtoTracer = protoTracer;
mNavigationModeController = navigationModeController;
mBackPanelControllerFactory = backPanelControllerFactory;
mViewConfiguration = viewConfiguration;
@@ -1392,7 +1369,6 @@
mHandler,
mBackgroundExecutor,
mUserTracker,
- mProtoTracer,
mNavigationModeController,
mBackPanelControllerFactory,
mViewConfiguration,
diff --git a/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt b/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt
index c13476f..809edc0 100644
--- a/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt
@@ -20,6 +20,7 @@
import android.os.PowerManager
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.power.data.repository.PowerRepository
import com.android.systemui.statusbar.phone.ScreenOffAnimationController
@@ -32,6 +33,7 @@
@Inject
constructor(
private val repository: PowerRepository,
+ private val keyguardRepository: KeyguardRepository,
private val falsingCollector: FalsingCollector,
private val screenOffAnimationController: ScreenOffAnimationController,
private val statusBarStateController: StatusBarStateController,
@@ -54,4 +56,34 @@
falsingCollector.onScreenOnFromTouch()
}
}
+
+ /**
+ * Wakes up the device if the device was dozing or going to sleep in order to display a
+ * full-screen intent.
+ */
+ fun wakeUpForFullScreenIntent() {
+ if (
+ keyguardRepository.wakefulness.value.isStartingToSleep() ||
+ statusBarStateController.isDozing
+ ) {
+ repository.wakeUp(why = FSI_WAKE_WHY, wakeReason = PowerManager.WAKE_REASON_APPLICATION)
+ }
+ }
+
+ /**
+ * Wakes up the device if dreaming with a screensaver.
+ *
+ * @param why a string explaining why we're waking the device for debugging purposes. Should be
+ * in SCREAMING_SNAKE_CASE.
+ * @param wakeReason the PowerManager-based reason why we're waking the device.
+ */
+ fun wakeUpIfDreaming(why: String, @PowerManager.WakeReason wakeReason: Int) {
+ if (statusBarStateController.isDreaming) {
+ repository.wakeUp(why, wakeReason)
+ }
+ }
+
+ companion object {
+ private const val FSI_WAKE_WHY = "full_screen_intent"
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt b/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt
index c3b5db4..310d234 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt
@@ -15,6 +15,8 @@
package com.android.systemui.privacy
import android.content.Context
+import android.content.pm.ActivityInfo
+import android.content.res.Configuration
import android.util.AttributeSet
import android.view.Gravity.CENTER_VERTICAL
import android.view.Gravity.END
@@ -35,6 +37,7 @@
defStyleRes: Int = 0
) : LaunchableFrameLayout(context, attrs, defStyleAttrs, defStyleRes), BackgroundAnimatableView {
+ private var configuration: Configuration
private var iconMargin = 0
private var iconSize = 0
private var iconColor = 0
@@ -54,6 +57,7 @@
clipChildren = true
clipToPadding = true
iconsContainer = requireViewById(R.id.icons_container)
+ configuration = Configuration(context.resources.configuration)
updateResources()
}
@@ -102,6 +106,17 @@
R.string.ongoing_privacy_chip_content_multiple_apps, typesText)
}
+ override fun onConfigurationChanged(newConfig: Configuration?) {
+ super.onConfigurationChanged(newConfig)
+ if (newConfig != null) {
+ val diff = newConfig.diff(configuration)
+ configuration.setTo(newConfig)
+ if (diff.and(ActivityInfo.CONFIG_DENSITY.or(ActivityInfo.CONFIG_FONT_SCALE)) != 0) {
+ updateResources()
+ }
+ }
+ }
+
private fun updateResources() {
iconMargin = context.resources
.getDimensionPixelSize(R.dimen.ongoing_appops_chip_icon_margin)
@@ -110,8 +125,11 @@
iconColor =
Utils.getColorAttrDefaultColor(context, com.android.internal.R.attr.colorPrimary)
+ val height = context.resources
+ .getDimensionPixelSize(R.dimen.ongoing_appops_chip_height)
val padding = context.resources
.getDimensionPixelSize(R.dimen.ongoing_appops_chip_side_padding)
+ iconsContainer.layoutParams.height = height
iconsContainer.setPaddingRelative(padding, 0, padding, 0)
iconsContainer.background = context.getDrawable(R.drawable.statusbar_privacy_chip_bg)
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
index 83b373d..856a92e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
@@ -58,6 +58,7 @@
private final BrightnessSliderController mBrightnessSliderController;
private final BrightnessMirrorHandler mBrightnessMirrorHandler;
private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+ private boolean mListening;
private View.OnTouchListener mTileLayoutTouchListener = new View.OnTouchListener() {
@Override
@@ -159,12 +160,15 @@
public void setListening(boolean listening, boolean expanded) {
setListening(listening && expanded);
- // Set the listening as soon as the QS fragment starts listening regardless of the
- //expansion, so it will update the current brightness before the slider is visible.
- if (listening) {
- mBrightnessController.registerCallbacks();
- } else {
- mBrightnessController.unregisterCallbacks();
+ if (listening != mListening) {
+ mListening = listening;
+ // Set the listening as soon as the QS fragment starts listening regardless of the
+ //expansion, so it will update the current brightness before the slider is visible.
+ if (listening) {
+ mBrightnessController.registerCallbacks();
+ } else {
+ mBrightnessController.unregisterCallbacks();
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
index d81e4c2..764ef68 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
@@ -116,6 +116,10 @@
private lateinit var customDrawableView: ImageView
private lateinit var chevronView: ImageView
private var mQsLogger: QSLogger? = null
+
+ /**
+ * Controls if tile background is set to a [RippleDrawable] see [setClickable]
+ */
protected var showRippleEffect = true
private lateinit var ripple: RippleDrawable
@@ -440,7 +444,6 @@
protected open fun handleStateChanged(state: QSTile.State) {
val allowAnimations = animationsEnabled()
- showRippleEffect = state.showRippleEffect
isClickable = state.state != Tile.STATE_UNAVAILABLE
isLongClickable = state.handlesLongClick
icon.setIcon(state, allowAnimations)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java
index a444e76..9f10e6f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java
@@ -155,7 +155,6 @@
state.contentDescription = state.label;
state.value = mPowerSave;
state.expandedAccessibilityClassName = Switch.class.getName();
- state.showRippleEffect = mSetting.getValue() == 0;
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java
index a60d1ad..9ffcba6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java
@@ -169,7 +169,6 @@
state.icon = ResourceIcon.get(state.state == Tile.STATE_ACTIVE
? R.drawable.qs_light_dark_theme_icon_on
: R.drawable.qs_light_dark_theme_icon_off);
- state.showRippleEffect = false;
state.expandedAccessibilityClassName = Switch.class.getName();
}
diff --git a/services/core/java/com/android/server/biometrics/BiometricSensorPrivacy.java b/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayEducationLottieViewWrapper.kt
similarity index 65%
copy from services/core/java/com/android/server/biometrics/BiometricSensorPrivacy.java
copy to packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayEducationLottieViewWrapper.kt
index 6727fbc..716a4d6 100644
--- a/services/core/java/com/android/server/biometrics/BiometricSensorPrivacy.java
+++ b/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayEducationLottieViewWrapper.kt
@@ -13,13 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package com.android.systemui.reardisplay
-package com.android.server.biometrics;
+import android.content.Context
+import android.util.AttributeSet
+import com.android.systemui.util.wrapper.LottieViewWrapper
-/**
- * Interface for biometric operations to get camera privacy state.
- */
-public interface BiometricSensorPrivacy {
- /* Returns true if privacy is enabled and camera access is disabled. */
- boolean isCameraPrivacyEnabled();
-}
+class RearDisplayEducationLottieViewWrapper
+@JvmOverloads
+constructor(context: Context, attrs: AttributeSet? = null) : LottieViewWrapper(context, attrs)
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index e7dde66..207cc139 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -25,11 +25,9 @@
import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON;
import static com.android.internal.accessibility.common.ShortcutConstants.CHOOSER_PACKAGE_NAME;
-import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SUPPORTS_WINDOW_CORNERS;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SYSUI_PROXY;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_UNFOLD_ANIMATION_FORWARDER;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_UNLOCK_ANIMATION_CONTROLLER;
-import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_WINDOW_CORNER_RADIUS;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_AWAKE;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BOUNCER_SHOWING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DEVICE_DOZING;
@@ -173,8 +171,6 @@
private boolean mInputFocusTransferStarted;
private float mInputFocusTransferStartY;
private long mInputFocusTransferStartMillis;
- private float mWindowCornerRadius;
- private boolean mSupportsRoundedCornersOnWindows;
private int mNavBarMode = NAV_BAR_MODE_3BUTTON;
@VisibleForTesting
@@ -454,8 +450,6 @@
Bundle params = new Bundle();
params.putBinder(KEY_EXTRA_SYSUI_PROXY, mSysUiProxy.asBinder());
- params.putFloat(KEY_EXTRA_WINDOW_CORNER_RADIUS, mWindowCornerRadius);
- params.putBoolean(KEY_EXTRA_SUPPORTS_WINDOW_CORNERS, mSupportsRoundedCornersOnWindows);
params.putBinder(KEY_EXTRA_UNLOCK_ANIMATION_CONTROLLER,
mSysuiUnlockAnimationController.asBinder());
mUnfoldTransitionProgressForwarder.ifPresent(
@@ -588,9 +582,6 @@
com.android.internal.R.string.config_recentsComponentName));
mQuickStepIntent = new Intent(ACTION_QUICKSTEP)
.setPackage(mRecentsComponentName.getPackageName());
- mWindowCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(mContext);
- mSupportsRoundedCornersOnWindows = ScreenDecorationsUtils
- .supportsRoundedCornersOnWindows(mContext.getResources());
mSysUiState = sysUiState;
mSysUiState.addCallback(this::notifySystemUiStateFlags);
mUiEventLogger = uiEventLogger;
@@ -1084,8 +1075,6 @@
pw.print(" mInputFocusTransferStarted="); pw.println(mInputFocusTransferStarted);
pw.print(" mInputFocusTransferStartY="); pw.println(mInputFocusTransferStartY);
pw.print(" mInputFocusTransferStartMillis="); pw.println(mInputFocusTransferStartMillis);
- pw.print(" mWindowCornerRadius="); pw.println(mWindowCornerRadius);
- pw.print(" mSupportsRoundedCornersOnWindows="); pw.println(mSupportsRoundedCornersOnWindows);
pw.print(" mActiveNavBarRegion="); pw.println(mActiveNavBarRegion);
pw.print(" mNavigationBarSurface="); pw.println(mNavigationBarSurface);
pw.print(" mNavBarMode="); pw.println(mNavBarMode);
diff --git a/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt b/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt
index 0a9839e..26c5219 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt
@@ -16,6 +16,7 @@
package com.android.systemui.scene
+import com.android.systemui.scene.domain.startable.SceneContainerStartableModule
import com.android.systemui.scene.shared.model.SceneContainerConfigModule
import com.android.systemui.scene.ui.composable.SceneModule
import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModelModule
@@ -25,6 +26,7 @@
includes =
[
SceneContainerConfigModule::class,
+ SceneContainerStartableModule::class,
SceneContainerViewModelModule::class,
SceneModule::class,
],
diff --git a/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt b/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt
index 1ebeced..0a86d35 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt
@@ -19,6 +19,7 @@
import com.android.systemui.scene.shared.model.SceneContainerConfig
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.scene.shared.model.SceneTransitionModel
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@@ -45,6 +46,9 @@
containerConfigByName
.map { (containerName, _) -> containerName to MutableStateFlow(1f) }
.toMap()
+ private val sceneTransitionByContainerName:
+ Map<String, MutableStateFlow<SceneTransitionModel?>> =
+ containerConfigByName.keys.associateWith { MutableStateFlow(null) }
/**
* Returns the keys to all scenes in the container with the given name.
@@ -70,11 +74,43 @@
currentSceneByContainerName.setValue(containerName, scene)
}
+ /** Sets the scene transition in the container with the given name. */
+ fun setSceneTransition(containerName: String, from: SceneKey, to: SceneKey) {
+ check(allSceneKeys(containerName).contains(from)) {
+ """
+ Cannot set current scene key to "$from". The container "$containerName" does
+ not contain a scene with that key.
+ """
+ .trimIndent()
+ }
+ check(allSceneKeys(containerName).contains(to)) {
+ """
+ Cannot set current scene key to "$to". The container "$containerName" does
+ not contain a scene with that key.
+ """
+ .trimIndent()
+ }
+
+ sceneTransitionByContainerName.setValue(
+ containerName,
+ SceneTransitionModel(from = from, to = to)
+ )
+ }
+
/** The current scene in the container with the given name. */
fun currentScene(containerName: String): StateFlow<SceneModel> {
return currentSceneByContainerName.mutableOrError(containerName).asStateFlow()
}
+ /**
+ * Scene transitions as pairs of keys. A new value is emitted exactly once, each time a scene
+ * transition occurs. The flow begins with a `null` value at first, because the initial scene is
+ * not something that we transition to from another scene.
+ */
+ fun sceneTransitions(containerName: String): StateFlow<SceneTransitionModel?> {
+ return sceneTransitionByContainerName.mutableOrError(containerName).asStateFlow()
+ }
+
/** Sets whether the container with the given name is visible. */
fun setVisible(containerName: String, isVisible: Boolean) {
containerVisibilityByName.setValue(containerName, isVisible)
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 1e55975..4582370 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
@@ -20,10 +20,21 @@
import com.android.systemui.scene.data.repository.SceneContainerRepository
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.scene.shared.model.SceneTransitionModel
import javax.inject.Inject
import kotlinx.coroutines.flow.StateFlow
-/** Business logic and app state accessors for the scene framework. */
+/**
+ * Generic business logic and app state accessors for the scene framework.
+ *
+ * Note that scene container specific business logic does not belong in this class. Instead, it
+ * should be hoisted to a class that is specific to that scene container, for an example, please see
+ * [SystemUiDefaultSceneContainerStartable].
+ *
+ * Also note that this class should not depend on state or logic of other modules or features.
+ * Instead, other feature modules should depend on and call into this class when their parts of the
+ * application state change.
+ */
@SysUISingleton
class SceneInteractor
@Inject
@@ -43,7 +54,9 @@
/** Sets the scene in the container with the given name. */
fun setCurrentScene(containerName: String, scene: SceneModel) {
+ val currentSceneKey = repository.currentScene(containerName).value.key
repository.setCurrentScene(containerName, scene)
+ repository.setSceneTransition(containerName, from = currentSceneKey, to = scene.key)
}
/** The current scene in the container with the given name. */
@@ -70,4 +83,13 @@
fun sceneTransitionProgress(containerName: String): StateFlow<Float> {
return repository.sceneTransitionProgress(containerName)
}
+
+ /**
+ * Scene transitions as pairs of keys. A new value is emitted exactly once, each time a scene
+ * transition occurs. The flow begins with a `null` value at first, because the initial scene is
+ * not something that we transition to from another scene.
+ */
+ fun sceneTransitions(containerName: String): StateFlow<SceneTransitionModel?> {
+ return repository.sceneTransitions(containerName)
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartableModule.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartableModule.kt
new file mode 100644
index 0000000..b3de2d1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartableModule.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright 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.scene.domain.startable
+
+import com.android.systemui.CoreStartable
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+
+@Module
+interface SceneContainerStartableModule {
+
+ @Binds
+ @IntoMap
+ @ClassKey(SystemUiDefaultSceneContainerStartable::class)
+ fun bind(impl: SystemUiDefaultSceneContainerStartable): CoreStartable
+}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SystemUiDefaultSceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SystemUiDefaultSceneContainerStartable.kt
new file mode 100644
index 0000000..b23c4ec
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SystemUiDefaultSceneContainerStartable.kt
@@ -0,0 +1,134 @@
+/*
+ * Copyright 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.scene.domain.startable
+
+import com.android.systemui.CoreStartable
+import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.shared.model.WakefulnessState
+import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.model.SceneContainerNames
+import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.SceneModel
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.launch
+
+/**
+ * Hooks up business logic that manipulates the state of the [SceneInteractor] for the default
+ * system UI scene container (the one named [SceneContainerNames.SYSTEM_UI_DEFAULT]) based on state
+ * from other systems.
+ */
+@SysUISingleton
+class SystemUiDefaultSceneContainerStartable
+@Inject
+constructor(
+ @Application private val applicationScope: CoroutineScope,
+ private val sceneInteractor: SceneInteractor,
+ private val authenticationInteractor: AuthenticationInteractor,
+ private val keyguardInteractor: KeyguardInteractor,
+ private val featureFlags: FeatureFlags,
+) : CoreStartable {
+
+ override fun start() {
+ if (featureFlags.isEnabled(Flags.SCENE_CONTAINER)) {
+ hydrateVisibility()
+ automaticallySwitchScenes()
+ }
+ }
+
+ /** Updates the visibility of the scene container based on the current scene. */
+ private fun hydrateVisibility() {
+ applicationScope.launch {
+ sceneInteractor
+ .currentScene(CONTAINER_NAME)
+ .map { it.key }
+ .distinctUntilChanged()
+ .collect { sceneKey ->
+ sceneInteractor.setVisible(CONTAINER_NAME, sceneKey != SceneKey.Gone)
+ }
+ }
+ }
+
+ /** Switches between scenes based on ever-changing application state. */
+ private fun automaticallySwitchScenes() {
+ applicationScope.launch {
+ authenticationInteractor.isUnlocked
+ .map { isUnlocked ->
+ val currentSceneKey = sceneInteractor.currentScene(CONTAINER_NAME).value.key
+ val isBypassEnabled = authenticationInteractor.isBypassEnabled()
+ when {
+ isUnlocked ->
+ when (currentSceneKey) {
+ // When the device becomes unlocked in Bouncer, go to Gone.
+ is SceneKey.Bouncer -> SceneKey.Gone
+ // When the device becomes unlocked in Lockscreen, go to Gone if
+ // bypass is enabled.
+ is SceneKey.Lockscreen -> SceneKey.Gone.takeIf { isBypassEnabled }
+ // We got unlocked while on a scene that's not Lockscreen or
+ // Bouncer, no need to change scenes.
+ else -> null
+ }
+ // When the device becomes locked, to Lockscreen.
+ !isUnlocked ->
+ when (currentSceneKey) {
+ // Already on lockscreen or bouncer, no need to change scenes.
+ is SceneKey.Lockscreen,
+ is SceneKey.Bouncer -> null
+ // We got locked while on a scene that's not Lockscreen or Bouncer,
+ // go to Lockscreen.
+ else -> SceneKey.Lockscreen
+ }
+ else -> null
+ }
+ }
+ .filterNotNull()
+ .collect { targetSceneKey -> switchToScene(targetSceneKey) }
+ }
+
+ applicationScope.launch {
+ keyguardInteractor.wakefulnessModel
+ .map { it.state == WakefulnessState.ASLEEP }
+ .distinctUntilChanged()
+ .collect { isAsleep ->
+ if (isAsleep) {
+ // When the device goes to sleep, reset the current scene.
+ val isUnlocked = authenticationInteractor.isUnlocked.value
+ switchToScene(if (isUnlocked) SceneKey.Gone else SceneKey.Lockscreen)
+ }
+ }
+ }
+ }
+
+ private fun switchToScene(targetSceneKey: SceneKey) {
+ sceneInteractor.setCurrentScene(
+ containerName = CONTAINER_NAME,
+ scene = SceneModel(targetSceneKey),
+ )
+ }
+
+ companion object {
+ private const val CONTAINER_NAME = SceneContainerNames.SYSTEM_UI_DEFAULT
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/BiometricSensorPrivacy.java b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneTransitionModel.kt
similarity index 62%
copy from services/core/java/com/android/server/biometrics/BiometricSensorPrivacy.java
copy to packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneTransitionModel.kt
index 6727fbc..c8f46a7 100644
--- a/services/core/java/com/android/server/biometrics/BiometricSensorPrivacy.java
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneTransitionModel.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright 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.
@@ -14,12 +14,12 @@
* limitations under the License.
*/
-package com.android.server.biometrics;
+package com.android.systemui.scene.shared.model
-/**
- * Interface for biometric operations to get camera privacy state.
- */
-public interface BiometricSensorPrivacy {
- /* Returns true if privacy is enabled and camera access is disabled. */
- boolean isCameraPrivacyEnabled();
-}
+/** Models a transition between two scenes. */
+data class SceneTransitionModel(
+ /** The scene we transitioned away from. */
+ val from: SceneKey,
+ /** The scene we transitioned into. */
+ val to: SceneKey,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt
index 2ad5429..c456be6 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt
@@ -2,19 +2,10 @@
import android.content.Context
import android.util.AttributeSet
-import androidx.activity.OnBackPressedDispatcher
-import androidx.activity.OnBackPressedDispatcherOwner
-import androidx.activity.setViewTreeOnBackPressedDispatcherOwner
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.lifecycleScope
-import androidx.lifecycle.repeatOnLifecycle
-import com.android.systemui.compose.ComposeFacade
-import com.android.systemui.lifecycle.repeatWhenAttached
+import android.view.View
import com.android.systemui.scene.shared.model.Scene
import com.android.systemui.scene.shared.model.SceneContainerConfig
-import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
-import kotlinx.coroutines.launch
/** A root view of the main SysUI window that supports scenes. */
class SceneWindowRootView(
@@ -30,45 +21,19 @@
containerConfig: SceneContainerConfig,
scenes: Set<Scene>,
) {
- val unsortedSceneByKey: Map<SceneKey, Scene> = scenes.associateBy { scene -> scene.key }
- val sortedSceneByKey: Map<SceneKey, Scene> = buildMap {
- containerConfig.sceneKeys.forEach { sceneKey ->
- val scene =
- checkNotNull(unsortedSceneByKey[sceneKey]) {
- "Scene not found for key \"$sceneKey\"!"
- }
-
- put(sceneKey, scene)
+ SceneWindowRootViewBinder.bind(
+ view = this@SceneWindowRootView,
+ viewModel = viewModel,
+ containerConfig = containerConfig,
+ scenes = scenes,
+ onVisibilityChangedInternal = { isVisible ->
+ super.setVisibility(if (isVisible) View.VISIBLE else View.INVISIBLE)
}
- }
+ )
+ }
- repeatWhenAttached {
- lifecycleScope.launch {
- repeatOnLifecycle(Lifecycle.State.CREATED) {
- setViewTreeOnBackPressedDispatcherOwner(
- object : OnBackPressedDispatcherOwner {
- override val onBackPressedDispatcher =
- OnBackPressedDispatcher().apply {
- setOnBackInvokedDispatcher(viewRootImpl.onBackInvokedDispatcher)
- }
-
- override val lifecycle: Lifecycle =
- this@repeatWhenAttached.lifecycle
- }
- )
-
- addView(
- ComposeFacade.createSceneContainerView(
- context = context,
- viewModel = viewModel,
- sceneByKey = sortedSceneByKey,
- )
- )
- }
-
- // Here when destroyed.
- removeAllViews()
- }
- }
+ override fun setVisibility(visibility: Int) {
+ // Do nothing. We don't want external callers to invoke this. Instead, we drive our own
+ // visibility from our view-binder.
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt
new file mode 100644
index 0000000..f9324a9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt
@@ -0,0 +1,125 @@
+/*
+ * Copyright 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.scene.ui.view
+
+import android.view.Gravity
+import android.view.View
+import android.view.ViewGroup
+import android.widget.FrameLayout
+import androidx.activity.OnBackPressedDispatcher
+import androidx.activity.OnBackPressedDispatcherOwner
+import androidx.activity.setViewTreeOnBackPressedDispatcherOwner
+import androidx.core.view.isVisible
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.R
+import com.android.systemui.compose.ComposeFacade
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.scene.shared.model.Scene
+import com.android.systemui.scene.shared.model.SceneContainerConfig
+import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
+import java.time.Instant
+import kotlinx.coroutines.launch
+
+object SceneWindowRootViewBinder {
+
+ /** Binds between the view and view-model pertaining to a specific scene container. */
+ fun bind(
+ view: ViewGroup,
+ viewModel: SceneContainerViewModel,
+ containerConfig: SceneContainerConfig,
+ scenes: Set<Scene>,
+ onVisibilityChangedInternal: (isVisible: Boolean) -> Unit,
+ ) {
+ val unsortedSceneByKey: Map<SceneKey, Scene> = scenes.associateBy { scene -> scene.key }
+ val sortedSceneByKey: Map<SceneKey, Scene> = buildMap {
+ containerConfig.sceneKeys.forEach { sceneKey ->
+ val scene =
+ checkNotNull(unsortedSceneByKey[sceneKey]) {
+ "Scene not found for key \"$sceneKey\"!"
+ }
+
+ put(sceneKey, scene)
+ }
+ }
+
+ view.repeatWhenAttached {
+ lifecycleScope.launch {
+ repeatOnLifecycle(Lifecycle.State.CREATED) {
+ view.setViewTreeOnBackPressedDispatcherOwner(
+ object : OnBackPressedDispatcherOwner {
+ override val onBackPressedDispatcher =
+ OnBackPressedDispatcher().apply {
+ setOnBackInvokedDispatcher(
+ view.viewRootImpl.onBackInvokedDispatcher
+ )
+ }
+
+ override val lifecycle: Lifecycle = this@repeatWhenAttached.lifecycle
+ }
+ )
+
+ view.addView(
+ ComposeFacade.createSceneContainerView(
+ context = view.context,
+ viewModel = viewModel,
+ sceneByKey = sortedSceneByKey,
+ )
+ )
+
+ val legacyView = view.requireViewById<View>(R.id.legacy_window_root)
+ view.addView(createVisibilityToggleView(legacyView))
+
+ launch {
+ viewModel.isVisible.collect { isVisible ->
+ onVisibilityChangedInternal(isVisible)
+ }
+ }
+ }
+
+ // Here when destroyed.
+ view.removeAllViews()
+ }
+ }
+ }
+
+ private var clickCount = 0
+ private var lastClick = Instant.now()
+
+ /**
+ * A temporary UI to toggle on/off the visibility of the given [otherView]. It is toggled by
+ * tapping 5 times in quick succession on the device camera (top center).
+ */
+ // TODO(b/291321285): Remove this when the Flexiglass UI is mature enough to turn off legacy
+ // SysUI altogether.
+ private fun createVisibilityToggleView(otherView: View): View {
+ val toggleView = View(otherView.context)
+ toggleView.layoutParams = FrameLayout.LayoutParams(200, 200, Gravity.CENTER_HORIZONTAL)
+ toggleView.setOnClickListener {
+ val now = Instant.now()
+ clickCount = if (now.minusSeconds(2) > lastClick) 1 else clickCount + 1
+ if (clickCount == 5) {
+ otherView.isVisible = !otherView.isVisible
+ clickCount = 0
+ }
+ lastClick = now
+ }
+ return toggleView
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java b/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java
index a9cecaa..6f2256e 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java
@@ -117,18 +117,22 @@
@Override
protected Parcelable onSaveInstanceState() {
+ Log.d(TAG, "onSaveInstanceState");
Parcelable superState = super.onSaveInstanceState();
SavedState ss = new SavedState(superState);
ss.mCrop = mCrop;
+ Log.d(TAG, "saving mCrop=" + mCrop);
+
return ss;
}
@Override
protected void onRestoreInstanceState(Parcelable state) {
+ Log.d(TAG, "onRestoreInstanceState");
SavedState ss = (SavedState) state;
super.onRestoreInstanceState(ss.getSuperState());
-
+ Log.d(TAG, "restoring mCrop=" + ss.mCrop + " (was " + mCrop + ")");
mCrop = ss.mCrop;
}
@@ -242,6 +246,7 @@
* Set the given boundary to the given value without animation.
*/
public void setBoundaryPosition(CropBoundary boundary, float position) {
+ Log.i(TAG, "setBoundaryPosition: " + boundary + ", position=" + position);
position = (float) getAllowedValues(boundary).clamp(position);
switch (boundary) {
case TOP:
@@ -260,6 +265,7 @@
Log.w(TAG, "No boundary selected");
break;
}
+ Log.i(TAG, "Updated mCrop: " + mCrop);
invalidate();
}
@@ -350,26 +356,31 @@
mCropInteractionListener = listener;
}
- private Range getAllowedValues(CropBoundary boundary) {
+ private Range<Float> getAllowedValues(CropBoundary boundary) {
+ float upper = 0f;
+ float lower = 1f;
switch (boundary) {
case TOP:
- return new Range<>(0f,
- mCrop.bottom - pixelDistanceToFraction(mCropTouchMargin,
- CropBoundary.BOTTOM));
+ lower = 0f;
+ upper = mCrop.bottom - pixelDistanceToFraction(mCropTouchMargin,
+ CropBoundary.BOTTOM);
+ break;
case BOTTOM:
- return new Range<>(
- mCrop.top + pixelDistanceToFraction(mCropTouchMargin,
- CropBoundary.TOP), 1f);
+ lower = mCrop.top + pixelDistanceToFraction(mCropTouchMargin, CropBoundary.TOP);
+ upper = 1;
+ break;
case LEFT:
- return new Range<>(0f,
- mCrop.right - pixelDistanceToFraction(mCropTouchMargin,
- CropBoundary.RIGHT));
+ lower = 0f;
+ upper = mCrop.right - pixelDistanceToFraction(mCropTouchMargin, CropBoundary.RIGHT);
+ break;
case RIGHT:
- return new Range<>(
- mCrop.left + pixelDistanceToFraction(mCropTouchMargin,
- CropBoundary.LEFT), 1f);
+ lower = mCrop.left + pixelDistanceToFraction(mCropTouchMargin, CropBoundary.LEFT);
+ upper = 1;
+ break;
}
- return null;
+ Log.i(TAG, "getAllowedValues: " + boundary + ", "
+ + "result=[lower=" + lower + ", upper=" + upper + "]");
+ return new Range<>(lower, upper);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
index 4bc7ec8..e6e1fac 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
@@ -36,17 +36,18 @@
import android.util.Log;
import android.view.ScrollCaptureResponse;
import android.view.View;
-import android.view.ViewTreeObserver;
import android.widget.ImageView;
import androidx.constraintlayout.widget.ConstraintLayout;
import com.android.internal.app.ChooserActivity;
import com.android.internal.logging.UiEventLogger;
+import com.android.internal.view.OneShotPreDrawListener;
import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.screenshot.CropView.CropBoundary;
import com.android.systemui.screenshot.ScrollCaptureController.LongScreenshot;
import com.android.systemui.settings.UserTracker;
@@ -215,6 +216,7 @@
mPreview.setImageDrawable(drawable);
mMagnifierView.setDrawable(mLongScreenshot.getDrawable(),
mLongScreenshot.getWidth(), mLongScreenshot.getHeight());
+ Log.i(TAG, "Completed: " + longScreenshot);
// Original boundaries go from the image tile set's y=0 to y=pageSize, so
// we animate to that as a starting crop position.
float topFraction = Math.max(0,
@@ -223,31 +225,26 @@
1 - (mLongScreenshot.getBottom() - mLongScreenshot.getPageHeight())
/ (float) mLongScreenshot.getHeight());
+ Log.i(TAG, "topFraction: " + topFraction);
+ Log.i(TAG, "bottomFraction: " + bottomFraction);
+
mEnterTransitionView.setImageDrawable(drawable);
- mEnterTransitionView.getViewTreeObserver().addOnPreDrawListener(
- new ViewTreeObserver.OnPreDrawListener() {
- @Override
- public boolean onPreDraw() {
- mEnterTransitionView.getViewTreeObserver().removeOnPreDrawListener(this);
- updateImageDimensions();
- mEnterTransitionView.post(() -> {
- Rect dest = new Rect();
- mEnterTransitionView.getBoundsOnScreen(dest);
- mLongScreenshotHolder.takeTransitionDestinationCallback()
- .setTransitionDestination(dest, () -> {
- mPreview.animate().alpha(1f);
- mCropView.setBoundaryPosition(
- CropView.CropBoundary.TOP, topFraction);
- mCropView.setBoundaryPosition(
- CropView.CropBoundary.BOTTOM, bottomFraction);
- mCropView.animateEntrance();
- mCropView.setVisibility(View.VISIBLE);
- setButtonsEnabled(true);
- });
+ OneShotPreDrawListener.add(mEnterTransitionView, () -> {
+ updateImageDimensions();
+ mEnterTransitionView.post(() -> {
+ Rect dest = new Rect();
+ mEnterTransitionView.getBoundsOnScreen(dest);
+ mLongScreenshotHolder.takeTransitionDestinationCallback()
+ .setTransitionDestination(dest, () -> {
+ mPreview.animate().alpha(1f);
+ mCropView.setBoundaryPosition(CropBoundary.TOP, topFraction);
+ mCropView.setBoundaryPosition(CropBoundary.BOTTOM, bottomFraction);
+ mCropView.animateEntrance();
+ mCropView.setVisibility(View.VISIBLE);
+ setButtonsEnabled(true);
});
- return true;
- }
- });
+ });
+ });
// Immediately export to temp image file for saved state
mCacheSaveFuture = mImageExporter.exportToRawFile(mBackgroundExecutor,
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureClient.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureClient.java
index 93e5021..e93f737 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureClient.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureClient.java
@@ -365,6 +365,9 @@
@Override
public void onImageAvailable(ImageReader reader) {
synchronized (mLock) {
+ if (mCapturedImage != null) {
+ mCapturedImage.close();
+ }
mCapturedImage = mReader.acquireLatestImage();
if (mCapturedArea != null) {
completeCaptureRequest();
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
index 30a0b8f..bb34ede 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
@@ -130,8 +130,14 @@
@Override
public String toString() {
- return "LongScreenshot{w=" + mImageTileSet.getWidth()
- + ", h=" + mImageTileSet.getHeight() + "}";
+ return "LongScreenshot{"
+ + "l=" + mImageTileSet.getLeft() + ", "
+ + "t=" + mImageTileSet.getTop() + ", "
+ + "r=" + mImageTileSet.getRight() + ", "
+ + "b=" + mImageTileSet.getBottom() + ", "
+ + "w=" + mImageTileSet.getWidth() + ", "
+ + "h=" + mImageTileSet.getHeight()
+ + "}";
}
public Drawable getDrawable() {
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
index 9594ba3..6af9b73 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
@@ -22,7 +22,6 @@
import android.animation.ValueAnimator;
import android.annotation.NonNull;
-import android.content.ContentResolver;
import android.content.Context;
import android.database.ContentObserver;
import android.hardware.display.BrightnessInfo;
@@ -31,10 +30,10 @@
import android.os.AsyncTask;
import android.os.Handler;
import android.os.HandlerExecutor;
+import android.os.Looper;
import android.os.Message;
import android.os.PowerManager;
import android.os.RemoteException;
-import android.os.ServiceManager;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
@@ -43,6 +42,8 @@
import android.util.Log;
import android.util.MathUtils;
+import androidx.annotation.Nullable;
+
import com.android.internal.display.BrightnessSynchronizer;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
@@ -52,10 +53,13 @@
import com.android.systemui.settings.DisplayTracker;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.policy.BrightnessMirrorController;
+import com.android.systemui.util.settings.SecureSettings;
import java.util.concurrent.Executor;
-import javax.inject.Inject;
+import dagger.assisted.Assisted;
+import dagger.assisted.AssistedFactory;
+import dagger.assisted.AssistedInject;
public class BrightnessController implements ToggleSlider.Listener, MirroredBrightnessController {
private static final String TAG = "CentralSurfaces.BrightnessController";
@@ -75,8 +79,11 @@
private final DisplayManager mDisplayManager;
private final UserTracker mUserTracker;
private final DisplayTracker mDisplayTracker;
+ @Nullable
private final IVrManager mVrManager;
+ private final SecureSettings mSecureSettings;
+
private final Executor mMainExecutor;
private final Handler mBackgroundHandler;
private final BrightnessObserver mBrightnessObserver;
@@ -106,6 +113,8 @@
/** ContentObserver to watch brightness */
private class BrightnessObserver extends ContentObserver {
+ private boolean mObserving = false;
+
BrightnessObserver(Handler handler) {
super(handler);
}
@@ -124,19 +133,17 @@
}
public void startObserving() {
- final ContentResolver cr = mContext.getContentResolver();
- cr.unregisterContentObserver(this);
- cr.registerContentObserver(
- BRIGHTNESS_MODE_URI,
- false, this, UserHandle.USER_ALL);
- mDisplayTracker.addBrightnessChangeCallback(mBrightnessListener,
- new HandlerExecutor(mHandler));
+ if (!mObserving) {
+ mObserving = true;
+ mSecureSettings.registerContentObserverForUser(
+ BRIGHTNESS_MODE_URI,
+ false, this, UserHandle.USER_ALL);
+ }
}
public void stopObserving() {
- final ContentResolver cr = mContext.getContentResolver();
- cr.unregisterContentObserver(this);
- mDisplayTracker.removeCallback(mBrightnessListener);
+ mSecureSettings.unregisterContentObserver(this);
+ mObserving = false;
}
}
@@ -159,6 +166,8 @@
}
mBrightnessObserver.startObserving();
+ mDisplayTracker.addBrightnessChangeCallback(mBrightnessListener,
+ new HandlerExecutor(mMainHandler));
mUserTracker.addCallback(mUserChangedCallback, mMainExecutor);
// Update the slider and mode before attaching the listener so we don't
@@ -166,7 +175,7 @@
mUpdateModeRunnable.run();
mUpdateSliderRunnable.run();
- mHandler.sendEmptyMessage(MSG_ATTACH_LISTENER);
+ mMainHandler.sendEmptyMessage(MSG_ATTACH_LISTENER);
}
};
@@ -187,9 +196,10 @@
}
mBrightnessObserver.stopObserving();
+ mDisplayTracker.removeCallback(mBrightnessListener);
mUserTracker.removeCallback(mUserChangedCallback);
- mHandler.sendEmptyMessage(MSG_DETACH_LISTENER);
+ mMainHandler.sendEmptyMessage(MSG_DETACH_LISTENER);
}
};
@@ -225,7 +235,7 @@
mBrightnessMin = info.brightnessMinimum;
// Value is passed as intbits, since this is what the message takes.
final int valueAsIntBits = Float.floatToIntBits(info.brightness);
- mHandler.obtainMessage(MSG_UPDATE_SLIDER, valueAsIntBits,
+ mMainHandler.obtainMessage(MSG_UPDATE_SLIDER, valueAsIntBits,
inVrMode ? 1 : 0).sendToTarget();
}
};
@@ -233,14 +243,14 @@
private final IVrStateCallbacks mVrStateCallbacks = new IVrStateCallbacks.Stub() {
@Override
public void onVrStateChanged(boolean enabled) {
- mHandler.obtainMessage(MSG_VR_MODE_CHANGED, enabled ? 1 : 0, 0)
+ mMainHandler.obtainMessage(MSG_VR_MODE_CHANGED, enabled ? 1 : 0, 0)
.sendToTarget();
}
};
- private final Handler mHandler = new Handler() {
+ private final Handler.Callback mHandlerCallback = new Handler.Callback() {
@Override
- public void handleMessage(Message msg) {
+ public boolean handleMessage(Message msg) {
mExternalChange = true;
try {
switch (msg.what) {
@@ -257,14 +267,18 @@
updateVrMode(msg.arg1 != 0);
break;
default:
- super.handleMessage(msg);
+ return false;
+
}
} finally {
mExternalChange = false;
}
+ return true;
}
};
+ private final Handler mMainHandler;
+
private final UserTracker.Callback mUserChangedCallback =
new UserTracker.Callback() {
@Override
@@ -274,12 +288,17 @@
}
};
+ @AssistedInject
public BrightnessController(
Context context,
- ToggleSlider control,
+ @Assisted ToggleSlider control,
UserTracker userTracker,
DisplayTracker displayTracker,
+ DisplayManager displayManager,
+ SecureSettings secureSettings,
+ @Nullable IVrManager iVrManager,
@Main Executor mainExecutor,
+ @Main Looper mainLooper,
@Background Handler bgHandler) {
mContext = context;
mControl = control;
@@ -288,22 +307,23 @@
mBackgroundHandler = bgHandler;
mUserTracker = userTracker;
mDisplayTracker = displayTracker;
- mBrightnessObserver = new BrightnessObserver(mHandler);
-
+ mSecureSettings = secureSettings;
mDisplayId = mContext.getDisplayId();
- PowerManager pm = context.getSystemService(PowerManager.class);
+ mDisplayManager = displayManager;
+ mVrManager = iVrManager;
- mDisplayManager = context.getSystemService(DisplayManager.class);
- mVrManager = IVrManager.Stub.asInterface(ServiceManager.getService(
- Context.VR_SERVICE));
+ mMainHandler = new Handler(mainLooper, mHandlerCallback);
+ mBrightnessObserver = new BrightnessObserver(mMainHandler);
}
public void registerCallbacks() {
+ mBackgroundHandler.removeCallbacks(mStartListeningRunnable);
mBackgroundHandler.post(mStartListeningRunnable);
}
/** Unregister all call backs, both to and from the controller */
public void unregisterCallbacks() {
+ mBackgroundHandler.removeCallbacks(mStopListeningRunnable);
mBackgroundHandler.post(mStopListeningRunnable);
mControlValueInitialized = false;
}
@@ -418,38 +438,12 @@
mSliderAnimator.start();
}
+
+
/** Factory for creating a {@link BrightnessController}. */
- public static class Factory {
- private final Context mContext;
- private final UserTracker mUserTracker;
- private final DisplayTracker mDisplayTracker;
- private final Executor mMainExecutor;
- private final Handler mBackgroundHandler;
-
- @Inject
- public Factory(
- Context context,
- UserTracker userTracker,
- DisplayTracker displayTracker,
- @Main Executor mainExecutor,
- @Background Handler bgHandler) {
- mContext = context;
- mUserTracker = userTracker;
- mDisplayTracker = displayTracker;
- mMainExecutor = mainExecutor;
- mBackgroundHandler = bgHandler;
- }
-
+ @AssistedFactory
+ public interface Factory {
/** Create a {@link BrightnessController} */
- public BrightnessController create(ToggleSlider toggleSlider) {
- return new BrightnessController(
- mContext,
- toggleSlider,
- mUserTracker,
- mDisplayTracker,
- mMainExecutor,
- mBackgroundHandler);
- }
+ BrightnessController create(ToggleSlider toggleSlider);
}
-
}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
index 5199bd4..38b1f14 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
@@ -18,54 +18,56 @@
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+import static android.view.WindowManagerPolicyConstants.EXTRA_FROM_BRIGHTNESS_KEY;
import android.app.Activity;
import android.graphics.Rect;
import android.os.Bundle;
-import android.os.Handler;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
+import android.view.accessibility.AccessibilityManager;
import android.widget.FrameLayout;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.systemui.R;
-import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.settings.DisplayTracker;
-import com.android.systemui.settings.UserTracker;
+import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
+import com.android.systemui.util.concurrency.DelayableExecutor;
import java.util.List;
-import java.util.concurrent.Executor;
import javax.inject.Inject;
/** A dialog that provides controls for adjusting the screen brightness. */
public class BrightnessDialog extends Activity {
+ @VisibleForTesting
+ static final int DIALOG_TIMEOUT_MILLIS = 3000;
+
private BrightnessController mBrightnessController;
private final BrightnessSliderController.Factory mToggleSliderFactory;
- private final UserTracker mUserTracker;
- private final DisplayTracker mDisplayTracker;
- private final Executor mMainExecutor;
- private final Handler mBackgroundHandler;
+ private final BrightnessController.Factory mBrightnessControllerFactory;
+ private final DelayableExecutor mMainExecutor;
+ private final AccessibilityManagerWrapper mAccessibilityMgr;
+ private Runnable mCancelTimeoutRunnable;
@Inject
public BrightnessDialog(
- UserTracker userTracker,
- DisplayTracker displayTracker,
- BrightnessSliderController.Factory factory,
- @Main Executor mainExecutor,
- @Background Handler bgHandler) {
- mUserTracker = userTracker;
- mDisplayTracker = displayTracker;
- mToggleSliderFactory = factory;
+ BrightnessSliderController.Factory brightnessSliderfactory,
+ BrightnessController.Factory brightnessControllerFactory,
+ @Main DelayableExecutor mainExecutor,
+ AccessibilityManagerWrapper accessibilityMgr
+ ) {
+ mToggleSliderFactory = brightnessSliderfactory;
+ mBrightnessControllerFactory = brightnessControllerFactory;
mMainExecutor = mainExecutor;
- mBackgroundHandler = bgHandler;
+ mAccessibilityMgr = accessibilityMgr;
}
@@ -110,8 +112,7 @@
controller.init();
frame.addView(controller.getRootView(), MATCH_PARENT, WRAP_CONTENT);
- mBrightnessController = new BrightnessController(
- this, controller, mUserTracker, mDisplayTracker, mMainExecutor, mBackgroundHandler);
+ mBrightnessController = mBrightnessControllerFactory.create(controller);
}
@Override
@@ -122,6 +123,14 @@
}
@Override
+ protected void onResume() {
+ super.onResume();
+ if (triggeredByBrightnessKey()) {
+ scheduleTimeout();
+ }
+ }
+
+ @Override
protected void onPause() {
super.onPause();
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
@@ -139,9 +148,25 @@
if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN
|| keyCode == KeyEvent.KEYCODE_VOLUME_UP
|| keyCode == KeyEvent.KEYCODE_VOLUME_MUTE) {
+ if (mCancelTimeoutRunnable != null) {
+ mCancelTimeoutRunnable.run();
+ }
finish();
}
return super.onKeyDown(keyCode, event);
}
+
+ private boolean triggeredByBrightnessKey() {
+ return getIntent().getBooleanExtra(EXTRA_FROM_BRIGHTNESS_KEY, false);
+ }
+
+ private void scheduleTimeout() {
+ if (mCancelTimeoutRunnable != null) {
+ mCancelTimeoutRunnable.run();
+ }
+ final int timeout = mAccessibilityMgr.getRecommendedTimeoutMillis(DIALOG_TIMEOUT_MILLIS,
+ AccessibilityManager.FLAG_CONTENT_CONTROLS);
+ mCancelTimeoutRunnable = mMainExecutor.executeDelayed(this::finish, timeout);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/LockscreenHostedDreamGestureListener.kt b/packages/SystemUI/src/com/android/systemui/shade/LockscreenHostedDreamGestureListener.kt
new file mode 100644
index 0000000..45fc68a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/LockscreenHostedDreamGestureListener.kt
@@ -0,0 +1,72 @@
+/*
+ * 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.shade
+
+import android.os.PowerManager
+import android.view.GestureDetector
+import android.view.MotionEvent
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.plugins.FalsingManager.LOW_PENALTY
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.statusbar.StatusBarState
+import javax.inject.Inject
+
+/**
+ * This gestureListener will wake up by tap when the device is dreaming but not dozing, and the
+ * selected screensaver is hosted in lockscreen. Tap is gated by the falsing manager.
+ *
+ * Touches go through the [NotificationShadeWindowViewController].
+ */
+@SysUISingleton
+class LockscreenHostedDreamGestureListener
+@Inject
+constructor(
+ private val falsingManager: FalsingManager,
+ private val powerInteractor: PowerInteractor,
+ private val statusBarStateController: StatusBarStateController,
+ private val primaryBouncerInteractor: PrimaryBouncerInteractor,
+ private val keyguardRepository: KeyguardRepository,
+ private val shadeLogger: ShadeLogger,
+) : GestureDetector.SimpleOnGestureListener() {
+ private val TAG = this::class.simpleName
+
+ override fun onSingleTapUp(e: MotionEvent): Boolean {
+ if (shouldHandleMotionEvent()) {
+ if (!falsingManager.isFalseTap(LOW_PENALTY)) {
+ shadeLogger.d("$TAG#onSingleTapUp tap handled, requesting wakeUpIfDreaming")
+ powerInteractor.wakeUpIfDreaming(
+ "DREAMING_SINGLE_TAP",
+ PowerManager.WAKE_REASON_TAP
+ )
+ } else {
+ shadeLogger.d("$TAG#onSingleTapUp false tap ignored")
+ }
+ return true
+ }
+ return false
+ }
+
+ private fun shouldHandleMotionEvent(): Boolean {
+ return keyguardRepository.isActiveDreamLockscreenHosted.value &&
+ statusBarStateController.state == StatusBarState.KEYGUARD &&
+ !primaryBouncerInteractor.isBouncerShowing()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index d97db3b..9399d48 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -168,7 +168,6 @@
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.NotificationShelfController;
import com.android.systemui.statusbar.PulseExpansionHandler;
-import com.android.systemui.statusbar.RemoteInputController;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.VibratorHelper;
@@ -224,6 +223,8 @@
import com.android.systemui.util.time.SystemClock;
import com.android.wm.shell.animation.FlingAnimationUtils;
+import kotlin.Unit;
+
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
@@ -234,8 +235,6 @@
import javax.inject.Inject;
import javax.inject.Provider;
-import kotlin.Unit;
-
import kotlinx.coroutines.CoroutineDispatcher;
@SysUISingleton
@@ -440,8 +439,6 @@
private final FalsingCollector mFalsingCollector;
private final ShadeHeadsUpTrackerImpl mShadeHeadsUpTracker = new ShadeHeadsUpTrackerImpl();
private final ShadeFoldAnimator mShadeFoldAnimator = new ShadeFoldAnimatorImpl();
- private final ShadeNotificationPresenterImpl mShadeNotificationPresenter =
- new ShadeNotificationPresenterImpl();
private boolean mShowIconsWhenExpanded;
private int mIndicationBottomPadding;
@@ -1076,6 +1073,8 @@
mTapAgainViewController.init();
mShadeHeaderController.init();
+ mShadeHeaderController.setShadeCollapseAction(
+ () -> collapse(/* delayed= */ false , /* speedUpFactor= */ 1.0f));
mKeyguardUnfoldTransition.ifPresent(u -> u.setup(mView));
mNotificationPanelUnfoldAnimationController.ifPresent(controller ->
controller.setup(mNotificationContainerParent));
@@ -1471,7 +1470,7 @@
// so we should not add a padding for them
stackScrollerPadding = 0;
} else {
- stackScrollerPadding = mQsController.getUnlockedStackScrollerPadding();
+ stackScrollerPadding = mQsController.getHeaderHeight();
}
} else {
stackScrollerPadding = mClockPositionResult.stackScrollerPaddingExpanded;
@@ -1518,7 +1517,7 @@
userSwitcherPreferredY,
darkAmount, mOverStretchAmount,
bypassEnabled,
- mQsController.getUnlockedStackScrollerPadding(),
+ mQsController.getHeaderHeight(),
mQsController.computeExpansionFraction(),
mDisplayTopInset,
mSplitShadeEnabled,
@@ -3321,23 +3320,6 @@
).printTableData(ipw);
}
- private final class ShadeNotificationPresenterImpl implements ShadeNotificationPresenter{
- @Override
- public RemoteInputController.Delegate createRemoteInputDelegate() {
- return mNotificationStackScrollLayoutController.createDelegate();
- }
-
- @Override
- public boolean hasPulsingNotifications() {
- return mNotificationListContainer.hasPulsingNotifications();
- }
- }
-
- @Override
- public ShadeNotificationPresenter getShadeNotificationPresenter() {
- return mShadeNotificationPresenter;
- }
-
@Override
public void initDependencies(
CentralSurfaces centralSurfaces,
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
index b6bb0ca..b328c55 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
@@ -480,7 +480,6 @@
private void applyWindowLayoutParams() {
if (mDeferWindowLayoutParams == 0 && mLp != null && mLp.copyFrom(mLpChanged) != 0) {
- mLogger.logApplyingWindowLayoutParams(mLp);
Trace.beginSection("updateViewLayout");
mWindowManager.updateViewLayout(mWindowRootView, mLp);
Trace.endSection();
@@ -544,7 +543,6 @@
state.forceUserActivity,
state.launchingActivityFromNotification,
state.mediaBackdropShowing,
- state.wallpaperSupportsAmbientMode,
state.windowNotTouchable,
state.componentsForcingTopUi,
state.forceOpenTokens,
@@ -735,12 +733,6 @@
apply(mCurrentState);
}
- @Override
- public void setWallpaperSupportsAmbientMode(boolean supportsAmbientMode) {
- mCurrentState.wallpaperSupportsAmbientMode = supportsAmbientMode;
- apply(mCurrentState);
- }
-
/**
* @param state The {@link StatusBarStateController} of the status bar.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt
index 7812f07..d252943 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt
@@ -46,7 +46,6 @@
@JvmField var forceUserActivity: Boolean = false,
@JvmField var launchingActivityFromNotification: Boolean = false,
@JvmField var mediaBackdropShowing: Boolean = false,
- @JvmField var wallpaperSupportsAmbientMode: Boolean = false,
@JvmField var windowNotTouchable: Boolean = false,
@JvmField var componentsForcingTopUi: MutableSet<String> = mutableSetOf(),
@JvmField var forceOpenTokens: MutableSet<Any> = mutableSetOf(),
@@ -84,7 +83,6 @@
forceUserActivity.toString(),
launchingActivityFromNotification.toString(),
mediaBackdropShowing.toString(),
- wallpaperSupportsAmbientMode.toString(),
windowNotTouchable.toString(),
componentsForcingTopUi.toString(),
forceOpenTokens.toString(),
@@ -124,7 +122,6 @@
forceUserActivity: Boolean,
launchingActivity: Boolean,
backdropShowing: Boolean,
- wallpaperSupportsAmbientMode: Boolean,
notTouchable: Boolean,
componentsForcingTopUi: MutableSet<String>,
forceOpenTokens: MutableSet<Any>,
@@ -153,7 +150,6 @@
this.forceUserActivity = forceUserActivity
this.launchingActivityFromNotification = launchingActivity
this.mediaBackdropShowing = backdropShowing
- this.wallpaperSupportsAmbientMode = wallpaperSupportsAmbientMode
this.windowNotTouchable = notTouchable
this.componentsForcingTopUi.clear()
this.componentsForcingTopUi.addAll(componentsForcingTopUi)
@@ -200,7 +196,6 @@
"forceUserActivity",
"launchingActivity",
"backdropShowing",
- "wallpaperSupportsAmbientMode",
"notTouchable",
"componentsForcingTopUi",
"forceOpenTokens",
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index 5c41d57..108ea68 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -16,6 +16,7 @@
package com.android.systemui.shade;
+import static com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED;
import static com.android.systemui.flags.Flags.TRACKPAD_GESTURE_COMMON;
import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
@@ -46,6 +47,7 @@
import com.android.systemui.bouncer.ui.viewmodel.KeyguardBouncerViewModel;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.compose.ComposeFacade;
+import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dock.DockManager;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
@@ -72,7 +74,6 @@
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.PhoneStatusBarViewController;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
-import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent;
import com.android.systemui.statusbar.window.StatusBarWindowStateController;
import com.android.systemui.unfold.UnfoldTransitionProgressProvider;
import com.android.systemui.util.time.SystemClock;
@@ -87,7 +88,7 @@
/**
* Controller for {@link NotificationShadeWindowView}.
*/
-@CentralSurfacesComponent.CentralSurfacesScope
+@SysUISingleton
public class NotificationShadeWindowViewController {
private static final String TAG = "NotifShadeWindowVC";
private final FalsingCollector mFalsingCollector;
@@ -102,9 +103,12 @@
private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
private final AmbientState mAmbientState;
private final PulsingGestureListener mPulsingGestureListener;
+ private final LockscreenHostedDreamGestureListener mLockscreenHostedDreamGestureListener;
private final NotificationInsetsController mNotificationInsetsController;
private final boolean mIsTrackpadCommonEnabled;
+ private final FeatureFlags mFeatureFlags;
private GestureDetector mPulsingWakeupGestureHandler;
+ private GestureDetector mDreamingWakeupGestureHandler;
private View mBrightnessMirror;
private boolean mTouchActive;
private boolean mTouchCancelled;
@@ -156,6 +160,7 @@
NotificationInsetsController notificationInsetsController,
AmbientState ambientState,
PulsingGestureListener pulsingGestureListener,
+ LockscreenHostedDreamGestureListener lockscreenHostedDreamGestureListener,
KeyguardBouncerViewModel keyguardBouncerViewModel,
KeyguardBouncerComponent.Factory keyguardBouncerComponentFactory,
KeyguardMessageAreaController.Factory messageAreaControllerFactory,
@@ -187,8 +192,10 @@
mKeyguardUnlockAnimationController = keyguardUnlockAnimationController;
mAmbientState = ambientState;
mPulsingGestureListener = pulsingGestureListener;
+ mLockscreenHostedDreamGestureListener = lockscreenHostedDreamGestureListener;
mNotificationInsetsController = notificationInsetsController;
mIsTrackpadCommonEnabled = featureFlags.isEnabled(TRACKPAD_GESTURE_COMMON);
+ mFeatureFlags = featureFlags;
// This view is not part of the newly inflated expanded status bar.
mBrightnessMirror = mView.findViewById(R.id.brightness_mirror_container);
@@ -237,7 +244,10 @@
mStackScrollLayout = mView.findViewById(R.id.notification_stack_scroller);
mPulsingWakeupGestureHandler = new GestureDetector(mView.getContext(),
mPulsingGestureListener);
-
+ if (mFeatureFlags.isEnabled(LOCKSCREEN_WALLPAPER_DREAM_ENABLED)) {
+ mDreamingWakeupGestureHandler = new GestureDetector(mView.getContext(),
+ mLockscreenHostedDreamGestureListener);
+ }
mView.setLayoutInsetsController(mNotificationInsetsController);
mView.setInteractionEventHandler(new NotificationShadeWindowView.InteractionEventHandler() {
@Override
@@ -291,6 +301,10 @@
mFalsingCollector.onTouchEvent(ev);
mPulsingWakeupGestureHandler.onTouchEvent(ev);
+ if (mDreamingWakeupGestureHandler != null
+ && mDreamingWakeupGestureHandler.onTouchEvent(ev)) {
+ return true;
+ }
if (mStatusBarKeyguardViewManager.dispatchTouchEvent(ev)) {
return true;
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
index fba0120..5c1dd56 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
@@ -29,6 +29,8 @@
import com.android.systemui.R
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.fragments.FragmentService
import com.android.systemui.navigationbar.NavigationModeController
import com.android.systemui.plugins.qs.QS
@@ -36,6 +38,7 @@
import com.android.systemui.recents.OverviewProxyService
import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener
import com.android.systemui.shared.system.QuickStepContract
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
import com.android.systemui.util.LargeScreenUtils
import com.android.systemui.util.ViewController
import com.android.systemui.util.concurrency.DelayableExecutor
@@ -54,7 +57,10 @@
private val shadeHeaderController: ShadeHeaderController,
private val shadeExpansionStateManager: ShadeExpansionStateManager,
private val fragmentService: FragmentService,
- @Main private val delayableExecutor: DelayableExecutor
+ @Main private val delayableExecutor: DelayableExecutor,
+ private val featureFlags: FeatureFlags,
+ private val
+ notificationStackScrollLayoutController: NotificationStackScrollLayoutController,
) : ViewController<NotificationsQuickSettingsContainer>(view), QSContainerController {
private var qsExpanded = false
@@ -118,6 +124,9 @@
isGestureNavigation = QuickStepContract.isGesturalMode(mode)
}
isGestureNavigation = QuickStepContract.isGesturalMode(currentMode)
+
+ mView.setStackScroller(notificationStackScrollLayoutController.getView())
+ mView.setMigratingNSSL(featureFlags.isEnabled(Flags.MIGRATE_NSSL))
}
public override fun onViewAttached() {
@@ -254,14 +263,17 @@
}
private fun setNotificationsConstraints(constraintSet: ConstraintSet) {
+ if (featureFlags.isEnabled(Flags.MIGRATE_NSSL)) {
+ return
+ }
val startConstraintId = if (splitShadeEnabled) R.id.qs_edge_guideline else PARENT_ID
+ val nsslId = R.id.notification_stack_scroller
constraintSet.apply {
- connect(R.id.notification_stack_scroller, START, startConstraintId, START)
- setMargin(R.id.notification_stack_scroller, START,
- if (splitShadeEnabled) 0 else panelMarginHorizontal)
- setMargin(R.id.notification_stack_scroller, END, panelMarginHorizontal)
- setMargin(R.id.notification_stack_scroller, TOP, topMargin)
- setMargin(R.id.notification_stack_scroller, BOTTOM, notificationsBottomMargin)
+ connect(nsslId, START, startConstraintId, START)
+ setMargin(nsslId, START, if (splitShadeEnabled) 0 else panelMarginHorizontal)
+ setMargin(nsslId, END, panelMarginHorizontal)
+ setMargin(nsslId, TOP, topMargin)
+ setMargin(nsslId, BOTTOM, notificationsBottomMargin)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java
index e5b84bd..3b3df50 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java
@@ -23,6 +23,7 @@
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.View;
+import android.view.ViewGroup.MarginLayoutParams;
import android.view.WindowInsets;
import androidx.annotation.Nullable;
@@ -56,6 +57,7 @@
private QS mQs;
private View mQSContainer;
private int mLastQSPaddingBottom;
+ private boolean mIsMigratingNSSL;
/**
* These are used to compute the bounding box containing the shade and the notification scrim,
@@ -75,10 +77,13 @@
protected void onFinishInflate() {
super.onFinishInflate();
mQsFrame = findViewById(R.id.qs_frame);
- mStackScroller = findViewById(R.id.notification_stack_scroller);
mKeyguardStatusBar = findViewById(R.id.keyguard_header);
}
+ void setStackScroller(View stackScroller) {
+ mStackScroller = stackScroller;
+ }
+
@Override
public void onFragmentViewCreated(String tag, Fragment fragment) {
mQs = (QS) fragment;
@@ -108,7 +113,7 @@
}
public void setNotificationsMarginBottom(int margin) {
- LayoutParams params = (LayoutParams) mStackScroller.getLayoutParams();
+ MarginLayoutParams params = (MarginLayoutParams) mStackScroller.getLayoutParams();
params.bottomMargin = margin;
mStackScroller.setLayoutParams(params);
}
@@ -173,8 +178,15 @@
super.dispatchDraw(canvas);
}
+ void setMigratingNSSL(boolean isMigrating) {
+ mIsMigratingNSSL = isMigrating;
+ }
+
@Override
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
+ if (mIsMigratingNSSL) {
+ return super.drawChild(canvas, child, drawingTime);
+ }
int layoutIndex = mLayoutDrawingOrder.indexOf(child);
if (layoutIndex >= 0) {
return super.drawChild(canvas, mDrawingOrderedChildren.get(layoutIndex), drawingTime);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
index 025c4611..baac57c 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
@@ -233,7 +233,6 @@
private int mMaxExpansionHeight;
/** Expansion fraction of the notification shade */
private float mShadeExpandedFraction;
- private int mPeekHeight;
private float mLastOverscroll;
private boolean mExpansionFromOverscroll;
private boolean mExpansionEnabledPolicy = true;
@@ -429,7 +428,6 @@
final ViewConfiguration configuration = ViewConfiguration.get(this.mPanelView.getContext());
mTouchSlop = configuration.getScaledTouchSlop();
mSlopMultiplier = configuration.getScaledAmbiguousGestureMultiplier();
- mPeekHeight = mResources.getDimensionPixelSize(R.dimen.qs_peek_height);
mStatusBarMinHeight = SystemBarUtils.getStatusBarHeight(mPanelView.getContext());
mScrimCornerRadius = mResources.getDimensionPixelSize(
R.dimen.notification_scrim_corner_radius);
@@ -500,12 +498,7 @@
}
int getHeaderHeight() {
- return mQs.getHeader().getHeight();
- }
-
- /** Returns the padding of the stackscroller when unlocked */
- int getUnlockedStackScrollerPadding() {
- return (mQs != null ? mQs.getHeader().getHeight() : 0) + mPeekHeight;
+ return isQsFragmentCreated() ? mQs.getHeader().getHeight() : 0;
}
private boolean isRemoteInputActiveWithKeyboardUp() {
@@ -2090,8 +2083,6 @@
ipw.println(mMaxExpansionHeight);
ipw.print("mShadeExpandedFraction=");
ipw.println(mShadeExpandedFraction);
- ipw.print("mPeekHeight=");
- ipw.println(mPeekHeight);
ipw.print("mLastOverscroll=");
ipw.println(mLastOverscroll);
ipw.print("mExpansionFromOverscroll=");
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java
index ebb9888..02f337a 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java
@@ -18,6 +18,7 @@
import android.view.MotionEvent;
+import com.android.systemui.CoreStartable;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationPresenter;
import com.android.systemui.statusbar.StatusBarState;
@@ -31,7 +32,7 @@
* these are coordinated with {@link StatusBarKeyguardViewManager} via
* {@link com.android.systemui.keyguard.KeyguardViewMediator} and others.
*/
-public interface ShadeController {
+public interface ShadeController extends CoreStartable {
/** Make our window larger and the shade expanded */
void instantExpandShade();
@@ -164,17 +165,14 @@
void onLaunchAnimationEnd(boolean launchIsFullScreen);
/** Sets the listener for when the visibility of the shade changes. */
- default void setVisibilityListener(ShadeVisibilityListener listener) {};
+ default void setVisibilityListener(ShadeVisibilityListener listener) {}
/** */
- default void setNotificationPresenter(NotificationPresenter presenter) {};
+ default void setNotificationPresenter(NotificationPresenter presenter) {}
/** */
default void setNotificationShadeWindowViewController(
- NotificationShadeWindowViewController notificationShadeWindowViewController) {};
-
- /** */
- default void setShadeViewController(ShadeViewController shadeViewController) {};
+ NotificationShadeWindowViewController notificationShadeWindowViewController) {}
/** Listens for shade visibility changes. */
interface ShadeVisibilityListener {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerEmptyImpl.kt
index 4d05007..5f95bca 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerEmptyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerEmptyImpl.kt
@@ -1,3 +1,19 @@
+/*
+ * 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.shade
import android.view.MotionEvent
@@ -7,6 +23,7 @@
/** Empty implementation of ShadeController for variants of Android without shades. */
@SysUISingleton
open class ShadeControllerEmptyImpl @Inject constructor() : ShadeController {
+ override fun start() {}
override fun instantExpandShade() {}
override fun instantCollapseShade() {}
override fun animateCollapseShade(
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
index af74a8d..22c63817 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
@@ -63,6 +63,7 @@
private final StatusBarWindowController mStatusBarWindowController;
private final DeviceProvisionedController mDeviceProvisionedController;
+ private final Lazy<ShadeViewController> mShadeViewControllerLazy;
private final Lazy<AssistManager> mAssistManagerLazy;
private final Lazy<NotificationGutsManager> mGutsManager;
@@ -70,8 +71,6 @@
private boolean mExpandedVisible;
- // TODO(b/237661616): Rename this variable to mShadeViewController.
- private ShadeViewController mNotificationPanelViewController;
private NotificationPresenter mPresenter;
private NotificationShadeWindowViewController mNotificationShadeWindowViewController;
private ShadeVisibilityListener mShadeVisibilityListener;
@@ -87,11 +86,13 @@
DeviceProvisionedController deviceProvisionedController,
NotificationShadeWindowController notificationShadeWindowController,
WindowManager windowManager,
+ Lazy<ShadeViewController> shadeViewControllerLazy,
Lazy<AssistManager> assistManagerLazy,
Lazy<NotificationGutsManager> gutsManager
) {
mCommandQueue = commandQueue;
mMainExecutor = mainExecutor;
+ mShadeViewControllerLazy = shadeViewControllerLazy;
mStatusBarStateController = statusBarStateController;
mStatusBarWindowController = statusBarWindowController;
mDeviceProvisionedController = deviceProvisionedController;
@@ -107,7 +108,7 @@
public void instantExpandShade() {
// Make our window larger and the panel expanded.
makeExpandedVisible(true /* force */);
- mNotificationPanelViewController.expand(false /* animate */);
+ getShadeViewController().expand(false /* animate */);
mCommandQueue.recomputeDisableFlags(mDisplayId, false /* animate */);
}
@@ -123,13 +124,13 @@
"animateCollapse(): mExpandedVisible=" + mExpandedVisible + "flags=" + flags);
}
if (getNotificationShadeWindowView() != null
- && mNotificationPanelViewController.canBeCollapsed()
+ && getShadeViewController().canBeCollapsed()
&& (flags & CommandQueue.FLAG_EXCLUDE_NOTIFICATION_PANEL) == 0) {
// release focus immediately to kick off focus change transition
mNotificationShadeWindowController.setNotificationShadeFocusable(false);
mNotificationShadeWindowViewController.cancelExpandHelper();
- mNotificationPanelViewController.collapse(true, delayed, speedUpFactor);
+ getShadeViewController().collapse(true, delayed, speedUpFactor);
}
}
@@ -138,7 +139,7 @@
if (!mCommandQueue.panelsEnabled()) {
return;
}
- mNotificationPanelViewController.expandToNotifications();
+ getShadeViewController().expandToNotifications();
}
@Override
@@ -149,12 +150,12 @@
// Settings are not available in setup
if (!mDeviceProvisionedController.isCurrentUserSetup()) return;
- mNotificationPanelViewController.expandToQs();
+ getShadeViewController().expandToQs();
}
@Override
public boolean closeShadeIfOpen() {
- if (!mNotificationPanelViewController.isFullyCollapsed()) {
+ if (!getShadeViewController().isFullyCollapsed()) {
mCommandQueue.animateCollapsePanels(
CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */);
notifyVisibilityChanged(false);
@@ -170,12 +171,12 @@
@Override
public boolean isShadeFullyOpen() {
- return mNotificationPanelViewController.isShadeFullyExpanded();
+ return getShadeViewController().isShadeFullyExpanded();
}
@Override
public boolean isExpandingOrCollapsing() {
- return mNotificationPanelViewController.isExpandingOrCollapsing();
+ return getShadeViewController().isExpandingOrCollapsing();
}
@Override
public void postAnimateCollapseShade() {
@@ -194,13 +195,13 @@
@Override
public void postOnShadeExpanded(Runnable executable) {
- mNotificationPanelViewController.addOnGlobalLayoutListener(
+ getShadeViewController().addOnGlobalLayoutListener(
new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
if (getNotificationShadeWindowView().isVisibleToUser()) {
- mNotificationPanelViewController.removeOnGlobalLayoutListener(this);
- mNotificationPanelViewController.postToView(executable);
+ getShadeViewController().removeOnGlobalLayoutListener(this);
+ getShadeViewController().postToView(executable);
}
}
});
@@ -224,7 +225,7 @@
@Override
public boolean collapseShade() {
- if (!mNotificationPanelViewController.isFullyCollapsed()) {
+ if (!getShadeViewController().isFullyCollapsed()) {
// close the shade if it was open
animateCollapseShadeForcedDelayed();
notifyVisibilityChanged(false);
@@ -252,10 +253,10 @@
@Override
public void cancelExpansionAndCollapseShade() {
- if (mNotificationPanelViewController.isTracking()) {
+ if (getShadeViewController().isTracking()) {
mNotificationShadeWindowViewController.cancelCurrentTouch();
}
- if (mNotificationPanelViewController.isPanelExpanded()
+ if (getShadeViewController().isPanelExpanded()
&& mStatusBarStateController.getState() == StatusBarState.SHADE) {
animateCollapseShade();
}
@@ -311,7 +312,7 @@
@Override
public void instantCollapseShade() {
- mNotificationPanelViewController.instantCollapse();
+ getShadeViewController().instantCollapse();
runPostCollapseRunnables();
}
@@ -342,7 +343,7 @@
}
// Ensure the panel is fully collapsed (just in case; bug 6765842, 7260868)
- mNotificationPanelViewController.collapse(false, false, 1.0f);
+ getShadeViewController().collapse(false, false, 1.0f);
mExpandedVisible = false;
notifyVisibilityChanged(false);
@@ -364,7 +365,7 @@
notifyExpandedVisibleChanged(false);
mCommandQueue.recomputeDisableFlags(
mDisplayId,
- mNotificationPanelViewController.shouldHideStatusBarIconsWhenExpanded());
+ getShadeViewController().shouldHideStatusBarIconsWhenExpanded());
// Trimming will happen later if Keyguard is showing - doing it here might cause a jank in
// the bouncer appear animation.
@@ -406,11 +407,14 @@
return mNotificationShadeWindowViewController.getView();
}
+ private ShadeViewController getShadeViewController() {
+ return mShadeViewControllerLazy.get();
+ }
+
@Override
- public void setShadeViewController(ShadeViewController shadeViewController) {
- mNotificationPanelViewController = shadeViewController;
- mNotificationPanelViewController.setTrackingStartedListener(this::runPostCollapseRunnables);
- mNotificationPanelViewController.setOpenCloseListener(
+ public void start() {
+ getShadeViewController().setTrackingStartedListener(this::runPostCollapseRunnables);
+ getShadeViewController().setOpenCloseListener(
new OpenCloseListener() {
@Override
public void onClosingFinished() {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
index 8789a8b..8b89ff4 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
@@ -122,6 +122,8 @@
}
}
+ var shadeCollapseAction: Runnable? = null
+
private lateinit var iconManager: StatusBarIconController.TintedIconManager
private lateinit var carrierIconSlots: List<String>
private lateinit var mShadeCarrierGroupController: ShadeCarrierGroupController
@@ -131,6 +133,7 @@
private val date: TextView = header.findViewById(R.id.date)
private val iconContainer: StatusIconContainer = header.findViewById(R.id.statusIcons)
private val mShadeCarrierGroup: ShadeCarrierGroup = header.findViewById(R.id.carrier_group)
+ private val systemIcons: View = header.findViewById(R.id.shade_header_system_icons)
private var roundedCorners = 0
private var cutout: DisplayCutout? = null
@@ -254,6 +257,14 @@
header.paddingRight,
header.paddingBottom
)
+ systemIcons.setPaddingRelative(
+ resources.getDimensionPixelSize(
+ R.dimen.shade_header_system_icons_padding_start
+ ),
+ systemIcons.paddingTop,
+ resources.getDimensionPixelSize(R.dimen.shade_header_system_icons_padding_end),
+ systemIcons.paddingBottom
+ )
}
override fun onDensityOrFontScaleChanged() {
@@ -266,6 +277,7 @@
lastInsets?.let { updateConstraintsForInsets(header, it) }
updateResources()
updateCarrierGroupPadding()
+ clock.onDensityOrFontScaleChanged()
}
}
@@ -459,9 +471,11 @@
if (largeScreenActive) {
logInstantEvent("Large screen constraints set")
header.setTransition(LARGE_SCREEN_HEADER_TRANSITION_ID)
+ systemIcons.setOnClickListener { shadeCollapseAction?.run() }
} else {
logInstantEvent("Small screen constraints set")
header.setTransition(HEADER_TRANSITION_ID)
+ systemIcons.setOnClickListener(null)
}
header.jumpToState(header.startState)
updatePosition()
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
index 2c560c9..be21df1 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
@@ -44,7 +44,9 @@
import com.android.systemui.statusbar.notification.row.dagger.NotificationShelfComponent
import com.android.systemui.statusbar.notification.shelf.ui.viewbinder.NotificationShelfViewBinderWrapperControllerImpl
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
+import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
import com.android.systemui.statusbar.phone.KeyguardBottomAreaView
+import com.android.systemui.statusbar.phone.StatusBarLocation
import com.android.systemui.statusbar.phone.StatusIconContainer
import com.android.systemui.statusbar.phone.TapAgainView
import com.android.systemui.statusbar.policy.BatteryController
@@ -59,7 +61,7 @@
import javax.inject.Provider
/** Module for classes related to the notification shade. */
-@Module
+@Module(includes = [StartShadeModule::class])
abstract class ShadeModule {
@Binds
@@ -184,6 +186,14 @@
return notificationShadeWindowView.findViewById(R.id.keyguard_root_view)
}
+ @Provides
+ @SysUISingleton
+ fun providesSharedNotificationContainer(
+ notificationShadeWindowView: NotificationShadeWindowView,
+ ): SharedNotificationContainer {
+ return notificationShadeWindowView.findViewById(R.id.shared_notification_container)
+ }
+
// TODO(b/277762009): Only allow this view's controller to inject the view. See above.
@Provides
@SysUISingleton
@@ -263,17 +273,16 @@
tunerService: TunerService,
@Main mainHandler: Handler,
contentResolver: ContentResolver,
- featureFlags: FeatureFlags,
batteryController: BatteryController,
): BatteryMeterViewController {
return BatteryMeterViewController(
batteryMeterView,
+ StatusBarLocation.QS,
userTracker,
configurationController,
tunerService,
mainHandler,
contentResolver,
- featureFlags,
batteryController,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt
index 8d5c30b..2532bad 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt
@@ -18,6 +18,7 @@
import android.view.ViewPropertyAnimator
import com.android.systemui.statusbar.GestureRecorder
import com.android.systemui.statusbar.NotificationShelfController
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
import com.android.systemui.statusbar.phone.CentralSurfaces
import com.android.systemui.statusbar.phone.HeadsUpManagerPhone
@@ -63,6 +64,9 @@
/** Animates the view from its current alpha to zero then runs the runnable. */
fun fadeOut(startDelayMs: Long, durationMs: Long, endAction: Runnable): ViewPropertyAnimator
+ /** Returns the NSSL controller. */
+ val notificationStackScrollLayoutController: NotificationStackScrollLayoutController
+
/** Set whether the bouncer is showing. */
fun setBouncerShowing(bouncerShowing: Boolean)
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
index 9aa5eb0..d5b5c87 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
@@ -19,9 +19,7 @@
import android.view.ViewGroup
import android.view.ViewTreeObserver
import com.android.systemui.keyguard.shared.model.WakefulnessModel
-import com.android.systemui.statusbar.RemoteInputController
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
-import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
import com.android.systemui.statusbar.phone.HeadsUpAppearanceController
import com.android.systemui.statusbar.phone.KeyguardStatusBarView
import com.android.systemui.statusbar.phone.KeyguardStatusBarViewController
@@ -141,9 +139,6 @@
/** Returns the StatusBarState. */
val barState: Int
- /** Returns the NSSL controller. */
- val notificationStackScrollLayoutController: NotificationStackScrollLayoutController
-
/** Sets the amount of progress in the status bar launch animation. */
fun applyLaunchAnimationProgress(linearProgress: Float)
@@ -261,9 +256,6 @@
/** Returns the ShadeFoldAnimator. */
val shadeFoldAnimator: ShadeFoldAnimator
- /** Returns the ShadeNotificationPresenter. */
- val shadeNotificationPresenter: ShadeNotificationPresenter
-
companion object {
/**
* Returns a multiplicative factor to use when determining the falsing threshold for touches
@@ -325,16 +317,7 @@
fun cancelFoldToAodAnimation()
/** Returns the main view of the shade. */
- val view: ViewGroup
-}
-
-/** Handles the shade's interactions with StatusBarNotificationPresenter. */
-interface ShadeNotificationPresenter {
- /** Returns a new delegate for some view controller pieces of the remote input process. */
- fun createRemoteInputDelegate(): RemoteInputController.Delegate
-
- /** Returns whether the screen has temporarily woken up to display notifications. */
- fun hasPulsingNotifications(): Boolean
+ val view: ViewGroup?
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt
new file mode 100644
index 0000000..287ac52
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt
@@ -0,0 +1,111 @@
+/*
+ * 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.shade
+
+import android.view.MotionEvent
+import android.view.ViewGroup
+import android.view.ViewTreeObserver
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
+import com.android.systemui.statusbar.phone.HeadsUpAppearanceController
+import java.util.function.Consumer
+import javax.inject.Inject
+
+/** Empty implementation of ShadeViewController for variants with no shade. */
+class ShadeViewControllerEmptyImpl @Inject constructor() : ShadeViewController {
+ override fun expand(animate: Boolean) {}
+ override fun expandToQs() {}
+ override fun expandToNotifications() {}
+ override val isExpandingOrCollapsing: Boolean = false
+ override val isExpanded: Boolean = false
+ override val isPanelExpanded: Boolean = false
+ override val isShadeFullyExpanded: Boolean = false
+ override fun collapse(delayed: Boolean, speedUpFactor: Float) {}
+ override fun collapse(animate: Boolean, delayed: Boolean, speedUpFactor: Float) {}
+ override fun collapseWithDuration(animationDuration: Int) {}
+ override fun instantCollapse() {}
+ override fun animateCollapseQs(fullyCollapse: Boolean) {}
+ override fun canBeCollapsed(): Boolean = false
+ override val isCollapsing: Boolean = false
+ override val isFullyCollapsed: Boolean = false
+ override val isTracking: Boolean = false
+ override val isViewEnabled: Boolean = false
+ override fun setOpenCloseListener(openCloseListener: OpenCloseListener) {}
+ override fun shouldHideStatusBarIconsWhenExpanded() = false
+ override fun blockExpansionForCurrentTouch() {}
+ override fun setTrackingStartedListener(trackingStartedListener: TrackingStartedListener) {}
+ override fun disableHeader(state1: Int, state2: Int, animated: Boolean) {}
+ override fun startExpandLatencyTracking() {}
+ override fun startBouncerPreHideAnimation() {}
+ override fun dozeTimeTick() {}
+ override fun resetViews(animate: Boolean) {}
+ override val barState: Int = 0
+ override fun applyLaunchAnimationProgress(linearProgress: Float) {}
+ override fun closeUserSwitcherIfOpen(): Boolean {
+ return false
+ }
+ override fun onBackPressed() {}
+ override fun setIsLaunchAnimationRunning(running: Boolean) {}
+ override fun setAlpha(alpha: Int, animate: Boolean) {}
+ override fun setAlphaChangeAnimationEndAction(r: Runnable) {}
+ override fun setPulsing(pulsing: Boolean) {}
+ override fun setQsScrimEnabled(qsScrimEnabled: Boolean) {}
+ override fun setAmbientIndicationTop(ambientIndicationTop: Int, ambientTextVisible: Boolean) {}
+ override fun updateSystemUiStateFlags() {}
+ override fun updateTouchableRegion() {}
+ override fun addOnGlobalLayoutListener(listener: ViewTreeObserver.OnGlobalLayoutListener) {}
+ override fun removeOnGlobalLayoutListener(listener: ViewTreeObserver.OnGlobalLayoutListener) {}
+ override fun postToView(action: Runnable): Boolean {
+ return false
+ }
+ override fun transitionToExpandedShade(delay: Long) {}
+ override val isUnlockHintRunning: Boolean = false
+
+ override fun resetViewGroupFade() {}
+ override fun setKeyguardTransitionProgress(keyguardAlpha: Float, keyguardTranslationY: Int) {}
+ override fun setOverStretchAmount(amount: Float) {}
+ override fun setKeyguardStatusBarAlpha(alpha: Float) {}
+ override fun showAodUi() {}
+ override fun isFullyExpanded(): Boolean {
+ return false
+ }
+ override fun handleExternalTouch(event: MotionEvent): Boolean {
+ return false
+ }
+ override fun startTrackingExpansionFromStatusBar() {}
+ override val shadeHeadsUpTracker = ShadeHeadsUpTrackerEmptyImpl()
+ override val shadeFoldAnimator = ShadeFoldAnimatorEmptyImpl()
+}
+
+class ShadeHeadsUpTrackerEmptyImpl : ShadeHeadsUpTracker {
+ override fun addTrackingHeadsUpListener(listener: Consumer<ExpandableNotificationRow>) {}
+ override fun removeTrackingHeadsUpListener(listener: Consumer<ExpandableNotificationRow>) {}
+ override fun setHeadsUpAppearanceController(
+ headsUpAppearanceController: HeadsUpAppearanceController?
+ ) {}
+ override val trackedHeadsUpNotification: ExpandableNotificationRow? = null
+}
+
+class ShadeFoldAnimatorEmptyImpl : ShadeFoldAnimator {
+ override fun prepareFoldToAodAnimation() {}
+ override fun startFoldToAodAnimation(
+ startAction: Runnable,
+ endAction: Runnable,
+ cancelAction: Runnable,
+ ) {}
+ override fun cancelFoldToAodAnimation() {}
+ override val view: ViewGroup? = null
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeWindowLogger.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeWindowLogger.kt
index 51a27cf..e7a397b 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeWindowLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeWindowLogger.kt
@@ -16,14 +16,13 @@
package com.android.systemui.shade
-import android.view.WindowManager
-import com.android.systemui.log.dagger.ShadeWindowLog
import com.android.systemui.log.ConstantStringsLogger
import com.android.systemui.log.ConstantStringsLoggerImpl
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.LogLevel
import com.android.systemui.log.core.LogLevel.DEBUG
import com.android.systemui.log.core.LogMessage
+import com.android.systemui.log.dagger.ShadeWindowLog
import javax.inject.Inject
private const val TAG = "systemui.shadewindow"
@@ -31,15 +30,6 @@
class ShadeWindowLogger @Inject constructor(@ShadeWindowLog private val buffer: LogBuffer) :
ConstantStringsLogger by ConstantStringsLoggerImpl(buffer, TAG) {
- fun logApplyingWindowLayoutParams(lp: WindowManager.LayoutParams) {
- buffer.log(
- TAG,
- DEBUG,
- { str1 = lp.toString() },
- { "Applying new window layout params: $str1" }
- )
- }
-
fun logNewState(state: Any) {
buffer.log(
TAG,
diff --git a/services/core/java/com/android/server/biometrics/BiometricSensorPrivacy.java b/packages/SystemUI/src/com/android/systemui/shade/StartShadeModule.kt
similarity index 61%
copy from services/core/java/com/android/server/biometrics/BiometricSensorPrivacy.java
copy to packages/SystemUI/src/com/android/systemui/shade/StartShadeModule.kt
index 6727fbc..c50693c 100644
--- a/services/core/java/com/android/server/biometrics/BiometricSensorPrivacy.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/StartShadeModule.kt
@@ -14,12 +14,18 @@
* limitations under the License.
*/
-package com.android.server.biometrics;
+package com.android.systemui.shade
-/**
- * Interface for biometric operations to get camera privacy state.
- */
-public interface BiometricSensorPrivacy {
- /* Returns true if privacy is enabled and camera access is disabled. */
- boolean isCameraPrivacyEnabled();
+import com.android.systemui.CoreStartable
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+
+@Module
+internal abstract class StartShadeModule {
+ @Binds
+ @IntoMap
+ @ClassKey(ShadeController::class)
+ abstract fun bind(shadeController: ShadeController): CoreStartable
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBarFrameLayout.kt b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBarFrameLayout.kt
index ce730ba..5d06f8d0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBarFrameLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBarFrameLayout.kt
@@ -21,8 +21,8 @@
/**
* A temporary base class that's shared between our old status bar connectivity view implementations
- * ([StatusBarWifiView], [StatusBarMobileView]) and our new status bar implementations (
- * [ModernStatusBarWifiView], [ModernStatusBarMobileView]).
+ * ([StatusBarMobileView]) and our new status bar implementations ([ModernStatusBarWifiView],
+ * [ModernStatusBarMobileView]).
*
* Once our refactor is over, we should be able to delete this go-between class and the old view
* class.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index a532195..92df78b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -73,7 +73,6 @@
import com.android.systemui.statusbar.CommandQueue.Callbacks;
import com.android.systemui.statusbar.commandline.CommandRegistry;
import com.android.systemui.statusbar.policy.CallbackController;
-import com.android.systemui.tracing.ProtoTracer;
import java.io.FileDescriptor;
import java.io.FileOutputStream;
@@ -190,7 +189,6 @@
*/
private int mLastUpdatedImeDisplayId = INVALID_DISPLAY;
private final DisplayTracker mDisplayTracker;
- private ProtoTracer mProtoTracer;
private final @Nullable CommandRegistry mRegistry;
private final @Nullable DumpHandler mDumpHandler;
@@ -504,18 +502,16 @@
@VisibleForTesting
public CommandQueue(Context context, DisplayTracker displayTracker) {
- this(context, displayTracker, null, null, null);
+ this(context, displayTracker, null, null);
}
public CommandQueue(
Context context,
DisplayTracker displayTracker,
- ProtoTracer protoTracer,
CommandRegistry registry,
DumpHandler dumpHandler
) {
mDisplayTracker = displayTracker;
- mProtoTracer = protoTracer;
mRegistry = registry;
mDumpHandler = dumpHandler;
mDisplayTracker.addDisplayChangeCallback(new DisplayTracker.Callback() {
@@ -1160,9 +1156,6 @@
@Override
public void startTracing() {
synchronized (mLock) {
- if (mProtoTracer != null) {
- mProtoTracer.start();
- }
mHandler.obtainMessage(MSG_TRACING_STATE_CHANGED, true).sendToTarget();
}
}
@@ -1170,9 +1163,6 @@
@Override
public void stopTracing() {
synchronized (mLock) {
- if (mProtoTracer != null) {
- mProtoTracer.stop();
- }
mHandler.obtainMessage(MSG_TRACING_STATE_CHANGED, false).sendToTarget();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java
index 47a4641..5ac542b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java
@@ -112,9 +112,6 @@
/** Sets the state of whether heads up is showing or not. */
default void setHeadsUpShowing(boolean showing) {}
- /** Sets whether the wallpaper supports ambient mode or not. */
- default void setWallpaperSupportsAmbientMode(boolean supportsAmbientMode) {}
-
/** Gets whether the wallpaper is showing or not. */
default boolean isShowingWallpaper() {
return false;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
index 91c08a0..fbbee53 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
@@ -26,6 +26,7 @@
import android.app.ActivityManager;
import android.app.Notification;
import android.content.Context;
+import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.res.ColorStateList;
import android.content.res.Configuration;
@@ -41,18 +42,19 @@
import android.os.UserHandle;
import android.service.notification.StatusBarNotification;
import android.text.TextUtils;
-import android.util.AttributeSet;
import android.util.FloatProperty;
import android.util.Log;
import android.util.Property;
import android.util.TypedValue;
import android.view.ViewDebug;
+import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
import android.view.animation.Interpolator;
import androidx.core.graphics.ColorUtils;
import com.android.app.animation.Interpolators;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.statusbar.StatusBarIcon;
import com.android.internal.util.ContrastColorUtil;
import com.android.systemui.R;
@@ -131,10 +133,12 @@
}
};
- private boolean mAlwaysScaleIcon;
private int mStatusBarIconDrawingSizeIncreased = 1;
- private int mStatusBarIconDrawingSize = 1;
- private int mStatusBarIconSize = 1;
+ @VisibleForTesting int mStatusBarIconDrawingSize = 1;
+
+ @VisibleForTesting int mOriginalStatusBarIconSize = 1;
+ @VisibleForTesting int mNewStatusBarIconSize = 1;
+ @VisibleForTesting float mScaleToFitNewIconSize = 1;
private StatusBarIcon mIcon;
@ViewDebug.ExportedProperty private String mSlot;
private Drawable mNumberBackground;
@@ -144,7 +148,7 @@
private String mNumberText;
private StatusBarNotification mNotification;
private final boolean mBlocked;
- private int mDensity;
+ private Configuration mConfiguration;
private boolean mNightMode;
private float mIconScale = 1.0f;
private final Paint mDotPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
@@ -156,7 +160,6 @@
private ObjectAnimator mIconAppearAnimator;
private ObjectAnimator mDotAnimator;
private float mDotAppearAmount;
- private OnVisibilityChangedListener mOnVisibilityChangedListener;
private int mDrawableColor;
private int mIconColor;
private int mDecorColor;
@@ -175,7 +178,6 @@
private int mCachedContrastBackgroundColor = NO_COLOR;
private float[] mMatrix;
private ColorMatrixColorFilter mMatrixColorFilter;
- private boolean mIsInShelf;
private Runnable mLayoutRunnable;
private boolean mDismissed;
private Runnable mOnDismissListener;
@@ -198,30 +200,20 @@
mNumberPain.setAntiAlias(true);
setNotification(sbn);
setScaleType(ScaleType.CENTER);
- mDensity = context.getResources().getDisplayMetrics().densityDpi;
- Configuration configuration = context.getResources().getConfiguration();
- mNightMode = (configuration.uiMode & Configuration.UI_MODE_NIGHT_MASK)
+ mConfiguration = new Configuration(context.getResources().getConfiguration());
+ mNightMode = (mConfiguration.uiMode & Configuration.UI_MODE_NIGHT_MASK)
== Configuration.UI_MODE_NIGHT_YES;
initializeDecorColor();
reloadDimens();
maybeUpdateIconScaleDimens();
}
- public StatusBarIconView(Context context, AttributeSet attrs) {
- super(context, attrs);
- mDozer = new NotificationIconDozeHelper(context);
- mBlocked = false;
- mAlwaysScaleIcon = true;
- reloadDimens();
- maybeUpdateIconScaleDimens();
- mDensity = context.getResources().getDisplayMetrics().densityDpi;
- }
-
/** Should always be preceded by {@link #reloadDimens()} */
- private void maybeUpdateIconScaleDimens() {
+ @VisibleForTesting
+ public void maybeUpdateIconScaleDimens() {
// We do not resize and scale system icons (on the right), only notification icons (on the
// left).
- if (mNotification != null || mAlwaysScaleIcon) {
+ if (isNotification()) {
updateIconScaleForNotifications();
} else {
updateIconScaleForSystemIcons();
@@ -229,22 +221,63 @@
}
private void updateIconScaleForNotifications() {
+ float iconScale;
+ // we need to scale the image size to be same as the original size
+ // (fit mOriginalStatusBarIconSize), then we can scale it with mScaleToFitNewIconSize
+ // to fit mNewStatusBarIconSize
+ float scaleToOriginalDrawingSize = 1.0f;
+ ViewGroup.LayoutParams lp = getLayoutParams();
+ if (getDrawable() != null && (lp != null && lp.width > 0 && lp.height > 0)) {
+ final int iconViewWidth = lp.width;
+ final int iconViewHeight = lp.height;
+ // first we estimate the image exact size when put the drawable in scaled iconView size,
+ // then we can compute the scaleToOriginalDrawingSize to make the image size fit in
+ // mOriginalStatusBarIconSize
+ final int drawableWidth = getDrawable().getIntrinsicWidth();
+ final int drawableHeight = getDrawable().getIntrinsicHeight();
+ float scaleToFitIconView = Math.min(
+ (float) iconViewWidth / drawableWidth,
+ (float) iconViewHeight / drawableHeight);
+ // if the drawable size <= the icon view size, the drawable won't be scaled
+ if (scaleToFitIconView > 1.0f) {
+ scaleToFitIconView = 1.0f;
+ }
+ final float scaledImageWidth = drawableWidth * scaleToFitIconView;
+ final float scaledImageHeight = drawableHeight * scaleToFitIconView;
+ // if the scaled image size <= mOriginalStatusBarIconSize, we don't need to enlarge it
+ scaleToOriginalDrawingSize = Math.min(
+ (float) mOriginalStatusBarIconSize / scaledImageWidth,
+ (float) mOriginalStatusBarIconSize / scaledImageHeight);
+ if (scaleToOriginalDrawingSize > 1.0f) {
+ scaleToOriginalDrawingSize = 1.0f;
+ }
+ }
+ iconScale = scaleToOriginalDrawingSize;
+
final float imageBounds = mIncreasedSize ?
mStatusBarIconDrawingSizeIncreased : mStatusBarIconDrawingSize;
- final int outerBounds = mStatusBarIconSize;
- mIconScale = imageBounds / (float)outerBounds;
+ final int originalOuterBounds = mOriginalStatusBarIconSize;
+ iconScale = iconScale * (imageBounds / (float) originalOuterBounds);
+
+ // scale image to fit new icon size
+ mIconScale = iconScale * mScaleToFitNewIconSize;
+
updatePivot();
}
// Makes sure that all icons are scaled to the same height (15dp). If we cannot get a height
// for the icon, it uses the default SCALE (15f / 17f) which is the old behavior
private void updateIconScaleForSystemIcons() {
+ float iconScale;
float iconHeight = getIconHeight();
if (iconHeight != 0) {
- mIconScale = mSystemIconDesiredHeight / iconHeight;
+ iconScale = mSystemIconDesiredHeight / iconHeight;
} else {
- mIconScale = mSystemIconDefaultScale;
+ iconScale = mSystemIconDefaultScale;
}
+
+ // scale image to fit new icon size
+ mIconScale = iconScale * mScaleToFitNewIconSize;
}
private float getIconHeight() {
@@ -267,12 +300,10 @@
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
- int density = newConfig.densityDpi;
- if (density != mDensity) {
- mDensity = density;
- reloadDimens();
- updateDrawable();
- maybeUpdateIconScaleDimens();
+ final int configDiff = newConfig.diff(mConfiguration);
+ mConfiguration.setTo(newConfig);
+ if ((configDiff & (ActivityInfo.CONFIG_DENSITY | ActivityInfo.CONFIG_FONT_SCALE)) != 0) {
+ updateIconDimens();
}
boolean nightMode = (newConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK)
== Configuration.UI_MODE_NIGHT_YES;
@@ -282,11 +313,22 @@
}
}
+ /**
+ * Update the icon dimens and drawable with current resources
+ */
+ public void updateIconDimens() {
+ reloadDimens();
+ updateDrawable();
+ maybeUpdateIconScaleDimens();
+ }
+
private void reloadDimens() {
boolean applyRadius = mDotRadius == mStaticDotRadius;
Resources res = getResources();
mStaticDotRadius = res.getDimensionPixelSize(R.dimen.overflow_dot_radius);
- mStatusBarIconSize = res.getDimensionPixelSize(R.dimen.status_bar_icon_size);
+ mOriginalStatusBarIconSize = res.getDimensionPixelSize(R.dimen.status_bar_icon_size);
+ mNewStatusBarIconSize = res.getDimensionPixelSize(R.dimen.status_bar_icon_size_sp);
+ mScaleToFitNewIconSize = (float) mNewStatusBarIconSize / mOriginalStatusBarIconSize;
mStatusBarIconDrawingSizeIncreased =
res.getDimensionPixelSize(R.dimen.status_bar_icon_drawing_size_dark);
mStatusBarIconDrawingSize =
@@ -309,17 +351,8 @@
maybeUpdateIconScaleDimens();
}
- private static boolean streq(String a, String b) {
- if (a == b) {
- return true;
- }
- if (a == null && b != null) {
- return false;
- }
- if (a != null && b == null) {
- return false;
- }
- return a.equals(b);
+ private boolean isNotification() {
+ return mNotification != null;
}
public boolean equalIcons(Icon a, Icon b) {
@@ -416,7 +449,7 @@
Drawable getIcon(StatusBarIcon icon) {
Context notifContext = getContext();
- if (mNotification != null) {
+ if (isNotification()) {
notifContext = mNotification.getPackageContext(getContext());
}
return getIcon(getContext(), notifContext != null ? notifContext : getContext(), icon);
@@ -471,7 +504,7 @@
@Override
public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
super.onInitializeAccessibilityEvent(event);
- if (mNotification != null) {
+ if (isNotification()) {
event.setParcelableData(mNotification.getNotification());
}
}
@@ -491,11 +524,29 @@
}
@Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+ if (!isNotification()) {
+ // for system icons, calculated measured width from super is for image drawable real
+ // width (17dp). We may scale the image with font scale, so we also need to scale the
+ // measured width so that scaled measured width and image width would be fit.
+ int measuredWidth = getMeasuredWidth();
+ int measuredHeight = getMeasuredHeight();
+ setMeasuredDimension((int) (measuredWidth * mScaleToFitNewIconSize), measuredHeight);
+ }
+ }
+
+ @Override
protected void onDraw(Canvas canvas) {
+ // In this method, for width/height division computation we intend to discard the
+ // fractional part as the original behavior.
if (mIconAppearAmount > 0.0f) {
canvas.save();
+ int px = getWidth() / 2;
+ int py = getHeight() / 2;
canvas.scale(mIconScale * mIconAppearAmount, mIconScale * mIconAppearAmount,
- getWidth() / 2, getHeight() / 2);
+ (float) px, (float) py);
super.onDraw(canvas);
canvas.restore();
}
@@ -512,10 +563,15 @@
} else {
float fadeOutAmount = mDotAppearAmount - 1.0f;
alpha = alpha * (1.0f - fadeOutAmount);
- radius = NotificationUtils.interpolate(mDotRadius, getWidth() / 4, fadeOutAmount);
+ int end = getWidth() / 4;
+ radius = NotificationUtils.interpolate(mDotRadius, (float) end, fadeOutAmount);
}
mDotPaint.setAlpha((int) (alpha * 255));
- canvas.drawCircle(mStatusBarIconSize / 2, getHeight() / 2, radius, mDotPaint);
+ int cx = mNewStatusBarIconSize / 2;
+ int cy = getHeight() / 2;
+ canvas.drawCircle(
+ (float) cx, (float) cy,
+ radius, mDotPaint);
}
}
@@ -624,7 +680,7 @@
}
private void initializeDecorColor() {
- if (mNotification != null) {
+ if (isNotification()) {
setDecorColor(getContext().getColor(mNightMode
? com.android.internal.R.color.notification_default_color_dark
: com.android.internal.R.color.notification_default_color_light));
@@ -837,7 +893,7 @@
if (targetAmount != currentAmount) {
mDotAnimator = ObjectAnimator.ofFloat(this, DOT_APPEAR_AMOUNT,
currentAmount, targetAmount);
- mDotAnimator.setInterpolator(interpolator);;
+ mDotAnimator.setInterpolator(interpolator);
mDotAnimator.setDuration(duration == 0 ? ANIMATION_DURATION_FAST
: duration);
final boolean runRunnable = !runnableAdded;
@@ -894,22 +950,10 @@
}
}
- @Override
- public void setVisibility(int visibility) {
- super.setVisibility(visibility);
- if (mOnVisibilityChangedListener != null) {
- mOnVisibilityChangedListener.onVisibilityChanged(visibility);
- }
- }
-
public float getDotAppearAmount() {
return mDotAppearAmount;
}
- public void setOnVisibilityChangedListener(OnVisibilityChangedListener listener) {
- mOnVisibilityChangedListener = listener;
- }
-
public void setDozing(boolean dozing, boolean fade, long delay) {
mDozer.setDozing(f -> {
mDozeAmount = f;
@@ -943,14 +987,6 @@
outRect.bottom += translationY;
}
- public void setIsInShelf(boolean isInShelf) {
- mIsInShelf = isInShelf;
- }
-
- public boolean isInShelf() {
- return mIsInShelf;
- }
-
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
@@ -1032,8 +1068,4 @@
public boolean showsConversation() {
return mShowsConversation;
}
-
- public interface OnVisibilityChangedListener {
- void onVisibilityChanged(int newVisibility);
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java
index fdad101..d6f6c2c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java
@@ -135,7 +135,7 @@
mDotView = new StatusBarIconView(mContext, mSlot, null);
mDotView.setVisibleState(STATE_DOT);
- int width = mContext.getResources().getDimensionPixelSize(R.dimen.status_bar_icon_size);
+ int width = mContext.getResources().getDimensionPixelSize(R.dimen.status_bar_icon_size_sp);
LayoutParams lp = new LayoutParams(width, width);
lp.gravity = Gravity.CENTER_VERTICAL | Gravity.START;
addView(mDotView, lp);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarWifiView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarWifiView.java
deleted file mode 100644
index decc70d..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarWifiView.java
+++ /dev/null
@@ -1,249 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar;
-
-import static com.android.systemui.plugins.DarkIconDispatcher.getTint;
-import static com.android.systemui.statusbar.StatusBarIconView.STATE_DOT;
-import static com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN;
-import static com.android.systemui.statusbar.StatusBarIconView.STATE_ICON;
-
-import android.content.Context;
-import android.content.res.ColorStateList;
-import android.graphics.Rect;
-import android.util.AttributeSet;
-import android.view.Gravity;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-
-import com.android.systemui.R;
-import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
-import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
-
-import java.util.ArrayList;
-
-/**
- * Start small: StatusBarWifiView will be able to layout from a WifiIconState
- */
-public class StatusBarWifiView extends BaseStatusBarFrameLayout implements DarkReceiver {
- private static final String TAG = "StatusBarWifiView";
-
- /// Used to show etc dots
- private StatusBarIconView mDotView;
- /// Contains the main icon layout
- private LinearLayout mWifiGroup;
- private ImageView mWifiIcon;
- private ImageView mIn;
- private ImageView mOut;
- private View mInoutContainer;
- private View mSignalSpacer;
- private View mAirplaneSpacer;
- private WifiIconState mState;
- private String mSlot;
- @StatusBarIconView.VisibleState
- private int mVisibleState = STATE_HIDDEN;
-
- public static StatusBarWifiView fromContext(Context context, String slot) {
- LayoutInflater inflater = LayoutInflater.from(context);
- StatusBarWifiView v = (StatusBarWifiView) inflater.inflate(R.layout.status_bar_wifi_group, null);
- v.setSlot(slot);
- v.init();
- v.setVisibleState(STATE_ICON);
- return v;
- }
-
- public StatusBarWifiView(Context context) {
- super(context);
- }
-
- public StatusBarWifiView(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- public StatusBarWifiView(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- }
-
- public void setSlot(String slot) {
- mSlot = slot;
- }
-
- @Override
- public void setStaticDrawableColor(int color) {
- ColorStateList list = ColorStateList.valueOf(color);
- mWifiIcon.setImageTintList(list);
- mIn.setImageTintList(list);
- mOut.setImageTintList(list);
- mDotView.setDecorColor(color);
- }
-
- @Override
- public void setDecorColor(int color) {
- mDotView.setDecorColor(color);
- }
-
- @Override
- public String getSlot() {
- return mSlot;
- }
-
- @Override
- public boolean isIconVisible() {
- return mState != null && mState.visible;
- }
-
- @Override
- public void setVisibleState(@StatusBarIconView.VisibleState int state, boolean animate) {
- if (state == mVisibleState) {
- return;
- }
- mVisibleState = state;
-
- switch (state) {
- case STATE_ICON:
- mWifiGroup.setVisibility(View.VISIBLE);
- mDotView.setVisibility(View.GONE);
- break;
- case STATE_DOT:
- mWifiGroup.setVisibility(View.GONE);
- mDotView.setVisibility(View.VISIBLE);
- break;
- case STATE_HIDDEN:
- default:
- mWifiGroup.setVisibility(View.GONE);
- mDotView.setVisibility(View.GONE);
- break;
- }
- }
-
- @Override
- @StatusBarIconView.VisibleState
- public int getVisibleState() {
- return mVisibleState;
- }
-
- @Override
- public void getDrawingRect(Rect outRect) {
- super.getDrawingRect(outRect);
- float translationX = getTranslationX();
- float translationY = getTranslationY();
- outRect.left += translationX;
- outRect.right += translationX;
- outRect.top += translationY;
- outRect.bottom += translationY;
- }
-
- private void init() {
- mWifiGroup = findViewById(R.id.wifi_group);
- mWifiIcon = findViewById(R.id.wifi_signal);
- mIn = findViewById(R.id.wifi_in);
- mOut = findViewById(R.id.wifi_out);
- mSignalSpacer = findViewById(R.id.wifi_signal_spacer);
- mAirplaneSpacer = findViewById(R.id.wifi_airplane_spacer);
- mInoutContainer = findViewById(R.id.inout_container);
-
- initDotView();
- }
-
- private void initDotView() {
- mDotView = new StatusBarIconView(mContext, mSlot, null);
- mDotView.setVisibleState(STATE_DOT);
-
- int width = mContext.getResources().getDimensionPixelSize(R.dimen.status_bar_icon_size);
- LayoutParams lp = new LayoutParams(width, width);
- lp.gravity = Gravity.CENTER_VERTICAL | Gravity.START;
- addView(mDotView, lp);
- }
-
- public void applyWifiState(WifiIconState state) {
- boolean requestLayout = false;
-
- if (state == null) {
- requestLayout = getVisibility() != View.GONE;
- setVisibility(View.GONE);
- mState = null;
- } else if (mState == null) {
- requestLayout = true;
- mState = state.copy();
- initViewState();
- } else if (!mState.equals(state)) {
- requestLayout = updateState(state.copy());
- }
-
- if (requestLayout) {
- requestLayout();
- }
- }
-
- private boolean updateState(WifiIconState state) {
- setContentDescription(state.contentDescription);
- if (mState.resId != state.resId && state.resId >= 0) {
- mWifiIcon.setImageDrawable(mContext.getDrawable(state.resId));
- }
-
- mIn.setVisibility(state.activityIn ? View.VISIBLE : View.GONE);
- mOut.setVisibility(state.activityOut ? View.VISIBLE : View.GONE);
- mInoutContainer.setVisibility(
- (state.activityIn || state.activityOut) ? View.VISIBLE : View.GONE);
- mAirplaneSpacer.setVisibility(state.airplaneSpacerVisible ? View.VISIBLE : View.GONE);
- mSignalSpacer.setVisibility(state.signalSpacerVisible ? View.VISIBLE : View.GONE);
-
- boolean needsLayout = state.activityIn != mState.activityIn
- ||state.activityOut != mState.activityOut;
-
- if (mState.visible != state.visible) {
- needsLayout |= true;
- setVisibility(state.visible ? View.VISIBLE : View.GONE);
- }
-
- mState = state;
- return needsLayout;
- }
-
- private void initViewState() {
- setContentDescription(mState.contentDescription);
- if (mState.resId >= 0) {
- mWifiIcon.setImageDrawable(mContext.getDrawable(mState.resId));
- }
-
- mIn.setVisibility(mState.activityIn ? View.VISIBLE : View.GONE);
- mOut.setVisibility(mState.activityOut ? View.VISIBLE : View.GONE);
- mInoutContainer.setVisibility(
- (mState.activityIn || mState.activityOut) ? View.VISIBLE : View.GONE);
- mAirplaneSpacer.setVisibility(mState.airplaneSpacerVisible ? View.VISIBLE : View.GONE);
- mSignalSpacer.setVisibility(mState.signalSpacerVisible ? View.VISIBLE : View.GONE);
- setVisibility(mState.visible ? View.VISIBLE : View.GONE);
- }
-
- @Override
- public void onDarkChanged(ArrayList<Rect> areas, float darkIntensity, int tint) {
- int areaTint = getTint(areas, this, tint);
- ColorStateList color = ColorStateList.valueOf(areaTint);
- mWifiIcon.setImageTintList(color);
- mIn.setImageTintList(color);
- mOut.setImageTintList(color);
- mDotView.setDecorColor(areaTint);
- mDotView.setIconColor(areaTint, false);
- }
-
-
- @Override
- public String toString() {
- return "StatusBarWifiView(slot=" + mSlot + " state=" + mState + ")";
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
index 73f181b..9aa28c3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
@@ -18,10 +18,6 @@
import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
-import static android.net.wifi.WifiManager.TrafficStateCallback.DATA_ACTIVITY_IN;
-import static android.net.wifi.WifiManager.TrafficStateCallback.DATA_ACTIVITY_INOUT;
-import static android.net.wifi.WifiManager.TrafficStateCallback.DATA_ACTIVITY_NONE;
-import static android.net.wifi.WifiManager.TrafficStateCallback.DATA_ACTIVITY_OUT;
import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
import android.annotation.Nullable;
@@ -557,10 +553,6 @@
mBroadcastDispatcher.unregisterReceiver(this);
}
- public int getConnectedWifiLevel() {
- return mWifiSignalController.getState().level;
- }
-
@Override
public AccessPointController getAccessPointController() {
return mAccessPoints;
@@ -654,14 +646,6 @@
return mWifiSignalController.isCarrierMergedWifi(subId);
}
- boolean hasDefaultNetwork() {
- return !mNoDefaultNetwork;
- }
-
- boolean isNonCarrierWifiNetworkAvailable() {
- return !mNoNetworksAvailable;
- }
-
boolean isEthernetDefault() {
return mConnectedTransports.get(NetworkCapabilities.TRANSPORT_ETHERNET);
}
@@ -1242,15 +1226,12 @@
}
private boolean mDemoInetCondition;
- private WifiState mDemoWifiState;
@Override
public void onDemoModeStarted() {
if (DEBUG) Log.d(TAG, "Entering demo mode");
unregisterListeners();
mDemoInetCondition = mInetCondition;
- mDemoWifiState = mWifiSignalController.getState();
- mDemoWifiState.ssid = "DemoMode";
}
@Override
@@ -1300,41 +1281,6 @@
controller.updateConnectivity(connected, connected);
}
}
- String wifi = args.getString("wifi");
- if (wifi != null && !mStatusBarPipelineFlags.runNewWifiIconBackend()) {
- boolean show = wifi.equals("show");
- String level = args.getString("level");
- if (level != null) {
- mDemoWifiState.level = level.equals("null") ? -1
- : Math.min(Integer.parseInt(level), WifiIcons.WIFI_LEVEL_COUNT - 1);
- mDemoWifiState.connected = mDemoWifiState.level >= 0;
- }
- String activity = args.getString("activity");
- if (activity != null) {
- switch (activity) {
- case "inout":
- mWifiSignalController.setActivity(DATA_ACTIVITY_INOUT);
- break;
- case "in":
- mWifiSignalController.setActivity(DATA_ACTIVITY_IN);
- break;
- case "out":
- mWifiSignalController.setActivity(DATA_ACTIVITY_OUT);
- break;
- default:
- mWifiSignalController.setActivity(DATA_ACTIVITY_NONE);
- break;
- }
- } else {
- mWifiSignalController.setActivity(DATA_ACTIVITY_NONE);
- }
- String ssid = args.getString("ssid");
- if (ssid != null) {
- mDemoWifiState.ssid = ssid;
- }
- mDemoWifiState.enabled = show;
- mWifiSignalController.notifyListeners();
- }
String sims = args.getString("sims");
if (sims != null && !mStatusBarPipelineFlags.useNewMobileIcons()) {
int num = MathUtils.constrain(Integer.parseInt(sims), 1, 8);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
index 035fa04..e5ba3ce 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
@@ -78,7 +78,6 @@
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.RemoteInputUriController;
import com.android.systemui.statusbar.window.StatusBarWindowController;
-import com.android.systemui.tracing.ProtoTracer;
import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.time.SystemClock;
@@ -195,11 +194,10 @@
static CommandQueue provideCommandQueue(
Context context,
DisplayTracker displayTracker,
- ProtoTracer protoTracer,
CommandRegistry registry,
DumpHandler dumpHandler
) {
- return new CommandQueue(context, displayTracker, protoTracer, registry, dumpHandler);
+ return new CommandQueue(context, displayTracker, registry, dumpHandler);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt
index 776956a..5639000 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt
@@ -30,9 +30,11 @@
import androidx.core.animation.AnimatorListenerAdapter
import androidx.core.animation.AnimatorSet
import androidx.core.animation.ValueAnimator
+import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.R
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
+import com.android.systemui.statusbar.phone.StatusBarContentInsetsChangedListener
import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider
import com.android.systemui.statusbar.window.StatusBarWindowController
import com.android.systemui.util.animation.AnimationUtil.Companion.frames
@@ -46,7 +48,7 @@
private val context: Context,
private val statusBarWindowController: StatusBarWindowController,
private val contentInsetsProvider: StatusBarContentInsetsProvider,
- private val featureFlags: FeatureFlags
+ private val featureFlags: FeatureFlags,
) : SystemStatusAnimationCallback {
private lateinit var animationWindowView: FrameLayout
@@ -56,7 +58,8 @@
// Left for LTR, Right for RTL
private var animationDirection = LEFT
- private var chipBounds = Rect()
+
+ @VisibleForTesting var chipBounds = Rect()
private val chipWidth get() = chipBounds.width()
private val chipRight get() = chipBounds.right
private val chipLeft get() = chipBounds.left
@@ -69,7 +72,7 @@
private var animRect = Rect()
// TODO: move to dagger
- private var initialized = false
+ @VisibleForTesting var initialized = false
/**
* Give the chip controller a chance to inflate and configure the chip view before we start
@@ -98,23 +101,7 @@
View.MeasureSpec.makeMeasureSpec(
(animationWindowView.parent as View).height, AT_MOST))
- // decide which direction we're animating from, and then set some screen coordinates
- val contentRect = contentInsetsProvider.getStatusBarContentAreaForCurrentRotation()
- val chipTop = ((animationWindowView.parent as View).height - it.view.measuredHeight) / 2
- val chipBottom = chipTop + it.view.measuredHeight
- val chipRight: Int
- val chipLeft: Int
- when (animationDirection) {
- LEFT -> {
- chipRight = contentRect.right
- chipLeft = contentRect.right - it.chipWidth
- }
- else /* RIGHT */ -> {
- chipLeft = contentRect.left
- chipRight = contentRect.left + it.chipWidth
- }
- }
- chipBounds = Rect(chipLeft, chipTop, chipRight, chipBottom)
+ updateChipBounds(it, contentInsetsProvider.getStatusBarContentAreaForCurrentRotation())
}
}
@@ -253,16 +240,67 @@
return animSet
}
- private fun init() {
+ fun init() {
initialized = true
themedContext = ContextThemeWrapper(context, R.style.Theme_SystemUI_QuickSettings)
animationWindowView = LayoutInflater.from(themedContext)
.inflate(R.layout.system_event_animation_window, null) as FrameLayout
- val lp = FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)
- lp.gravity = Gravity.END or Gravity.CENTER_VERTICAL
+ // Matches status_bar.xml
+ val height = themedContext.resources.getDimensionPixelSize(R.dimen.status_bar_height)
+ val lp = FrameLayout.LayoutParams(MATCH_PARENT, height)
+ lp.gravity = Gravity.END or Gravity.TOP
statusBarWindowController.addViewToWindow(animationWindowView, lp)
animationWindowView.clipToPadding = false
animationWindowView.clipChildren = false
+
+ // Use contentInsetsProvider rather than configuration controller, since we only care
+ // about status bar dimens
+ contentInsetsProvider.addCallback(object : StatusBarContentInsetsChangedListener {
+ override fun onStatusBarContentInsetsChanged() {
+ val newContentArea = contentInsetsProvider
+ .getStatusBarContentAreaForCurrentRotation()
+ updateDimens(newContentArea)
+
+ // If we are currently animating, we have to re-solve for the chip bounds. If we're
+ // not animating then [prepareChipAnimation] will take care of it for us
+ currentAnimatedView?.let {
+ updateChipBounds(it, newContentArea)
+ }
+ }
+ })
+ }
+
+ private fun updateDimens(contentArea: Rect) {
+ val lp = animationWindowView.layoutParams as FrameLayout.LayoutParams
+ lp.height = contentArea.height()
+
+ animationWindowView.layoutParams = lp
+ }
+
+ /**
+ * Use the current status bar content area and the current chip's measured size to update
+ * the animation rect and chipBounds. This method can be called at any time and will update
+ * the current animation values properly during e.g. a rotation.
+ */
+ private fun updateChipBounds(chip: BackgroundAnimatableView, contentArea: Rect) {
+ // decide which direction we're animating from, and then set some screen coordinates
+ val chipTop = (contentArea.bottom - chip.view.measuredHeight) / 2
+ val chipBottom = chipTop + chip.view.measuredHeight
+ val chipRight: Int
+ val chipLeft: Int
+
+ when (animationDirection) {
+ LEFT -> {
+ chipRight = contentArea.right
+ chipLeft = contentArea.right - chip.chipWidth
+ }
+ else /* RIGHT */ -> {
+ chipLeft = contentArea.left
+ chipRight = contentArea.left + chip.chipWidth
+ }
+ }
+ chipBounds = Rect(chipLeft, chipTop, chipRight, chipBottom)
+ animRect.set(chipBounds)
}
private fun layoutParamsDefault(marginEnd: Int): FrameLayout.LayoutParams =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
index 5f28ecb..577ad20 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
@@ -16,27 +16,21 @@
package com.android.systemui.statusbar.notification
-import android.content.Context
import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.FlagResolver
import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import javax.inject.Inject
-class NotifPipelineFlags @Inject constructor(
- val context: Context,
- val featureFlags: FeatureFlags,
- val sysPropFlags: FlagResolver,
+class NotifPipelineFlags
+@Inject
+constructor(
+ private val featureFlags: FeatureFlags,
+ private val sysPropFlags: FlagResolver,
) {
fun isDevLoggingEnabled(): Boolean =
featureFlags.isEnabled(Flags.NOTIFICATION_PIPELINE_DEVELOPER_LOGGING)
fun allowDismissOngoing(): Boolean =
- sysPropFlags.isEnabled(NotificationFlags.ALLOW_DISMISS_ONGOING)
-
- fun isOtpRedactionEnabled(): Boolean =
- sysPropFlags.isEnabled(NotificationFlags.OTP_REDACTION)
-
- val isNoHunForOldWhenEnabled: Boolean
- get() = featureFlags.isEnabled(Flags.NO_HUN_FOR_OLD_WHEN)
+ sysPropFlags.isEnabled(NotificationFlags.ALLOW_DISMISS_ONGOING)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt
index 212f2c215..1cf9c1e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt
@@ -3,7 +3,10 @@
import android.util.FloatProperty
import android.view.View
import androidx.annotation.FloatRange
+import com.android.systemui.Dependency
import com.android.systemui.R
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.statusbar.notification.stack.AnimationProperties
import com.android.systemui.statusbar.notification.stack.StackStateAnimator
import kotlin.math.abs
@@ -20,6 +23,8 @@
/** Properties required for a Roundable */
val roundableState: RoundableState
+ val clipHeight: Int
+
/** Current top roundness */
@get:FloatRange(from = 0.0, to = 1.0)
@JvmDefault
@@ -40,12 +45,16 @@
/** Current top corner in pixel, based on [topRoundness] and [maxRadius] */
@JvmDefault
val topCornerRadius: Float
- get() = topRoundness * maxRadius
+ get() =
+ if (roundableState.newHeadsUpAnimFlagEnabled) roundableState.topCornerRadius
+ else topRoundness * maxRadius
/** Current bottom corner in pixel, based on [bottomRoundness] and [maxRadius] */
@JvmDefault
val bottomCornerRadius: Float
- get() = bottomRoundness * maxRadius
+ get() =
+ if (roundableState.newHeadsUpAnimFlagEnabled) roundableState.bottomCornerRadius
+ else bottomRoundness * maxRadius
/** Get and update the current radii */
@JvmDefault
@@ -320,14 +329,20 @@
* @param roundable Target of the radius animation
* @param maxRadius Max corner radius in pixels
*/
-class RoundableState(
+class RoundableState
+@JvmOverloads
+constructor(
internal val targetView: View,
private val roundable: Roundable,
maxRadius: Float,
+ private val featureFlags: FeatureFlags = Dependency.get(FeatureFlags::class.java)
) {
internal var maxRadius = maxRadius
private set
+ internal val newHeadsUpAnimFlagEnabled
+ get() = featureFlags.isEnabled(Flags.IMPROVED_HUN_ANIMATIONS)
+
/** Animatable for top roundness */
private val topAnimatable = topAnimatable(roundable)
@@ -344,6 +359,41 @@
internal var bottomRoundness = 0f
private set
+ internal val topCornerRadius: Float
+ get() {
+ val height = roundable.clipHeight
+ val topRadius = topRoundness * maxRadius
+ val bottomRadius = bottomRoundness * maxRadius
+
+ if (height == 0) {
+ return 0f
+ } else if (topRadius + bottomRadius > height) {
+ // The sum of top and bottom corner radii should be at max the clipped height
+ val overShoot = topRadius + bottomRadius - height
+ return topRadius - (overShoot * topRoundness / (topRoundness + bottomRoundness))
+ }
+
+ return topRadius
+ }
+
+ internal val bottomCornerRadius: Float
+ get() {
+ val height = roundable.clipHeight
+ val topRadius = topRoundness * maxRadius
+ val bottomRadius = bottomRoundness * maxRadius
+
+ if (height == 0) {
+ return 0f
+ } else if (topRadius + bottomRadius > height) {
+ // The sum of top and bottom corner radii should be at max the clipped height
+ val overShoot = topRadius + bottomRadius - height
+ return bottomRadius -
+ (overShoot * bottomRoundness / (topRoundness + bottomRoundness))
+ }
+
+ return bottomRadius
+ }
+
/** Last requested top roundness associated by [SourceType] */
internal val topRoundnessMap = mutableMapOf<SourceType, Float>()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DreamCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DreamCoordinator.kt
new file mode 100644
index 0000000..d268e35
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DreamCoordinator.kt
@@ -0,0 +1,91 @@
+/*
+ * 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.statusbar.notification.collection.coordinator
+
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.SysuiStatusBarStateController
+import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+
+/**
+ * Filter out notifications on the lockscreen if the lockscreen hosted dream is active. If the user
+ * stops dreaming, pulls the shade down or unlocks the device, then the notifications are unhidden.
+ */
+@CoordinatorScope
+class DreamCoordinator
+@Inject
+constructor(
+ private val statusBarStateController: SysuiStatusBarStateController,
+ @Application private val scope: CoroutineScope,
+ private val keyguardRepository: KeyguardRepository,
+) : Coordinator {
+ private var isOnKeyguard = false
+ private var isLockscreenHostedDream = false
+
+ override fun attach(pipeline: NotifPipeline) {
+ pipeline.addPreGroupFilter(filter)
+ statusBarStateController.addCallback(statusBarStateListener)
+ scope.launch { attachFilterOnDreamingStateChange() }
+ recordStatusBarState(statusBarStateController.state)
+ }
+
+ private val filter =
+ object : NotifFilter("LockscreenHostedDreamFilter") {
+ var isFiltering = false
+ override fun shouldFilterOut(entry: NotificationEntry, now: Long): Boolean {
+ return isFiltering
+ }
+ inline fun update(msg: () -> String) {
+ val wasFiltering = isFiltering
+ isFiltering = isLockscreenHostedDream && isOnKeyguard
+ if (wasFiltering != isFiltering) {
+ invalidateList(msg())
+ }
+ }
+ }
+
+ private val statusBarStateListener =
+ object : StatusBarStateController.StateListener {
+ override fun onStateChanged(newState: Int) {
+ recordStatusBarState(newState)
+ }
+ }
+
+ private suspend fun attachFilterOnDreamingStateChange() {
+ keyguardRepository.isActiveDreamLockscreenHosted.collect { isDreaming ->
+ recordDreamingState(isDreaming)
+ }
+ }
+
+ private fun recordStatusBarState(newState: Int) {
+ isOnKeyguard = newState == StatusBarState.KEYGUARD
+ filter.update { "recordStatusBarState: " + StatusBarState.toString(newState) }
+ }
+
+ private fun recordDreamingState(isDreaming: Boolean) {
+ isLockscreenHostedDream = isDreaming
+ filter.update { "recordLockscreenHostedDreamState: $isDreaming" }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
index e5953cf..0ccab9e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
@@ -15,6 +15,8 @@
*/
package com.android.systemui.statusbar.notification.collection.coordinator
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED
import com.android.systemui.statusbar.notification.collection.NotifPipeline
import com.android.systemui.statusbar.notification.collection.PipelineDumpable
import com.android.systemui.statusbar.notification.collection.PipelineDumper
@@ -31,32 +33,34 @@
@CoordinatorScope
class NotifCoordinatorsImpl @Inject constructor(
- sectionStyleProvider: SectionStyleProvider,
- dataStoreCoordinator: DataStoreCoordinator,
- hideLocallyDismissedNotifsCoordinator: HideLocallyDismissedNotifsCoordinator,
- hideNotifsForOtherUsersCoordinator: HideNotifsForOtherUsersCoordinator,
- keyguardCoordinator: KeyguardCoordinator,
- rankingCoordinator: RankingCoordinator,
- appOpsCoordinator: AppOpsCoordinator,
- deviceProvisionedCoordinator: DeviceProvisionedCoordinator,
- bubbleCoordinator: BubbleCoordinator,
- headsUpCoordinator: HeadsUpCoordinator,
- gutsCoordinator: GutsCoordinator,
- conversationCoordinator: ConversationCoordinator,
- debugModeCoordinator: DebugModeCoordinator,
- groupCountCoordinator: GroupCountCoordinator,
- groupWhenCoordinator: GroupWhenCoordinator,
- mediaCoordinator: MediaCoordinator,
- preparationCoordinator: PreparationCoordinator,
- remoteInputCoordinator: RemoteInputCoordinator,
- rowAppearanceCoordinator: RowAppearanceCoordinator,
- stackCoordinator: StackCoordinator,
- shadeEventCoordinator: ShadeEventCoordinator,
- smartspaceDedupingCoordinator: SmartspaceDedupingCoordinator,
- viewConfigCoordinator: ViewConfigCoordinator,
- visualStabilityCoordinator: VisualStabilityCoordinator,
- sensitiveContentCoordinator: SensitiveContentCoordinator,
- dismissibilityCoordinator: DismissibilityCoordinator,
+ sectionStyleProvider: SectionStyleProvider,
+ featureFlags: FeatureFlags,
+ dataStoreCoordinator: DataStoreCoordinator,
+ hideLocallyDismissedNotifsCoordinator: HideLocallyDismissedNotifsCoordinator,
+ hideNotifsForOtherUsersCoordinator: HideNotifsForOtherUsersCoordinator,
+ keyguardCoordinator: KeyguardCoordinator,
+ rankingCoordinator: RankingCoordinator,
+ appOpsCoordinator: AppOpsCoordinator,
+ deviceProvisionedCoordinator: DeviceProvisionedCoordinator,
+ bubbleCoordinator: BubbleCoordinator,
+ headsUpCoordinator: HeadsUpCoordinator,
+ gutsCoordinator: GutsCoordinator,
+ conversationCoordinator: ConversationCoordinator,
+ debugModeCoordinator: DebugModeCoordinator,
+ groupCountCoordinator: GroupCountCoordinator,
+ groupWhenCoordinator: GroupWhenCoordinator,
+ mediaCoordinator: MediaCoordinator,
+ preparationCoordinator: PreparationCoordinator,
+ remoteInputCoordinator: RemoteInputCoordinator,
+ rowAppearanceCoordinator: RowAppearanceCoordinator,
+ stackCoordinator: StackCoordinator,
+ shadeEventCoordinator: ShadeEventCoordinator,
+ smartspaceDedupingCoordinator: SmartspaceDedupingCoordinator,
+ viewConfigCoordinator: ViewConfigCoordinator,
+ visualStabilityCoordinator: VisualStabilityCoordinator,
+ sensitiveContentCoordinator: SensitiveContentCoordinator,
+ dismissibilityCoordinator: DismissibilityCoordinator,
+ dreamCoordinator: DreamCoordinator,
) : NotifCoordinators {
private val mCoreCoordinators: MutableList<CoreCoordinator> = ArrayList()
@@ -96,6 +100,10 @@
mCoordinators.add(remoteInputCoordinator)
mCoordinators.add(dismissibilityCoordinator)
+ if (featureFlags.isEnabled(LOCKSCREEN_WALLPAPER_DREAM_ENABLED)) {
+ mCoordinators.add(dreamCoordinator)
+ }
+
// Manually add Ordered Sections
mOrderedSections.add(headsUpCoordinator.sectioner) // HeadsUp
mOrderedSections.add(appOpsCoordinator.sectioner) // ForegroundService
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt
index 90014c2..78225df 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt
@@ -87,6 +87,7 @@
private val userTrackerCallback = object : UserTracker.Callback {
override fun onUserChanged(newUser: Int, userContext: Context) {
+ readShowSilentNotificationSetting()
if (isLockedOrLocking) {
// maybe public mode changed
notifyStateChanged("onUserSwitched")
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
index 609f9d4..0c43da0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
@@ -582,10 +582,6 @@
}
private boolean shouldSuppressHeadsUpWhenAwakeForOldWhen(NotificationEntry entry, boolean log) {
- if (!mFlags.isNoHunForOldWhenEnabled()) {
- return false;
- }
-
final Notification notification = entry.getSbn().getNotification();
if (notification == null) {
return false;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
index 27510d4..908c11a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
@@ -566,12 +566,20 @@
@Override
public float getTopCornerRadius() {
+ if (isNewHeadsUpAnimFlagEnabled()) {
+ return super.getTopCornerRadius();
+ }
+
float fraction = getInterpolatedAppearAnimationFraction();
return MathUtils.lerp(0, super.getTopCornerRadius(), fraction);
}
@Override
public float getBottomCornerRadius() {
+ if (isNewHeadsUpAnimFlagEnabled()) {
+ return super.getBottomCornerRadius();
+ }
+
float fraction = getInterpolatedAppearAnimationFraction();
return MathUtils.lerp(0, super.getBottomCornerRadius(), fraction);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java
index 9aa50e9..7f23c1b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java
@@ -28,7 +28,10 @@
import android.view.View;
import android.view.ViewOutlineProvider;
+import com.android.systemui.Dependency;
import com.android.systemui.R;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.statusbar.notification.RoundableState;
import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainer;
import com.android.systemui.util.DumpUtilsKt;
@@ -47,12 +50,14 @@
private float mOutlineAlpha = -1f;
private boolean mAlwaysRoundBothCorners;
private Path mTmpPath = new Path();
+ private final FeatureFlags mFeatureFlags;
/**
* {@code false} if the children views of the {@link ExpandableOutlineView} are translated when
* it is moved. Otherwise, the translation is set on the {@code ExpandableOutlineView} itself.
*/
protected boolean mDismissUsingRowTranslationX = true;
+
private float[] mTmpCornerRadii = new float[8];
private final ViewOutlineProvider mProvider = new ViewOutlineProvider() {
@@ -81,6 +86,15 @@
return mRoundableState;
}
+ @Override
+ public int getClipHeight() {
+ if (mCustomOutline) {
+ return mOutlineRect.height();
+ }
+
+ return super.getClipHeight();
+ }
+
protected Path getClipPath(boolean ignoreTranslation) {
int left;
int top;
@@ -112,7 +126,7 @@
return EMPTY_PATH;
}
float bottomRadius = mAlwaysRoundBothCorners ? getMaxRadius() : getBottomCornerRadius();
- if (topRadius + bottomRadius > height) {
+ if (!isNewHeadsUpAnimFlagEnabled() && (topRadius + bottomRadius > height)) {
float overShoot = topRadius + bottomRadius - height;
float currentTopRoundness = getTopRoundness();
float currentBottomRoundness = getBottomRoundness();
@@ -153,6 +167,7 @@
super(context, attrs);
setOutlineProvider(mProvider);
initDimens();
+ mFeatureFlags = Dependency.get(FeatureFlags.class);
}
@Override
@@ -360,4 +375,9 @@
}
});
}
+
+ // TODO(b/290365128) replace with ViewRefactorFlag
+ protected boolean isNewHeadsUpAnimFlagEnabled() {
+ return mFeatureFlags.isEnabled(Flags.IMPROVED_HUN_ANIMATIONS);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
index f986244..c4c116b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
@@ -93,6 +93,12 @@
return mRoundableState;
}
+ @Override
+ public int getClipHeight() {
+ int clipHeight = Math.max(mActualHeight - mClipTopAmount - mClipBottomAmount, 0);
+ return Math.max(clipHeight, mMinimumHeightForClipping);
+ }
+
private void initDimens() {
mContentShift = getResources().getDimensionPixelSize(
R.dimen.shelf_transform_content_shift);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java
index b2a3780..867e08b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java
@@ -18,6 +18,7 @@
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
import dagger.Binds;
import dagger.Module;
@@ -58,9 +59,13 @@
@ElementsIntoSet
@Named(NOTIF_REMOTEVIEWS_FACTORIES)
static Set<NotifRemoteViewsFactory> provideNotifRemoteViewsFactories(
- FeatureFlags featureFlags
+ FeatureFlags featureFlags,
+ PrecomputedTextViewFactory precomputedTextViewFactory
) {
final Set<NotifRemoteViewsFactory> replacementFactories = new HashSet<>();
+ if (featureFlags.isEnabled(Flags.PRECOMPUTED_TEXT)) {
+ replacementFactories.add(precomputedTextViewFactory);
+ }
return replacementFactories;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PrecomputedImageFloatingTextView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PrecomputedImageFloatingTextView.kt
new file mode 100644
index 0000000..57c82dc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PrecomputedImageFloatingTextView.kt
@@ -0,0 +1,32 @@
+/*
+ * 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.statusbar.notification.row
+
+import android.content.Context
+import android.util.AttributeSet
+import android.widget.RemoteViews
+import com.android.internal.widget.ImageFloatingTextView
+
+/** Precomputed version of [ImageFloatingTextView] */
+@RemoteViews.RemoteView
+class PrecomputedImageFloatingTextView
+@JvmOverloads
+constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) :
+ ImageFloatingTextView(context, attrs, defStyleAttr), TextPrecomputer {
+
+ override fun setTextAsync(text: CharSequence?): Runnable = precompute(this, text)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PrecomputedTextView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PrecomputedTextView.kt
new file mode 100644
index 0000000..8508b1f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PrecomputedTextView.kt
@@ -0,0 +1,34 @@
+/*
+ * 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.statusbar.notification.row
+
+import android.content.Context
+import android.util.AttributeSet
+import android.widget.RemoteViews
+import android.widget.TextView
+
+/**
+ * A user interface element that uses the PrecomputedText API to display text in a notification,
+ * with the help of RemoteViews.
+ */
+@RemoteViews.RemoteView
+class PrecomputedTextView
+@JvmOverloads
+constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) :
+ TextView(context, attrs, defStyleAttr), TextPrecomputer {
+
+ override fun setTextAsync(text: CharSequence?): Runnable = precompute(this, text)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PrecomputedTextViewFactory.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PrecomputedTextViewFactory.kt
new file mode 100644
index 0000000..b002330
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PrecomputedTextViewFactory.kt
@@ -0,0 +1,41 @@
+/*
+ * 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.statusbar.notification.row
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.View
+import android.widget.TextView
+import com.android.internal.widget.ImageFloatingTextView
+import javax.inject.Inject
+
+class PrecomputedTextViewFactory @Inject constructor() : NotifRemoteViewsFactory {
+ override fun instantiate(
+ parent: View?,
+ name: String,
+ context: Context,
+ attrs: AttributeSet
+ ): View? {
+ return when (name) {
+ TextView::class.java.name,
+ TextView::class.java.simpleName -> PrecomputedTextView(context, attrs)
+ ImageFloatingTextView::class.java.name ->
+ PrecomputedImageFloatingTextView(context, attrs)
+ else -> null
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/TextPrecomputer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/TextPrecomputer.kt
new file mode 100644
index 0000000..49f4a33
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/TextPrecomputer.kt
@@ -0,0 +1,59 @@
+/*
+ * 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.statusbar.notification.row
+
+import android.text.PrecomputedText
+import android.text.Spannable
+import android.util.Log
+import android.widget.TextView
+
+internal interface TextPrecomputer {
+ /**
+ * Creates PrecomputedText from given text and returns a runnable which sets precomputed text to
+ * the textview on main thread.
+ *
+ * @param text text to be converted to PrecomputedText
+ * @return Runnable that sets precomputed text on the main thread
+ */
+ fun precompute(
+ textView: TextView,
+ text: CharSequence?,
+ logException: Boolean = true
+ ): Runnable {
+ val precomputedText: Spannable? =
+ text?.let { PrecomputedText.create(it, textView.textMetricsParams) }
+
+ return Runnable {
+ try {
+ textView.text = precomputedText
+ } catch (exception: IllegalArgumentException) {
+ if (logException) {
+ Log.wtf(
+ /* tag = */ TAG,
+ /* msg = */ "PrecomputedText setText failed for TextView:$textView",
+ /* tr = */ exception
+ )
+ }
+ textView.text = text
+ }
+ }
+ }
+
+ private companion object {
+ private const val TAG = "TextPrecomputer"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
index ef5e86f06..87205e2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
@@ -120,6 +120,11 @@
}
@Override
+ public int getClipHeight() {
+ return mView.getHeight();
+ }
+
+ @Override
public void applyRoundnessAndInvalidate() {
if (mRoundnessChangedListener != null) {
// We cannot apply the rounded corner to this View, so our parents (in drawChild()) will
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt
index 04308b4..a8d8a8e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt
@@ -30,8 +30,8 @@
*/
class MediaContainerView(context: Context, attrs: AttributeSet?) : ExpandableView(context, attrs) {
+ override var clipHeight = 0
var cornerRadius = 0f
- var clipHeight = 0
var clipRect = RectF()
var clipPath = Path()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
index dad8064..626f851 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
@@ -192,6 +192,11 @@
}
@Override
+ public int getClipHeight() {
+ return Math.max(mActualHeight - mClipBottomAmount, 0);
+ }
+
+ @Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int childCount =
Math.min(mAttachedChildren.size(), NUMBER_OF_CHILDREN_WHEN_CHILDREN_EXPANDED);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 8063c8c..c1ceb3c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -3758,20 +3758,20 @@
case MotionEvent.ACTION_UP:
if (mStatusBarState != StatusBarState.KEYGUARD && mTouchIsClick &&
isBelowLastNotification(mInitialTouchX, mInitialTouchY)) {
- debugLog("handleEmptySpaceClick: touch event propagated further");
+ debugShadeLog("handleEmptySpaceClick: touch event propagated further");
mOnEmptySpaceClickListener.onEmptySpaceClicked(mInitialTouchX, mInitialTouchY);
}
break;
default:
- debugLog("handleEmptySpaceClick: MotionEvent ignored");
+ debugShadeLog("handleEmptySpaceClick: MotionEvent ignored");
}
}
- private void debugLog(@CompileTimeConstant final String s) {
+ private void debugShadeLog(@CompileTimeConstant final String s) {
if (mLogger == null) {
return;
}
- mLogger.d(s);
+ mLogger.logShadeDebugEvent(s);
}
private void logEmptySpaceClick(MotionEvent ev, boolean isTouchBelowLastNotification,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt
index 2c38b8d..3396306 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt
@@ -7,6 +7,7 @@
import com.android.systemui.log.core.LogLevel.ERROR
import com.android.systemui.log.dagger.NotificationHeadsUpLog
import com.android.systemui.log.dagger.NotificationRenderLog
+import com.android.systemui.log.dagger.ShadeLog
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.logKey
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_ADD
@@ -19,7 +20,8 @@
class NotificationStackScrollLogger @Inject constructor(
@NotificationHeadsUpLog private val buffer: LogBuffer,
- @NotificationRenderLog private val notificationRenderBuffer: LogBuffer
+ @NotificationRenderLog private val notificationRenderBuffer: LogBuffer,
+ @ShadeLog private val shadeLogBuffer: LogBuffer,
) {
fun hunAnimationSkipped(entry: NotificationEntry, reason: String) {
buffer.log(TAG, INFO, {
@@ -63,7 +65,7 @@
})
}
- fun d(@CompileTimeConstant msg: String) = buffer.log(TAG, DEBUG, msg)
+ fun logShadeDebugEvent(@CompileTimeConstant msg: String) = shadeLogBuffer.log(TAG, DEBUG, msg)
fun logEmptySpaceClick(
isBelowLastNotification: Boolean,
@@ -71,7 +73,7 @@
touchIsClick: Boolean,
motionEventDesc: String
) {
- buffer.log(TAG, DEBUG, {
+ shadeLogBuffer.log(TAG, DEBUG, {
int1 = statusBarState
bool1 = touchIsClick
bool2 = isBelowLastNotification
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractor.kt
new file mode 100644
index 0000000..874450b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractor.kt
@@ -0,0 +1,65 @@
+/*
+ * 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.statusbar.notification.stack.domain.interactor
+
+import android.content.Context
+import com.android.systemui.R
+import com.android.systemui.common.ui.data.repository.ConfigurationRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
+
+/** Encapsulates business-logic specifically related to the shared notification stack container. */
+class SharedNotificationContainerInteractor
+@Inject
+constructor(
+ configurationRepository: ConfigurationRepository,
+ private val context: Context,
+) {
+ val configurationBasedDimensions: Flow<ConfigurationBasedDimensions> =
+ configurationRepository.onAnyConfigurationChange
+ .onStart { emit(Unit) }
+ .map { _ ->
+ with(context.resources) {
+ ConfigurationBasedDimensions(
+ useSplitShade = getBoolean(R.bool.config_use_split_notification_shade),
+ useLargeScreenHeader =
+ getBoolean(R.bool.config_use_large_screen_shade_header),
+ marginHorizontal =
+ getDimensionPixelSize(R.dimen.notification_panel_margin_horizontal),
+ marginBottom =
+ getDimensionPixelSize(R.dimen.notification_panel_margin_bottom),
+ marginTop = getDimensionPixelSize(R.dimen.notification_panel_margin_top),
+ marginTopLargeScreen =
+ getDimensionPixelSize(R.dimen.large_screen_shade_header_height),
+ )
+ }
+ }
+ .distinctUntilChanged()
+
+ data class ConfigurationBasedDimensions(
+ val useSplitShade: Boolean,
+ val useLargeScreenHeader: Boolean,
+ val marginHorizontal: Int,
+ val marginBottom: Int,
+ val marginTop: Int,
+ val marginTopLargeScreen: Int,
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/SharedNotificationContainer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/SharedNotificationContainer.kt
new file mode 100644
index 0000000..688843d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/SharedNotificationContainer.kt
@@ -0,0 +1,88 @@
+/*
+ * 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.statusbar.notification.stack.ui.view
+
+import android.content.Context
+import android.util.AttributeSet
+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.END
+import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
+import androidx.constraintlayout.widget.ConstraintSet.START
+import androidx.constraintlayout.widget.ConstraintSet.TOP
+import androidx.constraintlayout.widget.ConstraintSet.VERTICAL
+import com.android.systemui.R
+
+/**
+ * Container for the stack scroller, so that the bounds can be externally specified, such as from
+ * the keyguard or shade scenes.
+ */
+class SharedNotificationContainer(
+ context: Context,
+ private val attrs: AttributeSet?,
+) :
+ ConstraintLayout(
+ context,
+ attrs,
+ ) {
+
+ private val baseConstraintSet = ConstraintSet()
+
+ init {
+ baseConstraintSet.apply {
+ create(R.id.nssl_guideline, VERTICAL)
+ setGuidelinePercent(R.id.nssl_guideline, 0.5f)
+ }
+ baseConstraintSet.applyTo(this)
+ }
+
+ fun addNotificationStackScrollLayout(nssl: View) {
+ addView(nssl)
+ }
+
+ fun updateConstraints(
+ useSplitShade: Boolean,
+ marginStart: Int,
+ marginTop: Int,
+ marginEnd: Int,
+ marginBottom: Int
+ ) {
+ val constraintSet = ConstraintSet()
+ constraintSet.clone(baseConstraintSet)
+
+ val startConstraintId =
+ if (useSplitShade) {
+ R.id.nssl_guideline
+ } else {
+ PARENT_ID
+ }
+ val nsslId = R.id.notification_stack_scroller
+ constraintSet.apply {
+ connect(nsslId, START, startConstraintId, START)
+ connect(nsslId, END, PARENT_ID, END)
+ connect(nsslId, BOTTOM, PARENT_ID, BOTTOM)
+ connect(nsslId, TOP, PARENT_ID, TOP)
+ setMargin(nsslId, START, marginStart)
+ setMargin(nsslId, END, marginEnd)
+ setMargin(nsslId, TOP, marginTop)
+ setMargin(nsslId, BOTTOM, marginBottom)
+ }
+ constraintSet.applyTo(this)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
new file mode 100644
index 0000000..fb1d55d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
@@ -0,0 +1,50 @@
+/*
+ * 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.statusbar.notification.stack.ui.viewbinder
+
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
+import com.android.systemui.statusbar.notification.stack.ui.viewmodel.SharedNotificationContainerViewModel
+import kotlinx.coroutines.launch
+
+/** Binds the shared notification container to its view-model. */
+object SharedNotificationContainerBinder {
+
+ @JvmStatic
+ fun bind(
+ view: SharedNotificationContainer,
+ viewModel: SharedNotificationContainerViewModel,
+ ) {
+ view.repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ launch {
+ viewModel.configurationBasedDimensions.collect {
+ view.updateConstraints(
+ useSplitShade = it.useSplitShade,
+ marginStart = it.marginStart,
+ marginTop = it.marginTop,
+ marginEnd = it.marginEnd,
+ marginBottom = it.marginBottom,
+ )
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
new file mode 100644
index 0000000..b2e5ac1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
@@ -0,0 +1,53 @@
+/*
+ * 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.statusbar.notification.stack.ui.viewmodel
+
+import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
+
+/** View-model for the shared notification container */
+class SharedNotificationContainerViewModel
+@Inject
+constructor(
+ interactor: SharedNotificationContainerInteractor,
+) {
+ val configurationBasedDimensions: Flow<ConfigurationBasedDimensions> =
+ interactor.configurationBasedDimensions
+ .map {
+ ConfigurationBasedDimensions(
+ marginStart = if (it.useSplitShade) 0 else it.marginHorizontal,
+ marginEnd = it.marginHorizontal,
+ marginBottom = it.marginBottom,
+ marginTop =
+ if (it.useLargeScreenHeader) it.marginTopLargeScreen else it.marginTop,
+ useSplitShade = it.useSplitShade,
+ )
+ }
+ .distinctUntilChanged()
+
+ data class ConfigurationBasedDimensions(
+ val marginStart: Int,
+ val marginTop: Int,
+ val marginEnd: Int,
+ val marginBottom: Int,
+ val useSplitShade: Boolean,
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
index 730ef57..26b51a9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
@@ -37,6 +37,7 @@
import com.android.systemui.assist.AssistManager
import com.android.systemui.camera.CameraIntents.Companion.isInsecureCameraIntent
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.DisplayId
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.keyguard.KeyguardViewMediator
import com.android.systemui.keyguard.WakefulnessLifecycle
@@ -72,6 +73,7 @@
private val notifShadeWindowControllerLazy: Lazy<NotificationShadeWindowController>,
private val activityLaunchAnimator: ActivityLaunchAnimator,
private val context: Context,
+ @DisplayId private val displayId: Int,
private val lockScreenUserManager: NotificationLockscreenUserManager,
private val statusBarWindowController: StatusBarWindowController,
private val wakefulnessLifecycle: WakefulnessLifecycle,
@@ -471,9 +473,7 @@
intent.getPackage()
) { adapter: RemoteAnimationAdapter? ->
val options =
- ActivityOptions(
- CentralSurfaces.getActivityOptions(centralSurfaces!!.displayId, adapter)
- )
+ ActivityOptions(CentralSurfaces.getActivityOptions(displayId, adapter))
// We know that the intent of the caller is to dismiss the keyguard and
// this runnable is called right after the keyguard is solved, so we tell
@@ -596,7 +596,7 @@
val options =
ActivityOptions(
CentralSurfaces.getActivityOptions(
- centralSurfaces!!.displayId,
+ displayId,
animationAdapter
)
)
@@ -762,7 +762,7 @@
TaskStackBuilder.create(context)
.addNextIntent(intent)
.startActivities(
- CentralSurfaces.getActivityOptions(centralSurfaces!!.displayId, adapter),
+ CentralSurfaces.getActivityOptions(displayId, adapter),
userHandle
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index 66d217b..dc021fe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -160,6 +160,7 @@
private KeyguardViewController mKeyguardViewController;
private DozeScrimController mDozeScrimController;
private KeyguardViewMediator mKeyguardViewMediator;
+ private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
private PendingAuthenticated mPendingAuthenticated = null;
private boolean mHasScreenTurnedOnSinceAuthenticating;
private boolean mFadedAwayAfterWakeAndUnlock;
@@ -280,7 +281,8 @@
LatencyTracker latencyTracker,
ScreenOffAnimationController screenOffAnimationController,
VibratorHelper vibrator,
- SystemClock systemClock
+ SystemClock systemClock,
+ StatusBarKeyguardViewManager statusBarKeyguardViewManager
) {
mPowerManager = powerManager;
mUpdateMonitor = keyguardUpdateMonitor;
@@ -308,6 +310,7 @@
mVibratorHelper = vibrator;
mLogger = biometricUnlockLogger;
mSystemClock = systemClock;
+ mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
dumpManager.registerDumpable(this);
}
@@ -449,8 +452,19 @@
// During wake and unlock, we need to draw black before waking up to avoid abrupt
// brightness changes due to display state transitions.
Runnable wakeUp = ()-> {
- if (!wasDeviceInteractive || mUpdateMonitor.isDreaming()) {
+ // Check to see if we are still locked when we are waking and unlocking from dream.
+ // This runnable should be executed after unlock. If that's true, we could be not
+ // dreaming, but still locked. In this case, we should attempt to authenticate instead
+ // of waking up.
+ if (mode == MODE_WAKE_AND_UNLOCK_FROM_DREAM
+ && !mKeyguardStateController.isUnlocked()
+ && !mUpdateMonitor.isDreaming()) {
+ // Post wakeUp runnable is called from a callback in keyguard.
+ mHandler.post(() -> mKeyguardViewController.notifyKeyguardAuthenticated(
+ false /* primaryAuth */));
+ } else if (!wasDeviceInteractive || mUpdateMonitor.isDreaming()) {
mLogger.i("bio wakelock: Authenticated, waking up...");
+
mPowerManager.wakeUp(
mSystemClock.uptimeMillis(),
PowerManager.WAKE_REASON_BIOMETRIC,
@@ -462,7 +476,7 @@
Trace.endSection();
};
- if (mMode != MODE_NONE) {
+ if (mMode != MODE_NONE && mMode != MODE_WAKE_AND_UNLOCK_FROM_DREAM) {
wakeUp.run();
}
switch (mMode) {
@@ -484,6 +498,10 @@
Trace.endSection();
break;
case MODE_WAKE_AND_UNLOCK_FROM_DREAM:
+ // In the case of waking and unlocking from dream, waking up is delayed until after
+ // unlock is complete to avoid conflicts during each sequence's transitions.
+ mStatusBarKeyguardViewManager.addAfterKeyguardGoneRunnable(wakeUp);
+ // Execution falls through here to proceed unlocking.
case MODE_WAKE_AND_UNLOCK_PULSING:
case MODE_WAKE_AND_UNLOCK:
if (mMode == MODE_WAKE_AND_UNLOCK_PULSING) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
index 478baf2f..acd6e49 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
@@ -259,8 +259,6 @@
void readyForKeyguardDone();
- void setLockscreenUser(int newUserId);
-
void showKeyguard();
boolean hideKeyguard();
@@ -355,15 +353,11 @@
void updateNotificationPanelTouchState();
- int getDisplayId();
-
int getRotation();
@VisibleForTesting
void setBarStateForTest(int state);
- void wakeUpForFullScreenIntent();
-
void showTransientUnchecked();
void clearTransient();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 8902a18..8361d6b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -51,7 +51,6 @@
import android.app.StatusBarManager;
import android.app.TaskInfo;
import android.app.UiModeManager;
-import android.app.WallpaperInfo;
import android.app.WallpaperManager;
import android.app.admin.DevicePolicyManager;
import android.content.BroadcastReceiver;
@@ -413,7 +412,6 @@
private final Point mCurrentDisplaySize = new Point();
- protected NotificationShadeWindowView mNotificationShadeWindowView;
protected PhoneStatusBarView mStatusBarView;
private PhoneStatusBarViewController mPhoneStatusBarViewController;
private PhoneStatusBarTransitions mStatusBarTransitions;
@@ -456,7 +454,8 @@
private final FalsingManager mFalsingManager;
private final BroadcastDispatcher mBroadcastDispatcher;
private final ConfigurationController mConfigurationController;
- protected NotificationShadeWindowViewController mNotificationShadeWindowViewController;
+ private final Lazy<NotificationShadeWindowViewController>
+ mNotificationShadeWindowViewControllerLazy;
private final DozeParameters mDozeParameters;
private final Lazy<BiometricUnlockController> mBiometricUnlockControllerLazy;
private final CentralSurfacesComponent.Factory mCentralSurfacesComponentFactory;
@@ -722,6 +721,7 @@
Lazy<AssistManager> assistManagerLazy,
ConfigurationController configurationController,
NotificationShadeWindowController notificationShadeWindowController,
+ Lazy<NotificationShadeWindowViewController> notificationShadeWindowViewControllerLazy,
NotificationShelfController notificationShelfController,
NotificationStackScrollLayoutController notificationStackScrollLayoutController,
DozeParameters dozeParameters,
@@ -825,6 +825,7 @@
mAssistManagerLazy = assistManagerLazy;
mConfigurationController = configurationController;
mNotificationShadeWindowController = notificationShadeWindowController;
+ mNotificationShadeWindowViewControllerLazy = notificationShadeWindowViewControllerLazy;
mNotificationShelfController = notificationShelfController;
mStackScrollerController = notificationStackScrollLayoutController;
mStackScroller = mStackScrollerController.getView();
@@ -978,16 +979,6 @@
createAndAddWindows(result);
- if (mWallpaperSupported) {
- // Make sure we always have the most current wallpaper info.
- IntentFilter wallpaperChangedFilter = new IntentFilter(Intent.ACTION_WALLPAPER_CHANGED);
- mBroadcastDispatcher.registerReceiver(mWallpaperChangedReceiver, wallpaperChangedFilter,
- null /* handler */, UserHandle.ALL);
- mWallpaperChangedReceiver.onReceive(mContext, null);
- } else if (DEBUG) {
- Log.v(TAG, "start(): no wallpaper service ");
- }
-
// Set up the initial notification state. This needs to happen before CommandQueue.disable()
setUpPresenter();
@@ -1073,7 +1064,7 @@
mDozeServiceHost.initialize(
this,
mStatusBarKeyguardViewManager,
- mNotificationShadeWindowViewController,
+ getNotificationShadeWindowViewController(),
mShadeSurface,
mAmbientIndicationContainer);
updateLightRevealScrimVisibility();
@@ -1235,8 +1226,8 @@
updateTheme();
inflateStatusBarWindow();
- mNotificationShadeWindowView.setOnTouchListener(getStatusBarWindowTouchListener());
- mWallpaperController.setRootView(mNotificationShadeWindowView);
+ getNotificationShadeWindowView().setOnTouchListener(getStatusBarWindowTouchListener());
+ mWallpaperController.setRootView(getNotificationShadeWindowView());
// TODO: Deal with the ugliness that comes from having some of the status bar broken out
// into fragments, but the rest here, it leaves some awkward lifecycle and whatnot.
@@ -1257,7 +1248,7 @@
mStatusBarView = statusBarView;
mPhoneStatusBarViewController = statusBarViewController;
mStatusBarTransitions = statusBarTransitions;
- mNotificationShadeWindowViewController
+ getNotificationShadeWindowViewController()
.setStatusBarViewController(mPhoneStatusBarViewController);
// Ensure we re-propagate panel expansion values to the panel controller and
// any listeners it may have, such as PanelBar. This will also ensure we
@@ -1271,7 +1262,7 @@
mStatusBarInitializer.initializeStatusBar(
mCentralSurfacesComponent::createCollapsedStatusBarFragment);
- mStatusBarTouchableRegionManager.setup(this, mNotificationShadeWindowView);
+ mStatusBarTouchableRegionManager.setup(this, getNotificationShadeWindowView());
createNavigationBar(result);
@@ -1279,7 +1270,7 @@
mLockscreenWallpaper = mLockscreenWallpaperLazy.get();
}
- mAmbientIndicationContainer = mNotificationShadeWindowView.findViewById(
+ mAmbientIndicationContainer = getNotificationShadeWindowView().findViewById(
R.id.ambient_indication_container);
mAutoHideController.setStatusBar(new AutoHideUiElement() {
@@ -1304,10 +1295,10 @@
}
});
- ScrimView scrimBehind = mNotificationShadeWindowView.findViewById(R.id.scrim_behind);
- ScrimView notificationsScrim = mNotificationShadeWindowView
+ ScrimView scrimBehind = getNotificationShadeWindowView().findViewById(R.id.scrim_behind);
+ ScrimView notificationsScrim = getNotificationShadeWindowView()
.findViewById(R.id.scrim_notifications);
- ScrimView scrimInFront = mNotificationShadeWindowView.findViewById(R.id.scrim_in_front);
+ ScrimView scrimInFront = getNotificationShadeWindowView().findViewById(R.id.scrim_in_front);
mScrimController.setScrimVisibleListener(scrimsVisible -> {
mNotificationShadeWindowController.setScrimsVisibility(scrimsVisible);
@@ -1345,7 +1336,7 @@
mNotificationShelfController,
mHeadsUpManager);
- BackDropView backdrop = mNotificationShadeWindowView.findViewById(R.id.backdrop);
+ BackDropView backdrop = getNotificationShadeWindowView().findViewById(R.id.backdrop);
if (mWallpaperManager.isLockscreenLiveWallpaperEnabled()) {
mMediaManager.setup(null, null, null, mScrimController, null);
} else {
@@ -1364,7 +1355,7 @@
});
// Set up the quick settings tile panel
- final View container = mNotificationShadeWindowView.findViewById(R.id.qs_frame);
+ final View container = getNotificationShadeWindowView().findViewById(R.id.qs_frame);
if (container != null) {
FragmentHostManager fragmentHostManager =
mFragmentService.getFragmentHostManager(container);
@@ -1379,7 +1370,7 @@
.withDefault(this::createDefaultQSFragment)
.build());
mBrightnessMirrorController = new BrightnessMirrorController(
- mNotificationShadeWindowView,
+ getNotificationShadeWindowView(),
mShadeSurface,
mNotificationShadeDepthControllerLazy.get(),
mBrightnessSliderFactory,
@@ -1396,7 +1387,7 @@
});
}
- mReportRejectedTouch = mNotificationShadeWindowView
+ mReportRejectedTouch = getNotificationShadeWindowView()
.findViewById(R.id.report_rejected_touch);
if (mReportRejectedTouch != null) {
updateReportRejectedTouchVisibility();
@@ -1544,7 +1535,7 @@
protected QS createDefaultQSFragment() {
return mFragmentService
- .getFragmentHostManager(mNotificationShadeWindowView)
+ .getFragmentHostManager(getNotificationShadeWindowView())
.create(QSFragment.class);
}
@@ -1553,7 +1544,7 @@
mActivityLaunchAnimator.setCallback(mActivityLaunchAnimatorCallback);
mActivityLaunchAnimator.addListener(mActivityLaunchAnimatorListener);
mNotificationAnimationProvider = new NotificationLaunchAnimatorControllerProvider(
- mNotificationShadeWindowViewController,
+ getNotificationShadeWindowViewController(),
mNotifListContainer,
mHeadsUpManager,
mJankMonitor);
@@ -1592,7 +1583,7 @@
mAutoHideController.checkUserAutoHide(event);
mRemoteInputManager.checkRemoteInputOutside(event);
mShadeController.onStatusBarTouch(event);
- return mNotificationShadeWindowView.onTouchEvent(event);
+ return getNotificationShadeWindowView().onTouchEvent(event);
};
}
@@ -1606,16 +1597,12 @@
mCentralSurfacesComponent::createCollapsedStatusBarFragment);
ViewGroup windowRootView = mCentralSurfacesComponent.getWindowRootView();
- mNotificationShadeWindowView = mCentralSurfacesComponent.getNotificationShadeWindowView();
- mNotificationShadeWindowViewController = mCentralSurfacesComponent
- .getNotificationShadeWindowViewController();
// TODO(b/277762009): Inject [NotificationShadeWindowView] directly into the controller.
// (Right now, there's a circular dependency.)
mNotificationShadeWindowController.setWindowRootView(windowRootView);
- mNotificationShadeWindowViewController.setupExpandedStatusBar();
- mShadeController.setShadeViewController(mShadeSurface);
+ getNotificationShadeWindowViewController().setupExpandedStatusBar();
mShadeController.setNotificationShadeWindowViewController(
- mNotificationShadeWindowViewController);
+ getNotificationShadeWindowViewController());
mBackActionInteractor.setup(mQsController, mShadeSurface);
mPresenter = mCentralSurfacesComponent.getNotificationPresenter();
mNotificationActivityStarter = mCentralSurfacesComponent.getNotificationActivityStarter();
@@ -1634,6 +1621,14 @@
mCommandQueue.addCallback(mCommandQueueCallbacks);
}
+ protected NotificationShadeWindowViewController getNotificationShadeWindowViewController() {
+ return mNotificationShadeWindowViewControllerLazy.get();
+ }
+
+ protected NotificationShadeWindowView getNotificationShadeWindowView() {
+ return getNotificationShadeWindowViewController().getView();
+ }
+
protected void startKeyguard() {
Trace.beginSection("CentralSurfaces#startKeyguard");
mStatusBarStateController.addCallback(mStateListener,
@@ -1689,7 +1684,7 @@
@Override
public AuthKeyguardMessageArea getKeyguardMessageArea() {
- return mNotificationShadeWindowViewController.getKeyguardMessageArea();
+ return getNotificationShadeWindowViewController().getKeyguardMessageArea();
}
@Override
@@ -1773,7 +1768,7 @@
try {
EventLog.writeEvent(EventLogTags.SYSUI_HEADS_UP_ESCALATION,
sbn.getKey());
- wakeUpForFullScreenIntent();
+ mPowerInteractor.wakeUpForFullScreenIntent();
ActivityOptions opts = ActivityOptions.makeBasic();
opts.setPendingIntentBackgroundActivityStartMode(
ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
@@ -1786,16 +1781,6 @@
mHeadsUpManager.releaseAllImmediately();
}
- @Override
- public void wakeUpForFullScreenIntent() {
- if (isGoingToSleep() || mDozing) {
- mPowerManager.wakeUp(
- SystemClock.uptimeMillis(),
- PowerManager.WAKE_REASON_APPLICATION,
- "com.android.systemui:full_screen_intent");
- }
- }
-
/**
* Called when another window is about to transfer it's input focus.
*/
@@ -1993,11 +1978,9 @@
pw.print(" mWallpaperSupported= "); pw.println(mWallpaperSupported);
pw.println(" ShadeWindowView: ");
- if (mNotificationShadeWindowViewController != null) {
- mNotificationShadeWindowViewController.dump(pw, args);
- CentralSurfaces.dumpBarTransitions(
- pw, "PhoneStatusBarTransitions", mStatusBarTransitions);
- }
+ getNotificationShadeWindowViewController().dump(pw, args);
+ CentralSurfaces.dumpBarTransitions(
+ pw, "PhoneStatusBarTransitions", mStatusBarTransitions);
pw.println(" mMediaManager: ");
if (mMediaManager != null) {
@@ -2108,11 +2091,6 @@
}
@Override
- public int getDisplayId() {
- return mDisplayId;
- }
-
- @Override
public void readyForKeyguardDone() {
mStatusBarKeyguardViewManager.readyForKeyguardDone();
}
@@ -2175,18 +2153,6 @@
};
/**
- * Notify the shade controller that the current user changed
- *
- * @param newUserId userId of the new user
- */
- @Override
- public void setLockscreenUser(int newUserId) {
- if (mWallpaperSupported) {
- mWallpaperChangedReceiver.onReceive(mContext, null);
- }
- }
-
- /**
* Reload some of our resources when the configuration changes.
*
* We don't reload everything when the configuration changes -- we probably
@@ -2876,7 +2842,7 @@
updateVisibleToUser();
updateNotificationPanelTouchState();
- mNotificationShadeWindowViewController.cancelCurrentTouch();
+ getNotificationShadeWindowViewController().cancelCurrentTouch();
if (mLaunchCameraOnFinishedGoingToSleep) {
mLaunchCameraOnFinishedGoingToSleep = false;
@@ -3188,12 +3154,6 @@
updateScrimController();
}
- @VisibleForTesting
- public void setNotificationShadeWindowViewController(
- NotificationShadeWindowViewController nswvc) {
- mNotificationShadeWindowViewController = nswvc;
- }
-
/**
* Set the amount of progress we are currently in if we're transitioning to the full shade.
* 0.0f means we're not transitioning yet, while 1 means we're all the way in the full
@@ -3428,7 +3388,7 @@
mVisible = visible;
if (visible) {
DejankUtils.notifyRendererOfExpensiveFrame(
- mNotificationShadeWindowView, "onShadeVisibilityChanged");
+ getNotificationShadeWindowView(), "onShadeVisibilityChanged");
} else {
mGutsManager.closeAndSaveGuts(true /* removeLeavebehind */, true /* force */,
true /* removeControls */, -1 /* x */, -1 /* y */, true /* resetMenu */);
@@ -3566,30 +3526,6 @@
}
};
- private final BroadcastReceiver mWallpaperChangedReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (!mWallpaperSupported) {
- // Receiver should not have been registered at all...
- Log.wtf(TAG, "WallpaperManager not supported");
- return;
- }
- WallpaperInfo info = mWallpaperManager.getWallpaperInfoForUser(
- mUserTracker.getUserId());
- mWallpaperController.onWallpaperInfoUpdated(info);
-
- final boolean deviceSupportsAodWallpaper = mContext.getResources().getBoolean(
- com.android.internal.R.bool.config_dozeSupportsAodWallpaper);
- // If WallpaperInfo is null, it must be ImageWallpaper.
- final boolean supportsAmbientMode = deviceSupportsAodWallpaper
- && (info != null && info.supportsAmbientMode());
-
- mNotificationShadeWindowController.setWallpaperSupportsAmbientMode(supportsAmbientMode);
- mScrimController.setWallpaperSupportsAmbientMode(supportsAmbientMode);
- mKeyguardViewMediator.setWallpaperSupportsAmbientMode(supportsAmbientMode);
- }
- };
-
private final ConfigurationListener mConfigurationListener = new ConfigurationListener() {
@Override
public void onConfigChanged(Configuration newConfig) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
index 39b5b5a..8e9f382 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
@@ -35,11 +35,9 @@
import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
import com.android.systemui.statusbar.StatusBarIconView;
import com.android.systemui.statusbar.StatusBarMobileView;
-import com.android.systemui.statusbar.StatusBarWifiView;
import com.android.systemui.statusbar.StatusIconDisplayable;
import com.android.systemui.statusbar.connectivity.ui.MobileContextProvider;
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
-import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
import com.android.systemui.statusbar.pipeline.mobile.ui.MobileViewLogger;
import com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernStatusBarMobileView;
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel;
@@ -58,7 +56,6 @@
private final ArrayList<ModernStatusBarMobileView> mModernMobileViews = new ArrayList<>();
private final int mIconSize;
- private StatusBarWifiView mWifiView;
private ModernStatusBarWifiView mModernWifiView;
private boolean mDemoMode;
private int mColor;
@@ -238,36 +235,6 @@
addView(v, 0, createLayoutParams());
}
- public void addDemoWifiView(WifiIconState state) {
- Log.d(TAG, "addDemoWifiView: ");
- StatusBarWifiView view = StatusBarWifiView.fromContext(mContext, state.slot);
-
- int viewIndex = getChildCount();
- // If we have mobile views, put wifi before them
- for (int i = 0; i < getChildCount(); i++) {
- View child = getChildAt(i);
- if (child instanceof StatusBarMobileView
- || child instanceof ModernStatusBarMobileView) {
- viewIndex = i;
- break;
- }
- }
-
- mWifiView = view;
- mWifiView.applyWifiState(state);
- mWifiView.setStaticDrawableColor(mColor);
- addView(view, viewIndex, createLayoutParams());
- }
-
- public void updateWifiState(WifiIconState state) {
- Log.d(TAG, "updateWifiState: ");
- if (mWifiView == null) {
- addDemoWifiView(state);
- } else {
- mWifiView.applyWifiState(state);
- }
- }
-
/**
* Add a new mobile icon view
*/
@@ -352,10 +319,7 @@
public void onRemoveIcon(StatusIconDisplayable view) {
if (view.getSlot().equals("wifi")) {
- if (view instanceof StatusBarWifiView) {
- removeView(mWifiView);
- mWifiView = null;
- } else if (view instanceof ModernStatusBarWifiView) {
+ if (view instanceof ModernStatusBarWifiView) {
Log.d(TAG, "onRemoveIcon: removing modern wifi view");
removeView(mModernWifiView);
mModernWifiView = null;
@@ -409,9 +373,6 @@
public void onDarkChanged(ArrayList<Rect> areas, float darkIntensity, int tint) {
setColor(DarkIconDispatcher.getTint(areas, mStatusIcons, tint));
- if (mWifiView != null) {
- mWifiView.onDarkChanged(areas, darkIntensity, tint);
- }
if (mModernWifiView != null) {
mModernWifiView.onDarkChanged(areas, darkIntensity, tint);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
index 7312db6..ed9722e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
@@ -311,6 +311,7 @@
@Override
public void dozeTimeTick() {
+ mDozeInteractor.dozeTimeTick();
mNotificationPanel.dozeTimeTick();
mAuthController.dozeTimeTick();
if (mAmbientIndicationContainer instanceof DozeReceiver) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
index ff1b31d..924aac4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
@@ -50,12 +50,6 @@
@DevicePostureInt private var postureState: Int = DEVICE_POSTURE_UNKNOWN
private var pendingUnlock: PendingUnlock? = null
private val listeners = mutableListOf<OnBypassStateChangedListener>()
- private val postureCallback = DevicePostureController.Callback { posture ->
- if (postureState != posture) {
- postureState = posture
- notifyListeners()
- }
- }
private val faceAuthEnabledChangedCallback = object : KeyguardStateController.Callback {
override fun onFaceAuthEnabledChanged() = notifyListeners()
}
@@ -162,10 +156,8 @@
val dismissByDefault = if (context.resources.getBoolean(
com.android.internal.R.bool.config_faceAuthDismissesKeyguard)) 1 else 0
- tunerService.addTunable(object : TunerService.Tunable {
- override fun onTuningChanged(key: String?, newValue: String?) {
- bypassEnabled = tunerService.getValue(key, dismissByDefault) != 0
- }
+ tunerService.addTunable({ key, _ ->
+ bypassEnabled = tunerService.getValue(key, dismissByDefault) != 0
}, Settings.Secure.FACE_UNLOCK_DISMISSES_KEYGUARD)
lockscreenUserManager.addUserChangedListener(
object : NotificationLockscreenUserManager.UserChangedListener {
@@ -281,8 +273,6 @@
}
companion object {
- const val BYPASS_FADE_DURATION = 67
-
private const val FACE_UNLOCK_BYPASS_NO_OVERRIDE = 0
private const val FACE_UNLOCK_BYPASS_ALWAYS = 1
private const val FACE_UNLOCK_BYPASS_NEVER = 2
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
index f26a84b..dc5eb0d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
@@ -38,8 +38,6 @@
import com.android.systemui.R;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.settings.DisplayTracker;
@@ -65,7 +63,6 @@
private final SysuiDarkIconDispatcher mStatusBarIconController;
private final BatteryController mBatteryController;
- private final boolean mUseNewLightBarLogic;
private BiometricUnlockController mBiometricUnlockController;
private LightBarTransitionsController mNavigationBarController;
@@ -123,10 +120,8 @@
DarkIconDispatcher darkIconDispatcher,
BatteryController batteryController,
NavigationModeController navModeController,
- FeatureFlags featureFlags,
DumpManager dumpManager,
DisplayTracker displayTracker) {
- mUseNewLightBarLogic = featureFlags.isEnabled(Flags.NEW_LIGHT_BAR_LOGIC);
mDarkIconColor = ctx.getColor(R.color.dark_mode_icon_color_single_tone);
mLightIconColor = ctx.getColor(R.color.light_mode_icon_color_single_tone);
mStatusBarIconController = (SysuiDarkIconDispatcher) darkIconDispatcher;
@@ -188,51 +183,31 @@
final boolean last = mNavigationLight;
mHasLightNavigationBar = isLight(appearance, navigationBarMode,
APPEARANCE_LIGHT_NAVIGATION_BARS);
- if (mUseNewLightBarLogic) {
- final boolean ignoreScrimForce = mDirectReplying && mNavbarColorManagedByIme;
- final boolean darkForScrim = mForceDarkForScrim && !ignoreScrimForce;
- final boolean lightForScrim = mForceLightForScrim && !ignoreScrimForce;
- final boolean darkForQs = (mQsCustomizing || mQsExpanded) && !mBouncerVisible;
- final boolean darkForTop = darkForQs || mGlobalActionsVisible;
- mNavigationLight =
- ((mHasLightNavigationBar && !darkForScrim) || lightForScrim) && !darkForTop;
- if (DEBUG_NAVBAR) {
- mLastNavigationBarAppearanceChangedLog = getLogStringBuilder()
- .append("onNavigationBarAppearanceChanged()")
- .append(" appearance=").append(appearance)
- .append(" nbModeChanged=").append(nbModeChanged)
- .append(" navigationBarMode=").append(navigationBarMode)
- .append(" navbarColorManagedByIme=").append(navbarColorManagedByIme)
- .append(" mHasLightNavigationBar=").append(mHasLightNavigationBar)
- .append(" ignoreScrimForce=").append(ignoreScrimForce)
- .append(" darkForScrim=").append(darkForScrim)
- .append(" lightForScrim=").append(lightForScrim)
- .append(" darkForQs=").append(darkForQs)
- .append(" darkForTop=").append(darkForTop)
- .append(" mNavigationLight=").append(mNavigationLight)
- .append(" last=").append(last)
- .append(" timestamp=").append(System.currentTimeMillis())
- .toString();
- if (DEBUG_LOGS) Log.d(TAG, mLastNavigationBarAppearanceChangedLog);
- }
- } else {
- mNavigationLight = mHasLightNavigationBar
- && (mDirectReplying && mNavbarColorManagedByIme || !mForceDarkForScrim)
- && !mQsCustomizing;
- if (DEBUG_NAVBAR) {
- mLastNavigationBarAppearanceChangedLog = getLogStringBuilder()
- .append("onNavigationBarAppearanceChanged()")
- .append(" appearance=").append(appearance)
- .append(" nbModeChanged=").append(nbModeChanged)
- .append(" navigationBarMode=").append(navigationBarMode)
- .append(" navbarColorManagedByIme=").append(navbarColorManagedByIme)
- .append(" mHasLightNavigationBar=").append(mHasLightNavigationBar)
- .append(" mNavigationLight=").append(mNavigationLight)
- .append(" last=").append(last)
- .append(" timestamp=").append(System.currentTimeMillis())
- .toString();
- if (DEBUG_LOGS) Log.d(TAG, mLastNavigationBarAppearanceChangedLog);
- }
+ final boolean ignoreScrimForce = mDirectReplying && mNavbarColorManagedByIme;
+ final boolean darkForScrim = mForceDarkForScrim && !ignoreScrimForce;
+ final boolean lightForScrim = mForceLightForScrim && !ignoreScrimForce;
+ final boolean darkForQs = (mQsCustomizing || mQsExpanded) && !mBouncerVisible;
+ final boolean darkForTop = darkForQs || mGlobalActionsVisible;
+ mNavigationLight =
+ ((mHasLightNavigationBar && !darkForScrim) || lightForScrim) && !darkForTop;
+ if (DEBUG_NAVBAR) {
+ mLastNavigationBarAppearanceChangedLog = getLogStringBuilder()
+ .append("onNavigationBarAppearanceChanged()")
+ .append(" appearance=").append(appearance)
+ .append(" nbModeChanged=").append(nbModeChanged)
+ .append(" navigationBarMode=").append(navigationBarMode)
+ .append(" navbarColorManagedByIme=").append(navbarColorManagedByIme)
+ .append(" mHasLightNavigationBar=").append(mHasLightNavigationBar)
+ .append(" ignoreScrimForce=").append(ignoreScrimForce)
+ .append(" darkForScrim=").append(darkForScrim)
+ .append(" lightForScrim=").append(lightForScrim)
+ .append(" darkForQs=").append(darkForQs)
+ .append(" darkForTop=").append(darkForTop)
+ .append(" mNavigationLight=").append(mNavigationLight)
+ .append(" last=").append(last)
+ .append(" timestamp=").append(System.currentTimeMillis())
+ .toString();
+ if (DEBUG_LOGS) Log.d(TAG, mLastNavigationBarAppearanceChangedLog);
}
if (mNavigationLight != last) {
updateNavigation();
@@ -311,66 +286,39 @@
public void setScrimState(ScrimState scrimState, float scrimBehindAlpha,
GradientColors scrimInFrontColor) {
- if (mUseNewLightBarLogic) {
- boolean bouncerVisibleLast = mBouncerVisible;
- boolean forceDarkForScrimLast = mForceDarkForScrim;
- boolean forceLightForScrimLast = mForceLightForScrim;
- mBouncerVisible =
- scrimState == ScrimState.BOUNCER || scrimState == ScrimState.BOUNCER_SCRIMMED;
- final boolean forceForScrim = mBouncerVisible
- || scrimBehindAlpha >= NAV_BAR_INVERSION_SCRIM_ALPHA_THRESHOLD;
- final boolean scrimColorIsLight = scrimInFrontColor.supportsDarkText();
+ boolean bouncerVisibleLast = mBouncerVisible;
+ boolean forceDarkForScrimLast = mForceDarkForScrim;
+ boolean forceLightForScrimLast = mForceLightForScrim;
+ mBouncerVisible =
+ scrimState == ScrimState.BOUNCER || scrimState == ScrimState.BOUNCER_SCRIMMED;
+ final boolean forceForScrim = mBouncerVisible
+ || scrimBehindAlpha >= NAV_BAR_INVERSION_SCRIM_ALPHA_THRESHOLD;
+ final boolean scrimColorIsLight = scrimInFrontColor.supportsDarkText();
- mForceDarkForScrim = forceForScrim && !scrimColorIsLight;
- mForceLightForScrim = forceForScrim && scrimColorIsLight;
- if (mBouncerVisible != bouncerVisibleLast) {
- reevaluate();
- } else if (mHasLightNavigationBar) {
- if (mForceDarkForScrim != forceDarkForScrimLast) reevaluate();
- } else {
- if (mForceLightForScrim != forceLightForScrimLast) reevaluate();
- }
- if (DEBUG_NAVBAR) {
- mLastSetScrimStateLog = getLogStringBuilder()
- .append("setScrimState()")
- .append(" scrimState=").append(scrimState)
- .append(" scrimBehindAlpha=").append(scrimBehindAlpha)
- .append(" scrimInFrontColor=").append(scrimInFrontColor)
- .append(" forceForScrim=").append(forceForScrim)
- .append(" scrimColorIsLight=").append(scrimColorIsLight)
- .append(" mHasLightNavigationBar=").append(mHasLightNavigationBar)
- .append(" mBouncerVisible=").append(mBouncerVisible)
- .append(" mForceDarkForScrim=").append(mForceDarkForScrim)
- .append(" mForceLightForScrim=").append(mForceLightForScrim)
- .append(" timestamp=").append(System.currentTimeMillis())
- .toString();
- if (DEBUG_LOGS) Log.d(TAG, mLastSetScrimStateLog);
- }
+ mForceDarkForScrim = forceForScrim && !scrimColorIsLight;
+ mForceLightForScrim = forceForScrim && scrimColorIsLight;
+ if (mBouncerVisible != bouncerVisibleLast) {
+ reevaluate();
+ } else if (mHasLightNavigationBar) {
+ if (mForceDarkForScrim != forceDarkForScrimLast) reevaluate();
} else {
- boolean forceDarkForScrimLast = mForceDarkForScrim;
- // For BOUNCER/BOUNCER_SCRIMMED cases, we assume that alpha is always below threshold.
- // This enables IMEs to control the navigation bar color.
- // For other cases, scrim should be able to veto the light navigation bar.
- // NOTE: this was also wrong for S and has been removed in the new logic.
- mForceDarkForScrim = scrimState != ScrimState.BOUNCER
- && scrimState != ScrimState.BOUNCER_SCRIMMED
- && scrimBehindAlpha >= NAV_BAR_INVERSION_SCRIM_ALPHA_THRESHOLD
- && !scrimInFrontColor.supportsDarkText();
- if (mHasLightNavigationBar && (mForceDarkForScrim != forceDarkForScrimLast)) {
- reevaluate();
- }
- if (DEBUG_NAVBAR) {
- mLastSetScrimStateLog = getLogStringBuilder()
- .append("setScrimState()")
- .append(" scrimState=").append(scrimState)
- .append(" scrimBehindAlpha=").append(scrimBehindAlpha)
- .append(" scrimInFrontColor=").append(scrimInFrontColor)
- .append(" mHasLightNavigationBar=").append(mHasLightNavigationBar)
- .append(" mForceDarkForScrim=").append(mForceDarkForScrim)
- .append(" timestamp=").append(System.currentTimeMillis())
- .toString();
- if (DEBUG_LOGS) Log.d(TAG, mLastSetScrimStateLog);
- }
+ if (mForceLightForScrim != forceLightForScrimLast) reevaluate();
+ }
+ if (DEBUG_NAVBAR) {
+ mLastSetScrimStateLog = getLogStringBuilder()
+ .append("setScrimState()")
+ .append(" scrimState=").append(scrimState)
+ .append(" scrimBehindAlpha=").append(scrimBehindAlpha)
+ .append(" scrimInFrontColor=").append(scrimInFrontColor)
+ .append(" forceForScrim=").append(forceForScrim)
+ .append(" scrimColorIsLight=").append(scrimColorIsLight)
+ .append(" mHasLightNavigationBar=").append(mHasLightNavigationBar)
+ .append(" mBouncerVisible=").append(mBouncerVisible)
+ .append(" mForceDarkForScrim=").append(mForceDarkForScrim)
+ .append(" mForceLightForScrim=").append(mForceLightForScrim)
+ .append(" timestamp=").append(System.currentTimeMillis())
+ .toString();
+ if (DEBUG_LOGS) Log.d(TAG, mLastSetScrimStateLog);
}
}
@@ -498,7 +446,6 @@
private final DarkIconDispatcher mDarkIconDispatcher;
private final BatteryController mBatteryController;
private final NavigationModeController mNavModeController;
- private final FeatureFlags mFeatureFlags;
private final DumpManager mDumpManager;
private final DisplayTracker mDisplayTracker;
@@ -507,14 +454,12 @@
DarkIconDispatcher darkIconDispatcher,
BatteryController batteryController,
NavigationModeController navModeController,
- FeatureFlags featureFlags,
DumpManager dumpManager,
DisplayTracker displayTracker) {
mDarkIconDispatcher = darkIconDispatcher;
mBatteryController = batteryController;
mNavModeController = navModeController;
- mFeatureFlags = featureFlags;
mDumpManager = dumpManager;
mDisplayTracker = displayTracker;
}
@@ -522,7 +467,7 @@
/** Create an {@link LightBarController} */
public LightBarController create(Context context) {
return new LightBarController(context, mDarkIconDispatcher, mBatteryController,
- mNavModeController, mFeatureFlags, mDumpManager, mDisplayTracker);
+ mNavModeController, mDumpManager, mDisplayTracker);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
index 2fd244e..e18c9d8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
@@ -33,14 +33,11 @@
import com.android.systemui.statusbar.NotificationShelfController;
import com.android.systemui.statusbar.StatusBarIconView;
import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.notification.AnimatableProperty;
import com.android.systemui.statusbar.notification.NotificationUtils;
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
-import com.android.systemui.statusbar.notification.PropertyAnimator;
import com.android.systemui.statusbar.notification.collection.ListEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider;
-import com.android.systemui.statusbar.notification.stack.AnimationProperties;
import com.android.systemui.statusbar.window.StatusBarWindowController;
import com.android.wm.shell.bubbles.Bubbles;
@@ -91,8 +88,6 @@
private final ArrayList<Rect> mTintAreas = new ArrayList<>();
private final Context mContext;
- private final DemoModeController mDemoModeController;
-
private final FeatureFlags mFeatureFlags;
private int mAodIconAppearTranslation;
@@ -139,8 +134,7 @@
wakeUpCoordinator.addListener(this);
mBypassController = keyguardBypassController;
mBubblesOptional = bubblesOptional;
- mDemoModeController = demoModeController;
- mDemoModeController.addCallback(this);
+ demoModeController.addCallback(this);
mStatusBarWindowController = statusBarWindowController;
mScreenOffAnimationController = screenOffAnimationController;
notificationListener.addNotificationSettingsListener(mSettingsListener);
@@ -163,7 +157,6 @@
LayoutInflater layoutInflater = LayoutInflater.from(context);
mNotificationIconArea = inflateIconArea(layoutInflater);
mNotificationIcons = mNotificationIconArea.findViewById(R.id.notificationIcons);
-
}
/**
@@ -185,16 +178,6 @@
updateIconLayoutParams(mContext);
}
- /**
- * Update position of the view, with optional animation
- */
- public void updatePosition(int x, AnimationProperties props, boolean animate) {
- if (mAodIcons != null) {
- PropertyAnimator.setProperty(mAodIcons, AnimatableProperty.TRANSLATION_X, x, props,
- animate);
- }
- }
-
public void setupShelf(NotificationShelfController notificationShelfController) {
NotificationShelfController.assertRefactorFlagDisabled(mFeatureFlags);
mShelfIcons = notificationShelfController.getShelfIcons();
@@ -239,7 +222,7 @@
private void reloadDimens(Context context) {
Resources res = context.getResources();
- mIconSize = res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_icon_size);
+ mIconSize = res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_icon_size_sp);
mIconHPadding = res.getDimensionPixelSize(R.dimen.status_bar_icon_horizontal_margin);
mAodIconAppearTranslation = res.getDimensionPixelSize(
R.dimen.shelf_appear_translation);
@@ -303,6 +286,7 @@
}
return true;
}
+
/**
* Updates the notifications with the given list of notifications to display.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
index bef422c..3770c1d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
@@ -54,19 +54,13 @@
* correctly on the screen.
*/
public class NotificationIconContainer extends ViewGroup {
- /**
- * A float value indicating how much before the overflow start the icons should transform into
- * a dot. A value of 0 means that they are exactly at the end and a value of 1 means it starts
- * 1 icon width early.
- */
- public static final float OVERFLOW_EARLY_AMOUNT = 0.2f;
private static final int NO_VALUE = Integer.MIN_VALUE;
private static final String TAG = "NotificationIconContainer";
private static final boolean DEBUG = false;
private static final boolean DEBUG_OVERFLOW = false;
private static final int CANNED_ANIMATION_DURATION = 100;
private static final AnimationProperties DOT_ANIMATION_PROPERTIES = new AnimationProperties() {
- private AnimationFilter mAnimationFilter = new AnimationFilter().animateX();
+ private final AnimationFilter mAnimationFilter = new AnimationFilter().animateX();
@Override
public AnimationFilter getAnimationFilter() {
@@ -75,7 +69,7 @@
}.setDuration(200);
private static final AnimationProperties ICON_ANIMATION_PROPERTIES = new AnimationProperties() {
- private AnimationFilter mAnimationFilter = new AnimationFilter()
+ private final AnimationFilter mAnimationFilter = new AnimationFilter()
.animateX()
.animateY()
.animateAlpha()
@@ -92,7 +86,7 @@
* Temporary AnimationProperties to avoid unnecessary allocations.
*/
private static final AnimationProperties sTempProperties = new AnimationProperties() {
- private AnimationFilter mAnimationFilter = new AnimationFilter();
+ private final AnimationFilter mAnimationFilter = new AnimationFilter();
@Override
public AnimationFilter getAnimationFilter() {
@@ -101,7 +95,7 @@
};
private static final AnimationProperties ADD_ICON_PROPERTIES = new AnimationProperties() {
- private AnimationFilter mAnimationFilter = new AnimationFilter().animateAlpha();
+ private final AnimationFilter mAnimationFilter = new AnimationFilter().animateAlpha();
@Override
public AnimationFilter getAnimationFilter() {
@@ -115,7 +109,7 @@
*/
private static final AnimationProperties UNISOLATION_PROPERTY_OTHERS
= new AnimationProperties() {
- private AnimationFilter mAnimationFilter = new AnimationFilter().animateAlpha();
+ private final AnimationFilter mAnimationFilter = new AnimationFilter().animateAlpha();
@Override
public AnimationFilter getAnimationFilter() {
@@ -128,7 +122,7 @@
* This animates the translation back to the right position.
*/
private static final AnimationProperties UNISOLATION_PROPERTY = new AnimationProperties() {
- private AnimationFilter mAnimationFilter = new AnimationFilter().animateX();
+ private final AnimationFilter mAnimationFilter = new AnimationFilter().animateX();
@Override
public AnimationFilter getAnimationFilter() {
@@ -147,7 +141,6 @@
private boolean mIsStaticLayout = true;
private final HashMap<View, IconState> mIconStates = new HashMap<>();
private int mDotPadding;
- private int mStaticDotRadius;
private int mStaticDotDiameter;
private int mActualLayoutWidth = NO_VALUE;
private float mActualPaddingEnd = NO_VALUE;
@@ -170,7 +163,7 @@
private boolean mIsShowingOverflowDot;
private StatusBarIconView mIsolatedIcon;
private Rect mIsolatedIconLocation;
- private int[] mAbsolutePosition = new int[2];
+ private final int[] mAbsolutePosition = new int[2];
private View mIsolatedIconForAnimation;
private int mThemedTextColorPrimary;
@@ -186,8 +179,8 @@
mMaxStaticIcons = getResources().getInteger(R.integer.max_notif_static_icons);
mDotPadding = getResources().getDimensionPixelSize(R.dimen.overflow_icon_dot_padding);
- mStaticDotRadius = getResources().getDimensionPixelSize(R.dimen.overflow_dot_radius);
- mStaticDotDiameter = 2 * mStaticDotRadius;
+ int staticDotRadius = getResources().getDimensionPixelSize(R.dimen.overflow_dot_radius);
+ mStaticDotDiameter = 2 * staticDotRadius;
final Context themedContext = new ContextThemeWrapper(getContext(),
com.android.internal.R.style.Theme_DeviceDefault_DayNight);
@@ -339,6 +332,7 @@
}
}
if (child instanceof StatusBarIconView) {
+ ((StatusBarIconView) child).updateIconDimens();
((StatusBarIconView) child).setDozing(mDozing, false, 0);
}
}
@@ -447,9 +441,14 @@
@VisibleForTesting
boolean isOverflowing(boolean isLastChild, float translationX, float layoutEnd,
float iconSize) {
- // Layout end, as used here, does not include padding end.
- final float overflowX = isLastChild ? layoutEnd : layoutEnd - iconSize;
- return translationX >= overflowX;
+ if (isLastChild) {
+ return translationX + iconSize > layoutEnd;
+ } else {
+ // If the child is not the last child, we need to ensure that we have room for the next
+ // icon and the dot. The dot could be as large as an icon, so verify that we have room
+ // for 2 icons.
+ return translationX + iconSize * 2f > layoutEnd;
+ }
}
/**
@@ -489,10 +488,7 @@
// First icon to overflow.
if (firstOverflowIndex == -1 && isOverflowing) {
firstOverflowIndex = i;
- mVisualOverflowStart = layoutEnd - mIconSize;
- if (forceOverflow || mIsStaticLayout) {
- mVisualOverflowStart = Math.min(translationX, mVisualOverflowStart);
- }
+ mVisualOverflowStart = translationX;
}
final float drawingScale = mOnLockScreen && view instanceof StatusBarIconView
? ((StatusBarIconView) view).getIconScaleIncreased()
@@ -537,9 +533,10 @@
IconState iconState = mIconStates.get(mIsolatedIcon);
if (iconState != null) {
// Most of the time the icon isn't yet added when this is called but only happening
- // later
- iconState.setXTranslation(mIsolatedIconLocation.left - mAbsolutePosition[0]
- - (1 - mIsolatedIcon.getIconScale()) * mIsolatedIcon.getWidth() / 2.0f);
+ // later. The isolated icon position left should equal to the mIsolatedIconLocation
+ // to ensure the icon be put at the center of the HUN icon placeholder,
+ // {@See HeadsUpAppearanceController#updateIsolatedIconLocation}.
+ iconState.setXTranslation(mIsolatedIconLocation.left - mAbsolutePosition[0]);
iconState.visibleState = StatusBarIconView.STATE_ICON;
}
}
@@ -695,7 +692,6 @@
}
public class IconState extends ViewState {
- public static final int NO_VALUE = NotificationIconContainer.NO_VALUE;
public float iconAppearAmount = 1.0f;
public float clampedAppearAmount = 1.0f;
public int visibleState;
@@ -813,8 +809,6 @@
super.applyToView(view);
}
sTempProperties.setAnimationEndAction(null);
- boolean inShelf = iconAppearAmount == 1.0f;
- icon.setIsInShelf(inShelf);
}
justAdded = false;
justReplaced = false;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
index f0fc143..862f169 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
@@ -30,6 +30,7 @@
import com.android.systemui.flags.Flags
import com.android.systemui.shade.ShadeController
import com.android.systemui.shade.ShadeLogger
+import com.android.systemui.shade.ShadeViewController
import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.unfold.SysUIUnfoldComponent
@@ -51,6 +52,7 @@
@Named(UNFOLD_STATUS_BAR) private val progressProvider: ScopedUnfoldTransitionProgressProvider?,
private val centralSurfaces: CentralSurfaces,
private val shadeController: ShadeController,
+ private val shadeViewController: ShadeViewController,
private val shadeLogger: ShadeLogger,
private val moveFromCenterAnimationController: StatusBarMoveFromCenterAnimationController?,
private val userChipViewModel: StatusBarUserChipViewModel,
@@ -165,20 +167,20 @@
if (event.action == MotionEvent.ACTION_DOWN) {
// If the view that would receive the touch is disabled, just have status
// bar eat the gesture.
- if (!centralSurfaces.shadeViewController.isViewEnabled) {
+ if (!shadeViewController.isViewEnabled) {
shadeLogger.logMotionEvent(event,
"onTouchForwardedFromStatusBar: panel view disabled")
return true
}
- if (centralSurfaces.shadeViewController.isFullyCollapsed &&
+ if (shadeViewController.isFullyCollapsed &&
event.y < 1f) {
// b/235889526 Eat events on the top edge of the phone when collapsed
shadeLogger.logMotionEvent(event, "top edge touch ignored")
return true
}
- centralSurfaces.shadeViewController.startTrackingExpansionFromStatusBar()
+ shadeViewController.startTrackingExpansionFromStatusBar()
}
- return centralSurfaces.shadeViewController.handleExternalTouch(event)
+ return shadeViewController.handleExternalTouch(event)
}
}
@@ -222,6 +224,7 @@
private val userChipViewModel: StatusBarUserChipViewModel,
private val centralSurfaces: CentralSurfaces,
private val shadeController: ShadeController,
+ private val shadeViewController: ShadeViewController,
private val shadeLogger: ShadeLogger,
private val viewUtil: ViewUtil,
private val configurationController: ConfigurationController,
@@ -241,6 +244,7 @@
progressProvider.getOrNull(),
centralSurfaces,
shadeController,
+ shadeViewController,
shadeLogger,
statusBarMoveFromCenterAnimationController,
userChipViewModel,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index c16e13c..e82ac59 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -48,18 +48,17 @@
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.settingslib.Utils;
+import com.android.systemui.CoreStartable;
import com.android.systemui.DejankUtils;
import com.android.systemui.Dumpable;
import com.android.systemui.R;
import com.android.systemui.animation.ShadeInterpolation;
+import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dock.DockManager;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
-import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants;
import com.android.systemui.keyguard.shared.model.ScrimAlpha;
import com.android.systemui.keyguard.shared.model.TransitionState;
import com.android.systemui.keyguard.shared.model.TransitionStep;
@@ -71,8 +70,10 @@
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.AlarmTimeout;
+import com.android.systemui.util.kotlin.JavaAdapter;
import com.android.systemui.util.wakelock.DelayedWakeLock;
import com.android.systemui.util.wakelock.WakeLock;
+import com.android.systemui.wallpapers.data.repository.WallpaperRepository;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
@@ -89,7 +90,8 @@
* security method gets shown).
*/
@SysUISingleton
-public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dumpable {
+public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dumpable,
+ CoreStartable {
static final String TAG = "ScrimController";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
@@ -207,6 +209,7 @@
private final KeyguardVisibilityCallback mKeyguardVisibilityCallback;
private final Handler mHandler;
private final Executor mMainExecutor;
+ private final JavaAdapter mJavaAdapter;
private final ScreenOffAnimationController mScreenOffAnimationController;
private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
@@ -249,8 +252,6 @@
private int mScrimsVisibility;
private final TriConsumer<ScrimState, Float, GradientColors> mScrimStateListener;
private final LargeScreenShadeInterpolator mLargeScreenShadeInterpolator;
- private final FeatureFlags mFeatureFlags;
- private final boolean mUseNewLightBarLogic;
private Consumer<Integer> mScrimVisibleListener;
private boolean mBlankScreen;
private boolean mScreenBlankingCallbackCalled;
@@ -268,6 +269,7 @@
private boolean mKeyguardOccluded;
private KeyguardTransitionInteractor mKeyguardTransitionInteractor;
+ private final WallpaperRepository mWallpaperRepository;
private CoroutineDispatcher mMainDispatcher;
private boolean mIsBouncerToGoneTransitionRunning = false;
private PrimaryBouncerToGoneTransitionViewModel mPrimaryBouncerToGoneTransitionViewModel;
@@ -297,18 +299,17 @@
DockManager dockManager,
ConfigurationController configurationController,
@Main Executor mainExecutor,
+ JavaAdapter javaAdapter,
ScreenOffAnimationController screenOffAnimationController,
KeyguardUnlockAnimationController keyguardUnlockAnimationController,
StatusBarKeyguardViewManager statusBarKeyguardViewManager,
PrimaryBouncerToGoneTransitionViewModel primaryBouncerToGoneTransitionViewModel,
KeyguardTransitionInteractor keyguardTransitionInteractor,
+ WallpaperRepository wallpaperRepository,
@Main CoroutineDispatcher mainDispatcher,
- LargeScreenShadeInterpolator largeScreenShadeInterpolator,
- FeatureFlags featureFlags) {
+ LargeScreenShadeInterpolator largeScreenShadeInterpolator) {
mScrimStateListener = lightBarController::setScrimState;
mLargeScreenShadeInterpolator = largeScreenShadeInterpolator;
- mFeatureFlags = featureFlags;
- mUseNewLightBarLogic = featureFlags.isEnabled(Flags.NEW_LIGHT_BAR_LOGIC);
mDefaultScrimAlpha = BUSY_SCRIM_ALPHA;
mKeyguardStateController = keyguardStateController;
@@ -317,6 +318,7 @@
mKeyguardVisibilityCallback = new KeyguardVisibilityCallback();
mHandler = handler;
mMainExecutor = mainExecutor;
+ mJavaAdapter = javaAdapter;
mScreenOffAnimationController = screenOffAnimationController;
mTimeTicker = new AlarmTimeout(alarmManager, this::onHideWallpaperTimeout,
"hide_aod_wallpaper", mHandler);
@@ -348,9 +350,17 @@
mColors = new GradientColors();
mPrimaryBouncerToGoneTransitionViewModel = primaryBouncerToGoneTransitionViewModel;
mKeyguardTransitionInteractor = keyguardTransitionInteractor;
+ mWallpaperRepository = wallpaperRepository;
mMainDispatcher = mainDispatcher;
}
+ @Override
+ public void start() {
+ mJavaAdapter.alwaysCollectFlow(
+ mWallpaperRepository.getWallpaperSupportsAmbientMode(),
+ this::setWallpaperSupportsAmbientMode);
+ }
+
/**
* Attach the controller to the supplied views.
*/
@@ -1153,13 +1163,7 @@
if (mClipsQsScrim && mQsBottomVisible) {
alpha = mNotificationsAlpha;
}
- if (mUseNewLightBarLogic) {
- mScrimStateListener.accept(mState, alpha, mColors);
- } else {
- // NOTE: This wasn't wrong, but it implied that each scrim might have different colors,
- // when in fact they all share the same GradientColors instance, which we own.
- mScrimStateListener.accept(mState, alpha, mScrimInFront.getColors());
- }
+ mScrimStateListener.accept(mState, alpha, mColors);
}
private void dispatchScrimsVisible() {
@@ -1483,15 +1487,15 @@
int accent = Utils.getColorAccent(mScrimBehind.getContext()).getDefaultColor();
mColors.setMainColor(background);
mColors.setSecondaryColor(accent);
- if (mUseNewLightBarLogic) {
- final boolean isBackgroundLight = !ContrastColorUtil.isColorDark(background);
- mColors.setSupportsDarkText(isBackgroundLight);
- } else {
- // NOTE: This was totally backward, but LightBarController was flipping it back.
- // There may be other consumers of this which would struggle though
- mColors.setSupportsDarkText(
- ColorUtils.calculateContrast(mColors.getMainColor(), Color.WHITE) > 4.5);
+ final boolean isBackgroundLight = !ContrastColorUtil.isColorDark(background);
+ mColors.setSupportsDarkText(isBackgroundLight);
+
+ int surface = Utils.getColorAttr(mScrimBehind.getContext(),
+ com.android.internal.R.attr.materialColorSurface).getDefaultColor();
+ for (ScrimState state : ScrimState.values()) {
+ state.setSurfaceColor(surface);
}
+
mNeedsDrawableColorUpdate = true;
}
@@ -1544,7 +1548,7 @@
pw.println(mState.getMaxLightRevealScrimAlpha());
}
- public void setWallpaperSupportsAmbientMode(boolean wallpaperSupportsAmbientMode) {
+ private void setWallpaperSupportsAmbientMode(boolean wallpaperSupportsAmbientMode) {
mWallpaperSupportsAmbientMode = wallpaperSupportsAmbientMode;
ScrimState[] states = ScrimState.values();
for (int i = 0; i < states.length; i++) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
index 7b20283..e3b65ab 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
@@ -122,11 +122,19 @@
@Override
public void prepare(ScrimState previousState) {
mBehindAlpha = mClipQsScrim ? 1 : mDefaultScrimAlpha;
- mBehindTint = mClipQsScrim ? Color.BLACK : Color.TRANSPARENT;
+ mBehindTint = mClipQsScrim ? Color.BLACK : mSurfaceColor;
mNotifAlpha = mClipQsScrim ? mDefaultScrimAlpha : 0;
mNotifTint = Color.TRANSPARENT;
mFrontAlpha = 0f;
}
+
+ @Override
+ public void setSurfaceColor(int surfaceColor) {
+ super.setSurfaceColor(surfaceColor);
+ if (!mClipQsScrim) {
+ mBehindTint = mSurfaceColor;
+ }
+ }
},
/**
@@ -295,6 +303,7 @@
int mFrontTint = Color.TRANSPARENT;
int mBehindTint = Color.TRANSPARENT;
int mNotifTint = Color.TRANSPARENT;
+ int mSurfaceColor = Color.TRANSPARENT;
boolean mAnimateChange = true;
float mAodFrontScrimAlpha;
@@ -409,6 +418,10 @@
mDefaultScrimAlpha = defaultScrimAlpha;
}
+ public void setSurfaceColor(int surfaceColor) {
+ mSurfaceColor = surfaceColor;
+ }
+
public void setWallpaperSupportsAmbientMode(boolean wallpaperSupportsAmbientMode) {
mWallpaperSupportsAmbientMode = wallpaperSupportsAmbientMode;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java
index 481cf3c..9a295e6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java
@@ -21,6 +21,7 @@
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent;
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
import com.android.systemui.statusbar.window.StatusBarWindowController;
@@ -35,6 +36,7 @@
private final NotificationShadeWindowController mNotificationShadeWindowController;
private final StatusBarWindowController mStatusBarWindowController;
private final ShadeViewController mShadeViewController;
+ private final NotificationStackScrollLayoutController mNsslController;
private final KeyguardBypassController mKeyguardBypassController;
private final HeadsUpManagerPhone mHeadsUpManager;
private final StatusBarStateController mStatusBarStateController;
@@ -45,14 +47,15 @@
NotificationShadeWindowController notificationShadeWindowController,
StatusBarWindowController statusBarWindowController,
ShadeViewController shadeViewController,
+ NotificationStackScrollLayoutController nsslController,
KeyguardBypassController keyguardBypassController,
HeadsUpManagerPhone headsUpManager,
StatusBarStateController statusBarStateController,
NotificationRemoteInputManager notificationRemoteInputManager) {
-
mNotificationShadeWindowController = notificationShadeWindowController;
mStatusBarWindowController = statusBarWindowController;
mShadeViewController = shadeViewController;
+ mNsslController = nsslController;
mKeyguardBypassController = keyguardBypassController;
mHeadsUpManager = headsUpManager;
mStatusBarStateController = statusBarStateController;
@@ -85,8 +88,7 @@
//animation
// is finished.
mHeadsUpManager.setHeadsUpGoingAway(true);
- mShadeViewController.getNotificationStackScrollLayoutController()
- .runAfterAnimationFinished(() -> {
+ mNsslController.runAfterAnimationFinished(() -> {
if (!mHeadsUpManager.hasPinnedHeadsUp()) {
mNotificationShadeWindowController.setHeadsUpShowing(false);
mHeadsUpManager.setHeadsUpGoingAway(false);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
index b14fe90..42b0a4f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
@@ -17,7 +17,6 @@
import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_ICON;
import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_MOBILE;
import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_MOBILE_NEW;
-import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_WIFI;
import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_WIFI_NEW;
import android.annotation.Nullable;
@@ -43,12 +42,10 @@
import com.android.systemui.statusbar.BaseStatusBarFrameLayout;
import com.android.systemui.statusbar.StatusBarIconView;
import com.android.systemui.statusbar.StatusBarMobileView;
-import com.android.systemui.statusbar.StatusBarWifiView;
import com.android.systemui.statusbar.StatusIconDisplayable;
import com.android.systemui.statusbar.connectivity.ui.MobileContextProvider;
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.CallIndicatorIconState;
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
-import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags;
import com.android.systemui.statusbar.pipeline.mobile.ui.MobileUiAdapter;
import com.android.systemui.statusbar.pipeline.mobile.ui.binder.MobileIconsBinder;
@@ -97,16 +94,9 @@
*/
void setIcon(String slot, int resourceId, CharSequence contentDescription);
- /** */
- void setWifiIcon(String slot, WifiIconState state);
-
/**
* Sets up a wifi icon using the new data pipeline. No effect if the wifi icon has already been
* set up (inflated and added to the view hierarchy).
- *
- * This method completely replaces {@link #setWifiIcon} with the information from the new wifi
- * data pipeline. Icons will automatically keep their state up to date, so we don't have to
- * worry about funneling state objects through anymore.
*/
void setNewWifiIcon();
@@ -370,7 +360,7 @@
private final MobileIconsViewModel mMobileIconsViewModel;
protected final Context mContext;
- protected final int mIconSize;
+ protected int mIconSize;
// Whether or not these icons show up in dumpsys
protected boolean mShouldLog = false;
private StatusBarIconController mController;
@@ -395,10 +385,10 @@
mStatusBarPipelineFlags = statusBarPipelineFlags;
mMobileContextProvider = mobileContextProvider;
mContext = group.getContext();
- mIconSize = mContext.getResources().getDimensionPixelSize(
- com.android.internal.R.dimen.status_bar_icon_size);
mLocation = location;
+ reloadDimens();
+
if (statusBarPipelineFlags.runNewMobileIconsBackend()) {
// This starts the flow for the new pipeline, and will notify us of changes if
// {@link StatusBarPipelineFlags#useNewMobileIcons} is also true.
@@ -408,13 +398,7 @@
mMobileIconsViewModel = null;
}
- if (statusBarPipelineFlags.runNewWifiIconBackend()) {
- // This starts the flow for the new pipeline, and will notify us of changes if
- // {@link StatusBarPipelineFlags#useNewWifiIcon} is also true.
- mWifiViewModel = wifiUiAdapter.bindGroup(mGroup, mLocation);
- } else {
- mWifiViewModel = null;
- }
+ mWifiViewModel = wifiUiAdapter.bindGroup(mGroup, mLocation);
}
public boolean isDemoable() {
@@ -462,9 +446,6 @@
case TYPE_ICON:
return addIcon(index, slot, blocked, holder.getIcon());
- case TYPE_WIFI:
- return addWifiIcon(index, slot, holder.getWifiState());
-
case TYPE_WIFI_NEW:
return addNewWifiIcon(index, slot);
@@ -487,29 +468,7 @@
return view;
}
- @VisibleForTesting
- protected StatusIconDisplayable addWifiIcon(int index, String slot, WifiIconState state) {
- if (mStatusBarPipelineFlags.useNewWifiIcon()) {
- throw new IllegalStateException("Attempting to add a wifi icon while the new "
- + "icons are enabled is not supported");
- }
-
- final StatusBarWifiView view = onCreateStatusBarWifiView(slot);
- view.applyWifiState(state);
- mGroup.addView(view, index, onCreateLayoutParams());
-
- if (mIsInDemoMode) {
- mDemoStatusIcons.addDemoWifiView(state);
- }
- return view;
- }
-
protected StatusIconDisplayable addNewWifiIcon(int index, String slot) {
- if (!mStatusBarPipelineFlags.useNewWifiIcon()) {
- throw new IllegalStateException("Attempting to add a wifi icon using the new"
- + "pipeline, but the enabled flag is false.");
- }
-
ModernStatusBarWifiView view = onCreateModernStatusBarWifiView(slot);
mGroup.addView(view, index, onCreateLayoutParams());
@@ -573,11 +532,6 @@
return new StatusBarIconView(mContext, slot, null, blocked);
}
- private StatusBarWifiView onCreateStatusBarWifiView(String slot) {
- StatusBarWifiView view = StatusBarWifiView.fromContext(mContext, slot);
- return view;
- }
-
private ModernStatusBarWifiView onCreateModernStatusBarWifiView(String slot) {
return ModernStatusBarWifiView.constructAndBind(mContext, slot, mWifiViewModel);
}
@@ -609,13 +563,9 @@
mGroup.removeAllViews();
}
- protected void onDensityOrFontScaleChanged() {
- for (int i = 0; i < mGroup.getChildCount(); i++) {
- View child = mGroup.getChildAt(i);
- LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
- ViewGroup.LayoutParams.WRAP_CONTENT, mIconSize);
- child.setLayoutParams(lp);
- }
+ protected void reloadDimens() {
+ mIconSize = mContext.getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.status_bar_icon_size_sp);
}
private void setHeightAndCenter(ImageView imageView, int height) {
@@ -644,9 +594,6 @@
case TYPE_ICON:
onSetIcon(viewIndex, holder.getIcon());
return;
- case TYPE_WIFI:
- onSetWifiIcon(viewIndex, holder.getWifiState());
- return;
case TYPE_MOBILE:
onSetMobileIcon(viewIndex, holder.getMobileState());
return;
@@ -659,23 +606,6 @@
}
}
- public void onSetWifiIcon(int viewIndex, WifiIconState state) {
- View view = mGroup.getChildAt(viewIndex);
- if (view instanceof StatusBarWifiView) {
- ((StatusBarWifiView) view).applyWifiState(state);
- } else if (view instanceof ModernStatusBarWifiView) {
- // ModernStatusBarWifiView will automatically apply state based on its callbacks, so
- // we don't need to call applyWifiState.
- } else {
- throw new IllegalStateException("View at " + viewIndex + " must be of type "
- + "StatusBarWifiView or ModernStatusBarWifiView");
- }
-
- if (mIsInDemoMode) {
- mDemoStatusIcons.updateWifiState(state);
- }
- }
-
public void onSetMobileIcon(int viewIndex, MobileIconState state) {
View view = mGroup.getChildAt(viewIndex);
if (view instanceof StatusBarMobileView) {
@@ -707,9 +637,7 @@
mIsInDemoMode = true;
if (mDemoStatusIcons == null) {
mDemoStatusIcons = createDemoStatusIcons();
- if (mStatusBarPipelineFlags.useNewWifiIcon()) {
- mDemoStatusIcons.addModernWifiView(mWifiViewModel);
- }
+ mDemoStatusIcons.addModernWifiView(mWifiViewModel);
}
mDemoStatusIcons.onDemoModeStarted();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
index 3a18423..d1a02d6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
@@ -40,7 +40,6 @@
import com.android.systemui.statusbar.StatusIconDisplayable;
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.CallIndicatorIconState;
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
-import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
@@ -109,6 +108,7 @@
}
group.setController(this);
+ group.reloadDimens();
mIconGroups.add(group);
List<Slot> allSlots = mStatusBarIconList.getSlots();
for (int i = 0; i < allSlots.size(); i++) {
@@ -201,35 +201,7 @@
}
@Override
- public void setWifiIcon(String slot, WifiIconState state) {
- if (mStatusBarPipelineFlags.useNewWifiIcon()) {
- Log.d(TAG, "ignoring old pipeline callback because the new wifi icon is enabled");
- return;
- }
-
- if (state == null) {
- removeIcon(slot, 0);
- return;
- }
-
- StatusBarIconHolder holder = mStatusBarIconList.getIconHolder(slot, 0);
- if (holder == null) {
- holder = StatusBarIconHolder.fromWifiIconState(state);
- setIcon(slot, holder);
- } else {
- holder.setWifiState(state);
- handleSet(slot, holder);
- }
- }
-
-
- @Override
public void setNewWifiIcon() {
- if (!mStatusBarPipelineFlags.useNewWifiIcon()) {
- Log.d(TAG, "ignoring new pipeline callback because the new wifi icon is disabled");
- return;
- }
-
String slot = mContext.getString(com.android.internal.R.string.status_bar_wifi);
StatusBarIconHolder holder = mStatusBarIconList.getIconHolder(slot, /* tag= */ 0);
if (holder == null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java
index 833cb93..01fd247 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java
@@ -25,7 +25,6 @@
import com.android.internal.statusbar.StatusBarIcon;
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.CallIndicatorIconState;
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
-import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconViewModel;
import java.lang.annotation.Retention;
@@ -36,7 +35,6 @@
*/
public class StatusBarIconHolder {
public static final int TYPE_ICON = 0;
- public static final int TYPE_WIFI = 1;
public static final int TYPE_MOBILE = 2;
/**
* TODO (b/249790733): address this once the new pipeline is in place
@@ -65,7 +63,6 @@
@IntDef({
TYPE_ICON,
- TYPE_WIFI,
TYPE_MOBILE,
TYPE_MOBILE_NEW,
TYPE_WIFI_NEW
@@ -74,7 +71,6 @@
@interface IconType {}
private StatusBarIcon mIcon;
- private WifiIconState mWifiState;
private MobileIconState mMobileState;
private @IconType int mType = TYPE_ICON;
private int mTag = 0;
@@ -83,7 +79,6 @@
public static String getTypeString(@IconType int type) {
switch(type) {
case TYPE_ICON: return "ICON";
- case TYPE_WIFI: return "WIFI_OLD";
case TYPE_MOBILE: return "MOBILE_OLD";
case TYPE_MOBILE_NEW: return "MOBILE_NEW";
case TYPE_WIFI_NEW: return "WIFI_NEW";
@@ -101,25 +96,6 @@
return wrapper;
}
- /** */
- public static StatusBarIconHolder fromResId(
- Context context,
- int resId,
- CharSequence contentDescription) {
- StatusBarIconHolder holder = new StatusBarIconHolder();
- holder.mIcon = new StatusBarIcon(UserHandle.SYSTEM, context.getPackageName(),
- Icon.createWithResource( context, resId), 0, 0, contentDescription);
- return holder;
- }
-
- /** */
- public static StatusBarIconHolder fromWifiIconState(WifiIconState state) {
- StatusBarIconHolder holder = new StatusBarIconHolder();
- holder.mWifiState = state;
- holder.mType = TYPE_WIFI;
- return holder;
- }
-
/** Creates a new holder with for the new wifi icon. */
public static StatusBarIconHolder forNewWifiIcon() {
StatusBarIconHolder holder = new StatusBarIconHolder();
@@ -178,15 +154,6 @@
}
@Nullable
- public WifiIconState getWifiState() {
- return mWifiState;
- }
-
- public void setWifiState(WifiIconState state) {
- mWifiState = state;
- }
-
- @Nullable
public MobileIconState getMobileState() {
return mMobileState;
}
@@ -199,8 +166,6 @@
switch (mType) {
case TYPE_ICON:
return mIcon.visible;
- case TYPE_WIFI:
- return mWifiState.visible;
case TYPE_MOBILE:
return mMobileState.visible;
case TYPE_MOBILE_NEW:
@@ -223,10 +188,6 @@
mIcon.visible = visible;
break;
- case TYPE_WIFI:
- mWifiState.visible = visible;
- break;
-
case TYPE_MOBILE:
mMobileState.visible = visible;
break;
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 cb2a78d..5c1dfbe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -51,6 +51,7 @@
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.keyguard.KeyguardViewController;
+import com.android.keyguard.TrustGrantFlags;
import com.android.keyguard.ViewMediatorCallback;
import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor;
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
@@ -306,6 +307,16 @@
@Nullable private TaskbarDelegate mTaskbarDelegate;
private final KeyguardUpdateMonitorCallback mUpdateMonitorCallback =
new KeyguardUpdateMonitorCallback() {
+ @Override
+ public void onTrustGrantedForCurrentUser(
+ boolean dismissKeyguard,
+ boolean newlyUnlocked,
+ @NonNull TrustGrantFlags flags,
+ @Nullable String message
+ ) {
+ updateAlternateBouncerShowing(mAlternateBouncerInteractor.maybeHide());
+ }
+
@Override
public void onEmergencyCallAction() {
// Since we won't get a setOccluded call we have to reset the view manually such that
@@ -431,7 +442,6 @@
mDockManager.addListener(mDockEventListener);
mIsDocked = mDockManager.isDocked();
}
- mKeyguardStateController.addCallback(mKeyguardStateControllerCallback);
}
/** Register a callback, to be invoked by the Predictive Back system. */
@@ -1570,14 +1580,6 @@
|| mode == KeyguardSecurityModel.SecurityMode.SimPuk;
}
- private KeyguardStateController.Callback mKeyguardStateControllerCallback =
- new KeyguardStateController.Callback() {
- @Override
- public void onUnlockedChanged() {
- updateAlternateBouncerShowing(mAlternateBouncerInteractor.maybeHide());
- }
- };
-
/**
* Delegate used to send show and hide events to an alternate authentication method instead of
* the regular pin/pattern/password bouncer.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
index f79a081..ec0c00e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -54,8 +54,10 @@
import com.android.systemui.EventLogTags;
import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.assist.AssistManager;
+import com.android.systemui.dagger.qualifiers.DisplayId;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.power.domain.interactor.PowerInteractor;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.shade.ShadeViewController;
@@ -94,6 +96,7 @@
class StatusBarNotificationActivityStarter implements NotificationActivityStarter {
private final Context mContext;
+ private final int mDisplayId;
private final Handler mMainThreadHandler;
private final Executor mUiBgExecutor;
@@ -120,12 +123,12 @@
private final MetricsLogger mMetricsLogger;
private final StatusBarNotificationActivityStarterLogger mLogger;
- private final CentralSurfaces mCentralSurfaces;
private final NotificationPresenter mPresenter;
private final ShadeViewController mShadeViewController;
private final NotificationShadeWindowController mNotificationShadeWindowController;
private final ActivityLaunchAnimator mActivityLaunchAnimator;
private final NotificationLaunchAnimatorControllerProvider mNotificationAnimationProvider;
+ private final PowerInteractor mPowerInteractor;
private final UserTracker mUserTracker;
private final OnUserInteractionCallback mOnUserInteractionCallback;
@@ -134,6 +137,7 @@
@Inject
StatusBarNotificationActivityStarter(
Context context,
+ @DisplayId int displayId,
Handler mainThreadHandler,
Executor uiBgExecutor,
NotificationVisibilityProvider visibilityProvider,
@@ -156,16 +160,17 @@
MetricsLogger metricsLogger,
StatusBarNotificationActivityStarterLogger logger,
OnUserInteractionCallback onUserInteractionCallback,
- CentralSurfaces centralSurfaces,
NotificationPresenter presenter,
ShadeViewController shadeViewController,
NotificationShadeWindowController notificationShadeWindowController,
ActivityLaunchAnimator activityLaunchAnimator,
NotificationLaunchAnimatorControllerProvider notificationAnimationProvider,
LaunchFullScreenIntentProvider launchFullScreenIntentProvider,
+ PowerInteractor powerInteractor,
FeatureFlags featureFlags,
UserTracker userTracker) {
mContext = context;
+ mDisplayId = displayId;
mMainThreadHandler = mainThreadHandler;
mUiBgExecutor = uiBgExecutor;
mVisibilityProvider = visibilityProvider;
@@ -190,12 +195,11 @@
mMetricsLogger = metricsLogger;
mLogger = logger;
mOnUserInteractionCallback = onUserInteractionCallback;
- // TODO: use KeyguardStateController#isOccluded to remove this dependency
- mCentralSurfaces = centralSurfaces;
mPresenter = presenter;
mShadeViewController = shadeViewController;
mActivityLaunchAnimator = activityLaunchAnimator;
mNotificationAnimationProvider = notificationAnimationProvider;
+ mPowerInteractor = powerInteractor;
mUserTracker = userTracker;
launchFullScreenIntentProvider.registerListener(entry -> launchFullScreenIntent(entry));
@@ -280,7 +284,7 @@
mShadeController.addPostCollapseAction(runnable);
mShadeController.collapseShade(true /* animate */);
} else if (mKeyguardStateController.isShowing()
- && mCentralSurfaces.isOccluded()) {
+ && mKeyguardStateController.isOccluded()) {
mStatusBarKeyguardViewManager.addAfterKeyguardGoneRunnable(runnable);
mShadeController.collapseShade();
} else {
@@ -452,11 +456,11 @@
long eventTime = row.getAndResetLastActionUpTime();
Bundle options = eventTime > 0
? getActivityOptions(
- mCentralSurfaces.getDisplayId(),
+ mDisplayId,
adapter,
mKeyguardStateController.isShowing(),
eventTime)
- : getActivityOptions(mCentralSurfaces.getDisplayId(), adapter);
+ : getActivityOptions(mDisplayId, adapter);
int result = intent.sendAndReturnResult(mContext, 0, fillInIntent, null,
null, null, options);
mLogger.logSendPendingIntent(entry, intent, result);
@@ -491,7 +495,7 @@
(adapter) -> TaskStackBuilder.create(mContext)
.addNextIntentWithParentStack(intent)
.startActivities(getActivityOptions(
- mCentralSurfaces.getDisplayId(),
+ mDisplayId,
adapter),
new UserHandle(UserHandle.getUserId(appUid))));
});
@@ -539,7 +543,7 @@
mActivityLaunchAnimator.startIntentWithAnimation(animationController, animate,
intent.getPackage(),
(adapter) -> tsb.startActivities(
- getActivityOptions(mCentralSurfaces.getDisplayId(), adapter),
+ getActivityOptions(mDisplayId, adapter),
mUserTracker.getUserHandle()));
});
return true;
@@ -592,7 +596,7 @@
try {
EventLog.writeEvent(EventLogTags.SYSUI_FULLSCREEN_NOTIFICATION,
entry.getKey());
- mCentralSurfaces.wakeUpForFullScreenIntent();
+ mPowerInteractor.wakeUpForFullScreenIntent();
ActivityOptions options = ActivityOptions.makeBasic();
options.setPendingIntentBackgroundActivityStartMode(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
index 35285b2..a9135ac 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
@@ -28,7 +28,6 @@
import android.util.Log;
import android.util.Slog;
import android.view.View;
-import android.view.accessibility.AccessibilityManager;
import com.android.internal.statusbar.IStatusBarService;
import com.android.systemui.InitController;
@@ -50,7 +49,6 @@
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.notification.AboveShelfObserver;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
-import com.android.systemui.statusbar.notification.NotifPipelineFlags;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource;
import com.android.systemui.statusbar.notification.domain.interactor.NotificationsInteractor;
@@ -59,7 +57,6 @@
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager.OnSettingsClickListener;
-import com.android.systemui.statusbar.notification.row.NotificationInfo.CheckSaveListener;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent;
@@ -78,21 +75,18 @@
private final NotifShadeEventSource mNotifShadeEventSource;
private final NotificationMediaManager mMediaManager;
private final NotificationGutsManager mGutsManager;
-
private final ShadeViewController mNotificationPanel;
private final HeadsUpManagerPhone mHeadsUpManager;
private final AboveShelfObserver mAboveShelfObserver;
private final DozeScrimController mDozeScrimController;
private final CentralSurfaces mCentralSurfaces;
private final NotificationsInteractor mNotificationsInteractor;
+ private final NotificationStackScrollLayoutController mNsslController;
private final LockscreenShadeTransitionController mShadeTransitionController;
private final PowerInteractor mPowerInteractor;
private final CommandQueue mCommandQueue;
-
- private final AccessibilityManager mAccessibilityManager;
private final KeyguardManager mKeyguardManager;
private final NotificationShadeWindowController mNotificationShadeWindowController;
- private final NotifPipelineFlags mNotifPipelineFlags;
private final IStatusBarService mBarService;
private final DynamicPrivacyController mDynamicPrivacyController;
private final NotificationListContainer mNotifListContainer;
@@ -123,11 +117,9 @@
NotifShadeEventSource notifShadeEventSource,
NotificationMediaManager notificationMediaManager,
NotificationGutsManager notificationGutsManager,
- LockscreenGestureLogger lockscreenGestureLogger,
InitController initController,
NotificationInterruptStateProvider notificationInterruptStateProvider,
NotificationRemoteInputManager remoteInputManager,
- NotifPipelineFlags notifPipelineFlags,
NotificationRemoteInputManager.Callback remoteInputManagerCallback,
NotificationListContainer notificationListContainer) {
mActivityStarter = activityStarter;
@@ -139,6 +131,7 @@
// TODO: use KeyguardStateController#isOccluded to remove this dependency
mCentralSurfaces = centralSurfaces;
mNotificationsInteractor = notificationsInteractor;
+ mNsslController = stackScrollerController;
mShadeTransitionController = shadeTransitionController;
mPowerInteractor = powerInteractor;
mCommandQueue = commandQueue;
@@ -149,10 +142,8 @@
mGutsManager = notificationGutsManager;
mAboveShelfObserver = new AboveShelfObserver(stackScrollerController.getView());
mNotificationShadeWindowController = notificationShadeWindowController;
- mNotifPipelineFlags = notifPipelineFlags;
mAboveShelfObserver.setListener(statusBarWindow.findViewById(
R.id.notification_container_parent));
- mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
mDozeScrimController = dozeScrimController;
mKeyguardManager = context.getSystemService(KeyguardManager.class);
mBarService = IStatusBarService.Stub.asInterface(
@@ -170,7 +161,7 @@
}
remoteInputManager.setUpWithCallback(
remoteInputManagerCallback,
- mNotificationPanel.getShadeNotificationPresenter().createRemoteInputDelegate());
+ mNsslController.createDelegate());
initController.addPostInitTask(() -> {
mNotifShadeEventSource.setShadeEmptiedCallback(this::maybeClosePanelForShadeEmptied);
@@ -202,7 +193,7 @@
}
private void maybeEndAmbientPulse() {
- if (mNotificationPanel.getShadeNotificationPresenter().hasPulsingNotifications()
+ if (mNsslController.getNotificationListContainer().hasPulsingNotifications()
&& !mHeadsUpManager.hasNotifications()) {
// We were showing a pulse for a notification, but no notifications are pulsing anymore.
// Finish the pulse.
@@ -217,7 +208,6 @@
// End old BaseStatusBar.userSwitched
mCommandQueue.animateCollapsePanels();
mMediaManager.clearCurrentMediaNotification();
- mCentralSurfaces.setLockscreenUser(newUserId);
updateMediaMetaData(true, false);
}
@@ -272,22 +262,6 @@
}
};
- private final CheckSaveListener mCheckSaveListener = new CheckSaveListener() {
- @Override
- public void checkSave(Runnable saveImportance, StatusBarNotification sbn) {
- // If the user has security enabled, show challenge if the setting is changed.
- if (mLockscreenUserManager.isLockscreenPublicMode(sbn.getUser().getIdentifier())
- && mKeyguardManager.isKeyguardLocked()) {
- onLockedNotificationImportanceChange(() -> {
- saveImportance.run();
- return true;
- });
- } else {
- saveImportance.run();
- }
- }
- };
-
private final OnSettingsClickListener mOnSettingsClickListener = new OnSettingsClickListener() {
@Override
public void onSettingsClick(String key) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
index d731f88..6919996 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
@@ -30,7 +30,6 @@
import com.android.systemui.statusbar.connectivity.MobileDataIndicators;
import com.android.systemui.statusbar.connectivity.NetworkController;
import com.android.systemui.statusbar.connectivity.SignalCallback;
-import com.android.systemui.statusbar.connectivity.WifiIndicators;
import com.android.systemui.statusbar.policy.SecurityController;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.tuner.TunerService.Tunable;
@@ -51,7 +50,6 @@
private final String mSlotAirplane;
private final String mSlotMobile;
- private final String mSlotWifi;
private final String mSlotEthernet;
private final String mSlotVpn;
private final String mSlotNoCalling;
@@ -67,17 +65,14 @@
private boolean mHideAirplane;
private boolean mHideMobile;
- private boolean mHideWifi;
private boolean mHideEthernet;
private boolean mActivityEnabled;
// Track as little state as possible, and only for padding purposes
private boolean mIsAirplaneMode = false;
- private boolean mIsWifiEnabled = false;
private ArrayList<MobileIconState> mMobileStates = new ArrayList<>();
private ArrayList<CallIndicatorIconState> mCallIndicatorStates = new ArrayList<>();
- private WifiIconState mWifiIconState = new WifiIconState();
private boolean mInitialized;
@Inject
@@ -99,7 +94,6 @@
mSlotAirplane = mContext.getString(com.android.internal.R.string.status_bar_airplane);
mSlotMobile = mContext.getString(com.android.internal.R.string.status_bar_mobile);
- mSlotWifi = mContext.getString(com.android.internal.R.string.status_bar_wifi);
mSlotEthernet = mContext.getString(com.android.internal.R.string.status_bar_ethernet);
mSlotVpn = mContext.getString(com.android.internal.R.string.status_bar_vpn);
mSlotNoCalling = mContext.getString(com.android.internal.R.string.status_bar_no_calling);
@@ -154,15 +148,13 @@
ArraySet<String> hideList = StatusBarIconController.getIconHideList(mContext, newValue);
boolean hideAirplane = hideList.contains(mSlotAirplane);
boolean hideMobile = hideList.contains(mSlotMobile);
- boolean hideWifi = hideList.contains(mSlotWifi);
boolean hideEthernet = hideList.contains(mSlotEthernet);
if (hideAirplane != mHideAirplane || hideMobile != mHideMobile
- || hideEthernet != mHideEthernet || hideWifi != mHideWifi) {
+ || hideEthernet != mHideEthernet) {
mHideAirplane = hideAirplane;
mHideMobile = hideMobile;
mHideEthernet = hideEthernet;
- mHideWifi = hideWifi;
// Re-register to get new callbacks.
mNetworkController.removeCallback(this);
mNetworkController.addCallback(this);
@@ -170,56 +162,6 @@
}
@Override
- public void setWifiIndicators(@NonNull WifiIndicators indicators) {
- if (DEBUG) {
- Log.d(TAG, "setWifiIndicators: " + indicators);
- }
- boolean visible = indicators.statusIcon.visible && !mHideWifi;
- boolean in = indicators.activityIn && mActivityEnabled && visible;
- boolean out = indicators.activityOut && mActivityEnabled && visible;
- mIsWifiEnabled = indicators.enabled;
-
- WifiIconState newState = mWifiIconState.copy();
-
- if (mWifiIconState.noDefaultNetwork && mWifiIconState.noNetworksAvailable
- && !mIsAirplaneMode) {
- newState.visible = true;
- newState.resId = R.drawable.ic_qs_no_internet_unavailable;
- } else if (mWifiIconState.noDefaultNetwork && !mWifiIconState.noNetworksAvailable
- && (!mIsAirplaneMode || (mIsAirplaneMode && mIsWifiEnabled))) {
- newState.visible = true;
- newState.resId = R.drawable.ic_qs_no_internet_available;
- } else {
- newState.visible = visible;
- newState.resId = indicators.statusIcon.icon;
- newState.activityIn = in;
- newState.activityOut = out;
- newState.contentDescription = indicators.statusIcon.contentDescription;
- MobileIconState first = getFirstMobileState();
- newState.signalSpacerVisible = first != null && first.typeId != 0;
- }
- newState.slot = mSlotWifi;
- newState.airplaneSpacerVisible = mIsAirplaneMode;
- updateWifiIconWithState(newState);
- mWifiIconState = newState;
- }
-
- private void updateShowWifiSignalSpacer(WifiIconState state) {
- MobileIconState first = getFirstMobileState();
- state.signalSpacerVisible = first != null && first.typeId != 0;
- }
-
- private void updateWifiIconWithState(WifiIconState state) {
- if (DEBUG) Log.d(TAG, "WifiIconState: " + state == null ? "" : state.toString());
- if (state.visible && state.resId > 0) {
- mIconController.setWifiIcon(mSlotWifi, state);
- mIconController.setIconVisibility(mSlotWifi, true);
- } else {
- mIconController.setIconVisibility(mSlotWifi, false);
- }
- }
-
- @Override
public void setCallIndicator(@NonNull IconState statusIcon, int subId) {
if (DEBUG) {
Log.d(TAG, "setCallIndicator: "
@@ -257,10 +199,6 @@
return;
}
- // Visibility of the data type indicator changed
- boolean typeChanged = indicators.statusType != state.typeId
- && (indicators.statusType == 0 || state.typeId == 0);
-
state.visible = indicators.statusIcon.visible && !mHideMobile;
state.strengthId = indicators.statusIcon.icon;
state.typeId = indicators.statusType;
@@ -277,15 +215,6 @@
}
// Always send a copy to maintain value type semantics
mIconController.setMobileIcons(mSlotMobile, MobileIconState.copyStates(mMobileStates));
-
- if (typeChanged) {
- WifiIconState wifiCopy = mWifiIconState.copy();
- updateShowWifiSignalSpacer(wifiCopy);
- if (!Objects.equals(wifiCopy, mWifiIconState)) {
- updateWifiIconWithState(wifiCopy);
- mWifiIconState = wifiCopy;
- }
- }
}
private CallIndicatorIconState getNoCallingState(int subId) {
@@ -308,15 +237,6 @@
return null;
}
- private MobileIconState getFirstMobileState() {
- if (mMobileStates.size() > 0) {
- return mMobileStates.get(0);
- }
-
- return null;
- }
-
-
/**
* It is expected that a call to setSubs will be immediately followed by setMobileDataIndicators
* so we don't have to update the icon manager at this point, just remove the old ones
@@ -504,60 +424,6 @@
}
}
- public static class WifiIconState extends SignalIconState{
- public int resId;
- public boolean airplaneSpacerVisible;
- public boolean signalSpacerVisible;
- public boolean noDefaultNetwork;
- public boolean noValidatedNetwork;
- public boolean noNetworksAvailable;
-
- @Override
- public boolean equals(Object o) {
- // Skipping reference equality bc this should be more of a value type
- if (o == null || getClass() != o.getClass()) {
- return false;
- }
- if (!super.equals(o)) {
- return false;
- }
- WifiIconState that = (WifiIconState) o;
- return resId == that.resId
- && airplaneSpacerVisible == that.airplaneSpacerVisible
- && signalSpacerVisible == that.signalSpacerVisible
- && noDefaultNetwork == that.noDefaultNetwork
- && noValidatedNetwork == that.noValidatedNetwork
- && noNetworksAvailable == that.noNetworksAvailable;
- }
-
- public void copyTo(WifiIconState other) {
- super.copyTo(other);
- other.resId = resId;
- other.airplaneSpacerVisible = airplaneSpacerVisible;
- other.signalSpacerVisible = signalSpacerVisible;
- other.noDefaultNetwork = noDefaultNetwork;
- other.noValidatedNetwork = noValidatedNetwork;
- other.noNetworksAvailable = noNetworksAvailable;
- }
-
- public WifiIconState copy() {
- WifiIconState newState = new WifiIconState();
- copyTo(newState);
- return newState;
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(super.hashCode(),
- resId, airplaneSpacerVisible, signalSpacerVisible, noDefaultNetwork,
- noValidatedNetwork, noNetworksAvailable);
- }
-
- @Override public String toString() {
- return "WifiIconState(resId=" + resId + ", visible=" + visible + ")";
- }
- }
-
/**
* A little different. This one delegates to SignalDrawable instead of a specific resId
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java
index 604b1f5..d83664f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java
@@ -22,6 +22,8 @@
import android.annotation.Nullable;
import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.res.Configuration;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
@@ -73,13 +75,16 @@
// Any ignored icon will never be added as a child
private ArrayList<String> mIgnoredSlots = new ArrayList<>();
+ private Configuration mConfiguration;
+
public StatusIconContainer(Context context) {
this(context, null);
}
public StatusIconContainer(Context context, AttributeSet attrs) {
super(context, attrs);
- initDimens();
+ mConfiguration = new Configuration(context.getResources().getConfiguration());
+ reloadDimens();
setWillNotDraw(!DEBUG_OVERFLOW);
}
@@ -100,10 +105,10 @@
return mShouldRestrictIcons;
}
- private void initDimens() {
+ private void reloadDimens() {
// This is the same value that StatusBarIconView uses
mIconDotFrameWidth = getResources().getDimensionPixelSize(
- com.android.internal.R.dimen.status_bar_icon_size);
+ com.android.internal.R.dimen.status_bar_icon_size_sp);
mDotPadding = getResources().getDimensionPixelSize(R.dimen.overflow_icon_dot_padding);
mIconSpacing = getResources().getDimensionPixelSize(R.dimen.status_bar_system_icon_spacing);
int radius = getResources().getDimensionPixelSize(R.dimen.overflow_dot_radius);
@@ -233,6 +238,16 @@
child.setTag(R.id.status_bar_view_state_tag, null);
}
+ @Override
+ protected void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ final int configDiff = newConfig.diff(mConfiguration);
+ mConfiguration.setTo(newConfig);
+ if ((configDiff & (ActivityInfo.CONFIG_DENSITY | ActivityInfo.CONFIG_FONT_SCALE)) != 0) {
+ reloadDimens();
+ }
+ }
+
/**
* Add a name of an icon slot to be ignored. It will not show up nor be measured
* @param slotName name of the icon as it exists in
@@ -342,13 +357,17 @@
int totalVisible = mLayoutStates.size();
int maxVisible = totalVisible <= MAX_ICONS ? MAX_ICONS : MAX_ICONS - 1;
- mUnderflowStart = 0;
+ // Init mUnderflowStart value with the offset to let the dot be placed next to battery icon.
+ // This is to prevent if the underflow happens at rightest(totalVisible - 1) child then
+ // break the for loop with mUnderflowStart staying 0(initial value), causing the dot be
+ // placed at the leftest side.
+ mUnderflowStart = (int) Math.max(contentStart, width - getPaddingEnd() - mUnderflowWidth);
int visible = 0;
int firstUnderflowIndex = -1;
for (int i = totalVisible - 1; i >= 0; i--) {
StatusIconState state = mLayoutStates.get(i);
// Allow room for underflow if we found we need it in onMeasure
- if (mNeedsUnderflow && (state.getXTranslation() < (contentStart + mUnderflowWidth))
+ if ((mNeedsUnderflow && (state.getXTranslation() < (contentStart + mUnderflowWidth)))
|| (mShouldRestrictIcons && (visible >= maxVisible))) {
firstUnderflowIndex = i;
break;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesComponent.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesComponent.java
index 4ae460a..e77f419 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesComponent.java
@@ -21,8 +21,6 @@
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import com.android.systemui.scene.ui.view.WindowRootView;
-import com.android.systemui.shade.NotificationShadeWindowView;
-import com.android.systemui.shade.NotificationShadeWindowViewController;
import com.android.systemui.shade.ShadeHeaderController;
import com.android.systemui.statusbar.NotificationPresenter;
import com.android.systemui.statusbar.notification.NotificationActivityStarter;
@@ -77,16 +75,6 @@
WindowRootView getWindowRootView();
/**
- * Creates or returns a {@link NotificationShadeWindowView}.
- */
- NotificationShadeWindowView getNotificationShadeWindowView();
-
- /**
- * Creates a NotificationShadeWindowViewController.
- */
- NotificationShadeWindowViewController getNotificationShadeWindowViewController();
-
- /**
* Creates a StatusBarHeadsUpChangeListener.
*/
StatusBarHeadsUpChangeListener getStatusBarHeadsUpChangeListener();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java
index eb4d963..cd6ccb5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java
@@ -27,6 +27,7 @@
import com.android.systemui.statusbar.phone.PhoneStatusBarView;
import com.android.systemui.statusbar.phone.PhoneStatusBarViewController;
import com.android.systemui.statusbar.phone.StatusBarBoundsProvider;
+import com.android.systemui.statusbar.phone.StatusBarLocation;
import com.android.systemui.statusbar.phone.SystemBarAttributesListener;
import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment;
import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer;
@@ -73,6 +74,13 @@
/** */
@Provides
@StatusBarFragmentScope
+ static StatusBarLocation getStatusBarLocation() {
+ return StatusBarLocation.HOME;
+ }
+
+ /** */
+ @Provides
+ @StatusBarFragmentScope
@Named(START_SIDE_CONTENT)
static View startSideContent(@RootView PhoneStatusBarView view) {
return view.findViewById(R.id.status_bar_start_side_content);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt
index 4a684d9..29829e4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt
@@ -45,18 +45,6 @@
fun runNewMobileIconsBackend(): Boolean =
featureFlags.isEnabled(Flags.NEW_STATUS_BAR_MOBILE_ICONS_BACKEND) || useNewMobileIcons()
- /** True if we should display the wifi icon using the new status bar data pipeline. */
- fun useNewWifiIcon(): Boolean = featureFlags.isEnabled(Flags.NEW_STATUS_BAR_WIFI_ICON)
-
- /**
- * True if we should run the new wifi icon backend to get the logging.
- *
- * Does *not* affect whether we render the wifi icon using the new backend data. See
- * [useNewWifiIcon] for that.
- */
- fun runNewWifiIconBackend(): Boolean =
- featureFlags.isEnabled(Flags.NEW_STATUS_BAR_WIFI_ICON_BACKEND) || useNewWifiIcon()
-
/**
* Returns true if we should apply some coloring to the icons that were rendered with the new
* pipeline to help with debugging.
@@ -71,5 +59,5 @@
* @return true if this icon is controlled by any of the status bar pipeline flags
*/
fun isIconControlledByFlags(slotName: String): Boolean =
- slotName == wifiSlot && useNewWifiIcon() || slotName == mobileSlot && useNewMobileIcons()
+ slotName == wifiSlot || (slotName == mobileSlot && useNewMobileIcons())
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
index 27cc64f..0e99c67 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
@@ -19,12 +19,10 @@
import android.net.wifi.WifiManager
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.LogBuffer
import com.android.systemui.log.LogBufferFactory
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.log.table.TableLogBufferFactory
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.CollapsedStatusBarViewModel
-import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.CollapsedStatusBarViewModelImpl
import com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepository
import com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepositoryImpl
import com.android.systemui.statusbar.pipeline.airplane.ui.viewmodel.AirplaneModeViewModel
@@ -46,6 +44,8 @@
import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl
import com.android.systemui.statusbar.pipeline.shared.ui.binder.CollapsedStatusBarViewBinder
import com.android.systemui.statusbar.pipeline.shared.ui.binder.CollapsedStatusBarViewBinderImpl
+import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.CollapsedStatusBarViewModel
+import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.CollapsedStatusBarViewModelImpl
import com.android.systemui.statusbar.pipeline.wifi.data.repository.RealWifiRepository
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositorySwitcher
@@ -58,9 +58,9 @@
import dagger.Provides
import dagger.multibindings.ClassKey
import dagger.multibindings.IntoMap
-import kotlinx.coroutines.flow.Flow
import java.util.function.Supplier
import javax.inject.Named
+import kotlinx.coroutines.flow.Flow
@Module
abstract class StatusBarPipelineModule {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarView.kt
index 1a13404..a1b96dd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarView.kt
@@ -101,12 +101,7 @@
this.binding = bindingCreator.invoke()
}
- /**
- * Creates a [StatusBarIconView] that is always in DOT mode and adds it to this view.
- *
- * Mostly duplicated from [com.android.systemui.statusbar.StatusBarWifiView] and
- * [com.android.systemui.statusbar.StatusBarMobileView].
- */
+ /** Creates a [StatusBarIconView] that is always in DOT mode and adds it to this view. */
private fun initDotView() {
// TODO(b/238425913): Could we just have this dot view be part of the layout with a dot
// drawable so we don't need to inflate it manually? Would that not work with animations?
@@ -118,7 +113,7 @@
it.visibleState = STATE_DOT
}
- val width = mContext.resources.getDimensionPixelSize(R.dimen.status_bar_icon_size)
+ val width = mContext.resources.getDimensionPixelSize(R.dimen.status_bar_icon_size_sp)
val lp = LayoutParams(width, width)
lp.gravity = Gravity.CENTER_VERTICAL or Gravity.START
addView(dotView, lp)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
index 4e52be9..7f35dfb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
@@ -34,6 +34,7 @@
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.log.table.logDiffsForTable
@@ -48,6 +49,7 @@
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
import java.util.concurrent.Executor
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.awaitClose
@@ -60,7 +62,9 @@
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.withContext
/** Real implementation of [WifiRepository]. */
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
@@ -76,8 +80,9 @@
logger: WifiInputLogger,
@WifiTableLog wifiTableLogBuffer: TableLogBuffer,
@Main mainExecutor: Executor,
+ @Background private val bgDispatcher: CoroutineDispatcher,
@Application scope: CoroutineScope,
- wifiManager: WifiManager,
+ private val wifiManager: WifiManager,
) : RealWifiRepository {
private val wifiStateChangeEvents: Flow<Unit> =
@@ -93,20 +98,25 @@
// have changed.
override val isWifiEnabled: StateFlow<Boolean> =
merge(wifiNetworkChangeEvents, wifiStateChangeEvents)
- .mapLatest { wifiManager.isWifiEnabled }
+ .onStart { emit(Unit) }
+ .mapLatest { isWifiEnabled() }
.distinctUntilChanged()
.logDiffsForTable(
wifiTableLogBuffer,
columnPrefix = "",
columnName = "isEnabled",
- initialValue = wifiManager.isWifiEnabled,
+ initialValue = false,
)
.stateIn(
scope = scope,
- started = SharingStarted.WhileSubscribed(),
- initialValue = wifiManager.isWifiEnabled,
+ started = SharingStarted.Eagerly,
+ initialValue = false,
)
+ // [WifiManager.isWifiEnabled] is a blocking IPC call, so fetch it in the background.
+ private suspend fun isWifiEnabled(): Boolean =
+ withContext(bgDispatcher) { wifiManager.isWifiEnabled }
+
override val isWifiDefault: StateFlow<Boolean> =
connectivityRepository.defaultConnections
// TODO(b/274493701): Should wifi be considered default if it's carrier merged?
@@ -289,6 +299,7 @@
private val logger: WifiInputLogger,
@WifiTableLog private val wifiTableLogBuffer: TableLogBuffer,
@Main private val mainExecutor: Executor,
+ @Background private val bgDispatcher: CoroutineDispatcher,
@Application private val scope: CoroutineScope,
) {
fun create(wifiManager: WifiManager): WifiRepositoryImpl {
@@ -299,6 +310,7 @@
logger,
wifiTableLogBuffer,
mainExecutor,
+ bgDispatcher,
scope,
wifiManager,
)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/WifiUiAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/WifiUiAdapter.kt
index 174298a..6d71823 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/WifiUiAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/WifiUiAdapter.kt
@@ -68,12 +68,7 @@
launch {
locationViewModel.wifiIcon.collect { wifiIcon ->
// Only notify the icon controller if we want to *render* the new icon.
- // Note that this flow may still run if
- // [statusBarPipelineFlags.runNewWifiIconBackend] is true because we may
- // want to get the logging data without rendering.
- if (
- wifiIcon is WifiIcon.Visible && statusBarPipelineFlags.useNewWifiIcon()
- ) {
+ if (wifiIcon is WifiIcon.Visible) {
iconController.setNewWifiIcon()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
index 3d16591..7df083afc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
@@ -19,6 +19,9 @@
import android.annotation.Nullable;
import android.view.View;
+import androidx.annotation.NonNull;
+
+import com.android.systemui.Dumpable;
import com.android.systemui.demomode.DemoMode;
import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
@@ -136,7 +139,7 @@
* A listener that will be notified whenever a change in battery level or power save mode has
* occurred.
*/
- interface BatteryStateChangeCallback {
+ interface BatteryStateChangeCallback extends Dumpable {
default void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) {
}
@@ -158,6 +161,11 @@
default void onIsBatteryDefenderChanged(boolean isBatteryDefender) {
}
+
+ @Override
+ default void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
+ pw.println(this);
+ }
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
index e69d86c..d5d8f4d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
@@ -22,6 +22,7 @@
import static android.os.BatteryManager.EXTRA_PRESENT;
import static com.android.settingslib.fuelgauge.BatterySaverLogging.SAVER_ENABLED_QS;
+import static com.android.systemui.util.DumpUtilsKt.asIndenting;
import android.annotation.WorkerThread;
import android.content.BroadcastReceiver;
@@ -33,6 +34,7 @@
import android.os.Handler;
import android.os.PowerManager;
import android.os.PowerSaveState;
+import android.util.IndentingPrintWriter;
import android.util.Log;
import android.view.View;
@@ -157,15 +159,29 @@
}
@Override
- public void dump(PrintWriter pw, String[] args) {
- pw.println("BatteryController state:");
- pw.print(" mLevel="); pw.println(mLevel);
- pw.print(" mPluggedIn="); pw.println(mPluggedIn);
- pw.print(" mCharging="); pw.println(mCharging);
- pw.print(" mCharged="); pw.println(mCharged);
- pw.print(" mIsBatteryDefender="); pw.println(mIsBatteryDefender);
- pw.print(" mPowerSave="); pw.println(mPowerSave);
- pw.print(" mStateUnknown="); pw.println(mStateUnknown);
+ public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
+ IndentingPrintWriter ipw = asIndenting(pw);
+ ipw.println("BatteryController state:");
+ ipw.increaseIndent();
+ ipw.print("mHasReceivedBattery="); ipw.println(mHasReceivedBattery);
+ ipw.print("mLevel="); ipw.println(mLevel);
+ ipw.print("mPluggedIn="); ipw.println(mPluggedIn);
+ ipw.print("mCharging="); ipw.println(mCharging);
+ ipw.print("mCharged="); ipw.println(mCharged);
+ ipw.print("mIsBatteryDefender="); ipw.println(mIsBatteryDefender);
+ ipw.print("mPowerSave="); ipw.println(mPowerSave);
+ ipw.print("mStateUnknown="); ipw.println(mStateUnknown);
+ ipw.println("Callbacks:------------------");
+ // Since the above lines are already indented, we need to indent twice for the callbacks.
+ ipw.increaseIndent();
+ synchronized (mChangeCallbacks) {
+ final int n = mChangeCallbacks.size();
+ for (int i = 0; i < n; i++) {
+ mChangeCallbacks.get(i).dump(ipw, args);
+ }
+ }
+ ipw.decreaseIndent();
+ ipw.println("------------------");
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
index 6875b52..f994372 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
@@ -374,6 +374,13 @@
@Override
public void onDensityOrFontScaleChanged() {
+ reloadDimens();
+ }
+
+ private void reloadDimens() {
+ // reset mCachedWidth so the new width would be updated properly when next onMeasure
+ mCachedWidth = -1;
+
FontSizeUtils.updateFontSize(this, R.dimen.status_bar_clock_size);
setPaddingRelative(
mContext.getResources().getDimensionPixelSize(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java
index bcf3b0c..24987ab 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java
@@ -240,8 +240,10 @@
Insets.of(0, safeTouchRegionHeight, 0, 0));
}
lp.providedInsets = new InsetsFrameProvider[] {
- new InsetsFrameProvider(mInsetsSourceOwner, 0, statusBars()),
- new InsetsFrameProvider(mInsetsSourceOwner, 0, tappableElement()),
+ new InsetsFrameProvider(mInsetsSourceOwner, 0, statusBars())
+ .setInsetsSize(getInsets(height)),
+ new InsetsFrameProvider(mInsetsSourceOwner, 0, tappableElement())
+ .setInsetsSize(getInsets(height)),
gestureInsetsProvider
};
return lp;
@@ -306,12 +308,37 @@
mLpChanged.height =
state.mIsLaunchAnimationRunning ? ViewGroup.LayoutParams.MATCH_PARENT : mBarHeight;
for (int rot = Surface.ROTATION_0; rot <= Surface.ROTATION_270; rot++) {
+ int height = SystemBarUtils.getStatusBarHeightForRotation(mContext, rot);
mLpChanged.paramsForRotation[rot].height =
- state.mIsLaunchAnimationRunning ? ViewGroup.LayoutParams.MATCH_PARENT :
- SystemBarUtils.getStatusBarHeightForRotation(mContext, rot);
+ state.mIsLaunchAnimationRunning ? ViewGroup.LayoutParams.MATCH_PARENT : height;
+ // The status bar height could change at runtime if one display has a cutout while
+ // another doesn't (like some foldables). It could also change when using debug cutouts.
+ // So, we need to re-fetch the height and re-apply it to the insets each time to avoid
+ // bugs like b/290300359.
+ InsetsFrameProvider[] providers = mLpChanged.paramsForRotation[rot].providedInsets;
+ if (providers != null) {
+ for (InsetsFrameProvider provider : providers) {
+ provider.setInsetsSize(getInsets(height));
+ }
+ }
}
}
+ /**
+ * Get the insets that should be applied to the status bar window given the current status bar
+ * height.
+ *
+ * The status bar window height can sometimes be full-screen (see {@link #applyHeight(State)}.
+ * However, the status bar *insets* should *not* be full-screen, because this would prevent apps
+ * from drawing any content and can cause animations to be cancelled (see b/283958440). Instead,
+ * the status bar insets should always be equal to the space occupied by the actual status bar
+ * content -- setting the insets correctly will prevent window manager from unnecessarily
+ * re-drawing this window and other windows. This method provides the correct insets.
+ */
+ private Insets getInsets(int height) {
+ return Insets.of(0, height, 0, 0);
+ }
+
private void apply(State state) {
if (!mIsAttached) {
return;
diff --git a/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerUI.kt b/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerUI.kt
index 3e1c13c..c1ac800 100644
--- a/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerUI.kt
+++ b/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerUI.kt
@@ -64,9 +64,9 @@
// These values must only be accessed on the handler.
private var batteryCapacity = 1.0f
private var suppressed = false
- private var inputDeviceId: Int? = null
private var instanceId: InstanceId? = null
-
+ @VisibleForTesting var inputDeviceId: Int? = null
+ private set
@VisibleForTesting var instanceIdSequence = InstanceIdSequence(1 shl 13)
fun init() {
@@ -110,10 +110,10 @@
fun updateBatteryState(deviceId: Int, batteryState: BatteryState) {
handler.post updateBattery@{
+ inputDeviceId = deviceId
if (batteryState.capacity == batteryCapacity || batteryState.capacity <= 0f)
return@updateBattery
- inputDeviceId = deviceId
batteryCapacity = batteryState.capacity
debugLog {
"Updating notification battery state to $batteryCapacity " +
diff --git a/packages/SystemUI/src/com/android/systemui/toast/ToastLogger.kt b/packages/SystemUI/src/com/android/systemui/toast/ToastLogger.kt
index c109eb4..324ef4b 100644
--- a/packages/SystemUI/src/com/android/systemui/toast/ToastLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/toast/ToastLogger.kt
@@ -58,6 +58,16 @@
})
}
+ fun logOnSkipToastForInvalidDisplay(packageName: String, token: String, displayId: Int) {
+ log(DEBUG, {
+ str1 = packageName
+ str2 = token
+ int1 = displayId
+ }, {
+ "[$str2] Skip toast for [$str1] scheduled on unavailable display #$int1"
+ })
+ }
+
private inline fun log(
logLevel: LogLevel,
initializer: LogMessage.() -> Unit,
diff --git a/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java b/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java
index ed14c8a..ae8128d 100644
--- a/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java
+++ b/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java
@@ -32,6 +32,7 @@
import android.os.ServiceManager;
import android.os.UserHandle;
import android.util.Log;
+import android.view.Display;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.IAccessibilityManager;
import android.widget.ToastPresenter;
@@ -115,8 +116,14 @@
Context context = mContext.createContextAsUser(userHandle, 0);
DisplayManager mDisplayManager = mContext.getSystemService(DisplayManager.class);
- Context displayContext = context.createDisplayContext(
- mDisplayManager.getDisplay(displayId));
+ Display display = mDisplayManager.getDisplay(displayId);
+ if (display == null) {
+ // Display for which this toast was scheduled for is no longer available.
+ mToastLogger.logOnSkipToastForInvalidDisplay(packageName, token.toString(),
+ displayId);
+ return;
+ }
+ Context displayContext = context.createDisplayContext(display);
mToast = mToastFactory.createToast(mContext /* sysuiContext */, text, packageName,
userHandle.getIdentifier(), mOrientation);
diff --git a/packages/SystemUI/src/com/android/systemui/tracing/ProtoTracer.java b/packages/SystemUI/src/com/android/systemui/tracing/ProtoTracer.java
deleted file mode 100644
index b54d156..0000000
--- a/packages/SystemUI/src/com/android/systemui/tracing/ProtoTracer.java
+++ /dev/null
@@ -1,149 +0,0 @@
-/*
- * Copyright (C) 2019 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.tracing;
-
-import static com.android.systemui.tracing.nano.SystemUiTraceFileProto.MAGIC_NUMBER_H;
-import static com.android.systemui.tracing.nano.SystemUiTraceFileProto.MAGIC_NUMBER_L;
-
-import android.content.Context;
-import android.os.SystemClock;
-
-import androidx.annotation.NonNull;
-
-import com.android.systemui.Dumpable;
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dump.DumpManager;
-import com.android.systemui.shared.tracing.FrameProtoTracer;
-import com.android.systemui.shared.tracing.FrameProtoTracer.ProtoTraceParams;
-import com.android.systemui.shared.tracing.ProtoTraceable;
-import com.android.systemui.tracing.nano.SystemUiTraceEntryProto;
-import com.android.systemui.tracing.nano.SystemUiTraceFileProto;
-import com.android.systemui.tracing.nano.SystemUiTraceProto;
-
-import com.google.protobuf.nano.MessageNano;
-
-import java.io.File;
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.Queue;
-
-import javax.inject.Inject;
-
-/**
- * Controller for coordinating winscope proto tracing.
- */
-@SysUISingleton
-public class ProtoTracer implements
- Dumpable,
- ProtoTraceParams<
- MessageNano,
- SystemUiTraceFileProto,
- SystemUiTraceEntryProto,
- SystemUiTraceProto> {
-
- private static final String TAG = "ProtoTracer";
- private static final long MAGIC_NUMBER_VALUE = ((long) MAGIC_NUMBER_H << 32) | MAGIC_NUMBER_L;
-
- private final Context mContext;
- private final FrameProtoTracer<MessageNano, SystemUiTraceFileProto, SystemUiTraceEntryProto,
- SystemUiTraceProto> mProtoTracer;
-
- @Inject
- public ProtoTracer(Context context, DumpManager dumpManager) {
- mContext = context;
- mProtoTracer = new FrameProtoTracer<>(this);
- dumpManager.registerDumpable(this);
- }
-
- @Override
- public File getTraceFile() {
- return new File(mContext.getFilesDir(), "sysui_trace.pb");
- }
-
- @Override
- public SystemUiTraceFileProto getEncapsulatingTraceProto() {
- return new SystemUiTraceFileProto();
- }
-
- @Override
- public SystemUiTraceEntryProto updateBufferProto(SystemUiTraceEntryProto reuseObj,
- ArrayList<ProtoTraceable<SystemUiTraceProto>> traceables) {
- SystemUiTraceEntryProto proto = reuseObj != null
- ? reuseObj
- : new SystemUiTraceEntryProto();
- proto.elapsedRealtimeNanos = SystemClock.elapsedRealtimeNanos();
- proto.systemUi = proto.systemUi != null ? proto.systemUi : new SystemUiTraceProto();
- for (ProtoTraceable t : traceables) {
- t.writeToProto(proto.systemUi);
- }
- return proto;
- }
-
- @Override
- public byte[] serializeEncapsulatingProto(SystemUiTraceFileProto encapsulatingProto,
- Queue<SystemUiTraceEntryProto> buffer) {
- encapsulatingProto.magicNumber = MAGIC_NUMBER_VALUE;
- encapsulatingProto.entry = buffer.toArray(new SystemUiTraceEntryProto[0]);
- return MessageNano.toByteArray(encapsulatingProto);
- }
-
- @Override
- public byte[] getProtoBytes(MessageNano proto) {
- return MessageNano.toByteArray(proto);
- }
-
- @Override
- public int getProtoSize(MessageNano proto) {
- return proto.getCachedSize();
- }
-
- public void start() {
- mProtoTracer.start();
- }
-
- public void stop() {
- mProtoTracer.stop();
- }
-
- public boolean isEnabled() {
- return mProtoTracer.isEnabled();
- }
-
- public void add(ProtoTraceable<SystemUiTraceProto> traceable) {
- mProtoTracer.add(traceable);
- }
-
- public void remove(ProtoTraceable<SystemUiTraceProto> traceable) {
- mProtoTracer.remove(traceable);
- }
-
- public void scheduleFrameUpdate() {
- mProtoTracer.scheduleFrameUpdate();
- }
-
- public void update() {
- mProtoTracer.update();
- }
-
- @Override
- public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
- pw.println("ProtoTracer:");
- pw.print(" "); pw.println("enabled: " + mProtoTracer.isEnabled());
- pw.print(" "); pw.println("usagePct: " + mProtoTracer.getBufferUsagePct());
- pw.print(" "); pw.println("file: " + getTraceFile());
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/tracing/sysui_trace.proto b/packages/SystemUI/src/com/android/systemui/tracing/sysui_trace.proto
deleted file mode 100644
index d940a6b..0000000
--- a/packages/SystemUI/src/com/android/systemui/tracing/sysui_trace.proto
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (C) 2019 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.
- */
-
-syntax = "proto2";
-
-package com.android.systemui.tracing;
-
-option java_multiple_files = true;
-
-message SystemUiTraceProto {
-
- optional EdgeBackGestureHandlerProto edge_back_gesture_handler = 1;
-}
-
-message EdgeBackGestureHandlerProto {
-
- optional bool allow_gesture = 1;
-}
-
-/* represents a file full of system ui trace entries.
- Encoded, it should start with 0x9 0x53 0x59 0x53 0x55 0x49 0x54 0x52 0x43 (.SYSUITRC), such
- that they can be easily identified. */
-message SystemUiTraceFileProto {
-
- /* constant; MAGIC_NUMBER = (long) MAGIC_NUMBER_H << 32 | MagicNumber.MAGIC_NUMBER_L
- (this is needed because enums have to be 32 bits and there's no nice way to put 64bit
- constants into .proto files. */
- enum MagicNumber {
- INVALID = 0;
- MAGIC_NUMBER_L = 0x55535953; /* SYSU (little-endian ASCII) */
- MAGIC_NUMBER_H = 0x43525449; /* ITRC (little-endian ASCII) */
- }
-
- optional fixed64 magic_number = 1; /* Must be the first field, set to value in MagicNumber */
- repeated SystemUiTraceEntryProto entry = 2;
-}
-
-/* one system ui trace entry. */
-message SystemUiTraceEntryProto {
- /* required: elapsed realtime in nanos since boot of when this entry was logged */
- optional fixed64 elapsed_realtime_nanos = 1;
-
- optional SystemUiTraceProto system_ui = 3;
-}
diff --git a/packages/SystemUI/src/com/android/systemui/tv/TvSysUIComponent.java b/packages/SystemUI/src/com/android/systemui/tv/TvSysUIComponent.java
index 5e489b0..82589d3 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/TvSysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/tv/TvSysUIComponent.java
@@ -27,6 +27,7 @@
import com.android.systemui.statusbar.dagger.CentralSurfacesDependenciesModule;
import com.android.systemui.statusbar.notification.dagger.NotificationsModule;
import com.android.systemui.statusbar.notification.row.NotificationRowModule;
+import com.android.systemui.wallpapers.dagger.NoopWallpaperModule;
import dagger.Subcomponent;
@@ -39,6 +40,7 @@
DefaultComponentBinder.class,
DependencyProvider.class,
KeyguardModule.class,
+ NoopWallpaperModule.class,
NotificationRowModule.class,
NotificationsModule.class,
RecentsModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java
index 3f31ff94..8cf71a0 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java
@@ -45,7 +45,7 @@
import com.android.systemui.settings.dagger.MultiUserUtilsModule;
import com.android.systemui.shade.NotificationShadeWindowControllerImpl;
import com.android.systemui.shade.ShadeController;
-import com.android.systemui.shade.ShadeControllerImpl;
+import com.android.systemui.shade.ShadeControllerEmptyImpl;
import com.android.systemui.shade.ShadeExpansionStateManager;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.KeyboardShortcutsModule;
@@ -140,7 +140,7 @@
abstract DockManager bindDockManager(DockManagerImpl dockManager);
@Binds
- abstract ShadeController provideShadeController(ShadeControllerImpl shadeController);
+ abstract ShadeController provideShadeController(ShadeControllerEmptyImpl shadeController);
@SysUISingleton
@Provides
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt b/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt
index eed7950..cbe4020 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt
@@ -94,7 +94,7 @@
wakefulnessLifecycle.addObserver(this)
// TODO(b/254878364): remove this call to NPVC.getView()
- getShadeFoldAnimator().view.repeatWhenAttached {
+ getShadeFoldAnimator().view?.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.STARTED) { listenForDozing(this) }
}
}
@@ -161,10 +161,9 @@
// but we should wait for the initial animation preparations to be drawn
// (setting initial alpha/translation)
// TODO(b/254878364): remove this call to NPVC.getView()
- OneShotPreDrawListener.add(
- getShadeFoldAnimator().view,
- onReady
- )
+ getShadeFoldAnimator().view?.let {
+ OneShotPreDrawListener.add(it, onReady)
+ }
} else {
// No animation, call ready callback immediately
onReady.run()
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
index ab4ead6..4d506f0 100644
--- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
@@ -532,6 +532,12 @@
}
}
+ /** Returns the ID of the currently-selected user. */
+ @UserIdInt
+ fun getSelectedUserId(): Int {
+ return repository.getSelectedUserInfo().id
+ }
+
private fun showDialog(request: ShowDialogRequestModel) {
_dialogShowRequests.value = request
}
@@ -774,17 +780,16 @@
}
// TODO(b/246631653): cache the bitmaps to avoid the background work to fetch them.
- val userIcon = withContext(backgroundDispatcher) {
- manager.getUserIcon(userId)
- ?.let { bitmap ->
+ val userIcon =
+ withContext(backgroundDispatcher) {
+ manager.getUserIcon(userId)?.let { bitmap ->
val iconSize =
- applicationContext
- .resources
- .getDimensionPixelSize(R.dimen.bouncer_user_switcher_icon_size)
+ applicationContext.resources.getDimensionPixelSize(
+ R.dimen.bouncer_user_switcher_icon_size
+ )
Icon.scaleDownIfNecessary(bitmap, iconSize, iconSize)
}
- }
-
+ }
if (userIcon != null) {
return BitmapDrawable(userIcon)
diff --git a/packages/SystemUI/src/com/android/systemui/util/WallpaperController.kt b/packages/SystemUI/src/com/android/systemui/util/WallpaperController.kt
index db2aca8..65a0218 100644
--- a/packages/SystemUI/src/com/android/systemui/util/WallpaperController.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/WallpaperController.kt
@@ -16,32 +16,34 @@
package com.android.systemui.util
-import android.app.WallpaperInfo
import android.app.WallpaperManager
import android.util.Log
import android.view.View
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.wallpapers.data.repository.WallpaperRepository
import javax.inject.Inject
import kotlin.math.max
private const val TAG = "WallpaperController"
+/**
+ * Controller for wallpaper-related logic.
+ *
+ * Note: New logic should be added to [WallpaperRepository], not this class.
+ */
@SysUISingleton
-class WallpaperController @Inject constructor(private val wallpaperManager: WallpaperManager) {
+class WallpaperController @Inject constructor(
+ private val wallpaperManager: WallpaperManager,
+ private val wallpaperRepository: WallpaperRepository,
+) {
var rootView: View? = null
private var notificationShadeZoomOut: Float = 0f
private var unfoldTransitionZoomOut: Float = 0f
- private var wallpaperInfo: WallpaperInfo? = null
-
- fun onWallpaperInfoUpdated(wallpaperInfo: WallpaperInfo?) {
- this.wallpaperInfo = wallpaperInfo
- }
-
private val shouldUseDefaultUnfoldTransition: Boolean
- get() = wallpaperInfo?.shouldUseDefaultUnfoldTransition()
+ get() = wallpaperRepository.wallpaperInfo.value?.shouldUseDefaultUnfoldTransition()
?: true
fun setNotificationShadeZoom(zoomOut: Float) {
diff --git a/packages/SystemUI/src/com/android/systemui/util/wrapper/LottieViewWrapper.kt b/packages/SystemUI/src/com/android/systemui/util/wrapper/LottieViewWrapper.kt
new file mode 100644
index 0000000..a804923
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/wrapper/LottieViewWrapper.kt
@@ -0,0 +1,39 @@
+/*
+ * 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.util.wrapper
+
+import android.content.Context
+import android.util.AttributeSet
+import com.airbnb.lottie.LottieAnimationView
+import com.android.systemui.util.traceSection
+
+/** LottieAnimationView that traces each call to invalidate. */
+open class LottieViewWrapper : LottieAnimationView {
+ constructor(context: Context?) : super(context)
+ constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
+ constructor(
+ context: Context?,
+ attrs: AttributeSet?,
+ defStyleAttr: Int
+ ) : super(context, attrs, defStyleAttr)
+
+ override fun invalidate() {
+ traceSection<Any?>("${this::class} invalidate") {
+ super.invalidate()
+ null
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 9362220..87b2697 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -34,6 +34,7 @@
import static com.android.internal.jank.InteractionJankMonitor.CUJ_VOLUME_CONTROL;
import static com.android.internal.jank.InteractionJankMonitor.Configuration.Builder;
+import static com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION;
import static com.android.systemui.volume.Events.DISMISS_REASON_POSTURE_CHANGED;
import static com.android.systemui.volume.Events.DISMISS_REASON_SETTINGS_CLICKED;
@@ -82,6 +83,7 @@
import android.util.SparseBooleanArray;
import android.view.ContextThemeWrapper;
import android.view.Gravity;
+import android.view.HapticFeedbackConstants;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.AccessibilityDelegate;
@@ -120,6 +122,7 @@
import com.android.systemui.Prefs;
import com.android.systemui.R;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.media.dialog.MediaOutputDialogFactory;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.VolumeDialog;
@@ -168,6 +171,13 @@
/** Volume dialog slider animation. */
private static final String TYPE_UPDATE = "update";
+ /**
+ * TODO(b/290612381): remove lingering animations or tolerate them
+ * When false, this will cause this class to not listen to animator events and not record jank
+ * events. This should never be false in production code, and only is false for unit tests for
+ * this class. This flag should be true in Scenario/Integration tests.
+ */
+ private final boolean mShouldListenForJank;
private final int mDialogShowAnimationDurationMs;
private final int mDialogHideAnimationDurationMs;
private int mDialogWidth;
@@ -293,6 +303,7 @@
private final DevicePostureController mDevicePostureController;
private @DevicePostureController.DevicePostureInt int mDevicePosture;
private int mOrientation;
+ private final FeatureFlags mFeatureFlags;
public VolumeDialogImpl(
Context context,
@@ -304,13 +315,18 @@
VolumePanelFactory volumePanelFactory,
ActivityStarter activityStarter,
InteractionJankMonitor interactionJankMonitor,
+ boolean shouldListenForJank,
CsdWarningDialog.Factory csdWarningDialogFactory,
DevicePostureController devicePostureController,
Looper looper,
- DumpManager dumpManager) {
+ DumpManager dumpManager,
+ FeatureFlags featureFlags) {
+ mFeatureFlags = featureFlags;
mContext =
new ContextThemeWrapper(context, R.style.volume_dialog_theme);
mHandler = new H(looper);
+
+ mShouldListenForJank = shouldListenForJank;
mController = volumeDialogController;
mKeyguard = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
mActivityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
@@ -1309,12 +1325,14 @@
private void provideTouchFeedbackH(int newRingerMode) {
VibrationEffect effect = null;
+ int hapticConstant = HapticFeedbackConstants.NO_HAPTICS;
switch (newRingerMode) {
case RINGER_MODE_NORMAL:
mController.scheduleTouchFeedback();
break;
case RINGER_MODE_SILENT:
effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
+ hapticConstant = HapticFeedbackConstants.TOGGLE_OFF;
break;
case RINGER_MODE_VIBRATE:
// Feedback handled by onStateChange, for feedback both when user toggles
@@ -1322,8 +1340,11 @@
break;
default:
effect = VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK);
+ hapticConstant = HapticFeedbackConstants.TOGGLE_ON;
}
- if (effect != null) {
+ if (mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) {
+ mDialogView.performHapticFeedback(hapticConstant);
+ } else if (effect != null) {
mController.vibrate(effect);
}
}
@@ -1368,7 +1389,10 @@
}
private Animator.AnimatorListener getJankListener(View v, String type, long timeout) {
- return new Animator.AnimatorListener() {
+ if (!mShouldListenForJank) {
+ // TODO(b/290612381): temporary fix to prevent null pointers on leftover JankMonitors
+ return null;
+ } else return new Animator.AnimatorListener() {
@Override
public void onAnimationStart(@NonNull Animator animation) {
if (!v.isAttachedToWindow()) {
@@ -1757,7 +1781,22 @@
&& mState.ringerModeInternal != -1
&& mState.ringerModeInternal != state.ringerModeInternal
&& state.ringerModeInternal == AudioManager.RINGER_MODE_VIBRATE) {
- mController.vibrate(VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK));
+
+ if (mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) {
+ if (mShowing) {
+ // The dialog view is responsible for triggering haptics in the oneway API
+ mDialogView.performHapticFeedback(HapticFeedbackConstants.TOGGLE_ON);
+ }
+ /*
+ TODO(b/290642122): If the dialog is not showing, we have the case where haptics is
+ enabled by dragging the volume slider of Settings to a value of 0. This must be
+ handled by view Slices in Settings whilst using the performHapticFeedback API.
+ */
+
+ } else {
+ // Old behavior only active if the oneway API is not used.
+ mController.vibrate(VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK));
+ }
}
mState = state;
mDynamic.clear();
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
index aa4ee54..cc9f3e1 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
@@ -22,6 +22,7 @@
import com.android.internal.jank.InteractionJankMonitor;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.media.dialog.MediaOutputDialogFactory;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.VolumeDialog;
@@ -61,7 +62,8 @@
InteractionJankMonitor interactionJankMonitor,
CsdWarningDialog.Factory csdFactory,
DevicePostureController devicePostureController,
- DumpManager dumpManager) {
+ DumpManager dumpManager,
+ FeatureFlags featureFlags) {
VolumeDialogImpl impl = new VolumeDialogImpl(
context,
volumeDialogController,
@@ -72,10 +74,12 @@
volumePanelFactory,
activityStarter,
interactionJankMonitor,
+ true, /* should listen for jank */
csdFactory,
devicePostureController,
Looper.getMainLooper(),
- dumpManager);
+ dumpManager,
+ featureFlags);
impl.setStreamImportant(AudioManager.STREAM_SYSTEM, false);
impl.setAutomute(true);
impl.setSilentMode(false);
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/dagger/NoopWallpaperModule.kt b/packages/SystemUI/src/com/android/systemui/wallpapers/dagger/NoopWallpaperModule.kt
new file mode 100644
index 0000000..baf88b0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/dagger/NoopWallpaperModule.kt
@@ -0,0 +1,30 @@
+/*
+ * 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.wallpapers.dagger
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.wallpapers.data.repository.NoopWallpaperRepository
+import com.android.systemui.wallpapers.data.repository.WallpaperRepository
+import dagger.Binds
+import dagger.Module
+
+@Module
+interface NoopWallpaperModule {
+ @Binds
+ @SysUISingleton
+ fun bindWallpaperRepository(impl: NoopWallpaperRepository): WallpaperRepository
+}
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/dagger/WallpaperModule.kt b/packages/SystemUI/src/com/android/systemui/wallpapers/dagger/WallpaperModule.kt
new file mode 100644
index 0000000..1b89978
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/dagger/WallpaperModule.kt
@@ -0,0 +1,30 @@
+/*
+ * 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.wallpapers.dagger
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.wallpapers.data.repository.WallpaperRepository
+import com.android.systemui.wallpapers.data.repository.WallpaperRepositoryImpl
+import dagger.Binds
+import dagger.Module
+
+@Module
+interface WallpaperModule {
+ @Binds
+ @SysUISingleton
+ fun bindWallpaperRepository(impl: WallpaperRepositoryImpl): WallpaperRepository
+}
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/NoopWallpaperRepository.kt b/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/NoopWallpaperRepository.kt
new file mode 100644
index 0000000..b45b8cd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/NoopWallpaperRepository.kt
@@ -0,0 +1,36 @@
+/*
+ * 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.wallpapers.data.repository
+
+import android.app.WallpaperInfo
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+/**
+ * A no-op implementation of [WallpaperRepository].
+ *
+ * Used for variants of SysUI that do not support wallpaper but require other SysUI classes that
+ * have a wallpaper dependency.
+ */
+@SysUISingleton
+class NoopWallpaperRepository @Inject constructor() : WallpaperRepository {
+ override val wallpaperInfo: StateFlow<WallpaperInfo?> = MutableStateFlow(null).asStateFlow()
+ override val wallpaperSupportsAmbientMode = MutableStateFlow(false).asStateFlow()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/WallpaperRepository.kt b/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/WallpaperRepository.kt
new file mode 100644
index 0000000..b8f9583
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/WallpaperRepository.kt
@@ -0,0 +1,117 @@
+/*
+ * 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.wallpapers.data.repository
+
+import android.app.WallpaperInfo
+import android.app.WallpaperManager
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.os.UserHandle
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.user.data.model.SelectedUserModel
+import com.android.systemui.user.data.model.SelectionStatus
+import com.android.systemui.user.data.repository.UserRepository
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.stateIn
+
+/** A repository storing information about the current wallpaper. */
+interface WallpaperRepository {
+ /** Emits the current user's current wallpaper. */
+ val wallpaperInfo: StateFlow<WallpaperInfo?>
+
+ /** Emits true if the current user's current wallpaper supports ambient mode. */
+ val wallpaperSupportsAmbientMode: StateFlow<Boolean>
+}
+
+@SysUISingleton
+class WallpaperRepositoryImpl
+@Inject
+constructor(
+ @Application scope: CoroutineScope,
+ broadcastDispatcher: BroadcastDispatcher,
+ userRepository: UserRepository,
+ private val wallpaperManager: WallpaperManager,
+ context: Context,
+) : WallpaperRepository {
+ private val deviceSupportsAodWallpaper =
+ context.resources.getBoolean(com.android.internal.R.bool.config_dozeSupportsAodWallpaper)
+
+ private val wallpaperChanged: Flow<Unit> =
+ broadcastDispatcher
+ .broadcastFlow(
+ IntentFilter(Intent.ACTION_WALLPAPER_CHANGED),
+ user = UserHandle.ALL,
+ )
+ // The `combine` defining `wallpaperSupportsAmbientMode` will not run until both of the
+ // input flows emit at least once. Since this flow is an input flow, it needs to emit
+ // when it starts up to ensure that the `combine` will run if the user changes before we
+ // receive a ACTION_WALLPAPER_CHANGED intent.
+ // Note that the `selectedUser` flow does *not* need to emit on start because
+ // [UserRepository.selectedUser] is a state flow which will automatically emit a value
+ // on start.
+ .onStart { emit(Unit) }
+
+ private val selectedUser: Flow<SelectedUserModel> =
+ userRepository.selectedUser
+ // Only update the wallpaper status once the user selection has finished.
+ .filter { it.selectionStatus == SelectionStatus.SELECTION_COMPLETE }
+
+ override val wallpaperInfo: StateFlow<WallpaperInfo?> =
+ if (!wallpaperManager.isWallpaperSupported || !deviceSupportsAodWallpaper) {
+ MutableStateFlow(null).asStateFlow()
+ } else {
+ combine(wallpaperChanged, selectedUser) { _, selectedUser ->
+ getWallpaper(selectedUser)
+ }
+ .stateIn(
+ scope,
+ // Always be listening for wallpaper changes.
+ SharingStarted.Eagerly,
+ initialValue = getWallpaper(userRepository.selectedUser.value),
+ )
+ }
+
+ override val wallpaperSupportsAmbientMode: StateFlow<Boolean> =
+ wallpaperInfo
+ .map {
+ // If WallpaperInfo is null, it's ImageWallpaper which never supports ambient mode.
+ it?.supportsAmbientMode() == true
+ }
+ .stateIn(
+ scope,
+ // Always be listening for wallpaper changes.
+ SharingStarted.Eagerly,
+ initialValue = wallpaperInfo.value?.supportsAmbientMode() == true,
+ )
+
+ private fun getWallpaper(selectedUser: SelectedUserModel): WallpaperInfo? {
+ return wallpaperManager.getWallpaperInfoForUser(selectedUser.userInfo.id)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index 5144d19..943e906 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -51,15 +51,11 @@
import com.android.systemui.notetask.NoteTaskInitializer;
import com.android.systemui.settings.DisplayTracker;
import com.android.systemui.settings.UserTracker;
-import com.android.systemui.shared.tracing.ProtoTraceable;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.tracing.ProtoTracer;
-import com.android.systemui.tracing.nano.SystemUiTraceProto;
import com.android.wm.shell.desktopmode.DesktopMode;
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
-import com.android.wm.shell.nano.WmShellTraceProto;
import com.android.wm.shell.onehanded.OneHanded;
import com.android.wm.shell.onehanded.OneHandedEventCallback;
import com.android.wm.shell.onehanded.OneHandedTransitionCallback;
@@ -94,8 +90,7 @@
@SysUISingleton
public final class WMShell implements
CoreStartable,
- CommandQueue.Callbacks,
- ProtoTraceable<SystemUiTraceProto> {
+ CommandQueue.Callbacks {
private static final String TAG = WMShell.class.getName();
private static final int INVALID_SYSUI_STATE_MASK =
SYSUI_STATE_DIALOG_SHOWING
@@ -122,7 +117,6 @@
private final ScreenLifecycle mScreenLifecycle;
private final SysUiState mSysUiState;
private final WakefulnessLifecycle mWakefulnessLifecycle;
- private final ProtoTracer mProtoTracer;
private final UserTracker mUserTracker;
private final DisplayTracker mDisplayTracker;
private final NoteTaskInitializer mNoteTaskInitializer;
@@ -184,7 +178,6 @@
KeyguardUpdateMonitor keyguardUpdateMonitor,
ScreenLifecycle screenLifecycle,
SysUiState sysUiState,
- ProtoTracer protoTracer,
WakefulnessLifecycle wakefulnessLifecycle,
UserTracker userTracker,
DisplayTracker displayTracker,
@@ -203,7 +196,6 @@
mOneHandedOptional = oneHandedOptional;
mDesktopModeOptional = desktopMode;
mWakefulnessLifecycle = wakefulnessLifecycle;
- mProtoTracer = protoTracer;
mUserTracker = userTracker;
mDisplayTracker = displayTracker;
mNoteTaskInitializer = noteTaskInitializer;
@@ -223,7 +215,6 @@
// Subscribe to user changes
mUserTracker.addCallback(mUserChangedCallback, mContext.getMainExecutor());
- mProtoTracer.add(this);
mCommandQueue.addCallback(this);
mPipOptional.ifPresent(this::initPip);
mSplitScreenOptional.ifPresent(this::initSplitScreen);
@@ -361,12 +352,6 @@
}
@Override
- public void writeToProto(SystemUiTraceProto proto) {
- // Dump to WMShell proto here
- // TODO: Figure out how we want to synchronize while dumping to proto
- }
-
- @Override
public void dump(PrintWriter pw, String[] args) {
// Handle commands if provided
if (mShell.handleCommand(args, pw)) {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java
index 319a02d..d506584 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java
@@ -33,12 +33,15 @@
import android.app.admin.IKeyguardClient;
import android.content.ComponentName;
import android.content.Intent;
+import android.hardware.display.DisplayManager;
+import android.os.Binder;
import android.os.Handler;
import android.os.RemoteException;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
import android.testing.ViewUtils;
+import android.view.Display;
import android.view.SurfaceControlViewHost;
import android.view.SurfaceView;
import android.view.View;
@@ -50,7 +53,6 @@
import org.junit.After;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -60,7 +62,6 @@
@RunWithLooper
@RunWith(AndroidTestingRunner.class)
@SmallTest
-@Ignore("b/286245842")
public class AdminSecondaryLockScreenControllerTest extends SysuiTestCase {
private static final int TARGET_USER_ID = KeyguardUpdateMonitor.getCurrentUser();
@@ -79,7 +80,7 @@
private KeyguardSecurityCallback mKeyguardCallback;
@Mock
private KeyguardUpdateMonitor mUpdateMonitor;
- @Mock
+
private SurfaceControlViewHost.SurfacePackage mSurfacePackage;
@Before
@@ -99,6 +100,11 @@
when(mKeyguardClient.queryLocalInterface(anyString())).thenReturn(mKeyguardClient);
when(mKeyguardClient.asBinder()).thenReturn(mKeyguardClient);
+ Display display = mContext.getSystemService(DisplayManager.class).getDisplay(
+ Display.DEFAULT_DISPLAY);
+ mSurfacePackage = (new SurfaceControlViewHost(mContext, display,
+ new Binder())).getSurfacePackage();
+
mTestController = new AdminSecondaryLockScreenController.Factory(
mContext, mKeyguardSecurityContainer, mUpdateMonitor, mHandler)
.create(mKeyguardCallback);
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
index fa32835..677d3ff 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
@@ -187,9 +187,7 @@
@Test
public void testLockedOut_verifyPasswordAndUnlock_doesNotEnableViewInput() {
- mKeyguardAbsKeyInputViewController.handleAttemptLockout(
- SystemClock.elapsedRealtime() + 1000);
- mKeyguardAbsKeyInputViewController.verifyPasswordAndUnlock();
+ mKeyguardAbsKeyInputViewController.handleAttemptLockout(SystemClock.elapsedRealtime());
verify(mAbsKeyInputView).setPasswordEntryInputEnabled(false);
verify(mAbsKeyInputView).setPasswordEntryEnabled(false);
verify(mAbsKeyInputView, never()).setPasswordEntryInputEnabled(true);
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java
index f234662..ac04bc4 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java
@@ -28,15 +28,12 @@
import static org.mockito.Mockito.when;
import android.content.res.Resources;
-import android.testing.AndroidTestingRunner;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
-import androidx.test.filters.SmallTest;
-
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dump.DumpManager;
@@ -63,14 +60,11 @@
import com.android.systemui.util.time.FakeSystemClock;
import org.junit.Before;
-import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
public class KeyguardClockSwitchControllerBaseTest extends SysuiTestCase {
@Mock
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
index 5d75428..cb18229 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
@@ -76,7 +76,7 @@
private lateinit var mKeyguardMessageAreaController:
KeyguardMessageAreaController<BouncerKeyguardMessageArea>
- @Mock private lateinit var mPostureController: DevicePostureController
+ @Mock private lateinit var mPostureController: DevicePostureController
private lateinit var mKeyguardPatternViewController: KeyguardPatternViewController
private lateinit var fakeFeatureFlags: FakeFeatureFlags
@@ -119,7 +119,7 @@
mKeyguardPatternViewController.onViewAttached()
- assertThat(getPatternTopGuideline()).isEqualTo(getExpectedTopGuideline())
+ assertThat(getPatternTopGuideline()).isEqualTo(getHalfOpenedBouncerHeightRatio())
}
@Test
@@ -131,15 +131,20 @@
mKeyguardPatternViewController.onViewAttached()
// Verify view begins in posture state DEVICE_POSTURE_HALF_OPENED
- assertThat(getPatternTopGuideline()).isEqualTo(getExpectedTopGuideline())
+ assertThat(getPatternTopGuideline()).isEqualTo(getHalfOpenedBouncerHeightRatio())
// Simulate posture change to state DEVICE_POSTURE_OPENED with callback
verify(mPostureController).addCallback(postureCallbackCaptor.capture())
val postureCallback: DevicePostureController.Callback = postureCallbackCaptor.value
postureCallback.onPostureChanged(DEVICE_POSTURE_OPENED)
- // Verify view is now in posture state DEVICE_POSTURE_OPENED
- assertThat(getPatternTopGuideline()).isNotEqualTo(getExpectedTopGuideline())
+ // Simulate posture change to same state with callback
+ assertThat(getPatternTopGuideline()).isNotEqualTo(getHalfOpenedBouncerHeightRatio())
+
+ postureCallback.onPostureChanged(DEVICE_POSTURE_OPENED)
+
+ // Verify view is still in posture state DEVICE_POSTURE_OPENED
+ assertThat(getPatternTopGuideline()).isNotEqualTo(getHalfOpenedBouncerHeightRatio())
}
private fun getPatternTopGuideline(): Float {
@@ -150,7 +155,7 @@
return cs.getConstraint(R.id.pattern_top_guideline).layout.guidePercent
}
- private fun getExpectedTopGuideline(): Float {
+ private fun getHalfOpenedBouncerHeightRatio(): Float {
return mContext.resources.getFloat(R.dimen.half_opened_bouncer_height_ratio)
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
index 9db267c..4dc7652 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
@@ -19,6 +19,8 @@
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.View
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.constraintlayout.widget.ConstraintSet
import androidx.test.filters.SmallTest
import com.android.internal.util.LatencyTracker
import com.android.internal.widget.LockPatternUtils
@@ -32,6 +34,8 @@
import com.android.systemui.statusbar.policy.DevicePostureController
import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_HALF_OPENED
import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_OPENED
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -51,7 +55,10 @@
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper
class KeyguardPinViewControllerTest : SysuiTestCase() {
- @Mock private lateinit var keyguardPinView: KeyguardPINView
+
+ private lateinit var objectKeyguardPINView: KeyguardPINView
+
+ @Mock private lateinit var mockKeyguardPinView: KeyguardPINView
@Mock private lateinit var keyguardMessageArea: BouncerKeyguardMessageArea
@@ -83,63 +90,73 @@
@Mock lateinit var deleteButton: NumPadButton
@Mock lateinit var enterButton: View
- private lateinit var pinViewController: KeyguardPinViewController
-
@Captor lateinit var postureCallbackCaptor: ArgumentCaptor<DevicePostureController.Callback>
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
- Mockito.`when`(keyguardPinView.requireViewById<View>(R.id.bouncer_message_area))
+ Mockito.`when`(mockKeyguardPinView.requireViewById<View>(R.id.bouncer_message_area))
.thenReturn(keyguardMessageArea)
Mockito.`when`(
keyguardMessageAreaControllerFactory.create(any(KeyguardMessageArea::class.java))
)
.thenReturn(keyguardMessageAreaController)
- `when`(keyguardPinView.passwordTextViewId).thenReturn(R.id.pinEntry)
- `when`(keyguardPinView.findViewById<PasswordTextView>(R.id.pinEntry))
+ `when`(mockKeyguardPinView.passwordTextViewId).thenReturn(R.id.pinEntry)
+ `when`(mockKeyguardPinView.findViewById<PasswordTextView>(R.id.pinEntry))
.thenReturn(passwordTextView)
- `when`(keyguardPinView.resources).thenReturn(context.resources)
- `when`(keyguardPinView.findViewById<NumPadButton>(R.id.delete_button))
+ `when`(mockKeyguardPinView.resources).thenReturn(context.resources)
+ `when`(mockKeyguardPinView.findViewById<NumPadButton>(R.id.delete_button))
.thenReturn(deleteButton)
- `when`(keyguardPinView.findViewById<View>(R.id.key_enter)).thenReturn(enterButton)
+ `when`(mockKeyguardPinView.findViewById<View>(R.id.key_enter)).thenReturn(enterButton)
// For posture tests:
- `when`(keyguardPinView.buttons).thenReturn(arrayOf())
+ `when`(mockKeyguardPinView.buttons).thenReturn(arrayOf())
+ `when`(lockPatternUtils.getPinLength(anyInt())).thenReturn(6)
- pinViewController =
- KeyguardPinViewController(
- keyguardPinView,
- keyguardUpdateMonitor,
- securityMode,
- lockPatternUtils,
- mKeyguardSecurityCallback,
- keyguardMessageAreaControllerFactory,
- mLatencyTracker,
- liftToActivateListener,
- mEmergencyButtonController,
- falsingCollector,
- postureController,
- featureFlags
- )
+ objectKeyguardPINView =
+ View.inflate(mContext, R.layout.keyguard_pin_view, null)
+ .findViewById(R.id.keyguard_pin_view) as KeyguardPINView
+ }
+
+ private fun constructPinViewController(
+ mKeyguardPinView: KeyguardPINView
+ ): KeyguardPinViewController {
+ return KeyguardPinViewController(
+ mKeyguardPinView,
+ keyguardUpdateMonitor,
+ securityMode,
+ lockPatternUtils,
+ mKeyguardSecurityCallback,
+ keyguardMessageAreaControllerFactory,
+ mLatencyTracker,
+ liftToActivateListener,
+ mEmergencyButtonController,
+ falsingCollector,
+ postureController,
+ featureFlags
+ )
}
@Test
- fun onViewAttached_deviceHalfFolded_propagatedToPinView() {
- `when`(postureController.devicePosture).thenReturn(DEVICE_POSTURE_HALF_OPENED)
+ fun onViewAttached_deviceHalfFolded_propagatedToPatternView() {
+ val pinViewController = constructPinViewController(objectKeyguardPINView)
+ overrideResource(R.dimen.half_opened_bouncer_height_ratio, 0.5f)
+ whenever(postureController.devicePosture).thenReturn(DEVICE_POSTURE_HALF_OPENED)
pinViewController.onViewAttached()
- verify(keyguardPinView).onDevicePostureChanged(DEVICE_POSTURE_HALF_OPENED)
+ assertThat(getPinTopGuideline()).isEqualTo(getHalfOpenedBouncerHeightRatio())
}
@Test
- fun onDevicePostureChanged_deviceHalfFolded_propagatedToPinView() {
- `when`(postureController.devicePosture).thenReturn(DEVICE_POSTURE_HALF_OPENED)
+ fun onDevicePostureChanged_deviceOpened_propagatedToPatternView() {
+ val pinViewController = constructPinViewController(objectKeyguardPINView)
+ overrideResource(R.dimen.half_opened_bouncer_height_ratio, 0.5f)
+
+ whenever(postureController.devicePosture).thenReturn(DEVICE_POSTURE_HALF_OPENED)
+ pinViewController.onViewAttached()
// Verify view begins in posture state DEVICE_POSTURE_HALF_OPENED
- pinViewController.onViewAttached()
-
- verify(keyguardPinView).onDevicePostureChanged(DEVICE_POSTURE_HALF_OPENED)
+ assertThat(getPinTopGuideline()).isEqualTo(getHalfOpenedBouncerHeightRatio())
// Simulate posture change to state DEVICE_POSTURE_OPENED with callback
verify(postureController).addCallback(postureCallbackCaptor.capture())
@@ -147,25 +164,49 @@
postureCallback.onPostureChanged(DEVICE_POSTURE_OPENED)
// Verify view is now in posture state DEVICE_POSTURE_OPENED
- verify(keyguardPinView).onDevicePostureChanged(DEVICE_POSTURE_OPENED)
+ assertThat(getPinTopGuideline()).isNotEqualTo(getHalfOpenedBouncerHeightRatio())
+
+ // Simulate posture change to same state with callback
+ postureCallback.onPostureChanged(DEVICE_POSTURE_OPENED)
+
+ // Verify view is still in posture state DEVICE_POSTURE_OPENED
+ assertThat(getPinTopGuideline()).isNotEqualTo(getHalfOpenedBouncerHeightRatio())
+ }
+
+ private fun getPinTopGuideline(): Float {
+ val cs = ConstraintSet()
+ val container = objectKeyguardPINView.findViewById(R.id.pin_container) as ConstraintLayout
+ cs.clone(container)
+ return cs.getConstraint(R.id.pin_pad_top_guideline).layout.guidePercent
+ }
+
+ private fun getHalfOpenedBouncerHeightRatio(): Float {
+ return mContext.resources.getFloat(R.dimen.half_opened_bouncer_height_ratio)
}
@Test
fun startAppearAnimation() {
+ val pinViewController = constructPinViewController(mockKeyguardPinView)
+
pinViewController.startAppearAnimation()
+
verify(keyguardMessageAreaController)
.setMessage(context.resources.getString(R.string.keyguard_enter_your_pin), false)
}
@Test
fun startAppearAnimation_withExistingMessage() {
+ val pinViewController = constructPinViewController(mockKeyguardPinView)
Mockito.`when`(keyguardMessageAreaController.message).thenReturn("Unlock to continue.")
+
pinViewController.startAppearAnimation()
+
verify(keyguardMessageAreaController, Mockito.never()).setMessage(anyString(), anyBoolean())
}
@Test
fun startAppearAnimation_withAutoPinConfirmationFailedPasswordAttemptsLessThan5() {
+ val pinViewController = constructPinViewController(mockKeyguardPinView)
`when`(featureFlags.isEnabled(Flags.AUTO_PIN_CONFIRMATION)).thenReturn(true)
`when`(lockPatternUtils.getPinLength(anyInt())).thenReturn(6)
`when`(lockPatternUtils.isAutoPinConfirmEnabled(anyInt())).thenReturn(true)
@@ -173,6 +214,7 @@
`when`(passwordTextView.text).thenReturn("")
pinViewController.startAppearAnimation()
+
verify(deleteButton).visibility = View.INVISIBLE
verify(enterButton).visibility = View.INVISIBLE
verify(passwordTextView).setUsePinShapes(true)
@@ -181,6 +223,7 @@
@Test
fun startAppearAnimation_withAutoPinConfirmationFailedPasswordAttemptsMoreThan5() {
+ val pinViewController = constructPinViewController(mockKeyguardPinView)
`when`(featureFlags.isEnabled(Flags.AUTO_PIN_CONFIRMATION)).thenReturn(true)
`when`(lockPatternUtils.getPinLength(anyInt())).thenReturn(6)
`when`(lockPatternUtils.isAutoPinConfirmEnabled(anyInt())).thenReturn(true)
@@ -188,6 +231,7 @@
`when`(passwordTextView.text).thenReturn("")
pinViewController.startAppearAnimation()
+
verify(deleteButton).visibility = View.VISIBLE
verify(enterButton).visibility = View.VISIBLE
verify(passwordTextView).setUsePinShapes(true)
@@ -196,7 +240,10 @@
@Test
fun handleLockout_readsNumberOfErrorAttempts() {
+ val pinViewController = constructPinViewController(mockKeyguardPinView)
+
pinViewController.handleAttemptLockout(0)
+
verify(lockPatternUtils).getCurrentFailedPasswordAttempts(anyInt())
}
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
deleted file mode 100644
index 58b1edc..0000000
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
+++ /dev/null
@@ -1,732 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.keyguard;
-
-import static com.android.keyguard.KeyguardSecurityContainer.MODE_DEFAULT;
-import static com.android.keyguard.KeyguardSecurityContainer.MODE_ONE_HANDED;
-import static com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.EXPANSION_VISIBLE;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertTrue;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.argThat;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.atLeastOnce;
-import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.hardware.biometrics.BiometricOverlayConstants;
-import android.media.AudioManager;
-import android.telephony.TelephonyManager;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-import android.testing.TestableResources;
-import android.view.Gravity;
-import android.view.LayoutInflater;
-import android.view.MotionEvent;
-import android.view.WindowInsetsController;
-import android.widget.FrameLayout;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.UiEventLogger;
-import com.android.internal.widget.LockPatternUtils;
-import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
-import com.android.systemui.R;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.biometrics.SideFpsController;
-import com.android.systemui.biometrics.SideFpsUiRequestSource;
-import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor;
-import com.android.systemui.classifier.FalsingA11yDelegate;
-import com.android.systemui.classifier.FalsingCollector;
-import com.android.systemui.flags.FakeFeatureFlags;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
-import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
-import com.android.systemui.log.SessionTracker;
-import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.statusbar.policy.UserSwitcherController;
-import com.android.systemui.util.settings.GlobalSettings;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.ArgumentMatcher;
-import org.mockito.Captor;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoRule;
-
-import java.util.Optional;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper()
-public class KeyguardSecurityContainerControllerTest extends SysuiTestCase {
- private static final int TARGET_USER_ID = 100;
- @Rule
- public MockitoRule mRule = MockitoJUnit.rule();
- @Mock
- private KeyguardSecurityContainer mView;
- @Mock
- private AdminSecondaryLockScreenController.Factory mAdminSecondaryLockScreenControllerFactory;
- @Mock
- private AdminSecondaryLockScreenController mAdminSecondaryLockScreenController;
- @Mock
- private LockPatternUtils mLockPatternUtils;
- @Mock
- private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
- @Mock
- private KeyguardSecurityModel mKeyguardSecurityModel;
- @Mock
- private MetricsLogger mMetricsLogger;
- @Mock
- private UiEventLogger mUiEventLogger;
- @Mock
- private KeyguardStateController mKeyguardStateController;
- @Mock
- private KeyguardInputViewController mInputViewController;
- @Mock
- private WindowInsetsController mWindowInsetsController;
- @Mock
- private KeyguardSecurityViewFlipper mSecurityViewFlipper;
- @Mock
- private KeyguardSecurityViewFlipperController mKeyguardSecurityViewFlipperController;
- @Mock
- private KeyguardMessageAreaController.Factory mKeyguardMessageAreaControllerFactory;
- @Mock
- private KeyguardMessageAreaController mKeyguardMessageAreaController;
- @Mock
- private BouncerKeyguardMessageArea mKeyguardMessageArea;
- @Mock
- private ConfigurationController mConfigurationController;
- @Mock
- private EmergencyButtonController mEmergencyButtonController;
- @Mock
- private FalsingCollector mFalsingCollector;
- @Mock
- private FalsingManager mFalsingManager;
- @Mock
- private GlobalSettings mGlobalSettings;
- @Mock
- private FeatureFlags mFeatureFlags;
- @Mock
- private UserSwitcherController mUserSwitcherController;
- @Mock
- private SessionTracker mSessionTracker;
- @Mock
- private KeyguardViewController mKeyguardViewController;
- @Mock
- private SideFpsController mSideFpsController;
- @Mock
- private KeyguardPasswordViewController mKeyguardPasswordViewControllerMock;
- @Mock
- private FalsingA11yDelegate mFalsingA11yDelegate;
- @Mock
- private TelephonyManager mTelephonyManager;
- @Mock
- private ViewMediatorCallback mViewMediatorCallback;
- @Mock
- private AudioManager mAudioManager;
-
- @Captor
- private ArgumentCaptor<KeyguardUpdateMonitorCallback> mKeyguardUpdateMonitorCallback;
- @Captor
- private ArgumentCaptor<KeyguardSecurityContainer.SwipeListener> mSwipeListenerArgumentCaptor;
-
- @Captor
- private ArgumentCaptor<KeyguardSecurityViewFlipperController.OnViewInflatedCallback>
- mOnViewInflatedCallbackArgumentCaptor;
-
- private KeyguardSecurityContainerController mKeyguardSecurityContainerController;
- private KeyguardPasswordViewController mKeyguardPasswordViewController;
- private KeyguardPasswordView mKeyguardPasswordView;
- private TestableResources mTestableResources;
-
- @Before
- public void setup() {
- mTestableResources = mContext.getOrCreateTestableResources();
- mTestableResources.getResources().getConfiguration().orientation =
- Configuration.ORIENTATION_UNDEFINED;
-
- when(mView.getContext()).thenReturn(mContext);
- when(mView.getResources()).thenReturn(mTestableResources.getResources());
- FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(/* width= */ 0, /* height= */
- 0);
- lp.gravity = 0;
- when(mView.getLayoutParams()).thenReturn(lp);
- when(mAdminSecondaryLockScreenControllerFactory.create(any(KeyguardSecurityCallback.class)))
- .thenReturn(mAdminSecondaryLockScreenController);
- when(mSecurityViewFlipper.getWindowInsetsController()).thenReturn(mWindowInsetsController);
- mKeyguardPasswordView = spy((KeyguardPasswordView) LayoutInflater.from(mContext).inflate(
- R.layout.keyguard_password_view, null));
- when(mKeyguardPasswordView.getRootView()).thenReturn(mSecurityViewFlipper);
- when(mKeyguardPasswordView.requireViewById(R.id.bouncer_message_area))
- .thenReturn(mKeyguardMessageArea);
- when(mKeyguardMessageAreaControllerFactory.create(any(KeyguardMessageArea.class)))
- .thenReturn(mKeyguardMessageAreaController);
- when(mKeyguardPasswordView.getWindowInsetsController()).thenReturn(mWindowInsetsController);
- when(mKeyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(SecurityMode.PIN);
- when(mKeyguardStateController.canDismissLockScreen()).thenReturn(true);
- FakeFeatureFlags featureFlags = new FakeFeatureFlags();
- featureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true);
-
- mKeyguardPasswordViewController = new KeyguardPasswordViewController(
- (KeyguardPasswordView) mKeyguardPasswordView, mKeyguardUpdateMonitor,
- SecurityMode.Password, mLockPatternUtils, null,
- mKeyguardMessageAreaControllerFactory, null, null, mEmergencyButtonController,
- null, mock(Resources.class), null, mKeyguardViewController,
- featureFlags);
-
- mKeyguardSecurityContainerController = new KeyguardSecurityContainerController(
- mView, mAdminSecondaryLockScreenControllerFactory, mLockPatternUtils,
- mKeyguardUpdateMonitor, mKeyguardSecurityModel, mMetricsLogger, mUiEventLogger,
- mKeyguardStateController, mKeyguardSecurityViewFlipperController,
- mConfigurationController, mFalsingCollector, mFalsingManager,
- mUserSwitcherController, mFeatureFlags, mGlobalSettings,
- mSessionTracker, Optional.of(mSideFpsController), mFalsingA11yDelegate,
- mTelephonyManager, mViewMediatorCallback, mAudioManager,
- mock(KeyguardFaceAuthInteractor.class),
- mock(BouncerMessageInteractor.class));
- }
-
- @Test
- public void onInitConfiguresViewMode() {
- mKeyguardSecurityContainerController.onInit();
- verify(mView).initMode(eq(MODE_DEFAULT), eq(mGlobalSettings), eq(mFalsingManager),
- eq(mUserSwitcherController),
- any(KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class),
- eq(mFalsingA11yDelegate));
- }
-
- @Test
- public void showSecurityScreen_canInflateAllModes() {
- SecurityMode[] modes = SecurityMode.values();
- for (SecurityMode mode : modes) {
- when(mInputViewController.getSecurityMode()).thenReturn(mode);
- mKeyguardSecurityContainerController.showSecurityScreen(mode);
- if (mode == SecurityMode.Invalid) {
- verify(mKeyguardSecurityViewFlipperController, never()).getSecurityView(
- any(SecurityMode.class), any(KeyguardSecurityCallback.class), any(
- KeyguardSecurityViewFlipperController.OnViewInflatedCallback.class)
- );
- } else {
- verify(mKeyguardSecurityViewFlipperController).getSecurityView(
- eq(mode), any(KeyguardSecurityCallback.class), any(
- KeyguardSecurityViewFlipperController.OnViewInflatedCallback.class)
- );
- }
- }
- }
-
- @Test
- public void onResourcesUpdate_callsThroughOnRotationChange() {
- clearInvocations(mView);
-
- // Rotation is the same, shouldn't cause an update
- mKeyguardSecurityContainerController.updateResources();
- verify(mView, never()).initMode(eq(MODE_DEFAULT), eq(mGlobalSettings), eq(mFalsingManager),
- eq(mUserSwitcherController),
- any(KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class),
- eq(mFalsingA11yDelegate));
-
- // Update rotation. Should trigger update
- mTestableResources.getResources().getConfiguration().orientation =
- Configuration.ORIENTATION_LANDSCAPE;
-
- mKeyguardSecurityContainerController.updateResources();
- verify(mView).initMode(eq(MODE_DEFAULT), eq(mGlobalSettings), eq(mFalsingManager),
- eq(mUserSwitcherController),
- any(KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class),
- eq(mFalsingA11yDelegate));
- }
-
- private void touchDown() {
- mKeyguardSecurityContainerController.mGlobalTouchListener.onTouchEvent(
- MotionEvent.obtain(
- /* downTime= */0,
- /* eventTime= */0,
- MotionEvent.ACTION_DOWN,
- /* x= */0,
- /* y= */0,
- /* metaState= */0));
- }
-
- @Test
- public void onInterceptTap_inhibitsFalsingInSidedSecurityMode() {
-
- when(mView.isTouchOnTheOtherSideOfSecurity(any())).thenReturn(false);
- touchDown();
- verify(mFalsingCollector, never()).avoidGesture();
-
- when(mView.isTouchOnTheOtherSideOfSecurity(any())).thenReturn(true);
- touchDown();
- verify(mFalsingCollector).avoidGesture();
- }
-
- @Test
- public void showSecurityScreen_oneHandedMode_flagDisabled_noOneHandedMode() {
- mTestableResources.addOverride(R.bool.can_use_one_handed_bouncer, false);
- setupGetSecurityView(SecurityMode.Pattern);
-
- mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.Pattern);
- verify(mView).initMode(eq(MODE_DEFAULT), eq(mGlobalSettings), eq(mFalsingManager),
- eq(mUserSwitcherController),
- any(KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class),
- eq(mFalsingA11yDelegate));
- }
-
- @Test
- public void showSecurityScreen_oneHandedMode_flagEnabled_oneHandedMode() {
- mTestableResources.addOverride(R.bool.can_use_one_handed_bouncer, true);
- setupGetSecurityView(SecurityMode.Pattern);
- verify(mView).initMode(eq(MODE_ONE_HANDED), eq(mGlobalSettings), eq(mFalsingManager),
- eq(mUserSwitcherController),
- any(KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class),
- eq(mFalsingA11yDelegate));
- }
-
- @Test
- public void showSecurityScreen_twoHandedMode_flagEnabled_noOneHandedMode() {
- mTestableResources.addOverride(R.bool.can_use_one_handed_bouncer, true);
- setupGetSecurityView(SecurityMode.Password);
-
- verify(mView).initMode(eq(MODE_DEFAULT), eq(mGlobalSettings), eq(mFalsingManager),
- eq(mUserSwitcherController),
- any(KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class),
- eq(mFalsingA11yDelegate));
- }
-
- @Test
- public void addUserSwitcherCallback() {
- ArgumentCaptor<KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback>
- captor = ArgumentCaptor.forClass(
- KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class);
- setupGetSecurityView(SecurityMode.Password);
-
- verify(mView).initMode(anyInt(), any(GlobalSettings.class), any(FalsingManager.class),
- any(UserSwitcherController.class),
- captor.capture(),
- eq(mFalsingA11yDelegate));
- captor.getValue().showUnlockToContinueMessage();
- getViewControllerImmediately();
- verify(mKeyguardPasswordViewControllerMock).showMessage(
- /* message= */ getContext().getString(R.string.keyguard_unlock_to_continue),
- /* colorState= */ null,
- /* animated= */ true);
- }
-
- @Test
- public void addUserSwitchCallback() {
- mKeyguardSecurityContainerController.onViewAttached();
- verify(mUserSwitcherController)
- .addUserSwitchCallback(any(UserSwitcherController.UserSwitchCallback.class));
- mKeyguardSecurityContainerController.onViewDetached();
- verify(mUserSwitcherController)
- .removeUserSwitchCallback(any(UserSwitcherController.UserSwitchCallback.class));
- }
-
- @Test
- public void onBouncerVisibilityChanged_resetsScale() {
- mKeyguardSecurityContainerController.onBouncerVisibilityChanged(false);
- verify(mView).resetScale();
- }
-
- @Test
- public void showNextSecurityScreenOrFinish_setsSecurityScreenToPinAfterSimPinUnlock() {
- // GIVEN the current security method is SimPin
- when(mKeyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(false);
- when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(TARGET_USER_ID)).thenReturn(false);
- mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.SimPin);
-
- // WHEN a request is made from the SimPin screens to show the next security method
- when(mKeyguardSecurityModel.getSecurityMode(TARGET_USER_ID)).thenReturn(SecurityMode.PIN);
- mKeyguardSecurityContainerController.showNextSecurityScreenOrFinish(
- /* authenticated= */true,
- TARGET_USER_ID,
- /* bypassSecondaryLockScreen= */true,
- SecurityMode.SimPin);
-
- // THEN the next security method of PIN is set, and the keyguard is not marked as done
-
- verify(mViewMediatorCallback, never()).keyguardDonePending(anyBoolean(), anyInt());
- verify(mViewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt());
- assertThat(mKeyguardSecurityContainerController.getCurrentSecurityMode())
- .isEqualTo(SecurityMode.PIN);
- }
-
- @Test
- public void showNextSecurityScreenOrFinish_DeviceNotSecure() {
- // GIVEN the current security method is SimPin
- when(mKeyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(false);
- when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(TARGET_USER_ID)).thenReturn(false);
- mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.SimPin);
-
- // WHEN a request is made from the SimPin screens to show the next security method
- when(mKeyguardSecurityModel.getSecurityMode(TARGET_USER_ID)).thenReturn(SecurityMode.None);
- when(mLockPatternUtils.isLockScreenDisabled(anyInt())).thenReturn(true);
- mKeyguardSecurityContainerController.showNextSecurityScreenOrFinish(
- /* authenticated= */true,
- TARGET_USER_ID,
- /* bypassSecondaryLockScreen= */true,
- SecurityMode.SimPin);
-
- // THEN the next security method of None will dismiss keyguard.
- verify(mViewMediatorCallback).keyguardDone(anyBoolean(), anyInt());
- }
-
- @Test
- public void showNextSecurityScreenOrFinish_ignoresCallWhenSecurityMethodHasChanged() {
- //GIVEN current security mode has been set to PIN
- mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.PIN);
-
- //WHEN a request comes from SimPin to dismiss the security screens
- boolean keyguardDone = mKeyguardSecurityContainerController.showNextSecurityScreenOrFinish(
- /* authenticated= */true,
- TARGET_USER_ID,
- /* bypassSecondaryLockScreen= */true,
- SecurityMode.SimPin);
-
- //THEN no action has happened, which will not dismiss the security screens
- assertThat(keyguardDone).isEqualTo(false);
- verify(mKeyguardUpdateMonitor, never()).getUserHasTrust(anyInt());
- }
-
- @Test
- public void showNextSecurityScreenOrFinish_SimPin_Swipe() {
- // GIVEN the current security method is SimPin
- when(mKeyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(false);
- when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(TARGET_USER_ID)).thenReturn(false);
- mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.SimPin);
-
- // WHEN a request is made from the SimPin screens to show the next security method
- when(mKeyguardSecurityModel.getSecurityMode(TARGET_USER_ID)).thenReturn(SecurityMode.None);
- // WHEN security method is SWIPE
- when(mLockPatternUtils.isLockScreenDisabled(anyInt())).thenReturn(false);
- mKeyguardSecurityContainerController.showNextSecurityScreenOrFinish(
- /* authenticated= */true,
- TARGET_USER_ID,
- /* bypassSecondaryLockScreen= */true,
- SecurityMode.SimPin);
-
- // THEN the next security method of None will dismiss keyguard.
- verify(mViewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt());
- }
-
-
- @Test
- public void onSwipeUp_whenFaceDetectionIsNotRunning_initiatesFaceAuth() {
- KeyguardSecurityContainer.SwipeListener registeredSwipeListener =
- getRegisteredSwipeListener();
- when(mKeyguardUpdateMonitor.isFaceDetectionRunning()).thenReturn(false);
- setupGetSecurityView(SecurityMode.Password);
-
- registeredSwipeListener.onSwipeUp();
-
- verify(mKeyguardUpdateMonitor).requestFaceAuth(
- FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER);
- }
-
- @Test
- public void onSwipeUp_whenFaceDetectionIsRunning_doesNotInitiateFaceAuth() {
- KeyguardSecurityContainer.SwipeListener registeredSwipeListener =
- getRegisteredSwipeListener();
- when(mKeyguardUpdateMonitor.isFaceDetectionRunning()).thenReturn(true);
-
- registeredSwipeListener.onSwipeUp();
-
- verify(mKeyguardUpdateMonitor, never())
- .requestFaceAuth(FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER);
- }
-
- @Test
- public void onSwipeUp_whenFaceDetectionIsTriggered_hidesBouncerMessage() {
- KeyguardSecurityContainer.SwipeListener registeredSwipeListener =
- getRegisteredSwipeListener();
- when(mKeyguardUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER))
- .thenReturn(true);
- setupGetSecurityView(SecurityMode.Password);
-
- clearInvocations(mKeyguardSecurityViewFlipperController);
- registeredSwipeListener.onSwipeUp();
- getViewControllerImmediately();
-
- verify(mKeyguardPasswordViewControllerMock).showMessage(/* message= */
- null, /* colorState= */ null, /* animated= */ true);
- }
-
- @Test
- public void onSwipeUp_whenFaceDetectionIsNotTriggered_retainsBouncerMessage() {
- KeyguardSecurityContainer.SwipeListener registeredSwipeListener =
- getRegisteredSwipeListener();
- when(mKeyguardUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER))
- .thenReturn(false);
- setupGetSecurityView(SecurityMode.Password);
-
- registeredSwipeListener.onSwipeUp();
-
- verify(mKeyguardPasswordViewControllerMock, never()).showMessage(/* message= */
- null, /* colorState= */ null, /* animated= */ true);
- }
-
- @Test
- public void onDensityOrFontScaleChanged() {
- ArgumentCaptor<ConfigurationController.ConfigurationListener>
- configurationListenerArgumentCaptor = ArgumentCaptor.forClass(
- ConfigurationController.ConfigurationListener.class);
- mKeyguardSecurityContainerController.onViewAttached();
- verify(mConfigurationController).addCallback(configurationListenerArgumentCaptor.capture());
- clearInvocations(mKeyguardSecurityViewFlipperController);
-
- configurationListenerArgumentCaptor.getValue().onDensityOrFontScaleChanged();
-
- verify(mKeyguardSecurityViewFlipperController).clearViews();
- verify(mKeyguardSecurityViewFlipperController).asynchronouslyInflateView(
- eq(SecurityMode.PIN),
- any(KeyguardSecurityCallback.class),
- mOnViewInflatedCallbackArgumentCaptor.capture());
-
- mOnViewInflatedCallbackArgumentCaptor.getValue().onViewInflated(mInputViewController);
-
- verify(mView).onDensityOrFontScaleChanged();
- }
-
- @Test
- public void onThemeChanged() {
- ArgumentCaptor<ConfigurationController.ConfigurationListener>
- configurationListenerArgumentCaptor = ArgumentCaptor.forClass(
- ConfigurationController.ConfigurationListener.class);
- mKeyguardSecurityContainerController.onViewAttached();
- verify(mConfigurationController).addCallback(configurationListenerArgumentCaptor.capture());
- clearInvocations(mKeyguardSecurityViewFlipperController);
-
- configurationListenerArgumentCaptor.getValue().onThemeChanged();
-
- verify(mKeyguardSecurityViewFlipperController).clearViews();
- verify(mKeyguardSecurityViewFlipperController).asynchronouslyInflateView(
- eq(SecurityMode.PIN),
- any(KeyguardSecurityCallback.class),
- mOnViewInflatedCallbackArgumentCaptor.capture());
-
- mOnViewInflatedCallbackArgumentCaptor.getValue().onViewInflated(mInputViewController);
-
- verify(mView).reset();
- verify(mKeyguardSecurityViewFlipperController).reset();
- verify(mView).reloadColors();
- }
-
- @Test
- public void onUiModeChanged() {
- ArgumentCaptor<ConfigurationController.ConfigurationListener>
- configurationListenerArgumentCaptor = ArgumentCaptor.forClass(
- ConfigurationController.ConfigurationListener.class);
- mKeyguardSecurityContainerController.onViewAttached();
- verify(mConfigurationController).addCallback(configurationListenerArgumentCaptor.capture());
- clearInvocations(mKeyguardSecurityViewFlipperController);
-
- configurationListenerArgumentCaptor.getValue().onUiModeChanged();
-
- verify(mKeyguardSecurityViewFlipperController).clearViews();
- verify(mKeyguardSecurityViewFlipperController).asynchronouslyInflateView(
- eq(SecurityMode.PIN),
- any(KeyguardSecurityCallback.class),
- mOnViewInflatedCallbackArgumentCaptor.capture());
-
- mOnViewInflatedCallbackArgumentCaptor.getValue().onViewInflated(mInputViewController);
-
- verify(mView).reloadColors();
- }
-
- @Test
- public void testHasDismissActions() {
- assertFalse("Action not set yet", mKeyguardSecurityContainerController.hasDismissActions());
- mKeyguardSecurityContainerController.setOnDismissAction(mock(
- ActivityStarter.OnDismissAction.class),
- null /* cancelAction */);
- assertTrue("Action should exist", mKeyguardSecurityContainerController.hasDismissActions());
- }
-
- @Test
- public void testWillRunDismissFromKeyguardIsTrue() {
- ActivityStarter.OnDismissAction action = mock(ActivityStarter.OnDismissAction.class);
- when(action.willRunAnimationOnKeyguard()).thenReturn(true);
- mKeyguardSecurityContainerController.setOnDismissAction(action, null /* cancelAction */);
-
- mKeyguardSecurityContainerController.finish(false /* strongAuth */, 0 /* currentUser */);
-
- assertThat(mKeyguardSecurityContainerController.willRunDismissFromKeyguard()).isTrue();
- }
-
- @Test
- public void testWillRunDismissFromKeyguardIsFalse() {
- ActivityStarter.OnDismissAction action = mock(ActivityStarter.OnDismissAction.class);
- when(action.willRunAnimationOnKeyguard()).thenReturn(false);
- mKeyguardSecurityContainerController.setOnDismissAction(action, null /* cancelAction */);
-
- mKeyguardSecurityContainerController.finish(false /* strongAuth */, 0 /* currentUser */);
-
- assertThat(mKeyguardSecurityContainerController.willRunDismissFromKeyguard()).isFalse();
- }
-
- @Test
- public void testWillRunDismissFromKeyguardIsFalseWhenNoDismissActionSet() {
- mKeyguardSecurityContainerController.setOnDismissAction(null /* action */,
- null /* cancelAction */);
-
- mKeyguardSecurityContainerController.finish(false /* strongAuth */, 0 /* currentUser */);
-
- assertThat(mKeyguardSecurityContainerController.willRunDismissFromKeyguard()).isFalse();
- }
-
- @Test
- public void testOnStartingToHide() {
- mKeyguardSecurityContainerController.onStartingToHide();
- verify(mKeyguardSecurityViewFlipperController).getSecurityView(any(SecurityMode.class),
- any(KeyguardSecurityCallback.class),
- mOnViewInflatedCallbackArgumentCaptor.capture());
-
- mOnViewInflatedCallbackArgumentCaptor.getValue().onViewInflated(mInputViewController);
- verify(mInputViewController).onStartingToHide();
- }
-
- @Test
- public void testGravityReappliedOnConfigurationChange() {
- // Set initial gravity
- mTestableResources.addOverride(R.integer.keyguard_host_view_gravity,
- Gravity.CENTER);
- mTestableResources.addOverride(
- R.bool.can_use_one_handed_bouncer, false);
-
- // Kick off the initial pass...
- mKeyguardSecurityContainerController.onInit();
- verify(mView).setLayoutParams(any());
- clearInvocations(mView);
-
- // Now simulate a config change
- mTestableResources.addOverride(R.integer.keyguard_host_view_gravity,
- Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM);
-
- mKeyguardSecurityContainerController.updateResources();
- verify(mView).setLayoutParams(any());
- }
-
- @Test
- public void testGravityUsesOneHandGravityWhenApplicable() {
- mTestableResources.addOverride(
- R.integer.keyguard_host_view_gravity,
- Gravity.CENTER);
- mTestableResources.addOverride(
- R.integer.keyguard_host_view_one_handed_gravity,
- Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM);
-
- // Start disabled.
- mTestableResources.addOverride(
- R.bool.can_use_one_handed_bouncer, false);
-
- mKeyguardSecurityContainerController.onInit();
- verify(mView).setLayoutParams(argThat(
- (ArgumentMatcher<FrameLayout.LayoutParams>) argument ->
- argument.gravity == Gravity.CENTER));
- clearInvocations(mView);
-
- // And enable
- mTestableResources.addOverride(
- R.bool.can_use_one_handed_bouncer, true);
-
- mKeyguardSecurityContainerController.updateResources();
- verify(mView).setLayoutParams(argThat(
- (ArgumentMatcher<FrameLayout.LayoutParams>) argument ->
- argument.gravity == (Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM)));
- }
-
- @Test
- public void testUpdateKeyguardPositionDelegatesToSecurityContainer() {
- mKeyguardSecurityContainerController.updateKeyguardPosition(1.0f);
- verify(mView).updatePositionByTouchX(1.0f);
- }
-
- @Test
- public void testReinflateViewFlipper() {
- KeyguardSecurityViewFlipperController.OnViewInflatedCallback onViewInflatedCallback =
- controller -> {
- };
- mKeyguardSecurityContainerController.reinflateViewFlipper(onViewInflatedCallback);
- verify(mKeyguardSecurityViewFlipperController).clearViews();
- verify(mKeyguardSecurityViewFlipperController).asynchronouslyInflateView(
- any(SecurityMode.class),
- any(KeyguardSecurityCallback.class), eq(onViewInflatedCallback));
- }
-
- @Test
- public void testSideFpsControllerShow() {
- mKeyguardSecurityContainerController.updateSideFpsVisibility(/* isVisible= */ true);
- verify(mSideFpsController).show(
- SideFpsUiRequestSource.PRIMARY_BOUNCER,
- BiometricOverlayConstants.REASON_AUTH_KEYGUARD);
- }
-
- @Test
- public void testSideFpsControllerHide() {
- mKeyguardSecurityContainerController.updateSideFpsVisibility(/* isVisible= */ false);
- verify(mSideFpsController).hide(SideFpsUiRequestSource.PRIMARY_BOUNCER);
- }
-
- @Test
- public void setExpansion_setsAlpha() {
- mKeyguardSecurityContainerController.setExpansion(EXPANSION_VISIBLE);
-
- verify(mView).setAlpha(1f);
- verify(mView).setTranslationY(0f);
- }
-
- private KeyguardSecurityContainer.SwipeListener getRegisteredSwipeListener() {
- mKeyguardSecurityContainerController.onViewAttached();
- verify(mView).setSwipeListener(mSwipeListenerArgumentCaptor.capture());
- return mSwipeListenerArgumentCaptor.getValue();
- }
-
- private void setupGetSecurityView(SecurityMode securityMode) {
- mKeyguardSecurityContainerController.showSecurityScreen(securityMode);
- getViewControllerImmediately();
- }
-
- private void getViewControllerImmediately() {
- verify(mKeyguardSecurityViewFlipperController, atLeastOnce()).getSecurityView(
- any(SecurityMode.class), any(),
- mOnViewInflatedCallbackArgumentCaptor.capture());
- mOnViewInflatedCallbackArgumentCaptor.getValue().onViewInflated(
- (KeyguardInputViewController) mKeyguardPasswordViewControllerMock);
-
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
new file mode 100644
index 0000000..d447174
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
@@ -0,0 +1,810 @@
+/*
+ * 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.
+ */
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.keyguard
+
+import android.content.res.Configuration
+import android.hardware.biometrics.BiometricOverlayConstants
+import android.media.AudioManager
+import android.telephony.TelephonyManager
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import android.testing.TestableResources
+import android.view.Gravity
+import android.view.LayoutInflater
+import android.view.MotionEvent
+import android.view.WindowInsetsController
+import android.widget.FrameLayout
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.MetricsLogger
+import com.android.internal.logging.UiEventLogger
+import com.android.internal.widget.LockPatternUtils
+import com.android.keyguard.KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback
+import com.android.keyguard.KeyguardSecurityModel.SecurityMode
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.SideFpsController
+import com.android.systemui.biometrics.SideFpsUiRequestSource
+import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants
+import com.android.systemui.classifier.FalsingA11yDelegate
+import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.log.SessionTracker
+import com.android.systemui.plugins.ActivityStarter.OnDismissAction
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.statusbar.policy.UserSwitcherController
+import com.android.systemui.user.domain.interactor.UserInteractor
+import com.android.systemui.util.kotlin.JavaAdapter
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argThat
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.settings.GlobalSettings
+import com.google.common.truth.Truth
+import java.util.Optional
+import junit.framework.Assert
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatcher
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.atLeastOnce
+import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.never
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper
+class KeyguardSecurityContainerControllerTest : SysuiTestCase() {
+
+ @Mock private lateinit var view: KeyguardSecurityContainer
+ @Mock
+ private lateinit var adminSecondaryLockScreenControllerFactory:
+ AdminSecondaryLockScreenController.Factory
+ @Mock
+ private lateinit var adminSecondaryLockScreenController: AdminSecondaryLockScreenController
+ @Mock private lateinit var lockPatternUtils: LockPatternUtils
+ @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+ @Mock private lateinit var keyguardSecurityModel: KeyguardSecurityModel
+ @Mock private lateinit var metricsLogger: MetricsLogger
+ @Mock private lateinit var uiEventLogger: UiEventLogger
+ @Mock private lateinit var keyguardStateController: KeyguardStateController
+ @Mock private lateinit var inputViewController: KeyguardInputViewController<KeyguardInputView>
+ @Mock private lateinit var windowInsetsController: WindowInsetsController
+ @Mock private lateinit var securityViewFlipper: KeyguardSecurityViewFlipper
+ @Mock private lateinit var viewFlipperController: KeyguardSecurityViewFlipperController
+ @Mock private lateinit var messageAreaControllerFactory: KeyguardMessageAreaController.Factory
+ @Mock private lateinit var keyguardMessageAreaController: KeyguardMessageAreaController<*>
+ @Mock private lateinit var keyguardMessageArea: BouncerKeyguardMessageArea
+ @Mock private lateinit var configurationController: ConfigurationController
+ @Mock private lateinit var emergencyButtonController: EmergencyButtonController
+ @Mock private lateinit var falsingCollector: FalsingCollector
+ @Mock private lateinit var falsingManager: FalsingManager
+ @Mock private lateinit var globalSettings: GlobalSettings
+ @Mock private lateinit var userSwitcherController: UserSwitcherController
+ @Mock private lateinit var sessionTracker: SessionTracker
+ @Mock private lateinit var keyguardViewController: KeyguardViewController
+ @Mock private lateinit var sideFpsController: SideFpsController
+ @Mock private lateinit var keyguardPasswordViewControllerMock: KeyguardPasswordViewController
+ @Mock private lateinit var falsingA11yDelegate: FalsingA11yDelegate
+ @Mock private lateinit var telephonyManager: TelephonyManager
+ @Mock private lateinit var viewMediatorCallback: ViewMediatorCallback
+ @Mock private lateinit var audioManager: AudioManager
+ @Mock private lateinit var userInteractor: UserInteractor
+
+ @Captor
+ private lateinit var swipeListenerArgumentCaptor:
+ ArgumentCaptor<KeyguardSecurityContainer.SwipeListener>
+ @Captor
+ private lateinit var onViewInflatedCallbackArgumentCaptor:
+ ArgumentCaptor<KeyguardSecurityViewFlipperController.OnViewInflatedCallback>
+
+ private lateinit var featureFlags: FakeFeatureFlags
+ private lateinit var keyguardPasswordViewController: KeyguardPasswordViewController
+ private lateinit var keyguardPasswordView: KeyguardPasswordView
+ private lateinit var testableResources: TestableResources
+ private lateinit var sceneTestUtils: SceneTestUtils
+ private lateinit var sceneInteractor: SceneInteractor
+
+ private lateinit var underTest: KeyguardSecurityContainerController
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ testableResources = mContext.getOrCreateTestableResources()
+ testableResources.resources.configuration.orientation = Configuration.ORIENTATION_UNDEFINED
+ whenever(view.context).thenReturn(mContext)
+ whenever(view.resources).thenReturn(testableResources.resources)
+
+ val lp = FrameLayout.LayoutParams(/* width= */ 0, /* height= */ 0)
+ lp.gravity = 0
+ whenever(view.layoutParams).thenReturn(lp)
+
+ whenever(adminSecondaryLockScreenControllerFactory.create(any()))
+ .thenReturn(adminSecondaryLockScreenController)
+ whenever(securityViewFlipper.windowInsetsController).thenReturn(windowInsetsController)
+ keyguardPasswordView =
+ spy(
+ LayoutInflater.from(mContext).inflate(R.layout.keyguard_password_view, null)
+ as KeyguardPasswordView
+ )
+ whenever(keyguardPasswordView.rootView).thenReturn(securityViewFlipper)
+ whenever<Any?>(keyguardPasswordView.requireViewById(R.id.bouncer_message_area))
+ .thenReturn(keyguardMessageArea)
+ whenever(messageAreaControllerFactory.create(any()))
+ .thenReturn(keyguardMessageAreaController)
+ whenever(keyguardPasswordView.windowInsetsController).thenReturn(windowInsetsController)
+ whenever(keyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(SecurityMode.PIN)
+ whenever(keyguardStateController.canDismissLockScreen()).thenReturn(true)
+
+ featureFlags = FakeFeatureFlags()
+ featureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true)
+ featureFlags.set(Flags.SCENE_CONTAINER, false)
+ featureFlags.set(Flags.BOUNCER_USER_SWITCHER, false)
+
+ keyguardPasswordViewController =
+ KeyguardPasswordViewController(
+ keyguardPasswordView,
+ keyguardUpdateMonitor,
+ SecurityMode.Password,
+ lockPatternUtils,
+ null,
+ messageAreaControllerFactory,
+ null,
+ null,
+ emergencyButtonController,
+ null,
+ mock(),
+ null,
+ keyguardViewController,
+ featureFlags
+ )
+
+ whenever(userInteractor.getSelectedUserId()).thenReturn(TARGET_USER_ID)
+ sceneTestUtils = SceneTestUtils(this)
+ sceneInteractor = sceneTestUtils.sceneInteractor()
+
+ underTest =
+ KeyguardSecurityContainerController(
+ view,
+ adminSecondaryLockScreenControllerFactory,
+ lockPatternUtils,
+ keyguardUpdateMonitor,
+ keyguardSecurityModel,
+ metricsLogger,
+ uiEventLogger,
+ keyguardStateController,
+ viewFlipperController,
+ configurationController,
+ falsingCollector,
+ falsingManager,
+ userSwitcherController,
+ featureFlags,
+ globalSettings,
+ sessionTracker,
+ Optional.of(sideFpsController),
+ falsingA11yDelegate,
+ telephonyManager,
+ viewMediatorCallback,
+ audioManager,
+ mock(),
+ mock(),
+ { JavaAdapter(sceneTestUtils.testScope.backgroundScope) },
+ userInteractor,
+ ) {
+ sceneInteractor
+ }
+ }
+
+ @Test
+ fun onInitConfiguresViewMode() {
+ underTest.onInit()
+ verify(view)
+ .initMode(
+ eq(KeyguardSecurityContainer.MODE_DEFAULT),
+ eq(globalSettings),
+ eq(falsingManager),
+ eq(userSwitcherController),
+ any(),
+ eq(falsingA11yDelegate)
+ )
+ }
+
+ @Test
+ fun showSecurityScreen_canInflateAllModes() {
+ val modes = SecurityMode.values()
+ for (mode in modes) {
+ whenever(inputViewController.securityMode).thenReturn(mode)
+ underTest.showSecurityScreen(mode)
+ if (mode == SecurityMode.Invalid) {
+ verify(viewFlipperController, never()).getSecurityView(any(), any(), any())
+ } else {
+ verify(viewFlipperController).getSecurityView(eq(mode), any(), any())
+ }
+ }
+ }
+
+ @Test
+ fun onResourcesUpdate_callsThroughOnRotationChange() {
+ clearInvocations(view)
+
+ // Rotation is the same, shouldn't cause an update
+ underTest.updateResources()
+ verify(view, never())
+ .initMode(
+ eq(KeyguardSecurityContainer.MODE_DEFAULT),
+ eq(globalSettings),
+ eq(falsingManager),
+ eq(userSwitcherController),
+ any(),
+ eq(falsingA11yDelegate)
+ )
+
+ // Update rotation. Should trigger update
+ testableResources.resources.configuration.orientation = Configuration.ORIENTATION_LANDSCAPE
+ underTest.updateResources()
+ verify(view)
+ .initMode(
+ eq(KeyguardSecurityContainer.MODE_DEFAULT),
+ eq(globalSettings),
+ eq(falsingManager),
+ eq(userSwitcherController),
+ any(),
+ eq(falsingA11yDelegate)
+ )
+ }
+
+ private fun touchDown() {
+ underTest.mGlobalTouchListener.onTouchEvent(
+ MotionEvent.obtain(
+ /* downTime= */ 0,
+ /* eventTime= */ 0,
+ MotionEvent.ACTION_DOWN,
+ /* x= */ 0f,
+ /* y= */ 0f,
+ /* metaState= */ 0
+ )
+ )
+ }
+
+ @Test
+ fun onInterceptTap_inhibitsFalsingInSidedSecurityMode() {
+ whenever(view.isTouchOnTheOtherSideOfSecurity(any())).thenReturn(false)
+ touchDown()
+ verify(falsingCollector, never()).avoidGesture()
+ whenever(view.isTouchOnTheOtherSideOfSecurity(any())).thenReturn(true)
+ touchDown()
+ verify(falsingCollector).avoidGesture()
+ }
+
+ @Test
+ fun showSecurityScreen_oneHandedMode_flagDisabled_noOneHandedMode() {
+ testableResources.addOverride(R.bool.can_use_one_handed_bouncer, false)
+ setupGetSecurityView(SecurityMode.Pattern)
+ underTest.showSecurityScreen(SecurityMode.Pattern)
+ verify(view)
+ .initMode(
+ eq(KeyguardSecurityContainer.MODE_DEFAULT),
+ eq(globalSettings),
+ eq(falsingManager),
+ eq(userSwitcherController),
+ any(),
+ eq(falsingA11yDelegate)
+ )
+ }
+
+ @Test
+ fun showSecurityScreen_oneHandedMode_flagEnabled_oneHandedMode() {
+ testableResources.addOverride(R.bool.can_use_one_handed_bouncer, true)
+ setupGetSecurityView(SecurityMode.Pattern)
+ verify(view)
+ .initMode(
+ eq(KeyguardSecurityContainer.MODE_ONE_HANDED),
+ eq(globalSettings),
+ eq(falsingManager),
+ eq(userSwitcherController),
+ any(),
+ eq(falsingA11yDelegate)
+ )
+ }
+
+ @Test
+ fun showSecurityScreen_twoHandedMode_flagEnabled_noOneHandedMode() {
+ testableResources.addOverride(R.bool.can_use_one_handed_bouncer, true)
+ setupGetSecurityView(SecurityMode.Password)
+ verify(view)
+ .initMode(
+ eq(KeyguardSecurityContainer.MODE_DEFAULT),
+ eq(globalSettings),
+ eq(falsingManager),
+ eq(userSwitcherController),
+ any(),
+ eq(falsingA11yDelegate)
+ )
+ }
+
+ @Test
+ fun addUserSwitcherCallback() {
+ val captor = ArgumentCaptor.forClass(UserSwitcherCallback::class.java)
+ setupGetSecurityView(SecurityMode.Password)
+ verify(view)
+ .initMode(anyInt(), any(), any(), any(), captor.capture(), eq(falsingA11yDelegate))
+ captor.value.showUnlockToContinueMessage()
+ viewControllerImmediately
+ verify(keyguardPasswordViewControllerMock)
+ .showMessage(
+ /* message= */ context.getString(R.string.keyguard_unlock_to_continue),
+ /* colorState= */ null,
+ /* animated= */ true
+ )
+ }
+
+ @Test
+ fun addUserSwitchCallback() {
+ underTest.onViewAttached()
+ verify(userSwitcherController).addUserSwitchCallback(any())
+ underTest.onViewDetached()
+ verify(userSwitcherController).removeUserSwitchCallback(any())
+ }
+
+ @Test
+ fun onBouncerVisibilityChanged_resetsScale() {
+ underTest.onBouncerVisibilityChanged(false)
+ verify(view).resetScale()
+ }
+
+ @Test
+ fun showNextSecurityScreenOrFinish_setsSecurityScreenToPinAfterSimPinUnlock() {
+ // GIVEN the current security method is SimPin
+ whenever(keyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(false)
+ whenever(keyguardUpdateMonitor.getUserUnlockedWithBiometric(TARGET_USER_ID))
+ .thenReturn(false)
+ underTest.showSecurityScreen(SecurityMode.SimPin)
+
+ // WHEN a request is made from the SimPin screens to show the next security method
+ whenever(keyguardSecurityModel.getSecurityMode(TARGET_USER_ID)).thenReturn(SecurityMode.PIN)
+ underTest.showNextSecurityScreenOrFinish(
+ /* authenticated= */ true,
+ TARGET_USER_ID,
+ /* bypassSecondaryLockScreen= */ true,
+ SecurityMode.SimPin
+ )
+
+ // THEN the next security method of PIN is set, and the keyguard is not marked as done
+ verify(viewMediatorCallback, never()).keyguardDonePending(anyBoolean(), anyInt())
+ verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt())
+ Truth.assertThat(underTest.currentSecurityMode).isEqualTo(SecurityMode.PIN)
+ }
+
+ @Test
+ fun showNextSecurityScreenOrFinish_DeviceNotSecure() {
+ // GIVEN the current security method is SimPin
+ whenever(keyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(false)
+ whenever(keyguardUpdateMonitor.getUserUnlockedWithBiometric(TARGET_USER_ID))
+ .thenReturn(false)
+ underTest.showSecurityScreen(SecurityMode.SimPin)
+
+ // WHEN a request is made from the SimPin screens to show the next security method
+ whenever(keyguardSecurityModel.getSecurityMode(TARGET_USER_ID))
+ .thenReturn(SecurityMode.None)
+ whenever(lockPatternUtils.isLockScreenDisabled(anyInt())).thenReturn(true)
+ underTest.showNextSecurityScreenOrFinish(
+ /* authenticated= */ true,
+ TARGET_USER_ID,
+ /* bypassSecondaryLockScreen= */ true,
+ SecurityMode.SimPin
+ )
+
+ // THEN the next security method of None will dismiss keyguard.
+ verify(viewMediatorCallback).keyguardDone(anyBoolean(), anyInt())
+ }
+
+ @Test
+ fun showNextSecurityScreenOrFinish_ignoresCallWhenSecurityMethodHasChanged() {
+ // GIVEN current security mode has been set to PIN
+ underTest.showSecurityScreen(SecurityMode.PIN)
+
+ // WHEN a request comes from SimPin to dismiss the security screens
+ val keyguardDone =
+ underTest.showNextSecurityScreenOrFinish(
+ /* authenticated= */ true,
+ TARGET_USER_ID,
+ /* bypassSecondaryLockScreen= */ true,
+ SecurityMode.SimPin
+ )
+
+ // THEN no action has happened, which will not dismiss the security screens
+ Truth.assertThat(keyguardDone).isEqualTo(false)
+ verify(keyguardUpdateMonitor, never()).getUserHasTrust(anyInt())
+ }
+
+ @Test
+ fun showNextSecurityScreenOrFinish_SimPin_Swipe() {
+ // GIVEN the current security method is SimPin
+ whenever(keyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(false)
+ whenever(keyguardUpdateMonitor.getUserUnlockedWithBiometric(TARGET_USER_ID))
+ .thenReturn(false)
+ underTest.showSecurityScreen(SecurityMode.SimPin)
+
+ // WHEN a request is made from the SimPin screens to show the next security method
+ whenever(keyguardSecurityModel.getSecurityMode(TARGET_USER_ID))
+ .thenReturn(SecurityMode.None)
+ // WHEN security method is SWIPE
+ whenever(lockPatternUtils.isLockScreenDisabled(anyInt())).thenReturn(false)
+ underTest.showNextSecurityScreenOrFinish(
+ /* authenticated= */ true,
+ TARGET_USER_ID,
+ /* bypassSecondaryLockScreen= */ true,
+ SecurityMode.SimPin
+ )
+
+ // THEN the next security method of None will dismiss keyguard.
+ verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt())
+ }
+
+ @Test
+ fun onSwipeUp_whenFaceDetectionIsNotRunning_initiatesFaceAuth() {
+ val registeredSwipeListener = registeredSwipeListener
+ whenever(keyguardUpdateMonitor.isFaceDetectionRunning).thenReturn(false)
+ setupGetSecurityView(SecurityMode.Password)
+ registeredSwipeListener.onSwipeUp()
+ verify(keyguardUpdateMonitor).requestFaceAuth(FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER)
+ }
+
+ @Test
+ fun onSwipeUp_whenFaceDetectionIsRunning_doesNotInitiateFaceAuth() {
+ val registeredSwipeListener = registeredSwipeListener
+ whenever(keyguardUpdateMonitor.isFaceDetectionRunning).thenReturn(true)
+ registeredSwipeListener.onSwipeUp()
+ verify(keyguardUpdateMonitor, never())
+ .requestFaceAuth(FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER)
+ }
+
+ @Test
+ fun onSwipeUp_whenFaceDetectionIsTriggered_hidesBouncerMessage() {
+ val registeredSwipeListener = registeredSwipeListener
+ whenever(
+ keyguardUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER)
+ )
+ .thenReturn(true)
+ setupGetSecurityView(SecurityMode.Password)
+ clearInvocations(viewFlipperController)
+ registeredSwipeListener.onSwipeUp()
+ viewControllerImmediately
+ verify(keyguardPasswordViewControllerMock)
+ .showMessage(/* message= */ null, /* colorState= */ null, /* animated= */ true)
+ }
+
+ @Test
+ fun onSwipeUp_whenFaceDetectionIsNotTriggered_retainsBouncerMessage() {
+ val registeredSwipeListener = registeredSwipeListener
+ whenever(
+ keyguardUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER)
+ )
+ .thenReturn(false)
+ setupGetSecurityView(SecurityMode.Password)
+ registeredSwipeListener.onSwipeUp()
+ verify(keyguardPasswordViewControllerMock, never())
+ .showMessage(/* message= */ null, /* colorState= */ null, /* animated= */ true)
+ }
+
+ @Test
+ fun onDensityOrFontScaleChanged() {
+ val configurationListenerArgumentCaptor =
+ ArgumentCaptor.forClass(ConfigurationController.ConfigurationListener::class.java)
+ underTest.onViewAttached()
+ verify(configurationController).addCallback(configurationListenerArgumentCaptor.capture())
+ clearInvocations(viewFlipperController)
+ configurationListenerArgumentCaptor.value.onDensityOrFontScaleChanged()
+ verify(viewFlipperController).clearViews()
+ verify(viewFlipperController)
+ .asynchronouslyInflateView(
+ eq(SecurityMode.PIN),
+ any(),
+ onViewInflatedCallbackArgumentCaptor.capture()
+ )
+ onViewInflatedCallbackArgumentCaptor.value.onViewInflated(inputViewController)
+ verify(view).onDensityOrFontScaleChanged()
+ }
+
+ @Test
+ fun onThemeChanged() {
+ val configurationListenerArgumentCaptor =
+ ArgumentCaptor.forClass(ConfigurationController.ConfigurationListener::class.java)
+ underTest.onViewAttached()
+ verify(configurationController).addCallback(configurationListenerArgumentCaptor.capture())
+ clearInvocations(viewFlipperController)
+ configurationListenerArgumentCaptor.value.onThemeChanged()
+ verify(viewFlipperController).clearViews()
+ verify(viewFlipperController)
+ .asynchronouslyInflateView(
+ eq(SecurityMode.PIN),
+ any(),
+ onViewInflatedCallbackArgumentCaptor.capture()
+ )
+ onViewInflatedCallbackArgumentCaptor.value.onViewInflated(inputViewController)
+ verify(view).reset()
+ verify(viewFlipperController).reset()
+ verify(view).reloadColors()
+ }
+
+ @Test
+ fun onUiModeChanged() {
+ val configurationListenerArgumentCaptor =
+ ArgumentCaptor.forClass(ConfigurationController.ConfigurationListener::class.java)
+ underTest.onViewAttached()
+ verify(configurationController).addCallback(configurationListenerArgumentCaptor.capture())
+ clearInvocations(viewFlipperController)
+ configurationListenerArgumentCaptor.value.onUiModeChanged()
+ verify(viewFlipperController).clearViews()
+ verify(viewFlipperController)
+ .asynchronouslyInflateView(
+ eq(SecurityMode.PIN),
+ any(),
+ onViewInflatedCallbackArgumentCaptor.capture()
+ )
+ onViewInflatedCallbackArgumentCaptor.value.onViewInflated(inputViewController)
+ verify(view).reloadColors()
+ }
+
+ @Test
+ fun hasDismissActions() {
+ Assert.assertFalse("Action not set yet", underTest.hasDismissActions())
+ underTest.setOnDismissAction(mock(), null /* cancelAction */)
+ Assert.assertTrue("Action should exist", underTest.hasDismissActions())
+ }
+
+ @Test
+ fun willRunDismissFromKeyguardIsTrue() {
+ val action: OnDismissAction = mock()
+ whenever(action.willRunAnimationOnKeyguard()).thenReturn(true)
+ underTest.setOnDismissAction(action, null /* cancelAction */)
+ underTest.finish(false /* strongAuth */, 0 /* currentUser */)
+ Truth.assertThat(underTest.willRunDismissFromKeyguard()).isTrue()
+ }
+
+ @Test
+ fun willRunDismissFromKeyguardIsFalse() {
+ val action: OnDismissAction = mock()
+ whenever(action.willRunAnimationOnKeyguard()).thenReturn(false)
+ underTest.setOnDismissAction(action, null /* cancelAction */)
+ underTest.finish(false /* strongAuth */, 0 /* currentUser */)
+ Truth.assertThat(underTest.willRunDismissFromKeyguard()).isFalse()
+ }
+
+ @Test
+ fun willRunDismissFromKeyguardIsFalseWhenNoDismissActionSet() {
+ underTest.setOnDismissAction(null /* action */, null /* cancelAction */)
+ underTest.finish(false /* strongAuth */, 0 /* currentUser */)
+ Truth.assertThat(underTest.willRunDismissFromKeyguard()).isFalse()
+ }
+
+ @Test
+ fun onStartingToHide() {
+ underTest.onStartingToHide()
+ verify(viewFlipperController)
+ .getSecurityView(any(), any(), onViewInflatedCallbackArgumentCaptor.capture())
+ onViewInflatedCallbackArgumentCaptor.value.onViewInflated(inputViewController)
+ verify(inputViewController).onStartingToHide()
+ }
+
+ @Test
+ fun gravityReappliedOnConfigurationChange() {
+ // Set initial gravity
+ testableResources.addOverride(R.integer.keyguard_host_view_gravity, Gravity.CENTER)
+ testableResources.addOverride(R.bool.can_use_one_handed_bouncer, false)
+
+ // Kick off the initial pass...
+ underTest.onInit()
+ verify(view).layoutParams = any()
+ clearInvocations(view)
+
+ // Now simulate a config change
+ testableResources.addOverride(
+ R.integer.keyguard_host_view_gravity,
+ Gravity.CENTER_HORIZONTAL or Gravity.BOTTOM
+ )
+ underTest.updateResources()
+ verify(view).layoutParams = any()
+ }
+
+ @Test
+ fun gravityUsesOneHandGravityWhenApplicable() {
+ testableResources.addOverride(R.integer.keyguard_host_view_gravity, Gravity.CENTER)
+ testableResources.addOverride(
+ R.integer.keyguard_host_view_one_handed_gravity,
+ Gravity.CENTER_HORIZONTAL or Gravity.BOTTOM
+ )
+
+ // Start disabled.
+ testableResources.addOverride(R.bool.can_use_one_handed_bouncer, false)
+ underTest.onInit()
+ verify(view).layoutParams =
+ argThat(
+ ArgumentMatcher { argument: FrameLayout.LayoutParams ->
+ argument.gravity == Gravity.CENTER
+ }
+ as ArgumentMatcher<FrameLayout.LayoutParams>
+ )
+ clearInvocations(view)
+
+ // And enable
+ testableResources.addOverride(R.bool.can_use_one_handed_bouncer, true)
+ underTest.updateResources()
+ verify(view).layoutParams =
+ argThat(
+ ArgumentMatcher { argument: FrameLayout.LayoutParams ->
+ argument.gravity == Gravity.CENTER_HORIZONTAL or Gravity.BOTTOM
+ }
+ as ArgumentMatcher<FrameLayout.LayoutParams>
+ )
+ }
+
+ @Test
+ fun updateKeyguardPositionDelegatesToSecurityContainer() {
+ underTest.updateKeyguardPosition(1.0f)
+ verify(view).updatePositionByTouchX(1.0f)
+ }
+
+ @Test
+ fun reinflateViewFlipper() {
+ val onViewInflatedCallback = KeyguardSecurityViewFlipperController.OnViewInflatedCallback {}
+ underTest.reinflateViewFlipper(onViewInflatedCallback)
+ verify(viewFlipperController).clearViews()
+ verify(viewFlipperController)
+ .asynchronouslyInflateView(any(), any(), eq(onViewInflatedCallback))
+ }
+
+ @Test
+ fun sideFpsControllerShow() {
+ underTest.updateSideFpsVisibility(/* isVisible= */ true)
+ verify(sideFpsController)
+ .show(
+ SideFpsUiRequestSource.PRIMARY_BOUNCER,
+ BiometricOverlayConstants.REASON_AUTH_KEYGUARD
+ )
+ }
+
+ @Test
+ fun sideFpsControllerHide() {
+ underTest.updateSideFpsVisibility(/* isVisible= */ false)
+ verify(sideFpsController).hide(SideFpsUiRequestSource.PRIMARY_BOUNCER)
+ }
+
+ @Test
+ fun setExpansion_setsAlpha() {
+ underTest.setExpansion(KeyguardBouncerConstants.EXPANSION_VISIBLE)
+ verify(view).alpha = 1f
+ verify(view).translationY = 0f
+ }
+
+ @Test
+ fun dismissesKeyguard_whenSceneChangesFromBouncerToGone() =
+ sceneTestUtils.testScope.runTest {
+ featureFlags.set(Flags.SCENE_CONTAINER, true)
+
+ // Upon init, we have never dismisses the keyguard.
+ underTest.onInit()
+ runCurrent()
+ verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt())
+
+ // Once the view is attached, we start listening but simply going to the bouncer scene
+ // is
+ // not enough to trigger a dismissal of the keyguard.
+ underTest.onViewAttached()
+ sceneInteractor.setCurrentScene(
+ SceneTestUtils.CONTAINER_1,
+ SceneModel(SceneKey.Bouncer, null)
+ )
+ runCurrent()
+ verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt())
+
+ // While listening, going from the bouncer scene to the gone scene, does dismiss the
+ // keyguard.
+ sceneInteractor.setCurrentScene(
+ SceneTestUtils.CONTAINER_1,
+ SceneModel(SceneKey.Gone, null)
+ )
+ runCurrent()
+ verify(viewMediatorCallback).keyguardDone(anyBoolean(), anyInt())
+
+ // While listening, moving back to the bouncer scene does not dismiss the keyguard
+ // again.
+ clearInvocations(viewMediatorCallback)
+ sceneInteractor.setCurrentScene(
+ SceneTestUtils.CONTAINER_1,
+ SceneModel(SceneKey.Bouncer, null)
+ )
+ runCurrent()
+ verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt())
+
+ // Detaching the view stops listening, so moving from the bouncer scene to the gone
+ // scene
+ // does not dismiss the keyguard while we're not listening.
+ underTest.onViewDetached()
+ sceneInteractor.setCurrentScene(
+ SceneTestUtils.CONTAINER_1,
+ SceneModel(SceneKey.Gone, null)
+ )
+ runCurrent()
+ verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt())
+
+ // While not listening, moving back to the bouncer does not dismiss the keyguard.
+ sceneInteractor.setCurrentScene(
+ SceneTestUtils.CONTAINER_1,
+ SceneModel(SceneKey.Bouncer, null)
+ )
+ runCurrent()
+ verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt())
+
+ // Reattaching the view starts listening again so moving from the bouncer scene to the
+ // gone
+ // scene now does dismiss the keyguard again.
+ underTest.onViewAttached()
+ sceneInteractor.setCurrentScene(
+ SceneTestUtils.CONTAINER_1,
+ SceneModel(SceneKey.Gone, null)
+ )
+ runCurrent()
+ verify(viewMediatorCallback).keyguardDone(anyBoolean(), anyInt())
+ }
+
+ private val registeredSwipeListener: KeyguardSecurityContainer.SwipeListener
+ get() {
+ underTest.onViewAttached()
+ verify(view).setSwipeListener(swipeListenerArgumentCaptor.capture())
+ return swipeListenerArgumentCaptor.value
+ }
+
+ private fun setupGetSecurityView(securityMode: SecurityMode) {
+ underTest.showSecurityScreen(securityMode)
+ viewControllerImmediately
+ }
+
+ private val viewControllerImmediately: Unit
+ get() {
+ verify(viewFlipperController, atLeastOnce())
+ .getSecurityView(any(), any(), onViewInflatedCallbackArgumentCaptor.capture())
+ @Suppress("UNCHECKED_CAST")
+ onViewInflatedCallbackArgumentCaptor.value.onViewInflated(
+ keyguardPasswordViewControllerMock as KeyguardInputViewController<KeyguardInputView>
+ )
+ }
+
+ companion object {
+ private const val TARGET_USER_ID = 100
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
index 92c5014..09d0eeb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
@@ -512,10 +512,11 @@
assertNotNull(mirrorView);
mirrorView.getBoundsOnScreen(mirrorViewBound);
- assertEquals(mirrorViewBound.exactCenterX() - windowBounds.exactCenterX(),
- Math.round(offsetRatio * mirrorViewBound.width() / 2), 0.1f);
- assertEquals(mirrorViewBound.exactCenterY() - windowBounds.exactCenterY(),
- Math.round(offsetRatio * mirrorViewBound.height() / 2), 0.1f);
+ assertEquals((int) (offsetRatio * mirrorViewBound.width() / 2),
+ (int) (mirrorViewBound.exactCenterX() - windowBounds.exactCenterX()));
+ assertEquals((int) (offsetRatio * mirrorViewBound.height() / 2),
+ (int) (mirrorViewBound.exactCenterY() - windowBounds.exactCenterY()));
+
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt
index da9ceb4..212dad7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt
@@ -8,6 +8,7 @@
import android.widget.LinearLayout
import android.widget.RelativeLayout
import androidx.test.filters.SmallTest
+import com.android.app.animation.Interpolators
import com.android.systemui.SysuiTestCase
import com.android.systemui.util.children
import junit.framework.Assert.assertEquals
@@ -19,7 +20,6 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import com.android.app.animation.Interpolators
@SmallTest
@RunWith(AndroidTestingRunner::class)
@@ -178,7 +178,7 @@
}
@Test
- fun animatesRootAndChildren() {
+ fun animatesRootAndChildren_withoutExcludedViews() {
setUpRootWithChildren()
val success = ViewHierarchyAnimator.animate(rootView)
@@ -208,6 +208,40 @@
}
@Test
+ fun animatesRootAndChildren_withExcludedViews() {
+ setUpRootWithChildren()
+
+ val success = ViewHierarchyAnimator.animate(
+ rootView,
+ excludedViews = setOf(rootView.getChildAt(0))
+ )
+ // Change all bounds.
+ rootView.measure(
+ View.MeasureSpec.makeMeasureSpec(180, View.MeasureSpec.EXACTLY),
+ View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY)
+ )
+ rootView.layout(10 /* l */, 20 /* t */, 200 /* r */, 120 /* b */)
+
+ assertTrue(success)
+ assertNotNull(rootView.getTag(R.id.tag_animator))
+ assertNull(rootView.getChildAt(0).getTag(R.id.tag_animator))
+ assertNotNull(rootView.getChildAt(1).getTag(R.id.tag_animator))
+ // The initial values for the affected views should be those of the previous layout, while
+ // the excluded view should be at the final values from the beginning.
+ checkBounds(rootView, l = 0, t = 0, r = 200, b = 100)
+ checkBounds(rootView.getChildAt(0), l = 0, t = 0, r = 90, b = 100)
+ checkBounds(rootView.getChildAt(1), l = 100, t = 0, r = 200, b = 100)
+ endAnimation(rootView)
+ assertNull(rootView.getTag(R.id.tag_animator))
+ assertNull(rootView.getChildAt(0).getTag(R.id.tag_animator))
+ assertNull(rootView.getChildAt(1).getTag(R.id.tag_animator))
+ // The end values should be those of the latest layout.
+ checkBounds(rootView, l = 10, t = 20, r = 200, b = 120)
+ checkBounds(rootView.getChildAt(0), l = 0, t = 0, r = 90, b = 100)
+ checkBounds(rootView.getChildAt(1), l = 90, t = 0, r = 180, b = 100)
+ }
+
+ @Test
fun animatesInvisibleViews() {
rootView.layout(10 /* l */, 10 /* t */, 50 /* r */, 50 /* b */)
rootView.visibility = View.INVISIBLE
diff --git a/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
index ea3289c..a6ad4b2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
@@ -20,11 +20,16 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.authentication.data.repository.AuthenticationRepository
+import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.scene.SceneTestUtils
import com.google.common.truth.Truth.assertThat
+import kotlin.time.Duration.Companion.milliseconds
+import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Test
@@ -47,35 +52,54 @@
@Test
fun getAuthenticationMethod() =
testScope.runTest {
- assertThat(underTest.getAuthenticationMethod())
- .isEqualTo(AuthenticationMethodModel.Pin(1234))
+ assertThat(underTest.getAuthenticationMethod()).isEqualTo(AuthenticationMethodModel.Pin)
utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Password("password")
+ AuthenticationMethodModel.Password
)
+
assertThat(underTest.getAuthenticationMethod())
- .isEqualTo(AuthenticationMethodModel.Password("password"))
+ .isEqualTo(AuthenticationMethodModel.Password)
}
@Test
- fun isUnlocked_whenAuthMethodIsNone_isTrue() =
+ fun getAuthenticationMethod_noneTreatedAsSwipe_whenLockscreenEnabled() =
testScope.runTest {
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
+ utils.authenticationRepository.setLockscreenEnabled(true)
+
+ assertThat(underTest.getAuthenticationMethod())
+ .isEqualTo(AuthenticationMethodModel.Swipe)
+ }
+
+ @Test
+ fun getAuthenticationMethod_none_whenLockscreenDisabled() =
+ testScope.runTest {
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
+ utils.authenticationRepository.setLockscreenEnabled(false)
+
+ assertThat(underTest.getAuthenticationMethod())
+ .isEqualTo(AuthenticationMethodModel.None)
+ }
+
+ @Test
+ fun isUnlocked_whenAuthMethodIsNoneAndLockscreenDisabled_isTrue() =
+ testScope.runTest {
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
+ utils.authenticationRepository.setLockscreenEnabled(false)
+
val isUnlocked by collectLastValue(underTest.isUnlocked)
assertThat(isUnlocked).isTrue()
}
@Test
- fun toggleBypassEnabled() =
+ fun isUnlocked_whenAuthMethodIsNoneAndLockscreenEnabled_isFalse() =
testScope.runTest {
- val isBypassEnabled by collectLastValue(underTest.isBypassEnabled)
- assertThat(isBypassEnabled).isFalse()
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
+ utils.authenticationRepository.setLockscreenEnabled(true)
- underTest.toggleBypassEnabled()
- assertThat(isBypassEnabled).isTrue()
-
- underTest.toggleBypassEnabled()
- assertThat(isBypassEnabled).isFalse()
+ val isUnlocked by collectLastValue(underTest.isUnlocked)
+ assertThat(isUnlocked).isFalse()
}
@Test
@@ -84,7 +108,7 @@
utils.authenticationRepository.setUnlocked(false)
runCurrent()
utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Password("password")
+ AuthenticationMethodModel.Password
)
assertThat(underTest.isAuthenticationRequired()).isTrue()
@@ -106,7 +130,7 @@
utils.authenticationRepository.setUnlocked(true)
runCurrent()
utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Password("password")
+ AuthenticationMethodModel.Password
)
assertThat(underTest.isAuthenticationRequired()).isFalse()
@@ -125,49 +149,33 @@
@Test
fun authenticate_withCorrectPin_returnsTrue() =
testScope.runTest {
- val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin(1234)
- )
-
- assertThat(underTest.authenticate(listOf(1, 2, 3, 4))).isTrue()
- assertThat(failedAttemptCount).isEqualTo(0)
+ val isThrottled by collectLastValue(underTest.isThrottled)
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+ assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)).isTrue()
+ assertThat(isThrottled).isFalse()
}
@Test
fun authenticate_withIncorrectPin_returnsFalse() =
testScope.runTest {
- val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin(1234)
- )
-
- assertThat(underTest.authenticate(listOf(9, 8, 7))).isFalse()
- assertThat(failedAttemptCount).isEqualTo(1)
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+ assertThat(underTest.authenticate(listOf(9, 8, 7, 6, 5, 4))).isFalse()
}
- @Test
- fun authenticate_withEmptyPin_returnsFalse() =
+ @Test(expected = IllegalArgumentException::class)
+ fun authenticate_withEmptyPin_throwsException() =
testScope.runTest {
- val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin(1234)
- )
-
- assertThat(underTest.authenticate(listOf())).isFalse()
- assertThat(failedAttemptCount).isEqualTo(1)
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+ underTest.authenticate(listOf())
}
@Test
fun authenticate_withCorrectMaxLengthPin_returnsTrue() =
testScope.runTest {
- val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin(9999999999999999)
- )
-
- assertThat(underTest.authenticate(List(16) { 9 })).isTrue()
- assertThat(failedAttemptCount).isEqualTo(0)
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+ val pin = List(16) { 9 }
+ utils.authenticationRepository.overrideCredential(pin)
+ assertThat(underTest.authenticate(pin)).isTrue()
}
@Test
@@ -179,105 +187,47 @@
// If the policy changes, there is work to do in SysUI.
assertThat(DevicePolicyManager.MAX_PASSWORD_LENGTH).isLessThan(17)
- val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin(99999999999999999)
- )
-
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
assertThat(underTest.authenticate(List(17) { 9 })).isFalse()
- assertThat(failedAttemptCount).isEqualTo(1)
}
@Test
fun authenticate_withCorrectPassword_returnsTrue() =
testScope.runTest {
- val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
+ val isThrottled by collectLastValue(underTest.isThrottled)
utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Password("password")
+ AuthenticationMethodModel.Password
)
assertThat(underTest.authenticate("password".toList())).isTrue()
- assertThat(failedAttemptCount).isEqualTo(0)
+ assertThat(isThrottled).isFalse()
}
@Test
fun authenticate_withIncorrectPassword_returnsFalse() =
testScope.runTest {
- val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Password("password")
+ AuthenticationMethodModel.Password
)
assertThat(underTest.authenticate("alohomora".toList())).isFalse()
- assertThat(failedAttemptCount).isEqualTo(1)
}
@Test
fun authenticate_withCorrectPattern_returnsTrue() =
testScope.runTest {
- val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pattern(
- listOf(
- AuthenticationMethodModel.Pattern.PatternCoordinate(
- x = 0,
- y = 0,
- ),
- AuthenticationMethodModel.Pattern.PatternCoordinate(
- x = 0,
- y = 1,
- ),
- AuthenticationMethodModel.Pattern.PatternCoordinate(
- x = 0,
- y = 2,
- ),
- )
- )
+ AuthenticationMethodModel.Pattern
)
- assertThat(
- underTest.authenticate(
- listOf(
- AuthenticationMethodModel.Pattern.PatternCoordinate(
- x = 0,
- y = 0,
- ),
- AuthenticationMethodModel.Pattern.PatternCoordinate(
- x = 0,
- y = 1,
- ),
- AuthenticationMethodModel.Pattern.PatternCoordinate(
- x = 0,
- y = 2,
- ),
- )
- )
- )
- .isTrue()
- assertThat(failedAttemptCount).isEqualTo(0)
+ assertThat(underTest.authenticate(FakeAuthenticationRepository.PATTERN)).isTrue()
}
@Test
fun authenticate_withIncorrectPattern_returnsFalse() =
testScope.runTest {
- val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pattern(
- listOf(
- AuthenticationMethodModel.Pattern.PatternCoordinate(
- x = 0,
- y = 0,
- ),
- AuthenticationMethodModel.Pattern.PatternCoordinate(
- x = 0,
- y = 1,
- ),
- AuthenticationMethodModel.Pattern.PatternCoordinate(
- x = 0,
- y = 2,
- ),
- )
- )
+ AuthenticationMethodModel.Pattern
)
assertThat(
@@ -299,91 +249,243 @@
)
)
.isFalse()
- assertThat(failedAttemptCount).isEqualTo(1)
- }
-
- @Test
- fun tryAutoConfirm_withAutoConfirmPinAndEmptyInput_returnsNull() =
- testScope.runTest {
- val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin(1234, autoConfirm = true)
- )
-
- assertThat(underTest.authenticate(listOf(), tryAutoConfirm = true)).isNull()
- assertThat(failedAttemptCount).isEqualTo(0)
}
@Test
fun tryAutoConfirm_withAutoConfirmPinAndShorterPin_returnsNullAndHasNoEffect() =
testScope.runTest {
- val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin(1234, autoConfirm = true)
- )
-
- assertThat(underTest.authenticate(listOf(1, 2, 3), tryAutoConfirm = true)).isNull()
- assertThat(failedAttemptCount).isEqualTo(0)
+ val isThrottled by collectLastValue(underTest.isThrottled)
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+ utils.authenticationRepository.setAutoConfirmEnabled(true)
+ assertThat(
+ underTest.authenticate(
+ FakeAuthenticationRepository.DEFAULT_PIN.toMutableList().apply {
+ removeLast()
+ },
+ tryAutoConfirm = true
+ )
+ )
+ .isNull()
+ assertThat(isThrottled).isFalse()
}
@Test
fun tryAutoConfirm_withAutoConfirmWrongPinCorrectLength_returnsFalseAndDoesNotUnlockDevice() =
testScope.runTest {
- val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin(1234, autoConfirm = true)
- )
-
- assertThat(underTest.authenticate(listOf(1, 2, 4, 4), tryAutoConfirm = true)).isFalse()
- assertThat(failedAttemptCount).isEqualTo(1)
+ val isUnlocked by collectLastValue(underTest.isUnlocked)
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+ utils.authenticationRepository.setAutoConfirmEnabled(true)
+ assertThat(
+ underTest.authenticate(
+ FakeAuthenticationRepository.DEFAULT_PIN.map { it + 1 },
+ tryAutoConfirm = true
+ )
+ )
+ .isFalse()
+ assertThat(isUnlocked).isFalse()
}
@Test
fun tryAutoConfirm_withAutoConfirmLongerPin_returnsFalseAndDoesNotUnlockDevice() =
testScope.runTest {
- val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin(1234, autoConfirm = true)
- )
-
- assertThat(underTest.authenticate(listOf(1, 2, 3, 4, 5), tryAutoConfirm = true))
+ val isUnlocked by collectLastValue(underTest.isUnlocked)
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+ utils.authenticationRepository.setAutoConfirmEnabled(true)
+ assertThat(
+ underTest.authenticate(
+ FakeAuthenticationRepository.DEFAULT_PIN + listOf(7),
+ tryAutoConfirm = true
+ )
+ )
.isFalse()
- assertThat(failedAttemptCount).isEqualTo(1)
+ assertThat(isUnlocked).isFalse()
}
@Test
fun tryAutoConfirm_withAutoConfirmCorrectPin_returnsTrueAndUnlocksDevice() =
testScope.runTest {
- val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin(1234, autoConfirm = true)
- )
-
- assertThat(underTest.authenticate(listOf(1, 2, 4, 4), tryAutoConfirm = true)).isFalse()
- assertThat(failedAttemptCount).isEqualTo(1)
+ val isUnlocked by collectLastValue(underTest.isUnlocked)
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+ utils.authenticationRepository.setAutoConfirmEnabled(true)
+ assertThat(
+ underTest.authenticate(
+ FakeAuthenticationRepository.DEFAULT_PIN,
+ tryAutoConfirm = true
+ )
+ )
+ .isTrue()
+ assertThat(isUnlocked).isTrue()
}
@Test
fun tryAutoConfirm_withoutAutoConfirmButCorrectPin_returnsNullAndHasNoEffects() =
testScope.runTest {
- val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin(1234, autoConfirm = false)
- )
-
- assertThat(underTest.authenticate(listOf(1, 2, 3, 4), tryAutoConfirm = true)).isNull()
- assertThat(failedAttemptCount).isEqualTo(0)
+ val isUnlocked by collectLastValue(underTest.isUnlocked)
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+ utils.authenticationRepository.setAutoConfirmEnabled(false)
+ assertThat(
+ underTest.authenticate(
+ FakeAuthenticationRepository.DEFAULT_PIN,
+ tryAutoConfirm = true
+ )
+ )
+ .isNull()
+ assertThat(isUnlocked).isFalse()
}
@Test
fun tryAutoConfirm_withoutCorrectPassword_returnsNullAndHasNoEffects() =
testScope.runTest {
- val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
+ val isUnlocked by collectLastValue(underTest.isUnlocked)
utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Password("password")
+ AuthenticationMethodModel.Password
)
assertThat(underTest.authenticate("password".toList(), tryAutoConfirm = true)).isNull()
- assertThat(failedAttemptCount).isEqualTo(0)
+ assertThat(isUnlocked).isFalse()
+ }
+
+ @Test
+ fun throttling() =
+ testScope.runTest {
+ val isUnlocked by collectLastValue(underTest.isUnlocked)
+ val throttling by collectLastValue(underTest.throttling)
+ val isThrottled by collectLastValue(underTest.isThrottled)
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+ underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)
+ assertThat(isUnlocked).isTrue()
+ assertThat(isThrottled).isFalse()
+ assertThat(throttling).isEqualTo(AuthenticationThrottlingModel())
+
+ utils.authenticationRepository.setUnlocked(false)
+ assertThat(isUnlocked).isFalse()
+ assertThat(isThrottled).isFalse()
+ assertThat(throttling).isEqualTo(AuthenticationThrottlingModel())
+
+ // Make many wrong attempts, but just shy of what's needed to get throttled:
+ repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING - 1) {
+ underTest.authenticate(listOf(5, 6, 7)) // Wrong PIN
+ assertThat(isUnlocked).isFalse()
+ assertThat(isThrottled).isFalse()
+ assertThat(throttling).isEqualTo(AuthenticationThrottlingModel())
+ }
+
+ // Make one more wrong attempt, leading to throttling:
+ underTest.authenticate(listOf(5, 6, 7)) // Wrong PIN
+ assertThat(isUnlocked).isFalse()
+ assertThat(isThrottled).isTrue()
+ assertThat(throttling)
+ .isEqualTo(
+ AuthenticationThrottlingModel(
+ failedAttemptCount =
+ FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING,
+ remainingMs = FakeAuthenticationRepository.THROTTLE_DURATION_MS,
+ )
+ )
+
+ // Correct PIN, but throttled, so doesn't attempt it:
+ assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)).isNull()
+ assertThat(isUnlocked).isFalse()
+ assertThat(isThrottled).isTrue()
+ assertThat(throttling)
+ .isEqualTo(
+ AuthenticationThrottlingModel(
+ failedAttemptCount =
+ FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING,
+ remainingMs = FakeAuthenticationRepository.THROTTLE_DURATION_MS,
+ )
+ )
+
+ // Move the clock forward to ALMOST skip the throttling, leaving one second to go:
+ val throttleTimeoutSec =
+ FakeAuthenticationRepository.THROTTLE_DURATION_MS.milliseconds.inWholeSeconds
+ .toInt()
+ repeat(throttleTimeoutSec - 1) { time ->
+ advanceTimeBy(1000)
+ assertThat(isThrottled).isTrue()
+ assertThat(throttling)
+ .isEqualTo(
+ AuthenticationThrottlingModel(
+ failedAttemptCount =
+ FakeAuthenticationRepository
+ .MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING,
+ remainingMs =
+ ((throttleTimeoutSec - (time + 1)).seconds.inWholeMilliseconds)
+ .toInt(),
+ )
+ )
+ }
+
+ // Move the clock forward one more second, to completely finish the throttling period:
+ advanceTimeBy(1000)
+ assertThat(isUnlocked).isFalse()
+ assertThat(isThrottled).isFalse()
+ assertThat(throttling)
+ .isEqualTo(
+ AuthenticationThrottlingModel(
+ failedAttemptCount =
+ FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING,
+ remainingMs = 0,
+ )
+ )
+
+ // Correct PIN and no longer throttled so unlocks successfully:
+ assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)).isTrue()
+ assertThat(isUnlocked).isTrue()
+ assertThat(isThrottled).isFalse()
+ assertThat(throttling).isEqualTo(AuthenticationThrottlingModel())
+ }
+
+ @Test
+ fun hintedPinLength_withoutAutoConfirm_isNull() =
+ testScope.runTest {
+ val hintedPinLength by collectLastValue(underTest.hintedPinLength)
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+ utils.authenticationRepository.setAutoConfirmEnabled(false)
+
+ assertThat(hintedPinLength).isNull()
+ }
+
+ @Test
+ fun hintedPinLength_withAutoConfirmPinTooShort_isNull() =
+ testScope.runTest {
+ val hintedPinLength by collectLastValue(underTest.hintedPinLength)
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+ utils.authenticationRepository.overrideCredential(
+ buildList {
+ repeat(utils.authenticationRepository.hintedPinLength - 1) { add(it + 1) }
+ }
+ )
+ utils.authenticationRepository.setAutoConfirmEnabled(true)
+
+ assertThat(hintedPinLength).isNull()
+ }
+
+ @Test
+ fun hintedPinLength_withAutoConfirmPinAtRightLength_isSameLength() =
+ testScope.runTest {
+ val hintedPinLength by collectLastValue(underTest.hintedPinLength)
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+ utils.authenticationRepository.setAutoConfirmEnabled(true)
+ utils.authenticationRepository.overrideCredential(
+ buildList { repeat(utils.authenticationRepository.hintedPinLength) { add(it + 1) } }
+ )
+
+ assertThat(hintedPinLength).isEqualTo(utils.authenticationRepository.hintedPinLength)
+ }
+
+ @Test
+ fun hintedPinLength_withAutoConfirmPinTooLong_isNull() =
+ testScope.runTest {
+ val hintedPinLength by collectLastValue(underTest.hintedPinLength)
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+ utils.authenticationRepository.overrideCredential(
+ buildList {
+ repeat(utils.authenticationRepository.hintedPinLength + 1) { add(it + 1) }
+ }
+ )
+ utils.authenticationRepository.setAutoConfirmEnabled(true)
+
+ assertThat(hintedPinLength).isNull()
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewControllerTest.java
index 1482f29..40b5729 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewControllerTest.java
@@ -33,10 +33,10 @@
import androidx.test.filters.SmallTest;
+import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.flags.FakeFeatureFlags;
-import com.android.systemui.flags.Flags;
import com.android.systemui.settings.UserTracker;
+import com.android.systemui.statusbar.phone.StatusBarLocation;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.tuner.TunerService;
@@ -61,7 +61,6 @@
private Handler mHandler;
@Mock
private ContentResolver mContentResolver;
- private FakeFeatureFlags mFeatureFlags;
@Mock
private BatteryController mBatteryController;
@@ -74,8 +73,8 @@
when(mBatteryMeterView.getContext()).thenReturn(mContext);
when(mBatteryMeterView.getResources()).thenReturn(mContext.getResources());
- mFeatureFlags = new FakeFeatureFlags();
- mFeatureFlags.set(Flags.BATTERY_SHIELD_ICON, false);
+ mContext.getOrCreateTestableResources().addOverride(
+ R.bool.flag_battery_shield_icon, false);
}
@Test
@@ -134,7 +133,8 @@
@Test
public void shieldFlagDisabled_viewNotified() {
- mFeatureFlags.set(Flags.BATTERY_SHIELD_ICON, false);
+ mContext.getOrCreateTestableResources().addOverride(
+ R.bool.flag_battery_shield_icon, false);
initController();
@@ -143,7 +143,8 @@
@Test
public void shieldFlagEnabled_viewNotified() {
- mFeatureFlags.set(Flags.BATTERY_SHIELD_ICON, true);
+ mContext.getOrCreateTestableResources().addOverride(
+ R.bool.flag_battery_shield_icon, true);
initController();
@@ -153,12 +154,12 @@
private void initController() {
mController = new BatteryMeterViewController(
mBatteryMeterView,
+ StatusBarLocation.HOME,
mUserTracker,
mConfigurationController,
mTunerService,
mHandler,
mContentResolver,
- mFeatureFlags,
mBatteryController
);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
index d022653..ecc776b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
@@ -44,25 +44,27 @@
import android.view.ViewPropertyAnimator
import android.view.WindowInsets
import android.view.WindowManager
-import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION
-import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY
-import android.view.WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG
import android.view.WindowMetrics
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.airbnb.lottie.LottieAnimationView
+import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.R
import com.android.systemui.RoboPilotTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.SysuiTestableContext
+import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
import com.android.systemui.biometrics.data.repository.FakeRearDisplayStateRepository
import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractorImpl
+import com.android.systemui.biometrics.domain.interactor.SideFpsOverlayInteractorImpl
+import com.android.systemui.biometrics.shared.model.FingerprintSensorType
+import com.android.systemui.biometrics.shared.model.SensorStrength
+import com.android.systemui.biometrics.ui.viewmodel.SideFpsOverlayViewModel
import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
import com.android.systemui.dump.DumpManager
import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
-import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.concurrency.FakeExecutor
@@ -99,7 +101,7 @@
@SmallTest
@RoboPilotTest
@RunWith(AndroidJUnit4::class)
-@TestableLooper.RunWithLooper
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
class SideFpsControllerTest : SysuiTestCase() {
@JvmField @Rule var rule = MockitoJUnit.rule()
@@ -118,6 +120,8 @@
private lateinit var keyguardBouncerRepository: FakeKeyguardBouncerRepository
private lateinit var alternateBouncerInteractor: AlternateBouncerInteractor
private lateinit var displayStateInteractor: DisplayStateInteractor
+ private lateinit var sideFpsOverlayViewModel: SideFpsOverlayViewModel
+ private val fingerprintRepository = FakeFingerprintPropertyRepository()
private val executor = FakeExecutor(FakeSystemClock())
private val rearDisplayStateRepository = FakeRearDisplayStateRepository()
@@ -149,8 +153,8 @@
mock(KeyguardStateController::class.java),
keyguardBouncerRepository,
FakeBiometricSettingsRepository(),
- FakeDeviceEntryFingerprintAuthRepository(),
FakeSystemClock(),
+ mock(KeyguardUpdateMonitor::class.java),
)
displayStateInteractor =
DisplayStateInteractorImpl(
@@ -159,6 +163,15 @@
executor,
rearDisplayStateRepository
)
+ sideFpsOverlayViewModel =
+ SideFpsOverlayViewModel(context, SideFpsOverlayInteractorImpl(fingerprintRepository))
+
+ fingerprintRepository.setProperties(
+ sensorId = 1,
+ strength = SensorStrength.STRONG,
+ sensorType = FingerprintSensorType.REAR,
+ sensorLocations = mapOf("" to SensorLocationInternal("", 2500, 0, 0))
+ )
context.addMockSystemService(DisplayManager::class.java, displayManager)
context.addMockSystemService(WindowManager::class.java, windowManager)
@@ -265,6 +278,7 @@
executor,
handler,
alternateBouncerInteractor,
+ { sideFpsOverlayViewModel },
TestCoroutineScope(),
dumpManager
)
@@ -683,106 +697,6 @@
verify(windowManager).removeView(any())
}
- /**
- * {@link SideFpsController#updateOverlayParams} calculates indicator placement for ROTATION_0,
- * and uses RotateUtils.rotateBounds to map to the correct indicator location given the device
- * rotation. Assuming RotationUtils.rotateBounds works correctly, tests for indicator placement
- * in other rotations have been omitted.
- */
- @Test
- fun verifiesIndicatorPlacementForXAlignedSensor_0() =
- testWithDisplay(
- deviceConfig = DeviceConfig.X_ALIGNED,
- isReverseDefaultRotation = false,
- { rotation = Surface.ROTATION_0 }
- ) {
- sideFpsController.overlayOffsets = sensorLocation
-
- sideFpsController.updateOverlayParams(windowManager.defaultDisplay, indicatorBounds)
-
- overlayController.show(SENSOR_ID, REASON_UNKNOWN)
- executor.runAllReady()
-
- verify(windowManager).updateViewLayout(any(), overlayViewParamsCaptor.capture())
- assertThat(overlayViewParamsCaptor.value.x).isEqualTo(sensorLocation.sensorLocationX)
- assertThat(overlayViewParamsCaptor.value.y).isEqualTo(0)
- }
-
- /**
- * {@link SideFpsController#updateOverlayParams} calculates indicator placement for ROTATION_270
- * in reverse default rotation. It then uses RotateUtils.rotateBounds to map to the correct
- * indicator location given the device rotation. Assuming RotationUtils.rotateBounds works
- * correctly, tests for indicator placement in other rotations have been omitted.
- */
- @Test
- fun verifiesIndicatorPlacementForXAlignedSensor_InReverseDefaultRotation_270() =
- testWithDisplay(
- deviceConfig = DeviceConfig.X_ALIGNED,
- isReverseDefaultRotation = true,
- { rotation = Surface.ROTATION_270 }
- ) {
- sideFpsController.overlayOffsets = sensorLocation
-
- sideFpsController.updateOverlayParams(windowManager.defaultDisplay, indicatorBounds)
-
- overlayController.show(SENSOR_ID, REASON_UNKNOWN)
- executor.runAllReady()
-
- verify(windowManager).updateViewLayout(any(), overlayViewParamsCaptor.capture())
- assertThat(overlayViewParamsCaptor.value.x).isEqualTo(sensorLocation.sensorLocationX)
- assertThat(overlayViewParamsCaptor.value.y).isEqualTo(0)
- }
-
- /**
- * {@link SideFpsController#updateOverlayParams} calculates indicator placement for ROTATION_0,
- * and uses RotateUtils.rotateBounds to map to the correct indicator location given the device
- * rotation. Assuming RotationUtils.rotateBounds works correctly, tests for indicator placement
- * in other rotations have been omitted.
- */
- @Test
- fun verifiesIndicatorPlacementForYAlignedSensor_0() =
- testWithDisplay(
- deviceConfig = DeviceConfig.Y_ALIGNED,
- isReverseDefaultRotation = false,
- { rotation = Surface.ROTATION_0 }
- ) {
- sideFpsController.overlayOffsets = sensorLocation
-
- sideFpsController.updateOverlayParams(windowManager.defaultDisplay, indicatorBounds)
-
- overlayController.show(SENSOR_ID, REASON_UNKNOWN)
- executor.runAllReady()
-
- verify(windowManager).updateViewLayout(any(), overlayViewParamsCaptor.capture())
- assertThat(overlayViewParamsCaptor.value.x).isEqualTo(displayWidth - boundsWidth)
- assertThat(overlayViewParamsCaptor.value.y).isEqualTo(sensorLocation.sensorLocationY)
- }
-
- /**
- * {@link SideFpsController#updateOverlayParams} calculates indicator placement for ROTATION_270
- * in reverse default rotation. It then uses RotateUtils.rotateBounds to map to the correct
- * indicator location given the device rotation. Assuming RotationUtils.rotateBounds works
- * correctly, tests for indicator placement in other rotations have been omitted.
- */
- @Test
- fun verifiesIndicatorPlacementForYAlignedSensor_InReverseDefaultRotation_270() =
- testWithDisplay(
- deviceConfig = DeviceConfig.Y_ALIGNED,
- isReverseDefaultRotation = true,
- { rotation = Surface.ROTATION_270 }
- ) {
- sideFpsController.overlayOffsets = sensorLocation
-
- sideFpsController.updateOverlayParams(windowManager.defaultDisplay, indicatorBounds)
-
- overlayController.show(SENSOR_ID, REASON_UNKNOWN)
- executor.runAllReady()
-
- verify(windowManager).updateViewLayout(any(), overlayViewParamsCaptor.capture())
- assertThat(overlayViewParamsCaptor.value.x).isEqualTo(displayWidth - boundsWidth)
- assertThat(overlayViewParamsCaptor.value.y).isEqualTo(sensorLocation.sensorLocationY)
- }
-
@Test
fun hasSideFpsSensor_withSensorProps_returnsTrue() = testWithDisplay {
// By default all those tests assume the side fps sensor is available.
@@ -795,51 +709,6 @@
assertThat(fingerprintManager.hasSideFpsSensor()).isFalse()
}
-
- @Test
- fun testLayoutParams_isKeyguardDialogType() =
- testWithDisplay(deviceConfig = DeviceConfig.Y_ALIGNED) {
- sideFpsController.overlayOffsets = sensorLocation
- sideFpsController.updateOverlayParams(windowManager.defaultDisplay, indicatorBounds)
- overlayController.show(SENSOR_ID, REASON_UNKNOWN)
- executor.runAllReady()
-
- verify(windowManager).updateViewLayout(any(), overlayViewParamsCaptor.capture())
-
- val lpType = overlayViewParamsCaptor.value.type
-
- assertThat((lpType and TYPE_KEYGUARD_DIALOG) != 0).isTrue()
- }
-
- @Test
- fun testLayoutParams_hasNoMoveAnimationWindowFlag() =
- testWithDisplay(deviceConfig = DeviceConfig.Y_ALIGNED) {
- sideFpsController.overlayOffsets = sensorLocation
- sideFpsController.updateOverlayParams(windowManager.defaultDisplay, indicatorBounds)
- overlayController.show(SENSOR_ID, REASON_UNKNOWN)
- executor.runAllReady()
-
- verify(windowManager).updateViewLayout(any(), overlayViewParamsCaptor.capture())
-
- val lpFlags = overlayViewParamsCaptor.value.privateFlags
-
- assertThat((lpFlags and PRIVATE_FLAG_NO_MOVE_ANIMATION) != 0).isTrue()
- }
-
- @Test
- fun testLayoutParams_hasTrustedOverlayWindowFlag() =
- testWithDisplay(deviceConfig = DeviceConfig.Y_ALIGNED) {
- sideFpsController.overlayOffsets = sensorLocation
- sideFpsController.updateOverlayParams(windowManager.defaultDisplay, indicatorBounds)
- overlayController.show(SENSOR_ID, REASON_UNKNOWN)
- executor.runAllReady()
-
- verify(windowManager).updateViewLayout(any(), overlayViewParamsCaptor.capture())
-
- val lpFlags = overlayViewParamsCaptor.value.privateFlags
-
- assertThat((lpFlags and PRIVATE_FLAG_TRUSTED_OVERLAY) != 0).isTrue()
- }
}
private fun insetsForSmallNavbar() = insetsWithBottom(60)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt
index 9df06dc..8dfeb3b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt
@@ -34,7 +34,6 @@
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.DismissCallbackRegistry
import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
-import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.FakeTrustRepository
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.plugins.statusbar.StatusBarStateController
@@ -106,8 +105,8 @@
mock(KeyguardStateController::class.java),
keyguardBouncerRepository,
mock(BiometricSettingsRepository::class.java),
- mock(DeviceEntryFingerprintAuthRepository::class.java),
mock(SystemClock::class.java),
+ mKeyguardUpdateMonitor,
)
return createUdfpsKeyguardViewController(
/* useModernBouncer */ true, /* useExpandedOverlay */
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FingerprintRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FingerprintRepositoryImplTest.kt
index 239e317..ea25615 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FingerprintRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FingerprintRepositoryImplTest.kt
@@ -73,10 +73,15 @@
@Test
fun initializeProperties() =
testScope.runTest {
- val isInitialized = collectLastValue(repository.isInitialized)
+ val sensorId by collectLastValue(repository.sensorId)
+ val strength by collectLastValue(repository.strength)
+ val sensorType by collectLastValue(repository.sensorType)
+ val sensorLocations by collectLastValue(repository.sensorLocations)
- assertDefaultProperties()
- assertThat(isInitialized()).isFalse()
+ // Assert default properties.
+ assertThat(sensorId).isEqualTo(-1)
+ assertThat(strength).isEqualTo(SensorStrength.CONVENIENCE)
+ assertThat(sensorType).isEqualTo(FingerprintSensorType.UNKNOWN)
val fingerprintProps =
listOf(
@@ -115,31 +120,24 @@
fingerprintAuthenticatorsCaptor.value.onAllAuthenticatorsRegistered(fingerprintProps)
- assertThat(repository.sensorId.value).isEqualTo(1)
- assertThat(repository.strength.value).isEqualTo(SensorStrength.STRONG)
- assertThat(repository.sensorType.value).isEqualTo(FingerprintSensorType.REAR)
+ assertThat(sensorId).isEqualTo(1)
+ assertThat(strength).isEqualTo(SensorStrength.STRONG)
+ assertThat(sensorType).isEqualTo(FingerprintSensorType.REAR)
- assertThat(repository.sensorLocations.value.size).isEqualTo(2)
- assertThat(repository.sensorLocations.value).containsKey("display_id_1")
- with(repository.sensorLocations.value["display_id_1"]!!) {
+ assertThat(sensorLocations?.size).isEqualTo(2)
+ assertThat(sensorLocations).containsKey("display_id_1")
+ with(sensorLocations?.get("display_id_1")!!) {
assertThat(displayId).isEqualTo("display_id_1")
assertThat(sensorLocationX).isEqualTo(100)
assertThat(sensorLocationY).isEqualTo(300)
assertThat(sensorRadius).isEqualTo(20)
}
- assertThat(repository.sensorLocations.value).containsKey("")
- with(repository.sensorLocations.value[""]!!) {
+ assertThat(sensorLocations).containsKey("")
+ with(sensorLocations?.get("")!!) {
assertThat(displayId).isEqualTo("")
assertThat(sensorLocationX).isEqualTo(540)
assertThat(sensorLocationY).isEqualTo(1636)
assertThat(sensorRadius).isEqualTo(130)
}
- assertThat(isInitialized()).isTrue()
}
-
- private fun assertDefaultProperties() {
- assertThat(repository.sensorId.value).isEqualTo(-1)
- assertThat(repository.strength.value).isEqualTo(SensorStrength.CONVENIENCE)
- assertThat(repository.sensorType.value).isEqualTo(FingerprintSensorType.UNKNOWN)
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractorTest.kt
index fd96cf4..896f9b11 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractorTest.kt
@@ -22,6 +22,7 @@
import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
import com.android.systemui.biometrics.shared.model.FingerprintSensorType
import com.android.systemui.biometrics.shared.model.SensorStrength
+import com.android.systemui.coroutines.collectLastValue
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
@@ -51,8 +52,9 @@
}
@Test
- fun testGetOverlayOffsets() =
+ fun testGetOverlayoffsets() =
testScope.runTest {
+ // Arrange.
fingerprintRepository.setProperties(
sensorId = 1,
strength = SensorStrength.STRONG,
@@ -76,16 +78,33 @@
)
)
- var offsets = interactor.getOverlayOffsets("display_id_1")
- assertThat(offsets.displayId).isEqualTo("display_id_1")
- assertThat(offsets.sensorLocationX).isEqualTo(100)
- assertThat(offsets.sensorLocationY).isEqualTo(300)
- assertThat(offsets.sensorRadius).isEqualTo(20)
+ // Act.
+ val offsets by collectLastValue(interactor.overlayOffsets)
+ val displayId by collectLastValue(interactor.displayId)
- offsets = interactor.getOverlayOffsets("invalid_display_id")
- assertThat(offsets.displayId).isEqualTo("")
- assertThat(offsets.sensorLocationX).isEqualTo(540)
- assertThat(offsets.sensorLocationY).isEqualTo(1636)
- assertThat(offsets.sensorRadius).isEqualTo(130)
+ // Assert offsets of empty displayId.
+ assertThat(displayId).isEqualTo("")
+ assertThat(offsets?.displayId).isEqualTo("")
+ assertThat(offsets?.sensorLocationX).isEqualTo(540)
+ assertThat(offsets?.sensorLocationY).isEqualTo(1636)
+ assertThat(offsets?.sensorRadius).isEqualTo(130)
+
+ // Offsets should be updated correctly.
+ interactor.changeDisplay("display_id_1")
+ assertThat(displayId).isEqualTo("display_id_1")
+ assertThat(offsets?.displayId).isEqualTo("display_id_1")
+ assertThat(offsets?.sensorLocationX).isEqualTo(100)
+ assertThat(offsets?.sensorLocationY).isEqualTo(300)
+ assertThat(offsets?.sensorRadius).isEqualTo(20)
+
+ // Should return default offset when the displayId is invalid.
+ interactor.changeDisplay("invalid_display_id")
+ assertThat(displayId).isEqualTo("invalid_display_id")
+ assertThat(offsets?.displayId).isEqualTo(SensorLocationInternal.DEFAULT.displayId)
+ assertThat(offsets?.sensorLocationX)
+ .isEqualTo(SensorLocationInternal.DEFAULT.sensorLocationX)
+ assertThat(offsets?.sensorLocationY)
+ .isEqualTo(SensorLocationInternal.DEFAULT.sensorLocationY)
+ assertThat(offsets?.sensorRadius).isEqualTo(SensorLocationInternal.DEFAULT.sensorRadius)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptAuthStateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptAuthStateTest.kt
index fff1b81..278a43e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptAuthStateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptAuthStateTest.kt
@@ -18,7 +18,7 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.biometrics.domain.model.BiometricModality
+import com.android.systemui.biometrics.shared.model.BiometricModality
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
index 87c9e58..91140a9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
@@ -27,11 +27,12 @@
import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor
import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractorImpl
import com.android.systemui.biometrics.domain.model.BiometricModalities
-import com.android.systemui.biometrics.domain.model.BiometricModality
import com.android.systemui.biometrics.extractAuthenticatorTypes
import com.android.systemui.biometrics.faceSensorPropertiesInternal
import com.android.systemui.biometrics.fingerprintSensorPropertiesInternal
+import com.android.systemui.biometrics.shared.model.BiometricModality
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.coroutines.collectValues
import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.util.mockito.any
import com.google.common.truth.Truth.assertThat
@@ -131,20 +132,22 @@
}
@Test
- fun plays_haptic_on_authenticated() = runGenericTest {
- viewModel.showAuthenticated(testCase.authenticatedModality, 1000L)
+ fun play_haptic_on_confirm_when_confirmation_required_otherwise_on_authenticated() =
+ runGenericTest {
+ val expectConfirmation = testCase.expectConfirmation(atLeastOneFailure = false)
- verify(vibrator).vibrateAuthSuccess(any())
- verify(vibrator, never()).vibrateAuthError(any())
- }
+ viewModel.showAuthenticated(testCase.authenticatedModality, 1_000L)
- @Test
- fun plays_no_haptic_on_confirm() = runGenericTest {
- viewModel.confirmAuthenticated()
+ verify(vibrator, if (expectConfirmation) never() else times(1))
+ .vibrateAuthSuccess(any())
- verify(vibrator, never()).vibrateAuthSuccess(any())
- verify(vibrator, never()).vibrateAuthError(any())
- }
+ if (expectConfirmation) {
+ viewModel.confirmAuthenticated()
+ }
+
+ verify(vibrator).vibrateAuthSuccess(any())
+ verify(vibrator, never()).vibrateAuthError(any())
+ }
private suspend fun TestScope.showAuthenticated(
authenticatedModality: BiometricModality,
@@ -204,7 +207,12 @@
@Test
fun plays_haptic_on_errors() = runGenericTest {
- viewModel.showTemporaryError("so sad", hapticFeedback = true)
+ viewModel.showTemporaryError(
+ "so sad",
+ messageAfterError = "",
+ authenticateAfterError = false,
+ hapticFeedback = true,
+ )
verify(vibrator).vibrateAuthError(any())
verify(vibrator, never()).vibrateAuthSuccess(any())
@@ -212,7 +220,12 @@
@Test
fun plays_haptic_on_errors_unless_skipped() = runGenericTest {
- viewModel.showTemporaryError("still sad", hapticFeedback = false)
+ viewModel.showTemporaryError(
+ "still sad",
+ messageAfterError = "",
+ authenticateAfterError = false,
+ hapticFeedback = false,
+ )
verify(vibrator, never()).vibrateAuthError(any())
verify(vibrator, never()).vibrateAuthSuccess(any())
@@ -287,7 +300,13 @@
assertThat(canTryAgain).isFalse()
}
- val errorJob = launch { viewModel.showTemporaryError("error") }
+ val errorJob = launch {
+ viewModel.showTemporaryError(
+ "error",
+ messageAfterError = "",
+ authenticateAfterError = false,
+ )
+ }
verifyNoError()
errorJob.join()
verifyNoError()
@@ -306,12 +325,66 @@
assertThat(messageIsShowing).isTrue()
}
- // @Test
- fun `suppress errors`() = runGenericTest {
- val errorMessage = "woot"
- val message by collectLastValue(viewModel.message)
+ @Test
+ fun suppress_temporary_error() = runGenericTest {
+ val messages by collectValues(viewModel.message)
- val errorJob = launch { viewModel.showTemporaryError(errorMessage) }
+ for (error in listOf("never", "see", "me")) {
+ launch {
+ viewModel.showTemporaryError(
+ error,
+ messageAfterError = "or me",
+ authenticateAfterError = false,
+ suppressIf = { _ -> true },
+ )
+ }
+ }
+
+ testScheduler.advanceUntilIdle()
+ assertThat(messages).containsExactly(PromptMessage.Empty)
+ }
+
+ @Test
+ fun suppress_temporary_error_when_already_showing_when_requested() =
+ suppress_temporary_error_when_already_showing(suppress = true)
+
+ @Test
+ fun do_not_suppress_temporary_error_when_already_showing_when_not_requested() =
+ suppress_temporary_error_when_already_showing(suppress = false)
+
+ private fun suppress_temporary_error_when_already_showing(suppress: Boolean) = runGenericTest {
+ val errors = listOf("woot", "oh yeah", "nope")
+ val afterSuffix = "(after)"
+ val expectedErrorMessage = if (suppress) errors.first() else errors.last()
+ val messages by collectValues(viewModel.message)
+
+ for (error in errors) {
+ launch {
+ viewModel.showTemporaryError(
+ error,
+ messageAfterError = "$error $afterSuffix",
+ authenticateAfterError = false,
+ suppressIf = { currentMessage -> suppress && currentMessage.isError },
+ )
+ }
+ }
+
+ testScheduler.runCurrent()
+ assertThat(messages)
+ .containsExactly(
+ PromptMessage.Empty,
+ PromptMessage.Error(expectedErrorMessage),
+ )
+ .inOrder()
+
+ testScheduler.advanceUntilIdle()
+ assertThat(messages)
+ .containsExactly(
+ PromptMessage.Empty,
+ PromptMessage.Error(expectedErrorMessage),
+ PromptMessage.Help("$expectedErrorMessage $afterSuffix"),
+ )
+ .inOrder()
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt
new file mode 100644
index 0000000..a859321
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt
@@ -0,0 +1,263 @@
+/*
+ * 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.biometrics.ui.viewmodel
+
+import android.graphics.Rect
+import android.hardware.biometrics.SensorLocationInternal
+import android.hardware.display.DisplayManagerGlobal
+import android.view.Display
+import android.view.DisplayAdjustments
+import android.view.DisplayInfo
+import android.view.Surface
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.SysuiTestableContext
+import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
+import com.android.systemui.biometrics.domain.interactor.SideFpsOverlayInteractor
+import com.android.systemui.biometrics.domain.interactor.SideFpsOverlayInteractorImpl
+import com.android.systemui.biometrics.shared.model.FingerprintSensorType
+import com.android.systemui.biometrics.shared.model.SensorStrength
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.ArgumentMatchers
+import org.mockito.Mockito
+import org.mockito.junit.MockitoJUnit
+
+private const val DISPLAY_ID = 2
+
+@SmallTest
+@RunWith(JUnit4::class)
+class SideFpsOverlayViewModelTest : SysuiTestCase() {
+
+ @JvmField @Rule var mockitoRule = MockitoJUnit.rule()
+ private var testScope: TestScope = TestScope(StandardTestDispatcher())
+
+ private val fingerprintRepository = FakeFingerprintPropertyRepository()
+ private lateinit var interactor: SideFpsOverlayInteractor
+ private lateinit var viewModel: SideFpsOverlayViewModel
+
+ enum class DeviceConfig {
+ X_ALIGNED,
+ Y_ALIGNED,
+ }
+
+ private lateinit var deviceConfig: DeviceConfig
+ private lateinit var indicatorBounds: Rect
+ private lateinit var displayBounds: Rect
+ private lateinit var sensorLocation: SensorLocationInternal
+ private var displayWidth: Int = 0
+ private var displayHeight: Int = 0
+ private var boundsWidth: Int = 0
+ private var boundsHeight: Int = 0
+
+ @Before
+ fun setup() {
+ interactor = SideFpsOverlayInteractorImpl(fingerprintRepository)
+
+ fingerprintRepository.setProperties(
+ sensorId = 1,
+ strength = SensorStrength.STRONG,
+ sensorType = FingerprintSensorType.REAR,
+ sensorLocations =
+ mapOf(
+ "" to
+ SensorLocationInternal(
+ "" /* displayId */,
+ 540 /* sensorLocationX */,
+ 1636 /* sensorLocationY */,
+ 130 /* sensorRadius */
+ ),
+ "display_id_1" to
+ SensorLocationInternal(
+ "display_id_1" /* displayId */,
+ 100 /* sensorLocationX */,
+ 300 /* sensorLocationY */,
+ 20 /* sensorRadius */
+ )
+ )
+ )
+ }
+
+ @Test
+ fun testOverlayOffsets() =
+ testScope.runTest {
+ viewModel = SideFpsOverlayViewModel(mContext, interactor)
+
+ val interactorOffsets by collectLastValue(interactor.overlayOffsets)
+ val viewModelOffsets by collectLastValue(viewModel.overlayOffsets)
+
+ assertThat(viewModelOffsets).isEqualTo(interactorOffsets)
+ }
+
+ private fun testWithDisplay(
+ deviceConfig: DeviceConfig = DeviceConfig.X_ALIGNED,
+ isReverseDefaultRotation: Boolean = false,
+ initInfo: DisplayInfo.() -> Unit = {},
+ block: () -> Unit
+ ) {
+ this.deviceConfig = deviceConfig
+
+ when (deviceConfig) {
+ DeviceConfig.X_ALIGNED -> {
+ displayWidth = 3000
+ displayHeight = 1500
+ sensorLocation = SensorLocationInternal("", 2500, 0, 0)
+ boundsWidth = 200
+ boundsHeight = 100
+ }
+ DeviceConfig.Y_ALIGNED -> {
+ displayWidth = 2500
+ displayHeight = 2000
+ sensorLocation = SensorLocationInternal("", 0, 300, 0)
+ boundsWidth = 100
+ boundsHeight = 200
+ }
+ }
+
+ indicatorBounds = Rect(0, 0, boundsWidth, boundsHeight)
+ displayBounds = Rect(0, 0, displayWidth, displayHeight)
+
+ val displayInfo = DisplayInfo()
+ displayInfo.initInfo()
+
+ val dmGlobal = Mockito.mock(DisplayManagerGlobal::class.java)
+ val display =
+ Display(
+ dmGlobal,
+ DISPLAY_ID,
+ displayInfo,
+ DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS
+ )
+
+ whenever(dmGlobal.getDisplayInfo(ArgumentMatchers.eq(DISPLAY_ID))).thenReturn(displayInfo)
+
+ val sideFpsOverlayViewModelContext =
+ context.createDisplayContext(display) as SysuiTestableContext
+ sideFpsOverlayViewModelContext.orCreateTestableResources.addOverride(
+ com.android.internal.R.bool.config_reverseDefaultRotation,
+ isReverseDefaultRotation
+ )
+ viewModel = SideFpsOverlayViewModel(sideFpsOverlayViewModelContext, interactor)
+
+ block()
+ }
+
+ /**
+ * {@link SideFpsOverlayViewModel#updateSensorBounds} calculates indicator placement for
+ * ROTATION_0, and uses RotateUtils.rotateBounds to map to the correct indicator location given
+ * the device rotation. Assuming RotationUtils.rotateBounds works correctly, tests for indicator
+ * placement in other rotations have been omitted.
+ */
+ @Test
+ fun verifiesIndicatorPlacementForXAlignedSensor_0() =
+ testScope.runTest {
+ testWithDisplay(
+ deviceConfig = DeviceConfig.X_ALIGNED,
+ isReverseDefaultRotation = false,
+ { rotation = Surface.ROTATION_0 }
+ ) {
+ viewModel.updateSensorBounds(indicatorBounds, displayBounds, sensorLocation)
+
+ val displayInfo: DisplayInfo = DisplayInfo()
+ context.display!!.getDisplayInfo(displayInfo)
+ assertThat(displayInfo.rotation).isEqualTo(Surface.ROTATION_0)
+
+ assertThat(viewModel.sensorBounds.value).isNotNull()
+ assertThat(viewModel.sensorBounds.value.left)
+ .isEqualTo(sensorLocation.sensorLocationX)
+ assertThat(viewModel.sensorBounds.value.top).isEqualTo(0)
+ }
+ }
+
+ /**
+ * {@link SideFpsOverlayViewModel#updateSensorBounds} calculates indicator placement for
+ * ROTATION_270 in reverse default rotation. It then uses RotateUtils.rotateBounds to map to the
+ * correct indicator location given the device rotation. Assuming RotationUtils.rotateBounds
+ * works correctly, tests for indicator placement in other rotations have been omitted.
+ */
+ @Test
+ fun verifiesIndicatorPlacementForXAlignedSensor_InReverseDefaultRotation_270() =
+ testScope.runTest {
+ testWithDisplay(
+ deviceConfig = DeviceConfig.X_ALIGNED,
+ isReverseDefaultRotation = true,
+ { rotation = Surface.ROTATION_270 }
+ ) {
+ viewModel.updateSensorBounds(indicatorBounds, displayBounds, sensorLocation)
+
+ assertThat(viewModel.sensorBounds.value).isNotNull()
+ assertThat(viewModel.sensorBounds.value.left)
+ .isEqualTo(sensorLocation.sensorLocationX)
+ assertThat(viewModel.sensorBounds.value.top).isEqualTo(0)
+ }
+ }
+
+ /**
+ * {@link SideFpsOverlayViewModel#updateSensorBounds} calculates indicator placement for
+ * ROTATION_0, and uses RotateUtils.rotateBounds to map to the correct indicator location given
+ * the device rotation. Assuming RotationUtils.rotateBounds works correctly, tests for indicator
+ * placement in other rotations have been omitted.
+ */
+ @Test
+ fun verifiesIndicatorPlacementForYAlignedSensor_0() =
+ testScope.runTest {
+ testWithDisplay(
+ deviceConfig = DeviceConfig.Y_ALIGNED,
+ isReverseDefaultRotation = false,
+ { rotation = Surface.ROTATION_0 }
+ ) {
+ viewModel.updateSensorBounds(indicatorBounds, displayBounds, sensorLocation)
+
+ assertThat(viewModel.sensorBounds.value).isNotNull()
+ assertThat(viewModel.sensorBounds.value.left).isEqualTo(displayWidth - boundsWidth)
+ assertThat(viewModel.sensorBounds.value.top)
+ .isEqualTo(sensorLocation.sensorLocationY)
+ }
+ }
+
+ /**
+ * {@link SideFpsOverlayViewModel#updateSensorBounds} calculates indicator placement for
+ * ROTATION_270 in reverse default rotation. It then uses RotateUtils.rotateBounds to map to the
+ * correct indicator location given the device rotation. Assuming RotationUtils.rotateBounds
+ * works correctly, tests for indicator placement in other rotations have been omitted.
+ */
+ @Test
+ fun verifiesIndicatorPlacementForYAlignedSensor_InReverseDefaultRotation_270() =
+ testScope.runTest {
+ testWithDisplay(
+ deviceConfig = DeviceConfig.Y_ALIGNED,
+ isReverseDefaultRotation = true,
+ { rotation = Surface.ROTATION_270 }
+ ) {
+ viewModel.updateSensorBounds(indicatorBounds, displayBounds, sensorLocation)
+
+ assertThat(viewModel.sensorBounds.value).isNotNull()
+ assertThat(viewModel.sensorBounds.value.left).isEqualTo(displayWidth - boundsWidth)
+ assertThat(viewModel.sensorBounds.value.top)
+ .isEqualTo(sensorLocation.sensorLocationY)
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt
index 37b9ca4..186df02 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt
@@ -18,6 +18,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.RoboPilotTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository
@@ -54,6 +55,7 @@
@Mock private lateinit var keyguardStateController: KeyguardStateController
@Mock private lateinit var systemClock: SystemClock
@Mock private lateinit var bouncerLogger: TableLogBuffer
+ @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
@Before
fun setup() {
@@ -72,8 +74,8 @@
keyguardStateController,
bouncerRepository,
biometricSettingsRepository,
- deviceEntryFingerprintAuthRepository,
systemClock,
+ keyguardUpdateMonitor,
)
}
@@ -118,7 +120,7 @@
@Test
fun canShowAlternateBouncerForFingerprint_fingerprintLockedOut() {
givenCanShowAlternateBouncer()
- deviceEntryFingerprintAuthRepository.setLockedOut(true)
+ whenever(keyguardUpdateMonitor.isFingerprintLockedOut).thenReturn(true)
assertFalse(underTest.canShowAlternateBouncerForFingerprint())
}
@@ -168,7 +170,7 @@
biometricSettingsRepository.setFingerprintEnrolled(true)
biometricSettingsRepository.setStrongBiometricAllowed(true)
biometricSettingsRepository.setFingerprintEnabledByDevicePolicy(true)
- deviceEntryFingerprintAuthRepository.setLockedOut(false)
+ whenever(keyguardUpdateMonitor.isFingerprintLockedOut).thenReturn(false)
whenever(keyguardStateController.isUnlocked).thenReturn(false)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
index d09353b..6babf04 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
@@ -19,12 +19,16 @@
import androidx.test.filters.SmallTest
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.scene.SceneTestUtils
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
import com.google.common.truth.Truth.assertThat
+import kotlin.math.ceil
+import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runCurrent
@@ -65,14 +69,13 @@
@Test
fun pinAuthMethod() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene("container1"))
+ val currentScene by
+ collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
val message by collectLastValue(underTest.message)
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin(1234)
- )
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
utils.authenticationRepository.setUnlocked(false)
- underTest.showOrUnlockDevice("container1")
+ underTest.showOrUnlockDevice(SceneTestUtils.CONTAINER_1)
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PIN)
@@ -91,21 +94,21 @@
assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PIN)
// Correct input.
- assertThat(underTest.authenticate(listOf(1, 2, 3, 4))).isTrue()
+ assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)).isTrue()
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
}
@Test
fun pinAuthMethod_tryAutoConfirm_withAutoConfirmPin() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene("container1"))
+ val currentScene by
+ collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
val message by collectLastValue(underTest.message)
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin(1234, autoConfirm = true)
- )
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+ utils.authenticationRepository.setAutoConfirmEnabled(true)
utils.authenticationRepository.setUnlocked(false)
- underTest.showOrUnlockDevice("container1")
+ underTest.showOrUnlockDevice(SceneTestUtils.CONTAINER_1)
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PIN)
underTest.clearMessage()
@@ -115,27 +118,33 @@
assertThat(message).isEmpty()
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
- // Wrong 4-digit pin
- assertThat(underTest.authenticate(listOf(1, 2, 3, 5), tryAutoConfirm = true)).isFalse()
+ // Wrong 6-digit pin
+ assertThat(underTest.authenticate(listOf(1, 2, 3, 5, 5, 6), tryAutoConfirm = true))
+ .isFalse()
assertThat(message).isEqualTo(MESSAGE_WRONG_PIN)
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
// Correct input.
- assertThat(underTest.authenticate(listOf(1, 2, 3, 4), tryAutoConfirm = true)).isTrue()
+ assertThat(
+ underTest.authenticate(
+ FakeAuthenticationRepository.DEFAULT_PIN,
+ tryAutoConfirm = true
+ )
+ )
+ .isTrue()
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
}
@Test
fun pinAuthMethod_tryAutoConfirm_withoutAutoConfirmPin() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene("container1"))
+ val currentScene by
+ collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
val message by collectLastValue(underTest.message)
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin(1234, autoConfirm = false)
- )
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
utils.authenticationRepository.setUnlocked(false)
- underTest.showOrUnlockDevice("container1")
+ underTest.showOrUnlockDevice(SceneTestUtils.CONTAINER_1)
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.clearMessage()
@@ -145,7 +154,13 @@
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
// Correct input.
- assertThat(underTest.authenticate(listOf(1, 2, 3, 4), tryAutoConfirm = true)).isNull()
+ assertThat(
+ underTest.authenticate(
+ FakeAuthenticationRepository.DEFAULT_PIN,
+ tryAutoConfirm = true
+ )
+ )
+ .isNull()
assertThat(message).isEmpty()
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
}
@@ -153,13 +168,14 @@
@Test
fun passwordAuthMethod() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene("container1"))
+ val currentScene by
+ collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
val message by collectLastValue(underTest.message)
utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Password("password")
+ AuthenticationMethodModel.Password
)
utils.authenticationRepository.setUnlocked(false)
- underTest.showOrUnlockDevice("container1")
+ underTest.showOrUnlockDevice(SceneTestUtils.CONTAINER_1)
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PASSWORD)
@@ -185,13 +201,14 @@
@Test
fun patternAuthMethod() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene("container1"))
+ val currentScene by
+ collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
val message by collectLastValue(underTest.message)
utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pattern(emptyList())
+ AuthenticationMethodModel.Pattern
)
utils.authenticationRepository.setUnlocked(false)
- underTest.showOrUnlockDevice("container1")
+ underTest.showOrUnlockDevice(SceneTestUtils.CONTAINER_1)
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PATTERN)
@@ -204,7 +221,7 @@
// Wrong input.
assertThat(
underTest.authenticate(
- listOf(AuthenticationMethodModel.Pattern.PatternCoordinate(3, 4))
+ listOf(AuthenticationMethodModel.Pattern.PatternCoordinate(1, 2))
)
)
.isFalse()
@@ -215,21 +232,20 @@
assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PATTERN)
// Correct input.
- assertThat(underTest.authenticate(emptyList())).isTrue()
+ assertThat(underTest.authenticate(FakeAuthenticationRepository.PATTERN)).isTrue()
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
}
@Test
fun showOrUnlockDevice_notLocked_switchesToGoneScene() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene("container1"))
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin(1234)
- )
+ val currentScene by
+ collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
utils.authenticationRepository.setUnlocked(true)
runCurrent()
- underTest.showOrUnlockDevice("container1")
+ underTest.showOrUnlockDevice(SceneTestUtils.CONTAINER_1)
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
}
@@ -237,11 +253,12 @@
@Test
fun showOrUnlockDevice_authMethodNotSecure_switchesToGoneScene() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene("container1"))
+ val currentScene by
+ collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
utils.authenticationRepository.setUnlocked(false)
- underTest.showOrUnlockDevice("container1")
+ underTest.showOrUnlockDevice(SceneTestUtils.CONTAINER_1)
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
}
@@ -249,15 +266,16 @@
@Test
fun showOrUnlockDevice_customMessageShown() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene("container1"))
+ val currentScene by
+ collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
val message by collectLastValue(underTest.message)
utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Password("password")
+ AuthenticationMethodModel.Password
)
utils.authenticationRepository.setUnlocked(false)
val customMessage = "Hello there!"
- underTest.showOrUnlockDevice("container1", customMessage)
+ underTest.showOrUnlockDevice(SceneTestUtils.CONTAINER_1, customMessage)
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
assertThat(message).isEqualTo(customMessage)
@@ -266,67 +284,78 @@
@Test
fun throttling() =
testScope.runTest {
+ val isThrottled by collectLastValue(underTest.isThrottled)
val throttling by collectLastValue(underTest.throttling)
val message by collectLastValue(underTest.message)
val currentScene by
collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin(1234)
- )
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
runCurrent()
underTest.showOrUnlockDevice(SceneTestUtils.CONTAINER_1)
runCurrent()
assertThat(currentScene?.key).isEqualTo(SceneKey.Bouncer)
- assertThat(throttling).isNull()
+ assertThat(isThrottled).isFalse()
+ assertThat(throttling).isEqualTo(AuthenticationThrottlingModel())
assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PIN)
- repeat(BouncerInteractor.THROTTLE_EVERY) { times ->
+ repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING) { times ->
// Wrong PIN.
assertThat(underTest.authenticate(listOf(6, 7, 8, 9))).isFalse()
- if (times < BouncerInteractor.THROTTLE_EVERY - 1) {
+ if (
+ times < FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING - 1
+ ) {
assertThat(message).isEqualTo(MESSAGE_WRONG_PIN)
}
}
- assertThat(throttling).isNotNull()
- assertTryAgainMessage(message, BouncerInteractor.THROTTLE_DURATION_SEC)
+ assertThat(isThrottled).isTrue()
+ assertThat(throttling)
+ .isEqualTo(
+ AuthenticationThrottlingModel(
+ failedAttemptCount =
+ FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING,
+ remainingMs = FakeAuthenticationRepository.THROTTLE_DURATION_MS,
+ )
+ )
+ assertTryAgainMessage(
+ message,
+ FakeAuthenticationRepository.THROTTLE_DURATION_MS.milliseconds.inWholeSeconds
+ .toInt()
+ )
// Correct PIN, but throttled, so doesn't change away from the bouncer scene:
- assertThat(underTest.authenticate(listOf(1, 2, 3, 4))).isFalse()
+ assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)).isNull()
assertThat(currentScene?.key).isEqualTo(SceneKey.Bouncer)
- assertTryAgainMessage(message, BouncerInteractor.THROTTLE_DURATION_SEC)
+ assertTryAgainMessage(
+ message,
+ FakeAuthenticationRepository.THROTTLE_DURATION_MS.milliseconds.inWholeSeconds
+ .toInt()
+ )
- throttling?.totalDurationSec?.let { seconds ->
+ throttling?.remainingMs?.let { remainingMs ->
+ val seconds = ceil(remainingMs / 1000f).toInt()
repeat(seconds) { time ->
advanceTimeBy(1000)
- val remainingTime = seconds - time - 1
- if (remainingTime > 0) {
- assertTryAgainMessage(message, remainingTime)
+ val remainingTimeSec = seconds - time - 1
+ if (remainingTimeSec > 0) {
+ assertTryAgainMessage(message, remainingTimeSec)
}
}
}
assertThat(message).isEqualTo("")
- assertThat(throttling).isNull()
+ assertThat(isThrottled).isFalse()
+ assertThat(throttling)
+ .isEqualTo(
+ AuthenticationThrottlingModel(
+ failedAttemptCount =
+ FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING,
+ )
+ )
assertThat(currentScene?.key).isEqualTo(SceneKey.Bouncer)
// Correct PIN and no longer throttled so changes to the Gone scene:
- assertThat(underTest.authenticate(listOf(1, 2, 3, 4))).isTrue()
+ assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)).isTrue()
assertThat(currentScene?.key).isEqualTo(SceneKey.Gone)
- }
-
- @Test
- fun switchesToGone_whenUnlocked() =
- testScope.runTest {
- utils.authenticationRepository.setUnlocked(false)
- sceneInteractor.setCurrentScene(
- SceneTestUtils.CONTAINER_1,
- SceneModel(SceneKey.Bouncer)
- )
- val currentScene by
- collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
- assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
-
- utils.authenticationRepository.setUnlocked(true)
-
- assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
+ assertThat(isThrottled).isFalse()
+ assertThat(throttling).isEqualTo(AuthenticationThrottlingModel())
}
private fun assertTryAgainMessage(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt
index f811ce0..2cc9493 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt
@@ -18,6 +18,7 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.scene.SceneTestUtils
@@ -55,17 +56,14 @@
@Test
fun animateFailure() =
testScope.runTest {
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin(1234)
- )
val animateFailure by collectLastValue(underTest.animateFailure)
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
assertThat(animateFailure).isFalse()
// Wrong PIN:
- underTest.onPinButtonClicked(3)
- underTest.onPinButtonClicked(4)
- underTest.onPinButtonClicked(5)
- underTest.onPinButtonClicked(6)
+ FakeAuthenticationRepository.DEFAULT_PIN.drop(2).forEach { digit ->
+ underTest.onPinButtonClicked(digit)
+ }
underTest.onAuthenticateButtonClicked()
assertThat(animateFailure).isTrue()
@@ -73,10 +71,9 @@
assertThat(animateFailure).isFalse()
// Correct PIN:
- underTest.onPinButtonClicked(1)
- underTest.onPinButtonClicked(2)
- underTest.onPinButtonClicked(3)
- underTest.onPinButtonClicked(4)
+ FakeAuthenticationRepository.DEFAULT_PIN.forEach { digit ->
+ underTest.onPinButtonClicked(digit)
+ }
underTest.onAuthenticateButtonClicked()
assertThat(animateFailure).isFalse()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
index 5ffc471..0df0a17 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
@@ -18,8 +18,8 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
-import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.scene.SceneTestUtils
import com.google.common.truth.Truth.assertThat
@@ -52,7 +52,10 @@
authenticationInteractor = authenticationInteractor,
sceneInteractor = utils.sceneInteractor(),
)
- private val underTest = utils.bouncerViewModel(bouncerInteractor)
+ private val underTest =
+ utils.bouncerViewModel(
+ bouncerInteractor = bouncerInteractor,
+ )
@Test
fun authMethod_nonNullForSecureMethods_nullForNotSecureMethods() =
@@ -110,18 +113,16 @@
testScope.runTest {
val message by collectLastValue(underTest.message)
val throttling by collectLastValue(bouncerInteractor.throttling)
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin(1234)
- )
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
assertThat(message?.isUpdateAnimated).isTrue()
- repeat(BouncerInteractor.THROTTLE_EVERY) {
+ repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING) {
// Wrong PIN.
bouncerInteractor.authenticate(listOf(3, 4, 5, 6))
}
assertThat(message?.isUpdateAnimated).isFalse()
- throttling?.totalDurationSec?.let { seconds -> advanceTimeBy(seconds * 1000L) }
+ throttling?.remainingMs?.let { remainingMs -> advanceTimeBy(remainingMs.toLong()) }
assertThat(message?.isUpdateAnimated).isTrue()
}
@@ -135,18 +136,16 @@
}
)
val throttling by collectLastValue(bouncerInteractor.throttling)
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin(1234)
- )
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
assertThat(isInputEnabled).isTrue()
- repeat(BouncerInteractor.THROTTLE_EVERY) {
+ repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING) {
// Wrong PIN.
bouncerInteractor.authenticate(listOf(3, 4, 5, 6))
}
assertThat(isInputEnabled).isFalse()
- throttling?.totalDurationSec?.let { seconds -> advanceTimeBy(seconds * 1000L) }
+ throttling?.remainingMs?.let { milliseconds -> advanceTimeBy(milliseconds.toLong()) }
assertThat(isInputEnabled).isTrue()
}
@@ -154,11 +153,9 @@
fun throttlingDialogMessage() =
testScope.runTest {
val throttlingDialogMessage by collectLastValue(underTest.throttlingDialogMessage)
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin(1234)
- )
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
- repeat(BouncerInteractor.THROTTLE_EVERY) {
+ repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING) {
// Wrong PIN.
assertThat(throttlingDialogMessage).isNull()
bouncerInteractor.authenticate(listOf(3, 4, 5, 6))
@@ -173,11 +170,9 @@
return listOf(
AuthenticationMethodModel.None,
AuthenticationMethodModel.Swipe,
- AuthenticationMethodModel.Pin(1234),
- AuthenticationMethodModel.Password("password"),
- AuthenticationMethodModel.Pattern(
- listOf(AuthenticationMethodModel.Pattern.PatternCoordinate(1, 1))
- ),
+ AuthenticationMethodModel.Pin,
+ AuthenticationMethodModel.Password,
+ AuthenticationMethodModel.Pattern,
)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
index 699571b..b1533fe 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
@@ -72,14 +72,18 @@
@Test
fun onShown() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+ val currentScene by
+ collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
val message by collectLastValue(bouncerViewModel.message)
val password by collectLastValue(underTest.password)
utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Password("password")
+ AuthenticationMethodModel.Password
)
utils.authenticationRepository.setUnlocked(false)
- sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
+ sceneInteractor.setCurrentScene(
+ SceneTestUtils.CONTAINER_1,
+ SceneModel(SceneKey.Bouncer)
+ )
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
@@ -92,14 +96,18 @@
@Test
fun onPasswordInputChanged() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+ val currentScene by
+ collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
val message by collectLastValue(bouncerViewModel.message)
val password by collectLastValue(underTest.password)
utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Password("password")
+ AuthenticationMethodModel.Password
)
utils.authenticationRepository.setUnlocked(false)
- sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
+ sceneInteractor.setCurrentScene(
+ SceneTestUtils.CONTAINER_1,
+ SceneModel(SceneKey.Bouncer)
+ )
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
runCurrent()
@@ -114,12 +122,16 @@
@Test
fun onAuthenticateKeyPressed_whenCorrect() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+ val currentScene by
+ collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Password("password")
+ AuthenticationMethodModel.Password
)
utils.authenticationRepository.setUnlocked(false)
- sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
+ sceneInteractor.setCurrentScene(
+ SceneTestUtils.CONTAINER_1,
+ SceneModel(SceneKey.Bouncer)
+ )
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
underTest.onPasswordInputChanged("password")
@@ -132,14 +144,18 @@
@Test
fun onAuthenticateKeyPressed_whenWrong() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+ val currentScene by
+ collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
val message by collectLastValue(bouncerViewModel.message)
val password by collectLastValue(underTest.password)
utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Password("password")
+ AuthenticationMethodModel.Password
)
utils.authenticationRepository.setUnlocked(false)
- sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
+ sceneInteractor.setCurrentScene(
+ SceneTestUtils.CONTAINER_1,
+ SceneModel(SceneKey.Bouncer)
+ )
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
underTest.onPasswordInputChanged("wrong")
@@ -154,14 +170,18 @@
@Test
fun onAuthenticateKeyPressed_correctAfterWrong() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+ val currentScene by
+ collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
val message by collectLastValue(bouncerViewModel.message)
val password by collectLastValue(underTest.password)
utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Password("password")
+ AuthenticationMethodModel.Password
)
utils.authenticationRepository.setUnlocked(false)
- sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
+ sceneInteractor.setCurrentScene(
+ SceneTestUtils.CONTAINER_1,
+ SceneModel(SceneKey.Bouncer)
+ )
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
underTest.onPasswordInputChanged("wrong")
@@ -180,7 +200,6 @@
}
companion object {
- private const val CONTAINER_NAME = "container1"
private const val ENTER_YOUR_PASSWORD = "Enter your password"
private const val WRONG_PASSWORD = "Wrong password"
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
index 9a1f584..f69cbb8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
@@ -19,6 +19,7 @@
import androidx.test.filters.SmallTest
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.scene.SceneTestUtils
@@ -74,15 +75,19 @@
@Test
fun onShown() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+ val currentScene by
+ collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
val message by collectLastValue(bouncerViewModel.message)
val selectedDots by collectLastValue(underTest.selectedDots)
val currentDot by collectLastValue(underTest.currentDot)
utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pattern(CORRECT_PATTERN)
+ AuthenticationMethodModel.Pattern
)
utils.authenticationRepository.setUnlocked(false)
- sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
+ sceneInteractor.setCurrentScene(
+ SceneTestUtils.CONTAINER_1,
+ SceneModel(SceneKey.Bouncer)
+ )
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
@@ -96,15 +101,19 @@
@Test
fun onDragStart() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+ val currentScene by
+ collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
val message by collectLastValue(bouncerViewModel.message)
val selectedDots by collectLastValue(underTest.selectedDots)
val currentDot by collectLastValue(underTest.currentDot)
utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pattern(CORRECT_PATTERN)
+ AuthenticationMethodModel.Pattern
)
utils.authenticationRepository.setUnlocked(false)
- sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
+ sceneInteractor.setCurrentScene(
+ SceneTestUtils.CONTAINER_1,
+ SceneModel(SceneKey.Bouncer)
+ )
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
runCurrent()
@@ -120,14 +129,18 @@
@Test
fun onDragEnd_whenCorrect() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+ val currentScene by
+ collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
val selectedDots by collectLastValue(underTest.selectedDots)
val currentDot by collectLastValue(underTest.currentDot)
utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pattern(CORRECT_PATTERN)
+ AuthenticationMethodModel.Pattern
)
utils.authenticationRepository.setUnlocked(false)
- sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
+ sceneInteractor.setCurrentScene(
+ SceneTestUtils.CONTAINER_1,
+ SceneModel(SceneKey.Bouncer)
+ )
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
underTest.onDragStart()
@@ -167,15 +180,19 @@
@Test
fun onDragEnd_whenWrong() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+ val currentScene by
+ collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
val message by collectLastValue(bouncerViewModel.message)
val selectedDots by collectLastValue(underTest.selectedDots)
val currentDot by collectLastValue(underTest.currentDot)
utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pattern(CORRECT_PATTERN)
+ AuthenticationMethodModel.Pattern
)
utils.authenticationRepository.setUnlocked(false)
- sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
+ sceneInteractor.setCurrentScene(
+ SceneTestUtils.CONTAINER_1,
+ SceneModel(SceneKey.Bouncer)
+ )
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
underTest.onDragStart()
@@ -199,15 +216,19 @@
@Test
fun onDragEnd_correctAfterWrong() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+ val currentScene by
+ collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
val message by collectLastValue(bouncerViewModel.message)
val selectedDots by collectLastValue(underTest.selectedDots)
val currentDot by collectLastValue(underTest.currentDot)
utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pattern(CORRECT_PATTERN)
+ AuthenticationMethodModel.Pattern
)
utils.authenticationRepository.setUnlocked(false)
- sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
+ sceneInteractor.setCurrentScene(
+ SceneTestUtils.CONTAINER_1,
+ SceneModel(SceneKey.Bouncer)
+ )
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
underTest.onDragStart()
@@ -241,20 +262,8 @@
}
companion object {
- private const val CONTAINER_NAME = "container1"
private const val ENTER_YOUR_PATTERN = "Enter your pattern"
private const val WRONG_PATTERN = "Wrong pattern"
- private val CORRECT_PATTERN =
- listOf(
- AuthenticationMethodModel.Pattern.PatternCoordinate(x = 1, y = 1),
- AuthenticationMethodModel.Pattern.PatternCoordinate(x = 0, y = 1),
- AuthenticationMethodModel.Pattern.PatternCoordinate(x = 0, y = 0),
- AuthenticationMethodModel.Pattern.PatternCoordinate(x = 1, y = 0),
- AuthenticationMethodModel.Pattern.PatternCoordinate(x = 2, y = 0),
- AuthenticationMethodModel.Pattern.PatternCoordinate(x = 2, y = 1),
- AuthenticationMethodModel.Pattern.PatternCoordinate(x = 2, y = 2),
- AuthenticationMethodModel.Pattern.PatternCoordinate(x = 1, y = 2),
- AuthenticationMethodModel.Pattern.PatternCoordinate(x = 0, y = 2),
- )
+ private val CORRECT_PATTERN = FakeAuthenticationRepository.PATTERN
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
index 608a187..45d1af7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
@@ -19,8 +19,8 @@
import androidx.test.filters.SmallTest
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
-import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.scene.SceneTestUtils
import com.android.systemui.scene.shared.model.SceneKey
@@ -54,16 +54,8 @@
sceneInteractor = sceneInteractor,
)
private val bouncerViewModel =
- BouncerViewModel(
- applicationContext = context,
- applicationScope = testScope.backgroundScope,
- interactorFactory =
- object : BouncerInteractor.Factory {
- override fun create(containerName: String): BouncerInteractor {
- return bouncerInteractor
- }
- },
- containerName = CONTAINER_NAME,
+ utils.bouncerViewModel(
+ bouncerInteractor = bouncerInteractor,
)
private val underTest =
PinBouncerViewModel(
@@ -82,11 +74,15 @@
@Test
fun onShown() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+ val currentScene by
+ collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
val message by collectLastValue(bouncerViewModel.message)
val entries by collectLastValue(underTest.pinEntries)
utils.authenticationRepository.setUnlocked(false)
- sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
+ sceneInteractor.setCurrentScene(
+ SceneTestUtils.CONTAINER_1,
+ SceneModel(SceneKey.Bouncer)
+ )
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
@@ -99,14 +95,16 @@
@Test
fun onPinButtonClicked() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+ val currentScene by
+ collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
val message by collectLastValue(bouncerViewModel.message)
val entries by collectLastValue(underTest.pinEntries)
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin(1234)
- )
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
utils.authenticationRepository.setUnlocked(false)
- sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
+ sceneInteractor.setCurrentScene(
+ SceneTestUtils.CONTAINER_1,
+ SceneModel(SceneKey.Bouncer)
+ )
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
runCurrent()
@@ -122,14 +120,16 @@
@Test
fun onBackspaceButtonClicked() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+ val currentScene by
+ collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
val message by collectLastValue(bouncerViewModel.message)
val entries by collectLastValue(underTest.pinEntries)
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin(1234)
- )
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
utils.authenticationRepository.setUnlocked(false)
- sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
+ sceneInteractor.setCurrentScene(
+ SceneTestUtils.CONTAINER_1,
+ SceneModel(SceneKey.Bouncer)
+ )
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
runCurrent()
@@ -146,13 +146,15 @@
@Test
fun onPinEdit() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+ val currentScene by
+ collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
val entries by collectLastValue(underTest.pinEntries)
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin(1234)
- )
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
utils.authenticationRepository.setUnlocked(false)
- sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
+ sceneInteractor.setCurrentScene(
+ SceneTestUtils.CONTAINER_1,
+ SceneModel(SceneKey.Bouncer)
+ )
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
@@ -172,14 +174,16 @@
@Test
fun onBackspaceButtonLongPressed() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+ val currentScene by
+ collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
val message by collectLastValue(bouncerViewModel.message)
val entries by collectLastValue(underTest.pinEntries)
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin(1234)
- )
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
utils.authenticationRepository.setUnlocked(false)
- sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
+ sceneInteractor.setCurrentScene(
+ SceneTestUtils.CONTAINER_1,
+ SceneModel(SceneKey.Bouncer)
+ )
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
runCurrent()
@@ -198,18 +202,19 @@
@Test
fun onAuthenticateButtonClicked_whenCorrect() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin(1234)
- )
+ val currentScene by
+ collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
utils.authenticationRepository.setUnlocked(false)
- sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
+ sceneInteractor.setCurrentScene(
+ SceneTestUtils.CONTAINER_1,
+ SceneModel(SceneKey.Bouncer)
+ )
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
- underTest.onPinButtonClicked(1)
- underTest.onPinButtonClicked(2)
- underTest.onPinButtonClicked(3)
- underTest.onPinButtonClicked(4)
+ FakeAuthenticationRepository.DEFAULT_PIN.forEach { digit ->
+ underTest.onPinButtonClicked(digit)
+ }
underTest.onAuthenticateButtonClicked()
@@ -219,14 +224,16 @@
@Test
fun onAuthenticateButtonClicked_whenWrong() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+ val currentScene by
+ collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
val message by collectLastValue(bouncerViewModel.message)
val entries by collectLastValue(underTest.pinEntries)
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin(1234)
- )
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
utils.authenticationRepository.setUnlocked(false)
- sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
+ sceneInteractor.setCurrentScene(
+ SceneTestUtils.CONTAINER_1,
+ SceneModel(SceneKey.Bouncer)
+ )
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
underTest.onPinButtonClicked(1)
@@ -245,14 +252,16 @@
@Test
fun onAuthenticateButtonClicked_correctAfterWrong() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+ val currentScene by
+ collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
val message by collectLastValue(bouncerViewModel.message)
val entries by collectLastValue(underTest.pinEntries)
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin(1234)
- )
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
utils.authenticationRepository.setUnlocked(false)
- sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
+ sceneInteractor.setCurrentScene(
+ SceneTestUtils.CONTAINER_1,
+ SceneModel(SceneKey.Bouncer)
+ )
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
underTest.onPinButtonClicked(1)
@@ -266,10 +275,9 @@
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
// Enter the correct PIN:
- underTest.onPinButtonClicked(1)
- underTest.onPinButtonClicked(2)
- underTest.onPinButtonClicked(3)
- underTest.onPinButtonClicked(4)
+ FakeAuthenticationRepository.DEFAULT_PIN.forEach { digit ->
+ underTest.onPinButtonClicked(digit)
+ }
assertThat(message?.text).isEmpty()
underTest.onAuthenticateButtonClicked()
@@ -280,18 +288,20 @@
@Test
fun onAutoConfirm_whenCorrect() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin(1234, autoConfirm = true)
- )
+ val currentScene by
+ collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
utils.authenticationRepository.setUnlocked(false)
- sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
+ utils.authenticationRepository.setAutoConfirmEnabled(true)
+ sceneInteractor.setCurrentScene(
+ SceneTestUtils.CONTAINER_1,
+ SceneModel(SceneKey.Bouncer)
+ )
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
- underTest.onPinButtonClicked(1)
- underTest.onPinButtonClicked(2)
- underTest.onPinButtonClicked(3)
- underTest.onPinButtonClicked(4)
+ FakeAuthenticationRepository.DEFAULT_PIN.forEach { digit ->
+ underTest.onPinButtonClicked(digit)
+ }
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
}
@@ -299,20 +309,25 @@
@Test
fun onAutoConfirm_whenWrong() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+ val currentScene by
+ collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
val message by collectLastValue(bouncerViewModel.message)
val entries by collectLastValue(underTest.pinEntries)
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin(1234, autoConfirm = true)
- )
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
utils.authenticationRepository.setUnlocked(false)
- sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
+ utils.authenticationRepository.setAutoConfirmEnabled(true)
+ sceneInteractor.setCurrentScene(
+ SceneTestUtils.CONTAINER_1,
+ SceneModel(SceneKey.Bouncer)
+ )
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
- underTest.onPinButtonClicked(1)
- underTest.onPinButtonClicked(2)
- underTest.onPinButtonClicked(3)
- underTest.onPinButtonClicked(5) // PIN is now wrong!
+ FakeAuthenticationRepository.DEFAULT_PIN.dropLast(1).forEach { digit ->
+ underTest.onPinButtonClicked(digit)
+ }
+ underTest.onPinButtonClicked(
+ FakeAuthenticationRepository.DEFAULT_PIN.last() + 1
+ ) // PIN is now wrong!
assertThat(entries).hasSize(0)
assertThat(message?.text).isEqualTo(WRONG_PIN)
@@ -324,9 +339,7 @@
testScope.runTest {
val backspaceButtonAppearance by collectLastValue(underTest.backspaceButtonAppearance)
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin(1234, autoConfirm = false)
- )
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
assertThat(backspaceButtonAppearance).isEqualTo(ActionButtonAppearance.Shown)
}
@@ -335,9 +348,8 @@
fun backspaceButtonAppearance_withAutoConfirmButNoInput_isHidden() =
testScope.runTest {
val backspaceButtonAppearance by collectLastValue(underTest.backspaceButtonAppearance)
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin(1234, autoConfirm = true)
- )
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+ utils.authenticationRepository.setAutoConfirmEnabled(true)
assertThat(backspaceButtonAppearance).isEqualTo(ActionButtonAppearance.Hidden)
}
@@ -346,9 +358,8 @@
fun backspaceButtonAppearance_withAutoConfirmAndInput_isShownQuiet() =
testScope.runTest {
val backspaceButtonAppearance by collectLastValue(underTest.backspaceButtonAppearance)
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin(1234, autoConfirm = true)
- )
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+ utils.authenticationRepository.setAutoConfirmEnabled(true)
underTest.onPinButtonClicked(1)
@@ -360,9 +371,7 @@
testScope.runTest {
val confirmButtonAppearance by collectLastValue(underTest.confirmButtonAppearance)
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin(1234, autoConfirm = false)
- )
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
assertThat(confirmButtonAppearance).isEqualTo(ActionButtonAppearance.Shown)
}
@@ -371,59 +380,13 @@
fun confirmButtonAppearance_withAutoConfirm_isHidden() =
testScope.runTest {
val confirmButtonAppearance by collectLastValue(underTest.confirmButtonAppearance)
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin(1234, autoConfirm = true)
- )
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+ utils.authenticationRepository.setAutoConfirmEnabled(true)
assertThat(confirmButtonAppearance).isEqualTo(ActionButtonAppearance.Hidden)
}
- @Test
- fun hintedPinLength_withoutAutoConfirm_isNull() =
- testScope.runTest {
- val hintedPinLength by collectLastValue(underTest.hintedPinLength)
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin(1234, autoConfirm = false)
- )
-
- assertThat(hintedPinLength).isNull()
- }
-
- @Test
- fun hintedPinLength_withAutoConfirmPinLessThanSixDigits_isNull() =
- testScope.runTest {
- val hintedPinLength by collectLastValue(underTest.hintedPinLength)
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin(12345, autoConfirm = true)
- )
-
- assertThat(hintedPinLength).isNull()
- }
-
- @Test
- fun hintedPinLength_withAutoConfirmPinExactlySixDigits_isSix() =
- testScope.runTest {
- val hintedPinLength by collectLastValue(underTest.hintedPinLength)
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin(123456, autoConfirm = true)
- )
-
- assertThat(hintedPinLength).isEqualTo(6)
- }
-
- @Test
- fun hintedPinLength_withAutoConfirmPinMoreThanSixDigits_isNull() =
- testScope.runTest {
- val hintedPinLength by collectLastValue(underTest.hintedPinLength)
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin(1234567, autoConfirm = true)
- )
-
- assertThat(hintedPinLength).isNull()
- }
-
companion object {
- private const val CONTAINER_NAME = "container1"
private const val ENTER_YOUR_PIN = "Enter your pin"
private const val WRONG_PIN = "Wrong pin"
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index ddcbe79..00f67a3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -78,6 +78,7 @@
import com.android.keyguard.KeyguardSecurityView;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.keyguard.TestScopeProvider;
import com.android.keyguard.mediator.ScreenOnCoordinator;
import com.android.systemui.DejankUtils;
import com.android.systemui.SysuiTestCase;
@@ -115,9 +116,11 @@
import com.android.systemui.util.DeviceConfigProxy;
import com.android.systemui.util.DeviceConfigProxyFake;
import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.kotlin.JavaAdapter;
import com.android.systemui.util.settings.SecureSettings;
import com.android.systemui.util.settings.SystemSettings;
import com.android.systemui.util.time.FakeSystemClock;
+import com.android.systemui.wallpapers.data.repository.FakeWallpaperRepository;
import com.android.wm.shell.keyguard.KeyguardTransitions;
import org.junit.After;
@@ -132,6 +135,7 @@
import kotlinx.coroutines.CoroutineDispatcher;
import kotlinx.coroutines.flow.Flow;
+import kotlinx.coroutines.test.TestScope;
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
@@ -139,6 +143,9 @@
public class KeyguardViewMediatorTest extends SysuiTestCase {
private KeyguardViewMediator mViewMediator;
+ private final TestScope mTestScope = TestScopeProvider.getTestScope();
+ private final JavaAdapter mJavaAdapter = new JavaAdapter(mTestScope.getBackgroundScope());
+
private @Mock UserTracker mUserTracker;
private @Mock DevicePolicyManager mDevicePolicyManager;
private @Mock LockPatternUtils mLockPatternUtils;
@@ -191,6 +198,7 @@
private @Mock SecureSettings mSecureSettings;
private @Mock AlarmManager mAlarmManager;
private FakeSystemClock mSystemClock;
+ private final FakeWallpaperRepository mWallpaperRepository = new FakeWallpaperRepository();
/** Most recent value passed to {@link KeyguardStateController#notifyKeyguardGoingAway}. */
private boolean mKeyguardGoingAway = false;
@@ -928,6 +936,8 @@
mKeyguardTransitions,
mInteractionJankMonitor,
mDreamOverlayStateController,
+ mJavaAdapter,
+ mWallpaperRepository,
() -> mShadeController,
() -> mNotificationShadeWindowController,
() -> mActivityLaunchAnimator,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
index 020c0b2..47c662c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
@@ -36,6 +36,7 @@
import com.android.keyguard.FaceAuthUiEvent
import com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_TRIGGERED_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN
import com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER
+import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.R
import com.android.systemui.RoboPilotTest
import com.android.systemui.SysuiTestCase
@@ -50,12 +51,12 @@
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
-import com.android.systemui.keyguard.shared.model.AuthenticationStatus
-import com.android.systemui.keyguard.shared.model.DetectionStatus
-import com.android.systemui.keyguard.shared.model.ErrorAuthenticationStatus
-import com.android.systemui.keyguard.shared.model.HelpAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.ErrorFaceAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.FaceAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.FaceDetectionStatus
+import com.android.systemui.keyguard.shared.model.HelpFaceAuthenticationStatus
import com.android.systemui.keyguard.shared.model.KeyguardState
-import com.android.systemui.keyguard.shared.model.SuccessAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.SuccessFaceAuthenticationStatus
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.keyguard.shared.model.WakeSleepReason
@@ -113,6 +114,7 @@
@Mock private lateinit var sessionTracker: SessionTracker
@Mock private lateinit var uiEventLogger: UiEventLogger
@Mock private lateinit var dumpManager: DumpManager
+ @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
@Captor
private lateinit var authenticationCallback: ArgumentCaptor<FaceManager.AuthenticationCallback>
@@ -131,8 +133,8 @@
private lateinit var keyguardTransitionRepository: FakeKeyguardTransitionRepository
private lateinit var testScope: TestScope
private lateinit var fakeUserRepository: FakeUserRepository
- private lateinit var authStatus: FlowValue<AuthenticationStatus?>
- private lateinit var detectStatus: FlowValue<DetectionStatus?>
+ private lateinit var authStatus: FlowValue<FaceAuthenticationStatus?>
+ private lateinit var detectStatus: FlowValue<FaceDetectionStatus?>
private lateinit var authRunning: FlowValue<Boolean?>
private lateinit var bypassEnabled: FlowValue<Boolean?>
private lateinit var lockedOut: FlowValue<Boolean?>
@@ -175,10 +177,10 @@
AlternateBouncerInteractor(
bouncerRepository = bouncerRepository,
biometricSettingsRepository = biometricSettingsRepository,
- deviceEntryFingerprintAuthRepository = deviceEntryFingerprintAuthRepository,
systemClock = mock(SystemClock::class.java),
keyguardStateController = FakeKeyguardStateController(),
statusBarStateController = mock(StatusBarStateController::class.java),
+ keyguardUpdateMonitor = keyguardUpdateMonitor,
)
bypassStateChangedListener =
@@ -262,7 +264,7 @@
val successResult = successResult()
authenticationCallback.value.onAuthenticationSucceeded(successResult)
- assertThat(authStatus()).isEqualTo(SuccessAuthenticationStatus(successResult))
+ assertThat(authStatus()).isEqualTo(SuccessFaceAuthenticationStatus(successResult))
assertThat(authenticated()).isTrue()
assertThat(authRunning()).isFalse()
assertThat(canFaceAuthRun()).isFalse()
@@ -383,7 +385,7 @@
detectionCallback.value.onFaceDetected(1, 1, true)
- assertThat(detectStatus()).isEqualTo(DetectionStatus(1, 1, true))
+ assertThat(detectStatus()).isEqualTo(FaceDetectionStatus(1, 1, true))
}
@Test
@@ -423,7 +425,7 @@
FACE_ERROR_CANCELED,
"First auth attempt cancellation completed"
)
- val value = authStatus() as ErrorAuthenticationStatus
+ val value = authStatus() as ErrorFaceAuthenticationStatus
assertThat(value.msgId).isEqualTo(FACE_ERROR_CANCELED)
assertThat(value.msg).isEqualTo("First auth attempt cancellation completed")
@@ -465,7 +467,7 @@
authenticationCallback.value.onAuthenticationHelp(10, "Ignored help msg")
authenticationCallback.value.onAuthenticationHelp(11, "Ignored help msg")
- assertThat(authStatus()).isEqualTo(HelpAuthenticationStatus(9, "help msg"))
+ assertThat(authStatus()).isEqualTo(HelpFaceAuthenticationStatus(9, "help msg"))
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt
index 264328b..a73b57c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt
@@ -26,7 +26,11 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.AuthController
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.dump.DumpManager
+import com.android.systemui.keyguard.shared.model.AcquiredFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.ErrorFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.FailedFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.HelpFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -49,7 +53,6 @@
@RunWith(AndroidJUnit4::class)
class DeviceEntryFingerprintAuthRepositoryTest : SysuiTestCase() {
@Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
- @Mock private lateinit var dumpManager: DumpManager
@Mock private lateinit var authController: AuthController
@Captor
private lateinit var updateMonitorCallback: ArgumentCaptor<KeyguardUpdateMonitorCallback>
@@ -68,7 +71,6 @@
authController,
keyguardUpdateMonitor,
testScope.backgroundScope,
- dumpManager,
)
}
@@ -177,4 +179,129 @@
callback.value.onAllAuthenticatorsRegistered(TYPE_FINGERPRINT)
assertThat(availableFpSensorType()).isEqualTo(BiometricType.UNDER_DISPLAY_FINGERPRINT)
}
+
+ @Test
+ fun onFingerprintSuccess_successAuthenticationStatus() =
+ testScope.runTest {
+ val authenticationStatus by collectLastValue(underTest.authenticationStatus)
+ runCurrent()
+
+ verify(keyguardUpdateMonitor).registerCallback(updateMonitorCallback.capture())
+ updateMonitorCallback.value.onBiometricAuthenticated(
+ 0,
+ BiometricSourceType.FINGERPRINT,
+ true,
+ )
+
+ val status = authenticationStatus as SuccessFingerprintAuthenticationStatus
+ assertThat(status.userId).isEqualTo(0)
+ assertThat(status.isStrongBiometric).isEqualTo(true)
+ }
+
+ @Test
+ fun onFingerprintFailed_failedAuthenticationStatus() =
+ testScope.runTest {
+ val authenticationStatus by collectLastValue(underTest.authenticationStatus)
+ runCurrent()
+
+ verify(keyguardUpdateMonitor).registerCallback(updateMonitorCallback.capture())
+ updateMonitorCallback.value.onBiometricAuthFailed(
+ BiometricSourceType.FINGERPRINT,
+ )
+
+ assertThat(authenticationStatus)
+ .isInstanceOf(FailedFingerprintAuthenticationStatus::class.java)
+ }
+
+ @Test
+ fun onFingerprintError_errorAuthenticationStatus() =
+ testScope.runTest {
+ val authenticationStatus by collectLastValue(underTest.authenticationStatus)
+ runCurrent()
+
+ verify(keyguardUpdateMonitor).registerCallback(updateMonitorCallback.capture())
+ updateMonitorCallback.value.onBiometricError(
+ 1,
+ "test_string",
+ BiometricSourceType.FINGERPRINT,
+ )
+
+ val status = authenticationStatus as ErrorFingerprintAuthenticationStatus
+ assertThat(status.msgId).isEqualTo(1)
+ assertThat(status.msg).isEqualTo("test_string")
+ }
+
+ @Test
+ fun onFingerprintHelp_helpAuthenticationStatus() =
+ testScope.runTest {
+ val authenticationStatus by collectLastValue(underTest.authenticationStatus)
+ runCurrent()
+
+ verify(keyguardUpdateMonitor).registerCallback(updateMonitorCallback.capture())
+ updateMonitorCallback.value.onBiometricHelp(
+ 1,
+ "test_string",
+ BiometricSourceType.FINGERPRINT,
+ )
+
+ val status = authenticationStatus as HelpFingerprintAuthenticationStatus
+ assertThat(status.msgId).isEqualTo(1)
+ assertThat(status.msg).isEqualTo("test_string")
+ }
+
+ @Test
+ fun onFingerprintAcquired_acquiredAuthenticationStatus() =
+ testScope.runTest {
+ val authenticationStatus by collectLastValue(underTest.authenticationStatus)
+ runCurrent()
+
+ verify(keyguardUpdateMonitor).registerCallback(updateMonitorCallback.capture())
+ updateMonitorCallback.value.onBiometricAcquired(
+ BiometricSourceType.FINGERPRINT,
+ 5,
+ )
+
+ val status = authenticationStatus as AcquiredFingerprintAuthenticationStatus
+ assertThat(status.acquiredInfo).isEqualTo(5)
+ }
+
+ @Test
+ fun onFaceCallbacks_fingerprintAuthenticationStatusIsUnchanged() =
+ testScope.runTest {
+ val authenticationStatus by collectLastValue(underTest.authenticationStatus)
+ runCurrent()
+
+ verify(keyguardUpdateMonitor).registerCallback(updateMonitorCallback.capture())
+ updateMonitorCallback.value.onBiometricAuthenticated(
+ 0,
+ BiometricSourceType.FACE,
+ true,
+ )
+ assertThat(authenticationStatus).isNull()
+
+ updateMonitorCallback.value.onBiometricAuthFailed(
+ BiometricSourceType.FACE,
+ )
+ assertThat(authenticationStatus).isNull()
+
+ updateMonitorCallback.value.onBiometricHelp(
+ 1,
+ "test_string",
+ BiometricSourceType.FACE,
+ )
+ assertThat(authenticationStatus).isNull()
+
+ updateMonitorCallback.value.onBiometricAcquired(
+ BiometricSourceType.FACE,
+ 5,
+ )
+ assertThat(authenticationStatus).isNull()
+
+ updateMonitorCallback.value.onBiometricError(
+ 1,
+ "test_string",
+ BiometricSourceType.FACE,
+ )
+ assertThat(authenticationStatus).isNull()
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
index e9f0d56..ba7d349 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
@@ -31,16 +31,20 @@
import com.android.systemui.doze.DozeTransitionCallback
import com.android.systemui.doze.DozeTransitionListener
import com.android.systemui.dreams.DreamOverlayCallbackController
+import com.android.systemui.keyguard.ScreenLifecycle
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
import com.android.systemui.keyguard.shared.model.DozeStateModel
import com.android.systemui.keyguard.shared.model.DozeTransitionModel
+import com.android.systemui.keyguard.shared.model.ScreenModel
+import com.android.systemui.keyguard.shared.model.ScreenState
import com.android.systemui.keyguard.shared.model.WakefulnessModel
import com.android.systemui.keyguard.shared.model.WakefulnessState
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.phone.BiometricUnlockController
import com.android.systemui.statusbar.phone.DozeParameters
+import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.whenever
@@ -70,9 +74,11 @@
@Mock private lateinit var statusBarStateController: StatusBarStateController
@Mock private lateinit var keyguardStateController: KeyguardStateController
@Mock private lateinit var wakefulnessLifecycle: WakefulnessLifecycle
+ @Mock private lateinit var screenLifecycle: ScreenLifecycle
@Mock private lateinit var biometricUnlockController: BiometricUnlockController
@Mock private lateinit var dozeTransitionListener: DozeTransitionListener
@Mock private lateinit var authController: AuthController
+ @Mock private lateinit var keyguardBypassController: KeyguardBypassController
@Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
@Mock private lateinit var dreamOverlayCallbackController: DreamOverlayCallbackController
@Mock private lateinit var dozeParameters: DozeParameters
@@ -90,8 +96,10 @@
KeyguardRepositoryImpl(
statusBarStateController,
wakefulnessLifecycle,
+ screenLifecycle,
biometricUnlockController,
keyguardStateController,
+ keyguardBypassController,
keyguardUpdateMonitor,
dozeTransitionListener,
dozeParameters,
@@ -157,6 +165,16 @@
}
@Test
+ fun dozeTimeTick() =
+ testScope.runTest {
+ var dozeTimeTickValue = collectLastValue(underTest.dozeTimeTick)
+ underTest.dozeTimeTick()
+ runCurrent()
+
+ assertThat(dozeTimeTickValue()).isNull()
+ }
+
+ @Test
fun isKeyguardShowing() =
testScope.runTest {
whenever(keyguardStateController.isShowing).thenReturn(false)
@@ -186,6 +204,20 @@
}
@Test
+ fun isBypassEnabled_disabledInController() {
+ whenever(keyguardBypassController.isBypassEnabled).thenReturn(false)
+ whenever(keyguardBypassController.bypassEnabled).thenReturn(false)
+ assertThat(underTest.isBypassEnabled()).isFalse()
+ }
+
+ @Test
+ fun isBypassEnabled_enabledInController() {
+ whenever(keyguardBypassController.isBypassEnabled).thenReturn(true)
+ whenever(keyguardBypassController.bypassEnabled).thenReturn(true)
+ assertThat(underTest.isBypassEnabled()).isTrue()
+ }
+
+ @Test
fun isAodAvailable() = runTest {
val flow = underTest.isAodAvailable
var isAodAvailable = collectLastValue(flow)
@@ -354,6 +386,48 @@
}
@Test
+ fun screenModel() =
+ testScope.runTest {
+ val values = mutableListOf<ScreenModel>()
+ val job = underTest.screenModel.onEach(values::add).launchIn(this)
+
+ runCurrent()
+ val captor = argumentCaptor<ScreenLifecycle.Observer>()
+ verify(screenLifecycle).addObserver(captor.capture())
+
+ whenever(screenLifecycle.getScreenState()).thenReturn(ScreenLifecycle.SCREEN_TURNING_ON)
+ captor.value.onScreenTurningOn()
+ runCurrent()
+
+ whenever(screenLifecycle.getScreenState()).thenReturn(ScreenLifecycle.SCREEN_ON)
+ captor.value.onScreenTurnedOn()
+ runCurrent()
+
+ whenever(screenLifecycle.getScreenState())
+ .thenReturn(ScreenLifecycle.SCREEN_TURNING_OFF)
+ captor.value.onScreenTurningOff()
+ runCurrent()
+
+ whenever(screenLifecycle.getScreenState()).thenReturn(ScreenLifecycle.SCREEN_OFF)
+ captor.value.onScreenTurnedOff()
+ runCurrent()
+
+ assertThat(values.map { it.state })
+ .isEqualTo(
+ listOf(
+ // Initial value will be OFF
+ ScreenState.SCREEN_OFF,
+ ScreenState.SCREEN_TURNING_ON,
+ ScreenState.SCREEN_ON,
+ ScreenState.SCREEN_TURNING_OFF,
+ ScreenState.SCREEN_OFF,
+ )
+ )
+
+ job.cancel()
+ }
+
+ @Test
fun isUdfpsSupported() =
testScope.runTest {
whenever(keyguardUpdateMonitor.isUdfpsSupported).thenReturn(true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt
index 3e81cd3..ced0a21 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt
@@ -38,10 +38,9 @@
import com.android.systemui.keyguard.DismissCallbackRegistry
import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFaceAuthRepository
-import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.FakeTrustRepository
-import com.android.systemui.keyguard.shared.model.ErrorAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.ErrorFaceAuthenticationStatus
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
@@ -121,8 +120,8 @@
mock(KeyguardStateController::class.java),
bouncerRepository,
mock(BiometricSettingsRepository::class.java),
- FakeDeviceEntryFingerprintAuthRepository(),
FakeSystemClock(),
+ keyguardUpdateMonitor,
),
keyguardTransitionInteractor,
featureFlags,
@@ -160,7 +159,7 @@
underTest.onDeviceLifted()
- val outputValue = authenticationStatus()!! as ErrorAuthenticationStatus
+ val outputValue = authenticationStatus()!! as ErrorFaceAuthenticationStatus
assertThat(outputValue.msgId)
.isEqualTo(BiometricFaceConstants.FACE_ERROR_LOCKOUT_PERMANENT)
assertThat(outputValue.msg).isEqualTo("Face Unlock unavailable")
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LockscreenSceneInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LockscreenSceneInteractorTest.kt
index abbdc3d..ca6a5b6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LockscreenSceneInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LockscreenSceneInteractorTest.kt
@@ -47,7 +47,6 @@
private val underTest =
utils.lockScreenSceneInteractor(
authenticationInteractor = authenticationInteractor,
- sceneInteractor = sceneInteractor,
bouncerInteractor =
utils.bouncerInteractor(
authenticationInteractor = authenticationInteractor,
@@ -94,9 +93,7 @@
testScope.runTest {
val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
utils.authenticationRepository.setUnlocked(false)
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin(1234)
- )
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
underTest.dismissLockscreen()
@@ -109,9 +106,7 @@
testScope.runTest {
val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
utils.authenticationRepository.setUnlocked(true)
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin(1234)
- )
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
underTest.dismissLockscreen()
@@ -133,29 +128,11 @@
}
@Test
- fun deviceLockedInNonLockScreenScene_switchesToLockScreenScene() =
- testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
- runCurrent()
- sceneInteractor.setCurrentScene(CONTAINER_1, SceneModel(SceneKey.Gone))
- runCurrent()
- utils.authenticationRepository.setUnlocked(true)
- runCurrent()
- assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
-
- utils.authenticationRepository.setUnlocked(false)
-
- assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
- }
-
- @Test
fun switchFromLockScreenToGone_authMethodNotSwipe_doesNotUnlockDevice() =
testScope.runTest {
val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
sceneInteractor.setCurrentScene(CONTAINER_1, SceneModel(SceneKey.Lockscreen))
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin(1234)
- )
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
assertThat(isUnlocked).isFalse()
sceneInteractor.setCurrentScene(CONTAINER_1, SceneModel(SceneKey.Gone))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
index ff4ec4b..ba8e0f2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
@@ -56,7 +56,6 @@
override fun create(containerName: String): LockscreenSceneInteractor {
return utils.lockScreenSceneInteractor(
authenticationInteractor = authenticationInteractor,
- sceneInteractor = sceneInteractor,
bouncerInteractor =
utils.bouncerInteractor(
authenticationInteractor = authenticationInteractor,
@@ -73,7 +72,7 @@
testScope.runTest {
val lockButtonIcon by collectLastValue(underTest.lockButtonIcon)
utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Password("password")
+ AuthenticationMethodModel.Password
)
utils.authenticationRepository.setUnlocked(false)
@@ -86,7 +85,7 @@
testScope.runTest {
val lockButtonIcon by collectLastValue(underTest.lockButtonIcon)
utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Password("password")
+ AuthenticationMethodModel.Password
)
utils.authenticationRepository.setUnlocked(true)
@@ -108,9 +107,7 @@
fun upTransitionSceneKey_swipeToUnlockedNotEnabled_bouncer() =
testScope.runTest {
val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin(1234)
- )
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
utils.authenticationRepository.setUnlocked(false)
assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Bouncer)
@@ -120,9 +117,7 @@
fun onLockButtonClicked_deviceLockedSecurely_switchesToBouncer() =
testScope.runTest {
val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin(1234)
- )
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
utils.authenticationRepository.setUnlocked(false)
runCurrent()
@@ -135,9 +130,7 @@
fun onContentClicked_deviceUnlocked_switchesToGone() =
testScope.runTest {
val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin(1234)
- )
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
utils.authenticationRepository.setUnlocked(true)
runCurrent()
@@ -150,9 +143,7 @@
fun onContentClicked_deviceLockedSecurely_switchesToBouncer() =
testScope.runTest {
val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin(1234)
- )
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
utils.authenticationRepository.setUnlocked(false)
runCurrent()
@@ -165,9 +156,7 @@
fun onLockButtonClicked_deviceUnlocked_switchesToGone() =
testScope.runTest {
val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin(1234)
- )
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
utils.authenticationRepository.setUnlocked(true)
runCurrent()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
index b4b3073..9a90a5c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
@@ -216,9 +216,6 @@
@Mock private lateinit var recCardTitle: TextView
@Mock private lateinit var coverItem: ImageView
@Mock private lateinit var matrix: Matrix
- private lateinit var coverItem1: ImageView
- private lateinit var coverItem2: ImageView
- private lateinit var coverItem3: ImageView
private lateinit var recTitle1: TextView
private lateinit var recTitle2: TextView
private lateinit var recTitle3: TextView
@@ -233,7 +230,6 @@
FakeFeatureFlags().apply {
this.set(Flags.UMO_SURFACE_RIPPLE, false)
this.set(Flags.UMO_TURBULENCE_NOISE, false)
- this.set(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE, false)
}
@Mock private lateinit var globalSettings: GlobalSettings
@@ -467,21 +463,25 @@
recSubtitle3 = TextView(context)
whenever(recommendationViewHolder.recommendations).thenReturn(view)
- whenever(recommendationViewHolder.cardIcon).thenReturn(appIcon)
-
- // Add a recommendation item
- coverItem1 = ImageView(context).also { it.setId(R.id.media_cover1) }
- coverItem2 = ImageView(context).also { it.setId(R.id.media_cover2) }
- coverItem3 = ImageView(context).also { it.setId(R.id.media_cover3) }
-
+ whenever(recommendationViewHolder.mediaAppIcons)
+ .thenReturn(listOf(recAppIconItem, recAppIconItem, recAppIconItem))
+ whenever(recommendationViewHolder.cardTitle).thenReturn(recCardTitle)
whenever(recommendationViewHolder.mediaCoverItems)
- .thenReturn(listOf(coverItem1, coverItem2, coverItem3))
+ .thenReturn(listOf(coverItem, coverItem, coverItem))
whenever(recommendationViewHolder.mediaCoverContainers)
.thenReturn(listOf(coverContainer1, coverContainer2, coverContainer3))
whenever(recommendationViewHolder.mediaTitles)
.thenReturn(listOf(recTitle1, recTitle2, recTitle3))
whenever(recommendationViewHolder.mediaSubtitles)
.thenReturn(listOf(recSubtitle1, recSubtitle2, recSubtitle3))
+ whenever(recommendationViewHolder.mediaProgressBars)
+ .thenReturn(listOf(recProgressBar1, recProgressBar2, recProgressBar3))
+ whenever(coverItem.imageMatrix).thenReturn(matrix)
+
+ // set ids for recommendation containers
+ whenever(coverContainer1.id).thenReturn(1)
+ whenever(coverContainer2.id).thenReturn(2)
+ whenever(coverContainer3.id).thenReturn(3)
whenever(recommendationViewHolder.gutsViewHolder).thenReturn(gutsViewHolder)
@@ -1561,7 +1561,8 @@
verify(viewHolder.player).contentDescription = descriptionCaptor.capture()
val description = descriptionCaptor.value.toString()
- assertThat(description).contains(REC_APP_NAME)
+ assertThat(description)
+ .isEqualTo(context.getString(R.string.controls_media_smartspace_rec_header))
}
@Test
@@ -1585,7 +1586,8 @@
verify(viewHolder.player).contentDescription = descriptionCaptor.capture()
val description = descriptionCaptor.value.toString()
- assertThat(description).contains(REC_APP_NAME)
+ assertThat(description)
+ .isEqualTo(context.getString(R.string.controls_media_smartspace_rec_header))
}
@Test
@@ -2151,7 +2153,6 @@
@Test
fun bindRecommendation_setAfterExecutors() {
- setupUpdatedRecommendationViewHolder()
val albumArt = getColorIcon(Color.RED)
val data =
smartspaceData.copy(
@@ -2189,7 +2190,6 @@
@Test
fun bindRecommendationWithProgressBars() {
useRealConstraintSets()
- setupUpdatedRecommendationViewHolder()
val albumArt = getColorIcon(Color.RED)
val bundle =
Bundle().apply {
@@ -2236,7 +2236,6 @@
@Test
fun bindRecommendation_carouselNotFitThreeRecs_OrientationPortrait() {
useRealConstraintSets()
- setupUpdatedRecommendationViewHolder()
val albumArt = getColorIcon(Color.RED)
val data =
smartspaceData.copy(
@@ -2290,7 +2289,6 @@
@Test
fun bindRecommendation_carouselNotFitThreeRecs_OrientationLandscape() {
useRealConstraintSets()
- setupUpdatedRecommendationViewHolder()
val albumArt = getColorIcon(Color.RED)
val data =
smartspaceData.copy(
@@ -2505,27 +2503,6 @@
verify(activityStarter).postStartActivityDismissingKeyguard(eq(pendingIntent))
}
- private fun setupUpdatedRecommendationViewHolder() {
- fakeFeatureFlag.set(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE, true)
- whenever(recommendationViewHolder.mediaAppIcons)
- .thenReturn(listOf(recAppIconItem, recAppIconItem, recAppIconItem))
- whenever(recommendationViewHolder.cardTitle).thenReturn(recCardTitle)
- whenever(recommendationViewHolder.mediaCoverContainers)
- .thenReturn(listOf(coverContainer1, coverContainer2, coverContainer3))
- whenever(recommendationViewHolder.mediaCoverItems)
- .thenReturn(listOf(coverItem, coverItem, coverItem))
- whenever(recommendationViewHolder.mediaProgressBars)
- .thenReturn(listOf(recProgressBar1, recProgressBar2, recProgressBar3))
- whenever(recommendationViewHolder.mediaSubtitles)
- .thenReturn(listOf(recSubtitle1, recSubtitle2, recSubtitle3))
- whenever(coverItem.imageMatrix).thenReturn(matrix)
-
- // set ids for recommendation containers
- whenever(coverContainer1.id).thenReturn(1)
- whenever(coverContainer2.id).thenReturn(2)
- whenever(coverContainer3.id).thenReturn(3)
- }
-
private fun getColorIcon(color: Int): Icon {
val bmp = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888)
val canvas = Canvas(bmp)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt
index c9956f3..ba97df9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt
@@ -201,8 +201,8 @@
whenever(mockCopiedState.widgetStates)
.thenReturn(
mutableMapOf(
- R.id.media_title1 to mediaTitleWidgetState,
- R.id.media_subtitle1 to mediaSubTitleWidgetState,
+ R.id.media_title to mediaTitleWidgetState,
+ R.id.media_subtitle to mediaSubTitleWidgetState,
R.id.media_cover1_container to mediaContainerWidgetState
)
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeMediaProjectionManager.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeMediaProjectionManager.kt
new file mode 100644
index 0000000..45f0a8c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeMediaProjectionManager.kt
@@ -0,0 +1,71 @@
+/*
+ * 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.mediaprojection.taskswitcher.data.repository
+
+import android.media.projection.MediaProjectionInfo
+import android.media.projection.MediaProjectionManager
+import android.os.Binder
+import android.os.IBinder
+import android.os.UserHandle
+import android.view.ContentRecordingSession
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+
+class FakeMediaProjectionManager {
+
+ val mediaProjectionManager = mock<MediaProjectionManager>()
+
+ private val callbacks = mutableListOf<MediaProjectionManager.Callback>()
+
+ init {
+ whenever(mediaProjectionManager.addCallback(any(), any())).thenAnswer {
+ callbacks += it.arguments[0] as MediaProjectionManager.Callback
+ return@thenAnswer Unit
+ }
+ whenever(mediaProjectionManager.removeCallback(any())).thenAnswer {
+ callbacks -= it.arguments[0] as MediaProjectionManager.Callback
+ return@thenAnswer Unit
+ }
+ }
+
+ fun dispatchOnStart(info: MediaProjectionInfo = DEFAULT_INFO) {
+ callbacks.forEach { it.onStart(info) }
+ }
+
+ fun dispatchOnStop(info: MediaProjectionInfo = DEFAULT_INFO) {
+ callbacks.forEach { it.onStop(info) }
+ }
+
+ fun dispatchOnSessionSet(
+ info: MediaProjectionInfo = DEFAULT_INFO,
+ session: ContentRecordingSession?
+ ) {
+ callbacks.forEach { it.onRecordingSessionSet(info, session) }
+ }
+
+ companion object {
+ fun createDisplaySession(): ContentRecordingSession =
+ ContentRecordingSession.createDisplaySession(/* displayToMirror = */ 123)
+ fun createSingleTaskSession(token: IBinder = Binder()): ContentRecordingSession =
+ ContentRecordingSession.createTaskSession(token)
+
+ private const val DEFAULT_PACKAGE_NAME = "com.media.projection.test"
+ private val DEFAULT_USER_HANDLE = UserHandle.getUserHandleForUid(UserHandle.myUserId())
+ private val DEFAULT_INFO = MediaProjectionInfo(DEFAULT_PACKAGE_NAME, DEFAULT_USER_HANDLE)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeMediaProjectionRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeMediaProjectionRepository.kt
deleted file mode 100644
index c59fd60..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeMediaProjectionRepository.kt
+++ /dev/null
@@ -1,42 +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.mediaprojection.taskswitcher.data.repository
-
-import android.app.TaskInfo
-import com.android.systemui.mediaprojection.taskswitcher.data.model.MediaProjectionState
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.asStateFlow
-
-class FakeMediaProjectionRepository : MediaProjectionRepository {
-
- private val state = MutableStateFlow<MediaProjectionState>(MediaProjectionState.NotProjecting)
-
- fun switchProjectedTask(newTask: TaskInfo) {
- state.value = MediaProjectionState.SingleTask(newTask)
- }
-
- override val mediaProjectionState: Flow<MediaProjectionState> = state.asStateFlow()
-
- fun projectEntireScreen() {
- state.value = MediaProjectionState.EntireScreen
- }
-
- fun stopProjecting() {
- state.value = MediaProjectionState.NotProjecting
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeTasksRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeTasksRepository.kt
deleted file mode 100644
index 593e389..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeTasksRepository.kt
+++ /dev/null
@@ -1,68 +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.mediaprojection.taskswitcher.data.repository
-
-import android.app.ActivityManager.RunningTaskInfo
-import android.content.Intent
-import android.os.IBinder
-import android.window.IWindowContainerToken
-import android.window.WindowContainerToken
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.asStateFlow
-
-class FakeTasksRepository : TasksRepository {
-
- private val _foregroundTask = MutableStateFlow(DEFAULT_TASK)
-
- override val foregroundTask: Flow<RunningTaskInfo> = _foregroundTask.asStateFlow()
-
- private val runningTasks = mutableListOf(DEFAULT_TASK)
-
- override suspend fun findRunningTaskFromWindowContainerToken(
- windowContainerToken: IBinder
- ): RunningTaskInfo? = runningTasks.firstOrNull { it.token.asBinder() == windowContainerToken }
-
- fun addRunningTask(task: RunningTaskInfo) {
- runningTasks.add(task)
- }
-
- fun moveTaskToForeground(task: RunningTaskInfo) {
- _foregroundTask.value = task
- }
-
- companion object {
- val DEFAULT_TASK = createTask(taskId = -1)
- val LAUNCHER_INTENT: Intent = Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME)
-
- fun createTask(
- taskId: Int,
- token: WindowContainerToken = createToken(),
- baseIntent: Intent = Intent()
- ) =
- RunningTaskInfo().apply {
- this.taskId = taskId
- this.token = token
- this.baseIntent = baseIntent
- }
-
- fun createToken(): WindowContainerToken {
- val realToken = object : IWindowContainerToken.Stub() {}
- return WindowContainerToken(realToken)
- }
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepositoryTest.kt
index 2b07465..3a74c72 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepositoryTest.kt
@@ -16,27 +16,22 @@
package com.android.systemui.mediaprojection.taskswitcher.data.repository
-import android.media.projection.MediaProjectionInfo
-import android.media.projection.MediaProjectionManager
import android.os.Binder
import android.os.Handler
-import android.os.UserHandle
import android.testing.AndroidTestingRunner
import android.view.ContentRecordingSession
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.mediaprojection.taskswitcher.data.model.MediaProjectionState
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.mockito.whenever
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager.Companion.createTask
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager.Companion.createToken
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
-import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -45,29 +40,26 @@
@SmallTest
class MediaProjectionManagerRepositoryTest : SysuiTestCase() {
- private val mediaProjectionManager = mock<MediaProjectionManager>()
-
private val dispatcher = StandardTestDispatcher()
private val testScope = TestScope(dispatcher)
- private val tasksRepo = FakeTasksRepository()
- private lateinit var callback: MediaProjectionManager.Callback
- private lateinit var repo: MediaProjectionManagerRepository
+ private val fakeMediaProjectionManager = FakeMediaProjectionManager()
+ private val fakeActivityTaskManager = FakeActivityTaskManager()
- @Before
- fun setUp() {
- whenever(mediaProjectionManager.addCallback(any(), any())).thenAnswer {
- callback = it.arguments[0] as MediaProjectionManager.Callback
- return@thenAnswer Unit
- }
- repo =
- MediaProjectionManagerRepository(
- mediaProjectionManager = mediaProjectionManager,
- handler = Handler.getMain(),
- applicationScope = testScope.backgroundScope,
- tasksRepository = tasksRepo
- )
- }
+ private val tasksRepo =
+ ActivityTaskManagerTasksRepository(
+ activityTaskManager = fakeActivityTaskManager.activityTaskManager,
+ applicationScope = testScope.backgroundScope,
+ backgroundDispatcher = dispatcher
+ )
+
+ private val repo =
+ MediaProjectionManagerRepository(
+ mediaProjectionManager = fakeMediaProjectionManager.mediaProjectionManager,
+ handler = Handler.getMain(),
+ applicationScope = testScope.backgroundScope,
+ tasksRepository = tasksRepo
+ )
@Test
fun mediaProjectionState_onStart_emitsNotProjecting() =
@@ -75,7 +67,7 @@
val state by collectLastValue(repo.mediaProjectionState)
runCurrent()
- callback.onStart(TEST_MEDIA_INFO)
+ fakeMediaProjectionManager.dispatchOnStart()
assertThat(state).isEqualTo(MediaProjectionState.NotProjecting)
}
@@ -86,7 +78,7 @@
val state by collectLastValue(repo.mediaProjectionState)
runCurrent()
- callback.onStop(TEST_MEDIA_INFO)
+ fakeMediaProjectionManager.dispatchOnStop()
assertThat(state).isEqualTo(MediaProjectionState.NotProjecting)
}
@@ -97,7 +89,7 @@
val state by collectLastValue(repo.mediaProjectionState)
runCurrent()
- callback.onRecordingSessionSet(TEST_MEDIA_INFO, /* session= */ null)
+ fakeMediaProjectionManager.dispatchOnSessionSet(session = null)
assertThat(state).isEqualTo(MediaProjectionState.NotProjecting)
}
@@ -108,8 +100,9 @@
val state by collectLastValue(repo.mediaProjectionState)
runCurrent()
- val session = ContentRecordingSession.createDisplaySession(/* displayToMirror= */ 123)
- callback.onRecordingSessionSet(TEST_MEDIA_INFO, session)
+ fakeMediaProjectionManager.dispatchOnSessionSet(
+ session = ContentRecordingSession.createDisplaySession(/* displayToMirror= */ 123)
+ )
assertThat(state).isEqualTo(MediaProjectionState.EntireScreen)
}
@@ -120,9 +113,10 @@
val state by collectLastValue(repo.mediaProjectionState)
runCurrent()
- val session =
- ContentRecordingSession.createTaskSession(/* taskWindowContainerToken= */ null)
- callback.onRecordingSessionSet(TEST_MEDIA_INFO, session)
+ fakeMediaProjectionManager.dispatchOnSessionSet(
+ session =
+ ContentRecordingSession.createTaskSession(/* taskWindowContainerToken= */ null)
+ )
assertThat(state).isEqualTo(MediaProjectionState.EntireScreen)
}
@@ -134,8 +128,9 @@
runCurrent()
val taskWindowContainerToken = Binder()
- val session = ContentRecordingSession.createTaskSession(taskWindowContainerToken)
- callback.onRecordingSessionSet(TEST_MEDIA_INFO, session)
+ fakeMediaProjectionManager.dispatchOnSessionSet(
+ session = ContentRecordingSession.createTaskSession(taskWindowContainerToken)
+ )
assertThat(state).isEqualTo(MediaProjectionState.EntireScreen)
}
@@ -143,20 +138,16 @@
@Test
fun mediaProjectionState_sessionSet_taskWithToken_matchingRunningTask_emitsSingleTask() =
testScope.runTest {
- val token = FakeTasksRepository.createToken()
- val task = FakeTasksRepository.createTask(taskId = 1, token = token)
- tasksRepo.addRunningTask(task)
+ val token = createToken()
+ val task = createTask(taskId = 1, token = token)
+ fakeActivityTaskManager.addRunningTasks(task)
val state by collectLastValue(repo.mediaProjectionState)
runCurrent()
- val session = ContentRecordingSession.createTaskSession(token.asBinder())
- callback.onRecordingSessionSet(TEST_MEDIA_INFO, session)
+ fakeMediaProjectionManager.dispatchOnSessionSet(
+ session = ContentRecordingSession.createTaskSession(token.asBinder())
+ )
assertThat(state).isEqualTo(MediaProjectionState.SingleTask(task))
}
-
- companion object {
- val TEST_MEDIA_INFO =
- MediaProjectionInfo(/* packageName= */ "com.test.package", UserHandle.CURRENT)
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractorTest.kt
index 112950b..b2ebe1bc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractorTest.kt
@@ -17,6 +17,7 @@
package com.android.systemui.mediaprojection.taskswitcher.domain.interactor
import android.content.Intent
+import android.os.Handler
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -24,7 +25,9 @@
import com.android.systemui.mediaprojection.taskswitcher.data.repository.ActivityTaskManagerTasksRepository
import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager
import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager.Companion.createTask
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeMediaProjectionRepository
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeMediaProjectionManager
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeMediaProjectionManager.Companion.createSingleTaskSession
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.MediaProjectionManagerRepository
import com.android.systemui.mediaprojection.taskswitcher.domain.model.TaskSwitchState
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -43,7 +46,8 @@
private val testScope = TestScope(dispatcher)
private val fakeActivityTaskManager = FakeActivityTaskManager()
- private val mediaRepo = FakeMediaProjectionRepository()
+ private val fakeMediaProjectionManager = FakeMediaProjectionManager()
+
private val tasksRepo =
ActivityTaskManagerTasksRepository(
activityTaskManager = fakeActivityTaskManager.activityTaskManager,
@@ -51,15 +55,26 @@
backgroundDispatcher = dispatcher
)
+ private val mediaRepo =
+ MediaProjectionManagerRepository(
+ mediaProjectionManager = fakeMediaProjectionManager.mediaProjectionManager,
+ handler = Handler.getMain(),
+ applicationScope = testScope.backgroundScope,
+ tasksRepository = tasksRepo,
+ )
+
private val interactor = TaskSwitchInteractor(mediaRepo, tasksRepo)
@Test
fun taskSwitchChanges_notProjecting_foregroundTaskChange_emitsNotProjectingTask() =
testScope.runTest {
- mediaRepo.stopProjecting()
+ val backgroundTask = createTask(taskId = 0)
+ val foregroundTask = createTask(taskId = 1)
val taskSwitchState by collectLastValue(interactor.taskSwitchChanges)
- fakeActivityTaskManager.moveTaskToForeground(createTask(taskId = 1))
+ fakeActivityTaskManager.addRunningTasks(backgroundTask, foregroundTask)
+ fakeMediaProjectionManager.dispatchOnStop()
+ fakeActivityTaskManager.moveTaskToForeground(foregroundTask)
assertThat(taskSwitchState).isEqualTo(TaskSwitchState.NotProjectingTask)
}
@@ -67,10 +82,15 @@
@Test
fun taskSwitchChanges_projectingScreen_foregroundTaskChange_emitsNotProjectingTask() =
testScope.runTest {
- mediaRepo.projectEntireScreen()
+ val backgroundTask = createTask(taskId = 0)
+ val foregroundTask = createTask(taskId = 1)
val taskSwitchState by collectLastValue(interactor.taskSwitchChanges)
- fakeActivityTaskManager.moveTaskToForeground(createTask(taskId = 1))
+ fakeActivityTaskManager.addRunningTasks(backgroundTask, foregroundTask)
+ fakeMediaProjectionManager.dispatchOnSessionSet(
+ session = FakeMediaProjectionManager.createDisplaySession()
+ )
+ fakeActivityTaskManager.moveTaskToForeground(foregroundTask)
assertThat(taskSwitchState).isEqualTo(TaskSwitchState.NotProjectingTask)
}
@@ -80,9 +100,12 @@
testScope.runTest {
val projectedTask = createTask(taskId = 0)
val foregroundTask = createTask(taskId = 1)
- mediaRepo.switchProjectedTask(projectedTask)
val taskSwitchState by collectLastValue(interactor.taskSwitchChanges)
+ fakeActivityTaskManager.addRunningTasks(projectedTask, foregroundTask)
+ fakeMediaProjectionManager.dispatchOnSessionSet(
+ session = createSingleTaskSession(token = projectedTask.token.asBinder())
+ )
fakeActivityTaskManager.moveTaskToForeground(foregroundTask)
assertThat(taskSwitchState)
@@ -99,9 +122,12 @@
testScope.runTest {
val projectedTask = createTask(taskId = 0)
val foregroundTask = createTask(taskId = 1, baseIntent = LAUNCHER_INTENT)
- mediaRepo.switchProjectedTask(projectedTask)
val taskSwitchState by collectLastValue(interactor.taskSwitchChanges)
+ fakeActivityTaskManager.addRunningTasks(projectedTask, foregroundTask)
+ fakeMediaProjectionManager.dispatchOnSessionSet(
+ session = createSingleTaskSession(projectedTask.token.asBinder())
+ )
fakeActivityTaskManager.moveTaskToForeground(foregroundTask)
assertThat(taskSwitchState).isEqualTo(TaskSwitchState.TaskUnchanged)
@@ -111,11 +137,13 @@
fun taskSwitchChanges_projectingTask_foregroundTaskSame_emitsTaskUnchanged() =
testScope.runTest {
val projectedTask = createTask(taskId = 0)
- val foregroundTask = createTask(taskId = 0)
- mediaRepo.switchProjectedTask(projectedTask)
val taskSwitchState by collectLastValue(interactor.taskSwitchChanges)
- fakeActivityTaskManager.moveTaskToForeground(foregroundTask)
+ fakeActivityTaskManager.addRunningTasks(projectedTask)
+ fakeMediaProjectionManager.dispatchOnSessionSet(
+ session = createSingleTaskSession(projectedTask.token.asBinder())
+ )
+ fakeActivityTaskManager.moveTaskToForeground(projectedTask)
assertThat(taskSwitchState).isEqualTo(TaskSwitchState.TaskUnchanged)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinatorTest.kt
new file mode 100644
index 0000000..b396caf
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinatorTest.kt
@@ -0,0 +1,145 @@
+/*
+ * 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.mediaprojection.taskswitcher.ui
+
+import android.app.Notification
+import android.app.NotificationManager
+import android.os.Handler
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.ActivityTaskManagerTasksRepository
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeMediaProjectionManager
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.MediaProjectionManagerRepository
+import com.android.systemui.mediaprojection.taskswitcher.domain.interactor.TaskSwitchInteractor
+import com.android.systemui.mediaprojection.taskswitcher.ui.viewmodel.TaskSwitcherNotificationViewModel
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import junit.framework.Assert.assertEquals
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Mockito.verify
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class TaskSwitcherNotificationCoordinatorTest : SysuiTestCase() {
+
+ private val notificationManager: NotificationManager = mock()
+
+ private val dispatcher = UnconfinedTestDispatcher()
+ private val testScope = TestScope(dispatcher)
+
+ private val fakeActivityTaskManager = FakeActivityTaskManager()
+ private val fakeMediaProjectionManager = FakeMediaProjectionManager()
+
+ private val tasksRepo =
+ ActivityTaskManagerTasksRepository(
+ activityTaskManager = fakeActivityTaskManager.activityTaskManager,
+ applicationScope = testScope.backgroundScope,
+ backgroundDispatcher = dispatcher
+ )
+
+ private val mediaRepo =
+ MediaProjectionManagerRepository(
+ mediaProjectionManager = fakeMediaProjectionManager.mediaProjectionManager,
+ handler = Handler.getMain(),
+ applicationScope = testScope.backgroundScope,
+ tasksRepository = tasksRepo,
+ )
+
+ private val interactor = TaskSwitchInteractor(mediaRepo, tasksRepo)
+ private val viewModel = TaskSwitcherNotificationViewModel(interactor)
+
+ private val coordinator =
+ TaskSwitcherNotificationCoordinator(
+ context,
+ notificationManager,
+ testScope.backgroundScope,
+ dispatcher,
+ viewModel
+ )
+
+ @Before
+ fun setup() {
+ coordinator.start()
+ }
+
+ @Test
+ fun showNotification() {
+ testScope.runTest {
+ switchTask()
+
+ val notification = ArgumentCaptor.forClass(Notification::class.java)
+ verify(notificationManager).notify(any(), any(), notification.capture())
+ assertNotification(notification)
+ }
+ }
+
+ @Test
+ fun hideNotification() {
+ testScope.runTest {
+ fakeMediaProjectionManager.dispatchOnStop()
+
+ verify(notificationManager).cancel(any())
+ }
+ }
+
+ @Test
+ fun notificationIdIsConsistent() {
+ testScope.runTest {
+ fakeMediaProjectionManager.dispatchOnStop()
+ val idCancel = argumentCaptor<Int>()
+ verify(notificationManager).cancel(idCancel.capture())
+
+ switchTask()
+ val idNotify = argumentCaptor<Int>()
+ verify(notificationManager).notify(any(), idNotify.capture(), any())
+
+ assertEquals(idCancel.value, idNotify.value)
+ }
+ }
+
+ private fun switchTask() {
+ val projectedTask = FakeActivityTaskManager.createTask(taskId = 1)
+ val foregroundTask = FakeActivityTaskManager.createTask(taskId = 2)
+ fakeActivityTaskManager.addRunningTasks(projectedTask, foregroundTask)
+ fakeMediaProjectionManager.dispatchOnSessionSet(
+ session =
+ FakeMediaProjectionManager.createSingleTaskSession(projectedTask.token.asBinder())
+ )
+ fakeActivityTaskManager.moveTaskToForeground(foregroundTask)
+ }
+
+ private fun assertNotification(notification: ArgumentCaptor<Notification>) {
+ val text = notification.value.extras.getCharSequence(Notification.EXTRA_TEXT)
+ assertEquals(context.getString(R.string.media_projection_task_switcher_text), text)
+
+ val actions = notification.value.actions
+ assertThat(actions).hasLength(2)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModelTest.kt
index ea44fb3..7d38de4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModelTest.kt
@@ -17,6 +17,7 @@
package com.android.systemui.mediaprojection.taskswitcher.ui.viewmodel
import android.content.Intent
+import android.os.Handler
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -24,7 +25,10 @@
import com.android.systemui.mediaprojection.taskswitcher.data.repository.ActivityTaskManagerTasksRepository
import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager
import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager.Companion.createTask
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeMediaProjectionRepository
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeMediaProjectionManager
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeMediaProjectionManager.Companion.createDisplaySession
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeMediaProjectionManager.Companion.createSingleTaskSession
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.MediaProjectionManagerRepository
import com.android.systemui.mediaprojection.taskswitcher.domain.interactor.TaskSwitchInteractor
import com.android.systemui.mediaprojection.taskswitcher.ui.model.TaskSwitcherNotificationUiState
import com.google.common.truth.Truth.assertThat
@@ -44,13 +48,23 @@
private val testScope = TestScope(dispatcher)
private val fakeActivityTaskManager = FakeActivityTaskManager()
- private val mediaRepo = FakeMediaProjectionRepository()
+ private val fakeMediaProjectionManager = FakeMediaProjectionManager()
+
private val tasksRepo =
ActivityTaskManagerTasksRepository(
activityTaskManager = fakeActivityTaskManager.activityTaskManager,
applicationScope = testScope.backgroundScope,
backgroundDispatcher = dispatcher
)
+
+ private val mediaRepo =
+ MediaProjectionManagerRepository(
+ mediaProjectionManager = fakeMediaProjectionManager.mediaProjectionManager,
+ handler = Handler.getMain(),
+ applicationScope = testScope.backgroundScope,
+ tasksRepository = tasksRepo,
+ )
+
private val interactor = TaskSwitchInteractor(mediaRepo, tasksRepo)
private val viewModel = TaskSwitcherNotificationViewModel(interactor)
@@ -58,19 +72,23 @@
@Test
fun uiState_notProjecting_emitsNotShowing() =
testScope.runTest {
- mediaRepo.stopProjecting()
val uiState by collectLastValue(viewModel.uiState)
+ fakeMediaProjectionManager.dispatchOnStop()
+
assertThat(uiState).isEqualTo(TaskSwitcherNotificationUiState.NotShowing)
}
@Test
fun uiState_notProjecting_foregroundTaskChanged_emitsNotShowing() =
testScope.runTest {
- mediaRepo.stopProjecting()
+ val backgroundTask = createTask(taskId = 0)
+ val foregroundTask = createTask(taskId = 1)
val uiState by collectLastValue(viewModel.uiState)
- mediaRepo.switchProjectedTask(createTask(taskId = 1))
+ fakeActivityTaskManager.addRunningTasks(backgroundTask, foregroundTask)
+ fakeMediaProjectionManager.dispatchOnStop()
+ fakeActivityTaskManager.moveTaskToForeground(foregroundTask)
assertThat(uiState).isEqualTo(TaskSwitcherNotificationUiState.NotShowing)
}
@@ -78,19 +96,23 @@
@Test
fun uiState_projectingEntireScreen_emitsNotShowing() =
testScope.runTest {
- mediaRepo.projectEntireScreen()
val uiState by collectLastValue(viewModel.uiState)
+ fakeMediaProjectionManager.dispatchOnSessionSet(session = createDisplaySession())
+
assertThat(uiState).isEqualTo(TaskSwitcherNotificationUiState.NotShowing)
}
@Test
fun uiState_projectingEntireScreen_foregroundTaskChanged_emitsNotShowing() =
testScope.runTest {
- mediaRepo.projectEntireScreen()
+ val backgroundTask = createTask(taskId = 0)
+ val foregroundTask = createTask(taskId = 1)
val uiState by collectLastValue(viewModel.uiState)
- mediaRepo.switchProjectedTask(createTask(taskId = 1))
+ fakeActivityTaskManager.addRunningTasks(backgroundTask, foregroundTask)
+ fakeMediaProjectionManager.dispatchOnSessionSet(session = createDisplaySession())
+ fakeActivityTaskManager.moveTaskToForeground(foregroundTask)
assertThat(uiState).isEqualTo(TaskSwitcherNotificationUiState.NotShowing)
}
@@ -100,9 +122,12 @@
testScope.runTest {
val projectedTask = createTask(taskId = 1)
val foregroundTask = createTask(taskId = 2)
- mediaRepo.switchProjectedTask(projectedTask)
val uiState by collectLastValue(viewModel.uiState)
+ fakeActivityTaskManager.addRunningTasks(projectedTask, foregroundTask)
+ fakeMediaProjectionManager.dispatchOnSessionSet(
+ session = createSingleTaskSession(projectedTask.token.asBinder())
+ )
fakeActivityTaskManager.moveTaskToForeground(foregroundTask)
assertThat(uiState)
@@ -113,9 +138,12 @@
fun uiState_projectingTask_foregroundTaskChanged_same_emitsNotShowing() =
testScope.runTest {
val projectedTask = createTask(taskId = 1)
- mediaRepo.switchProjectedTask(projectedTask)
val uiState by collectLastValue(viewModel.uiState)
+ fakeActivityTaskManager.addRunningTasks(projectedTask)
+ fakeMediaProjectionManager.dispatchOnSessionSet(
+ session = createSingleTaskSession(projectedTask.token.asBinder())
+ )
fakeActivityTaskManager.moveTaskToForeground(projectedTask)
assertThat(uiState).isEqualTo(TaskSwitcherNotificationUiState.NotShowing)
@@ -126,9 +154,12 @@
testScope.runTest {
val projectedTask = createTask(taskId = 1)
val foregroundTask = createTask(taskId = 2, baseIntent = LAUNCHER_INTENT)
- mediaRepo.switchProjectedTask(projectedTask)
val uiState by collectLastValue(viewModel.uiState)
+ fakeActivityTaskManager.addRunningTasks(projectedTask, foregroundTask)
+ fakeMediaProjectionManager.dispatchOnSessionSet(
+ session = createSingleTaskSession(projectedTask.token.asBinder())
+ )
fakeActivityTaskManager.moveTaskToForeground(foregroundTask)
assertThat(uiState).isEqualTo(TaskSwitcherNotificationUiState.NotShowing)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/domain/interactor/PowerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/power/domain/interactor/PowerInteractorTest.kt
index 023ed06..435a1f1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/power/domain/interactor/PowerInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/power/domain/interactor/PowerInteractorTest.kt
@@ -21,6 +21,10 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.shared.model.WakeSleepReason
+import com.android.systemui.keyguard.shared.model.WakefulnessModel
+import com.android.systemui.keyguard.shared.model.WakefulnessState
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.power.data.repository.FakePowerRepository
import com.android.systemui.statusbar.phone.ScreenOffAnimationController
@@ -44,6 +48,7 @@
private lateinit var underTest: PowerInteractor
private lateinit var repository: FakePowerRepository
+ private val keyguardRepository = FakeKeyguardRepository()
@Mock private lateinit var falsingCollector: FalsingCollector
@Mock private lateinit var screenOffAnimationController: ScreenOffAnimationController
@Mock private lateinit var statusBarStateController: StatusBarStateController
@@ -59,6 +64,7 @@
underTest =
PowerInteractor(
repository,
+ keyguardRepository,
falsingCollector,
screenOffAnimationController,
statusBarStateController,
@@ -125,6 +131,83 @@
verify(falsingCollector).onScreenOnFromTouch()
}
+ @Test
+ fun wakeUpForFullScreenIntent_notGoingToSleepAndNotDozing_notWoken() {
+ keyguardRepository.setWakefulnessModel(
+ WakefulnessModel(
+ state = WakefulnessState.AWAKE,
+ lastWakeReason = WakeSleepReason.OTHER,
+ lastSleepReason = WakeSleepReason.OTHER,
+ )
+ )
+ whenever(statusBarStateController.isDozing).thenReturn(false)
+
+ underTest.wakeUpForFullScreenIntent()
+
+ assertThat(repository.lastWakeWhy).isNull()
+ assertThat(repository.lastWakeReason).isNull()
+ }
+
+ @Test
+ fun wakeUpForFullScreenIntent_startingToSleep_woken() {
+ keyguardRepository.setWakefulnessModel(
+ WakefulnessModel(
+ state = WakefulnessState.STARTING_TO_SLEEP,
+ lastWakeReason = WakeSleepReason.OTHER,
+ lastSleepReason = WakeSleepReason.OTHER,
+ )
+ )
+ whenever(statusBarStateController.isDozing).thenReturn(false)
+
+ underTest.wakeUpForFullScreenIntent()
+
+ assertThat(repository.lastWakeWhy).isNotNull()
+ assertThat(repository.lastWakeReason).isEqualTo(PowerManager.WAKE_REASON_APPLICATION)
+ }
+
+ @Test
+ fun wakeUpForFullScreenIntent_dozing_woken() {
+ whenever(statusBarStateController.isDozing).thenReturn(true)
+ keyguardRepository.setWakefulnessModel(
+ WakefulnessModel(
+ state = WakefulnessState.AWAKE,
+ lastWakeReason = WakeSleepReason.OTHER,
+ lastSleepReason = WakeSleepReason.OTHER,
+ )
+ )
+
+ underTest.wakeUpForFullScreenIntent()
+
+ assertThat(repository.lastWakeWhy).isNotNull()
+ assertThat(repository.lastWakeReason).isEqualTo(PowerManager.WAKE_REASON_APPLICATION)
+ }
+
+ @Test
+ fun wakeUpIfDreaming_dreaming_woken() {
+ // GIVEN device is dreaming
+ whenever(statusBarStateController.isDreaming).thenReturn(true)
+
+ // WHEN wakeUpIfDreaming is called
+ underTest.wakeUpIfDreaming("testReason", PowerManager.WAKE_REASON_GESTURE)
+
+ // THEN device is woken up
+ assertThat(repository.lastWakeWhy).isEqualTo("testReason")
+ assertThat(repository.lastWakeReason).isEqualTo(PowerManager.WAKE_REASON_GESTURE)
+ }
+
+ @Test
+ fun wakeUpIfDreaming_notDreaming_notWoken() {
+ // GIVEN device is not dreaming
+ whenever(statusBarStateController.isDreaming).thenReturn(false)
+
+ // WHEN wakeUpIfDreaming is called
+ underTest.wakeUpIfDreaming("why", PowerManager.WAKE_REASON_TAP)
+
+ // THEN device is not woken
+ assertThat(repository.lastWakeWhy).isNull()
+ assertThat(repository.lastWakeReason).isNull()
+ }
+
companion object {
private val IMMEDIATE = Dispatchers.Main.immediate
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
index a0d8f98..9d9d0c7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
@@ -154,6 +154,21 @@
verify(qsPanel).setCanCollapse(true)
}
+ @Test
+ fun multipleListeningOnlyCallsBrightnessControllerOnce() {
+ controller.setListening(true, true)
+ controller.setListening(true, false)
+ controller.setListening(true, true)
+
+ verify(brightnessController).registerCallbacks()
+
+ controller.setListening(false, true)
+ controller.setListening(false, false)
+ controller.setListening(false, true)
+
+ verify(brightnessController).unregisterCallbacks()
+ }
+
private fun setShouldUseSplitShade(shouldUse: Boolean) {
testableResources.addOverride(R.bool.config_use_split_notification_shade, shouldUse)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
index c85c8ba..ed7a59e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
@@ -53,7 +53,6 @@
override fun create(containerName: String): LockscreenSceneInteractor {
return utils.lockScreenSceneInteractor(
authenticationInteractor = authenticationInteractor,
- sceneInteractor = sceneInteractor,
bouncerInteractor =
utils.bouncerInteractor(
authenticationInteractor = authenticationInteractor,
@@ -69,9 +68,7 @@
fun onContentClicked_deviceUnlocked_switchesToGone() =
testScope.runTest {
val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin(1234)
- )
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
utils.authenticationRepository.setUnlocked(true)
runCurrent()
@@ -84,9 +81,7 @@
fun onContentClicked_deviceLockedSecurely_switchesToBouncer() =
testScope.runTest {
val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin(1234)
- )
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
utils.authenticationRepository.setUnlocked(false)
runCurrent()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt
index de15c77..9ce378d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt
@@ -24,6 +24,7 @@
import com.android.systemui.scene.SceneTestUtils
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.scene.shared.model.SceneTransitionModel
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
@@ -40,7 +41,7 @@
@Test
fun allSceneKeys() {
val underTest = utils.fakeSceneContainerRepository()
- assertThat(underTest.allSceneKeys("container1"))
+ assertThat(underTest.allSceneKeys(SceneTestUtils.CONTAINER_1))
.isEqualTo(
listOf(
SceneKey.QuickSettings,
@@ -61,10 +62,10 @@
@Test
fun currentScene() = runTest {
val underTest = utils.fakeSceneContainerRepository()
- val currentScene by collectLastValue(underTest.currentScene("container1"))
+ val currentScene by collectLastValue(underTest.currentScene(SceneTestUtils.CONTAINER_1))
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
- underTest.setCurrentScene("container1", SceneModel(SceneKey.Shade))
+ underTest.setCurrentScene(SceneTestUtils.CONTAINER_1, SceneModel(SceneKey.Shade))
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Shade))
}
@@ -85,26 +86,26 @@
val underTest =
utils.fakeSceneContainerRepository(
setOf(
- utils.fakeSceneContainerConfig("container1"),
+ utils.fakeSceneContainerConfig(SceneTestUtils.CONTAINER_1),
utils.fakeSceneContainerConfig(
- "container2",
+ SceneTestUtils.CONTAINER_2,
listOf(SceneKey.QuickSettings, SceneKey.Lockscreen)
),
)
)
- underTest.setCurrentScene("container2", SceneModel(SceneKey.Shade))
+ underTest.setCurrentScene(SceneTestUtils.CONTAINER_2, SceneModel(SceneKey.Shade))
}
@Test
fun isVisible() = runTest {
val underTest = utils.fakeSceneContainerRepository()
- val isVisible by collectLastValue(underTest.isVisible("container1"))
+ val isVisible by collectLastValue(underTest.isVisible(SceneTestUtils.CONTAINER_1))
assertThat(isVisible).isTrue()
- underTest.setVisible("container1", false)
+ underTest.setVisible(SceneTestUtils.CONTAINER_1, false)
assertThat(isVisible).isFalse()
- underTest.setVisible("container1", true)
+ underTest.setVisible(SceneTestUtils.CONTAINER_1, true)
assertThat(isVisible).isTrue()
}
@@ -124,13 +125,13 @@
fun sceneTransitionProgress() = runTest {
val underTest = utils.fakeSceneContainerRepository()
val sceneTransitionProgress by
- collectLastValue(underTest.sceneTransitionProgress("container1"))
+ collectLastValue(underTest.sceneTransitionProgress(SceneTestUtils.CONTAINER_1))
assertThat(sceneTransitionProgress).isEqualTo(1f)
- underTest.setSceneTransitionProgress("container1", 0.1f)
+ underTest.setSceneTransitionProgress(SceneTestUtils.CONTAINER_1, 0.1f)
assertThat(sceneTransitionProgress).isEqualTo(0.1f)
- underTest.setSceneTransitionProgress("container1", 0.9f)
+ underTest.setSceneTransitionProgress(SceneTestUtils.CONTAINER_1, 0.9f)
assertThat(sceneTransitionProgress).isEqualTo(0.9f)
}
@@ -139,4 +140,75 @@
val underTest = utils.fakeSceneContainerRepository()
underTest.sceneTransitionProgress("nonExistingContainer")
}
+
+ @Test
+ fun setSceneTransition() = runTest {
+ val underTest =
+ utils.fakeSceneContainerRepository(
+ setOf(
+ utils.fakeSceneContainerConfig(SceneTestUtils.CONTAINER_1),
+ utils.fakeSceneContainerConfig(
+ SceneTestUtils.CONTAINER_2,
+ listOf(SceneKey.QuickSettings, SceneKey.Lockscreen)
+ ),
+ )
+ )
+ val sceneTransition by
+ collectLastValue(underTest.sceneTransitions(SceneTestUtils.CONTAINER_2))
+ assertThat(sceneTransition).isNull()
+
+ underTest.setSceneTransition(
+ SceneTestUtils.CONTAINER_2,
+ SceneKey.Lockscreen,
+ SceneKey.QuickSettings
+ )
+ assertThat(sceneTransition)
+ .isEqualTo(
+ SceneTransitionModel(from = SceneKey.Lockscreen, to = SceneKey.QuickSettings)
+ )
+ }
+
+ @Test(expected = IllegalStateException::class)
+ fun setSceneTransition_noSuchContainer_throws() {
+ val underTest = utils.fakeSceneContainerRepository()
+ underTest.setSceneTransition("nonExistingContainer", SceneKey.Lockscreen, SceneKey.Shade)
+ }
+
+ @Test(expected = IllegalStateException::class)
+ fun setSceneTransition_noFromSceneInContainer_throws() {
+ val underTest =
+ utils.fakeSceneContainerRepository(
+ setOf(
+ utils.fakeSceneContainerConfig(SceneTestUtils.CONTAINER_1),
+ utils.fakeSceneContainerConfig(
+ SceneTestUtils.CONTAINER_2,
+ listOf(SceneKey.QuickSettings, SceneKey.Lockscreen)
+ ),
+ )
+ )
+ underTest.setSceneTransition(
+ SceneTestUtils.CONTAINER_2,
+ SceneKey.Shade,
+ SceneKey.Lockscreen
+ )
+ }
+
+ @Test(expected = IllegalStateException::class)
+ fun setSceneTransition_noToSceneInContainer_throws() {
+ val underTest =
+ utils.fakeSceneContainerRepository(
+ setOf(
+ utils.fakeSceneContainerConfig(SceneTestUtils.CONTAINER_1),
+ utils.fakeSceneContainerConfig(
+ SceneTestUtils.CONTAINER_2,
+ listOf(SceneKey.QuickSettings, SceneKey.Lockscreen)
+ ),
+ )
+ )
+ underTest.setSceneTransition(
+ SceneTestUtils.CONTAINER_2,
+ SceneKey.Shade,
+ SceneKey.Lockscreen
+ )
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
index ee4f6c2..3050c4e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
@@ -24,6 +24,7 @@
import com.android.systemui.scene.SceneTestUtils
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.scene.shared.model.SceneTransitionModel
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
@@ -40,36 +41,63 @@
@Test
fun allSceneKeys() {
- assertThat(underTest.allSceneKeys("container1")).isEqualTo(utils.fakeSceneKeys())
+ assertThat(underTest.allSceneKeys(SceneTestUtils.CONTAINER_1))
+ .isEqualTo(utils.fakeSceneKeys())
}
@Test
- fun sceneTransitions() = runTest {
- val currentScene by collectLastValue(underTest.currentScene("container1"))
+ fun currentScene() = runTest {
+ val currentScene by collectLastValue(underTest.currentScene(SceneTestUtils.CONTAINER_1))
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
- underTest.setCurrentScene("container1", SceneModel(SceneKey.Shade))
+ underTest.setCurrentScene(SceneTestUtils.CONTAINER_1, SceneModel(SceneKey.Shade))
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Shade))
}
@Test
fun sceneTransitionProgress() = runTest {
- val progress by collectLastValue(underTest.sceneTransitionProgress("container1"))
+ val progress by
+ collectLastValue(underTest.sceneTransitionProgress(SceneTestUtils.CONTAINER_1))
assertThat(progress).isEqualTo(1f)
- underTest.setSceneTransitionProgress("container1", 0.55f)
+ underTest.setSceneTransitionProgress(SceneTestUtils.CONTAINER_1, 0.55f)
assertThat(progress).isEqualTo(0.55f)
}
@Test
fun isVisible() = runTest {
- val isVisible by collectLastValue(underTest.isVisible("container1"))
+ val isVisible by collectLastValue(underTest.isVisible(SceneTestUtils.CONTAINER_1))
assertThat(isVisible).isTrue()
- underTest.setVisible("container1", false)
+ underTest.setVisible(SceneTestUtils.CONTAINER_1, false)
assertThat(isVisible).isFalse()
- underTest.setVisible("container1", true)
+ underTest.setVisible(SceneTestUtils.CONTAINER_1, true)
assertThat(isVisible).isTrue()
}
+
+ @Test
+ fun sceneTransitions() = runTest {
+ val transitions by collectLastValue(underTest.sceneTransitions(SceneTestUtils.CONTAINER_1))
+ assertThat(transitions).isNull()
+
+ val initialSceneKey = underTest.currentScene(SceneTestUtils.CONTAINER_1).value.key
+ underTest.setCurrentScene(SceneTestUtils.CONTAINER_1, SceneModel(SceneKey.Shade))
+ assertThat(transitions)
+ .isEqualTo(
+ SceneTransitionModel(
+ from = initialSceneKey,
+ to = SceneKey.Shade,
+ )
+ )
+
+ underTest.setCurrentScene(SceneTestUtils.CONTAINER_1, SceneModel(SceneKey.QuickSettings))
+ assertThat(transitions)
+ .isEqualTo(
+ SceneTransitionModel(
+ from = SceneKey.Shade,
+ to = SceneKey.QuickSettings,
+ )
+ )
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SystemUiDefaultSceneContainerStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SystemUiDefaultSceneContainerStartableTest.kt
new file mode 100644
index 0000000..5638d70
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SystemUiDefaultSceneContainerStartableTest.kt
@@ -0,0 +1,400 @@
+/*
+ * Copyright 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.scene.domain.startable
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.shared.model.WakeSleepReason
+import com.android.systemui.keyguard.shared.model.WakefulnessModel
+import com.android.systemui.keyguard.shared.model.WakefulnessState
+import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.scene.shared.model.SceneContainerNames
+import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.SceneModel
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@SmallTest
+@RunWith(JUnit4::class)
+class SystemUiDefaultSceneContainerStartableTest : SysuiTestCase() {
+
+ private val utils = SceneTestUtils(this)
+ private val testScope = utils.testScope
+ private val sceneInteractor = utils.sceneInteractor()
+ private val featureFlags = utils.featureFlags
+ private val authenticationRepository = utils.authenticationRepository()
+ private val authenticationInteractor =
+ utils.authenticationInteractor(
+ repository = authenticationRepository,
+ )
+ private val keyguardRepository = utils.keyguardRepository()
+ private val keyguardInteractor =
+ utils.keyguardInteractor(
+ repository = keyguardRepository,
+ )
+
+ private val underTest =
+ SystemUiDefaultSceneContainerStartable(
+ applicationScope = testScope.backgroundScope,
+ sceneInteractor = sceneInteractor,
+ authenticationInteractor = authenticationInteractor,
+ keyguardInteractor = keyguardInteractor,
+ featureFlags = featureFlags,
+ )
+
+ @Before
+ fun setUp() {
+ prepareState()
+ }
+
+ @Test
+ fun hydrateVisibility_featureEnabled() =
+ testScope.runTest {
+ val currentSceneKey by
+ collectLastValue(
+ sceneInteractor.currentScene(SceneContainerNames.SYSTEM_UI_DEFAULT).map {
+ it.key
+ }
+ )
+ val isVisible by
+ collectLastValue(sceneInteractor.isVisible(SceneContainerNames.SYSTEM_UI_DEFAULT))
+ prepareState(
+ isFeatureEnabled = true,
+ isDeviceUnlocked = true,
+ initialSceneKey = SceneKey.Gone,
+ )
+ assertThat(currentSceneKey).isEqualTo(SceneKey.Gone)
+ assertThat(isVisible).isTrue()
+
+ underTest.start()
+
+ assertThat(isVisible).isFalse()
+
+ sceneInteractor.setCurrentScene(
+ SceneContainerNames.SYSTEM_UI_DEFAULT,
+ SceneModel(SceneKey.Shade)
+ )
+ assertThat(isVisible).isTrue()
+ }
+
+ @Test
+ fun hydrateVisibility_featureDisabled() =
+ testScope.runTest {
+ val currentSceneKey by
+ collectLastValue(
+ sceneInteractor.currentScene(SceneContainerNames.SYSTEM_UI_DEFAULT).map {
+ it.key
+ }
+ )
+ val isVisible by
+ collectLastValue(sceneInteractor.isVisible(SceneContainerNames.SYSTEM_UI_DEFAULT))
+ prepareState(
+ isFeatureEnabled = false,
+ isDeviceUnlocked = true,
+ initialSceneKey = SceneKey.Lockscreen,
+ )
+ assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
+ assertThat(isVisible).isTrue()
+
+ underTest.start()
+ assertThat(isVisible).isTrue()
+
+ sceneInteractor.setCurrentScene(
+ SceneContainerNames.SYSTEM_UI_DEFAULT,
+ SceneModel(SceneKey.Gone)
+ )
+ assertThat(isVisible).isTrue()
+
+ sceneInteractor.setCurrentScene(
+ SceneContainerNames.SYSTEM_UI_DEFAULT,
+ SceneModel(SceneKey.Shade)
+ )
+ assertThat(isVisible).isTrue()
+ }
+
+ @Test
+ fun switchToLockscreenWhenDeviceLocks_featureEnabled() =
+ testScope.runTest {
+ val currentSceneKey by
+ collectLastValue(
+ sceneInteractor.currentScene(SceneContainerNames.SYSTEM_UI_DEFAULT).map {
+ it.key
+ }
+ )
+ prepareState(
+ isFeatureEnabled = true,
+ isDeviceUnlocked = true,
+ initialSceneKey = SceneKey.Gone,
+ )
+ assertThat(currentSceneKey).isEqualTo(SceneKey.Gone)
+ underTest.start()
+
+ authenticationRepository.setUnlocked(false)
+
+ assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
+ }
+
+ @Test
+ fun switchToLockscreenWhenDeviceLocks_featureDisabled() =
+ testScope.runTest {
+ val currentSceneKey by
+ collectLastValue(
+ sceneInteractor.currentScene(SceneContainerNames.SYSTEM_UI_DEFAULT).map {
+ it.key
+ }
+ )
+ prepareState(
+ isFeatureEnabled = false,
+ isDeviceUnlocked = false,
+ initialSceneKey = SceneKey.Gone,
+ )
+ assertThat(currentSceneKey).isEqualTo(SceneKey.Gone)
+ underTest.start()
+
+ authenticationRepository.setUnlocked(false)
+
+ assertThat(currentSceneKey).isEqualTo(SceneKey.Gone)
+ }
+
+ @Test
+ fun switchFromBouncerToGoneWhenDeviceUnlocked_featureEnabled() =
+ testScope.runTest {
+ val currentSceneKey by
+ collectLastValue(
+ sceneInteractor.currentScene(SceneContainerNames.SYSTEM_UI_DEFAULT).map {
+ it.key
+ }
+ )
+ prepareState(
+ isFeatureEnabled = true,
+ isDeviceUnlocked = false,
+ initialSceneKey = SceneKey.Bouncer,
+ )
+ assertThat(currentSceneKey).isEqualTo(SceneKey.Bouncer)
+ underTest.start()
+
+ authenticationRepository.setUnlocked(true)
+
+ assertThat(currentSceneKey).isEqualTo(SceneKey.Gone)
+ }
+
+ @Test
+ fun switchFromBouncerToGoneWhenDeviceUnlocked_featureDisabled() =
+ testScope.runTest {
+ val currentSceneKey by
+ collectLastValue(
+ sceneInteractor.currentScene(SceneContainerNames.SYSTEM_UI_DEFAULT).map {
+ it.key
+ }
+ )
+ prepareState(
+ isFeatureEnabled = false,
+ isDeviceUnlocked = false,
+ initialSceneKey = SceneKey.Bouncer,
+ )
+ assertThat(currentSceneKey).isEqualTo(SceneKey.Bouncer)
+ underTest.start()
+
+ authenticationRepository.setUnlocked(true)
+
+ assertThat(currentSceneKey).isEqualTo(SceneKey.Bouncer)
+ }
+
+ @Test
+ fun switchFromLockscreenToGoneWhenDeviceUnlocksWithBypassOn_featureOn_bypassOn() =
+ testScope.runTest {
+ val currentSceneKey by
+ collectLastValue(
+ sceneInteractor.currentScene(SceneContainerNames.SYSTEM_UI_DEFAULT).map {
+ it.key
+ }
+ )
+ prepareState(
+ isFeatureEnabled = true,
+ isBypassEnabled = true,
+ initialSceneKey = SceneKey.Lockscreen,
+ )
+ assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
+ underTest.start()
+
+ authenticationRepository.setUnlocked(true)
+
+ assertThat(currentSceneKey).isEqualTo(SceneKey.Gone)
+ }
+
+ @Test
+ fun switchFromLockscreenToGoneWhenDeviceUnlocksWithBypassOn_featureOn_bypassOff() =
+ testScope.runTest {
+ val currentSceneKey by
+ collectLastValue(
+ sceneInteractor.currentScene(SceneContainerNames.SYSTEM_UI_DEFAULT).map {
+ it.key
+ }
+ )
+ prepareState(
+ isFeatureEnabled = true,
+ isBypassEnabled = false,
+ initialSceneKey = SceneKey.Lockscreen,
+ )
+ assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
+ underTest.start()
+
+ authenticationRepository.setUnlocked(true)
+
+ assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
+ }
+
+ @Test
+ fun switchFromLockscreenToGoneWhenDeviceUnlocksWithBypassOn_featureOff_bypassOn() =
+ testScope.runTest {
+ val currentSceneKey by
+ collectLastValue(
+ sceneInteractor.currentScene(SceneContainerNames.SYSTEM_UI_DEFAULT).map {
+ it.key
+ }
+ )
+ prepareState(
+ isFeatureEnabled = false,
+ isBypassEnabled = true,
+ initialSceneKey = SceneKey.Lockscreen,
+ )
+ assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
+ underTest.start()
+
+ authenticationRepository.setUnlocked(true)
+
+ assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
+ }
+
+ @Test
+ fun switchToGoneWhenDeviceSleepsUnlocked_featureEnabled() =
+ testScope.runTest {
+ val currentSceneKey by
+ collectLastValue(
+ sceneInteractor.currentScene(SceneContainerNames.SYSTEM_UI_DEFAULT).map {
+ it.key
+ }
+ )
+ prepareState(
+ isFeatureEnabled = true,
+ isDeviceUnlocked = true,
+ initialSceneKey = SceneKey.Shade,
+ )
+ assertThat(currentSceneKey).isEqualTo(SceneKey.Shade)
+ underTest.start()
+
+ keyguardRepository.setWakefulnessModel(ASLEEP)
+
+ assertThat(currentSceneKey).isEqualTo(SceneKey.Gone)
+ }
+
+ @Test
+ fun switchToGoneWhenDeviceSleepsUnlocked_featureDisabled() =
+ testScope.runTest {
+ val currentSceneKey by
+ collectLastValue(
+ sceneInteractor.currentScene(SceneContainerNames.SYSTEM_UI_DEFAULT).map {
+ it.key
+ }
+ )
+ prepareState(
+ isFeatureEnabled = false,
+ isDeviceUnlocked = true,
+ initialSceneKey = SceneKey.Shade,
+ )
+ assertThat(currentSceneKey).isEqualTo(SceneKey.Shade)
+ underTest.start()
+
+ keyguardRepository.setWakefulnessModel(ASLEEP)
+
+ assertThat(currentSceneKey).isEqualTo(SceneKey.Shade)
+ }
+
+ @Test
+ fun switchToLockscreenWhenDeviceSleepsLocked_featureEnabled() =
+ testScope.runTest {
+ val currentSceneKey by
+ collectLastValue(
+ sceneInteractor.currentScene(SceneContainerNames.SYSTEM_UI_DEFAULT).map {
+ it.key
+ }
+ )
+ prepareState(
+ isFeatureEnabled = true,
+ isDeviceUnlocked = false,
+ initialSceneKey = SceneKey.Shade,
+ )
+ assertThat(currentSceneKey).isEqualTo(SceneKey.Shade)
+ underTest.start()
+
+ keyguardRepository.setWakefulnessModel(ASLEEP)
+
+ assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
+ }
+
+ @Test
+ fun switchToLockscreenWhenDeviceSleepsLocked_featureDisabled() =
+ testScope.runTest {
+ val currentSceneKey by
+ collectLastValue(
+ sceneInteractor.currentScene(SceneContainerNames.SYSTEM_UI_DEFAULT).map {
+ it.key
+ }
+ )
+ prepareState(
+ isFeatureEnabled = false,
+ isDeviceUnlocked = false,
+ initialSceneKey = SceneKey.Shade,
+ )
+ assertThat(currentSceneKey).isEqualTo(SceneKey.Shade)
+ underTest.start()
+
+ keyguardRepository.setWakefulnessModel(ASLEEP)
+
+ assertThat(currentSceneKey).isEqualTo(SceneKey.Shade)
+ }
+
+ private fun prepareState(
+ isFeatureEnabled: Boolean = true,
+ isDeviceUnlocked: Boolean = false,
+ isBypassEnabled: Boolean = false,
+ initialSceneKey: SceneKey? = null,
+ ) {
+ featureFlags.set(Flags.SCENE_CONTAINER, isFeatureEnabled)
+ authenticationRepository.setUnlocked(isDeviceUnlocked)
+ keyguardRepository.setBypassEnabled(isBypassEnabled)
+ initialSceneKey?.let {
+ sceneInteractor.setCurrentScene(SceneContainerNames.SYSTEM_UI_DEFAULT, SceneModel(it))
+ }
+ }
+
+ companion object {
+ private val ASLEEP =
+ WakefulnessModel(
+ state = WakefulnessState.ASLEEP,
+ lastWakeReason = WakeSleepReason.POWER_BUTTON,
+ lastSleepReason = WakeSleepReason.POWER_BUTTON
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
index cd2f5af..6882be7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
@@ -40,7 +40,7 @@
private val underTest =
SceneContainerViewModel(
interactor = interactor,
- containerName = "container1",
+ containerName = SceneTestUtils.CONTAINER_1,
)
@Test
@@ -48,10 +48,10 @@
val isVisible by collectLastValue(underTest.isVisible)
assertThat(isVisible).isTrue()
- interactor.setVisible("container1", false)
+ interactor.setVisible(SceneTestUtils.CONTAINER_1, false)
assertThat(isVisible).isFalse()
- interactor.setVisible("container1", true)
+ interactor.setVisible(SceneTestUtils.CONTAINER_1, true)
assertThat(isVisible).isTrue()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessControllerTest.kt
new file mode 100644
index 0000000..2b78405
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessControllerTest.kt
@@ -0,0 +1,105 @@
+/*
+ * 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.settings.brightness
+
+import android.hardware.display.DisplayManager
+import android.os.Handler
+import android.service.vr.IVrManager
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.testing.TestableLooper.RunWithLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.settings.DisplayTracker
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.settings.FakeSettings
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper
+class BrightnessControllerTest : SysuiTestCase() {
+
+ private val executor = FakeExecutor(FakeSystemClock())
+ private val secureSettings = FakeSettings()
+ @Mock private lateinit var toggleSlider: ToggleSlider
+ @Mock private lateinit var userTracker: UserTracker
+ @Mock private lateinit var displayTracker: DisplayTracker
+ @Mock private lateinit var displayManager: DisplayManager
+ @Mock private lateinit var iVrManager: IVrManager
+
+ private lateinit var testableLooper: TestableLooper
+
+ private lateinit var underTest: BrightnessController
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ testableLooper = TestableLooper.get(this)
+
+ underTest =
+ BrightnessController(
+ context,
+ toggleSlider,
+ userTracker,
+ displayTracker,
+ displayManager,
+ secureSettings,
+ iVrManager,
+ executor,
+ mock(),
+ Handler(testableLooper.looper)
+ )
+ }
+
+ @Test
+ fun registerCallbacksMultipleTimes_onlyOneRegistration() {
+ val repeats = 100
+ repeat(repeats) { underTest.registerCallbacks() }
+ val messagesProcessed = testableLooper.processMessagesNonBlocking(repeats)
+
+ verify(displayTracker).addBrightnessChangeCallback(any(), any())
+ verify(iVrManager).registerListener(any())
+
+ assertThat(messagesProcessed).isEqualTo(1)
+ }
+
+ @Test
+ fun unregisterCallbacksMultipleTimes_onlyOneUnregistration() {
+ val repeats = 100
+ underTest.registerCallbacks()
+ testableLooper.processAllMessages()
+
+ repeat(repeats) { underTest.unregisterCallbacks() }
+ val messagesProcessed = testableLooper.processMessagesNonBlocking(repeats)
+
+ verify(displayTracker).removeCallback(any())
+ verify(iVrManager).unregisterListener(any())
+
+ assertThat(messagesProcessed).isEqualTo(1)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt
index 16751c9..ed1397f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt
@@ -16,28 +16,32 @@
package com.android.systemui.settings.brightness
+import android.content.Intent
import android.graphics.Rect
-import android.os.Handler
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.View
import android.view.ViewGroup
+import android.view.WindowManagerPolicyConstants.EXTRA_FROM_BRIGHTNESS_KEY
import androidx.test.filters.SmallTest
import androidx.test.rule.ActivityTestRule
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.activity.SingleActivityFactory
-import com.android.systemui.settings.FakeDisplayTracker
-import com.android.systemui.settings.UserTracker
+import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper
+import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
+import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
-import java.util.concurrent.Executor
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.eq
import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
@@ -46,13 +50,14 @@
@TestableLooper.RunWithLooper
class BrightnessDialogTest : SysuiTestCase() {
- @Mock private lateinit var userTracker: UserTracker
@Mock private lateinit var brightnessSliderControllerFactory: BrightnessSliderController.Factory
- @Mock private lateinit var mainExecutor: Executor
- @Mock private lateinit var backgroundHandler: Handler
@Mock private lateinit var brightnessSliderController: BrightnessSliderController
+ @Mock private lateinit var brightnessControllerFactory: BrightnessController.Factory
+ @Mock private lateinit var brightnessController: BrightnessController
+ @Mock private lateinit var accessibilityMgr: AccessibilityManagerWrapper
- private var displayTracker = FakeDisplayTracker(mContext)
+ private val clock = FakeSystemClock()
+ private val mainExecutor = FakeExecutor(clock)
@Rule
@JvmField
@@ -60,11 +65,10 @@
ActivityTestRule(
/* activityFactory= */ SingleActivityFactory {
TestDialog(
- userTracker,
- displayTracker,
brightnessSliderControllerFactory,
+ brightnessControllerFactory,
mainExecutor,
- backgroundHandler
+ accessibilityMgr
)
},
/* initialTouchMode= */ false,
@@ -77,8 +81,7 @@
`when`(brightnessSliderControllerFactory.create(any(), any()))
.thenReturn(brightnessSliderController)
`when`(brightnessSliderController.rootView).thenReturn(View(context))
-
- activityRule.launchActivity(null)
+ `when`(brightnessControllerFactory.create(any())).thenReturn(brightnessController)
}
@After
@@ -88,6 +91,7 @@
@Test
fun testGestureExclusion() {
+ activityRule.launchActivity(Intent(Intent.ACTION_SHOW_BRIGHTNESS_DIALOG))
val frame = activityRule.activity.requireViewById<View>(R.id.brightness_mirror_container)
val lp = frame.layoutParams as ViewGroup.MarginLayoutParams
@@ -104,18 +108,79 @@
.isEqualTo(Rect(-horizontalMargin, 0, frame.width + horizontalMargin, frame.height))
}
+ @Test
+ fun testTimeout() {
+ `when`(
+ accessibilityMgr.getRecommendedTimeoutMillis(
+ eq(BrightnessDialog.DIALOG_TIMEOUT_MILLIS),
+ anyInt()
+ )
+ )
+ .thenReturn(BrightnessDialog.DIALOG_TIMEOUT_MILLIS)
+ val intent = Intent(Intent.ACTION_SHOW_BRIGHTNESS_DIALOG)
+ intent.putExtra(EXTRA_FROM_BRIGHTNESS_KEY, true)
+ activityRule.launchActivity(intent)
+
+ assertThat(activityRule.activity.isFinishing()).isFalse()
+
+ clock.advanceTime(BrightnessDialog.DIALOG_TIMEOUT_MILLIS.toLong())
+ assertThat(activityRule.activity.isFinishing()).isTrue()
+ }
+
+ @Test
+ fun testRestartTimeout() {
+ `when`(
+ accessibilityMgr.getRecommendedTimeoutMillis(
+ eq(BrightnessDialog.DIALOG_TIMEOUT_MILLIS),
+ anyInt()
+ )
+ )
+ .thenReturn(BrightnessDialog.DIALOG_TIMEOUT_MILLIS)
+ val intent = Intent(Intent.ACTION_SHOW_BRIGHTNESS_DIALOG)
+ intent.putExtra(EXTRA_FROM_BRIGHTNESS_KEY, true)
+ activityRule.launchActivity(intent)
+
+ assertThat(activityRule.activity.isFinishing()).isFalse()
+
+ clock.advanceTime(BrightnessDialog.DIALOG_TIMEOUT_MILLIS.toLong() / 2)
+ // Restart the timeout
+ activityRule.activity.onResume()
+
+ clock.advanceTime(BrightnessDialog.DIALOG_TIMEOUT_MILLIS.toLong() / 2)
+ // The dialog should not have disappeared yet
+ assertThat(activityRule.activity.isFinishing()).isFalse()
+
+ clock.advanceTime(BrightnessDialog.DIALOG_TIMEOUT_MILLIS.toLong() / 2)
+ assertThat(activityRule.activity.isFinishing()).isTrue()
+ }
+
+ @Test
+ fun testNoTimeoutIfNotStartedByBrightnessKey() {
+ `when`(
+ accessibilityMgr.getRecommendedTimeoutMillis(
+ eq(BrightnessDialog.DIALOG_TIMEOUT_MILLIS),
+ anyInt()
+ )
+ )
+ .thenReturn(BrightnessDialog.DIALOG_TIMEOUT_MILLIS)
+ activityRule.launchActivity(Intent(Intent.ACTION_SHOW_BRIGHTNESS_DIALOG))
+
+ assertThat(activityRule.activity.isFinishing()).isFalse()
+
+ clock.advanceTime(BrightnessDialog.DIALOG_TIMEOUT_MILLIS.toLong())
+ assertThat(activityRule.activity.isFinishing()).isFalse()
+ }
+
class TestDialog(
- userTracker: UserTracker,
- displayTracker: FakeDisplayTracker,
brightnessSliderControllerFactory: BrightnessSliderController.Factory,
- mainExecutor: Executor,
- backgroundHandler: Handler
+ brightnessControllerFactory: BrightnessController.Factory,
+ mainExecutor: DelayableExecutor,
+ accessibilityMgr: AccessibilityManagerWrapper
) :
BrightnessDialog(
- userTracker,
- displayTracker,
brightnessSliderControllerFactory,
+ brightnessControllerFactory,
mainExecutor,
- backgroundHandler
+ accessibilityMgr
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/LockscreenHostedDreamGestureListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/LockscreenHostedDreamGestureListenerTest.kt
new file mode 100644
index 0000000..24d62fb
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/LockscreenHostedDreamGestureListenerTest.kt
@@ -0,0 +1,189 @@
+/*
+ * 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.shade
+
+import android.os.PowerManager
+import android.testing.AndroidTestingRunner
+import android.view.MotionEvent
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
+import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.power.data.repository.FakePowerRepository
+import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.phone.ScreenOffAnimationController
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidTestingRunner::class)
+class LockscreenHostedDreamGestureListenerTest : SysuiTestCase() {
+ @Mock private lateinit var falsingManager: FalsingManager
+ @Mock private lateinit var falsingCollector: FalsingCollector
+ @Mock private lateinit var statusBarStateController: StatusBarStateController
+ @Mock private lateinit var shadeLogger: ShadeLogger
+ @Mock private lateinit var screenOffAnimationController: ScreenOffAnimationController
+ @Mock private lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor
+
+ private val testDispatcher = UnconfinedTestDispatcher()
+ private val testScope = TestScope(testDispatcher)
+
+ private lateinit var powerRepository: FakePowerRepository
+ private lateinit var keyguardRepository: FakeKeyguardRepository
+ private lateinit var underTest: LockscreenHostedDreamGestureListener
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ powerRepository = FakePowerRepository()
+ keyguardRepository = FakeKeyguardRepository()
+
+ underTest =
+ LockscreenHostedDreamGestureListener(
+ falsingManager,
+ PowerInteractor(
+ powerRepository,
+ keyguardRepository,
+ falsingCollector,
+ screenOffAnimationController,
+ statusBarStateController,
+ ),
+ statusBarStateController,
+ primaryBouncerInteractor,
+ keyguardRepository,
+ shadeLogger,
+ )
+ whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
+ whenever(primaryBouncerInteractor.isBouncerShowing()).thenReturn(false)
+ }
+
+ @Test
+ fun testGestureDetector_onSingleTap_whileDreaming() =
+ testScope.runTest {
+ // GIVEN device dreaming and the dream is hosted in lockscreen
+ whenever(statusBarStateController.isDreaming).thenReturn(true)
+ keyguardRepository.setIsActiveDreamLockscreenHosted(true)
+ testScope.runCurrent()
+
+ // GIVEN the falsing manager does NOT think the tap is a false tap
+ whenever(falsingManager.isFalseTap(ArgumentMatchers.anyInt())).thenReturn(false)
+
+ // WHEN there's a tap
+ underTest.onSingleTapUp(upEv)
+
+ // THEN wake up device if dreaming
+ Truth.assertThat(powerRepository.lastWakeWhy).isNotNull()
+ Truth.assertThat(powerRepository.lastWakeReason).isEqualTo(PowerManager.WAKE_REASON_TAP)
+ }
+
+ @Test
+ fun testGestureDetector_onSingleTap_notOnKeyguard() =
+ testScope.runTest {
+ // GIVEN device dreaming and the dream is hosted in lockscreen
+ whenever(statusBarStateController.isDreaming).thenReturn(true)
+ keyguardRepository.setIsActiveDreamLockscreenHosted(true)
+ testScope.runCurrent()
+
+ // GIVEN shade is open
+ whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE)
+
+ // GIVEN the falsing manager does NOT think the tap is a false tap
+ whenever(falsingManager.isFalseTap(ArgumentMatchers.anyInt())).thenReturn(false)
+
+ // WHEN there's a tap
+ underTest.onSingleTapUp(upEv)
+
+ // THEN the falsing manager never gets a call
+ verify(falsingManager, never()).isFalseTap(ArgumentMatchers.anyInt())
+ }
+
+ @Test
+ fun testGestureDetector_onSingleTap_bouncerShown() =
+ testScope.runTest {
+ // GIVEN device dreaming and the dream is hosted in lockscreen
+ whenever(statusBarStateController.isDreaming).thenReturn(true)
+ keyguardRepository.setIsActiveDreamLockscreenHosted(true)
+ testScope.runCurrent()
+
+ // GIVEN bouncer is expanded
+ whenever(primaryBouncerInteractor.isBouncerShowing()).thenReturn(true)
+
+ // GIVEN the falsing manager does NOT think the tap is a false tap
+ whenever(falsingManager.isFalseTap(ArgumentMatchers.anyInt())).thenReturn(false)
+
+ // WHEN there's a tap
+ underTest.onSingleTapUp(upEv)
+
+ // THEN the falsing manager never gets a call
+ verify(falsingManager, never()).isFalseTap(ArgumentMatchers.anyInt())
+ }
+
+ @Test
+ fun testGestureDetector_onSingleTap_falsing() =
+ testScope.runTest {
+ // GIVEN device dreaming and the dream is hosted in lockscreen
+ whenever(statusBarStateController.isDreaming).thenReturn(true)
+ keyguardRepository.setIsActiveDreamLockscreenHosted(true)
+ testScope.runCurrent()
+
+ // GIVEN the falsing manager thinks the tap is a false tap
+ whenever(falsingManager.isFalseTap(ArgumentMatchers.anyInt())).thenReturn(true)
+
+ // WHEN there's a tap
+ underTest.onSingleTapUp(upEv)
+
+ // THEN the device doesn't wake up
+ Truth.assertThat(powerRepository.lastWakeWhy).isNull()
+ Truth.assertThat(powerRepository.lastWakeReason).isNull()
+ }
+
+ @Test
+ fun testSingleTap_notDreaming_noFalsingCheck() =
+ testScope.runTest {
+ // GIVEN device not dreaming with lockscreen hosted dream
+ whenever(statusBarStateController.isDreaming).thenReturn(false)
+ keyguardRepository.setIsActiveDreamLockscreenHosted(false)
+ testScope.runCurrent()
+
+ // WHEN there's a tap
+ underTest.onSingleTapUp(upEv)
+
+ // THEN the falsing manager never gets a call
+ verify(falsingManager, never()).isFalseTap(ArgumentMatchers.anyInt())
+ }
+}
+
+private val upEv = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_UP, 0f, 0f, 0)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationQSContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationQSContainerControllerTest.kt
deleted file mode 100644
index 168cbb7..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationQSContainerControllerTest.kt
+++ /dev/null
@@ -1,512 +0,0 @@
-package com.android.systemui.shade
-
-import android.testing.AndroidTestingRunner
-import android.testing.TestableLooper
-import android.view.View
-import android.view.ViewGroup
-import android.view.WindowInsets
-import android.view.WindowManagerPolicyConstants
-import androidx.annotation.IdRes
-import androidx.constraintlayout.widget.ConstraintLayout
-import androidx.constraintlayout.widget.ConstraintSet
-import androidx.test.filters.SmallTest
-import com.android.systemui.R
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.fragments.FragmentHostManager
-import com.android.systemui.fragments.FragmentService
-import com.android.systemui.navigationbar.NavigationModeController
-import com.android.systemui.navigationbar.NavigationModeController.ModeChangedListener
-import com.android.systemui.plugins.qs.QS
-import com.android.systemui.recents.OverviewProxyService
-import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener
-import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.time.FakeSystemClock
-import com.google.common.truth.Truth.assertThat
-import java.util.function.Consumer
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.ArgumentCaptor
-import org.mockito.Captor
-import org.mockito.Mock
-import org.mockito.Mockito
-import org.mockito.Mockito.RETURNS_DEEP_STUBS
-import org.mockito.Mockito.any
-import org.mockito.Mockito.anyInt
-import org.mockito.Mockito.doNothing
-import org.mockito.Mockito.eq
-import org.mockito.Mockito.mock
-import org.mockito.Mockito.never
-import org.mockito.Mockito.reset
-import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
-import org.mockito.Mockito.`when` as whenever
-
-@SmallTest
-@RunWith(AndroidTestingRunner::class)
-@TestableLooper.RunWithLooper
-class NotificationQSContainerControllerTest : SysuiTestCase() {
-
- companion object {
- const val STABLE_INSET_BOTTOM = 100
- const val CUTOUT_HEIGHT = 50
- const val GESTURES_NAVIGATION = WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL
- const val BUTTONS_NAVIGATION = WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON
- const val NOTIFICATIONS_MARGIN = 50
- const val SCRIM_MARGIN = 10
- const val FOOTER_ACTIONS_INSET = 2
- const val FOOTER_ACTIONS_PADDING = 2
- const val FOOTER_ACTIONS_OFFSET = FOOTER_ACTIONS_INSET + FOOTER_ACTIONS_PADDING
- const val QS_PADDING_OFFSET = SCRIM_MARGIN + FOOTER_ACTIONS_OFFSET
- }
-
- @Mock
- private lateinit var navigationModeController: NavigationModeController
- @Mock
- private lateinit var overviewProxyService: OverviewProxyService
- @Mock
- private lateinit var notificationsQSContainer: NotificationsQuickSettingsContainer
- @Mock
- private lateinit var mShadeHeaderController: ShadeHeaderController
- @Mock
- private lateinit var shadeExpansionStateManager: ShadeExpansionStateManager
- @Mock
- private lateinit var fragmentService: FragmentService
- @Mock
- private lateinit var fragmentHostManager: FragmentHostManager
- @Captor
- lateinit var navigationModeCaptor: ArgumentCaptor<ModeChangedListener>
- @Captor
- lateinit var taskbarVisibilityCaptor: ArgumentCaptor<OverviewProxyListener>
- @Captor
- lateinit var windowInsetsCallbackCaptor: ArgumentCaptor<Consumer<WindowInsets>>
- @Captor
- lateinit var constraintSetCaptor: ArgumentCaptor<ConstraintSet>
- @Captor
- lateinit var attachStateListenerCaptor: ArgumentCaptor<View.OnAttachStateChangeListener>
-
- private lateinit var controller: NotificationsQSContainerController
- private lateinit var navigationModeCallback: ModeChangedListener
- private lateinit var taskbarVisibilityCallback: OverviewProxyListener
- private lateinit var windowInsetsCallback: Consumer<WindowInsets>
- private lateinit var delayableExecutor: FakeExecutor
- private lateinit var fakeSystemClock: FakeSystemClock
-
- @Before
- fun setup() {
- MockitoAnnotations.initMocks(this)
- mContext.ensureTestableResources()
- whenever(notificationsQSContainer.context).thenReturn(mContext)
- whenever(notificationsQSContainer.resources).thenReturn(mContext.resources)
- whenever(fragmentService.getFragmentHostManager(any())).thenReturn(fragmentHostManager)
- fakeSystemClock = FakeSystemClock()
- delayableExecutor = FakeExecutor(fakeSystemClock)
-
- controller = NotificationsQSContainerController(
- notificationsQSContainer,
- navigationModeController,
- overviewProxyService,
- mShadeHeaderController,
- shadeExpansionStateManager,
- fragmentService,
- delayableExecutor
- )
-
- overrideResource(R.dimen.split_shade_notifications_scrim_margin_bottom, SCRIM_MARGIN)
- overrideResource(R.dimen.notification_panel_margin_bottom, NOTIFICATIONS_MARGIN)
- overrideResource(R.bool.config_use_split_notification_shade, false)
- overrideResource(R.dimen.qs_footer_actions_bottom_padding, FOOTER_ACTIONS_PADDING)
- overrideResource(R.dimen.qs_footer_action_inset, FOOTER_ACTIONS_INSET)
- whenever(navigationModeController.addListener(navigationModeCaptor.capture()))
- .thenReturn(GESTURES_NAVIGATION)
- doNothing().`when`(overviewProxyService).addCallback(taskbarVisibilityCaptor.capture())
- doNothing().`when`(notificationsQSContainer)
- .setInsetsChangedListener(windowInsetsCallbackCaptor.capture())
- doNothing().`when`(notificationsQSContainer).applyConstraints(constraintSetCaptor.capture())
- doNothing().`when`(notificationsQSContainer)
- .addOnAttachStateChangeListener(attachStateListenerCaptor.capture())
- controller.init()
- attachStateListenerCaptor.value.onViewAttachedToWindow(notificationsQSContainer)
-
- navigationModeCallback = navigationModeCaptor.value
- taskbarVisibilityCallback = taskbarVisibilityCaptor.value
- windowInsetsCallback = windowInsetsCallbackCaptor.value
- }
-
- @Test
- fun testTaskbarVisibleInSplitShade() {
- enableSplitShade()
-
- given(taskbarVisible = true,
- navigationMode = GESTURES_NAVIGATION,
- insets = windowInsets().withStableBottom())
- then(expectedContainerPadding = 0, // taskbar should disappear when shade is expanded
- expectedNotificationsMargin = NOTIFICATIONS_MARGIN,
- expectedQsPadding = NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET)
-
- given(taskbarVisible = true,
- navigationMode = BUTTONS_NAVIGATION,
- insets = windowInsets().withStableBottom())
- then(expectedContainerPadding = STABLE_INSET_BOTTOM,
- expectedNotificationsMargin = NOTIFICATIONS_MARGIN,
- expectedQsPadding = NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET)
- }
-
- @Test
- fun testTaskbarNotVisibleInSplitShade() {
- // when taskbar is not visible, it means we're on the home screen
- enableSplitShade()
-
- given(taskbarVisible = false,
- navigationMode = GESTURES_NAVIGATION,
- insets = windowInsets().withStableBottom())
- then(expectedContainerPadding = 0,
- expectedQsPadding = NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET)
-
- given(taskbarVisible = false,
- navigationMode = BUTTONS_NAVIGATION,
- insets = windowInsets().withStableBottom())
- then(expectedContainerPadding = 0, // qs goes full height as it's not obscuring nav buttons
- expectedNotificationsMargin = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN,
- expectedQsPadding = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET)
- }
-
- @Test
- fun testTaskbarNotVisibleInSplitShadeWithCutout() {
- enableSplitShade()
-
- given(taskbarVisible = false,
- navigationMode = GESTURES_NAVIGATION,
- insets = windowInsets().withCutout())
- then(expectedContainerPadding = CUTOUT_HEIGHT,
- expectedQsPadding = NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET)
-
- given(taskbarVisible = false,
- navigationMode = BUTTONS_NAVIGATION,
- insets = windowInsets().withCutout().withStableBottom())
- then(expectedContainerPadding = 0,
- expectedNotificationsMargin = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN,
- expectedQsPadding = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET)
- }
-
- @Test
- fun testTaskbarVisibleInSinglePaneShade() {
- disableSplitShade()
-
- given(taskbarVisible = true,
- navigationMode = GESTURES_NAVIGATION,
- insets = windowInsets().withStableBottom())
- then(expectedContainerPadding = 0,
- expectedQsPadding = STABLE_INSET_BOTTOM)
-
- given(taskbarVisible = true,
- navigationMode = BUTTONS_NAVIGATION,
- insets = windowInsets().withStableBottom())
- then(expectedContainerPadding = STABLE_INSET_BOTTOM,
- expectedQsPadding = STABLE_INSET_BOTTOM)
- }
-
- @Test
- fun testTaskbarNotVisibleInSinglePaneShade() {
- disableSplitShade()
-
- given(taskbarVisible = false,
- navigationMode = GESTURES_NAVIGATION,
- insets = emptyInsets())
- then(expectedContainerPadding = 0)
-
- given(taskbarVisible = false,
- navigationMode = GESTURES_NAVIGATION,
- insets = windowInsets().withCutout().withStableBottom())
- then(expectedContainerPadding = CUTOUT_HEIGHT, expectedQsPadding = STABLE_INSET_BOTTOM)
-
- given(taskbarVisible = false,
- navigationMode = BUTTONS_NAVIGATION,
- insets = windowInsets().withStableBottom())
- then(expectedContainerPadding = 0,
- expectedNotificationsMargin = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN,
- expectedQsPadding = STABLE_INSET_BOTTOM)
- }
-
- @Test
- fun testDetailShowingInSinglePaneShade() {
- disableSplitShade()
- controller.setDetailShowing(true)
-
- // always sets spacings to 0
- given(taskbarVisible = false,
- navigationMode = GESTURES_NAVIGATION,
- insets = windowInsets().withStableBottom())
- then(expectedContainerPadding = 0,
- expectedNotificationsMargin = 0)
-
- given(taskbarVisible = false,
- navigationMode = BUTTONS_NAVIGATION,
- insets = emptyInsets())
- then(expectedContainerPadding = 0,
- expectedNotificationsMargin = 0)
- }
-
- @Test
- fun testDetailShowingInSplitShade() {
- enableSplitShade()
- controller.setDetailShowing(true)
-
- given(taskbarVisible = false,
- navigationMode = GESTURES_NAVIGATION,
- insets = windowInsets().withStableBottom())
- then(expectedContainerPadding = 0)
-
- // should not influence spacing
- given(taskbarVisible = false,
- navigationMode = BUTTONS_NAVIGATION,
- insets = emptyInsets())
- then(expectedContainerPadding = 0)
- }
-
- @Test
- fun testNotificationsMarginBottomIsUpdated() {
- Mockito.clearInvocations(notificationsQSContainer)
- enableSplitShade()
- verify(notificationsQSContainer).setNotificationsMarginBottom(NOTIFICATIONS_MARGIN)
-
- overrideResource(R.dimen.notification_panel_margin_bottom, 100)
- disableSplitShade()
- verify(notificationsQSContainer).setNotificationsMarginBottom(100)
- }
-
- @Test
- fun testSplitShadeLayout_isAlignedToGuideline() {
- enableSplitShade()
- controller.updateResources()
- assertThat(getConstraintSetLayout(R.id.qs_frame).endToEnd)
- .isEqualTo(R.id.qs_edge_guideline)
- assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).startToStart)
- .isEqualTo(R.id.qs_edge_guideline)
- }
-
- @Test
- fun testSinglePaneLayout_childrenHaveEqualMargins() {
- disableSplitShade()
- controller.updateResources()
- val qsStartMargin = getConstraintSetLayout(R.id.qs_frame).startMargin
- val qsEndMargin = getConstraintSetLayout(R.id.qs_frame).endMargin
- val notifStartMargin = getConstraintSetLayout(R.id.notification_stack_scroller).startMargin
- val notifEndMargin = getConstraintSetLayout(R.id.notification_stack_scroller).endMargin
- assertThat(qsStartMargin == qsEndMargin &&
- notifStartMargin == notifEndMargin &&
- qsStartMargin == notifStartMargin
- ).isTrue()
- }
-
- @Test
- fun testSplitShadeLayout_childrenHaveInsideMarginsOfZero() {
- enableSplitShade()
- controller.updateResources()
- assertThat(getConstraintSetLayout(R.id.qs_frame).endMargin).isEqualTo(0)
- assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).startMargin)
- .isEqualTo(0)
- }
-
- @Test
- fun testSplitShadeLayout_qsFrameHasHorizontalMarginsOfZero() {
- enableSplitShade()
- controller.updateResources()
- assertThat(getConstraintSetLayout(R.id.qs_frame).endMargin).isEqualTo(0)
- assertThat(getConstraintSetLayout(R.id.qs_frame).startMargin).isEqualTo(0)
- }
-
- @Test
- fun testLargeScreenLayout_qsAndNotifsTopMarginIsOfHeaderHeight() {
- setLargeScreen()
- val largeScreenHeaderHeight = 100
- overrideResource(R.dimen.large_screen_shade_header_height, largeScreenHeaderHeight)
-
- controller.updateResources()
-
- assertThat(getConstraintSetLayout(R.id.qs_frame).topMargin)
- .isEqualTo(largeScreenHeaderHeight)
- assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).topMargin)
- .isEqualTo(largeScreenHeaderHeight)
- }
-
- @Test
- fun testSmallScreenLayout_qsAndNotifsTopMarginIsZero() {
- setSmallScreen()
- controller.updateResources()
- assertThat(getConstraintSetLayout(R.id.qs_frame).topMargin).isEqualTo(0)
- assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).topMargin)
- .isEqualTo(0)
- }
-
- @Test
- fun testSinglePaneShadeLayout_qsFrameHasHorizontalMarginsSetToCorrectValue() {
- disableSplitShade()
- controller.updateResources()
- val notificationPanelMarginHorizontal = context.resources
- .getDimensionPixelSize(R.dimen.notification_panel_margin_horizontal)
- assertThat(getConstraintSetLayout(R.id.qs_frame).endMargin)
- .isEqualTo(notificationPanelMarginHorizontal)
- assertThat(getConstraintSetLayout(R.id.qs_frame).startMargin)
- .isEqualTo(notificationPanelMarginHorizontal)
- }
-
- @Test
- fun testSinglePaneShadeLayout_isAlignedToParent() {
- disableSplitShade()
- controller.updateResources()
- assertThat(getConstraintSetLayout(R.id.qs_frame).endToEnd)
- .isEqualTo(ConstraintSet.PARENT_ID)
- assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).startToStart)
- .isEqualTo(ConstraintSet.PARENT_ID)
- }
-
- @Test
- fun testAllChildrenOfNotificationContainer_haveIds() {
- // set dimen to 0 to avoid triggering updating bottom spacing
- overrideResource(R.dimen.split_shade_notifications_scrim_margin_bottom, 0)
- val container = NotificationsQuickSettingsContainer(context, null)
- container.removeAllViews()
- container.addView(newViewWithId(1))
- container.addView(newViewWithId(View.NO_ID))
- val controller = NotificationsQSContainerController(
- container,
- navigationModeController,
- overviewProxyService,
- mShadeHeaderController,
- shadeExpansionStateManager,
- fragmentService,
- delayableExecutor
- )
- controller.updateConstraints()
-
- assertThat(container.getChildAt(0).id).isEqualTo(1)
- assertThat(container.getChildAt(1).id).isNotEqualTo(View.NO_ID)
- }
-
- @Test
- fun testWindowInsetDebounce() {
- disableSplitShade()
-
- given(taskbarVisible = false,
- navigationMode = GESTURES_NAVIGATION,
- insets = emptyInsets(),
- applyImmediately = false)
- fakeSystemClock.advanceTime(INSET_DEBOUNCE_MILLIS / 2)
- windowInsetsCallback.accept(windowInsets().withStableBottom())
-
- delayableExecutor.advanceClockToLast()
- delayableExecutor.runAllReady()
-
- verify(notificationsQSContainer, never()).setQSContainerPaddingBottom(0)
- verify(notificationsQSContainer).setQSContainerPaddingBottom(STABLE_INSET_BOTTOM)
- }
-
- @Test
- fun testStartCustomizingWithDuration() {
- controller.setCustomizerShowing(true, 100L)
- verify(mShadeHeaderController).startCustomizingAnimation(true, 100L)
- }
-
- @Test
- fun testEndCustomizingWithDuration() {
- controller.setCustomizerShowing(true, 0L) // Only tracks changes
- reset(mShadeHeaderController)
-
- controller.setCustomizerShowing(false, 100L)
- verify(mShadeHeaderController).startCustomizingAnimation(false, 100L)
- }
-
- @Test
- fun testTagListenerAdded() {
- verify(fragmentHostManager).addTagListener(eq(QS.TAG), eq(notificationsQSContainer))
- }
-
- @Test
- fun testTagListenerRemoved() {
- attachStateListenerCaptor.value.onViewDetachedFromWindow(notificationsQSContainer)
- verify(fragmentHostManager).removeTagListener(eq(QS.TAG), eq(notificationsQSContainer))
- }
-
- private fun disableSplitShade() {
- setSplitShadeEnabled(false)
- }
-
- private fun enableSplitShade() {
- setSplitShadeEnabled(true)
- }
-
- private fun setSplitShadeEnabled(enabled: Boolean) {
- overrideResource(R.bool.config_use_split_notification_shade, enabled)
- controller.updateResources()
- }
-
- private fun setSmallScreen() {
- setLargeScreenEnabled(false)
- }
-
- private fun setLargeScreen() {
- setLargeScreenEnabled(true)
- }
-
- private fun setLargeScreenEnabled(enabled: Boolean) {
- overrideResource(R.bool.config_use_large_screen_shade_header, enabled)
- }
-
- private fun given(
- taskbarVisible: Boolean,
- navigationMode: Int,
- insets: WindowInsets,
- applyImmediately: Boolean = true
- ) {
- Mockito.clearInvocations(notificationsQSContainer)
- taskbarVisibilityCallback.onTaskbarStatusUpdated(taskbarVisible, false)
- navigationModeCallback.onNavigationModeChanged(navigationMode)
- windowInsetsCallback.accept(insets)
- if (applyImmediately) {
- delayableExecutor.advanceClockToLast()
- delayableExecutor.runAllReady()
- }
- }
-
- fun then(
- expectedContainerPadding: Int,
- expectedNotificationsMargin: Int = NOTIFICATIONS_MARGIN,
- expectedQsPadding: Int = 0
- ) {
- verify(notificationsQSContainer)
- .setPadding(anyInt(), anyInt(), anyInt(), eq(expectedContainerPadding))
- verify(notificationsQSContainer).setNotificationsMarginBottom(expectedNotificationsMargin)
- verify(notificationsQSContainer)
- .setQSContainerPaddingBottom(expectedQsPadding)
- Mockito.clearInvocations(notificationsQSContainer)
- }
-
- private fun windowInsets() = mock(WindowInsets::class.java, RETURNS_DEEP_STUBS)
-
- private fun emptyInsets() = mock(WindowInsets::class.java)
-
- private fun WindowInsets.withCutout(): WindowInsets {
- whenever(displayCutout.safeInsetBottom).thenReturn(CUTOUT_HEIGHT)
- return this
- }
-
- private fun WindowInsets.withStableBottom(): WindowInsets {
- whenever(stableInsetBottom).thenReturn(STABLE_INSET_BOTTOM)
- return this
- }
-
- private fun getConstraintSetLayout(@IdRes id: Int): ConstraintSet.Layout {
- return constraintSetCaptor.value.getConstraint(id).layout
- }
-
- private fun newViewWithId(id: Int): View {
- val view = View(mContext)
- view.id = id
- val layoutParams = ConstraintLayout.LayoutParams(
- ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)
- // required as cloning ConstraintSet fails if view doesn't have layout params
- view.layoutParams = layoutParams
- return view
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
index 5fb3a79..2a398c55 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -111,6 +111,8 @@
@Mock private lateinit var lockIconViewController: LockIconViewController
@Mock private lateinit var phoneStatusBarViewController: PhoneStatusBarViewController
@Mock private lateinit var pulsingGestureListener: PulsingGestureListener
+ @Mock
+ private lateinit var mLockscreenHostedDreamGestureListener: LockscreenHostedDreamGestureListener
@Mock private lateinit var notificationInsetsController: NotificationInsetsController
@Mock lateinit var keyguardBouncerComponentFactory: KeyguardBouncerComponent.Factory
@Mock lateinit var keyguardBouncerComponent: KeyguardBouncerComponent
@@ -147,6 +149,7 @@
featureFlags.set(Flags.DUAL_SHADE, false)
featureFlags.set(Flags.SPLIT_SHADE_SUBPIXEL_OPTIMIZATION, true)
featureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true)
+ featureFlags.set(Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED, false)
val inputProxy = MultiShadeInputProxy()
testScope = TestScope()
@@ -183,6 +186,7 @@
notificationInsetsController,
ambientState,
pulsingGestureListener,
+ mLockscreenHostedDreamGestureListener,
keyguardBouncerViewModel,
keyguardBouncerComponentFactory,
mock(KeyguardMessageAreaController.Factory::class.java),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
index 544137e..d9eb9b9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
@@ -113,6 +113,8 @@
@Mock private lateinit var keyguardUnlockAnimationController: KeyguardUnlockAnimationController
@Mock private lateinit var ambientState: AmbientState
@Mock private lateinit var pulsingGestureListener: PulsingGestureListener
+ @Mock
+ private lateinit var mLockscreenHostedDreamGestureListener: LockscreenHostedDreamGestureListener
@Mock private lateinit var keyguardBouncerViewModel: KeyguardBouncerViewModel
@Mock private lateinit var keyguardBouncerComponentFactory: KeyguardBouncerComponent.Factory
@Mock private lateinit var keyguardBouncerComponent: KeyguardBouncerComponent
@@ -161,6 +163,7 @@
featureFlags.set(Flags.DUAL_SHADE, false)
featureFlags.set(Flags.SPLIT_SHADE_SUBPIXEL_OPTIMIZATION, true)
featureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true)
+ featureFlags.set(Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED, false)
val inputProxy = MultiShadeInputProxy()
testScope = TestScope()
val multiShadeInteractor =
@@ -196,6 +199,7 @@
notificationInsetsController,
ambientState,
pulsingGestureListener,
+ mLockscreenHostedDreamGestureListener,
keyguardBouncerViewModel,
keyguardBouncerComponentFactory,
Mockito.mock(KeyguardMessageAreaController.Factory::class.java),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt
new file mode 100644
index 0000000..2bc112d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt
@@ -0,0 +1,596 @@
+/*
+ * 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.shade
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.testing.TestableResources
+import android.view.View
+import android.view.ViewGroup
+import android.view.WindowInsets
+import android.view.WindowManagerPolicyConstants
+import androidx.annotation.IdRes
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.constraintlayout.widget.ConstraintSet
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.fragments.FragmentHostManager
+import com.android.systemui.fragments.FragmentService
+import com.android.systemui.navigationbar.NavigationModeController
+import com.android.systemui.navigationbar.NavigationModeController.ModeChangedListener
+import com.android.systemui.plugins.qs.QS
+import com.android.systemui.recents.OverviewProxyService
+import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import java.util.function.Consumer
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.RETURNS_DEEP_STUBS
+import org.mockito.Mockito.any
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.doNothing
+import org.mockito.Mockito.eq
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+/** Uses Flags.MIGRATE_NSSL set to false. If all goes well, this set of tests will be deleted. */
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+@SmallTest
+class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() {
+
+ @Mock lateinit var view: NotificationsQuickSettingsContainer
+ @Mock lateinit var navigationModeController: NavigationModeController
+ @Mock lateinit var overviewProxyService: OverviewProxyService
+ @Mock lateinit var shadeHeaderController: ShadeHeaderController
+ @Mock lateinit var shadeExpansionStateManager: ShadeExpansionStateManager
+ @Mock lateinit var fragmentService: FragmentService
+ @Mock lateinit var fragmentHostManager: FragmentHostManager
+ @Mock
+ lateinit var notificationStackScrollLayoutController: NotificationStackScrollLayoutController
+
+ @Captor lateinit var navigationModeCaptor: ArgumentCaptor<ModeChangedListener>
+ @Captor lateinit var taskbarVisibilityCaptor: ArgumentCaptor<OverviewProxyListener>
+ @Captor lateinit var windowInsetsCallbackCaptor: ArgumentCaptor<Consumer<WindowInsets>>
+ @Captor lateinit var constraintSetCaptor: ArgumentCaptor<ConstraintSet>
+ @Captor lateinit var attachStateListenerCaptor: ArgumentCaptor<View.OnAttachStateChangeListener>
+
+ lateinit var underTest: NotificationsQSContainerController
+
+ private lateinit var fakeResources: TestableResources
+ private lateinit var featureFlags: FakeFeatureFlags
+ private lateinit var navigationModeCallback: ModeChangedListener
+ private lateinit var taskbarVisibilityCallback: OverviewProxyListener
+ private lateinit var windowInsetsCallback: Consumer<WindowInsets>
+ private lateinit var fakeSystemClock: FakeSystemClock
+ private lateinit var delayableExecutor: FakeExecutor
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ fakeSystemClock = FakeSystemClock()
+ delayableExecutor = FakeExecutor(fakeSystemClock)
+ featureFlags = FakeFeatureFlags().apply { set(Flags.MIGRATE_NSSL, false) }
+ mContext.ensureTestableResources()
+ whenever(view.context).thenReturn(mContext)
+ whenever(view.resources).thenReturn(mContext.resources)
+
+ whenever(fragmentService.getFragmentHostManager(any())).thenReturn(fragmentHostManager)
+
+ underTest =
+ NotificationsQSContainerController(
+ view,
+ navigationModeController,
+ overviewProxyService,
+ shadeHeaderController,
+ shadeExpansionStateManager,
+ fragmentService,
+ delayableExecutor,
+ featureFlags,
+ notificationStackScrollLayoutController,
+ )
+
+ overrideResource(R.dimen.split_shade_notifications_scrim_margin_bottom, SCRIM_MARGIN)
+ overrideResource(R.dimen.notification_panel_margin_bottom, NOTIFICATIONS_MARGIN)
+ overrideResource(R.bool.config_use_split_notification_shade, false)
+ overrideResource(R.dimen.qs_footer_actions_bottom_padding, FOOTER_ACTIONS_PADDING)
+ overrideResource(R.dimen.qs_footer_action_inset, FOOTER_ACTIONS_INSET)
+ whenever(navigationModeController.addListener(navigationModeCaptor.capture()))
+ .thenReturn(GESTURES_NAVIGATION)
+ doNothing().`when`(overviewProxyService).addCallback(taskbarVisibilityCaptor.capture())
+ doNothing().`when`(view).setInsetsChangedListener(windowInsetsCallbackCaptor.capture())
+ doNothing().`when`(view).applyConstraints(constraintSetCaptor.capture())
+ doNothing().`when`(view).addOnAttachStateChangeListener(attachStateListenerCaptor.capture())
+ underTest.init()
+ attachStateListenerCaptor.value.onViewAttachedToWindow(view)
+
+ navigationModeCallback = navigationModeCaptor.value
+ taskbarVisibilityCallback = taskbarVisibilityCaptor.value
+ windowInsetsCallback = windowInsetsCallbackCaptor.value
+
+ Mockito.clearInvocations(view)
+ }
+
+ @Test
+ fun testSmallScreen_updateResources_splitShadeHeightIsSet() {
+ overrideResource(R.bool.config_use_large_screen_shade_header, false)
+ overrideResource(R.dimen.qs_header_height, 1)
+ overrideResource(R.dimen.large_screen_shade_header_height, 2)
+
+ underTest.updateResources()
+
+ val captor = ArgumentCaptor.forClass(ConstraintSet::class.java)
+ verify(view).applyConstraints(capture(captor))
+ assertThat(captor.value.getHeight(R.id.split_shade_status_bar)).isEqualTo(1)
+ }
+
+ @Test
+ fun testLargeScreen_updateResources_splitShadeHeightIsSet() {
+ overrideResource(R.bool.config_use_large_screen_shade_header, true)
+ overrideResource(R.dimen.qs_header_height, 1)
+ overrideResource(R.dimen.large_screen_shade_header_height, 2)
+
+ underTest.updateResources()
+
+ val captor = ArgumentCaptor.forClass(ConstraintSet::class.java)
+ verify(view).applyConstraints(capture(captor))
+ assertThat(captor.value.getHeight(R.id.split_shade_status_bar)).isEqualTo(2)
+ }
+
+ @Test
+ fun testTaskbarVisibleInSplitShade() {
+ enableSplitShade()
+
+ given(
+ taskbarVisible = true,
+ navigationMode = GESTURES_NAVIGATION,
+ insets = windowInsets().withStableBottom()
+ )
+ then(
+ expectedContainerPadding = 0, // taskbar should disappear when shade is expanded
+ expectedNotificationsMargin = NOTIFICATIONS_MARGIN,
+ expectedQsPadding = NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET
+ )
+
+ given(
+ taskbarVisible = true,
+ navigationMode = BUTTONS_NAVIGATION,
+ insets = windowInsets().withStableBottom()
+ )
+ then(
+ expectedContainerPadding = STABLE_INSET_BOTTOM,
+ expectedNotificationsMargin = NOTIFICATIONS_MARGIN,
+ expectedQsPadding = NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET
+ )
+ }
+
+ @Test
+ fun testTaskbarNotVisibleInSplitShade() {
+ // when taskbar is not visible, it means we're on the home screen
+ enableSplitShade()
+
+ given(
+ taskbarVisible = false,
+ navigationMode = GESTURES_NAVIGATION,
+ insets = windowInsets().withStableBottom()
+ )
+ then(
+ expectedContainerPadding = 0,
+ expectedQsPadding = NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET
+ )
+
+ given(
+ taskbarVisible = false,
+ navigationMode = BUTTONS_NAVIGATION,
+ insets = windowInsets().withStableBottom()
+ )
+ then(
+ expectedContainerPadding = 0, // qs goes full height as it's not obscuring nav buttons
+ expectedNotificationsMargin = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN,
+ expectedQsPadding = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET
+ )
+ }
+
+ @Test
+ fun testTaskbarNotVisibleInSplitShadeWithCutout() {
+ enableSplitShade()
+
+ given(
+ taskbarVisible = false,
+ navigationMode = GESTURES_NAVIGATION,
+ insets = windowInsets().withCutout()
+ )
+ then(
+ expectedContainerPadding = CUTOUT_HEIGHT,
+ expectedQsPadding = NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET
+ )
+
+ given(
+ taskbarVisible = false,
+ navigationMode = BUTTONS_NAVIGATION,
+ insets = windowInsets().withCutout().withStableBottom()
+ )
+ then(
+ expectedContainerPadding = 0,
+ expectedNotificationsMargin = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN,
+ expectedQsPadding = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET
+ )
+ }
+
+ @Test
+ fun testTaskbarVisibleInSinglePaneShade() {
+ disableSplitShade()
+
+ given(
+ taskbarVisible = true,
+ navigationMode = GESTURES_NAVIGATION,
+ insets = windowInsets().withStableBottom()
+ )
+ then(expectedContainerPadding = 0, expectedQsPadding = STABLE_INSET_BOTTOM)
+
+ given(
+ taskbarVisible = true,
+ navigationMode = BUTTONS_NAVIGATION,
+ insets = windowInsets().withStableBottom()
+ )
+ then(
+ expectedContainerPadding = STABLE_INSET_BOTTOM,
+ expectedQsPadding = STABLE_INSET_BOTTOM
+ )
+ }
+
+ @Test
+ fun testTaskbarNotVisibleInSinglePaneShade() {
+ disableSplitShade()
+
+ given(taskbarVisible = false, navigationMode = GESTURES_NAVIGATION, insets = emptyInsets())
+ then(expectedContainerPadding = 0)
+
+ given(
+ taskbarVisible = false,
+ navigationMode = GESTURES_NAVIGATION,
+ insets = windowInsets().withCutout().withStableBottom()
+ )
+ then(expectedContainerPadding = CUTOUT_HEIGHT, expectedQsPadding = STABLE_INSET_BOTTOM)
+
+ given(
+ taskbarVisible = false,
+ navigationMode = BUTTONS_NAVIGATION,
+ insets = windowInsets().withStableBottom()
+ )
+ then(
+ expectedContainerPadding = 0,
+ expectedNotificationsMargin = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN,
+ expectedQsPadding = STABLE_INSET_BOTTOM
+ )
+ }
+
+ @Test
+ fun testDetailShowingInSinglePaneShade() {
+ disableSplitShade()
+ underTest.setDetailShowing(true)
+
+ // always sets spacings to 0
+ given(
+ taskbarVisible = false,
+ navigationMode = GESTURES_NAVIGATION,
+ insets = windowInsets().withStableBottom()
+ )
+ then(expectedContainerPadding = 0, expectedNotificationsMargin = 0)
+
+ given(taskbarVisible = false, navigationMode = BUTTONS_NAVIGATION, insets = emptyInsets())
+ then(expectedContainerPadding = 0, expectedNotificationsMargin = 0)
+ }
+
+ @Test
+ fun testDetailShowingInSplitShade() {
+ enableSplitShade()
+ underTest.setDetailShowing(true)
+
+ given(
+ taskbarVisible = false,
+ navigationMode = GESTURES_NAVIGATION,
+ insets = windowInsets().withStableBottom()
+ )
+ then(expectedContainerPadding = 0)
+
+ // should not influence spacing
+ given(taskbarVisible = false, navigationMode = BUTTONS_NAVIGATION, insets = emptyInsets())
+ then(expectedContainerPadding = 0)
+ }
+
+ @Test
+ fun testNotificationsMarginBottomIsUpdated() {
+ Mockito.clearInvocations(view)
+ enableSplitShade()
+ verify(view).setNotificationsMarginBottom(NOTIFICATIONS_MARGIN)
+
+ overrideResource(R.dimen.notification_panel_margin_bottom, 100)
+ disableSplitShade()
+ verify(view).setNotificationsMarginBottom(100)
+ }
+
+ @Test
+ fun testSplitShadeLayout_isAlignedToGuideline() {
+ enableSplitShade()
+ underTest.updateResources()
+ assertThat(getConstraintSetLayout(R.id.qs_frame).endToEnd).isEqualTo(R.id.qs_edge_guideline)
+ assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).startToStart)
+ .isEqualTo(R.id.qs_edge_guideline)
+ }
+
+ @Test
+ fun testSinglePaneLayout_childrenHaveEqualMargins() {
+ disableSplitShade()
+ underTest.updateResources()
+ val qsStartMargin = getConstraintSetLayout(R.id.qs_frame).startMargin
+ val qsEndMargin = getConstraintSetLayout(R.id.qs_frame).endMargin
+ val notifStartMargin = getConstraintSetLayout(R.id.notification_stack_scroller).startMargin
+ val notifEndMargin = getConstraintSetLayout(R.id.notification_stack_scroller).endMargin
+ assertThat(
+ qsStartMargin == qsEndMargin &&
+ notifStartMargin == notifEndMargin &&
+ qsStartMargin == notifStartMargin
+ )
+ .isTrue()
+ }
+
+ @Test
+ fun testSplitShadeLayout_childrenHaveInsideMarginsOfZero() {
+ enableSplitShade()
+ underTest.updateResources()
+ assertThat(getConstraintSetLayout(R.id.qs_frame).endMargin).isEqualTo(0)
+ assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).startMargin)
+ .isEqualTo(0)
+ }
+
+ @Test
+ fun testSplitShadeLayout_qsFrameHasHorizontalMarginsOfZero() {
+ enableSplitShade()
+ underTest.updateResources()
+ assertThat(getConstraintSetLayout(R.id.qs_frame).endMargin).isEqualTo(0)
+ assertThat(getConstraintSetLayout(R.id.qs_frame).startMargin).isEqualTo(0)
+ }
+
+ @Test
+ fun testLargeScreenLayout_qsAndNotifsTopMarginIsOfHeaderHeight() {
+ setLargeScreen()
+ val largeScreenHeaderHeight = 100
+ overrideResource(R.dimen.large_screen_shade_header_height, largeScreenHeaderHeight)
+
+ underTest.updateResources()
+
+ assertThat(getConstraintSetLayout(R.id.qs_frame).topMargin)
+ .isEqualTo(largeScreenHeaderHeight)
+ assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).topMargin)
+ .isEqualTo(largeScreenHeaderHeight)
+ }
+
+ @Test
+ fun testSmallScreenLayout_qsAndNotifsTopMarginIsZero() {
+ setSmallScreen()
+ underTest.updateResources()
+ assertThat(getConstraintSetLayout(R.id.qs_frame).topMargin).isEqualTo(0)
+ assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).topMargin).isEqualTo(0)
+ }
+
+ @Test
+ fun testSinglePaneShadeLayout_qsFrameHasHorizontalMarginsSetToCorrectValue() {
+ disableSplitShade()
+ underTest.updateResources()
+ val notificationPanelMarginHorizontal =
+ mContext.resources.getDimensionPixelSize(R.dimen.notification_panel_margin_horizontal)
+ assertThat(getConstraintSetLayout(R.id.qs_frame).endMargin)
+ .isEqualTo(notificationPanelMarginHorizontal)
+ assertThat(getConstraintSetLayout(R.id.qs_frame).startMargin)
+ .isEqualTo(notificationPanelMarginHorizontal)
+ }
+
+ @Test
+ fun testSinglePaneShadeLayout_isAlignedToParent() {
+ disableSplitShade()
+ underTest.updateResources()
+ assertThat(getConstraintSetLayout(R.id.qs_frame).endToEnd)
+ .isEqualTo(ConstraintSet.PARENT_ID)
+ assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).startToStart)
+ .isEqualTo(ConstraintSet.PARENT_ID)
+ }
+
+ @Test
+ fun testAllChildrenOfNotificationContainer_haveIds() {
+ // set dimen to 0 to avoid triggering updating bottom spacing
+ overrideResource(R.dimen.split_shade_notifications_scrim_margin_bottom, 0)
+ val container = NotificationsQuickSettingsContainer(mContext, null)
+ container.removeAllViews()
+ container.addView(newViewWithId(1))
+ container.addView(newViewWithId(View.NO_ID))
+ val controller =
+ NotificationsQSContainerController(
+ container,
+ navigationModeController,
+ overviewProxyService,
+ shadeHeaderController,
+ shadeExpansionStateManager,
+ fragmentService,
+ delayableExecutor,
+ featureFlags,
+ notificationStackScrollLayoutController,
+ )
+ controller.updateConstraints()
+
+ assertThat(container.getChildAt(0).id).isEqualTo(1)
+ assertThat(container.getChildAt(1).id).isNotEqualTo(View.NO_ID)
+ }
+
+ @Test
+ fun testWindowInsetDebounce() {
+ disableSplitShade()
+
+ given(
+ taskbarVisible = false,
+ navigationMode = GESTURES_NAVIGATION,
+ insets = emptyInsets(),
+ applyImmediately = false
+ )
+ fakeSystemClock.advanceTime(INSET_DEBOUNCE_MILLIS / 2)
+ windowInsetsCallback.accept(windowInsets().withStableBottom())
+
+ delayableExecutor.advanceClockToLast()
+ delayableExecutor.runAllReady()
+
+ verify(view, never()).setQSContainerPaddingBottom(0)
+ verify(view).setQSContainerPaddingBottom(STABLE_INSET_BOTTOM)
+ }
+
+ @Test
+ fun testStartCustomizingWithDuration() {
+ underTest.setCustomizerShowing(true, 100L)
+ verify(shadeHeaderController).startCustomizingAnimation(true, 100L)
+ }
+
+ @Test
+ fun testEndCustomizingWithDuration() {
+ underTest.setCustomizerShowing(true, 0L) // Only tracks changes
+ reset(shadeHeaderController)
+
+ underTest.setCustomizerShowing(false, 100L)
+ verify(shadeHeaderController).startCustomizingAnimation(false, 100L)
+ }
+
+ @Test
+ fun testTagListenerAdded() {
+ verify(fragmentHostManager).addTagListener(eq(QS.TAG), eq(view))
+ }
+
+ @Test
+ fun testTagListenerRemoved() {
+ attachStateListenerCaptor.value.onViewDetachedFromWindow(view)
+ verify(fragmentHostManager).removeTagListener(eq(QS.TAG), eq(view))
+ }
+
+ private fun disableSplitShade() {
+ setSplitShadeEnabled(false)
+ }
+
+ private fun enableSplitShade() {
+ setSplitShadeEnabled(true)
+ }
+
+ private fun setSplitShadeEnabled(enabled: Boolean) {
+ overrideResource(R.bool.config_use_split_notification_shade, enabled)
+ underTest.updateResources()
+ }
+
+ private fun setSmallScreen() {
+ setLargeScreenEnabled(false)
+ }
+
+ private fun setLargeScreen() {
+ setLargeScreenEnabled(true)
+ }
+
+ private fun setLargeScreenEnabled(enabled: Boolean) {
+ overrideResource(R.bool.config_use_large_screen_shade_header, enabled)
+ }
+
+ private fun given(
+ taskbarVisible: Boolean,
+ navigationMode: Int,
+ insets: WindowInsets,
+ applyImmediately: Boolean = true
+ ) {
+ Mockito.clearInvocations(view)
+ taskbarVisibilityCallback.onTaskbarStatusUpdated(taskbarVisible, false)
+ navigationModeCallback.onNavigationModeChanged(navigationMode)
+ windowInsetsCallback.accept(insets)
+ if (applyImmediately) {
+ delayableExecutor.advanceClockToLast()
+ delayableExecutor.runAllReady()
+ }
+ }
+
+ fun then(
+ expectedContainerPadding: Int,
+ expectedNotificationsMargin: Int = NOTIFICATIONS_MARGIN,
+ expectedQsPadding: Int = 0
+ ) {
+ verify(view).setPadding(anyInt(), anyInt(), anyInt(), eq(expectedContainerPadding))
+ verify(view).setNotificationsMarginBottom(expectedNotificationsMargin)
+ verify(view).setQSContainerPaddingBottom(expectedQsPadding)
+ Mockito.clearInvocations(view)
+ }
+
+ private fun windowInsets() = mock(WindowInsets::class.java, RETURNS_DEEP_STUBS)
+
+ private fun emptyInsets() = mock(WindowInsets::class.java)
+
+ private fun WindowInsets.withCutout(): WindowInsets {
+ whenever(displayCutout.safeInsetBottom).thenReturn(CUTOUT_HEIGHT)
+ return this
+ }
+
+ private fun WindowInsets.withStableBottom(): WindowInsets {
+ whenever(stableInsetBottom).thenReturn(STABLE_INSET_BOTTOM)
+ return this
+ }
+
+ private fun getConstraintSetLayout(@IdRes id: Int): ConstraintSet.Layout {
+ return constraintSetCaptor.value.getConstraint(id).layout
+ }
+
+ private fun newViewWithId(id: Int): View {
+ val view = View(mContext)
+ view.id = id
+ val layoutParams =
+ ConstraintLayout.LayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT
+ )
+ // required as cloning ConstraintSet fails if view doesn't have layout params
+ view.layoutParams = layoutParams
+ return view
+ }
+
+ companion object {
+ const val STABLE_INSET_BOTTOM = 100
+ const val CUTOUT_HEIGHT = 50
+ const val GESTURES_NAVIGATION = WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL
+ const val BUTTONS_NAVIGATION = WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON
+ const val NOTIFICATIONS_MARGIN = 50
+ const val SCRIM_MARGIN = 10
+ const val FOOTER_ACTIONS_INSET = 2
+ const val FOOTER_ACTIONS_PADDING = 2
+ const val FOOTER_ACTIONS_OFFSET = FOOTER_ACTIONS_INSET + FOOTER_ACTIONS_PADDING
+ const val QS_PADDING_OFFSET = SCRIM_MARGIN + FOOTER_ACTIONS_OFFSET
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt
index d4751c8..a504818 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt
@@ -19,24 +19,47 @@
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.testing.TestableResources
+import android.view.View
+import android.view.ViewGroup
+import android.view.WindowInsets
+import android.view.WindowManagerPolicyConstants
+import androidx.annotation.IdRes
+import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import androidx.test.filters.SmallTest
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.fragments.FragmentHostManager
import com.android.systemui.fragments.FragmentService
import com.android.systemui.navigationbar.NavigationModeController
+import com.android.systemui.navigationbar.NavigationModeController.ModeChangedListener
+import com.android.systemui.plugins.qs.QS
import com.android.systemui.recents.OverviewProxyService
-import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.capture
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
+import java.util.function.Consumer
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
+import org.mockito.Captor
import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.RETURNS_DEEP_STUBS
+import org.mockito.Mockito.any
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.doNothing
+import org.mockito.Mockito.eq
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@@ -51,19 +74,37 @@
@Mock lateinit var shadeHeaderController: ShadeHeaderController
@Mock lateinit var shadeExpansionStateManager: ShadeExpansionStateManager
@Mock lateinit var fragmentService: FragmentService
+ @Mock lateinit var fragmentHostManager: FragmentHostManager
+ @Mock
+ lateinit var notificationStackScrollLayoutController: NotificationStackScrollLayoutController
+
+ @Captor lateinit var navigationModeCaptor: ArgumentCaptor<ModeChangedListener>
+ @Captor lateinit var taskbarVisibilityCaptor: ArgumentCaptor<OverviewProxyListener>
+ @Captor lateinit var windowInsetsCallbackCaptor: ArgumentCaptor<Consumer<WindowInsets>>
+ @Captor lateinit var constraintSetCaptor: ArgumentCaptor<ConstraintSet>
+ @Captor lateinit var attachStateListenerCaptor: ArgumentCaptor<View.OnAttachStateChangeListener>
lateinit var underTest: NotificationsQSContainerController
private lateinit var fakeResources: TestableResources
-
- private val delayableExecutor: DelayableExecutor = FakeExecutor(FakeSystemClock())
+ private lateinit var featureFlags: FakeFeatureFlags
+ private lateinit var navigationModeCallback: ModeChangedListener
+ private lateinit var taskbarVisibilityCallback: OverviewProxyListener
+ private lateinit var windowInsetsCallback: Consumer<WindowInsets>
+ private lateinit var fakeSystemClock: FakeSystemClock
+ private lateinit var delayableExecutor: FakeExecutor
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
- fakeResources = TestableResources(context.resources)
+ fakeSystemClock = FakeSystemClock()
+ delayableExecutor = FakeExecutor(fakeSystemClock)
+ featureFlags = FakeFeatureFlags().apply { set(Flags.MIGRATE_NSSL, true) }
+ mContext.ensureTestableResources()
+ whenever(view.context).thenReturn(mContext)
+ whenever(view.resources).thenReturn(mContext.resources)
- whenever(view.resources).thenReturn(fakeResources.resources)
+ whenever(fragmentService.getFragmentHostManager(any())).thenReturn(fragmentHostManager)
underTest =
NotificationsQSContainerController(
@@ -74,16 +115,36 @@
shadeExpansionStateManager,
fragmentService,
delayableExecutor,
+ featureFlags,
+ notificationStackScrollLayoutController,
)
+
+ overrideResource(R.dimen.split_shade_notifications_scrim_margin_bottom, SCRIM_MARGIN)
+ overrideResource(R.dimen.notification_panel_margin_bottom, NOTIFICATIONS_MARGIN)
+ overrideResource(R.bool.config_use_split_notification_shade, false)
+ overrideResource(R.dimen.qs_footer_actions_bottom_padding, FOOTER_ACTIONS_PADDING)
+ overrideResource(R.dimen.qs_footer_action_inset, FOOTER_ACTIONS_INSET)
+ whenever(navigationModeController.addListener(navigationModeCaptor.capture()))
+ .thenReturn(GESTURES_NAVIGATION)
+ doNothing().`when`(overviewProxyService).addCallback(taskbarVisibilityCaptor.capture())
+ doNothing().`when`(view).setInsetsChangedListener(windowInsetsCallbackCaptor.capture())
+ doNothing().`when`(view).applyConstraints(constraintSetCaptor.capture())
+ doNothing().`when`(view).addOnAttachStateChangeListener(attachStateListenerCaptor.capture())
+ underTest.init()
+ attachStateListenerCaptor.value.onViewAttachedToWindow(view)
+
+ navigationModeCallback = navigationModeCaptor.value
+ taskbarVisibilityCallback = taskbarVisibilityCaptor.value
+ windowInsetsCallback = windowInsetsCallbackCaptor.value
+
+ Mockito.clearInvocations(view)
}
@Test
fun testSmallScreen_updateResources_splitShadeHeightIsSet() {
- with(fakeResources) {
- addOverride(R.bool.config_use_large_screen_shade_header, false)
- addOverride(R.dimen.qs_header_height, 1)
- addOverride(R.dimen.large_screen_shade_header_height, 2)
- }
+ overrideResource(R.bool.config_use_large_screen_shade_header, false)
+ overrideResource(R.dimen.qs_header_height, 1)
+ overrideResource(R.dimen.large_screen_shade_header_height, 2)
underTest.updateResources()
@@ -94,11 +155,9 @@
@Test
fun testLargeScreen_updateResources_splitShadeHeightIsSet() {
- with(fakeResources) {
- addOverride(R.bool.config_use_large_screen_shade_header, true)
- addOverride(R.dimen.qs_header_height, 1)
- addOverride(R.dimen.large_screen_shade_header_height, 2)
- }
+ overrideResource(R.bool.config_use_large_screen_shade_header, true)
+ overrideResource(R.dimen.qs_header_height, 1)
+ overrideResource(R.dimen.large_screen_shade_header_height, 2)
underTest.updateResources()
@@ -106,4 +165,415 @@
verify(view).applyConstraints(capture(captor))
assertThat(captor.value.getHeight(R.id.split_shade_status_bar)).isEqualTo(2)
}
+
+ @Test
+ fun testTaskbarVisibleInSplitShade() {
+ enableSplitShade()
+
+ given(
+ taskbarVisible = true,
+ navigationMode = GESTURES_NAVIGATION,
+ insets = windowInsets().withStableBottom()
+ )
+ then(
+ expectedContainerPadding = 0, // taskbar should disappear when shade is expanded
+ expectedNotificationsMargin = NOTIFICATIONS_MARGIN,
+ expectedQsPadding = NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET
+ )
+
+ given(
+ taskbarVisible = true,
+ navigationMode = BUTTONS_NAVIGATION,
+ insets = windowInsets().withStableBottom()
+ )
+ then(
+ expectedContainerPadding = STABLE_INSET_BOTTOM,
+ expectedNotificationsMargin = NOTIFICATIONS_MARGIN,
+ expectedQsPadding = NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET
+ )
+ }
+
+ @Test
+ fun testTaskbarNotVisibleInSplitShade() {
+ // when taskbar is not visible, it means we're on the home screen
+ enableSplitShade()
+
+ given(
+ taskbarVisible = false,
+ navigationMode = GESTURES_NAVIGATION,
+ insets = windowInsets().withStableBottom()
+ )
+ then(
+ expectedContainerPadding = 0,
+ expectedQsPadding = NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET
+ )
+
+ given(
+ taskbarVisible = false,
+ navigationMode = BUTTONS_NAVIGATION,
+ insets = windowInsets().withStableBottom()
+ )
+ then(
+ expectedContainerPadding = 0, // qs goes full height as it's not obscuring nav buttons
+ expectedNotificationsMargin = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN,
+ expectedQsPadding = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET
+ )
+ }
+
+ @Test
+ fun testTaskbarNotVisibleInSplitShadeWithCutout() {
+ enableSplitShade()
+
+ given(
+ taskbarVisible = false,
+ navigationMode = GESTURES_NAVIGATION,
+ insets = windowInsets().withCutout()
+ )
+ then(
+ expectedContainerPadding = CUTOUT_HEIGHT,
+ expectedQsPadding = NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET
+ )
+
+ given(
+ taskbarVisible = false,
+ navigationMode = BUTTONS_NAVIGATION,
+ insets = windowInsets().withCutout().withStableBottom()
+ )
+ then(
+ expectedContainerPadding = 0,
+ expectedNotificationsMargin = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN,
+ expectedQsPadding = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET
+ )
+ }
+
+ @Test
+ fun testTaskbarVisibleInSinglePaneShade() {
+ disableSplitShade()
+
+ given(
+ taskbarVisible = true,
+ navigationMode = GESTURES_NAVIGATION,
+ insets = windowInsets().withStableBottom()
+ )
+ then(expectedContainerPadding = 0, expectedQsPadding = STABLE_INSET_BOTTOM)
+
+ given(
+ taskbarVisible = true,
+ navigationMode = BUTTONS_NAVIGATION,
+ insets = windowInsets().withStableBottom()
+ )
+ then(
+ expectedContainerPadding = STABLE_INSET_BOTTOM,
+ expectedQsPadding = STABLE_INSET_BOTTOM
+ )
+ }
+
+ @Test
+ fun testTaskbarNotVisibleInSinglePaneShade() {
+ disableSplitShade()
+
+ given(taskbarVisible = false, navigationMode = GESTURES_NAVIGATION, insets = emptyInsets())
+ then(expectedContainerPadding = 0)
+
+ given(
+ taskbarVisible = false,
+ navigationMode = GESTURES_NAVIGATION,
+ insets = windowInsets().withCutout().withStableBottom()
+ )
+ then(expectedContainerPadding = CUTOUT_HEIGHT, expectedQsPadding = STABLE_INSET_BOTTOM)
+
+ given(
+ taskbarVisible = false,
+ navigationMode = BUTTONS_NAVIGATION,
+ insets = windowInsets().withStableBottom()
+ )
+ then(
+ expectedContainerPadding = 0,
+ expectedNotificationsMargin = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN,
+ expectedQsPadding = STABLE_INSET_BOTTOM
+ )
+ }
+
+ @Test
+ fun testDetailShowingInSinglePaneShade() {
+ disableSplitShade()
+ underTest.setDetailShowing(true)
+
+ // always sets spacings to 0
+ given(
+ taskbarVisible = false,
+ navigationMode = GESTURES_NAVIGATION,
+ insets = windowInsets().withStableBottom()
+ )
+ then(expectedContainerPadding = 0, expectedNotificationsMargin = 0)
+
+ given(taskbarVisible = false, navigationMode = BUTTONS_NAVIGATION, insets = emptyInsets())
+ then(expectedContainerPadding = 0, expectedNotificationsMargin = 0)
+ }
+
+ @Test
+ fun testDetailShowingInSplitShade() {
+ enableSplitShade()
+ underTest.setDetailShowing(true)
+
+ given(
+ taskbarVisible = false,
+ navigationMode = GESTURES_NAVIGATION,
+ insets = windowInsets().withStableBottom()
+ )
+ then(expectedContainerPadding = 0)
+
+ // should not influence spacing
+ given(taskbarVisible = false, navigationMode = BUTTONS_NAVIGATION, insets = emptyInsets())
+ then(expectedContainerPadding = 0)
+ }
+
+ @Test
+ fun testNotificationsMarginBottomIsUpdated() {
+ Mockito.clearInvocations(view)
+ enableSplitShade()
+ verify(view).setNotificationsMarginBottom(NOTIFICATIONS_MARGIN)
+
+ overrideResource(R.dimen.notification_panel_margin_bottom, 100)
+ disableSplitShade()
+ verify(view).setNotificationsMarginBottom(100)
+ }
+
+ @Test
+ fun testSplitShadeLayout_isAlignedToGuideline() {
+ enableSplitShade()
+ underTest.updateResources()
+ assertThat(getConstraintSetLayout(R.id.qs_frame).endToEnd).isEqualTo(R.id.qs_edge_guideline)
+ }
+
+ @Test
+ fun testSinglePaneLayout_childrenHaveEqualMargins() {
+ disableSplitShade()
+ underTest.updateResources()
+ val qsStartMargin = getConstraintSetLayout(R.id.qs_frame).startMargin
+ val qsEndMargin = getConstraintSetLayout(R.id.qs_frame).endMargin
+ assertThat(qsStartMargin == qsEndMargin).isTrue()
+ }
+
+ @Test
+ fun testSplitShadeLayout_childrenHaveInsideMarginsOfZero() {
+ enableSplitShade()
+ underTest.updateResources()
+ assertThat(getConstraintSetLayout(R.id.qs_frame).endMargin).isEqualTo(0)
+ }
+
+ @Test
+ fun testSplitShadeLayout_qsFrameHasHorizontalMarginsOfZero() {
+ enableSplitShade()
+ underTest.updateResources()
+ assertThat(getConstraintSetLayout(R.id.qs_frame).endMargin).isEqualTo(0)
+ assertThat(getConstraintSetLayout(R.id.qs_frame).startMargin).isEqualTo(0)
+ }
+
+ @Test
+ fun testLargeScreenLayout_qsAndNotifsTopMarginIsOfHeaderHeight() {
+ setLargeScreen()
+ val largeScreenHeaderHeight = 100
+ overrideResource(R.dimen.large_screen_shade_header_height, largeScreenHeaderHeight)
+
+ underTest.updateResources()
+
+ assertThat(getConstraintSetLayout(R.id.qs_frame).topMargin)
+ .isEqualTo(largeScreenHeaderHeight)
+ }
+
+ @Test
+ fun testSmallScreenLayout_qsAndNotifsTopMarginIsZero() {
+ setSmallScreen()
+ underTest.updateResources()
+ assertThat(getConstraintSetLayout(R.id.qs_frame).topMargin).isEqualTo(0)
+ }
+
+ @Test
+ fun testSinglePaneShadeLayout_qsFrameHasHorizontalMarginsSetToCorrectValue() {
+ disableSplitShade()
+ underTest.updateResources()
+ val notificationPanelMarginHorizontal =
+ mContext.resources.getDimensionPixelSize(R.dimen.notification_panel_margin_horizontal)
+ assertThat(getConstraintSetLayout(R.id.qs_frame).endMargin)
+ .isEqualTo(notificationPanelMarginHorizontal)
+ assertThat(getConstraintSetLayout(R.id.qs_frame).startMargin)
+ .isEqualTo(notificationPanelMarginHorizontal)
+ }
+
+ @Test
+ fun testSinglePaneShadeLayout_isAlignedToParent() {
+ disableSplitShade()
+ underTest.updateResources()
+ assertThat(getConstraintSetLayout(R.id.qs_frame).endToEnd)
+ .isEqualTo(ConstraintSet.PARENT_ID)
+ }
+
+ @Test
+ fun testAllChildrenOfNotificationContainer_haveIds() {
+ // set dimen to 0 to avoid triggering updating bottom spacing
+ overrideResource(R.dimen.split_shade_notifications_scrim_margin_bottom, 0)
+ val container = NotificationsQuickSettingsContainer(mContext, null)
+ container.removeAllViews()
+ container.addView(newViewWithId(1))
+ container.addView(newViewWithId(View.NO_ID))
+ val controller =
+ NotificationsQSContainerController(
+ container,
+ navigationModeController,
+ overviewProxyService,
+ shadeHeaderController,
+ shadeExpansionStateManager,
+ fragmentService,
+ delayableExecutor,
+ featureFlags,
+ notificationStackScrollLayoutController,
+ )
+ controller.updateConstraints()
+
+ assertThat(container.getChildAt(0).id).isEqualTo(1)
+ assertThat(container.getChildAt(1).id).isNotEqualTo(View.NO_ID)
+ }
+
+ @Test
+ fun testWindowInsetDebounce() {
+ disableSplitShade()
+
+ given(
+ taskbarVisible = false,
+ navigationMode = GESTURES_NAVIGATION,
+ insets = emptyInsets(),
+ applyImmediately = false
+ )
+ fakeSystemClock.advanceTime(INSET_DEBOUNCE_MILLIS / 2)
+ windowInsetsCallback.accept(windowInsets().withStableBottom())
+
+ delayableExecutor.advanceClockToLast()
+ delayableExecutor.runAllReady()
+
+ verify(view, never()).setQSContainerPaddingBottom(0)
+ verify(view).setQSContainerPaddingBottom(STABLE_INSET_BOTTOM)
+ }
+
+ @Test
+ fun testStartCustomizingWithDuration() {
+ underTest.setCustomizerShowing(true, 100L)
+ verify(shadeHeaderController).startCustomizingAnimation(true, 100L)
+ }
+
+ @Test
+ fun testEndCustomizingWithDuration() {
+ underTest.setCustomizerShowing(true, 0L) // Only tracks changes
+ reset(shadeHeaderController)
+
+ underTest.setCustomizerShowing(false, 100L)
+ verify(shadeHeaderController).startCustomizingAnimation(false, 100L)
+ }
+
+ @Test
+ fun testTagListenerAdded() {
+ verify(fragmentHostManager).addTagListener(eq(QS.TAG), eq(view))
+ }
+
+ @Test
+ fun testTagListenerRemoved() {
+ attachStateListenerCaptor.value.onViewDetachedFromWindow(view)
+ verify(fragmentHostManager).removeTagListener(eq(QS.TAG), eq(view))
+ }
+
+ private fun disableSplitShade() {
+ setSplitShadeEnabled(false)
+ }
+
+ private fun enableSplitShade() {
+ setSplitShadeEnabled(true)
+ }
+
+ private fun setSplitShadeEnabled(enabled: Boolean) {
+ overrideResource(R.bool.config_use_split_notification_shade, enabled)
+ underTest.updateResources()
+ }
+
+ private fun setSmallScreen() {
+ setLargeScreenEnabled(false)
+ }
+
+ private fun setLargeScreen() {
+ setLargeScreenEnabled(true)
+ }
+
+ private fun setLargeScreenEnabled(enabled: Boolean) {
+ overrideResource(R.bool.config_use_large_screen_shade_header, enabled)
+ }
+
+ private fun given(
+ taskbarVisible: Boolean,
+ navigationMode: Int,
+ insets: WindowInsets,
+ applyImmediately: Boolean = true
+ ) {
+ Mockito.clearInvocations(view)
+ taskbarVisibilityCallback.onTaskbarStatusUpdated(taskbarVisible, false)
+ navigationModeCallback.onNavigationModeChanged(navigationMode)
+ windowInsetsCallback.accept(insets)
+ if (applyImmediately) {
+ delayableExecutor.advanceClockToLast()
+ delayableExecutor.runAllReady()
+ }
+ }
+
+ fun then(
+ expectedContainerPadding: Int,
+ expectedNotificationsMargin: Int = NOTIFICATIONS_MARGIN,
+ expectedQsPadding: Int = 0
+ ) {
+ verify(view).setPadding(anyInt(), anyInt(), anyInt(), eq(expectedContainerPadding))
+ verify(view).setNotificationsMarginBottom(expectedNotificationsMargin)
+ verify(view).setQSContainerPaddingBottom(expectedQsPadding)
+ Mockito.clearInvocations(view)
+ }
+
+ private fun windowInsets() = mock(WindowInsets::class.java, RETURNS_DEEP_STUBS)
+
+ private fun emptyInsets() = mock(WindowInsets::class.java)
+
+ private fun WindowInsets.withCutout(): WindowInsets {
+ whenever(displayCutout.safeInsetBottom).thenReturn(CUTOUT_HEIGHT)
+ return this
+ }
+
+ private fun WindowInsets.withStableBottom(): WindowInsets {
+ whenever(stableInsetBottom).thenReturn(STABLE_INSET_BOTTOM)
+ return this
+ }
+
+ private fun getConstraintSetLayout(@IdRes id: Int): ConstraintSet.Layout {
+ return constraintSetCaptor.value.getConstraint(id).layout
+ }
+
+ private fun newViewWithId(id: Int): View {
+ val view = View(mContext)
+ view.id = id
+ val layoutParams =
+ ConstraintLayout.LayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT
+ )
+ // required as cloning ConstraintSet fails if view doesn't have layout params
+ view.layoutParams = layoutParams
+ return view
+ }
+
+ companion object {
+ const val STABLE_INSET_BOTTOM = 100
+ const val CUTOUT_HEIGHT = 50
+ const val GESTURES_NAVIGATION = WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL
+ const val BUTTONS_NAVIGATION = WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON
+ const val NOTIFICATIONS_MARGIN = 50
+ const val SCRIM_MARGIN = 10
+ const val FOOTER_ACTIONS_INSET = 2
+ const val FOOTER_ACTIONS_PADDING = 2
+ const val FOOTER_ACTIONS_OFFSET = FOOTER_ACTIONS_INSET + FOOTER_ACTIONS_PADDING
+ const val QS_PADDING_OFFSET = SCRIM_MARGIN + FOOTER_ACTIONS_OFFSET
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/PulsingGestureListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/PulsingGestureListenerTest.kt
index a4fab1d..77a22ac 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/PulsingGestureListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/PulsingGestureListenerTest.kt
@@ -28,6 +28,7 @@
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.dock.DockManager
import com.android.systemui.dump.DumpManager
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.power.data.repository.FakePowerRepository
@@ -89,6 +90,7 @@
dockManager,
PowerInteractor(
powerRepository,
+ FakeKeyguardRepository(),
falsingCollector,
screenOffAnimationController,
statusBarStateController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt
index 729c4a9..52e0c9c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt
@@ -78,11 +78,11 @@
deviceProvisionedController,
notificationShadeWindowController,
windowManager,
+ Lazy { shadeViewController },
Lazy { assistManager },
Lazy { gutsManager },
)
shadeController.setNotificationShadeWindowViewController(nswvc)
- shadeController.setShadeViewController(shadeViewController)
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt
index f542ab0..bf25f29 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt
@@ -29,6 +29,7 @@
import android.view.View
import android.view.ViewPropertyAnimator
import android.view.WindowInsets
+import android.widget.LinearLayout
import android.widget.TextView
import androidx.constraintlayout.motion.widget.MotionLayout
import androidx.constraintlayout.widget.ConstraintSet
@@ -127,6 +128,7 @@
var viewVisibility = View.GONE
var viewAlpha = 1f
+ private val systemIcons = LinearLayout(context)
private lateinit var shadeHeaderController: ShadeHeaderController
private lateinit var carrierIconSlots: List<String>
private val configurationController = FakeConfigurationController()
@@ -146,6 +148,7 @@
.thenReturn(batteryMeterView)
whenever<StatusIconContainer>(view.findViewById(R.id.statusIcons)).thenReturn(statusIcons)
+ whenever<View>(view.findViewById(R.id.shade_header_system_icons)).thenReturn(systemIcons)
viewContext = Mockito.spy(context)
whenever(view.context).thenReturn(viewContext)
@@ -451,6 +454,17 @@
}
@Test
+ fun testLargeScreenActive_collapseActionRun_onSystemIconsClick() {
+ shadeHeaderController.largeScreenActive = true
+ var wasRun = false
+ shadeHeaderController.shadeCollapseAction = Runnable { wasRun = true }
+
+ systemIcons.performClick()
+
+ assertThat(wasRun).isTrue()
+ }
+
+ @Test
fun testShadeExpandedFraction() {
// View needs to be visible for this to actually take effect
shadeHeaderController.qsVisible = true
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
index 5d2d192..6e9fba6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
@@ -22,7 +22,6 @@
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyguard.domain.interactor.LockscreenSceneInteractor
import com.android.systemui.scene.SceneTestUtils
-import com.android.systemui.scene.SceneTestUtils.Companion.CONTAINER_1
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
import com.google.common.truth.Truth.assertThat
@@ -54,7 +53,6 @@
override fun create(containerName: String): LockscreenSceneInteractor {
return utils.lockScreenSceneInteractor(
authenticationInteractor = authenticationInteractor,
- sceneInteractor = sceneInteractor,
bouncerInteractor =
utils.bouncerInteractor(
authenticationInteractor = authenticationInteractor,
@@ -70,9 +68,7 @@
fun upTransitionSceneKey_deviceLocked_lockScreen() =
testScope.runTest {
val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin(1234)
- )
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
utils.authenticationRepository.setUnlocked(false)
assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Lockscreen)
@@ -82,9 +78,7 @@
fun upTransitionSceneKey_deviceUnlocked_gone() =
testScope.runTest {
val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin(1234)
- )
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
utils.authenticationRepository.setUnlocked(true)
assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Gone)
@@ -93,10 +87,9 @@
@Test
fun onContentClicked_deviceUnlocked_switchesToGone() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin(1234)
- )
+ val currentScene by
+ collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
utils.authenticationRepository.setUnlocked(true)
runCurrent()
@@ -108,10 +101,9 @@
@Test
fun onContentClicked_deviceLockedSecurely_switchesToBouncer() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin(1234)
- )
+ val currentScene by
+ collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
utils.authenticationRepository.setUnlocked(false)
runCurrent()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
index ff2f106..4a2518a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
@@ -101,16 +101,18 @@
@Mock lateinit var activityStarter: ActivityStarter
@Mock lateinit var transitionControllerCallback: LockscreenShadeTransitionController.Callback
private val disableFlagsRepository = FakeDisableFlagsRepository()
+ private val keyguardRepository = FakeKeyguardRepository()
private val shadeInteractor = ShadeInteractor(
testScope.backgroundScope,
disableFlagsRepository,
- keyguardRepository = FakeKeyguardRepository(),
+ keyguardRepository,
userSetupRepository = FakeUserSetupRepository(),
deviceProvisionedController = mock(),
userInteractor = mock(),
)
private val powerInteractor = PowerInteractor(
FakePowerRepository(),
+ keyguardRepository,
FalsingCollectorFake(),
screenOffAnimationController = mock(),
statusBarStateController = mock(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java
index 1b1f4e4..8d016e3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java
@@ -45,6 +45,7 @@
import android.os.Bundle;
import android.os.UserHandle;
import android.service.notification.StatusBarNotification;
+import android.view.ViewGroup;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -65,6 +66,8 @@
@RunWith(AndroidJUnit4.class)
public class StatusBarIconViewTest extends SysuiTestCase {
+ private static final int TEST_STATUS_BAR_HEIGHT = 150;
+
@Rule
public ExpectedException mThrown = ExpectedException.none();
@@ -184,4 +187,218 @@
// no crash, good
}
+
+ @Test
+ public void testUpdateIconScale_constrainedDrawableSizeLessThanDpIconSize() {
+ int dpIconSize = 60;
+ int dpDrawingSize = 30;
+ // the icon view layout size would be 60x150
+ // (the height is always 150 due to TEST_STATUS_BAR_HEIGHT)
+ setUpIconView(dpIconSize, dpDrawingSize, dpIconSize);
+ mIconView.setNotification(mock(StatusBarNotification.class));
+ // the raw drawable size is 50x50. When put the drawable into iconView whose
+ // layout size is 60x150, the drawable size would not be constrained and thus keep 50x50
+ setIconDrawableWithSize(/* width= */ 50, /* height= */ 50);
+ mIconView.maybeUpdateIconScaleDimens();
+
+ // WHEN both the constrained drawable width/height are less than dpIconSize,
+ // THEN the icon is scaled down from dpIconSize to fit the dpDrawingSize
+ float scaleToFitDrawingSize = (float) dpDrawingSize / dpIconSize;
+ assertEquals(scaleToFitDrawingSize, mIconView.getIconScale(), 0.01f);
+ }
+
+ @Test
+ public void testUpdateIconScale_constrainedDrawableHeightLargerThanDpIconSize() {
+ int dpIconSize = 60;
+ int dpDrawingSize = 30;
+ // the icon view layout size would be 60x150
+ // (the height is always 150 due to TEST_STATUS_BAR_HEIGHT)
+ setUpIconView(dpIconSize, dpDrawingSize, dpIconSize);
+ mIconView.setNotification(mock(StatusBarNotification.class));
+ // the raw drawable size is 50x100. When put the drawable into iconView whose
+ // layout size is 60x150, the drawable size would not be constrained and thus keep 50x100
+ setIconDrawableWithSize(/* width= */ 50, /* height= */ 100);
+ mIconView.maybeUpdateIconScaleDimens();
+
+ // WHEN constrained drawable larger side length 100 >= dpIconSize
+ // THEN the icon is scaled down from larger side length 100 to ensure both side
+ // length fit in dpDrawingSize.
+ float scaleToFitDrawingSize = (float) dpDrawingSize / 100;
+ assertEquals(scaleToFitDrawingSize, mIconView.getIconScale(), 0.01f);
+ }
+
+ @Test
+ public void testUpdateIconScale_constrainedDrawableWidthLargerThanDpIconSize() {
+ int dpIconSize = 60;
+ int dpDrawingSize = 30;
+ // the icon view layout size would be 60x150
+ // (the height is always 150 due to TEST_STATUS_BAR_HEIGHT)
+ setUpIconView(dpIconSize, dpDrawingSize, dpIconSize);
+ mIconView.setNotification(mock(StatusBarNotification.class));
+ // the raw drawable size is 100x50. When put the drawable into iconView whose
+ // layout size is 60x150, the drawable size would be constrained to 60x30
+ setIconDrawableWithSize(/* width= */ 100, /* height= */ 50);
+ mIconView.maybeUpdateIconScaleDimens();
+
+ // WHEN constrained drawable larger side length 60 >= dpIconSize
+ // THEN the icon is scaled down from larger side length 60 to ensure both side
+ // length fit in dpDrawingSize.
+ float scaleToFitDrawingSize = (float) dpDrawingSize / 60;
+ assertEquals(scaleToFitDrawingSize, mIconView.getIconScale(), 0.01f);
+ }
+
+ @Test
+ public void testUpdateIconScale_smallerFontAndConstrainedDrawableSizeLessThanDpIconSize() {
+ int dpIconSize = 60;
+ int dpDrawingSize = 30;
+ // smaller font scaling causes the spIconSize < dpIconSize
+ int spIconSize = 40;
+ // the icon view layout size would be 40x150
+ // (the height is always 150 due to TEST_STATUS_BAR_HEIGHT)
+ setUpIconView(dpIconSize, dpDrawingSize, spIconSize);
+ mIconView.setNotification(mock(StatusBarNotification.class));
+ // the raw drawable size is 50x50. When put the drawable into iconView whose
+ // layout size is 40x150, the drawable size would be constrained to 40x40
+ setIconDrawableWithSize(/* width= */ 50, /* height= */ 50);
+ mIconView.maybeUpdateIconScaleDimens();
+
+ // WHEN both the constrained drawable width/height are less than dpIconSize,
+ // THEN the icon is scaled down from dpIconSize to fit the dpDrawingSize
+ float scaleToFitDrawingSize = (float) dpDrawingSize / dpIconSize;
+ // THEN the scaled icon should be scaled down further to fit spIconSize
+ float scaleToFitSpIconSize = (float) spIconSize / dpIconSize;
+ assertEquals(scaleToFitDrawingSize * scaleToFitSpIconSize, mIconView.getIconScale(), 0.01f);
+ }
+
+ @Test
+ public void testUpdateIconScale_smallerFontAndConstrainedDrawableHeightLargerThanDpIconSize() {
+ int dpIconSize = 60;
+ int dpDrawingSize = 30;
+ // smaller font scaling causes the spIconSize < dpIconSize
+ int spIconSize = 40;
+ // the icon view layout size would be 40x150
+ // (the height is always 150 due to TEST_STATUS_BAR_HEIGHT)
+ setUpIconView(dpIconSize, dpDrawingSize, spIconSize);
+ mIconView.setNotification(mock(StatusBarNotification.class));
+ // the raw drawable size is 50x100. When put the drawable into iconView whose
+ // layout size is 40x150, the drawable size would be constrained to 40x80
+ setIconDrawableWithSize(/* width= */ 50, /* height= */ 100);
+ mIconView.maybeUpdateIconScaleDimens();
+
+ // WHEN constrained drawable larger side length 80 >= dpIconSize
+ // THEN the icon is scaled down from larger side length 80 to ensure both side
+ // length fit in dpDrawingSize.
+ float scaleToFitDrawingSize = (float) dpDrawingSize / 80;
+ // THEN the scaled icon should be scaled down further to fit spIconSize
+ float scaleToFitSpIconSize = (float) spIconSize / dpIconSize;
+ assertEquals(scaleToFitDrawingSize * scaleToFitSpIconSize, mIconView.getIconScale(), 0.01f);
+ }
+
+ @Test
+ public void testUpdateIconScale_largerFontAndConstrainedDrawableSizeLessThanDpIconSize() {
+ int dpIconSize = 60;
+ int dpDrawingSize = 30;
+ // larger font scaling causes the spIconSize > dpIconSize
+ int spIconSize = 80;
+ // the icon view layout size would be 80x150
+ // (the height is always 150 due to TEST_STATUS_BAR_HEIGHT)
+ setUpIconView(dpIconSize, dpDrawingSize, spIconSize);
+ mIconView.setNotification(mock(StatusBarNotification.class));
+ // the raw drawable size is 50x50. When put the drawable into iconView whose
+ // layout size is 80x150, the drawable size would not be constrained and thus keep 50x50
+ setIconDrawableWithSize(/* width= */ 50, /* height= */ 50);
+ mIconView.maybeUpdateIconScaleDimens();
+
+ // WHEN both the constrained drawable width/height are less than dpIconSize,
+ // THEN the icon is scaled down from dpIconSize to fit the dpDrawingSize
+ float scaleToFitDrawingSize = (float) dpDrawingSize / dpIconSize;
+ // THEN the scaled icon should be scaled up to fit spIconSize
+ float scaleToFitSpIconSize = (float) spIconSize / dpIconSize;
+ assertEquals(scaleToFitDrawingSize * scaleToFitSpIconSize, mIconView.getIconScale(), 0.01f);
+ }
+
+ @Test
+ public void testUpdateIconScale_largerFontAndConstrainedDrawableHeightLargerThanDpIconSize() {
+ int dpIconSize = 60;
+ int dpDrawingSize = 30;
+ // larger font scaling causes the spIconSize > dpIconSize
+ int spIconSize = 80;
+ // the icon view layout size would be 80x150
+ // (the height is always 150 due to TEST_STATUS_BAR_HEIGHT)
+ setUpIconView(dpIconSize, dpDrawingSize, spIconSize);
+ mIconView.setNotification(mock(StatusBarNotification.class));
+ // the raw drawable size is 50x100. When put the drawable into iconView whose
+ // layout size is 80x150, the drawable size would not be constrained and thus keep 50x100
+ setIconDrawableWithSize(/* width= */ 50, /* height= */ 100);
+ mIconView.maybeUpdateIconScaleDimens();
+
+ // WHEN constrained drawable larger side length 100 >= dpIconSize
+ // THEN the icon is scaled down from larger side length 100 to ensure both side
+ // length fit in dpDrawingSize.
+ float scaleToFitDrawingSize = (float) dpDrawingSize / 100;
+ // THEN the scaled icon should be scaled up to fit spIconSize
+ float scaleToFitSpIconSize = (float) spIconSize / dpIconSize;
+ assertEquals(scaleToFitDrawingSize * scaleToFitSpIconSize, mIconView.getIconScale(), 0.01f);
+ }
+
+ @Test
+ public void testUpdateIconScale_largerFontAndConstrainedDrawableWidthLargerThanDpIconSize() {
+ int dpIconSize = 60;
+ int dpDrawingSize = 30;
+ // larger font scaling causes the spIconSize > dpIconSize
+ int spIconSize = 80;
+ // the icon view layout size would be 80x150
+ // (the height is always 150 due to TEST_STATUS_BAR_HEIGHT)
+ setUpIconView(dpIconSize, dpDrawingSize, spIconSize);
+ mIconView.setNotification(mock(StatusBarNotification.class));
+ // the raw drawable size is 100x50. When put the drawable into iconView whose
+ // layout size is 80x150, the drawable size would not be constrained and thus keep 80x40
+ setIconDrawableWithSize(/* width= */ 100, /* height= */ 50);
+ mIconView.maybeUpdateIconScaleDimens();
+
+ // WHEN constrained drawable larger side length 80 >= dpIconSize
+ // THEN the icon is scaled down from larger side length 80 to ensure both side
+ // length fit in dpDrawingSize.
+ float scaleToFitDrawingSize = (float) dpDrawingSize / 80;
+ // THEN the scaled icon should be scaled up to fit spIconSize
+ float scaleToFitSpIconSize = (float) spIconSize / dpIconSize;
+ assertEquals(scaleToFitDrawingSize * scaleToFitSpIconSize,
+ mIconView.getIconScale(), 0.01f);
+ }
+
+ /**
+ * Setup iconView dimens for testing. The result icon view layout width would
+ * be spIconSize and height would be 150.
+ *
+ * @param dpIconSize corresponding to status_bar_icon_size
+ * @param dpDrawingSize corresponding to status_bar_icon_drawing_size
+ * @param spIconSize corresponding to status_bar_icon_size_sp under different font scaling
+ */
+ private void setUpIconView(int dpIconSize, int dpDrawingSize, int spIconSize) {
+ mIconView.setIncreasedSize(false);
+ mIconView.mOriginalStatusBarIconSize = dpIconSize;
+ mIconView.mStatusBarIconDrawingSize = dpDrawingSize;
+
+ mIconView.mNewStatusBarIconSize = spIconSize;
+ mIconView.mScaleToFitNewIconSize = (float) spIconSize / dpIconSize;
+
+ // the layout width would be spIconSize + 2 * iconPadding, and we assume iconPadding
+ // is 0 here.
+ ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(spIconSize, TEST_STATUS_BAR_HEIGHT);
+ mIconView.setLayoutParams(lp);
+ }
+
+ private void setIconDrawableWithSize(int width, int height) {
+ Bitmap bitmap = Bitmap.createBitmap(
+ width, height, Bitmap.Config.ARGB_8888);
+ Icon icon = Icon.createWithBitmap(bitmap);
+ mStatusBarIcon = new StatusBarIcon(UserHandle.ALL, "mockPackage",
+ icon, 0, 0, "");
+ // Since we only want to verify icon scale logic here, we directly use
+ // {@link StatusBarIconView#setImageDrawable(Drawable)} to set the image drawable
+ // to iconView instead of call {@link StatusBarIconView#set(StatusBarIcon)}. It's to prevent
+ // the icon drawable size being scaled down when internally calling
+ // {@link StatusBarIconView#getIcon(Context,Context,StatusBarIcon)}.
+ mIconView.setImageDrawable(icon.loadDrawable(mContext));
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventChipAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventChipAnimationControllerTest.kt
new file mode 100644
index 0000000..55b6be9
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventChipAnimationControllerTest.kt
@@ -0,0 +1,189 @@
+/*
+ * 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.statusbar.events
+
+import android.content.Context
+import android.graphics.Rect
+import android.util.Pair
+import android.view.Gravity
+import android.view.View
+import android.widget.FrameLayout
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.statusbar.phone.StatusBarContentInsetsChangedListener
+import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider
+import com.android.systemui.statusbar.window.StatusBarWindowController
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+class SystemEventChipAnimationControllerTest : SysuiTestCase() {
+ private lateinit var controller: SystemEventChipAnimationController
+
+ @Mock private lateinit var sbWindowController: StatusBarWindowController
+ @Mock private lateinit var insetsProvider: StatusBarContentInsetsProvider
+
+ private var testView = TestView(mContext)
+ private var viewCreator: ViewCreator = { testView }
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+
+ // StatusBarWindowController is mocked. The addViewToWindow function needs to be mocked to
+ // ensure that the chip view is added to a parent view
+ whenever(sbWindowController.addViewToWindow(any(), any())).then {
+ val statusbarFake = FrameLayout(mContext)
+ statusbarFake.layout(
+ portraitArea.left,
+ portraitArea.top,
+ portraitArea.right,
+ portraitArea.bottom,
+ )
+ statusbarFake.addView(
+ it.arguments[0] as View,
+ it.arguments[1] as FrameLayout.LayoutParams
+ )
+ }
+
+ whenever(insetsProvider.getStatusBarContentInsetsForCurrentRotation())
+ .thenReturn(Pair(insets, insets))
+ whenever(insetsProvider.getStatusBarContentAreaForCurrentRotation())
+ .thenReturn(portraitArea)
+
+ controller =
+ SystemEventChipAnimationController(
+ context = mContext,
+ statusBarWindowController = sbWindowController,
+ contentInsetsProvider = insetsProvider,
+ featureFlags = FakeFeatureFlags(),
+ )
+ }
+
+ @Test
+ fun prepareChipAnimation_lazyInitializes() {
+ // Until Dagger can do our initialization, make sure that the first chip animation calls
+ // init()
+ assertFalse(controller.initialized)
+ controller.prepareChipAnimation(viewCreator)
+ assertTrue(controller.initialized)
+ }
+
+ @Test
+ fun prepareChipAnimation_positionsChip() {
+ controller.prepareChipAnimation(viewCreator)
+ val chipRect = controller.chipBounds
+
+ // SB area = 10, 0, 990, 100
+ // chip size = 0, 0, 100, 50
+ assertThat(chipRect).isEqualTo(Rect(890, 25, 990, 75))
+ }
+
+ @Test
+ fun prepareChipAnimation_rotation_repositionsChip() {
+ controller.prepareChipAnimation(viewCreator)
+
+ // Chip has been prepared, and is located at (890, 25, 990, 75)
+ // Rotation should put it into its landscape location:
+ // SB area = 10, 0, 1990, 80
+ // chip size = 0, 0, 100, 50
+
+ whenever(insetsProvider.getStatusBarContentAreaForCurrentRotation())
+ .thenReturn(landscapeArea)
+ getInsetsListener().onStatusBarContentInsetsChanged()
+
+ val chipRect = controller.chipBounds
+ assertThat(chipRect).isEqualTo(Rect(1890, 15, 1990, 65))
+ }
+
+ /** regression test for (b/289378932) */
+ @Test
+ fun fullScreenStatusBar_positionsChipAtTop_withTopGravity() {
+ // In the case of a fullscreen status bar window, the content insets area is still correct
+ // (because it uses the dimens), but the window can be full screen. This seems to happen
+ // when launching an app from the ongoing call chip.
+
+ // GIVEN layout the status bar window fullscreen portrait
+ whenever(sbWindowController.addViewToWindow(any(), any())).then {
+ val statusbarFake = FrameLayout(mContext)
+ statusbarFake.layout(
+ fullScreenSb.left,
+ fullScreenSb.top,
+ fullScreenSb.right,
+ fullScreenSb.bottom,
+ )
+
+ val lp = it.arguments[1] as FrameLayout.LayoutParams
+ assertThat(lp.gravity and Gravity.VERTICAL_GRAVITY_MASK).isEqualTo(Gravity.TOP)
+
+ statusbarFake.addView(
+ it.arguments[0] as View,
+ lp,
+ )
+ }
+
+ // GIVEN insets provider gives the correct content area
+ whenever(insetsProvider.getStatusBarContentAreaForCurrentRotation())
+ .thenReturn(portraitArea)
+
+ // WHEN the controller lays out the chip in a fullscreen window
+ controller.prepareChipAnimation(viewCreator)
+
+ // THEN it still aligns the chip to the content area provided by the insets provider
+ val chipRect = controller.chipBounds
+ assertThat(chipRect).isEqualTo(Rect(890, 25, 990, 75))
+ }
+
+ class TestView(context: Context) : View(context), BackgroundAnimatableView {
+ override val view: View
+ get() = this
+
+ override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+ setMeasuredDimension(100, 50)
+ }
+
+ override fun setBoundsForAnimation(l: Int, t: Int, r: Int, b: Int) {
+ setLeftTopRightBottom(l, t, r, b)
+ }
+ }
+
+ private fun getInsetsListener(): StatusBarContentInsetsChangedListener {
+ val callbackCaptor = argumentCaptor<StatusBarContentInsetsChangedListener>()
+ verify(insetsProvider).addCallback(capture(callbackCaptor))
+ return callbackCaptor.value!!
+ }
+
+ companion object {
+ private val portraitArea = Rect(10, 0, 990, 100)
+ private val landscapeArea = Rect(10, 0, 1990, 80)
+ private val fullScreenSb = Rect(10, 0, 990, 2000)
+
+ // 10px insets on both sides
+ private const val insets = 10
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/RoundableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/RoundableTest.kt
index 89faa239..a56fb2c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/RoundableTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/RoundableTest.kt
@@ -3,7 +3,11 @@
import android.view.View
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
@@ -15,8 +19,9 @@
@SmallTest
@RunWith(JUnit4::class)
class RoundableTest : SysuiTestCase() {
- val targetView: View = mock()
- val roundable = FakeRoundable(targetView)
+ private val targetView: View = mock()
+ private val featureFlags = FakeFeatureFlags()
+ private val roundable = FakeRoundable(targetView = targetView, featureFlags = featureFlags)
@Test
fun defaultConfig_shouldNotHaveRoundedCorner() {
@@ -144,16 +149,62 @@
assertEquals(0.2f, roundable.roundableState.bottomRoundness)
}
+ @Test
+ fun getCornerRadii_radius_maxed_to_height() {
+ whenever(targetView.height).thenReturn(10)
+ featureFlags.set(Flags.IMPROVED_HUN_ANIMATIONS, true)
+ roundable.requestRoundness(1f, 1f, SOURCE1)
+
+ assertCornerRadiiEquals(5f, 5f)
+ }
+
+ @Test
+ fun getCornerRadii_topRadius_maxed_to_height() {
+ whenever(targetView.height).thenReturn(5)
+ featureFlags.set(Flags.IMPROVED_HUN_ANIMATIONS, true)
+ roundable.requestRoundness(1f, 0f, SOURCE1)
+
+ assertCornerRadiiEquals(5f, 0f)
+ }
+
+ @Test
+ fun getCornerRadii_bottomRadius_maxed_to_height() {
+ whenever(targetView.height).thenReturn(5)
+ featureFlags.set(Flags.IMPROVED_HUN_ANIMATIONS, true)
+ roundable.requestRoundness(0f, 1f, SOURCE1)
+
+ assertCornerRadiiEquals(0f, 5f)
+ }
+
+ @Test
+ fun getCornerRadii_radii_kept() {
+ whenever(targetView.height).thenReturn(100)
+ featureFlags.set(Flags.IMPROVED_HUN_ANIMATIONS, true)
+ roundable.requestRoundness(1f, 1f, SOURCE1)
+
+ assertCornerRadiiEquals(MAX_RADIUS, MAX_RADIUS)
+ }
+
+ private fun assertCornerRadiiEquals(top: Float, bottom: Float) {
+ assertEquals("topCornerRadius", top, roundable.topCornerRadius)
+ assertEquals("bottomCornerRadius", bottom, roundable.bottomCornerRadius)
+ }
+
class FakeRoundable(
targetView: View,
radius: Float = MAX_RADIUS,
+ featureFlags: FeatureFlags
) : Roundable {
override val roundableState =
RoundableState(
targetView = targetView,
roundable = this,
maxRadius = radius,
+ featureFlags = featureFlags
)
+
+ override val clipHeight: Int
+ get() = roundableState.targetView.height
}
companion object {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DreamCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DreamCoordinatorTest.kt
new file mode 100644
index 0000000..a544cad
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DreamCoordinatorTest.kt
@@ -0,0 +1,166 @@
+/*
+ * 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.
+ */
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.statusbar.notification.collection.coordinator
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.SysuiStatusBarStateController
+import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.withArgCaptor
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class DreamCoordinatorTest : SysuiTestCase() {
+ @Mock private lateinit var statusBarStateController: SysuiStatusBarStateController
+ @Mock private lateinit var notifPipeline: NotifPipeline
+ @Mock private lateinit var filterListener: Pluggable.PluggableListener<NotifFilter>
+
+ private val keyguardRepository = FakeKeyguardRepository()
+ private var fakeEntry: NotificationEntry = NotificationEntryBuilder().build()
+ val testDispatcher = UnconfinedTestDispatcher()
+ val testScope = TestScope(testDispatcher)
+
+ private lateinit var filter: NotifFilter
+ private lateinit var statusBarListener: StatusBarStateController.StateListener
+ private lateinit var dreamCoordinator: DreamCoordinator
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
+
+ // Build the coordinator
+ dreamCoordinator =
+ DreamCoordinator(
+ statusBarStateController,
+ testScope.backgroundScope,
+ keyguardRepository
+ )
+
+ // Attach the pipeline and capture the listeners/filters that it registers
+ dreamCoordinator.attach(notifPipeline)
+
+ filter = withArgCaptor { verify(notifPipeline).addPreGroupFilter(capture()) }
+ filter.setInvalidationListener(filterListener)
+
+ statusBarListener = withArgCaptor {
+ verify(statusBarStateController).addCallback(capture())
+ }
+ }
+
+ @Test
+ fun hideNotifications_whenDreamingAndOnKeyguard() =
+ testScope.runTest {
+ // GIVEN we are on keyguard and not dreaming
+ keyguardRepository.setKeyguardShowing(true)
+ keyguardRepository.setIsActiveDreamLockscreenHosted(false)
+ runCurrent()
+
+ // THEN notifications are not filtered out
+ verifyPipelinesNotInvalidated()
+ assertThat(filter.shouldFilterOut(fakeEntry, 0L)).isFalse()
+
+ // WHEN dreaming starts and the active dream is hosted in lockscreen
+ keyguardRepository.setIsActiveDreamLockscreenHosted(true)
+ runCurrent()
+
+ // THEN pipeline is notified and notifications should all be filtered out
+ verifyPipelinesInvalidated()
+ assertThat(filter.shouldFilterOut(fakeEntry, 0L)).isTrue()
+ }
+
+ @Test
+ fun showNotifications_whenDreamingAndNotOnKeyguard() =
+ testScope.runTest {
+ // GIVEN we are on the keyguard and active dream is hosted in lockscreen
+ keyguardRepository.setKeyguardShowing(true)
+ keyguardRepository.setIsActiveDreamLockscreenHosted(true)
+ runCurrent()
+
+ // THEN pipeline is notified and notifications are all filtered out
+ verifyPipelinesInvalidated()
+ clearPipelineInvocations()
+ assertThat(filter.shouldFilterOut(fakeEntry, 0L)).isTrue()
+
+ // WHEN we are no longer on the keyguard
+ statusBarListener.onStateChanged(StatusBarState.SHADE)
+
+ // THEN pipeline is notified and notifications are not filtered out
+ verifyPipelinesInvalidated()
+ assertThat(filter.shouldFilterOut(fakeEntry, 0L)).isFalse()
+ }
+
+ @Test
+ fun showNotifications_whenOnKeyguardAndNotDreaming() =
+ testScope.runTest {
+ // GIVEN we are on the keyguard and active dream is hosted in lockscreen
+ keyguardRepository.setKeyguardShowing(true)
+ keyguardRepository.setIsActiveDreamLockscreenHosted(true)
+ runCurrent()
+
+ // THEN pipeline is notified and notifications are all filtered out
+ verifyPipelinesInvalidated()
+ clearPipelineInvocations()
+ assertThat(filter.shouldFilterOut(fakeEntry, 0L)).isTrue()
+
+ // WHEN the lockscreen hosted dream stops
+ keyguardRepository.setIsActiveDreamLockscreenHosted(false)
+ runCurrent()
+
+ // THEN pipeline is notified and notifications are not filtered out
+ verifyPipelinesInvalidated()
+ assertThat(filter.shouldFilterOut(fakeEntry, 0L)).isFalse()
+ }
+
+ private fun verifyPipelinesInvalidated() {
+ verify(filterListener).onPluggableInvalidated(eq(filter), any())
+ }
+
+ private fun verifyPipelinesNotInvalidated() {
+ verify(filterListener, never()).onPluggableInvalidated(eq(filter), any())
+ }
+
+ private fun clearPipelineInvocations() {
+ clearInvocations(filterListener)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
index d3e5816..daa45db 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
@@ -426,23 +426,8 @@
}
@Test
- public void testShouldHeadsUp_oldWhen_flagDisabled() throws Exception {
- ensureStateForHeadsUpWhenAwake();
- when(mFlags.isNoHunForOldWhenEnabled()).thenReturn(false);
-
- NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
- entry.getSbn().getNotification().when = makeWhenHoursAgo(25);
-
- assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isTrue();
-
- verify(mLogger, never()).logNoHeadsUpOldWhen(any(), anyLong(), anyLong());
- verify(mLogger, never()).logMaybeHeadsUpDespiteOldWhen(any(), anyLong(), anyLong(), any());
- }
-
- @Test
public void testShouldHeadsUp_oldWhen_whenNow() throws Exception {
ensureStateForHeadsUpWhenAwake();
- when(mFlags.isNoHunForOldWhenEnabled()).thenReturn(true);
NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
@@ -455,7 +440,6 @@
@Test
public void testShouldHeadsUp_oldWhen_whenRecent() throws Exception {
ensureStateForHeadsUpWhenAwake();
- when(mFlags.isNoHunForOldWhenEnabled()).thenReturn(true);
NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
entry.getSbn().getNotification().when = makeWhenHoursAgo(13);
@@ -469,7 +453,6 @@
@Test
public void testShouldHeadsUp_oldWhen_whenZero() throws Exception {
ensureStateForHeadsUpWhenAwake();
- when(mFlags.isNoHunForOldWhenEnabled()).thenReturn(true);
NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
entry.getSbn().getNotification().when = 0L;
@@ -484,7 +467,6 @@
@Test
public void testShouldHeadsUp_oldWhen_whenNegative() throws Exception {
ensureStateForHeadsUpWhenAwake();
- when(mFlags.isNoHunForOldWhenEnabled()).thenReturn(true);
NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
entry.getSbn().getNotification().when = -1L;
@@ -498,7 +480,6 @@
@Test
public void testShouldHeadsUp_oldWhen_hasFullScreenIntent() throws Exception {
ensureStateForHeadsUpWhenAwake();
- when(mFlags.isNoHunForOldWhenEnabled()).thenReturn(true);
long when = makeWhenHoursAgo(25);
NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silent= */ false);
@@ -514,7 +495,6 @@
@Test
public void testShouldHeadsUp_oldWhen_isForegroundService() throws Exception {
ensureStateForHeadsUpWhenAwake();
- when(mFlags.isNoHunForOldWhenEnabled()).thenReturn(true);
long when = makeWhenHoursAgo(25);
NotificationEntry entry = createFgsNotification(IMPORTANCE_HIGH);
@@ -530,7 +510,6 @@
@Test
public void testShouldNotHeadsUp_oldWhen() throws Exception {
ensureStateForHeadsUpWhenAwake();
- when(mFlags.isNoHunForOldWhenEnabled()).thenReturn(true);
long when = makeWhenHoursAgo(25);
NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/TextPrecomputerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/TextPrecomputerTest.kt
new file mode 100644
index 0000000..d46763d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/TextPrecomputerTest.kt
@@ -0,0 +1,95 @@
+/*
+ * 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.statusbar.notification.row
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import android.text.PrecomputedText
+import android.text.TextPaint
+import android.widget.TextView
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper
+class TextPrecomputerTest : SysuiTestCase() {
+
+ private lateinit var textPrecomputer: TextPrecomputer
+
+ private lateinit var textView: TextView
+
+ @Before
+ fun before() {
+ textPrecomputer = object : TextPrecomputer {}
+ textView = TextView(mContext)
+ }
+
+ @Test
+ fun precompute_returnRunnable() {
+ // WHEN
+ val precomputeResult = textPrecomputer.precompute(textView, TEXT)
+
+ // THEN
+ assertThat(precomputeResult).isInstanceOf(Runnable::class.java)
+ }
+
+ @Test
+ fun precomputeRunnable_anyText_setPrecomputedText() {
+ // WHEN
+ textPrecomputer.precompute(textView, TEXT).run()
+
+ // THEN
+ assertThat(textView.text).isInstanceOf(PrecomputedText::class.java)
+ }
+
+ @Test
+ fun precomputeRunnable_differentPrecomputedTextConfig_notSetPrecomputedText() {
+ // GIVEN
+ val precomputedTextRunnable =
+ textPrecomputer.precompute(textView, TEXT, logException = false)
+
+ // WHEN
+ textView.textMetricsParams = PrecomputedText.Params.Builder(PAINT).build()
+ precomputedTextRunnable.run()
+
+ // THEN
+ assertThat(textView.text).isInstanceOf(String::class.java)
+ }
+
+ @Test
+ fun precomputeRunnable_nullText_setNull() {
+ // GIVEN
+ textView.text = TEXT
+ val precomputedTextRunnable = textPrecomputer.precompute(textView, null)
+
+ // WHEN
+ precomputedTextRunnable.run()
+
+ // THEN
+ assertThat(textView.text).isEqualTo("")
+ }
+
+ private companion object {
+ private val PAINT = TextPaint()
+ private const val TEXT = "Example Notification Test"
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractorTest.kt
index a87dd2d..8881f42 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractorTest.kt
@@ -58,6 +58,7 @@
private val powerInteractor =
PowerInteractor(
powerRepository,
+ keyguardRepository,
FalsingCollectorFake(),
screenOffAnimationController,
statusBarStateController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt
index 7ae1502..6221f3e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt
@@ -69,6 +69,7 @@
private val powerInteractor by lazy {
PowerInteractor(
powerRepository,
+ keyguardRepository,
FalsingCollectorFake(),
screenOffAnimationController,
statusBarStateController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractorTest.kt
new file mode 100644
index 0000000..7bbb094
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractorTest.kt
@@ -0,0 +1,72 @@
+/*
+ * 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.statusbar.notification.stack.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class SharedNotificationContainerInteractorTest : SysuiTestCase() {
+ private lateinit var configurationRepository: FakeConfigurationRepository
+ private lateinit var underTest: SharedNotificationContainerInteractor
+
+ @Before
+ fun setUp() {
+ configurationRepository = FakeConfigurationRepository()
+ underTest =
+ SharedNotificationContainerInteractor(
+ configurationRepository,
+ mContext,
+ )
+ }
+
+ @Test
+ fun validateConfigValues() = runTest {
+ overrideResource(R.bool.config_use_split_notification_shade, true)
+ overrideResource(R.bool.config_use_large_screen_shade_header, false)
+ overrideResource(R.dimen.notification_panel_margin_horizontal, 0)
+ overrideResource(R.dimen.notification_panel_margin_bottom, 10)
+ overrideResource(R.dimen.notification_panel_margin_top, 10)
+ overrideResource(R.dimen.large_screen_shade_header_height, 0)
+
+ val dimens = collectLastValue(underTest.configurationBasedDimensions)
+
+ configurationRepository.onAnyConfigurationChange()
+ runCurrent()
+
+ val lastDimens = dimens()!!
+
+ assertThat(lastDimens.useSplitShade).isTrue()
+ assertThat(lastDimens.useLargeScreenHeader).isFalse()
+ assertThat(lastDimens.marginHorizontal).isEqualTo(0)
+ assertThat(lastDimens.marginBottom).isGreaterThan(0)
+ assertThat(lastDimens.marginTop).isGreaterThan(0)
+ assertThat(lastDimens.marginTopLargeScreen).isEqualTo(0)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
new file mode 100644
index 0000000..afd9954
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
@@ -0,0 +1,142 @@
+/*
+ * 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.statusbar.notification.stack.ui.viewmodel
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class SharedNotificationContainerViewModelTest : SysuiTestCase() {
+ private lateinit var configurationRepository: FakeConfigurationRepository
+ private lateinit var sharedNotificationContainerInteractor:
+ SharedNotificationContainerInteractor
+ private lateinit var underTest: SharedNotificationContainerViewModel
+
+ @Before
+ fun setUp() {
+ configurationRepository = FakeConfigurationRepository()
+ sharedNotificationContainerInteractor =
+ SharedNotificationContainerInteractor(
+ configurationRepository,
+ mContext,
+ )
+ underTest = SharedNotificationContainerViewModel(sharedNotificationContainerInteractor)
+ }
+
+ @Test
+ fun validateMarginStartInSplitShade() = runTest {
+ overrideResource(R.bool.config_use_split_notification_shade, true)
+ overrideResource(R.dimen.notification_panel_margin_horizontal, 20)
+
+ val dimens = collectLastValue(underTest.configurationBasedDimensions)
+
+ configurationRepository.onAnyConfigurationChange()
+ runCurrent()
+
+ val lastDimens = dimens()!!
+
+ assertThat(lastDimens.marginStart).isEqualTo(0)
+ }
+
+ @Test
+ fun validateMarginStart() = runTest {
+ overrideResource(R.bool.config_use_split_notification_shade, false)
+ overrideResource(R.dimen.notification_panel_margin_horizontal, 20)
+
+ val dimens = collectLastValue(underTest.configurationBasedDimensions)
+
+ configurationRepository.onAnyConfigurationChange()
+ runCurrent()
+
+ val lastDimens = dimens()!!
+
+ assertThat(lastDimens.marginStart).isEqualTo(20)
+ }
+
+ @Test
+ fun validateMarginEnd() = runTest {
+ overrideResource(R.dimen.notification_panel_margin_horizontal, 50)
+
+ val dimens = collectLastValue(underTest.configurationBasedDimensions)
+
+ configurationRepository.onAnyConfigurationChange()
+ runCurrent()
+
+ val lastDimens = dimens()!!
+
+ assertThat(lastDimens.marginEnd).isEqualTo(50)
+ }
+
+ @Test
+ fun validateMarginBottom() = runTest {
+ overrideResource(R.dimen.notification_panel_margin_bottom, 50)
+
+ val dimens = collectLastValue(underTest.configurationBasedDimensions)
+
+ configurationRepository.onAnyConfigurationChange()
+ runCurrent()
+
+ val lastDimens = dimens()!!
+
+ assertThat(lastDimens.marginBottom).isEqualTo(50)
+ }
+
+ @Test
+ fun validateMarginTopWithLargeScreenHeader() = runTest {
+ overrideResource(R.bool.config_use_large_screen_shade_header, true)
+ overrideResource(R.dimen.large_screen_shade_header_height, 50)
+ overrideResource(R.dimen.notification_panel_margin_top, 0)
+
+ val dimens = collectLastValue(underTest.configurationBasedDimensions)
+
+ configurationRepository.onAnyConfigurationChange()
+ runCurrent()
+
+ val lastDimens = dimens()!!
+
+ assertThat(lastDimens.marginTop).isEqualTo(50)
+ }
+
+ @Test
+ fun validateMarginTop() = runTest {
+ overrideResource(R.bool.config_use_large_screen_shade_header, false)
+ overrideResource(R.dimen.large_screen_shade_header_height, 50)
+ overrideResource(R.dimen.notification_panel_margin_top, 0)
+
+ val dimens = collectLastValue(underTest.configurationBasedDimensions)
+
+ configurationRepository.onAnyConfigurationChange()
+ runCurrent()
+
+ val lastDimens = dimens()!!
+
+ assertThat(lastDimens.marginTop).isEqualTo(0)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt
index 442ba09..5e0e140 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt
@@ -95,6 +95,7 @@
Lazy { notifShadeWindowController },
activityLaunchAnimator,
context,
+ DISPLAY_ID,
lockScreenUserManager,
statusBarWindowController,
wakefulnessLifecycle,
@@ -274,4 +275,8 @@
mainExecutor.runAllReady()
verify(statusBarStateController).setLeaveOpenOnKeyguardHide(true)
}
+
+ private companion object {
+ private const val DISPLAY_ID = 0
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
index 89f8bdb..479803e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
@@ -142,7 +142,8 @@
mNotificationMediaManager, mWakefulnessLifecycle, mScreenLifecycle,
mAuthController, mStatusBarStateController,
mSessionTracker, mLatencyTracker, mScreenOffAnimationController, mVibratorHelper,
- mSystemClock
+ mSystemClock,
+ mStatusBarKeyguardViewManager
);
mBiometricUnlockController.setKeyguardViewController(mStatusBarKeyguardViewManager);
mBiometricUnlockController.addListener(mBiometricUnlockEventsListener);
@@ -464,6 +465,69 @@
}
@Test
+ public void onSideFingerprintSuccess_dreaming_unlockThenWake() {
+ when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true);
+ when(mWakefulnessLifecycle.getLastWakeReason())
+ .thenReturn(PowerManager.WAKE_REASON_POWER_BUTTON);
+ final ArgumentCaptor<Runnable> afterKeyguardGoneRunnableCaptor =
+ ArgumentCaptor.forClass(Runnable.class);
+ givenDreamingLocked();
+ mBiometricUnlockController.startWakeAndUnlock(BiometricSourceType.FINGERPRINT, true);
+
+ // Make sure the BiometricUnlockController has registered a callback for when the keyguard
+ // is gone
+ verify(mStatusBarKeyguardViewManager).addAfterKeyguardGoneRunnable(
+ afterKeyguardGoneRunnableCaptor.capture());
+ // Ensure that the power hasn't been told to wake up yet.
+ verify(mPowerManager, never()).wakeUp(anyLong(), anyInt(), anyString());
+ // Check that the keyguard has been told to unlock.
+ verify(mKeyguardViewMediator).onWakeAndUnlocking();
+
+ // Simulate the keyguard disappearing.
+ afterKeyguardGoneRunnableCaptor.getValue().run();
+ // Verify that the power manager has been told to wake up now.
+ verify(mPowerManager).wakeUp(anyLong(), anyInt(), anyString());
+ }
+
+ @Test
+ public void onSideFingerprintSuccess_dreaming_unlockIfStillLockedNotDreaming() {
+ when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true);
+ when(mWakefulnessLifecycle.getLastWakeReason())
+ .thenReturn(PowerManager.WAKE_REASON_POWER_BUTTON);
+ final ArgumentCaptor<Runnable> afterKeyguardGoneRunnableCaptor =
+ ArgumentCaptor.forClass(Runnable.class);
+ givenDreamingLocked();
+ mBiometricUnlockController.startWakeAndUnlock(BiometricSourceType.FINGERPRINT, true);
+
+ // Make sure the BiometricUnlockController has registered a callback for when the keyguard
+ // is gone
+ verify(mStatusBarKeyguardViewManager).addAfterKeyguardGoneRunnable(
+ afterKeyguardGoneRunnableCaptor.capture());
+ // Ensure that the power hasn't been told to wake up yet.
+ verify(mPowerManager, never()).wakeUp(anyLong(), anyInt(), anyString());
+ // Check that the keyguard has been told to unlock.
+ verify(mKeyguardViewMediator).onWakeAndUnlocking();
+
+ when(mUpdateMonitor.isDreaming()).thenReturn(false);
+ when(mKeyguardStateController.isUnlocked()).thenReturn(false);
+
+ // Simulate the keyguard disappearing.
+ afterKeyguardGoneRunnableCaptor.getValue().run();
+
+ final ArgumentCaptor<Runnable> dismissKeyguardRunnableCaptor =
+ ArgumentCaptor.forClass(Runnable.class);
+ verify(mHandler).post(dismissKeyguardRunnableCaptor.capture());
+
+ // Verify that the power manager was not told to wake up.
+ verify(mPowerManager, never()).wakeUp(anyLong(), anyInt(), anyString());
+
+ dismissKeyguardRunnableCaptor.getValue().run();
+ // Verify that the keyguard controller is told to unlock.
+ verify(mStatusBarKeyguardViewManager).notifyKeyguardAuthenticated(eq(false));
+ }
+
+
+ @Test
public void onSideFingerprintSuccess_oldPowerButtonPress_playHaptic() {
// GIVEN side fingerprint enrolled, last wake reason was power button
when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true);
@@ -537,6 +601,11 @@
verify(mStatusBarKeyguardViewManager).showPrimaryBouncer(anyBoolean());
}
+ private void givenDreamingLocked() {
+ when(mUpdateMonitor.isDreaming()).thenReturn(true);
+ when(mUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
+ }
+
private void givenFingerprintModeUnlockCollapsing() {
when(mUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
when(mUpdateMonitor.isDeviceInteractive()).thenReturn(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index 88d8dfc..5300851 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -129,7 +129,6 @@
import com.android.systemui.shade.CameraLauncher;
import com.android.systemui.shade.NotificationPanelView;
import com.android.systemui.shade.NotificationPanelViewController;
-import com.android.systemui.shade.NotificationShadeWindowView;
import com.android.systemui.shade.NotificationShadeWindowViewController;
import com.android.systemui.shade.QuickSettingsController;
import com.android.systemui.shade.ShadeController;
@@ -252,7 +251,6 @@
@Mock private NotificationLogger.ExpansionStateLogger mExpansionStateLogger;
@Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@Mock private StatusBarSignalPolicy mStatusBarSignalPolicy;
- @Mock private NotificationShadeWindowView mNotificationShadeWindowView;
@Mock private BroadcastDispatcher mBroadcastDispatcher;
@Mock private AssistManager mAssistManager;
@Mock private NotificationGutsManager mNotificationGutsManager;
@@ -276,6 +274,8 @@
@Mock private NotificationShadeWindowController mNotificationShadeWindowController;
@Mock private NotificationIconAreaController mNotificationIconAreaController;
@Mock private NotificationShadeWindowViewController mNotificationShadeWindowViewController;
+ @Mock private Lazy<NotificationShadeWindowViewController>
+ mNotificationShadeWindowViewControllerLazy;
@Mock private NotificationShelfController mNotificationShelfController;
@Mock private DozeParameters mDozeParameters;
@Mock private Lazy<LockscreenWallpaper> mLockscreenWallpaperLazy;
@@ -428,10 +428,10 @@
when(mLockscreenWallpaperLazy.get()).thenReturn(mLockscreenWallpaper);
when(mBiometricUnlockControllerLazy.get()).thenReturn(mBiometricUnlockController);
when(mCameraLauncherLazy.get()).thenReturn(mCameraLauncher);
+ when(mNotificationShadeWindowViewControllerLazy.get())
+ .thenReturn(mNotificationShadeWindowViewController);
when(mStatusBarComponentFactory.create()).thenReturn(mCentralSurfacesComponent);
- when(mCentralSurfacesComponent.getNotificationShadeWindowViewController()).thenReturn(
- mNotificationShadeWindowViewController);
doAnswer(invocation -> {
((Runnable) invocation.getArgument(0)).run();
return null;
@@ -447,10 +447,10 @@
mDeviceProvisionedController,
mNotificationShadeWindowController,
mContext.getSystemService(WindowManager.class),
+ () -> mNotificationPanelViewController,
() -> mAssistManager,
() -> mNotificationGutsManager
));
- mShadeController.setShadeViewController(mNotificationPanelViewController);
mShadeController.setNotificationShadeWindowViewController(
mNotificationShadeWindowViewController);
mShadeController.setNotificationPresenter(mNotificationPresenter);
@@ -510,6 +510,7 @@
() -> mAssistManager,
configurationController,
mNotificationShadeWindowController,
+ mNotificationShadeWindowViewControllerLazy,
mNotificationShelfController,
mStackScrollerController,
mDozeParameters,
@@ -586,9 +587,8 @@
when(mKeyguardViewMediator.getViewMediatorCallback()).thenReturn(
mKeyguardVieMediatorCallback);
- // TODO: we should be able to call mCentralSurfaces.start() and have all the below values
- // initialized automatically and make NPVC private.
- mCentralSurfaces.mNotificationShadeWindowView = mNotificationShadeWindowView;
+ // TODO(b/277764509): we should be able to call mCentralSurfaces.start() and have all the
+ // below values initialized automatically.
mCentralSurfaces.mDozeScrimController = mDozeScrimController;
mCentralSurfaces.mPresenter = mNotificationPresenter;
mCentralSurfaces.mKeyguardIndicationController = mKeyguardIndicationController;
@@ -823,8 +823,6 @@
*/
@Test
public void testPredictiveBackCallback_invocationCollapsesPanel() {
- mCentralSurfaces.setNotificationShadeWindowViewController(
- mNotificationShadeWindowViewController);
mCentralSurfaces.handleVisibleToUserChanged(true);
verify(mOnBackInvokedDispatcher).registerOnBackInvokedCallback(
eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT),
@@ -841,8 +839,6 @@
*/
@Test
public void testPredictiveBackAnimation_progressMaxScalesPanel() {
- mCentralSurfaces.setNotificationShadeWindowViewController(
- mNotificationShadeWindowViewController);
mCentralSurfaces.handleVisibleToUserChanged(true);
verify(mOnBackInvokedDispatcher).registerOnBackInvokedCallback(
eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT),
@@ -864,8 +860,6 @@
*/
@Test
public void testPredictiveBackAnimation_progressMinScalesPanel() {
- mCentralSurfaces.setNotificationShadeWindowViewController(
- mNotificationShadeWindowViewController);
mCentralSurfaces.handleVisibleToUserChanged(true);
verify(mOnBackInvokedDispatcher).registerOnBackInvokedCallback(
eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java
index 6a4b3c5..df3c1e5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java
@@ -22,7 +22,6 @@
import static junit.framework.Assert.assertTrue;
-import static org.junit.Assume.assumeTrue;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
@@ -45,8 +44,6 @@
import com.android.internal.view.AppearanceRegion;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FakeFeatureFlags;
-import com.android.systemui.flags.Flags;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.settings.FakeDisplayTracker;
import com.android.systemui.statusbar.policy.BatteryController;
@@ -65,20 +62,13 @@
private static final GradientColors COLORS_LIGHT = makeColors(Color.WHITE);
private static final GradientColors COLORS_DARK = makeColors(Color.BLACK);
- private final FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
private LightBarTransitionsController mLightBarTransitionsController;
private LightBarTransitionsController mNavBarController;
private SysuiDarkIconDispatcher mStatusBarIconController;
private LightBarController mLightBarController;
- /** Allow testing with NEW_LIGHT_BAR_LOGIC flag in different states */
- protected boolean testNewLightBarLogic() {
- return false;
- }
-
@Before
public void setup() {
- mFeatureFlags.set(Flags.NEW_LIGHT_BAR_LOGIC, testNewLightBarLogic());
mStatusBarIconController = mock(SysuiDarkIconDispatcher.class);
mNavBarController = mock(LightBarTransitionsController.class);
when(mNavBarController.supportsIconTintForNavMode(anyInt())).thenReturn(true);
@@ -90,7 +80,6 @@
mStatusBarIconController,
mock(BatteryController.class),
mock(NavigationModeController.class),
- mFeatureFlags,
mock(DumpManager.class),
new FakeDisplayTracker(mContext));
}
@@ -211,8 +200,6 @@
@Test
public void validateNavBarChangesUpdateIcons() {
- assumeTrue(testNewLightBarLogic()); // Only run in the new suite
-
// On the launcher in dark mode buttons are light
mLightBarController.setScrimState(ScrimState.UNLOCKED, 0f, COLORS_DARK);
mLightBarController.onNavigationBarAppearanceChanged(
@@ -251,8 +238,6 @@
@Test
public void navBarHasDarkIconsInLockedShade_lightMode() {
- assumeTrue(testNewLightBarLogic()); // Only run in the new suite
-
// On the locked shade QS in light mode buttons are light
mLightBarController.setScrimState(ScrimState.SHADE_LOCKED, 1f, COLORS_LIGHT);
mLightBarController.onNavigationBarAppearanceChanged(
@@ -287,8 +272,6 @@
@Test
public void navBarHasLightIconsInLockedShade_darkMode() {
- assumeTrue(testNewLightBarLogic()); // Only run in the new suite
-
// On the locked shade QS in light mode buttons are light
mLightBarController.setScrimState(ScrimState.SHADE_LOCKED, 1f, COLORS_DARK);
mLightBarController.onNavigationBarAppearanceChanged(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerWithNewLogicTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerWithNewLogicTest.kt
deleted file mode 100644
index d9c2cfa..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerWithNewLogicTest.kt
+++ /dev/null
@@ -1,28 +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.statusbar.phone
-
-import androidx.test.filters.SmallTest
-import com.android.systemui.flags.Flags.NEW_LIGHT_BAR_LOGIC
-
-/**
- * This file only needs to live as long as [NEW_LIGHT_BAR_LOGIC] does. When we delete that flag, we
- * can roll this back into the old test.
- */
-@SmallTest
-class LightBarControllerWithNewLogicTest : LightBarControllerTest() {
- override fun testNewLightBarLogic(): Boolean = true
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt
index b80b825..c282c1e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt
@@ -21,6 +21,8 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.StatusBarIconView
+import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT
+import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
import junit.framework.Assert.assertEquals
import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertTrue
@@ -49,7 +51,7 @@
fun calculateWidthFor_oneIcon_widthForOneIcon() {
iconContainer.setActualPaddingStart(10f)
iconContainer.setActualPaddingEnd(10f)
- iconContainer.setIconSize(10);
+ iconContainer.setIconSize(10)
assertEquals(/* expected= */ iconContainer.calculateWidthFor(/* numIcons= */ 1f),
/* actual= */ 30f)
@@ -59,7 +61,7 @@
fun calculateWidthFor_fourIcons_widthForFourIcons() {
iconContainer.setActualPaddingStart(10f)
iconContainer.setActualPaddingEnd(10f)
- iconContainer.setIconSize(10);
+ iconContainer.setIconSize(10)
assertEquals(/* expected= */ iconContainer.calculateWidthFor(/* numIcons= */ 4f),
/* actual= */ 60f)
@@ -69,7 +71,7 @@
fun calculateWidthFor_fiveIcons_widthForFourIcons() {
iconContainer.setActualPaddingStart(10f)
iconContainer.setActualPaddingEnd(10f)
- iconContainer.setIconSize(10);
+ iconContainer.setIconSize(10)
assertEquals(/* expected= */ iconContainer.calculateWidthFor(/* numIcons= */ 5f),
/* actual= */ 60f)
}
@@ -78,7 +80,7 @@
fun calculateIconXTranslations_shortShelfOneIcon_atCorrectXWithoutOverflowDot() {
iconContainer.setActualPaddingStart(10f)
iconContainer.setActualPaddingEnd(10f)
- iconContainer.setIconSize(10);
+ iconContainer.setIconSize(10)
val icon = mockStatusBarIcon()
iconContainer.addView(icon)
@@ -99,7 +101,7 @@
fun calculateIconXTranslations_shortShelfFourIcons_atCorrectXWithoutOverflowDot() {
iconContainer.setActualPaddingStart(10f)
iconContainer.setActualPaddingEnd(10f)
- iconContainer.setIconSize(10);
+ iconContainer.setIconSize(10)
val iconOne = mockStatusBarIcon()
val iconTwo = mockStatusBarIcon()
@@ -128,7 +130,7 @@
fun calculateIconXTranslations_shortShelfFiveIcons_atCorrectXWithOverflowDot() {
iconContainer.setActualPaddingStart(10f)
iconContainer.setActualPaddingEnd(10f)
- iconContainer.setIconSize(10);
+ iconContainer.setIconSize(10)
val iconOne = mockStatusBarIcon()
val iconTwo = mockStatusBarIcon()
@@ -154,6 +156,55 @@
}
@Test
+ fun calculateIconXTranslations_givenWidthEnoughForThreeIcons_atCorrectXWithoutOverflowDot() {
+ iconContainer.setActualPaddingStart(0f)
+ iconContainer.setActualPaddingEnd(0f)
+ iconContainer.setActualLayoutWidth(30)
+ iconContainer.setIconSize(10)
+
+ val iconOne = mockStatusBarIcon()
+ val iconTwo = mockStatusBarIcon()
+ val iconThree = mockStatusBarIcon()
+
+ iconContainer.addView(iconOne)
+ iconContainer.addView(iconTwo)
+ iconContainer.addView(iconThree)
+ assertEquals(3, iconContainer.childCount)
+
+ iconContainer.calculateIconXTranslations()
+ assertEquals(0f, iconContainer.getIconState(iconOne).xTranslation)
+ assertEquals(10f, iconContainer.getIconState(iconTwo).xTranslation)
+ assertEquals(20f, iconContainer.getIconState(iconThree).xTranslation)
+ assertFalse(iconContainer.areIconsOverflowing())
+ }
+
+ @Test
+ fun calculateIconXTranslations_givenWidthNotEnoughForFourIcons_atCorrectXWithOverflowDot() {
+ iconContainer.setActualPaddingStart(0f)
+ iconContainer.setActualPaddingEnd(0f)
+ iconContainer.setActualLayoutWidth(35)
+ iconContainer.setIconSize(10)
+
+ val iconOne = mockStatusBarIcon()
+ val iconTwo = mockStatusBarIcon()
+ val iconThree = mockStatusBarIcon()
+ val iconFour = mockStatusBarIcon()
+
+ iconContainer.addView(iconOne)
+ iconContainer.addView(iconTwo)
+ iconContainer.addView(iconThree)
+ iconContainer.addView(iconFour)
+ assertEquals(4, iconContainer.childCount)
+
+ iconContainer.calculateIconXTranslations()
+ assertEquals(0f, iconContainer.getIconState(iconOne).xTranslation)
+ assertEquals(10f, iconContainer.getIconState(iconTwo).xTranslation)
+ assertEquals(STATE_DOT, iconContainer.getIconState(iconThree).visibleState)
+ assertEquals(STATE_HIDDEN, iconContainer.getIconState(iconFour).visibleState)
+ assertTrue(iconContainer.areIconsOverflowing())
+ }
+
+ @Test
fun shouldForceOverflow_appearingAboveSpeedBump_true() {
val forceOverflow = iconContainer.shouldForceOverflow(
/* i= */ 1,
@@ -161,7 +212,7 @@
/* iconAppearAmount= */ 1f,
/* maxVisibleIcons= */ 5
)
- assertTrue(forceOverflow);
+ assertTrue(forceOverflow)
}
@Test
@@ -172,7 +223,7 @@
/* iconAppearAmount= */ 0f,
/* maxVisibleIcons= */ 5
)
- assertTrue(forceOverflow);
+ assertTrue(forceOverflow)
}
@Test
@@ -183,7 +234,7 @@
/* iconAppearAmount= */ 0f,
/* maxVisibleIcons= */ 5
)
- assertFalse(forceOverflow);
+ assertFalse(forceOverflow)
}
@Test
@@ -210,6 +261,17 @@
}
@Test
+ fun isOverflowing_lastChildXGreaterThanDotX_true() {
+ val isOverflowing = iconContainer.isOverflowing(
+ /* isLastChild= */ true,
+ /* translationX= */ 9f,
+ /* layoutEnd= */ 10f,
+ /* iconSize= */ 2f,
+ )
+ assertTrue(isOverflowing)
+ }
+
+ @Test
fun isOverflowing_lastChildXGreaterThanLayoutEnd_true() {
val isOverflowing = iconContainer.isOverflowing(
/* isLastChild= */ true,
@@ -253,7 +315,7 @@
assertTrue(isOverflowing)
}
- private fun mockStatusBarIcon() : StatusBarIconView {
+ private fun mockStatusBarIcon(): StatusBarIconView {
val iconView = mock(StatusBarIconView::class.java)
whenever(iconView.width).thenReturn(10)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
index 2d96e59..c8ec1bf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
@@ -204,6 +204,7 @@
userChipViewModel,
centralSurfacesImpl,
shadeControllerImpl,
+ shadeViewController,
shadeLogger,
viewUtil,
configurationController
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
index ab80158..0dc1d9a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
@@ -44,6 +44,9 @@
import android.animation.Animator;
import android.app.AlarmManager;
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.content.res.TypedArray;
import android.graphics.Color;
import android.os.Handler;
import android.testing.AndroidTestingRunner;
@@ -56,14 +59,14 @@
import com.android.internal.colorextraction.ColorExtractor.GradientColors;
import com.android.keyguard.BouncerPanelExpansionCalculator;
import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.keyguard.TestScopeProvider;
import com.android.systemui.DejankUtils;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.ShadeInterpolation;
+import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants;
import com.android.systemui.dock.DockManager;
-import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
-import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants;
import com.android.systemui.keyguard.shared.model.KeyguardState;
import com.android.systemui.keyguard.shared.model.TransitionState;
import com.android.systemui.keyguard.shared.model.TransitionStep;
@@ -74,9 +77,11 @@
import com.android.systemui.statusbar.policy.FakeConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.kotlin.JavaAdapter;
import com.android.systemui.util.time.FakeSystemClock;
import com.android.systemui.util.wakelock.DelayedWakeLock;
import com.android.systemui.utils.os.FakeHandler;
+import com.android.systemui.wallpapers.data.repository.FakeWallpaperRepository;
import com.google.common.truth.Expect;
@@ -97,6 +102,7 @@
import java.util.Map;
import kotlinx.coroutines.CoroutineDispatcher;
+import kotlinx.coroutines.test.TestScope;
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@@ -110,6 +116,9 @@
private final LargeScreenShadeInterpolator
mLinearLargeScreenShadeInterpolator = new LinearLargeScreenShadeInterpolator();
+ private final TestScope mTestScope = TestScopeProvider.getTestScope();
+ private final JavaAdapter mJavaAdapter = new JavaAdapter(mTestScope.getBackgroundScope());
+
private ScrimController mScrimController;
private ScrimView mScrimBehind;
private ScrimView mNotificationsScrim;
@@ -120,6 +129,7 @@
private int mScrimVisibility;
private boolean mAlwaysOnEnabled;
private TestableLooper mLooper;
+ private Context mContext;
@Mock private AlarmManager mAlarmManager;
@Mock private DozeParameters mDozeParameters;
@Mock private LightBarController mLightBarController;
@@ -132,12 +142,13 @@
@Mock private KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
@Mock private PrimaryBouncerToGoneTransitionViewModel mPrimaryBouncerToGoneTransitionViewModel;
@Mock private KeyguardTransitionInteractor mKeyguardTransitionInteractor;
+ private final FakeWallpaperRepository mWallpaperRepository = new FakeWallpaperRepository();
@Mock private CoroutineDispatcher mMainDispatcher;
+ @Mock private TypedArray mMockTypedArray;
// TODO(b/204991468): Use a real PanelExpansionStateManager object once this bug is fixed. (The
// event-dispatch-on-registration pattern caused some of these unit tests to fail.)
@Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
- @Mock private FeatureFlags mFeatureFlags;
private static class AnimatorListener implements Animator.AnimatorListener {
private int mNumStarts;
@@ -181,10 +192,11 @@
mNumEnds = 0;
mNumCancels = 0;
}
- };
+ }
private AnimatorListener mAnimatorListener = new AnimatorListener();
+ private int mSurfaceColor = 0x112233;
private void finishAnimationsImmediately() {
// Execute code that will trigger animations.
@@ -213,10 +225,17 @@
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
+ mContext = spy(getContext());
+ when(mContext.obtainStyledAttributes(
+ new int[]{com.android.internal.R.attr.materialColorSurface}))
+ .thenReturn(mMockTypedArray);
- mScrimBehind = spy(new ScrimView(getContext()));
- mScrimInFront = new ScrimView(getContext());
- mNotificationsScrim = new ScrimView(getContext());
+ when(mMockTypedArray.getColorStateList(anyInt()))
+ .thenAnswer((invocation) -> ColorStateList.valueOf(mSurfaceColor));
+
+ mScrimBehind = spy(new ScrimView(mContext));
+ mScrimInFront = new ScrimView(mContext);
+ mNotificationsScrim = new ScrimView(mContext);
mAlwaysOnEnabled = true;
mLooper = TestableLooper.get(this);
DejankUtils.setImmediate(true);
@@ -261,20 +280,25 @@
mDockManager,
mConfigurationController,
new FakeExecutor(new FakeSystemClock()),
+ mJavaAdapter,
mScreenOffAnimationController,
mKeyguardUnlockAnimationController,
mStatusBarKeyguardViewManager,
mPrimaryBouncerToGoneTransitionViewModel,
mKeyguardTransitionInteractor,
+ mWallpaperRepository,
mMainDispatcher,
- mLinearLargeScreenShadeInterpolator,
- mFeatureFlags);
+ mLinearLargeScreenShadeInterpolator);
+ mScrimController.start();
mScrimController.setScrimVisibleListener(visible -> mScrimVisibility = visible);
mScrimController.attachViews(mScrimBehind, mNotificationsScrim, mScrimInFront);
mScrimController.setAnimatorListener(mAnimatorListener);
mScrimController.setHasBackdrop(false);
- mScrimController.setWallpaperSupportsAmbientMode(false);
+
+ mWallpaperRepository.getWallpaperSupportsAmbientMode().setValue(false);
+ mTestScope.getTestScheduler().runCurrent();
+
mScrimController.transitionTo(ScrimState.KEYGUARD);
finishAnimationsImmediately();
}
@@ -375,7 +399,9 @@
@Test
public void transitionToAod_withAodWallpaper() {
- mScrimController.setWallpaperSupportsAmbientMode(true);
+ mWallpaperRepository.getWallpaperSupportsAmbientMode().setValue(true);
+ mTestScope.getTestScheduler().runCurrent();
+
mScrimController.transitionTo(ScrimState.AOD);
finishAnimationsImmediately();
@@ -397,7 +423,9 @@
@Test
public void transitionToAod_withAodWallpaperAndLockScreenWallpaper() {
mScrimController.setHasBackdrop(true);
- mScrimController.setWallpaperSupportsAmbientMode(true);
+ mWallpaperRepository.getWallpaperSupportsAmbientMode().setValue(true);
+ mTestScope.getTestScheduler().runCurrent();
+
mScrimController.transitionTo(ScrimState.AOD);
finishAnimationsImmediately();
@@ -414,7 +442,9 @@
@Test
public void setHasBackdrop_withAodWallpaperAndAlbumArt() {
- mScrimController.setWallpaperSupportsAmbientMode(true);
+ mWallpaperRepository.getWallpaperSupportsAmbientMode().setValue(true);
+ mTestScope.getTestScheduler().runCurrent();
+
mScrimController.transitionTo(ScrimState.AOD);
finishAnimationsImmediately();
mScrimController.setHasBackdrop(true);
@@ -527,7 +557,9 @@
// Pre-condition
// Need to go to AoD first because PULSING doesn't change
// the back scrim opacity - otherwise it would hide AoD wallpapers.
- mScrimController.setWallpaperSupportsAmbientMode(false);
+ mWallpaperRepository.getWallpaperSupportsAmbientMode().setValue(false);
+ mTestScope.getTestScheduler().runCurrent();
+
mScrimController.transitionTo(ScrimState.AOD);
finishAnimationsImmediately();
assertScrimAlpha(Map.of(
@@ -576,7 +608,7 @@
mScrimController.transitionTo(BOUNCER);
finishAnimationsImmediately();
// Front scrim should be transparent
- // Back scrim should be visible without tint
+ // Back scrim should be visible and tinted to the surface color
assertScrimAlpha(Map.of(
mScrimInFront, TRANSPARENT,
mNotificationsScrim, TRANSPARENT,
@@ -584,9 +616,31 @@
assertScrimTinted(Map.of(
mScrimInFront, false,
- mScrimBehind, false,
+ mScrimBehind, true,
mNotificationsScrim, false
));
+
+ assertScrimTint(mScrimBehind, mSurfaceColor);
+ }
+
+ @Test
+ public void onThemeChange_bouncerBehindTint_isUpdatedToSurfaceColor() {
+ assertEquals(BOUNCER.getBehindTint(), 0x112233);
+ mSurfaceColor = 0x223344;
+ mConfigurationController.notifyThemeChanged();
+ assertEquals(BOUNCER.getBehindTint(), 0x223344);
+ }
+
+ @Test
+ public void onThemeChangeWhileClipQsScrim_bouncerBehindTint_remainsBlack() {
+ mScrimController.setClipsQsScrim(true);
+ mScrimController.transitionTo(BOUNCER);
+ finishAnimationsImmediately();
+
+ assertEquals(BOUNCER.getBehindTint(), Color.BLACK);
+ mSurfaceColor = 0x223344;
+ mConfigurationController.notifyThemeChanged();
+ assertEquals(BOUNCER.getBehindTint(), Color.BLACK);
}
@Test
@@ -618,16 +672,17 @@
finishAnimationsImmediately();
// Front scrim should be transparent
- // Back scrim should be visible without tint
+ // Back scrim should be visible and has a tint of surfaceColor
assertScrimAlpha(Map.of(
mScrimInFront, TRANSPARENT,
mNotificationsScrim, TRANSPARENT,
mScrimBehind, OPAQUE));
assertScrimTinted(Map.of(
mScrimInFront, false,
- mScrimBehind, false,
+ mScrimBehind, true,
mNotificationsScrim, false
));
+ assertScrimTint(mScrimBehind, mSurfaceColor);
}
@Test
@@ -932,19 +987,22 @@
mDockManager,
mConfigurationController,
new FakeExecutor(new FakeSystemClock()),
+ mJavaAdapter,
mScreenOffAnimationController,
mKeyguardUnlockAnimationController,
mStatusBarKeyguardViewManager,
mPrimaryBouncerToGoneTransitionViewModel,
mKeyguardTransitionInteractor,
+ mWallpaperRepository,
mMainDispatcher,
- mLinearLargeScreenShadeInterpolator,
- mFeatureFlags);
+ mLinearLargeScreenShadeInterpolator);
+ mScrimController.start();
mScrimController.setScrimVisibleListener(visible -> mScrimVisibility = visible);
mScrimController.attachViews(mScrimBehind, mNotificationsScrim, mScrimInFront);
mScrimController.setAnimatorListener(mAnimatorListener);
mScrimController.setHasBackdrop(false);
- mScrimController.setWallpaperSupportsAmbientMode(false);
+ mWallpaperRepository.getWallpaperSupportsAmbientMode().setValue(false);
+ mTestScope.getTestScheduler().runCurrent();
mScrimController.transitionTo(ScrimState.KEYGUARD);
finishAnimationsImmediately();
@@ -1069,7 +1127,9 @@
@Test
public void testWillHideAodWallpaper() {
- mScrimController.setWallpaperSupportsAmbientMode(true);
+ mWallpaperRepository.getWallpaperSupportsAmbientMode().setValue(true);
+ mTestScope.getTestScheduler().runCurrent();
+
mScrimController.transitionTo(ScrimState.AOD);
verify(mAlarmManager).setExact(anyInt(), anyLong(), any(), any(), any());
mScrimController.transitionTo(ScrimState.KEYGUARD);
@@ -1080,7 +1140,8 @@
public void testWillHideDockedWallpaper() {
mAlwaysOnEnabled = false;
when(mDockManager.isDocked()).thenReturn(true);
- mScrimController.setWallpaperSupportsAmbientMode(true);
+ mWallpaperRepository.getWallpaperSupportsAmbientMode().setValue(true);
+ mTestScope.getTestScheduler().runCurrent();
mScrimController.transitionTo(ScrimState.AOD);
@@ -1129,7 +1190,9 @@
@Test
public void testHidesShowWhenLockedActivity() {
- mScrimController.setWallpaperSupportsAmbientMode(true);
+ mWallpaperRepository.getWallpaperSupportsAmbientMode().setValue(true);
+ mTestScope.getTestScheduler().runCurrent();
+
mScrimController.setKeyguardOccluded(true);
mScrimController.transitionTo(ScrimState.AOD);
finishAnimationsImmediately();
@@ -1146,7 +1209,9 @@
@Test
public void testHidesShowWhenLockedActivity_whenAlreadyInAod() {
- mScrimController.setWallpaperSupportsAmbientMode(true);
+ mWallpaperRepository.getWallpaperSupportsAmbientMode().setValue(true);
+ mTestScope.getTestScheduler().runCurrent();
+
mScrimController.transitionTo(ScrimState.AOD);
finishAnimationsImmediately();
assertScrimAlpha(Map.of(
@@ -1764,6 +1829,13 @@
assertEquals(message, hasTint, scrim.getTint() != Color.TRANSPARENT);
}
+ private void assertScrimTint(ScrimView scrim, int expectedTint) {
+ String message = "Tint test failed with expected scrim tint: "
+ + Integer.toHexString(expectedTint) + " and actual tint: "
+ + Integer.toHexString(scrim.getTint()) + " for scrim: " + getScrimName(scrim);
+ assertEquals(message, expectedTint, scrim.getTint(), 0.1);
+ }
+
private String getScrimName(ScrimView scrim) {
if (scrim == mScrimInFront) {
return "front";
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java
index 8aaa57f..9157cd9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java
@@ -16,7 +16,6 @@
import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_ICON;
import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_MOBILE;
-import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_WIFI;
import static junit.framework.Assert.assertTrue;
@@ -41,13 +40,11 @@
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.StatusBarIconView;
import com.android.systemui.statusbar.StatusBarMobileView;
-import com.android.systemui.statusbar.StatusBarWifiView;
import com.android.systemui.statusbar.StatusIconDisplayable;
import com.android.systemui.statusbar.connectivity.ui.MobileContextProvider;
import com.android.systemui.statusbar.phone.StatusBarIconController.DarkIconManager;
import com.android.systemui.statusbar.phone.StatusBarIconController.IconManager;
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
-import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags;
import com.android.systemui.statusbar.pipeline.mobile.ui.MobileUiAdapter;
import com.android.systemui.statusbar.pipeline.wifi.ui.WifiUiAdapter;
@@ -156,13 +153,9 @@
assertTrue("Expected StatusBarIconView",
(manager.getViewAt(0) instanceof StatusBarIconView));
- holder = holderForType(TYPE_WIFI);
- manager.onIconAdded(1, "test_wifi", false, holder);
- assertTrue(manager.getViewAt(1) instanceof StatusBarWifiView);
-
holder = holderForType(TYPE_MOBILE);
- manager.onIconAdded(2, "test_mobile", false, holder);
- assertTrue(manager.getViewAt(2) instanceof StatusBarMobileView);
+ manager.onIconAdded(1, "test_mobile", false, holder);
+ assertTrue(manager.getViewAt(1) instanceof StatusBarMobileView);
}
private StatusBarIconHolder holderForType(int type) {
@@ -170,9 +163,6 @@
case TYPE_MOBILE:
return StatusBarIconHolder.fromMobileIconState(mock(MobileIconState.class));
- case TYPE_WIFI:
- return StatusBarIconHolder.fromWifiIconState(mock(WifiIconState.class));
-
case TYPE_ICON:
default:
return StatusBarIconHolder.fromIcon(mock(StatusBarIcon.class));
@@ -214,13 +204,6 @@
}
@Override
- protected StatusBarWifiView addWifiIcon(int index, String slot, WifiIconState state) {
- StatusBarWifiView mock = mock(StatusBarWifiView.class);
- mGroup.addView(mock, index);
- return mock;
- }
-
- @Override
protected StatusBarMobileView addMobileIcon(int index, String slot, MobileIconState state) {
StatusBarMobileView mock = mock(StatusBarMobileView.class);
mGroup.addView(mock, index);
@@ -254,13 +237,6 @@
}
@Override
- protected StatusBarWifiView addWifiIcon(int index, String slot, WifiIconState state) {
- StatusBarWifiView mock = mock(StatusBarWifiView.class);
- mGroup.addView(mock, index);
- return mock;
- }
-
- @Override
protected StatusBarMobileView addMobileIcon(int index, String slot, MobileIconState state) {
StatusBarMobileView mock = mock(StatusBarMobileView.class);
mGroup.addView(mock, index);
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 c7143de..ed9cf3f 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
@@ -35,6 +35,7 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.service.trust.TrustAgentService;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.MotionEvent;
@@ -57,6 +58,8 @@
import com.android.keyguard.KeyguardMessageAreaController;
import com.android.keyguard.KeyguardSecurityModel;
import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.keyguard.TrustGrantFlags;
import com.android.keyguard.ViewMediatorCallback;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor;
@@ -84,7 +87,6 @@
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.unfold.SysUIUnfoldComponent;
import com.google.common.truth.Truth;
@@ -154,7 +156,7 @@
@Captor
private ArgumentCaptor<OnBackInvokedCallback> mBackCallbackCaptor;
@Captor
- private ArgumentCaptor<KeyguardStateController.Callback> mKeyguardStateControllerCallback;
+ private ArgumentCaptor<KeyguardUpdateMonitorCallback> mKeyguardUpdateMonitorCallback;
@Before
@@ -936,18 +938,24 @@
}
@Test
- public void onDeviceUnlocked_hideAlternateBouncerAndClearMessageArea() {
+ public void onTrustChanged_hideAlternateBouncerAndClearMessageArea() {
+ // GIVEN keyguard update monitor callback is registered
+ verify(mKeyguardUpdateMonitor).registerCallback(mKeyguardUpdateMonitorCallback.capture());
+
reset(mKeyguardUpdateMonitor);
reset(mKeyguardMessageAreaController);
- // GIVEN keyguard state controller callback is registered
- verify(mKeyguardStateController).addCallback(mKeyguardStateControllerCallback.capture());
-
// GIVEN alternate bouncer state = not visible
when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(false);
- // WHEN the device is unlocked
- mKeyguardStateControllerCallback.getValue().onUnlockedChanged();
+ // WHEN the device is trusted by active unlock
+ mKeyguardUpdateMonitorCallback.getValue().onTrustGrantedForCurrentUser(
+ true,
+ true,
+ new TrustGrantFlags(TrustAgentService.FLAG_GRANT_TRUST_DISMISS_KEYGUARD
+ | TrustAgentService.FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE),
+ null
+ );
// THEN the false visibility state is propagated to the keyguardUpdateMonitor
verify(mKeyguardUpdateMonitor).setAlternateBouncerShowing(eq(false));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
index d44af88..9c7f619 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
@@ -21,6 +21,8 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.mockito.AdditionalAnswers.answerVoid;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -61,10 +63,14 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.assist.AssistManager;
+import com.android.systemui.classifier.FalsingCollectorFake;
import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.power.data.repository.FakePowerRepository;
+import com.android.systemui.power.domain.interactor.PowerInteractor;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shade.NotificationShadeWindowViewController;
import com.android.systemui.shade.ShadeControllerImpl;
@@ -110,6 +116,8 @@
@TestableLooper.RunWithLooper(setAsMainLooper = true)
public class StatusBarNotificationActivityStarterTest extends SysuiTestCase {
+ private static final int DISPLAY_ID = 0;
+
@Mock
private AssistManager mAssistManager;
@Mock
@@ -118,13 +126,12 @@
private NotificationClickNotifier mClickNotifier;
@Mock
private StatusBarStateController mStatusBarStateController;
+ @Mock private ScreenOffAnimationController mScreenOffAnimationController;
@Mock
private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
@Mock
private NotificationRemoteInputManager mRemoteInputManager;
@Mock
- private CentralSurfaces mCentralSurfaces;
- @Mock
private KeyguardStateController mKeyguardStateController;
@Mock
private NotificationInterruptStateProvider mNotificationInterruptStateProvider;
@@ -150,6 +157,8 @@
private ActivityLaunchAnimator mActivityLaunchAnimator;
@Mock
private InteractionJankMonitor mJankMonitor;
+ private FakePowerRepository mPowerRepository;
+ private PowerInteractor mPowerInteractor;
@Mock
private UserTracker mUserTracker;
private final FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock());
@@ -199,6 +208,14 @@
when(mUserTracker.getUserHandle()).thenReturn(
UserHandle.of(ActivityManager.getCurrentUser()));
+ mPowerRepository = new FakePowerRepository();
+ mPowerInteractor = new PowerInteractor(
+ mPowerRepository,
+ new FakeKeyguardRepository(),
+ new FalsingCollectorFake(),
+ mScreenOffAnimationController,
+ mStatusBarStateController);
+
HeadsUpManagerPhone headsUpManager = mock(HeadsUpManagerPhone.class);
NotificationLaunchAnimatorControllerProvider notificationAnimationProvider =
new NotificationLaunchAnimatorControllerProvider(
@@ -209,6 +226,7 @@
mNotificationActivityStarter =
new StatusBarNotificationActivityStarter(
getContext(),
+ DISPLAY_ID,
mHandler,
mUiBgExecutor,
mVisibilityProvider,
@@ -231,13 +249,13 @@
mock(MetricsLogger.class),
mock(StatusBarNotificationActivityStarterLogger.class),
mOnUserInteractionCallback,
- mCentralSurfaces,
mock(NotificationPresenter.class),
mock(ShadeViewController.class),
mock(NotificationShadeWindowController.class),
mActivityLaunchAnimator,
notificationAnimationProvider,
mock(LaunchFullScreenIntentProvider.class),
+ mPowerInteractor,
mock(FeatureFlags.class),
mUserTracker
);
@@ -274,7 +292,7 @@
notification.flags |= Notification.FLAG_AUTO_CANCEL;
when(mKeyguardStateController.isShowing()).thenReturn(true);
- when(mCentralSurfaces.isOccluded()).thenReturn(true);
+ when(mKeyguardStateController.isOccluded()).thenReturn(true);
// When
mNotificationActivityStarter.onNotificationClicked(entry, mNotificationRow);
@@ -340,7 +358,7 @@
// Given
sbn.getNotification().contentIntent = null;
when(mKeyguardStateController.isShowing()).thenReturn(true);
- when(mCentralSurfaces.isOccluded()).thenReturn(true);
+ when(mKeyguardStateController.isOccluded()).thenReturn(true);
// When
mNotificationActivityStarter.onNotificationClicked(entry, mBubbleNotificationRow);
@@ -368,7 +386,7 @@
// Given
sbn.getNotification().contentIntent = mContentIntent;
when(mKeyguardStateController.isShowing()).thenReturn(true);
- when(mCentralSurfaces.isOccluded()).thenReturn(true);
+ when(mKeyguardStateController.isOccluded()).thenReturn(true);
// When
mNotificationActivityStarter.onNotificationClicked(entry, mBubbleNotificationRow);
@@ -402,11 +420,13 @@
when(entry.getImportance()).thenReturn(NotificationManager.IMPORTANCE_HIGH);
when(entry.getSbn()).thenReturn(sbn);
- // WHEN
+ // WHEN the intent is launched while dozing
+ when(mStatusBarStateController.isDozing()).thenReturn(true);
mNotificationActivityStarter.launchFullScreenIntent(entry);
// THEN display should try wake up for the full screen intent
- verify(mCentralSurfaces).wakeUpForFullScreenIntent();
+ assertThat(mPowerRepository.getLastWakeReason()).isNotNull();
+ assertThat(mPowerRepository.getLastWakeWhy()).isNotNull();
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
index 5bd6ff4..cd8aaa2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
@@ -42,7 +42,6 @@
import com.android.systemui.shade.NotificationShadeWindowView;
import com.android.systemui.shade.QuickSettingsController;
import com.android.systemui.shade.ShadeController;
-import com.android.systemui.shade.ShadeNotificationPresenter;
import com.android.systemui.shade.ShadeViewController;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
@@ -52,7 +51,6 @@
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
-import com.android.systemui.statusbar.notification.NotifPipelineFlags;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource;
@@ -86,14 +84,11 @@
mock(NotificationsInteractor.class);
private final KeyguardStateController mKeyguardStateController =
mock(KeyguardStateController.class);
- private final NotifPipelineFlags mNotifPipelineFlags = mock(NotifPipelineFlags.class);
private final InitController mInitController = new InitController();
@Before
public void setup() {
mMetricsLogger = new FakeMetricsLogger();
- LockscreenGestureLogger lockscreenGestureLogger = new LockscreenGestureLogger(
- mMetricsLogger);
mCommandQueue = new CommandQueue(mContext, new FakeDisplayTracker(mContext));
mDependency.injectTestDependency(StatusBarStateController.class,
mock(SysuiStatusBarStateController.class));
@@ -111,8 +106,6 @@
when(notificationShadeWindowView.getResources()).thenReturn(mContext.getResources());
ShadeViewController shadeViewController = mock(ShadeViewController.class);
- when(shadeViewController.getShadeNotificationPresenter())
- .thenReturn(mock(ShadeNotificationPresenter.class));
mStatusBarNotificationPresenter = new StatusBarNotificationPresenter(
mContext,
shadeViewController,
@@ -135,11 +128,9 @@
mock(NotifShadeEventSource.class),
mock(NotificationMediaManager.class),
mock(NotificationGutsManager.class),
- lockscreenGestureLogger,
mInitController,
mNotificationInterruptStateProvider,
mock(NotificationRemoteInputManager.class),
- mNotifPipelineFlags,
mock(NotificationRemoteInputManager.Callback.class),
mock(NotificationListContainer.class));
mInitController.executePostInitTasks();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
index 862eb00..c8b6f13d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
@@ -159,6 +159,7 @@
mock(),
mock(),
FakeExecutor(FakeSystemClock()),
+ dispatcher,
testScope.backgroundScope,
mock(),
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcherTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcherTest.kt
index 30b95ef..5bc98e0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcherTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcherTest.kt
@@ -83,6 +83,7 @@
logger,
tableLogger,
mainExecutor,
+ testDispatcher,
testScope.backgroundScope,
wifiManager,
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
index 5ed3a5c..7007345 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
@@ -76,7 +76,8 @@
private lateinit var executor: Executor
private lateinit var connectivityRepository: ConnectivityRepository
- private val testScope = TestScope(UnconfinedTestDispatcher())
+ private val dispatcher = UnconfinedTestDispatcher()
+ private val testScope = TestScope(dispatcher)
@Before
fun setUp() {
@@ -1301,6 +1302,7 @@
logger,
tableLogger,
executor,
+ dispatcher,
testScope.backgroundScope,
wifiManager,
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerUiTest.kt b/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerUiTest.kt
index 90821bd..d212c02 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerUiTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerUiTest.kt
@@ -126,6 +126,16 @@
}
@Test
+ fun updateBatteryState_capacitySame_inputDeviceChanges_updatesInputDeviceId() {
+ stylusUsiPowerUi.updateBatteryState(0, FixedCapacityBatteryState(0.1f))
+ stylusUsiPowerUi.updateBatteryState(1, FixedCapacityBatteryState(0.1f))
+
+ assertThat(stylusUsiPowerUi.inputDeviceId).isEqualTo(1)
+ verify(notificationManager, times(1))
+ .notify(eq(R.string.stylus_battery_low_percentage), any())
+ }
+
+ @Test
fun updateBatteryState_existingNotification_capacityAboveThreshold_cancelsNotification() {
stylusUsiPowerUi.updateBatteryState(0, FixedCapacityBatteryState(0.1f))
stylusUsiPowerUi.updateBatteryState(0, FixedCapacityBatteryState(0.8f))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java b/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java
index 27957ed..f299ad4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java
@@ -28,6 +28,7 @@
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import android.app.Application;
@@ -36,6 +37,7 @@
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
+import android.hardware.display.DisplayManager;
import android.os.Binder;
import android.os.Build;
import android.os.Parcel;
@@ -74,6 +76,8 @@
import org.mockito.MockitoAnnotations;
import org.mockito.stubbing.Answer;
+import java.util.Arrays;
+
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
@@ -400,6 +404,18 @@
verify(mToastLogger, never()).logOnHideToast(PACKAGE_NAME_1, TOKEN_1.toString());
}
+ @Test
+ public void testShowToast_invalidDisplayId_logsAndSkipsToast() {
+ int invalidDisplayId = getInvalidDisplayId();
+
+ mToastUI.showToast(UID_1, PACKAGE_NAME_1, TOKEN_1, TEXT, WINDOW_TOKEN_1, Toast.LENGTH_LONG,
+ mCallback, invalidDisplayId);
+
+ verify(mToastLogger).logOnSkipToastForInvalidDisplay(PACKAGE_NAME_1, TOKEN_1.toString(),
+ invalidDisplayId);
+ verifyZeroInteractions(mWindowManager);
+ }
+
private View verifyWmAddViewAndAttachToParent() {
ArgumentCaptor<View> viewCaptor = ArgumentCaptor.forClass(View.class);
verify(mWindowManager).addView(viewCaptor.capture(), any());
@@ -416,4 +432,10 @@
return null;
};
}
+
+ private int getInvalidDisplayId() {
+ return Arrays.stream(
+ mContext.getSystemService(DisplayManager.class).getDisplays())
+ .map(Display::getDisplayId).max(Integer::compare).get() + 1;
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/WallpaperControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/WallpaperControllerTest.kt
index d8e418a..b13cb72 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/WallpaperControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/WallpaperControllerTest.kt
@@ -26,6 +26,7 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.util.mockito.eq
+import com.android.systemui.wallpapers.data.repository.FakeWallpaperRepository
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@@ -56,6 +57,7 @@
private lateinit var viewRootImpl: ViewRootImpl
@Mock
private lateinit var windowToken: IBinder
+ private val wallpaperRepository = FakeWallpaperRepository()
@JvmField
@Rule
@@ -69,7 +71,7 @@
`when`(root.windowToken).thenReturn(windowToken)
`when`(root.isAttachedToWindow).thenReturn(true)
- wallaperController = WallpaperController(wallpaperManager)
+ wallaperController = WallpaperController(wallpaperManager, wallpaperRepository)
wallaperController.rootView = root
}
@@ -90,9 +92,9 @@
@Test
fun setUnfoldTransitionZoom_defaultUnfoldTransitionIsDisabled_doesNotUpdateWallpaperZoom() {
- wallaperController.onWallpaperInfoUpdated(createWallpaperInfo(
+ wallpaperRepository.wallpaperInfo.value = createWallpaperInfo(
useDefaultTransition = false
- ))
+ )
wallaperController.setUnfoldTransitionZoom(0.5f)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
index 8f725be..c819108 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
@@ -16,6 +16,7 @@
package com.android.systemui.volume;
+import static com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION;
import static com.android.systemui.volume.Events.DISMISS_REASON_UNKNOWN;
import static com.android.systemui.volume.Events.SHOW_REASON_UNKNOWN;
import static com.android.systemui.volume.VolumeDialogControllerImpl.STREAMS;
@@ -51,6 +52,7 @@
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FakeFeatureFlags;
import com.android.systemui.media.dialog.MediaOutputDialogFactory;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.VolumeDialogController;
@@ -117,6 +119,8 @@
}
};
+ private FakeFeatureFlags mFeatureFlags;
+
@Before
public void setup() throws Exception {
MockitoAnnotations.initMocks(this);
@@ -132,6 +136,8 @@
mConfigurationController = new FakeConfigurationController();
+ mFeatureFlags = new FakeFeatureFlags();
+
mDialog = new VolumeDialogImpl(
getContext(),
mVolumeDialogController,
@@ -142,10 +148,12 @@
mVolumePanelFactory,
mActivityStarter,
mInteractionJankMonitor,
+ false,
mCsdWarningDialogFactory,
mPostureController,
mTestableLooper.getLooper(),
- mDumpManager);
+ mDumpManager,
+ mFeatureFlags);
mDialog.init(0, null);
State state = createShellState();
mDialog.onStateChangedH(state);
@@ -253,6 +261,7 @@
@Test
public void testVibrateOnRingerChangedToVibrate() {
+ mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false);
final State initialSilentState = new State();
initialSilentState.ringerModeInternal = AudioManager.RINGER_MODE_SILENT;
@@ -273,7 +282,30 @@
}
@Test
+ public void testControllerDoesNotVibrateOnRingerChangedToVibrate_OnewayAPI_On() {
+ mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true);
+ final State initialSilentState = new State();
+ initialSilentState.ringerModeInternal = AudioManager.RINGER_MODE_SILENT;
+
+ final State vibrateState = new State();
+ vibrateState.ringerModeInternal = AudioManager.RINGER_MODE_VIBRATE;
+
+ // change ringer to silent
+ mDialog.onStateChangedH(initialSilentState);
+
+ // expected: shouldn't call vibrate yet
+ verify(mVolumeDialogController, never()).vibrate(any());
+
+ // changed ringer to vibrate
+ mDialog.onStateChangedH(vibrateState);
+
+ // expected: vibrate method of controller is not used
+ verify(mVolumeDialogController, never()).vibrate(any());
+ }
+
+ @Test
public void testNoVibrateOnRingerInitialization() {
+ mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false);
final State initialUnsetState = new State();
initialUnsetState.ringerModeInternal = -1;
@@ -291,7 +323,42 @@
}
@Test
+ public void testControllerDoesNotVibrateOnRingerInitialization_OnewayAPI_On() {
+ mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true);
+ final State initialUnsetState = new State();
+ initialUnsetState.ringerModeInternal = -1;
+
+ // ringer not initialized yet:
+ mDialog.onStateChangedH(initialUnsetState);
+
+ final State vibrateState = new State();
+ vibrateState.ringerModeInternal = AudioManager.RINGER_MODE_VIBRATE;
+
+ // changed ringer to vibrate
+ mDialog.onStateChangedH(vibrateState);
+
+ // shouldn't call vibrate on the controller either
+ verify(mVolumeDialogController, never()).vibrate(any());
+ }
+
+ @Test
public void testSelectVibrateFromDrawer() {
+ mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false);
+ final State initialUnsetState = new State();
+ initialUnsetState.ringerModeInternal = AudioManager.RINGER_MODE_NORMAL;
+ mDialog.onStateChangedH(initialUnsetState);
+
+ mActiveRinger.performClick();
+ mDrawerVibrate.performClick();
+
+ // Make sure we've actually changed the ringer mode.
+ verify(mVolumeDialogController, times(1)).setRingerMode(
+ AudioManager.RINGER_MODE_VIBRATE, false);
+ }
+
+ @Test
+ public void testSelectVibrateFromDrawer_OnewayAPI_On() {
+ mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true);
final State initialUnsetState = new State();
initialUnsetState.ringerModeInternal = AudioManager.RINGER_MODE_NORMAL;
mDialog.onStateChangedH(initialUnsetState);
@@ -306,6 +373,22 @@
@Test
public void testSelectMuteFromDrawer() {
+ mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false);
+ final State initialUnsetState = new State();
+ initialUnsetState.ringerModeInternal = AudioManager.RINGER_MODE_NORMAL;
+ mDialog.onStateChangedH(initialUnsetState);
+
+ mActiveRinger.performClick();
+ mDrawerMute.performClick();
+
+ // Make sure we've actually changed the ringer mode.
+ verify(mVolumeDialogController, times(1)).setRingerMode(
+ AudioManager.RINGER_MODE_SILENT, false);
+ }
+
+ @Test
+ public void testSelectMuteFromDrawer_OnewayAPI_On() {
+ mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true);
final State initialUnsetState = new State();
initialUnsetState.ringerModeInternal = AudioManager.RINGER_MODE_NORMAL;
mDialog.onStateChangedH(initialUnsetState);
@@ -320,6 +403,22 @@
@Test
public void testSelectNormalFromDrawer() {
+ mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false);
+ final State initialUnsetState = new State();
+ initialUnsetState.ringerModeInternal = AudioManager.RINGER_MODE_VIBRATE;
+ mDialog.onStateChangedH(initialUnsetState);
+
+ mActiveRinger.performClick();
+ mDrawerNormal.performClick();
+
+ // Make sure we've actually changed the ringer mode.
+ verify(mVolumeDialogController, times(1)).setRingerMode(
+ AudioManager.RINGER_MODE_NORMAL, false);
+ }
+
+ @Test
+ public void testSelectNormalFromDrawer_OnewayAPI_On() {
+ mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true);
final State initialUnsetState = new State();
initialUnsetState.ringerModeInternal = AudioManager.RINGER_MODE_VIBRATE;
mDialog.onStateChangedH(initialUnsetState);
@@ -378,10 +477,12 @@
mVolumePanelFactory,
mActivityStarter,
mInteractionJankMonitor,
+ false,
mCsdWarningDialogFactory,
devicePostureController,
mTestableLooper.getLooper(),
- mDumpManager
+ mDumpManager,
+ mFeatureFlags
);
dialog.init(0 , null);
@@ -397,6 +498,8 @@
int gravity = dialog.getWindowGravity();
assertEquals(Gravity.TOP, gravity & Gravity.VERTICAL_GRAVITY_MASK);
+
+ cleanUp(dialog);
}
@Test
@@ -415,10 +518,12 @@
mVolumePanelFactory,
mActivityStarter,
mInteractionJankMonitor,
+ false,
mCsdWarningDialogFactory,
devicePostureController,
mTestableLooper.getLooper(),
- mDumpManager
+ mDumpManager,
+ mFeatureFlags
);
dialog.init(0, null);
@@ -433,6 +538,8 @@
int gravity = dialog.getWindowGravity();
assertEquals(Gravity.CENTER_VERTICAL, gravity & Gravity.VERTICAL_GRAVITY_MASK);
+
+ cleanUp(dialog);
}
@Test
@@ -451,10 +558,12 @@
mVolumePanelFactory,
mActivityStarter,
mInteractionJankMonitor,
+ false,
mCsdWarningDialogFactory,
devicePostureController,
mTestableLooper.getLooper(),
- mDumpManager
+ mDumpManager,
+ mFeatureFlags
);
dialog.init(0, null);
@@ -469,6 +578,8 @@
int gravity = dialog.getWindowGravity();
assertEquals(Gravity.CENTER_VERTICAL, gravity & Gravity.VERTICAL_GRAVITY_MASK);
+
+ cleanUp(dialog);
}
@Test
@@ -489,18 +600,21 @@
mVolumePanelFactory,
mActivityStarter,
mInteractionJankMonitor,
+ false,
mCsdWarningDialogFactory,
mPostureController,
mTestableLooper.getLooper(),
- mDumpManager
+ mDumpManager,
+ mFeatureFlags
);
dialog.init(0, null);
verify(mPostureController, never()).removeCallback(any());
-
dialog.destroy();
verify(mPostureController).removeCallback(any());
+
+ cleanUp(dialog);
}
private void setOrientation(int orientation) {
@@ -513,14 +627,18 @@
@After
public void teardown() {
- if (mDialog != null) {
- mDialog.clearInternalHandlerAfterTest();
- }
+ cleanUp(mDialog);
setOrientation(mOriginalOrientation);
mTestableLooper.processAllMessages();
reset(mPostureController);
}
+ private void cleanUp(VolumeDialogImpl dialog) {
+ if (dialog != null) {
+ dialog.clearInternalHandlerAfterTest();
+ }
+ }
+
/*
@Test
public void testContentDescriptions() {
diff --git a/services/core/java/com/android/server/biometrics/BiometricSensorPrivacy.java b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/data/repository/FakeWallpaperRepository.kt
similarity index 60%
copy from services/core/java/com/android/server/biometrics/BiometricSensorPrivacy.java
copy to packages/SystemUI/tests/src/com/android/systemui/wallpapers/data/repository/FakeWallpaperRepository.kt
index 6727fbc..fe5024f 100644
--- a/services/core/java/com/android/server/biometrics/BiometricSensorPrivacy.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/data/repository/FakeWallpaperRepository.kt
@@ -14,12 +14,13 @@
* limitations under the License.
*/
-package com.android.server.biometrics;
+package com.android.systemui.wallpapers.data.repository
-/**
- * Interface for biometric operations to get camera privacy state.
- */
-public interface BiometricSensorPrivacy {
- /* Returns true if privacy is enabled and camera access is disabled. */
- boolean isCameraPrivacyEnabled();
+import android.app.WallpaperInfo
+import kotlinx.coroutines.flow.MutableStateFlow
+
+/** Fake implementation of the wallpaper repository. */
+class FakeWallpaperRepository : WallpaperRepository {
+ override val wallpaperInfo = MutableStateFlow<WallpaperInfo?>(null)
+ override val wallpaperSupportsAmbientMode = MutableStateFlow(false)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryImplTest.kt
new file mode 100644
index 0000000..f8b096a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryImplTest.kt
@@ -0,0 +1,425 @@
+/*
+ * 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.wallpapers.data.repository
+
+import android.app.WallpaperInfo
+import android.app.WallpaperManager
+import android.content.Intent
+import android.content.pm.UserInfo
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.user.data.model.SelectedUserModel
+import com.android.systemui.user.data.model.SelectionStatus
+import com.android.systemui.user.data.repository.FakeUserRepository
+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.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+
+@SmallTest
+@OptIn(ExperimentalCoroutinesApi::class)
+class WallpaperRepositoryImplTest : SysuiTestCase() {
+
+ private val testScope = TestScope(StandardTestDispatcher())
+ private val userRepository = FakeUserRepository()
+ private val wallpaperManager: WallpaperManager = mock()
+
+ private val underTest: WallpaperRepositoryImpl by lazy {
+ WallpaperRepositoryImpl(
+ testScope.backgroundScope,
+ fakeBroadcastDispatcher,
+ userRepository,
+ wallpaperManager,
+ context,
+ )
+ }
+
+ @Before
+ fun setUp() {
+ whenever(wallpaperManager.isWallpaperSupported).thenReturn(true)
+ context.orCreateTestableResources.addOverride(
+ com.android.internal.R.bool.config_dozeSupportsAodWallpaper,
+ true,
+ )
+ }
+
+ @Test
+ fun wallpaperInfo_nullInfo() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.wallpaperInfo)
+
+ whenever(wallpaperManager.getWallpaperInfoForUser(any())).thenReturn(null)
+
+ fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+ context,
+ Intent(Intent.ACTION_WALLPAPER_CHANGED),
+ )
+
+ assertThat(latest).isNull()
+ }
+
+ @Test
+ fun wallpaperInfo_hasInfoFromManager() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.wallpaperInfo)
+
+ whenever(wallpaperManager.getWallpaperInfoForUser(any())).thenReturn(UNSUPPORTED_WP)
+
+ fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+ context,
+ Intent(Intent.ACTION_WALLPAPER_CHANGED),
+ )
+
+ assertThat(latest).isEqualTo(UNSUPPORTED_WP)
+ }
+
+ @Test
+ fun wallpaperInfo_initialValueIsFetched() =
+ testScope.runTest {
+ whenever(wallpaperManager.getWallpaperInfoForUser(USER_WITH_SUPPORTED_WP.id))
+ .thenReturn(SUPPORTED_WP)
+ userRepository.setUserInfos(listOf(USER_WITH_SUPPORTED_WP))
+ userRepository.setSelectedUserInfo(USER_WITH_SUPPORTED_WP)
+
+ // WHEN the repo initially starts up (underTest is lazy), then it fetches the current
+ // value for the wallpaper
+ assertThat(underTest.wallpaperInfo.value).isEqualTo(SUPPORTED_WP)
+ }
+
+ @Test
+ fun wallpaperInfo_updatesOnUserChanged() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.wallpaperInfo)
+
+ val user3 = UserInfo(/* id= */ 3, /* name= */ "user3", /* flags= */ 0)
+ val user3Wp = mock<WallpaperInfo>()
+ whenever(wallpaperManager.getWallpaperInfoForUser(user3.id)).thenReturn(user3Wp)
+
+ val user4 = UserInfo(/* id= */ 4, /* name= */ "user4", /* flags= */ 0)
+ val user4Wp = mock<WallpaperInfo>()
+ whenever(wallpaperManager.getWallpaperInfoForUser(user4.id)).thenReturn(user4Wp)
+
+ userRepository.setUserInfos(listOf(user3, user4))
+
+ // WHEN user3 is selected
+ userRepository.setSelectedUserInfo(user3)
+
+ // THEN user3's wallpaper is used
+ assertThat(latest).isEqualTo(user3Wp)
+
+ // WHEN the user is switched to user4
+ userRepository.setSelectedUserInfo(user4)
+
+ // THEN user4's wallpaper is used
+ assertThat(latest).isEqualTo(user4Wp)
+ }
+
+ @Test
+ fun wallpaperInfo_doesNotUpdateOnUserChanging() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.wallpaperInfo)
+
+ val user3 = UserInfo(/* id= */ 3, /* name= */ "user3", /* flags= */ 0)
+ val user3Wp = mock<WallpaperInfo>()
+ whenever(wallpaperManager.getWallpaperInfoForUser(user3.id)).thenReturn(user3Wp)
+
+ val user4 = UserInfo(/* id= */ 4, /* name= */ "user4", /* flags= */ 0)
+ val user4Wp = mock<WallpaperInfo>()
+ whenever(wallpaperManager.getWallpaperInfoForUser(user4.id)).thenReturn(user4Wp)
+
+ userRepository.setUserInfos(listOf(user3, user4))
+
+ // WHEN user3 is selected
+ userRepository.setSelectedUserInfo(user3)
+
+ // THEN user3's wallpaper is used
+ assertThat(latest).isEqualTo(user3Wp)
+
+ // WHEN the user has started switching to user4 but hasn't finished yet
+ userRepository.selectedUser.value =
+ SelectedUserModel(user4, SelectionStatus.SELECTION_IN_PROGRESS)
+
+ // THEN the wallpaper still matches user3
+ assertThat(latest).isEqualTo(user3Wp)
+ }
+
+ @Test
+ fun wallpaperInfo_updatesOnIntent() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.wallpaperInfo)
+
+ val wp1 = mock<WallpaperInfo>()
+ whenever(wallpaperManager.getWallpaperInfoForUser(any())).thenReturn(wp1)
+
+ assertThat(latest).isEqualTo(wp1)
+
+ // WHEN the info is new and a broadcast is sent
+ val wp2 = mock<WallpaperInfo>()
+ whenever(wallpaperManager.getWallpaperInfoForUser(any())).thenReturn(wp2)
+ fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+ context,
+ Intent(Intent.ACTION_WALLPAPER_CHANGED),
+ )
+
+ // THEN the flow updates
+ assertThat(latest).isEqualTo(wp2)
+ }
+
+ @Test
+ fun wallpaperInfo_wallpaperNotSupported_alwaysNull() =
+ testScope.runTest {
+ whenever(wallpaperManager.isWallpaperSupported).thenReturn(false)
+
+ val latest by collectLastValue(underTest.wallpaperInfo)
+ assertThat(latest).isNull()
+
+ // Even WHEN there *is* current wallpaper
+ val wp1 = mock<WallpaperInfo>()
+ whenever(wallpaperManager.getWallpaperInfoForUser(any())).thenReturn(wp1)
+ fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+ context,
+ Intent(Intent.ACTION_WALLPAPER_CHANGED),
+ )
+
+ // THEN the value is still null because wallpaper isn't supported
+ assertThat(latest).isNull()
+ }
+
+ @Test
+ fun wallpaperInfo_deviceDoesNotSupportAmbientWallpaper_alwaysFalse() =
+ testScope.runTest {
+ context.orCreateTestableResources.addOverride(
+ com.android.internal.R.bool.config_dozeSupportsAodWallpaper,
+ false
+ )
+
+ val latest by collectLastValue(underTest.wallpaperInfo)
+ assertThat(latest).isNull()
+
+ // Even WHEN there *is* current wallpaper
+ val wp1 = mock<WallpaperInfo>()
+ whenever(wallpaperManager.getWallpaperInfoForUser(any())).thenReturn(wp1)
+ fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+ context,
+ Intent(Intent.ACTION_WALLPAPER_CHANGED),
+ )
+
+ // THEN the value is still null because wallpaper isn't supported
+ assertThat(latest).isNull()
+ }
+
+ @Test
+ fun wallpaperSupportsAmbientMode_nullInfo_false() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.wallpaperSupportsAmbientMode)
+
+ whenever(wallpaperManager.getWallpaperInfoForUser(any())).thenReturn(null)
+
+ fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+ context,
+ Intent(Intent.ACTION_WALLPAPER_CHANGED),
+ )
+
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun wallpaperSupportsAmbientMode_infoDoesNotSupport_false() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.wallpaperSupportsAmbientMode)
+
+ whenever(wallpaperManager.getWallpaperInfoForUser(any())).thenReturn(UNSUPPORTED_WP)
+
+ fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+ context,
+ Intent(Intent.ACTION_WALLPAPER_CHANGED),
+ )
+
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun wallpaperSupportsAmbientMode_infoSupports_true() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.wallpaperSupportsAmbientMode)
+
+ whenever(wallpaperManager.getWallpaperInfoForUser(any())).thenReturn(SUPPORTED_WP)
+
+ fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+ context,
+ Intent(Intent.ACTION_WALLPAPER_CHANGED),
+ )
+
+ assertThat(latest).isTrue()
+ }
+
+ @Test
+ fun wallpaperSupportsAmbientMode_initialValueIsFetched_true() =
+ testScope.runTest {
+ whenever(wallpaperManager.getWallpaperInfoForUser(USER_WITH_SUPPORTED_WP.id))
+ .thenReturn(SUPPORTED_WP)
+ userRepository.setUserInfos(listOf(USER_WITH_SUPPORTED_WP))
+ userRepository.setSelectedUserInfo(USER_WITH_SUPPORTED_WP)
+
+ // WHEN the repo initially starts up (underTest is lazy), then it fetches the current
+ // value for the wallpaper
+ assertThat(underTest.wallpaperSupportsAmbientMode.value).isTrue()
+ }
+
+ @Test
+ fun wallpaperSupportsAmbientMode_initialValueIsFetched_false() =
+ testScope.runTest {
+ whenever(wallpaperManager.getWallpaperInfoForUser(USER_WITH_UNSUPPORTED_WP.id))
+ .thenReturn(UNSUPPORTED_WP)
+ userRepository.setUserInfos(listOf(USER_WITH_UNSUPPORTED_WP))
+ userRepository.setSelectedUserInfo(USER_WITH_UNSUPPORTED_WP)
+
+ // WHEN the repo initially starts up (underTest is lazy), then it fetches the current
+ // value for the wallpaper
+ assertThat(underTest.wallpaperSupportsAmbientMode.value).isFalse()
+ }
+
+ @Test
+ fun wallpaperSupportsAmbientMode_updatesOnUserChanged() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.wallpaperSupportsAmbientMode)
+
+ whenever(wallpaperManager.getWallpaperInfoForUser(USER_WITH_SUPPORTED_WP.id))
+ .thenReturn(SUPPORTED_WP)
+ whenever(wallpaperManager.getWallpaperInfoForUser(USER_WITH_UNSUPPORTED_WP.id))
+ .thenReturn(UNSUPPORTED_WP)
+ userRepository.setUserInfos(listOf(USER_WITH_SUPPORTED_WP, USER_WITH_UNSUPPORTED_WP))
+
+ // WHEN a user with supported wallpaper is selected
+ userRepository.setSelectedUserInfo(USER_WITH_SUPPORTED_WP)
+
+ // THEN it's true
+ assertThat(latest).isTrue()
+
+ // WHEN the user is switched to a user with unsupported wallpaper
+ userRepository.setSelectedUserInfo(USER_WITH_UNSUPPORTED_WP)
+
+ // THEN it's false
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun wallpaperSupportsAmbientMode_doesNotUpdateOnUserChanging() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.wallpaperSupportsAmbientMode)
+
+ whenever(wallpaperManager.getWallpaperInfoForUser(USER_WITH_SUPPORTED_WP.id))
+ .thenReturn(SUPPORTED_WP)
+ whenever(wallpaperManager.getWallpaperInfoForUser(USER_WITH_UNSUPPORTED_WP.id))
+ .thenReturn(UNSUPPORTED_WP)
+ userRepository.setUserInfos(listOf(USER_WITH_SUPPORTED_WP, USER_WITH_UNSUPPORTED_WP))
+
+ // WHEN a user with supported wallpaper is selected
+ userRepository.setSelectedUserInfo(USER_WITH_SUPPORTED_WP)
+
+ // THEN it's true
+ assertThat(latest).isTrue()
+
+ // WHEN the user has started switching to a user with unsupported wallpaper but hasn't
+ // finished yet
+ userRepository.selectedUser.value =
+ SelectedUserModel(USER_WITH_UNSUPPORTED_WP, SelectionStatus.SELECTION_IN_PROGRESS)
+
+ // THEN it still matches the old user
+ assertThat(latest).isTrue()
+ }
+
+ @Test
+ fun wallpaperSupportsAmbientMode_updatesOnIntent() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.wallpaperSupportsAmbientMode)
+
+ whenever(wallpaperManager.getWallpaperInfoForUser(any())).thenReturn(UNSUPPORTED_WP)
+
+ assertThat(latest).isFalse()
+
+ // WHEN the info now supports ambient mode and a broadcast is sent
+ whenever(wallpaperManager.getWallpaperInfoForUser(any())).thenReturn(SUPPORTED_WP)
+ fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+ context,
+ Intent(Intent.ACTION_WALLPAPER_CHANGED),
+ )
+
+ // THEN the flow updates
+ assertThat(latest).isTrue()
+ }
+
+ @Test
+ fun wallpaperSupportsAmbientMode_wallpaperNotSupported_alwaysFalse() =
+ testScope.runTest {
+ whenever(wallpaperManager.isWallpaperSupported).thenReturn(false)
+
+ val latest by collectLastValue(underTest.wallpaperSupportsAmbientMode)
+ assertThat(latest).isFalse()
+
+ // Even WHEN the current wallpaper *does* support ambient mode
+ whenever(wallpaperManager.getWallpaperInfoForUser(any())).thenReturn(SUPPORTED_WP)
+
+ fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+ context,
+ Intent(Intent.ACTION_WALLPAPER_CHANGED),
+ )
+
+ // THEN the value is still false because wallpaper isn't supported
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun wallpaperSupportsAmbientMode_deviceDoesNotSupportAmbientWallpaper_alwaysFalse() =
+ testScope.runTest {
+ context.orCreateTestableResources.addOverride(
+ com.android.internal.R.bool.config_dozeSupportsAodWallpaper,
+ false
+ )
+
+ val latest by collectLastValue(underTest.wallpaperSupportsAmbientMode)
+ assertThat(latest).isFalse()
+
+ // Even WHEN the current wallpaper *does* support ambient mode
+ whenever(wallpaperManager.getWallpaperInfoForUser(any())).thenReturn(SUPPORTED_WP)
+
+ fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+ context,
+ Intent(Intent.ACTION_WALLPAPER_CHANGED),
+ )
+
+ // THEN the value is still false because the device doesn't support it
+ assertThat(latest).isFalse()
+ }
+
+ private companion object {
+ val USER_WITH_UNSUPPORTED_WP = UserInfo(/* id= */ 3, /* name= */ "user3", /* flags= */ 0)
+ val UNSUPPORTED_WP =
+ mock<WallpaperInfo>().apply { whenever(this.supportsAmbientMode()).thenReturn(false) }
+
+ val USER_WITH_SUPPORTED_WP = UserInfo(/* id= */ 4, /* name= */ "user4", /* flags= */ 0)
+ val SUPPORTED_WP =
+ mock<WallpaperInfo>().apply { whenever(this.supportsAmbientMode()).thenReturn(true) }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
index 9de7a87..ef0adbb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
@@ -34,7 +34,6 @@
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.tracing.ProtoTracer;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.desktopmode.DesktopMode;
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
@@ -76,7 +75,6 @@
@Mock SplitScreen mSplitScreen;
@Mock OneHanded mOneHanded;
@Mock WakefulnessLifecycle mWakefulnessLifecycle;
- @Mock ProtoTracer mProtoTracer;
@Mock UserTracker mUserTracker;
@Mock ShellExecutor mSysUiMainExecutor;
@Mock NoteTaskInitializer mNoteTaskInitializer;
@@ -99,7 +97,6 @@
mKeyguardUpdateMonitor,
mScreenLifecycle,
mSysUiState,
- mProtoTracer,
mWakefulnessLifecycle,
mUserTracker,
displayTracker,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt
index a718f70..c2e1ac7 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt
@@ -16,40 +16,158 @@
package com.android.systemui.authentication.data.repository
+import com.android.internal.widget.LockPatternUtils
+import com.android.internal.widget.LockPatternView
+import com.android.internal.widget.LockscreenCredential
import com.android.keyguard.KeyguardSecurityModel.SecurityMode
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.authentication.shared.model.AuthenticationResultModel
+import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
class FakeAuthenticationRepository(
- private val delegate: AuthenticationRepository,
- private val onSecurityModeChanged: (SecurityMode) -> Unit,
-) : AuthenticationRepository by delegate {
+ private val currentTime: () -> Long,
+) : AuthenticationRepository {
+
+ private val _isAutoConfirmEnabled = MutableStateFlow(false)
+ override val isAutoConfirmEnabled: StateFlow<Boolean> = _isAutoConfirmEnabled.asStateFlow()
private val _isUnlocked = MutableStateFlow(false)
override val isUnlocked: StateFlow<Boolean> = _isUnlocked.asStateFlow()
- private var authenticationMethod: AuthenticationMethodModel = DEFAULT_AUTHENTICATION_METHOD
+ override val hintedPinLength: Int = HINTING_PIN_LENGTH
+
+ private val _isPatternVisible = MutableStateFlow(true)
+ override val isPatternVisible: StateFlow<Boolean> = _isPatternVisible.asStateFlow()
+
+ private val _throttling = MutableStateFlow(AuthenticationThrottlingModel())
+ override val throttling: StateFlow<AuthenticationThrottlingModel> = _throttling.asStateFlow()
+
+ private val _authenticationMethod =
+ MutableStateFlow<AuthenticationMethodModel>(DEFAULT_AUTHENTICATION_METHOD)
+ val authenticationMethod: StateFlow<AuthenticationMethodModel> =
+ _authenticationMethod.asStateFlow()
+
+ private var isLockscreenEnabled = true
+ private var failedAttemptCount = 0
+ private var throttlingEndTimestamp = 0L
+ private var credentialOverride: List<Any>? = null
+ private var securityMode: SecurityMode = DEFAULT_AUTHENTICATION_METHOD.toSecurityMode()
override suspend fun getAuthenticationMethod(): AuthenticationMethodModel {
- return authenticationMethod
+ return authenticationMethod.value
}
fun setAuthenticationMethod(authenticationMethod: AuthenticationMethodModel) {
- this.authenticationMethod = authenticationMethod
- onSecurityModeChanged(authenticationMethod.toSecurityMode())
+ _authenticationMethod.value = authenticationMethod
+ securityMode = authenticationMethod.toSecurityMode()
+ }
+
+ fun overrideCredential(pin: List<Int>) {
+ credentialOverride = pin
+ }
+
+ override suspend fun isLockscreenEnabled(): Boolean {
+ return isLockscreenEnabled
+ }
+
+ override suspend fun reportAuthenticationAttempt(isSuccessful: Boolean) {
+ failedAttemptCount = if (isSuccessful) 0 else failedAttemptCount + 1
+ _isUnlocked.value = isSuccessful
+ }
+
+ override suspend fun getPinLength(): Int {
+ return (credentialOverride ?: DEFAULT_PIN).size
+ }
+
+ override suspend fun getFailedAuthenticationAttemptCount(): Int {
+ return failedAttemptCount
+ }
+
+ override suspend fun getThrottlingEndTimestamp(): Long {
+ return throttlingEndTimestamp
+ }
+
+ override fun setThrottling(throttlingModel: AuthenticationThrottlingModel) {
+ _throttling.value = throttlingModel
}
fun setUnlocked(isUnlocked: Boolean) {
_isUnlocked.value = isUnlocked
}
- companion object {
- val DEFAULT_AUTHENTICATION_METHOD =
- AuthenticationMethodModel.Pin(listOf(1, 2, 3, 4), autoConfirm = false)
+ fun setAutoConfirmEnabled(isEnabled: Boolean) {
+ _isAutoConfirmEnabled.value = isEnabled
+ }
- fun AuthenticationMethodModel.toSecurityMode(): SecurityMode {
+ fun setLockscreenEnabled(isLockscreenEnabled: Boolean) {
+ this.isLockscreenEnabled = isLockscreenEnabled
+ }
+
+ override suspend fun setThrottleDuration(durationMs: Int) {
+ throttlingEndTimestamp = if (durationMs > 0) currentTime() + durationMs else 0
+ }
+
+ override suspend fun checkCredential(
+ credential: LockscreenCredential
+ ): AuthenticationResultModel {
+ val expectedCredential = credentialOverride ?: getExpectedCredential(securityMode)
+ val isSuccessful =
+ when {
+ credential.type != getCurrentCredentialType(securityMode) -> false
+ credential.type == LockPatternUtils.CREDENTIAL_TYPE_PIN ->
+ credential.isPin && credential.matches(expectedCredential)
+ credential.type == LockPatternUtils.CREDENTIAL_TYPE_PASSWORD ->
+ credential.isPassword && credential.matches(expectedCredential)
+ credential.type == LockPatternUtils.CREDENTIAL_TYPE_PATTERN ->
+ credential.isPattern && credential.matches(expectedCredential)
+ else -> error("Unexpected credential type ${credential.type}!")
+ }
+
+ return if (
+ isSuccessful || failedAttemptCount < MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING - 1
+ ) {
+ AuthenticationResultModel(
+ isSuccessful = isSuccessful,
+ throttleDurationMs = 0,
+ )
+ } else {
+ AuthenticationResultModel(
+ isSuccessful = false,
+ throttleDurationMs = THROTTLE_DURATION_MS,
+ )
+ }
+ }
+
+ private fun getExpectedCredential(securityMode: SecurityMode): List<Any> {
+ return when (val credentialType = getCurrentCredentialType(securityMode)) {
+ LockPatternUtils.CREDENTIAL_TYPE_PIN -> credentialOverride ?: DEFAULT_PIN
+ LockPatternUtils.CREDENTIAL_TYPE_PASSWORD -> "password".toList()
+ LockPatternUtils.CREDENTIAL_TYPE_PATTERN -> PATTERN.toCells()
+ else -> error("Unsupported credential type $credentialType!")
+ }
+ }
+
+ companion object {
+ val DEFAULT_AUTHENTICATION_METHOD = AuthenticationMethodModel.Pin
+ val PATTERN =
+ listOf(
+ AuthenticationMethodModel.Pattern.PatternCoordinate(2, 0),
+ AuthenticationMethodModel.Pattern.PatternCoordinate(2, 1),
+ AuthenticationMethodModel.Pattern.PatternCoordinate(2, 2),
+ AuthenticationMethodModel.Pattern.PatternCoordinate(1, 1),
+ AuthenticationMethodModel.Pattern.PatternCoordinate(0, 0),
+ AuthenticationMethodModel.Pattern.PatternCoordinate(0, 1),
+ AuthenticationMethodModel.Pattern.PatternCoordinate(0, 2),
+ )
+ const val MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING = 5
+ const val THROTTLE_DURATION_MS = 30000
+ const val HINTING_PIN_LENGTH = 6
+ val DEFAULT_PIN = buildList { repeat(HINTING_PIN_LENGTH) { add(it + 1) } }
+
+ private fun AuthenticationMethodModel.toSecurityMode(): SecurityMode {
return when (this) {
is AuthenticationMethodModel.Pin -> SecurityMode.PIN
is AuthenticationMethodModel.Password -> SecurityMode.Password
@@ -58,5 +176,41 @@
is AuthenticationMethodModel.None -> SecurityMode.None
}
}
+
+ @LockPatternUtils.CredentialType
+ private fun getCurrentCredentialType(
+ securityMode: SecurityMode,
+ ): Int {
+ return when (securityMode) {
+ SecurityMode.PIN,
+ SecurityMode.SimPin,
+ SecurityMode.SimPuk -> LockPatternUtils.CREDENTIAL_TYPE_PIN
+ SecurityMode.Password -> LockPatternUtils.CREDENTIAL_TYPE_PASSWORD
+ SecurityMode.Pattern -> LockPatternUtils.CREDENTIAL_TYPE_PATTERN
+ SecurityMode.None -> LockPatternUtils.CREDENTIAL_TYPE_NONE
+ else -> error("Unsupported SecurityMode $securityMode!")
+ }
+ }
+
+ private fun LockscreenCredential.matches(expectedCredential: List<Any>): Boolean {
+ @Suppress("UNCHECKED_CAST")
+ return when {
+ isPin ->
+ credential.map { byte -> byte.toInt().toChar() - '0' } == expectedCredential
+ isPassword -> credential.map { byte -> byte.toInt().toChar() } == expectedCredential
+ isPattern ->
+ credential.contentEquals(
+ LockPatternUtils.patternToByteArray(
+ expectedCredential as List<LockPatternView.Cell>
+ )
+ )
+ else -> error("Unsupported credential type $type!")
+ }
+ }
+
+ private fun List<AuthenticationMethodModel.Pattern.PatternCoordinate>.toCells():
+ List<LockPatternView.Cell> {
+ return map { coordinate -> LockPatternView.Cell.of(coordinate.y, coordinate.x) }
+ }
}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFingerprintPropertyRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFingerprintPropertyRepository.kt
index 2362a52..0c5e438 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFingerprintPropertyRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFingerprintPropertyRepository.kt
@@ -20,16 +20,12 @@
import com.android.systemui.biometrics.shared.model.FingerprintSensorType
import com.android.systemui.biometrics.shared.model.SensorStrength
import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
class FakeFingerprintPropertyRepository : FingerprintPropertyRepository {
- private val _isInitialized: MutableStateFlow<Boolean> = MutableStateFlow(false)
- override val isInitialized = _isInitialized.asStateFlow()
-
private val _sensorId: MutableStateFlow<Int> = MutableStateFlow(-1)
- override val sensorId: StateFlow<Int> = _sensorId.asStateFlow()
+ override val sensorId = _sensorId.asStateFlow()
private val _strength: MutableStateFlow<SensorStrength> =
MutableStateFlow(SensorStrength.CONVENIENCE)
@@ -37,12 +33,11 @@
private val _sensorType: MutableStateFlow<FingerprintSensorType> =
MutableStateFlow(FingerprintSensorType.UNKNOWN)
- override val sensorType: StateFlow<FingerprintSensorType> = _sensorType.asStateFlow()
+ override val sensorType = _sensorType.asStateFlow()
private val _sensorLocations: MutableStateFlow<Map<String, SensorLocationInternal>> =
MutableStateFlow(mapOf("" to SensorLocationInternal.DEFAULT))
- override val sensorLocations: StateFlow<Map<String, SensorLocationInternal>> =
- _sensorLocations.asStateFlow()
+ override val sensorLocations = _sensorLocations.asStateFlow()
fun setProperties(
sensorId: Int,
@@ -54,6 +49,5 @@
_strength.value = strength
_sensorType.value = sensorType
_sensorLocations.value = sensorLocations
- _isInitialized.value = true
}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt
index 2715aaa..548169e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt
@@ -17,8 +17,8 @@
package com.android.systemui.keyguard.data.repository
import com.android.keyguard.FaceAuthUiEvent
-import com.android.systemui.keyguard.shared.model.AuthenticationStatus
-import com.android.systemui.keyguard.shared.model.DetectionStatus
+import com.android.systemui.keyguard.shared.model.FaceAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.FaceDetectionStatus
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@@ -29,16 +29,16 @@
override val isAuthenticated = MutableStateFlow(false)
override val canRunFaceAuth = MutableStateFlow(false)
- private val _authenticationStatus = MutableStateFlow<AuthenticationStatus?>(null)
- override val authenticationStatus: Flow<AuthenticationStatus> =
+ private val _authenticationStatus = MutableStateFlow<FaceAuthenticationStatus?>(null)
+ override val authenticationStatus: Flow<FaceAuthenticationStatus> =
_authenticationStatus.filterNotNull()
- fun setAuthenticationStatus(status: AuthenticationStatus) {
+ fun setAuthenticationStatus(status: FaceAuthenticationStatus) {
_authenticationStatus.value = status
}
- private val _detectionStatus = MutableStateFlow<DetectionStatus?>(null)
- override val detectionStatus: Flow<DetectionStatus>
+ private val _detectionStatus = MutableStateFlow<FaceDetectionStatus?>(null)
+ override val detectionStatus: Flow<FaceDetectionStatus>
get() = _detectionStatus.filterNotNull()
- fun setDetectionStatus(status: DetectionStatus) {
+ fun setDetectionStatus(status: FaceDetectionStatus) {
_detectionStatus.value = status
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFingerprintAuthRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFingerprintAuthRepository.kt
index 4bfd3d6..38791ca 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFingerprintAuthRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFingerprintAuthRepository.kt
@@ -17,32 +17,38 @@
package com.android.systemui.keyguard.data.repository
+import com.android.systemui.keyguard.shared.model.FingerprintAuthenticationStatus
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.filterNotNull
class FakeDeviceEntryFingerprintAuthRepository : DeviceEntryFingerprintAuthRepository {
private val _isLockedOut = MutableStateFlow(false)
override val isLockedOut: StateFlow<Boolean> = _isLockedOut.asStateFlow()
-
- private val _isRunning = MutableStateFlow(false)
- override val isRunning: Flow<Boolean>
- get() = _isRunning
-
- private var fpSensorType = MutableStateFlow<BiometricType?>(null)
- override val availableFpSensorType: Flow<BiometricType?>
- get() = fpSensorType
-
fun setLockedOut(lockedOut: Boolean) {
_isLockedOut.value = lockedOut
}
+ private val _isRunning = MutableStateFlow(false)
+ override val isRunning: Flow<Boolean>
+ get() = _isRunning
fun setIsRunning(value: Boolean) {
_isRunning.value = value
}
+ private var fpSensorType = MutableStateFlow<BiometricType?>(null)
+ override val availableFpSensorType: Flow<BiometricType?>
+ get() = fpSensorType
fun setAvailableFpSensorType(value: BiometricType?) {
fpSensorType.value = value
}
+
+ private var _authenticationStatus = MutableStateFlow<FingerprintAuthenticationStatus?>(null)
+ override val authenticationStatus: Flow<FingerprintAuthenticationStatus>
+ get() = _authenticationStatus.filterNotNull()
+ fun setAuthenticationStatus(status: FingerprintAuthenticationStatus) {
+ _authenticationStatus.value = status
+ }
}
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 eb2f71a9..8428566 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
@@ -22,11 +22,14 @@
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
import com.android.systemui.keyguard.shared.model.DozeTransitionModel
+import com.android.systemui.keyguard.shared.model.ScreenModel
+import com.android.systemui.keyguard.shared.model.ScreenState
import com.android.systemui.keyguard.shared.model.StatusBarState
import com.android.systemui.keyguard.shared.model.WakeSleepReason
import com.android.systemui.keyguard.shared.model.WakefulnessModel
import com.android.systemui.keyguard.shared.model.WakefulnessState
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
@@ -48,7 +51,7 @@
override val isKeyguardShowing: Flow<Boolean> = _isKeyguardShowing
private val _isKeyguardUnlocked = MutableStateFlow(false)
- override val isKeyguardUnlocked: Flow<Boolean> = _isKeyguardUnlocked
+ override val isKeyguardUnlocked: StateFlow<Boolean> = _isKeyguardUnlocked.asStateFlow()
private val _isKeyguardOccluded = MutableStateFlow(false)
override val isKeyguardOccluded: Flow<Boolean> = _isKeyguardOccluded
@@ -56,6 +59,9 @@
private val _isDozing = MutableStateFlow(false)
override val isDozing: StateFlow<Boolean> = _isDozing
+ private val _dozeTimeTick = MutableSharedFlow<Unit>()
+ override val dozeTimeTick = _dozeTimeTick
+
private val _lastDozeTapToWakePosition = MutableStateFlow<Point?>(null)
override val lastDozeTapToWakePosition = _lastDozeTapToWakePosition.asStateFlow()
@@ -86,6 +92,9 @@
)
override val wakefulness = _wakefulnessModel
+ private val _screenModel = MutableStateFlow(ScreenModel(ScreenState.SCREEN_OFF))
+ override val screenModel = _screenModel
+
private val _isUdfpsSupported = MutableStateFlow(false)
private val _isKeyguardGoingAway = MutableStateFlow(false)
@@ -114,6 +123,11 @@
return _isKeyguardShowing.value
}
+ private var _isBypassEnabled = false
+ override fun isBypassEnabled(): Boolean {
+ return _isBypassEnabled
+ }
+
override fun setAnimateDozingTransitions(animate: Boolean) {
_animateBottomAreaDozingTransitions.tryEmit(animate)
}
@@ -142,6 +156,10 @@
_isDozing.value = isDozing
}
+ override fun dozeTimeTick() {
+ _dozeTimeTick.tryEmit(Unit)
+ }
+
override fun setLastDozeTapToWakePosition(position: Point) {
_lastDozeTapToWakePosition.value = position
}
@@ -194,6 +212,14 @@
_statusBarState.value = state
}
+ fun setKeyguardUnlocked(isUnlocked: Boolean) {
+ _isKeyguardUnlocked.value = isUnlocked
+ }
+
+ fun setBypassEnabled(isEnabled: Boolean) {
+ _isBypassEnabled = isEnabled
+ }
+
override fun isUdfpsSupported(): Boolean {
return _isUdfpsSupported.value
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
index 0b6e2a2..9317981 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
@@ -16,29 +16,41 @@
package com.android.systemui.scene
-import com.android.keyguard.KeyguardSecurityModel.SecurityMode
+import android.content.pm.UserInfo
import com.android.systemui.SysuiTestCase
import com.android.systemui.authentication.data.repository.AuthenticationRepository
-import com.android.systemui.authentication.data.repository.AuthenticationRepositoryImpl
import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
-import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository.Companion.toSecurityMode
import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
import com.android.systemui.bouncer.data.repository.BouncerRepository
+import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
+import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.data.repository.FakeCommandQueue
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.LockscreenSceneInteractor
+import com.android.systemui.keyguard.shared.model.WakeSleepReason
+import com.android.systemui.keyguard.shared.model.WakefulnessModel
+import com.android.systemui.keyguard.shared.model.WakefulnessState
import com.android.systemui.scene.data.repository.SceneContainerRepository
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.model.SceneContainerConfig
+import com.android.systemui.scene.shared.model.SceneContainerNames
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.user.data.repository.UserRepository
import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestDispatcher
import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.currentTime
/**
* Utilities for creating scene container framework related repositories, interactors, and
@@ -48,24 +60,37 @@
class SceneTestUtils(
test: SysuiTestCase,
) {
- val testDispatcher: TestDispatcher by lazy { StandardTestDispatcher() }
- val testScope: TestScope by lazy { TestScope(testDispatcher) }
- private var securityMode: SecurityMode =
- FakeAuthenticationRepository.DEFAULT_AUTHENTICATION_METHOD.toSecurityMode()
+ val testDispatcher = StandardTestDispatcher()
+ val testScope = TestScope(testDispatcher)
+ val featureFlags =
+ FakeFeatureFlags().apply {
+ set(Flags.SCENE_CONTAINER, true)
+ set(Flags.FACE_AUTH_REFACTOR, false)
+ }
+ private val userRepository: UserRepository by lazy {
+ FakeUserRepository().apply {
+ val users = listOf(UserInfo(/* id= */ 0, "name", /* flags= */ 0))
+ setUserInfos(users)
+ runBlocking { setSelectedUserInfo(users.first()) }
+ }
+ }
+
val authenticationRepository: FakeAuthenticationRepository by lazy {
FakeAuthenticationRepository(
- delegate =
- AuthenticationRepositoryImpl(
- applicationScope = applicationScope(),
- getSecurityMode = { securityMode },
- backgroundDispatcher = testDispatcher,
- userRepository = FakeUserRepository(),
- lockPatternUtils = mock(),
- keyguardRepository = FakeKeyguardRepository(),
- ),
- onSecurityModeChanged = { securityMode = it },
+ currentTime = { testScope.currentTime },
)
}
+ val keyguardRepository: FakeKeyguardRepository by lazy {
+ FakeKeyguardRepository().apply {
+ setWakefulnessModel(
+ WakefulnessModel(
+ WakefulnessState.AWAKE,
+ WakeSleepReason.OTHER,
+ WakeSleepReason.OTHER,
+ )
+ )
+ }
+ }
private val context = test.context
fun fakeSceneContainerRepository(
@@ -105,7 +130,7 @@
)
}
- fun authenticationRepository(): AuthenticationRepository {
+ fun authenticationRepository(): FakeAuthenticationRepository {
return authenticationRepository
}
@@ -115,6 +140,24 @@
return AuthenticationInteractor(
applicationScope = applicationScope(),
repository = repository,
+ backgroundDispatcher = testDispatcher,
+ userRepository = userRepository,
+ keyguardRepository = keyguardRepository,
+ clock = mock { whenever(elapsedRealtime()).thenAnswer { testScope.currentTime } }
+ )
+ }
+
+ fun keyguardRepository(): FakeKeyguardRepository {
+ return keyguardRepository
+ }
+
+ fun keyguardInteractor(repository: KeyguardRepository): KeyguardInteractor {
+ return KeyguardInteractor(
+ repository = repository,
+ commandQueue = FakeCommandQueue(),
+ featureFlags = featureFlags,
+ bouncerRepository = FakeKeyguardBouncerRepository(),
+ configurationRepository = FakeConfigurationRepository()
)
}
@@ -128,6 +171,7 @@
repository = BouncerRepository(),
authenticationInteractor = authenticationInteractor,
sceneInteractor = sceneInteractor,
+ featureFlags = featureFlags,
containerName = CONTAINER_1,
)
}
@@ -144,13 +188,13 @@
return bouncerInteractor
}
},
+ featureFlags = featureFlags,
containerName = CONTAINER_1,
)
}
fun lockScreenSceneInteractor(
authenticationInteractor: AuthenticationInteractor,
- sceneInteractor: SceneInteractor,
bouncerInteractor: BouncerInteractor,
): LockscreenSceneInteractor {
return LockscreenSceneInteractor(
@@ -162,7 +206,6 @@
return bouncerInteractor
}
},
- sceneInteractor = sceneInteractor,
containerName = CONTAINER_1,
)
}
@@ -172,7 +215,7 @@
}
companion object {
- const val CONTAINER_1 = "container1"
+ const val CONTAINER_1 = SceneContainerNames.SYSTEM_UI_DEFAULT
const val CONTAINER_2 = "container2"
}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
index c664c99..56837e8 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
@@ -21,7 +21,6 @@
import com.android.systemui.statusbar.phone.StatusBarIconController.IconManager;
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.CallIndicatorIconState;
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
-import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
import java.util.List;
@@ -62,10 +61,6 @@
}
@Override
- public void setWifiIcon(String slot, WifiIconState state) {
- }
-
- @Override
public void setNewWifiIcon() {
}
diff --git a/services/Android.bp b/services/Android.bp
index 7b64b47..53dc068 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -164,6 +164,7 @@
"services.coverage",
"services.credentials",
"services.devicepolicy",
+ "services.flags",
"services.midi",
"services.musicsearch",
"services.net",
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
index ba3d434..1482d07 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
@@ -110,7 +110,6 @@
private boolean mAlwaysOnMagnificationEnabled = false;
private final DisplayManagerInternal mDisplayManagerInternal;
- private final MagnificationThumbnailFeatureFlag mMagnificationThumbnailFeatureFlag;
@NonNull private final Supplier<MagnificationThumbnail> mThumbnailSupplier;
/**
@@ -643,13 +642,6 @@
}
}
- void onThumbnailFeatureFlagChanged() {
- synchronized (mLock) {
- destroyThumbnail();
- createThumbnailIfSupported();
- }
- }
-
/**
* Updates the current magnification spec.
*
@@ -810,43 +802,19 @@
addInfoChangedCallback(magnificationInfoChangedCallback);
mScaleProvider = scaleProvider;
mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class);
- mMagnificationThumbnailFeatureFlag = new MagnificationThumbnailFeatureFlag();
- mMagnificationThumbnailFeatureFlag.addOnChangedListener(
- backgroundExecutor, this::onMagnificationThumbnailFeatureFlagChanged);
if (thumbnailSupplier != null) {
mThumbnailSupplier = thumbnailSupplier;
} else {
mThumbnailSupplier = () -> {
- if (mMagnificationThumbnailFeatureFlag.isFeatureFlagEnabled()) {
- return new MagnificationThumbnail(
- ctx.getContext(),
- ctx.getContext().getSystemService(WindowManager.class),
- new Handler(ctx.getContext().getMainLooper())
- );
- }
- return null;
+ return new MagnificationThumbnail(
+ ctx.getContext(),
+ ctx.getContext().getSystemService(WindowManager.class),
+ new Handler(ctx.getContext().getMainLooper())
+ );
};
}
}
- private void onMagnificationThumbnailFeatureFlagChanged() {
- synchronized (mLock) {
- for (int i = 0; i < mDisplays.size(); i++) {
- onMagnificationThumbnailFeatureFlagChanged(mDisplays.keyAt(i));
- }
- }
- }
-
- private void onMagnificationThumbnailFeatureFlagChanged(int displayId) {
- synchronized (mLock) {
- final DisplayMagnification display = mDisplays.get(displayId);
- if (display == null) {
- return;
- }
- display.onThumbnailFeatureFlagChanged();
- }
- }
-
/**
* Start tracking the magnification region for services that control magnification and the
* magnification gesture handler.
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationThumbnailFeatureFlag.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationThumbnailFeatureFlag.java
deleted file mode 100644
index 519f31b..0000000
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationThumbnailFeatureFlag.java
+++ /dev/null
@@ -1,46 +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.accessibility.magnification;
-
-import android.provider.DeviceConfig;
-
-/**
- * Encapsulates the feature flags for magnification thumbnail. {@see DeviceConfig}
- *
- * @hide
- */
-public class MagnificationThumbnailFeatureFlag extends MagnificationFeatureFlagBase {
-
- private static final String NAMESPACE = DeviceConfig.NAMESPACE_ACCESSIBILITY;
- private static final String FEATURE_NAME_ENABLE_MAGNIFIER_THUMBNAIL =
- "enable_magnifier_thumbnail";
-
- @Override
- String getNamespace() {
- return NAMESPACE;
- }
-
- @Override
- String getFeatureName() {
- return FEATURE_NAME_ENABLE_MAGNIFIER_THUMBNAIL;
- }
-
- @Override
- boolean getDefaultValue() {
- return false;
- }
-}
diff --git a/services/autofill/java/com/android/server/autofill/Helper.java b/services/autofill/java/com/android/server/autofill/Helper.java
index 82af382..ad8f5e1 100644
--- a/services/autofill/java/com/android/server/autofill/Helper.java
+++ b/services/autofill/java/com/android/server/autofill/Helper.java
@@ -20,6 +20,8 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.app.ActivityManager;
import android.app.assist.AssistStructure;
import android.app.assist.AssistStructure.ViewNode;
import android.app.assist.AssistStructure.WindowNode;
@@ -40,6 +42,7 @@
import android.view.WindowManager;
import android.view.autofill.AutofillId;
import android.view.autofill.AutofillValue;
+import android.widget.RemoteViews;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.util.ArrayUtils;
@@ -50,6 +53,8 @@
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.concurrent.atomic.AtomicBoolean;
+
public final class Helper {
@@ -83,6 +88,44 @@
throw new UnsupportedOperationException("contains static members only");
}
+ private static boolean checkRemoteViewUriPermissions(
+ @UserIdInt int userId, @NonNull RemoteViews rView) {
+ final AtomicBoolean permissionsOk = new AtomicBoolean(true);
+
+ rView.visitUris(uri -> {
+ int uriOwnerId = android.content.ContentProvider.getUserIdFromUri(uri);
+ boolean allowed = uriOwnerId == userId;
+ permissionsOk.set(allowed & permissionsOk.get());
+ });
+
+ return permissionsOk.get();
+ }
+
+ /**
+ * Checks the URI permissions of the remote view,
+ * to see if the current userId is able to access it.
+ *
+ * Returns the RemoteView that is passed if user is able, null otherwise.
+ *
+ * TODO: instead of returning a null remoteview when
+ * the current userId cannot access an URI,
+ * return a new RemoteView with the URI removed.
+ */
+ public static @Nullable RemoteViews sanitizeRemoteView(RemoteViews rView) {
+ if (rView == null) return null;
+
+ int userId = ActivityManager.getCurrentUser();
+
+ boolean ok = checkRemoteViewUriPermissions(userId, rView);
+ if (!ok) {
+ Slog.w(TAG,
+ "sanitizeRemoteView() user: " + userId
+ + " tried accessing resource that does not belong to them");
+ }
+ return (ok ? rView : null);
+ }
+
+
@Nullable
static AutofillId[] toArray(@Nullable ArraySet<AutofillId> set) {
if (set == null) return null;
diff --git a/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java b/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java
index dbeb624..fa414e3 100644
--- a/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java
+++ b/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java
@@ -53,6 +53,7 @@
import com.android.internal.R;
import com.android.server.autofill.AutofillManagerService;
+import com.android.server.autofill.Helper;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -208,7 +209,8 @@
}
private void setHeader(View decor, FillResponse response) {
- final RemoteViews presentation = response.getDialogHeader();
+ final RemoteViews presentation =
+ Helper.sanitizeRemoteView(response.getDialogHeader());
if (presentation == null) {
return;
}
@@ -243,9 +245,10 @@
}
private void initialAuthenticationLayout(View decor, FillResponse response) {
- RemoteViews presentation = response.getDialogPresentation();
+ RemoteViews presentation = Helper.sanitizeRemoteView(
+ response.getDialogPresentation());
if (presentation == null) {
- presentation = response.getPresentation();
+ presentation = Helper.sanitizeRemoteView(response.getPresentation());
}
if (presentation == null) {
throw new RuntimeException("No presentation for fill dialog authentication");
@@ -289,7 +292,8 @@
final Dataset dataset = response.getDatasets().get(i);
final int index = dataset.getFieldIds().indexOf(focusedViewId);
if (index >= 0) {
- RemoteViews presentation = dataset.getFieldDialogPresentation(index);
+ RemoteViews presentation = Helper.sanitizeRemoteView(
+ dataset.getFieldDialogPresentation(index));
if (presentation == null) {
if (sDebug) {
Slog.w(TAG, "not displaying UI on field " + focusedViewId + " because "
diff --git a/services/autofill/java/com/android/server/autofill/ui/FillUi.java b/services/autofill/java/com/android/server/autofill/ui/FillUi.java
index 129ce72..cdfe7bb 100644
--- a/services/autofill/java/com/android/server/autofill/ui/FillUi.java
+++ b/services/autofill/java/com/android/server/autofill/ui/FillUi.java
@@ -148,8 +148,9 @@
final LayoutInflater inflater = LayoutInflater.from(mContext);
- final RemoteViews headerPresentation = response.getHeader();
- final RemoteViews footerPresentation = response.getFooter();
+ final RemoteViews headerPresentation = Helper.sanitizeRemoteView(response.getHeader());
+ final RemoteViews footerPresentation = Helper.sanitizeRemoteView(response.getFooter());
+
final ViewGroup decor;
if (mFullScreen) {
decor = (ViewGroup) inflater.inflate(R.layout.autofill_dataset_picker_fullscreen, null);
@@ -227,6 +228,9 @@
ViewGroup container = decor.findViewById(R.id.autofill_dataset_picker);
final View content;
try {
+ if (Helper.sanitizeRemoteView(response.getPresentation()) == null) {
+ throw new RuntimeException("Permission error accessing RemoteView");
+ }
content = response.getPresentation().applyWithTheme(
mContext, decor, interceptionHandler, mThemeId);
container.addView(content);
@@ -306,7 +310,8 @@
final Dataset dataset = response.getDatasets().get(i);
final int index = dataset.getFieldIds().indexOf(focusedViewId);
if (index >= 0) {
- final RemoteViews presentation = dataset.getFieldPresentation(index);
+ final RemoteViews presentation = Helper.sanitizeRemoteView(
+ dataset.getFieldPresentation(index));
if (presentation == null) {
Slog.w(TAG, "not displaying UI on field " + focusedViewId + " because "
+ "service didn't provide a presentation for it on " + dataset);
diff --git a/services/autofill/java/com/android/server/autofill/ui/SaveUi.java b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
index f035d07..70382f1 100644
--- a/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
+++ b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
@@ -384,8 +384,7 @@
return false;
}
writeLog(MetricsEvent.AUTOFILL_SAVE_CUSTOM_DESCRIPTION);
-
- final RemoteViews template = customDescription.getPresentation();
+ final RemoteViews template = Helper.sanitizeRemoteView(customDescription.getPresentation());
if (template == null) {
Slog.w(TAG, "No remote view on custom description");
return false;
diff --git a/services/cloudsearch/OWNERS b/services/cloudsearch/OWNERS
index aa4da3b..3a5841e 100644
--- a/services/cloudsearch/OWNERS
+++ b/services/cloudsearch/OWNERS
@@ -1,4 +1,3 @@
# Bug component: 758286
-huiwu@google.com
srazdan@google.com
diff --git a/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java b/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java
index 77275e0..3e7d759 100644
--- a/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java
+++ b/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java
@@ -19,7 +19,6 @@
import static android.app.PendingIntent.FLAG_CANCEL_CURRENT;
import static android.app.PendingIntent.FLAG_IMMUTABLE;
import static android.app.PendingIntent.FLAG_ONE_SHOT;
-import static android.companion.CompanionDeviceManager.COMPANION_DEVICE_DISCOVERY_PACKAGE_NAME;
import static android.companion.CompanionDeviceManager.REASON_INTERNAL_ERROR;
import static android.companion.CompanionDeviceManager.RESULT_INTERNAL_ERROR;
import static android.content.ComponentName.createRelative;
@@ -60,6 +59,7 @@
import android.util.PackageUtils;
import android.util.Slog;
+import com.android.internal.R;
import com.android.internal.util.ArrayUtils;
import java.util.Arrays;
@@ -114,9 +114,6 @@
class AssociationRequestsProcessor {
private static final String TAG = "CDM_AssociationRequestsProcessor";
- private static final ComponentName ASSOCIATION_REQUEST_APPROVAL_ACTIVITY =
- createRelative(COMPANION_DEVICE_DISCOVERY_PACKAGE_NAME, ".CompanionDeviceActivity");
-
// AssociationRequestsProcessor <-> UI
private static final String EXTRA_APPLICATION_CALLBACK = "application_callback";
private static final String EXTRA_ASSOCIATION_REQUEST = "association_request";
@@ -138,6 +135,8 @@
private final @NonNull CompanionDeviceManagerService mService;
private final @NonNull PackageManagerInternal mPackageManager;
private final @NonNull AssociationStoreImpl mAssociationStore;
+ @NonNull
+ private final ComponentName mCompanionDeviceActivity;
AssociationRequestsProcessor(@NonNull CompanionDeviceManagerService service,
@NonNull AssociationStoreImpl associationStore) {
@@ -145,6 +144,9 @@
mService = service;
mPackageManager = service.mPackageManagerInternal;
mAssociationStore = associationStore;
+ mCompanionDeviceActivity = createRelative(
+ mContext.getString(R.string.config_companionDeviceManagerPackage),
+ ".CompanionDeviceActivity");
}
/**
@@ -197,7 +199,7 @@
extras.putParcelable(EXTRA_RESULT_RECEIVER, prepareForIpc(mOnRequestConfirmationReceiver));
final Intent intent = new Intent();
- intent.setComponent(ASSOCIATION_REQUEST_APPROVAL_ACTIVITY);
+ intent.setComponent(mCompanionDeviceActivity);
intent.putExtras(extras);
// 2b.3. Create a PendingIntent.
@@ -224,7 +226,7 @@
extras.putBoolean(EXTRA_FORCE_CANCEL_CONFIRMATION, true);
final Intent intent = new Intent();
- intent.setComponent(ASSOCIATION_REQUEST_APPROVAL_ACTIVITY);
+ intent.setComponent(mCompanionDeviceActivity);
intent.putExtras(extras);
return createPendingIntent(packageUid, intent);
diff --git a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
index dd7d38f..82387a1 100644
--- a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
+++ b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
@@ -19,7 +19,6 @@
import static android.app.PendingIntent.FLAG_CANCEL_CURRENT;
import static android.app.PendingIntent.FLAG_IMMUTABLE;
import static android.app.PendingIntent.FLAG_ONE_SHOT;
-import static android.companion.CompanionDeviceManager.COMPANION_DEVICE_DISCOVERY_PACKAGE_NAME;
import static android.content.ComponentName.createRelative;
import static com.android.server.companion.Utils.prepareForIpc;
@@ -48,6 +47,7 @@
import android.permission.PermissionControllerManager;
import android.util.Slog;
+import com.android.internal.R;
import com.android.server.companion.AssociationStore;
import com.android.server.companion.CompanionDeviceManagerService;
import com.android.server.companion.PermissionsUtils;
@@ -75,9 +75,6 @@
private static final String EXTRA_COMPANION_DEVICE_NAME = "companion_device_name";
private static final String EXTRA_SYSTEM_DATA_TRANSFER_RESULT_RECEIVER =
"system_data_transfer_result_receiver";
- private static final ComponentName SYSTEM_DATA_TRANSFER_REQUEST_APPROVAL_ACTIVITY =
- createRelative(COMPANION_DEVICE_DISCOVERY_PACKAGE_NAME,
- ".CompanionDeviceDataTransferActivity");
private final Context mContext;
private final AssociationStore mAssociationStore;
@@ -85,6 +82,7 @@
private final CompanionTransportManager mTransportManager;
private final PermissionControllerManager mPermissionControllerManager;
private final ExecutorService mExecutor;
+ private final ComponentName mCompanionDeviceDataTransferActivity;
public SystemDataTransferProcessor(CompanionDeviceManagerService service,
AssociationStore associationStore,
@@ -108,6 +106,9 @@
mTransportManager.addListener(MESSAGE_REQUEST_PERMISSION_RESTORE, messageListener);
mPermissionControllerManager = mContext.getSystemService(PermissionControllerManager.class);
mExecutor = Executors.newSingleThreadExecutor();
+ mCompanionDeviceDataTransferActivity = createRelative(
+ mContext.getString(R.string.config_companionDeviceManagerPackage),
+ ".CompanionDeviceDataTransferActivity");
}
/**
@@ -146,7 +147,7 @@
prepareForIpc(mOnSystemDataTransferRequestConfirmationReceiver));
final Intent intent = new Intent();
- intent.setComponent(SYSTEM_DATA_TRANSFER_REQUEST_APPROVAL_ACTIVITY);
+ intent.setComponent(mCompanionDeviceDataTransferActivity);
intent.putExtras(extras);
// Create a PendingIntent
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index 9355741..51acfd3 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -1053,8 +1053,8 @@
}
void onEnteringPipBlocked(int uid) {
- showToastWhereUidIsRunning(uid, com.android.internal.R.string.vdm_pip_blocked,
- Toast.LENGTH_LONG, mContext.getMainLooper());
+ // Do nothing. ActivityRecord#checkEnterPictureInPictureState logs that the display does not
+ // support PiP.
}
void playSoundEffect(int effectType) {
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 63f8ae0..459e24d 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -230,8 +230,7 @@
name: "services.core.json.gz",
srcs: [":checked-protolog.json"],
out: ["services.core.protolog.json.gz"],
- cmd: "$(location minigzip) -c < $(in) > $(out)",
- tools: ["minigzip"],
+ cmd: "gzip -c < $(in) > $(out)",
}
prebuilt_etc {
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index ecc0aa5..830d55a 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -1269,6 +1269,21 @@
}
}
+ /**
+ * This method checks if the volume is public and the volume is visible and the volume it is
+ * trying to mount doesn't have the same mount user id as the current user being maintained by
+ * StorageManagerService and change the mount Id. The checks are same as
+ * {@link StorageManagerService#maybeRemountVolumes(int)}
+ * @param VolumeInfo object to consider for changing the mountId
+ */
+ private void updateVolumeMountIdIfRequired(VolumeInfo vol) {
+ synchronized (mLock) {
+ if (!vol.isPrimary() && vol.isVisible() && vol.getMountUserId() != mCurrentUserId) {
+ vol.mountUserId = mCurrentUserId;
+ }
+ }
+ }
+
private boolean supportsBlockCheckpoint() throws RemoteException {
enforcePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS);
return mVold.supportsBlockCheckpoint();
@@ -1382,13 +1397,14 @@
}
@Override
- public void onVolumeStateChanged(String volId, final int newState) {
+ public void onVolumeStateChanged(String volId, final int newState, final int userId) {
synchronized (mLock) {
final VolumeInfo vol = mVolumes.get(volId);
if (vol != null) {
final int oldState = vol.state;
vol.state = newState;
final VolumeInfo vInfo = new VolumeInfo(vol);
+ vInfo.mountUserId = userId;
final SomeArgs args = SomeArgs.obtain();
args.arg1 = vInfo;
args.argi1 = oldState;
@@ -2232,7 +2248,7 @@
if (isMountDisallowed(vol)) {
throw new SecurityException("Mounting " + volId + " restricted by policy");
}
-
+ updateVolumeMountIdIfRequired(vol);
mount(vol);
}
diff --git a/services/core/java/com/android/server/SystemConfig.java b/services/core/java/com/android/server/SystemConfig.java
index 7fae31c..caf1684 100644
--- a/services/core/java/com/android/server/SystemConfig.java
+++ b/services/core/java/com/android/server/SystemConfig.java
@@ -35,7 +35,6 @@
import android.os.incremental.IncrementalManager;
import android.os.storage.StorageManager;
import android.permission.PermissionManager.SplitPermissionInfo;
-import android.sysprop.ApexProperties;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -1208,8 +1207,7 @@
boolean systemExt = permFile.toPath().startsWith(
Environment.getSystemExtDirectory().toPath() + "/");
boolean apex = permFile.toPath().startsWith(
- Environment.getApexDirectory().toPath() + "/")
- && ApexProperties.updatable().orElse(false);
+ Environment.getApexDirectory().toPath() + "/");
if (vendor) {
readPrivAppPermissions(parser,
mPermissionAllowlist.getVendorPrivilegedAppAllowlist());
@@ -1827,6 +1825,9 @@
soname, soname, new String[0], true);
mSharedLibraries.put(entry.name, entry);
}
+ } catch (FileNotFoundException e) {
+ // Expected for /vendor/etc/public.libraries.txt on some devices
+ Slog.d(TAG, listFile + " does not exist");
} catch (IOException e) {
Slog.w(TAG, "Failed to read public libraries file " + listFile, e);
}
diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java
index 48de441..6baae4b 100644
--- a/services/core/java/com/android/server/Watchdog.java
+++ b/services/core/java/com/android/server/Watchdog.java
@@ -453,7 +453,7 @@
mInterestingJavaPids.add(Process.myPid());
// See the notes on DEFAULT_TIMEOUT.
- assert DB ||
+ assert DB || Build.IS_USERDEBUG ||
DEFAULT_TIMEOUT > ZygoteConnectionConstants.WRAPPED_PID_TIMEOUT_MILLIS;
mTraceErrorLogger = new TraceErrorLogger();
@@ -527,7 +527,8 @@
*/
void updateWatchdogTimeout(long timeoutMillis) {
// See the notes on DEFAULT_TIMEOUT.
- if (!DB && timeoutMillis <= ZygoteConnectionConstants.WRAPPED_PID_TIMEOUT_MILLIS) {
+ if (!DB && !Build.IS_USERDEBUG
+ && timeoutMillis <= ZygoteConnectionConstants.WRAPPED_PID_TIMEOUT_MILLIS) {
timeoutMillis = ZygoteConnectionConstants.WRAPPED_PID_TIMEOUT_MILLIS + 1;
}
mWatchdogTimeoutMillis = timeoutMillis;
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index f4c8a57..146215b 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -357,15 +357,25 @@
@Override
public void onPackageAdded(String packageName, int uid) {
// Called on a handler, and running as the system
- UserAccounts accounts = getUserAccounts(UserHandle.getUserId(uid));
- cancelAccountAccessRequestNotificationIfNeeded(uid, true, accounts);
+ try {
+ UserAccounts accounts = getUserAccounts(UserHandle.getUserId(uid));
+ cancelAccountAccessRequestNotificationIfNeeded(uid, true, accounts);
+ } catch (SQLiteCantOpenDatabaseException e) {
+ Log.w(TAG, "Can't read accounts database", e);
+ return;
+ }
}
@Override
public void onPackageUpdateFinished(String packageName, int uid) {
// Called on a handler, and running as the system
- UserAccounts accounts = getUserAccounts(UserHandle.getUserId(uid));
- cancelAccountAccessRequestNotificationIfNeeded(uid, true, accounts);
+ try {
+ UserAccounts accounts = getUserAccounts(UserHandle.getUserId(uid));
+ cancelAccountAccessRequestNotificationIfNeeded(uid, true, accounts);
+ } catch (SQLiteCantOpenDatabaseException e) {
+ Log.w(TAG, "Can't read accounts database", e);
+ return;
+ }
}
}.register(mContext, mHandler.getLooper(), UserHandle.ALL, true);
@@ -391,6 +401,9 @@
}
} catch (NameNotFoundException e) {
/* ignore */
+ } catch (SQLiteCantOpenDatabaseException e) {
+ Log.w(TAG, "Can't read accounts database", e);
+ return;
}
}
});
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 2f7c890..d199006 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -256,7 +256,7 @@
public final class ActiveServices {
private static final String TAG = TAG_WITH_CLASS_NAME ? "ActiveServices" : TAG_AM;
private static final String TAG_MU = TAG + POSTFIX_MU;
- private static final String TAG_SERVICE = TAG + POSTFIX_SERVICE;
+ static final String TAG_SERVICE = TAG + POSTFIX_SERVICE;
private static final String TAG_SERVICE_EXECUTING = TAG + POSTFIX_SERVICE_EXECUTING;
private static final boolean DEBUG_DELAYED_SERVICE = DEBUG_SERVICE;
@@ -850,8 +850,7 @@
// Service.startForeground()), at that point we will consult the BFSL check and the timeout
// and make the necessary decisions.
setFgsRestrictionLocked(callingPackage, callingPid, callingUid, service, r, userId,
- backgroundStartPrivileges, false /* isBindService */,
- !fgRequired /* isStartService */);
+ backgroundStartPrivileges, false /* isBindService */);
if (!mAm.mUserController.exists(r.userId)) {
Slog.w(TAG, "Trying to start service with non-existent user! " + r.userId);
@@ -894,7 +893,7 @@
if (fgRequired) {
logFgsBackgroundStart(r);
- if (r.mAllowStartForeground == REASON_DENIED && isBgFgsRestrictionEnabled(r)) {
+ if (!r.isFgsAllowedStart() && isBgFgsRestrictionEnabled(r)) {
String msg = "startForegroundService() not allowed due to "
+ "mAllowStartForeground false: service "
+ r.shortInstanceName;
@@ -1060,7 +1059,7 @@
// Use that as a shortcut if possible to avoid having to recheck all the conditions.
final boolean whileInUseAllowsUiJobScheduling =
ActivityManagerService.doesReasonCodeAllowSchedulingUserInitiatedJobs(
- r.mAllowWhileInUsePermissionInFgsReason);
+ r.getFgsAllowWIU());
r.updateAllowUiJobScheduling(whileInUseAllowsUiJobScheduling
|| mAm.canScheduleUserInitiatedJobs(callingUid, callingPid, callingPackage));
} else {
@@ -2178,12 +2177,12 @@
// on a SHORT_SERVICE FGS.
// See if the app could start an FGS or not.
- r.mAllowStartForeground = REASON_DENIED;
+ r.clearFgsAllowStart();
setFgsRestrictionLocked(r.serviceInfo.packageName, r.app.getPid(),
r.appInfo.uid, r.intent.getIntent(), r, r.userId,
BackgroundStartPrivileges.NONE,
- false /* isBindService */, false /* isStartService */);
- if (r.mAllowStartForeground == REASON_DENIED) {
+ false /* isBindService */);
+ if (!r.isFgsAllowedStart()) {
Slog.w(TAG_SERVICE, "FGS type change to/from SHORT_SERVICE: "
+ " BFSL DENIED.");
} else {
@@ -2191,13 +2190,13 @@
Slog.w(TAG_SERVICE, "FGS type change to/from SHORT_SERVICE: "
+ " BFSL Allowed: "
+ PowerExemptionManager.reasonCodeToString(
- r.mAllowStartForeground));
+ r.getFgsAllowStart()));
}
}
final boolean fgsStartAllowed =
!isBgFgsRestrictionEnabledForService
- || (r.mAllowStartForeground != REASON_DENIED);
+ || r.isFgsAllowedStart();
if (fgsStartAllowed) {
if (isNewTypeShortFgs) {
@@ -2246,7 +2245,7 @@
setFgsRestrictionLocked(r.serviceInfo.packageName, r.app.getPid(),
r.appInfo.uid, r.intent.getIntent(), r, r.userId,
BackgroundStartPrivileges.NONE,
- false /* isBindService */, false /* isStartService */);
+ false /* isBindService */);
final String temp = "startForegroundDelayMs:" + delayMs;
if (r.mInfoAllowStartForeground != null) {
r.mInfoAllowStartForeground += "; " + temp;
@@ -2266,20 +2265,21 @@
setFgsRestrictionLocked(r.serviceInfo.packageName, r.app.getPid(),
r.appInfo.uid, r.intent.getIntent(), r, r.userId,
BackgroundStartPrivileges.NONE,
- false /* isBindService */, false /* isStartService */);
+ false /* isBindService */);
}
// If the foreground service is not started from TOP process, do not allow it to
// have while-in-use location/camera/microphone access.
- if (!r.mAllowWhileInUsePermissionInFgs) {
+ if (!r.isFgsAllowedWIU()) {
Slog.w(TAG,
"Foreground service started from background can not have "
+ "location/camera/microphone access: service "
+ r.shortInstanceName);
}
+ r.maybeLogFgsLogicChange();
if (!bypassBfslCheck) {
logFgsBackgroundStart(r);
- if (r.mAllowStartForeground == REASON_DENIED
+ if (!r.isFgsAllowedStart()
&& isBgFgsRestrictionEnabledForService) {
final String msg = "Service.startForeground() not allowed due to "
+ "mAllowStartForeground false: service "
@@ -2378,9 +2378,9 @@
// The logging of FOREGROUND_SERVICE_STATE_CHANGED__STATE__ENTER event could
// be deferred, make a copy of mAllowStartForeground and
// mAllowWhileInUsePermissionInFgs.
- r.mAllowStartForegroundAtEntering = r.mAllowStartForeground;
+ r.mAllowStartForegroundAtEntering = r.getFgsAllowStart();
r.mAllowWhileInUsePermissionInFgsAtEntering =
- r.mAllowWhileInUsePermissionInFgs;
+ r.isFgsAllowedWIU();
r.mStartForegroundCount++;
r.mFgsEnterTime = SystemClock.uptimeMillis();
if (!stopProcStatsOp) {
@@ -2558,7 +2558,7 @@
policy.getForegroundServiceTypePolicyInfo(type, defaultToType);
final @ForegroundServicePolicyCheckCode int code = policy.checkForegroundServiceTypePolicy(
mAm.mContext, r.packageName, r.app.uid, r.app.getPid(),
- r.mAllowWhileInUsePermissionInFgs, policyInfo);
+ r.isFgsAllowedWIU(), policyInfo);
RuntimeException exception = null;
switch (code) {
case FGS_TYPE_POLICY_CHECK_DEPRECATED: {
@@ -3744,8 +3744,7 @@
}
}
setFgsRestrictionLocked(callingPackage, callingPid, callingUid, service, s, userId,
- BackgroundStartPrivileges.NONE, true /* isBindService */,
- false /* isStartService */);
+ BackgroundStartPrivileges.NONE, true /* isBindService */);
if (s.app != null) {
ProcessServiceRecord servicePsr = s.app.mServices;
@@ -7444,54 +7443,80 @@
* @param callingUid caller app's uid.
* @param intent intent to start/bind service.
* @param r the service to start.
- * @param isStartService True if it's called from Context.startService().
- * False if it's called from Context.startForegroundService() or
- * Service.startForeground().
+ * @param isBindService True if it's called from bindService().
* @return true if allow, false otherwise.
*/
private void setFgsRestrictionLocked(String callingPackage,
int callingPid, int callingUid, Intent intent, ServiceRecord r, int userId,
- BackgroundStartPrivileges backgroundStartPrivileges, boolean isBindService,
- boolean isStartService) {
- // Check DeviceConfig flag.
- if (!mAm.mConstants.mFlagBackgroundFgsStartRestrictionEnabled) {
- if (!r.mAllowWhileInUsePermissionInFgs) {
- // BGFGS start restrictions are disabled. We're allowing while-in-use permissions.
- // Note REASON_OTHER since there's no other suitable reason.
- r.mAllowWhileInUsePermissionInFgsReason = REASON_OTHER;
- }
- r.mAllowWhileInUsePermissionInFgs = true;
+ BackgroundStartPrivileges backgroundStartPrivileges, boolean isBindService) {
+
+ @ReasonCode int allowWIU;
+ @ReasonCode int allowStart;
+
+ // If called from bindService(), do not update the actual fields, but instead
+ // keep it in a separate set of fields.
+ if (isBindService) {
+ allowWIU = r.mAllowWIUInBindService;
+ allowStart = r.mAllowStartInBindService;
+ } else {
+ allowWIU = r.mAllowWhileInUsePermissionInFgsReasonNoBinding;
+ allowStart = r.mAllowStartForegroundNoBinding;
}
- if (!r.mAllowWhileInUsePermissionInFgs
- || (r.mAllowStartForeground == REASON_DENIED)) {
+ // Check DeviceConfig flag.
+ if (!mAm.mConstants.mFlagBackgroundFgsStartRestrictionEnabled) {
+ if (allowWIU == REASON_DENIED) {
+ // BGFGS start restrictions are disabled. We're allowing while-in-use permissions.
+ // Note REASON_OTHER since there's no other suitable reason.
+ allowWIU = REASON_OTHER;
+ }
+ }
+
+ if ((allowWIU == REASON_DENIED)
+ || (allowStart == REASON_DENIED)) {
@ReasonCode final int allowWhileInUse = shouldAllowFgsWhileInUsePermissionLocked(
callingPackage, callingPid, callingUid, r.app, backgroundStartPrivileges);
// We store them to compare the old and new while-in-use logics to each other.
// (They're not used for any other purposes.)
- if (!r.mAllowWhileInUsePermissionInFgs) {
- r.mAllowWhileInUsePermissionInFgs = (allowWhileInUse != REASON_DENIED);
- r.mAllowWhileInUsePermissionInFgsReason = allowWhileInUse;
+ if (allowWIU == REASON_DENIED) {
+ allowWIU = allowWhileInUse;
}
- if (r.mAllowStartForeground == REASON_DENIED) {
- r.mAllowStartForeground = shouldAllowFgsStartForegroundWithBindingCheckLocked(
+ if (allowStart == REASON_DENIED) {
+ allowStart = shouldAllowFgsStartForegroundWithBindingCheckLocked(
allowWhileInUse, callingPackage, callingPid, callingUid, intent, r,
backgroundStartPrivileges, isBindService);
}
}
+
+ if (isBindService) {
+ r.mAllowWIUInBindService = allowWIU;
+ r.mAllowStartInBindService = allowStart;
+ } else {
+ r.mAllowWhileInUsePermissionInFgsReasonNoBinding = allowWIU;
+ r.mAllowStartForegroundNoBinding = allowStart;
+
+ // Also do a binding client check, unless called from bindService().
+ if (r.mAllowWIUByBindings == REASON_DENIED) {
+ r.mAllowWIUByBindings =
+ shouldAllowFgsWhileInUsePermissionByBindingsLocked(callingUid);
+ }
+ if (r.mAllowStartByBindings == REASON_DENIED) {
+ r.mAllowStartByBindings = r.mAllowWIUByBindings;
+ }
+ }
}
/**
* Reset various while-in-use and BFSL related information.
*/
void resetFgsRestrictionLocked(ServiceRecord r) {
- r.mAllowWhileInUsePermissionInFgs = false;
- r.mAllowWhileInUsePermissionInFgsReason = REASON_DENIED;
- r.mAllowStartForeground = REASON_DENIED;
+ r.clearFgsAllowWIU();
+ r.clearFgsAllowStart();
+
r.mInfoAllowStartForeground = null;
r.mInfoTempFgsAllowListReason = null;
r.mLoggedInfoAllowStartForeground = false;
- r.updateAllowUiJobScheduling(r.mAllowWhileInUsePermissionInFgs);
+ r.updateAllowUiJobScheduling(r.isFgsAllowedWIU());
}
boolean canStartForegroundServiceLocked(int callingPid, int callingUid, String callingPackage) {
@@ -8063,10 +8088,10 @@
*/
if (!r.mLoggedInfoAllowStartForeground) {
final String msg = "Background started FGS: "
- + ((r.mAllowStartForeground != REASON_DENIED) ? "Allowed " : "Disallowed ")
+ + (r.isFgsAllowedStart() ? "Allowed " : "Disallowed ")
+ r.mInfoAllowStartForeground
+ (r.isShortFgs() ? " (Called on SHORT_SERVICE)" : "");
- if (r.mAllowStartForeground != REASON_DENIED) {
+ if (r.isFgsAllowedStart()) {
if (ActivityManagerUtils.shouldSamplePackageForAtom(r.packageName,
mAm.mConstants.mFgsStartAllowedLogSampleRate)) {
Slog.wtfQuiet(TAG, msg);
@@ -8106,8 +8131,8 @@
allowWhileInUsePermissionInFgs = r.mAllowWhileInUsePermissionInFgsAtEntering;
fgsStartReasonCode = r.mAllowStartForegroundAtEntering;
} else {
- allowWhileInUsePermissionInFgs = r.mAllowWhileInUsePermissionInFgs;
- fgsStartReasonCode = r.mAllowStartForeground;
+ allowWhileInUsePermissionInFgs = r.isFgsAllowedWIU();
+ fgsStartReasonCode = r.getFgsAllowStart();
}
final int callerTargetSdkVersion = r.mRecentCallerApplicationInfo != null
? r.mRecentCallerApplicationInfo.targetSdkVersion : 0;
@@ -8296,8 +8321,7 @@
r.mFgsEnterTime = SystemClock.uptimeMillis();
r.foregroundServiceType = options.mForegroundServiceTypes;
setFgsRestrictionLocked(callingPackage, callingPid, callingUid, intent, r, userId,
- BackgroundStartPrivileges.NONE, false /* isBindService */,
- false /* isStartService */);
+ BackgroundStartPrivileges.NONE, false /* isBindService */);
final ProcessServiceRecord psr = callerApp.mServices;
final boolean newService = psr.startService(r);
// updateOomAdj.
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 359c3cd..8012c26 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -559,6 +559,10 @@
// How long we wait for a launched process to attach to the activity manager
// before we decide it's never going to come up for real.
static final int PROC_START_TIMEOUT = 10 * 1000 * Build.HW_TIMEOUT_MULTIPLIER;
+
+ // How long we wait for a launched process to complete its app startup before we ANR.
+ static final int BIND_APPLICATION_TIMEOUT = 10 * 1000 * Build.HW_TIMEOUT_MULTIPLIER;
+
// How long we wait to kill an application zygote, after the last process using
// it has gone away.
static final int KILL_APP_ZYGOTE_DELAY_MS = 5 * 1000;
@@ -1625,6 +1629,7 @@
static final int UPDATE_CACHED_APP_HIGH_WATERMARK = 79;
static final int ADD_UID_TO_OBSERVER_MSG = 80;
static final int REMOVE_UID_FROM_OBSERVER_MSG = 81;
+ static final int BIND_APPLICATION_TIMEOUT_MSG = 82;
static final int FIRST_BROADCAST_QUEUE_MSG = 200;
@@ -1977,6 +1982,16 @@
case UPDATE_CACHED_APP_HIGH_WATERMARK: {
mAppProfiler.mCachedAppsWatermarkData.updateCachedAppsSnapshot((long) msg.obj);
} break;
+ case BIND_APPLICATION_TIMEOUT_MSG: {
+ ProcessRecord app = (ProcessRecord) msg.obj;
+
+ final String anrMessage;
+ synchronized (app) {
+ anrMessage = "Process " + app + " failed to complete startup";
+ }
+
+ mAnrHelper.appNotResponding(app, TimeoutRecord.forAppStart(anrMessage));
+ } break;
}
}
}
@@ -4737,6 +4752,12 @@
app.getDisabledCompatChanges(), serializedSystemFontMap,
app.getStartElapsedTime(), app.getStartUptime());
}
+
+ Message msg = mHandler.obtainMessage(BIND_APPLICATION_TIMEOUT_MSG);
+ msg.obj = app;
+ mHandler.sendMessageDelayed(msg, BIND_APPLICATION_TIMEOUT);
+ mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app);
+
if (profilerInfo != null) {
profilerInfo.closeFd();
profilerInfo = null;
@@ -4811,7 +4832,7 @@
}
if (app != null && app.getStartUid() == uid && app.getStartSeq() == startSeq) {
- mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app);
+ mHandler.removeMessages(BIND_APPLICATION_TIMEOUT_MSG, app);
} else {
Slog.wtf(TAG, "Mismatched or missing ProcessRecord: " + app + ". Pid: " + pid
+ ". Uid: " + uid);
@@ -5822,12 +5843,25 @@
/**
* Allows if {@code pid} is {@link #MY_PID}, then denies if the {@code pid} has been denied
* provided non-{@code null} {@code permission} before. Otherwise calls into
- * {@link ActivityManager#checkComponentPermission(String, int, int, boolean)}.
+ * {@link ActivityManager#checkComponentPermission(String, int, int, int, boolean)}.
*/
@PackageManager.PermissionResult
@PermissionMethod
public static int checkComponentPermission(@PermissionName String permission, int pid, int uid,
int owningUid, boolean exported) {
+ return checkComponentPermission(permission, pid, uid, Context.DEVICE_ID_DEFAULT,
+ owningUid, exported);
+ }
+
+ /**
+ * Allows if {@code pid} is {@link #MY_PID}, then denies if the {@code pid} has been denied
+ * provided non-{@code null} {@code permission} before. Otherwise calls into
+ * {@link ActivityManager#checkComponentPermission(String, int, int, int, boolean)}.
+ */
+ @PackageManager.PermissionResult
+ @PermissionMethod
+ public static int checkComponentPermission(@PermissionName String permission, int pid, int uid,
+ int deviceId, int owningUid, boolean exported) {
if (pid == MY_PID) {
return PackageManager.PERMISSION_GRANTED;
}
@@ -5846,7 +5880,7 @@
}
}
}
- return ActivityManager.checkComponentPermission(permission, uid,
+ return ActivityManager.checkComponentPermission(permission, uid, deviceId,
owningUid, exported);
}
@@ -5875,10 +5909,27 @@
@PackageManager.PermissionResult
@PermissionMethod
public int checkPermission(@PermissionName String permission, int pid, int uid) {
+ return checkPermissionForDevice(permission, pid, uid, Context.DEVICE_ID_DEFAULT);
+ }
+
+ /**
+ * As the only public entry point for permissions checking, this method
+ * can enforce the semantic that requesting a check on a null global
+ * permission is automatically denied. (Internally a null permission
+ * string is used when calling {@link #checkComponentPermission} in cases
+ * when only uid-based security is needed.)
+ *
+ * This can be called with or without the global lock held.
+ */
+ @Override
+ @PackageManager.PermissionResult
+ @PermissionMethod
+ public int checkPermissionForDevice(@PermissionName String permission, int pid, int uid,
+ int deviceId) {
if (permission == null) {
return PackageManager.PERMISSION_DENIED;
}
- return checkComponentPermission(permission, pid, uid, -1, true);
+ return checkComponentPermission(permission, pid, uid, deviceId, -1, true);
}
/**
@@ -8108,8 +8159,8 @@
return killed;
}
- @Override
- public void killUid(int appId, int userId, String reason) {
+ private void killUid(int appId, int userId, int reason, int subReason,
+ String reasonAsString) {
enforceCallingPermission(Manifest.permission.KILL_UID, "killUid");
synchronized (this) {
final long identity = Binder.clearCallingIdentity();
@@ -8120,9 +8171,9 @@
true /* callerWillRestart */, true /* doit */,
true /* evenPersistent */, false /* setRemoved */,
false /* uninstalling */,
- ApplicationExitInfo.REASON_OTHER,
- ApplicationExitInfo.SUBREASON_KILL_UID,
- reason != null ? reason : "kill uid");
+ reason,
+ subReason,
+ reasonAsString != null ? reasonAsString : "kill uid");
}
} finally {
Binder.restoreCallingIdentity(identity);
@@ -8131,6 +8182,12 @@
}
@Override
+ public void killUid(int appId, int userId, String reason) {
+ killUid(appId, userId, ApplicationExitInfo.REASON_OTHER,
+ ApplicationExitInfo.SUBREASON_KILL_UID, reason);
+ }
+
+ @Override
public void killUidForPermissionChange(int appId, int userId, String reason) {
enforceCallingPermission(Manifest.permission.KILL_UID, "killUid");
synchronized (this) {
@@ -8645,6 +8702,8 @@
Slog.i(TAG, "Skipping kill (uid is SYSTEM)");
} else {
killUid(UserHandle.getAppId(uid), UserHandle.getUserId(uid),
+ ApplicationExitInfo.REASON_EXCESSIVE_RESOURCE_USAGE,
+ ApplicationExitInfo.SUBREASON_EXCESSIVE_BINDER_OBJECTS,
"Too many Binders sent to SYSTEM");
// We need to run a GC here, because killing the processes involved
// actually isn't guaranteed to free up the proxies; in fact, if the
@@ -8658,7 +8717,7 @@
// cleaning up the old proxies.
VMRuntime.getRuntime().requestConcurrentGC();
}
- }, mHandler);
+ }, BackgroundThread.getHandler());
t.traceEnd(); // setBinderProxies
t.traceEnd(); // ActivityManagerStartApps
@@ -9683,6 +9742,7 @@
ResultReceiver resultReceiver) {
final int callingUid = Binder.getCallingUid();
if (callingUid != ROOT_UID && callingUid != Process.SHELL_UID) {
+ resultReceiver.send(-1, null);
throw new SecurityException("Shell commands are only callable by root or shell");
}
(new ActivityManagerShellCommand(this, false)).exec(
diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
index 0fcec6f..fc8175b 100644
--- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
@@ -1144,9 +1144,6 @@
} else if (mProcessPersistent) {
mRunnableAt = runnableAt + constants.DELAY_PERSISTENT_PROC_MILLIS;
mRunnableAtReason = REASON_PERSISTENT;
- } else if (UserHandle.isCore(uid)) {
- mRunnableAt = runnableAt;
- mRunnableAtReason = REASON_CORE_UID;
} else if (mCountOrdered > 0) {
mRunnableAt = runnableAt;
mRunnableAtReason = REASON_CONTAINS_ORDERED;
@@ -1193,6 +1190,9 @@
// is already cached, they'll be deferred on the line above
mRunnableAt = runnableAt;
mRunnableAtReason = REASON_CONTAINS_RESULT_TO;
+ } else if (UserHandle.isCore(uid)) {
+ mRunnableAt = runnableAt;
+ mRunnableAtReason = REASON_CORE_UID;
} else {
mRunnableAt = runnableAt + constants.DELAY_NORMAL_MILLIS;
mRunnableAtReason = REASON_NORMAL;
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index f5e2eb5..8d2edaa 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -249,6 +249,7 @@
private static final int MSG_CHECK_HEALTH = 5;
private static final int MSG_CHECK_PENDING_COLD_START_VALIDITY = 6;
private static final int MSG_PROCESS_FREEZABLE_CHANGED = 7;
+ private static final int MSG_UID_STATE_CHANGED = 8;
private void enqueueUpdateRunningList() {
mLocalHandler.removeMessages(MSG_UPDATE_RUNNING_LIST);
@@ -295,6 +296,19 @@
}
return true;
}
+ case MSG_UID_STATE_CHANGED: {
+ final int uid = (int) msg.obj;
+ final int procState = msg.arg1;
+ synchronized (mService) {
+ if (procState == ActivityManager.PROCESS_STATE_TOP) {
+ mUidForeground.put(uid, true);
+ } else {
+ mUidForeground.delete(uid);
+ }
+ refreshProcessQueuesLocked(uid);
+ }
+ return true;
+ }
}
return false;
};
@@ -672,7 +686,7 @@
@Override
public void onProcessFreezableChangedLocked(@NonNull ProcessRecord app) {
mLocalHandler.removeMessages(MSG_PROCESS_FREEZABLE_CHANGED, app);
- mLocalHandler.sendMessage(mHandler.obtainMessage(MSG_PROCESS_FREEZABLE_CHANGED, app));
+ mLocalHandler.obtainMessage(MSG_PROCESS_FREEZABLE_CHANGED, app).sendToTarget();
}
@Override
@@ -1601,14 +1615,9 @@
@Override
public void onUidStateChanged(int uid, int procState, long procStateSeq,
int capability) {
- synchronized (mService) {
- if (procState == ActivityManager.PROCESS_STATE_TOP) {
- mUidForeground.put(uid, true);
- } else {
- mUidForeground.delete(uid);
- }
- refreshProcessQueuesLocked(uid);
- }
+ mLocalHandler.removeMessages(MSG_UID_STATE_CHANGED, uid);
+ mLocalHandler.obtainMessage(MSG_UID_STATE_CHANGED, procState, 0, uid)
+ .sendToTarget();
}
}, ActivityManager.UID_OBSERVER_PROCSTATE,
ActivityManager.PROCESS_STATE_TOP, "android");
diff --git a/services/core/java/com/android/server/am/ContentProviderHelper.java b/services/core/java/com/android/server/am/ContentProviderHelper.java
index 3ab40d2..e26ee9c 100644
--- a/services/core/java/com/android/server/am/ContentProviderHelper.java
+++ b/services/core/java/com/android/server/am/ContentProviderHelper.java
@@ -96,7 +96,9 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
/**
@@ -116,6 +118,8 @@
private final ProviderMap mProviderMap;
private boolean mSystemProvidersInstalled;
+ private final Map<String, Boolean> mCloneProfileAuthorityRedirectionCache = new HashMap<>();
+
ContentProviderHelper(ActivityManagerService service, boolean createProviderMap) {
mService = service;
mProviderMap = createProviderMap ? new ProviderMap(mService) : null;
@@ -201,9 +205,24 @@
final UserProperties userProps = umInternal.getUserProperties(userId);
final boolean isMediaSharedWithParent =
userProps != null && userProps.isMediaSharedWithParent();
- if (!isAuthorityRedirectedForCloneProfile(name) || !isMediaSharedWithParent) {
+ if (!isAuthorityRedirectedForCloneProfileCached(name) || !isMediaSharedWithParent) {
// First check if this content provider has been published...
cpr = mProviderMap.getProviderByName(name, userId);
+ // In case we are on media authority and callingUid is cloned app asking to access
+ // the contentProvider of user 0 by specifying content as
+ // content://<parent-id>@media/external/file, we skip checkUser.
+ if (isAuthorityRedirectedForCloneProfileCached(name)) {
+ final int callingUserId = UserHandle.getUserId(callingUid);
+ final UserProperties userPropsCallingUser =
+ umInternal.getUserProperties(callingUserId);
+ final boolean isMediaSharedWithParentForCallingUser =
+ userPropsCallingUser != null
+ && userPropsCallingUser.isMediaSharedWithParent();
+ if (isMediaSharedWithParentForCallingUser
+ && umInternal.getProfileParentId(callingUserId) == userId) {
+ checkCrossUser = false;
+ }
+ }
}
// If that didn't work, check if it exists for user 0 and then
// verify that it's a singleton provider before using it.
@@ -218,7 +237,7 @@
r == null ? callingUid : r.uid, cpi.applicationInfo.uid)) {
userId = UserHandle.USER_SYSTEM;
checkCrossUser = false;
- } else if (isAuthorityRedirectedForCloneProfile(name)) {
+ } else if (isAuthorityRedirectedForCloneProfileCached(name)) {
if (isMediaSharedWithParent) {
userId = umInternal.getProfileParentId(userId);
checkCrossUser = false;
@@ -1073,7 +1092,7 @@
return false;
}
- if (isAuthorityRedirectedForCloneProfile(holder.info.authority)
+ if (isAuthorityRedirectedForCloneProfileCached(holder.info.authority)
&& resolveParentUserIdForCloneProfile(userId) != userId) {
// Since clone profile shares certain providers with its parent and the access is
// re-directed as well, the holder may not actually be installed on the clone profile.
@@ -1108,7 +1127,7 @@
userId = UserHandle.getCallingUserId();
}
- if (isAuthorityRedirectedForCloneProfile(authority)) {
+ if (isAuthorityRedirectedForCloneProfileCached(authority)) {
UserManagerInternal umInternal = LocalServices.getService(UserManagerInternal.class);
UserInfo userInfo = umInternal.getUserInfo(userId);
@@ -1954,4 +1973,14 @@
String[] args) {
return mProviderMap.dumpProviderProto(fd, pw, name, args);
}
+
+ private Boolean isAuthorityRedirectedForCloneProfileCached(String auth) {
+ if (mCloneProfileAuthorityRedirectionCache.containsKey(auth)) {
+ return mCloneProfileAuthorityRedirectionCache.get(auth);
+ } else {
+ boolean isAuthRedirected = isAuthorityRedirectedForCloneProfile(auth);
+ mCloneProfileAuthorityRedirectionCache.put(auth, isAuthRedirected);
+ return isAuthRedirected;
+ }
+ }
}
diff --git a/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java b/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java
index 38e7371..4f5b5e1 100644
--- a/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java
+++ b/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java
@@ -479,8 +479,8 @@
r.appInfo.uid,
r.shortInstanceName,
fgsState, // FGS State
- r.mAllowWhileInUsePermissionInFgs, // allowWhileInUsePermissionInFgs
- r.mAllowStartForeground, // fgsStartReasonCode
+ r.isFgsAllowedWIU(), // allowWhileInUsePermissionInFgs
+ r.getFgsAllowStart(), // fgsStartReasonCode
r.appInfo.targetSdkVersion,
r.mRecentCallingUid,
0, // callerTargetSdkVersion
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index e51fc0a..459c6ff 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -1713,6 +1713,11 @@
}
}
+ private boolean isScreenOnOrAnimatingLocked(ProcessStateRecord state) {
+ return mService.mWakefulness.get() == PowerManagerInternal.WAKEFULNESS_AWAKE
+ || state.isRunningRemoteAnimation();
+ }
+
@GuardedBy({"mService", "mProcLock"})
private boolean computeOomAdjLSP(ProcessRecord app, int cachedAdj,
ProcessRecord topApp, boolean doingAll, long now, boolean cycleReEval,
@@ -1794,8 +1799,7 @@
state.setSystemNoUi(false);
}
if (!state.isSystemNoUi()) {
- if (mService.mWakefulness.get() == PowerManagerInternal.WAKEFULNESS_AWAKE
- || state.isRunningRemoteAnimation()) {
+ if (isScreenOnOrAnimatingLocked(state)) {
// screen on or animating, promote UI
state.setCurProcState(ActivityManager.PROCESS_STATE_PERSISTENT_UI);
state.setCurrentSchedulingGroup(SCHED_GROUP_TOP_APP);
@@ -2218,7 +2222,7 @@
if (s.isForeground) {
final int fgsType = s.foregroundServiceType;
- if (s.mAllowWhileInUsePermissionInFgs) {
+ if (s.isFgsAllowedWIU()) {
capabilityFromFGS |=
(fgsType & FOREGROUND_SERVICE_TYPE_LOCATION)
!= 0 ? PROCESS_CAPABILITY_FOREGROUND_LOCATION : 0;
@@ -3281,8 +3285,10 @@
} else {
setThreadPriority(app.getPid(), THREAD_PRIORITY_TOP_APP_BOOST);
}
- initialSchedGroup = SCHED_GROUP_TOP_APP;
- initialProcState = PROCESS_STATE_TOP;
+ if (isScreenOnOrAnimatingLocked(state)) {
+ initialSchedGroup = SCHED_GROUP_TOP_APP;
+ initialProcState = PROCESS_STATE_TOP;
+ }
initialCapability = PROCESS_CAPABILITY_ALL;
initialCached = false;
} catch (Exception e) {
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index 50fe6d7..aabab61 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -21,9 +21,11 @@
import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
import static android.app.ProcessMemoryState.HOSTING_COMPONENT_TYPE_BOUND_SERVICE;
import static android.os.PowerExemptionManager.REASON_DENIED;
+import static android.os.PowerExemptionManager.reasonCodeToString;
import static android.os.Process.INVALID_UID;
import static com.android.internal.util.Preconditions.checkArgument;
+import static com.android.server.am.ActiveServices.TAG_SERVICE;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_FOREGROUND_SERVICE;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -172,11 +174,11 @@
private BackgroundStartPrivileges mBackgroundStartPrivilegesByStartMerged =
BackgroundStartPrivileges.NONE;
- // allow while-in-use permissions in foreground service or not.
+ // Reason code for allow while-in-use permissions in foreground service.
+ // If it's not DENIED, while-in-use permissions are allowed.
// while-in-use permissions in FGS started from background might be restricted.
- boolean mAllowWhileInUsePermissionInFgs;
@PowerExemptionManager.ReasonCode
- int mAllowWhileInUsePermissionInFgsReason = REASON_DENIED;
+ int mAllowWhileInUsePermissionInFgsReasonNoBinding = REASON_DENIED;
// A copy of mAllowWhileInUsePermissionInFgs's value when the service is entering FGS state.
boolean mAllowWhileInUsePermissionInFgsAtEntering;
@@ -205,15 +207,114 @@
// allow the service becomes foreground service? Service started from background may not be
// allowed to become a foreground service.
- @PowerExemptionManager.ReasonCode int mAllowStartForeground = REASON_DENIED;
+ @PowerExemptionManager.ReasonCode
+ int mAllowStartForegroundNoBinding = REASON_DENIED;
// A copy of mAllowStartForeground's value when the service is entering FGS state.
- @PowerExemptionManager.ReasonCode int mAllowStartForegroundAtEntering = REASON_DENIED;
+ @PowerExemptionManager.ReasonCode
+ int mAllowStartForegroundAtEntering = REASON_DENIED;
// Debug info why mAllowStartForeground is allowed or denied.
String mInfoAllowStartForeground;
// Debug info if mAllowStartForeground is allowed because of a temp-allowlist.
ActivityManagerService.FgsTempAllowListItem mInfoTempFgsAllowListReason;
// Is the same mInfoAllowStartForeground string has been logged before? Used for dedup.
boolean mLoggedInfoAllowStartForeground;
+
+ @PowerExemptionManager.ReasonCode
+ int mAllowWIUInBindService = REASON_DENIED;
+
+ @PowerExemptionManager.ReasonCode
+ int mAllowWIUByBindings = REASON_DENIED;
+
+ @PowerExemptionManager.ReasonCode
+ int mAllowStartInBindService = REASON_DENIED;
+
+ @PowerExemptionManager.ReasonCode
+ int mAllowStartByBindings = REASON_DENIED;
+
+ @PowerExemptionManager.ReasonCode
+ int getFgsAllowWIU() {
+ return mAllowWhileInUsePermissionInFgsReasonNoBinding != REASON_DENIED
+ ? mAllowWhileInUsePermissionInFgsReasonNoBinding
+ : mAllowWIUInBindService;
+ }
+
+ boolean isFgsAllowedWIU() {
+ return getFgsAllowWIU() != REASON_DENIED;
+ }
+
+ @PowerExemptionManager.ReasonCode
+ int getFgsAllowStart() {
+ return mAllowStartForegroundNoBinding != REASON_DENIED
+ ? mAllowStartForegroundNoBinding
+ : mAllowStartInBindService;
+ }
+
+ boolean isFgsAllowedStart() {
+ return getFgsAllowStart() != REASON_DENIED;
+ }
+
+ void clearFgsAllowWIU() {
+ mAllowWhileInUsePermissionInFgsReasonNoBinding = REASON_DENIED;
+ mAllowWIUInBindService = REASON_DENIED;
+ mAllowWIUByBindings = REASON_DENIED;
+ }
+
+ void clearFgsAllowStart() {
+ mAllowStartForegroundNoBinding = REASON_DENIED;
+ mAllowStartInBindService = REASON_DENIED;
+ mAllowStartByBindings = REASON_DENIED;
+ }
+
+ @PowerExemptionManager.ReasonCode
+ int reasonOr(@PowerExemptionManager.ReasonCode int first,
+ @PowerExemptionManager.ReasonCode int second) {
+ return first != REASON_DENIED ? first : second;
+ }
+
+ boolean allowedChanged(@PowerExemptionManager.ReasonCode int first,
+ @PowerExemptionManager.ReasonCode int second) {
+ return (first == REASON_DENIED) != (second == REASON_DENIED);
+ }
+
+ String changeMessage(@PowerExemptionManager.ReasonCode int first,
+ @PowerExemptionManager.ReasonCode int second) {
+ return reasonOr(first, second) == REASON_DENIED ? "DENIED"
+ : ("ALLOWED ("
+ + reasonCodeToString(first)
+ + "+"
+ + reasonCodeToString(second)
+ + ")");
+ }
+
+ void maybeLogFgsLogicChange() {
+ final int origWiu = reasonOr(mAllowWhileInUsePermissionInFgsReasonNoBinding,
+ mAllowWIUInBindService);
+ final int newWiu = reasonOr(mAllowWhileInUsePermissionInFgsReasonNoBinding,
+ mAllowWIUByBindings);
+ final int origStart = reasonOr(mAllowStartForegroundNoBinding, mAllowStartInBindService);
+ final int newStart = reasonOr(mAllowStartForegroundNoBinding, mAllowStartByBindings);
+
+ final boolean wiuChanged = allowedChanged(origWiu, newWiu);
+ final boolean startChanged = allowedChanged(origStart, newStart);
+
+ if (!wiuChanged && !startChanged) {
+ return;
+ }
+ final String message = "FGS logic changed:"
+ + (wiuChanged ? " [WIU changed]" : "")
+ + (startChanged ? " [BFSL changed]" : "")
+ + " OW:" // Orig-WIU
+ + changeMessage(mAllowWhileInUsePermissionInFgsReasonNoBinding,
+ mAllowWIUInBindService)
+ + " NW:" // New-WIU
+ + changeMessage(mAllowWhileInUsePermissionInFgsReasonNoBinding, mAllowWIUByBindings)
+ + " OS:" // Orig-start
+ + changeMessage(mAllowStartForegroundNoBinding, mAllowStartInBindService)
+ + " NS:" // New-start
+ + changeMessage(mAllowStartForegroundNoBinding, mAllowStartByBindings);
+ Slog.wtf(TAG_SERVICE, message);
+ }
+
// The number of times Service.startForeground() is called, after this service record is
// created. (i.e. due to "bound" or "start".) It never decreases, even when stopForeground()
// is called.
@@ -502,7 +603,7 @@
ProtoUtils.toDuration(proto, ServiceRecordProto.RESTART_TIME, restartTime, now);
proto.write(ServiceRecordProto.CREATED_FROM_FG, createdFromFg);
proto.write(ServiceRecordProto.ALLOW_WHILE_IN_USE_PERMISSION_IN_FGS,
- mAllowWhileInUsePermissionInFgs);
+ isFgsAllowedWIU());
if (startRequested || delayedStop || lastStartId != 0) {
long startToken = proto.start(ServiceRecordProto.START);
@@ -618,7 +719,13 @@
pw.println(mBackgroundStartPrivilegesByStartMerged);
}
pw.print(prefix); pw.print("mAllowWhileInUsePermissionInFgsReason=");
- pw.println(PowerExemptionManager.reasonCodeToString(mAllowWhileInUsePermissionInFgsReason));
+ pw.println(PowerExemptionManager.reasonCodeToString(
+ mAllowWhileInUsePermissionInFgsReasonNoBinding));
+
+ pw.print(prefix); pw.print("mAllowWIUInBindService=");
+ pw.println(PowerExemptionManager.reasonCodeToString(mAllowWIUInBindService));
+ pw.print(prefix); pw.print("mAllowWIUByBindings=");
+ pw.println(PowerExemptionManager.reasonCodeToString(mAllowWIUByBindings));
pw.print(prefix); pw.print("allowUiJobScheduling="); pw.println(mAllowUiJobScheduling);
pw.print(prefix); pw.print("recentCallingPackage=");
@@ -626,7 +733,12 @@
pw.print(prefix); pw.print("recentCallingUid=");
pw.println(mRecentCallingUid);
pw.print(prefix); pw.print("allowStartForeground=");
- pw.println(PowerExemptionManager.reasonCodeToString(mAllowStartForeground));
+ pw.println(PowerExemptionManager.reasonCodeToString(mAllowStartForegroundNoBinding));
+ pw.print(prefix); pw.print("mAllowStartInBindService=");
+ pw.println(PowerExemptionManager.reasonCodeToString(mAllowStartInBindService));
+ pw.print(prefix); pw.print("mAllowStartByBindings=");
+ pw.println(PowerExemptionManager.reasonCodeToString(mAllowStartByBindings));
+
pw.print(prefix); pw.print("startForegroundCount=");
pw.println(mStartForegroundCount);
pw.print(prefix); pw.print("infoAllowStartForeground=");
diff --git a/services/core/java/com/android/server/am/TEST_MAPPING b/services/core/java/com/android/server/am/TEST_MAPPING
index 97d4879..0af9b2b 100644
--- a/services/core/java/com/android/server/am/TEST_MAPPING
+++ b/services/core/java/com/android/server/am/TEST_MAPPING
@@ -145,12 +145,7 @@
]
},
{
- "name": "CtsAppSecurityHostTestCases",
- "options": [
- {
- "include-filter": "android.appsecurity.cts.AppDataIsolationTests"
- }
- ]
+ "name": "CtsAppDataIsolationHostTestCases"
},
{
"name": "CtsAppTestCases",
diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java
index ca15dd7..c6d6122 100644
--- a/services/core/java/com/android/server/app/GameManagerService.java
+++ b/services/core/java/com/android/server/app/GameManagerService.java
@@ -40,6 +40,7 @@
import android.app.GameState;
import android.app.IGameManagerService;
import android.app.IGameModeListener;
+import android.app.IGameStateListener;
import android.app.StatsManager;
import android.app.UidObserver;
import android.content.BroadcastReceiver;
@@ -148,6 +149,7 @@
private final Object mLock = new Object();
private final Object mDeviceConfigLock = new Object();
private final Object mGameModeListenerLock = new Object();
+ private final Object mGameStateListenerLock = new Object();
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
final Handler mHandler;
private final PackageManager mPackageManager;
@@ -164,6 +166,8 @@
// listener to caller uid map
@GuardedBy("mGameModeListenerLock")
private final ArrayMap<IGameModeListener, Integer> mGameModeListeners = new ArrayMap<>();
+ @GuardedBy("mGameStateListenerLock")
+ private final ArrayMap<IGameStateListener, Integer> mGameStateListeners = new ArrayMap<>();
@Nullable
private final GameServiceController mGameServiceController;
private final Object mUidObserverLock = new Object();
@@ -352,6 +356,16 @@
loadingBoostDuration);
}
}
+ synchronized (mGameStateListenerLock) {
+ for (IGameStateListener listener : mGameStateListeners.keySet()) {
+ try {
+ listener.onGameStateChanged(packageName, gameState, userId);
+ } catch (RemoteException ex) {
+ Slog.w(TAG, "Cannot notify game state change for listener added by "
+ + mGameStateListeners.get(listener));
+ }
+ }
+ }
break;
}
case CANCEL_GAME_LOADING_MODE: {
@@ -1474,6 +1488,43 @@
}
/**
+ * Adds a game state listener.
+ */
+ @Override
+ public void addGameStateListener(@NonNull IGameStateListener listener) {
+ try {
+ final IBinder listenerBinder = listener.asBinder();
+ listenerBinder.linkToDeath(new DeathRecipient() {
+ @Override public void binderDied() {
+ removeGameStateListenerUnchecked(listener);
+ listenerBinder.unlinkToDeath(this, 0 /*flags*/);
+ }
+ }, 0 /*flags*/);
+ synchronized (mGameStateListenerLock) {
+ mGameStateListeners.put(listener, Binder.getCallingUid());
+ }
+ } catch (RemoteException ex) {
+ Slog.e(TAG,
+ "Failed to link death recipient for IGameStateListener from caller "
+ + Binder.getCallingUid() + ", abandoned its listener registration", ex);
+ }
+ }
+
+ /**
+ * Removes a game state listener.
+ */
+ @Override
+ public void removeGameStateListener(@NonNull IGameStateListener listener) {
+ removeGameStateListenerUnchecked(listener);
+ }
+
+ private void removeGameStateListenerUnchecked(IGameStateListener listener) {
+ synchronized (mGameStateListenerLock) {
+ mGameStateListeners.remove(listener);
+ }
+ }
+
+ /**
* Notified when boot is completed.
*/
@VisibleForTesting
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 1dff62e..4ee4c30 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -2124,11 +2124,7 @@
try {
pvr = verifyAndGetBypass(uid, packageName, null);
} catch (SecurityException e) {
- if (Process.isIsolated(uid)) {
- Slog.e(TAG, "Cannot setMode: isolated process");
- } else {
- Slog.e(TAG, "Cannot setMode", e);
- }
+ logVerifyAndGetBypassFailure(uid, e, "setMode");
return;
}
@@ -2580,11 +2576,7 @@
try {
pvr = verifyAndGetBypass(uid, packageName, null);
} catch (SecurityException e) {
- if (Process.isIsolated(uid)) {
- Slog.e(TAG, "Cannot checkOperation: isolated process");
- } else {
- Slog.e(TAG, "Cannot checkOperation", e);
- }
+ logVerifyAndGetBypassFailure(uid, e, "checkOperation");
return AppOpsManager.opToDefaultMode(code);
}
@@ -2796,11 +2788,7 @@
attributionTag = null;
}
} catch (SecurityException e) {
- if (Process.isIsolated(uid)) {
- Slog.e(TAG, "Cannot noteOperation: isolated process");
- } else {
- Slog.e(TAG, "Cannot noteOperation", e);
- }
+ logVerifyAndGetBypassFailure(uid, e, "noteOperation");
return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
packageName);
}
@@ -3219,7 +3207,7 @@
return startOperationUnchecked(clientId, code, uid, packageName, attributionTag,
Process.INVALID_UID, null, null, OP_FLAG_SELF, startIfModeDefault,
shouldCollectAsyncNotedOp, message, shouldCollectMessage, attributionFlags,
- attributionChainId, /*dryRun*/ false);
+ attributionChainId);
}
@Override
@@ -3287,11 +3275,10 @@
if (!skipProxyOperation) {
// Test if the proxied operation will succeed before starting the proxy operation
- final SyncNotedAppOp testProxiedOp = startOperationUnchecked(clientId, code,
- proxiedUid, resolvedProxiedPackageName, proxiedAttributionTag, proxyUid,
- resolvedProxyPackageName, proxyAttributionTag, proxiedFlags, startIfModeDefault,
- shouldCollectAsyncNotedOp, message, shouldCollectMessage,
- proxiedAttributionFlags, attributionChainId, /*dryRun*/ true);
+ final SyncNotedAppOp testProxiedOp = startOperationDryRun(code,
+ proxiedUid, resolvedProxiedPackageName, proxiedAttributionTag,
+ resolvedProxyPackageName, proxiedFlags, startIfModeDefault);
+
if (!shouldStartForMode(testProxiedOp.getOpMode(), startIfModeDefault)) {
return testProxiedOp;
}
@@ -3302,8 +3289,7 @@
final SyncNotedAppOp proxyAppOp = startOperationUnchecked(clientId, code, proxyUid,
resolvedProxyPackageName, proxyAttributionTag, Process.INVALID_UID, null, null,
proxyFlags, startIfModeDefault, !isProxyTrusted, "proxy " + message,
- shouldCollectMessage, proxyAttributionFlags, attributionChainId,
- /*dryRun*/ false);
+ shouldCollectMessage, proxyAttributionFlags, attributionChainId);
if (!shouldStartForMode(proxyAppOp.getOpMode(), startIfModeDefault)) {
return proxyAppOp;
}
@@ -3312,8 +3298,7 @@
return startOperationUnchecked(clientId, code, proxiedUid, resolvedProxiedPackageName,
proxiedAttributionTag, proxyUid, resolvedProxyPackageName, proxyAttributionTag,
proxiedFlags, startIfModeDefault, shouldCollectAsyncNotedOp, message,
- shouldCollectMessage, proxiedAttributionFlags, attributionChainId,
- /*dryRun*/ false);
+ shouldCollectMessage, proxiedAttributionFlags, attributionChainId);
}
private boolean shouldStartForMode(int mode, boolean startIfModeDefault) {
@@ -3325,7 +3310,7 @@
String proxyPackageName, @Nullable String proxyAttributionTag, @OpFlags int flags,
boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, @Nullable String message,
boolean shouldCollectMessage, @AttributionFlags int attributionFlags,
- int attributionChainId, boolean dryRun) {
+ int attributionChainId) {
PackageVerificationResult pvr;
try {
pvr = verifyAndGetBypass(uid, packageName, attributionTag, proxyPackageName);
@@ -3333,11 +3318,7 @@
attributionTag = null;
}
} catch (SecurityException e) {
- if (Process.isIsolated(uid)) {
- Slog.e(TAG, "Cannot startOperation: isolated process");
- } else {
- Slog.e(TAG, "Cannot startOperation", e);
- }
+ logVerifyAndGetBypassFailure(uid, e, "startOperation");
return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
packageName);
}
@@ -3348,11 +3329,9 @@
final Ops ops = getOpsLocked(uid, packageName, attributionTag,
pvr.isAttributionTagValid, pvr.bypass, /* edit */ true);
if (ops == null) {
- if (!dryRun) {
- scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag,
- flags, AppOpsManager.MODE_IGNORED, startType, attributionFlags,
- attributionChainId);
- }
+ scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag,
+ flags, AppOpsManager.MODE_IGNORED, startType, attributionFlags,
+ attributionChainId);
if (DEBUG) Slog.d(TAG, "startOperation: no op for code " + code + " uid " + uid
+ " package " + packageName + " flags: "
+ AppOpsManager.flagsToString(flags));
@@ -3375,11 +3354,9 @@
+ switchCode + " (" + code + ") uid " + uid + " package "
+ packageName + " flags: " + AppOpsManager.flagsToString(flags));
}
- if (!dryRun) {
- attributedOp.rejected(uidState.getState(), flags);
- scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag,
- flags, uidMode, startType, attributionFlags, attributionChainId);
- }
+ attributedOp.rejected(uidState.getState(), flags);
+ scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag,
+ flags, uidMode, startType, attributionFlags, attributionChainId);
return new SyncNotedAppOp(uidMode, code, attributionTag, packageName);
}
} else {
@@ -3391,39 +3368,35 @@
if (DEBUG) Slog.d(TAG, "startOperation: reject #" + mode + " for code "
+ switchCode + " (" + code + ") uid " + uid + " package "
+ packageName + " flags: " + AppOpsManager.flagsToString(flags));
- if (!dryRun) {
- attributedOp.rejected(uidState.getState(), flags);
- scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag,
- flags, mode, startType, attributionFlags, attributionChainId);
- }
+ attributedOp.rejected(uidState.getState(), flags);
+ scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag,
+ flags, mode, startType, attributionFlags, attributionChainId);
return new SyncNotedAppOp(mode, code, attributionTag, packageName);
}
}
if (DEBUG) Slog.d(TAG, "startOperation: allowing code " + code + " uid " + uid
+ " package " + packageName + " restricted: " + isRestricted
+ " flags: " + AppOpsManager.flagsToString(flags));
- if (!dryRun) {
- try {
- if (isRestricted) {
- attributedOp.createPaused(clientId, proxyUid, proxyPackageName,
- proxyAttributionTag, uidState.getState(), flags,
- attributionFlags, attributionChainId);
- } else {
- attributedOp.started(clientId, proxyUid, proxyPackageName,
- proxyAttributionTag, uidState.getState(), flags,
- attributionFlags, attributionChainId);
- startType = START_TYPE_STARTED;
- }
- } catch (RemoteException e) {
- throw new RuntimeException(e);
+ try {
+ if (isRestricted) {
+ attributedOp.createPaused(clientId, proxyUid, proxyPackageName,
+ proxyAttributionTag, uidState.getState(), flags,
+ attributionFlags, attributionChainId);
+ } else {
+ attributedOp.started(clientId, proxyUid, proxyPackageName,
+ proxyAttributionTag, uidState.getState(), flags,
+ attributionFlags, attributionChainId);
+ startType = START_TYPE_STARTED;
}
- scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag, flags,
- isRestricted ? MODE_IGNORED : MODE_ALLOWED, startType, attributionFlags,
- attributionChainId);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
}
+ scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag, flags,
+ isRestricted ? MODE_IGNORED : MODE_ALLOWED, startType, attributionFlags,
+ attributionChainId);
}
- if (shouldCollectAsyncNotedOp && !dryRun && !isRestricted) {
+ if (shouldCollectAsyncNotedOp && !isRestricted) {
collectAsyncNotedOp(uid, packageName, code, attributionTag, AppOpsManager.OP_FLAG_SELF,
message, shouldCollectMessage);
}
@@ -3432,6 +3405,88 @@
packageName);
}
+ /**
+ * Performs a dry run of the start operation i.e. determines the result of the start operation
+ * without actually updating the op state to be started.
+ *
+ * <p>This is used for proxy operations; before starting the op as the proxy, we must check that
+ * the proxied app can successfully start the operation.
+ */
+ private SyncNotedAppOp startOperationDryRun(int code, int uid,
+ @NonNull String packageName, @Nullable String attributionTag,
+ String proxyPackageName, @OpFlags int flags,
+ boolean startIfModeDefault) {
+ PackageVerificationResult pvr;
+ try {
+ pvr = verifyAndGetBypass(uid, packageName, attributionTag, proxyPackageName);
+ if (!pvr.isAttributionTagValid) {
+ attributionTag = null;
+ }
+ } catch (SecurityException e) {
+ if (Process.isIsolated(uid)) {
+ Slog.e(TAG, "Cannot startOperation: isolated process");
+ } else {
+ Slog.e(TAG, "Cannot startOperation", e);
+ }
+ return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
+ packageName);
+ }
+
+ boolean isRestricted = false;
+ synchronized (this) {
+ final Ops ops = getOpsLocked(uid, packageName, attributionTag,
+ pvr.isAttributionTagValid, pvr.bypass, /* edit */ true);
+ if (ops == null) {
+ if (DEBUG) {
+ Slog.d(TAG, "startOperation: no op for code " + code + " uid " + uid
+ + " package " + packageName + " flags: "
+ + AppOpsManager.flagsToString(flags));
+ }
+ return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
+ packageName);
+ }
+ final Op op = getOpLocked(ops, code, uid, true);
+ final UidState uidState = ops.uidState;
+ isRestricted = isOpRestrictedLocked(uid, code, packageName, attributionTag, pvr.bypass,
+ false);
+ final int switchCode = AppOpsManager.opToSwitch(code);
+ // If there is a non-default mode per UID policy (we set UID op mode only if
+ // non-default) it takes over, otherwise use the per package policy.
+ if (uidState.getUidMode(switchCode) != AppOpsManager.opToDefaultMode(switchCode)) {
+ final int uidMode = uidState.evalMode(code, uidState.getUidMode(switchCode));
+ if (!shouldStartForMode(uidMode, startIfModeDefault)) {
+ if (DEBUG) {
+ Slog.d(TAG, "startOperation: uid reject #" + uidMode + " for code "
+ + switchCode + " (" + code + ") uid " + uid + " package "
+ + packageName + " flags: " + AppOpsManager.flagsToString(flags));
+ }
+ return new SyncNotedAppOp(uidMode, code, attributionTag, packageName);
+ }
+ } else {
+ final Op switchOp = switchCode != code ? getOpLocked(ops, switchCode, uid, true)
+ : op;
+ final int mode = switchOp.uidState.evalMode(switchOp.op, switchOp.getMode());
+ if (mode != AppOpsManager.MODE_ALLOWED
+ && (!startIfModeDefault || mode != MODE_DEFAULT)) {
+ if (DEBUG) {
+ Slog.d(TAG, "startOperation: reject #" + mode + " for code "
+ + switchCode + " (" + code + ") uid " + uid + " package "
+ + packageName + " flags: " + AppOpsManager.flagsToString(flags));
+ }
+ return new SyncNotedAppOp(mode, code, attributionTag, packageName);
+ }
+ }
+ if (DEBUG) {
+ Slog.d(TAG, "startOperation: allowing code " + code + " uid " + uid
+ + " package " + packageName + " restricted: " + isRestricted
+ + " flags: " + AppOpsManager.flagsToString(flags));
+ }
+ }
+
+ return new SyncNotedAppOp(isRestricted ? MODE_IGNORED : MODE_ALLOWED, code, attributionTag,
+ packageName);
+ }
+
@Override
public void finishOperation(IBinder clientId, int code, int uid, String packageName,
String attributionTag) {
@@ -3513,11 +3568,7 @@
attributionTag = null;
}
} catch (SecurityException e) {
- if (Process.isIsolated(uid)) {
- Slog.e(TAG, "Cannot finishOperation: isolated process");
- } else {
- Slog.e(TAG, "Cannot finishOperation", e);
- }
+ logVerifyAndGetBypassFailure(uid, e, "finishOperation");
return;
}
@@ -4073,6 +4124,17 @@
return false;
}
+ private void logVerifyAndGetBypassFailure(int uid, @NonNull SecurityException e,
+ @NonNull String methodName) {
+ if (Process.isIsolated(uid)) {
+ Slog.e(TAG, "Cannot " + methodName + ": isolated UID");
+ } else if (UserHandle.getAppId(uid) < Process.FIRST_APPLICATION_UID) {
+ Slog.e(TAG, "Cannot " + methodName + ": non-application UID " + uid);
+ } else {
+ Slog.e(TAG, "Cannot " + methodName, e);
+ }
+ }
+
/**
* Get (and potentially create) ops.
*
diff --git a/services/core/java/com/android/server/biometrics/BiometricSensorPrivacy.java b/services/core/java/com/android/server/biometrics/BiometricCameraManager.java
similarity index 71%
rename from services/core/java/com/android/server/biometrics/BiometricSensorPrivacy.java
rename to services/core/java/com/android/server/biometrics/BiometricCameraManager.java
index 6727fbc..058ea6b 100644
--- a/services/core/java/com/android/server/biometrics/BiometricSensorPrivacy.java
+++ b/services/core/java/com/android/server/biometrics/BiometricCameraManager.java
@@ -17,9 +17,16 @@
package com.android.server.biometrics;
/**
- * Interface for biometric operations to get camera privacy state.
+ * Interface for biometrics to get camera status.
*/
-public interface BiometricSensorPrivacy {
- /* Returns true if privacy is enabled and camera access is disabled. */
+public interface BiometricCameraManager {
+ /**
+ * Returns true if any camera is in use.
+ */
+ boolean isAnyCameraUnavailable();
+
+ /**
+ * Returns true if privacy is enabled and camera access is disabled.
+ */
boolean isCameraPrivacyEnabled();
}
diff --git a/services/core/java/com/android/server/biometrics/BiometricCameraManagerImpl.java b/services/core/java/com/android/server/biometrics/BiometricCameraManagerImpl.java
new file mode 100644
index 0000000..000ee54
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/BiometricCameraManagerImpl.java
@@ -0,0 +1,68 @@
+/*
+ * 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.biometrics;
+
+import static android.hardware.SensorPrivacyManager.Sensors.CAMERA;
+
+import android.annotation.NonNull;
+import android.hardware.SensorPrivacyManager;
+import android.hardware.camera2.CameraManager;
+
+import java.util.concurrent.ConcurrentHashMap;
+
+public class BiometricCameraManagerImpl implements BiometricCameraManager {
+
+ private final CameraManager mCameraManager;
+ private final SensorPrivacyManager mSensorPrivacyManager;
+ private final ConcurrentHashMap<String, Boolean> mIsCameraAvailable = new ConcurrentHashMap<>();
+
+ private final CameraManager.AvailabilityCallback mCameraAvailabilityCallback =
+ new CameraManager.AvailabilityCallback() {
+ @Override
+ public void onCameraAvailable(@NonNull String cameraId) {
+ mIsCameraAvailable.put(cameraId, true);
+ }
+
+ @Override
+ public void onCameraUnavailable(@NonNull String cameraId) {
+ mIsCameraAvailable.put(cameraId, false);
+ }
+ };
+
+ public BiometricCameraManagerImpl(@NonNull CameraManager cameraManager,
+ @NonNull SensorPrivacyManager sensorPrivacyManager) {
+ mCameraManager = cameraManager;
+ mSensorPrivacyManager = sensorPrivacyManager;
+ mCameraManager.registerAvailabilityCallback(mCameraAvailabilityCallback, null);
+ }
+
+ @Override
+ public boolean isAnyCameraUnavailable() {
+ for (String cameraId : mIsCameraAvailable.keySet()) {
+ if (!mIsCameraAvailable.get(cameraId)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean isCameraPrivacyEnabled() {
+ return mSensorPrivacyManager != null && mSensorPrivacyManager
+ .isSensorPrivacyEnabled(SensorPrivacyManager.TOGGLE_TYPE_SOFTWARE, CAMERA);
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/BiometricSensorPrivacyImpl.java b/services/core/java/com/android/server/biometrics/BiometricSensorPrivacyImpl.java
deleted file mode 100644
index b6701da..0000000
--- a/services/core/java/com/android/server/biometrics/BiometricSensorPrivacyImpl.java
+++ /dev/null
@@ -1,37 +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.biometrics;
-
-import static android.hardware.SensorPrivacyManager.Sensors.CAMERA;
-
-import android.annotation.Nullable;
-import android.hardware.SensorPrivacyManager;
-
-public class BiometricSensorPrivacyImpl implements
- BiometricSensorPrivacy {
- private final SensorPrivacyManager mSensorPrivacyManager;
-
- public BiometricSensorPrivacyImpl(@Nullable SensorPrivacyManager sensorPrivacyManager) {
- mSensorPrivacyManager = sensorPrivacyManager;
- }
-
- @Override
- public boolean isCameraPrivacyEnabled() {
- return mSensorPrivacyManager != null && mSensorPrivacyManager
- .isSensorPrivacyEnabled(SensorPrivacyManager.TOGGLE_TYPE_SOFTWARE, CAMERA);
- }
-}
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index 1fa97a3..279aaf9 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -17,6 +17,8 @@
package com.android.server.biometrics;
import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
import static android.hardware.biometrics.BiometricManager.Authenticators;
import static com.android.server.biometrics.BiometricServiceStateProto.STATE_AUTH_IDLE;
@@ -48,6 +50,7 @@
import android.hardware.biometrics.ITestSessionCallback;
import android.hardware.biometrics.PromptInfo;
import android.hardware.biometrics.SensorPropertiesInternal;
+import android.hardware.camera2.CameraManager;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.net.Uri;
@@ -125,7 +128,7 @@
AuthSession mAuthSession;
private final Handler mHandler = new Handler(Looper.getMainLooper());
- private final BiometricSensorPrivacy mBiometricSensorPrivacy;
+ private final BiometricCameraManager mBiometricCameraManager;
/**
* Tracks authenticatorId invalidation. For more details, see
@@ -368,7 +371,7 @@
public boolean getConfirmationAlwaysRequired(@BiometricAuthenticator.Modality int modality,
int userId) {
switch (modality) {
- case BiometricAuthenticator.TYPE_FACE:
+ case TYPE_FACE:
if (!mFaceAlwaysRequireConfirmation.containsKey(userId)) {
onChange(true /* selfChange */,
FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION,
@@ -566,22 +569,6 @@
Utils.combineAuthenticatorBundles(promptInfo);
- // Set the default title if necessary.
- if (promptInfo.isUseDefaultTitle()) {
- if (TextUtils.isEmpty(promptInfo.getTitle())) {
- promptInfo.setTitle(getContext()
- .getString(R.string.biometric_dialog_default_title));
- }
- }
-
- // Set the default subtitle if necessary.
- if (promptInfo.isUseDefaultSubtitle()) {
- if (TextUtils.isEmpty(promptInfo.getSubtitle())) {
- promptInfo.setSubtitle(getContext()
- .getString(R.string.biometric_dialog_default_subtitle));
- }
- }
-
final long requestId = mRequestCounter.get();
mHandler.post(() -> handleAuthenticate(
token, requestId, operationId, userId, receiver, opPackageName, promptInfo));
@@ -936,7 +923,7 @@
return PreAuthInfo.create(mTrustManager, mDevicePolicyManager, mSettingObserver, mSensors,
userId, promptInfo, opPackageName, false /* checkDevicePolicyManager */,
- getContext(), mBiometricSensorPrivacy);
+ getContext(), mBiometricCameraManager);
}
/**
@@ -1030,9 +1017,9 @@
return context.getSystemService(UserManager.class);
}
- public BiometricSensorPrivacy getBiometricSensorPrivacy(Context context) {
- return new BiometricSensorPrivacyImpl(context.getSystemService(
- SensorPrivacyManager.class));
+ public BiometricCameraManager getBiometricCameraManager(Context context) {
+ return new BiometricCameraManagerImpl(context.getSystemService(CameraManager.class),
+ context.getSystemService(SensorPrivacyManager.class));
}
}
@@ -1062,7 +1049,7 @@
mRequestCounter = mInjector.getRequestGenerator();
mBiometricContext = injector.getBiometricContext(context);
mUserManager = injector.getUserManager(context);
- mBiometricSensorPrivacy = injector.getBiometricSensorPrivacy(context);
+ mBiometricCameraManager = injector.getBiometricCameraManager(context);
try {
injector.getActivityManagerService().registerUserSwitchObserver(
@@ -1299,7 +1286,34 @@
final PreAuthInfo preAuthInfo = PreAuthInfo.create(mTrustManager,
mDevicePolicyManager, mSettingObserver, mSensors, userId, promptInfo,
opPackageName, promptInfo.isDisallowBiometricsIfPolicyExists(),
- getContext(), mBiometricSensorPrivacy);
+ getContext(), mBiometricCameraManager);
+
+ // Set the default title if necessary.
+ if (promptInfo.isUseDefaultTitle()) {
+ if (TextUtils.isEmpty(promptInfo.getTitle())) {
+ promptInfo.setTitle(getContext()
+ .getString(R.string.biometric_dialog_default_title));
+ }
+ }
+
+ final int eligible = preAuthInfo.getEligibleModalities();
+ final boolean hasEligibleFingerprintSensor =
+ (eligible & TYPE_FINGERPRINT) == TYPE_FINGERPRINT;
+ final boolean hasEligibleFaceSensor = (eligible & TYPE_FACE) == TYPE_FACE;
+
+ // Set the subtitle according to the modality.
+ if (promptInfo.isUseDefaultSubtitle()) {
+ if (hasEligibleFingerprintSensor && hasEligibleFaceSensor) {
+ promptInfo.setSubtitle(getContext()
+ .getString(R.string.biometric_dialog_default_subtitle));
+ } else if (hasEligibleFingerprintSensor) {
+ promptInfo.setSubtitle(getContext()
+ .getString(R.string.biometric_dialog_fingerprint_subtitle));
+ } else if (hasEligibleFaceSensor) {
+ promptInfo.setSubtitle(getContext()
+ .getString(R.string.biometric_dialog_face_subtitle));
+ }
+ }
final Pair<Integer, Integer> preAuthStatus = preAuthInfo.getPreAuthenticateStatus();
diff --git a/services/core/java/com/android/server/biometrics/PreAuthInfo.java b/services/core/java/com/android/server/biometrics/PreAuthInfo.java
index e6f25cb..b1740a7 100644
--- a/services/core/java/com/android/server/biometrics/PreAuthInfo.java
+++ b/services/core/java/com/android/server/biometrics/PreAuthInfo.java
@@ -72,16 +72,16 @@
final Context context;
private final boolean mBiometricRequested;
private final int mBiometricStrengthRequested;
- private final BiometricSensorPrivacy mBiometricSensorPrivacy;
+ private final BiometricCameraManager mBiometricCameraManager;
private PreAuthInfo(boolean biometricRequested, int biometricStrengthRequested,
boolean credentialRequested, List<BiometricSensor> eligibleSensors,
List<Pair<BiometricSensor, Integer>> ineligibleSensors, boolean credentialAvailable,
boolean confirmationRequested, boolean ignoreEnrollmentState, int userId,
- Context context, BiometricSensorPrivacy biometricSensorPrivacy) {
+ Context context, BiometricCameraManager biometricCameraManager) {
mBiometricRequested = biometricRequested;
mBiometricStrengthRequested = biometricStrengthRequested;
- mBiometricSensorPrivacy = biometricSensorPrivacy;
+ mBiometricCameraManager = biometricCameraManager;
this.credentialRequested = credentialRequested;
this.eligibleSensors = eligibleSensors;
@@ -99,7 +99,7 @@
List<BiometricSensor> sensors,
int userId, PromptInfo promptInfo, String opPackageName,
boolean checkDevicePolicyManager, Context context,
- BiometricSensorPrivacy biometricSensorPrivacy)
+ BiometricCameraManager biometricCameraManager)
throws RemoteException {
final boolean confirmationRequested = promptInfo.isConfirmationRequested();
@@ -127,7 +127,7 @@
checkDevicePolicyManager, requestedStrength,
promptInfo.getAllowedSensorIds(),
promptInfo.isIgnoreEnrollmentState(),
- biometricSensorPrivacy);
+ biometricCameraManager);
Slog.d(TAG, "Package: " + opPackageName
+ " Sensor ID: " + sensor.id
@@ -151,7 +151,7 @@
return new PreAuthInfo(biometricRequested, requestedStrength, credentialRequested,
eligibleSensors, ineligibleSensors, credentialAvailable, confirmationRequested,
- promptInfo.isIgnoreEnrollmentState(), userId, context, biometricSensorPrivacy);
+ promptInfo.isIgnoreEnrollmentState(), userId, context, biometricCameraManager);
}
/**
@@ -168,12 +168,16 @@
BiometricSensor sensor, int userId, String opPackageName,
boolean checkDevicePolicyManager, int requestedStrength,
@NonNull List<Integer> requestedSensorIds,
- boolean ignoreEnrollmentState, BiometricSensorPrivacy biometricSensorPrivacy) {
+ boolean ignoreEnrollmentState, BiometricCameraManager biometricCameraManager) {
if (!requestedSensorIds.isEmpty() && !requestedSensorIds.contains(sensor.id)) {
return BIOMETRIC_NO_HARDWARE;
}
+ if (sensor.modality == TYPE_FACE && biometricCameraManager.isAnyCameraUnavailable()) {
+ return BIOMETRIC_HARDWARE_NOT_DETECTED;
+ }
+
final boolean wasStrongEnough =
Utils.isAtLeastStrength(sensor.oemStrength, requestedStrength);
final boolean isStrongEnough =
@@ -195,8 +199,8 @@
return BIOMETRIC_NOT_ENROLLED;
}
- if (biometricSensorPrivacy != null && sensor.modality == TYPE_FACE) {
- if (biometricSensorPrivacy.isCameraPrivacyEnabled()) {
+ if (biometricCameraManager != null && sensor.modality == TYPE_FACE) {
+ if (biometricCameraManager.isCameraPrivacyEnabled()) {
//Camera privacy is enabled as the access is disabled
return BIOMETRIC_SENSOR_PRIVACY_ENABLED;
}
@@ -307,8 +311,8 @@
@BiometricAuthenticator.Modality int modality = TYPE_NONE;
boolean cameraPrivacyEnabled = false;
- if (mBiometricSensorPrivacy != null) {
- cameraPrivacyEnabled = mBiometricSensorPrivacy.isCameraPrivacyEnabled();
+ if (mBiometricCameraManager != null) {
+ cameraPrivacyEnabled = mBiometricCameraManager.isCameraPrivacyEnabled();
}
if (mBiometricRequested && credentialRequested) {
diff --git a/services/core/java/com/android/server/biometrics/Utils.java b/services/core/java/com/android/server/biometrics/Utils.java
index 06417d7..f51b62d 100644
--- a/services/core/java/com/android/server/biometrics/Utils.java
+++ b/services/core/java/com/android/server/biometrics/Utils.java
@@ -66,7 +66,6 @@
import com.android.internal.widget.LockPatternUtils;
import com.android.server.biometrics.sensors.BaseClientMonitor;
-import java.util.ArrayList;
import java.util.List;
public class Utils {
@@ -98,33 +97,6 @@
}
/**
- * Get the enabled HAL instances. If virtual is enabled and available it will be returned as
- * the only instance, otherwise all other instances will be returned.
- *
- * @param context system context
- * @param declaredInstances known instances
- * @return filtered list of enabled instances
- */
- @NonNull
- public static List<String> filterAvailableHalInstances(@NonNull Context context,
- @NonNull List<String> declaredInstances) {
- if (declaredInstances.size() <= 1) {
- return declaredInstances;
- }
-
- final int virtualAt = declaredInstances.indexOf("virtual");
- if (isVirtualEnabled(context) && virtualAt != -1) {
- return List.of(declaredInstances.get(virtualAt));
- }
-
- declaredInstances = new ArrayList<>(declaredInstances);
- if (virtualAt != -1) {
- declaredInstances.remove(virtualAt);
- }
- return declaredInstances;
- }
-
- /**
* Combines {@link PromptInfo#setDeviceCredentialAllowed(boolean)} with
* {@link PromptInfo#setAuthenticators(int)}, as the former is not flexible enough.
*/
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
index 28cb7d9..7cc6940 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
@@ -871,19 +871,22 @@
super.registerAuthenticators_enforcePermission();
mRegistry.registerAll(() -> {
- final List<ServiceProvider> providers = new ArrayList<>();
- providers.addAll(getHidlProviders(hidlSensors));
List<String> aidlSensors = new ArrayList<>();
final String[] instances = mAidlInstanceNameSupplier.get();
if (instances != null) {
aidlSensors.addAll(Lists.newArrayList(instances));
}
- providers.addAll(getAidlProviders(
- Utils.filterAvailableHalInstances(getContext(), aidlSensors)));
+
+ final Pair<List<FingerprintSensorPropertiesInternal>, List<String>>
+ filteredInstances = filterAvailableHalInstances(hidlSensors, aidlSensors);
+
+ final List<ServiceProvider> providers = new ArrayList<>();
+ providers.addAll(getHidlProviders(filteredInstances.first));
+ providers.addAll(getAidlProviders(filteredInstances.second));
+
return providers;
});
}
-
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override
public void addAuthenticatorsRegisteredCallback(
@@ -1038,6 +1041,33 @@
});
}
+ private Pair<List<FingerprintSensorPropertiesInternal>, List<String>>
+ filterAvailableHalInstances(
+ @NonNull List<FingerprintSensorPropertiesInternal> hidlInstances,
+ @NonNull List<String> aidlInstances) {
+ if ((hidlInstances.size() + aidlInstances.size()) <= 1) {
+ return new Pair(hidlInstances, aidlInstances);
+ }
+
+ final int virtualAt = aidlInstances.indexOf("virtual");
+ if (Utils.isVirtualEnabled(getContext())) {
+ if (virtualAt != -1) {
+ //only virtual instance should be returned
+ return new Pair(new ArrayList<>(), List.of(aidlInstances.get(virtualAt)));
+ } else {
+ Slog.e(TAG, "Could not find virtual interface while it is enabled");
+ return new Pair(hidlInstances, aidlInstances);
+ }
+ } else {
+ //remove virtual instance
+ aidlInstances = new ArrayList<>(aidlInstances);
+ if (virtualAt != -1) {
+ aidlInstances.remove(virtualAt);
+ }
+ return new Pair(hidlInstances, aidlInstances);
+ }
+ }
+
@NonNull
private List<ServiceProvider> getHidlProviders(
@NonNull List<FingerprintSensorPropertiesInternal> hidlSensors) {
diff --git a/services/core/java/com/android/server/camera/CameraServiceProxy.java b/services/core/java/com/android/server/camera/CameraServiceProxy.java
index 0b04159..f8f0088 100644
--- a/services/core/java/com/android/server/camera/CameraServiceProxy.java
+++ b/services/core/java/com/android/server/camera/CameraServiceProxy.java
@@ -613,16 +613,26 @@
@Override
public boolean isCameraDisabled(int userId) {
- DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
- if (dpm == null) {
- Slog.e(TAG, "Failed to get the device policy manager service");
+ if (Binder.getCallingUid() != Process.CAMERASERVER_UID) {
+ Slog.e(TAG, "Calling UID: " + Binder.getCallingUid()
+ + " doesn't match expected camera service UID!");
return false;
}
+ final long ident = Binder.clearCallingIdentity();
try {
- return dpm.getCameraDisabled(null, userId);
- } catch (Exception e) {
- e.printStackTrace();
- return false;
+ DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
+ if (dpm == null) {
+ Slog.e(TAG, "Failed to get the device policy manager service");
+ return false;
+ }
+ try {
+ return dpm.getCameraDisabled(null, userId);
+ } catch (Exception e) {
+ e.printStackTrace();
+ return false;
+ }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
}
}
};
diff --git a/services/core/java/com/android/server/clipboard/ClipboardService.java b/services/core/java/com/android/server/clipboard/ClipboardService.java
index 9069eb2..38c6a52 100644
--- a/services/core/java/com/android/server/clipboard/ClipboardService.java
+++ b/services/core/java/com/android/server/clipboard/ClipboardService.java
@@ -193,8 +193,11 @@
if (IS_EMULATOR) {
mEmulatorClipboardMonitor = new EmulatorClipboardMonitor((clip) -> {
synchronized (mLock) {
- setPrimaryClipInternalLocked(getClipboardLocked(0, DEVICE_ID_DEFAULT), clip,
- android.os.Process.SYSTEM_UID, null);
+ Clipboard clipboard = getClipboardLocked(0, DEVICE_ID_DEFAULT);
+ if (clipboard != null) {
+ setPrimaryClipInternalLocked(clipboard, clip, android.os.Process.SYSTEM_UID,
+ null);
+ }
}
});
} else {
@@ -637,6 +640,9 @@
}
Clipboard clipboard = getClipboardLocked(intendingUserId, intendingDeviceId);
+ if (clipboard == null) {
+ return null;
+ }
showAccessNotificationLocked(pkg, intendingUid, intendingUserId, clipboard);
notifyTextClassifierLocked(clipboard, pkg, intendingUid);
if (clipboard.primaryClip != null) {
@@ -665,7 +671,7 @@
}
synchronized (mLock) {
Clipboard clipboard = getClipboardLocked(intendingUserId, intendingDeviceId);
- return clipboard.primaryClip != null
+ return (clipboard != null && clipboard.primaryClip != null)
? clipboard.primaryClip.getDescription() : null;
}
}
@@ -688,7 +694,8 @@
return false;
}
synchronized (mLock) {
- return getClipboardLocked(intendingUserId, intendingDeviceId).primaryClip != null;
+ Clipboard clipboard = getClipboardLocked(intendingUserId, intendingDeviceId);
+ return clipboard != null && clipboard.primaryClip != null;
}
}
@@ -709,8 +716,11 @@
return;
}
synchronized (mLock) {
- getClipboardLocked(intendingUserId, intendingDeviceId)
- .primaryClipListeners
+ Clipboard clipboard = getClipboardLocked(intendingUserId, intendingDeviceId);
+ if (clipboard == null) {
+ return;
+ }
+ clipboard.primaryClipListeners
.register(
listener,
new ListenerInfo(intendingUid, callingPackage, attributionTag));
@@ -733,8 +743,11 @@
return;
}
synchronized (mLock) {
- getClipboardLocked(intendingUserId,
- intendingDeviceId).primaryClipListeners.unregister(listener);
+ Clipboard clipboard = getClipboardLocked(intendingUserId,
+ intendingDeviceId);
+ if (clipboard != null) {
+ clipboard.primaryClipListeners.unregister(listener);
+ }
}
}
@@ -757,7 +770,7 @@
}
synchronized (mLock) {
Clipboard clipboard = getClipboardLocked(intendingUserId, intendingDeviceId);
- if (clipboard.primaryClip != null) {
+ if (clipboard != null && clipboard.primaryClip != null) {
CharSequence text = clipboard.primaryClip.getItemAt(0).getText();
return text != null && text.length() > 0;
}
@@ -786,7 +799,7 @@
}
synchronized (mLock) {
Clipboard clipboard = getClipboardLocked(intendingUserId, intendingDeviceId);
- if (clipboard.primaryClip != null) {
+ if (clipboard != null && clipboard.primaryClip != null) {
return clipboard.mPrimaryClipPackage;
}
return null;
@@ -808,7 +821,8 @@
final int intendingUid = msg.arg2;
final int intendingDeviceId = ((Pair<Integer, Integer>) msg.obj).second;
synchronized (mLock) {
- if (getClipboardLocked(userId, intendingDeviceId).primaryClip != null) {
+ Clipboard clipboard = getClipboardLocked(userId, intendingDeviceId);
+ if (clipboard != null && clipboard.primaryClip != null) {
FrameworkStatsLog.write(FrameworkStatsLog.CLIPBOARD_CLEARED,
FrameworkStatsLog.CLIPBOARD_CLEARED__SOURCE__AUTO_CLEAR);
setPrimaryClipInternalLocked(
@@ -824,9 +838,23 @@
};
@GuardedBy("mLock")
- private Clipboard getClipboardLocked(@UserIdInt int userId, int deviceId) {
+ private @Nullable Clipboard getClipboardLocked(@UserIdInt int userId, int deviceId) {
Clipboard clipboard = mClipboards.get(userId, deviceId);
if (clipboard == null) {
+ try {
+ if (!mUm.isUserRunning(userId)) {
+ Slog.w(TAG, "getClipboardLocked called with not running userId " + userId);
+ return null;
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "RemoteException calling UserManager: " + e);
+ return null;
+ }
+ if (deviceId != DEVICE_ID_DEFAULT && !mVdm.isValidVirtualDeviceId(deviceId)) {
+ Slog.w(TAG, "getClipboardLocked called with invalid (possibly released) deviceId "
+ + deviceId);
+ return null;
+ }
clipboard = new Clipboard(userId, deviceId);
mClipboards.add(userId, deviceId, clipboard);
}
@@ -876,8 +904,11 @@
final int userId = UserHandle.getUserId(uid);
// Update this user
- setPrimaryClipInternalLocked(getClipboardLocked(userId, deviceId), clip, uid,
- sourcePackage);
+ Clipboard clipboard = getClipboardLocked(userId, deviceId);
+ if (clipboard == null) {
+ return;
+ }
+ setPrimaryClipInternalLocked(clipboard, clip, uid, sourcePackage);
// Update related users
List<UserInfo> related = getRelatedProfiles(userId);
@@ -911,8 +942,11 @@
final boolean canCopyIntoProfile = !hasRestriction(
UserManager.DISALLOW_SHARE_INTO_MANAGED_PROFILE, id);
if (canCopyIntoProfile) {
- setPrimaryClipInternalNoClassifyLocked(
- getClipboardLocked(id, deviceId), clip, uid, sourcePackage);
+ Clipboard relatedClipboard = getClipboardLocked(id, deviceId);
+ if (relatedClipboard != null) {
+ setPrimaryClipInternalNoClassifyLocked(relatedClipboard, clip, uid,
+ sourcePackage);
+ }
}
}
}
@@ -1046,6 +1080,9 @@
synchronized (mLock) {
Clipboard clipboard = getClipboardLocked(userId, deviceId);
+ if (clipboard == null) {
+ return;
+ }
if (clipboard.primaryClip == clip) {
applyClassificationAndSendBroadcastLocked(
clipboard, confidences, links, classifier);
@@ -1061,7 +1098,8 @@
UserManager.DISALLOW_SHARE_INTO_MANAGED_PROFILE, id);
if (canCopyIntoProfile) {
Clipboard relatedClipboard = getClipboardLocked(id, deviceId);
- if (hasTextLocked(relatedClipboard, text)) {
+ if (relatedClipboard != null
+ && hasTextLocked(relatedClipboard, text)) {
applyClassificationAndSendBroadcastLocked(
relatedClipboard, confidences, links, classifier);
}
@@ -1184,9 +1222,10 @@
Binder.restoreCallingIdentity(oldIdentity);
}
Clipboard clipboard = getClipboardLocked(UserHandle.getUserId(uid), deviceId);
- if (clipboard.primaryClip != null && !clipboard.activePermissionOwners.contains(pkg)) {
+ if (clipboard != null && clipboard.primaryClip != null
+ && !clipboard.activePermissionOwners.contains(pkg)) {
final int N = clipboard.primaryClip.getItemCount();
- for (int i=0; i<N; i++) {
+ for (int i = 0; i < N; i++) {
grantItemPermission(clipboard.primaryClip.getItemAt(i), clipboard.primaryClipUid,
pkg, UserHandle.getUserId(uid));
}
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 63bb026..cba5039 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -3346,7 +3346,7 @@
// Transforms do not need to be persisted; the IkeSession will keep
// them alive for us
mIpSecManager.applyTunnelModeTransform(mTunnelIface, direction, transform);
- } catch (IOException e) {
+ } catch (IOException | IllegalArgumentException e) {
Log.d(TAG, "Transform application failed for token " + token, e);
onSessionLost(token, e);
}
@@ -3440,7 +3440,7 @@
mTunnelIface, IpSecManager.DIRECTION_IN, inTransform);
mIpSecManager.applyTunnelModeTransform(
mTunnelIface, IpSecManager.DIRECTION_OUT, outTransform);
- } catch (IOException e) {
+ } catch (IOException | IllegalArgumentException e) {
Log.d(TAG, "Transform application failed for token " + token, e);
onSessionLost(token, e);
}
diff --git a/services/core/java/com/android/server/display/BrightnessThrottler.java b/services/core/java/com/android/server/display/BrightnessThrottler.java
index c421ec0..59844e1 100644
--- a/services/core/java/com/android/server/display/BrightnessThrottler.java
+++ b/services/core/java/com/android/server/display/BrightnessThrottler.java
@@ -38,17 +38,24 @@
import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData;
import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel;
import com.android.server.display.feature.DeviceConfigParameterProvider;
+import com.android.server.display.utils.DeviceConfigParsingUtils;
import java.io.PrintWriter;
-import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.concurrent.Executor;
+import java.util.function.BiFunction;
+import java.util.function.Function;
/**
* This class monitors various conditions, such as skin temperature throttling status, and limits
* the allowed brightness range accordingly.
+ *
+ * @deprecated will be replaced by
+ * {@link com.android.server.display.brightness.clamper.BrightnessThermalClamper}
*/
+@Deprecated
class BrightnessThrottler {
private static final String TAG = "BrightnessThrottler";
private static final boolean DEBUG = false;
@@ -93,8 +100,21 @@
// time the underlying display device changes.
// This map is indexed by uniqueDisplayId, to provide maps for throttlingId -> throttlingData.
// HashMap< uniqueDisplayId, HashMap< throttlingDataId, ThermalBrightnessThrottlingData >>
- private final HashMap<String, HashMap<String, ThermalBrightnessThrottlingData>>
- mThermalBrightnessThrottlingDataOverride = new HashMap<>(1);
+ private final Map<String, Map<String, ThermalBrightnessThrottlingData>>
+ mThermalBrightnessThrottlingDataOverride = new HashMap<>();
+
+ private final BiFunction<String, String, ThrottlingLevel> mDataPointMapper = (key, value) -> {
+ try {
+ int status = DeviceConfigParsingUtils.parseThermalStatus(key);
+ float brightnessPoint = DeviceConfigParsingUtils.parseBrightness(value);
+ return new ThrottlingLevel(status, brightnessPoint);
+ } catch (IllegalArgumentException iae) {
+ return null;
+ }
+ };
+
+ private final Function<List<ThrottlingLevel>, ThermalBrightnessThrottlingData>
+ mDataSetMapper = ThermalBrightnessThrottlingData::create;
BrightnessThrottler(Handler handler, Runnable throttlingChangeCallback, String uniqueDisplayId,
String throttlingDataId,
@@ -257,79 +277,15 @@
// 456,2,moderate,0.9,critical,0.7,id_2
// displayId, number, <state, val> * number
// displayId, <number, <state, val> * number>, throttlingId
- private boolean parseAndAddData(@NonNull String strArray,
- @NonNull HashMap<String, HashMap<String, ThermalBrightnessThrottlingData>>
- displayIdToThrottlingIdToBtd) {
- boolean validConfig = true;
- String[] items = strArray.split(",");
- int i = 0;
-
- try {
- String uniqueDisplayId = items[i++];
-
- // number of throttling points
- int noOfThrottlingPoints = Integer.parseInt(items[i++]);
- List<ThrottlingLevel> throttlingLevels = new ArrayList<>(noOfThrottlingPoints);
-
- // throttling level and point
- for (int j = 0; j < noOfThrottlingPoints; j++) {
- String severity = items[i++];
- int status = parseThermalStatus(severity);
- float brightnessPoint = parseBrightness(items[i++]);
- throttlingLevels.add(new ThrottlingLevel(status, brightnessPoint));
- }
-
- String throttlingDataId = (i < items.length) ? items[i++] : DEFAULT_ID;
- ThermalBrightnessThrottlingData throttlingLevelsData =
- DisplayDeviceConfig.ThermalBrightnessThrottlingData.create(throttlingLevels);
-
- // Add throttlingLevelsData to inner map where necessary.
- HashMap<String, ThermalBrightnessThrottlingData> throttlingMapForDisplay =
- displayIdToThrottlingIdToBtd.get(uniqueDisplayId);
- if (throttlingMapForDisplay == null) {
- throttlingMapForDisplay = new HashMap<>();
- throttlingMapForDisplay.put(throttlingDataId, throttlingLevelsData);
- displayIdToThrottlingIdToBtd.put(uniqueDisplayId, throttlingMapForDisplay);
- } else if (throttlingMapForDisplay.containsKey(throttlingDataId)) {
- Slog.e(TAG, "Throttling data for display " + uniqueDisplayId
- + "contains duplicate throttling ids: '" + throttlingDataId + "'");
- return false;
- } else {
- throttlingMapForDisplay.put(throttlingDataId, throttlingLevelsData);
- }
- } catch (NumberFormatException | IndexOutOfBoundsException
- | UnknownThermalStatusException e) {
- Slog.e(TAG, "Throttling data is invalid array: '" + strArray + "'", e);
- validConfig = false;
- }
-
- if (i != items.length) {
- validConfig = false;
- }
- return validConfig;
- }
-
private void loadThermalBrightnessThrottlingDataFromDeviceConfig() {
- HashMap<String, HashMap<String, ThermalBrightnessThrottlingData>> tempThrottlingData =
- new HashMap<>(1);
mThermalBrightnessThrottlingDataString =
mConfigParameterProvider.getBrightnessThrottlingData();
- boolean validConfig = true;
mThermalBrightnessThrottlingDataOverride.clear();
if (mThermalBrightnessThrottlingDataString != null) {
- String[] throttlingDataSplits = mThermalBrightnessThrottlingDataString.split(";");
- for (String s : throttlingDataSplits) {
- if (!parseAndAddData(s, tempThrottlingData)) {
- validConfig = false;
- break;
- }
- }
-
- if (validConfig) {
- mThermalBrightnessThrottlingDataOverride.putAll(tempThrottlingData);
- tempThrottlingData.clear();
- }
-
+ Map<String, Map<String, ThermalBrightnessThrottlingData>> tempThrottlingData =
+ DeviceConfigParsingUtils.parseDeviceConfigMap(
+ mThermalBrightnessThrottlingDataString, mDataPointMapper, mDataSetMapper);
+ mThermalBrightnessThrottlingDataOverride.putAll(tempThrottlingData);
} else {
Slog.w(TAG, "DeviceConfig ThermalBrightnessThrottlingData is null");
}
@@ -395,42 +351,6 @@
}
}
- private float parseBrightness(String intVal) throws NumberFormatException {
- float value = Float.parseFloat(intVal);
- if (value < PowerManager.BRIGHTNESS_MIN || value > PowerManager.BRIGHTNESS_MAX) {
- throw new NumberFormatException("Brightness constraint value out of bounds.");
- }
- return value;
- }
-
- @PowerManager.ThermalStatus private int parseThermalStatus(@NonNull String value)
- throws UnknownThermalStatusException {
- switch (value) {
- case "none":
- return PowerManager.THERMAL_STATUS_NONE;
- case "light":
- return PowerManager.THERMAL_STATUS_LIGHT;
- case "moderate":
- return PowerManager.THERMAL_STATUS_MODERATE;
- case "severe":
- return PowerManager.THERMAL_STATUS_SEVERE;
- case "critical":
- return PowerManager.THERMAL_STATUS_CRITICAL;
- case "emergency":
- return PowerManager.THERMAL_STATUS_EMERGENCY;
- case "shutdown":
- return PowerManager.THERMAL_STATUS_SHUTDOWN;
- default:
- throw new UnknownThermalStatusException("Invalid Thermal Status: " + value);
- }
- }
-
- private static class UnknownThermalStatusException extends Exception {
- UnknownThermalStatusException(String message) {
- super(message);
- }
- }
-
private final class SkinThermalStatusObserver extends IThermalEventListener.Stub {
private final Injector mInjector;
private final Handler mHandler;
diff --git a/services/core/java/com/android/server/display/DisplayBrightnessState.java b/services/core/java/com/android/server/display/DisplayBrightnessState.java
index e27182f..dd5afa2 100644
--- a/services/core/java/com/android/server/display/DisplayBrightnessState.java
+++ b/services/core/java/com/android/server/display/DisplayBrightnessState.java
@@ -16,6 +16,8 @@
package com.android.server.display;
+import android.text.TextUtils;
+
import com.android.server.display.brightness.BrightnessReason;
import java.util.Objects;
@@ -29,12 +31,14 @@
private final float mSdrBrightness;
private final BrightnessReason mBrightnessReason;
private final String mDisplayBrightnessStrategyName;
+ private final boolean mShouldUseAutoBrightness;
private DisplayBrightnessState(Builder builder) {
- this.mBrightness = builder.getBrightness();
- this.mSdrBrightness = builder.getSdrBrightness();
- this.mBrightnessReason = builder.getBrightnessReason();
- this.mDisplayBrightnessStrategyName = builder.getDisplayBrightnessStrategyName();
+ mBrightness = builder.getBrightness();
+ mSdrBrightness = builder.getSdrBrightness();
+ mBrightnessReason = builder.getBrightnessReason();
+ mDisplayBrightnessStrategyName = builder.getDisplayBrightnessStrategyName();
+ mShouldUseAutoBrightness = builder.getShouldUseAutoBrightness();
}
/**
@@ -66,6 +70,13 @@
return mDisplayBrightnessStrategyName;
}
+ /**
+ * @return {@code true} if the device is set up to run auto-brightness.
+ */
+ public boolean getShouldUseAutoBrightness() {
+ return mShouldUseAutoBrightness;
+ }
+
@Override
public String toString() {
StringBuilder stringBuilder = new StringBuilder("DisplayBrightnessState:");
@@ -75,6 +86,8 @@
stringBuilder.append(getSdrBrightness());
stringBuilder.append("\n brightnessReason:");
stringBuilder.append(getBrightnessReason());
+ stringBuilder.append("\n shouldUseAutoBrightness:");
+ stringBuilder.append(getShouldUseAutoBrightness());
return stringBuilder.toString();
}
@@ -91,28 +104,20 @@
return false;
}
- DisplayBrightnessState
- displayBrightnessState = (DisplayBrightnessState) other;
+ DisplayBrightnessState otherState = (DisplayBrightnessState) other;
- if (mBrightness != displayBrightnessState.getBrightness()) {
- return false;
- }
- if (mSdrBrightness != displayBrightnessState.getSdrBrightness()) {
- return false;
- }
- if (!mBrightnessReason.equals(displayBrightnessState.getBrightnessReason())) {
- return false;
- }
- if (!mDisplayBrightnessStrategyName.equals(
- displayBrightnessState.getDisplayBrightnessStrategyName())) {
- return false;
- }
- return true;
+ return mBrightness == otherState.getBrightness()
+ && mSdrBrightness == otherState.getSdrBrightness()
+ && mBrightnessReason.equals(otherState.getBrightnessReason())
+ && TextUtils.equals(mDisplayBrightnessStrategyName,
+ otherState.getDisplayBrightnessStrategyName())
+ && mShouldUseAutoBrightness == otherState.getShouldUseAutoBrightness();
}
@Override
public int hashCode() {
- return Objects.hash(mBrightness, mSdrBrightness, mBrightnessReason);
+ return Objects.hash(
+ mBrightness, mSdrBrightness, mBrightnessReason, mShouldUseAutoBrightness);
}
/**
@@ -123,6 +128,23 @@
private float mSdrBrightness;
private BrightnessReason mBrightnessReason = new BrightnessReason();
private String mDisplayBrightnessStrategyName;
+ private boolean mShouldUseAutoBrightness;
+
+ /**
+ * Create a builder starting with the values from the specified {@link
+ * DisplayBrightnessState}.
+ *
+ * @param state The state from which to initialize.
+ */
+ public static Builder from(DisplayBrightnessState state) {
+ Builder builder = new Builder();
+ builder.setBrightness(state.getBrightness());
+ builder.setSdrBrightness(state.getSdrBrightness());
+ builder.setBrightnessReason(state.getBrightnessReason());
+ builder.setDisplayBrightnessStrategyName(state.getDisplayBrightnessStrategyName());
+ builder.setShouldUseAutoBrightness(state.getShouldUseAutoBrightness());
+ return builder;
+ }
/**
* Gets the brightness
@@ -200,6 +222,21 @@
}
/**
+ * See {@link DisplayBrightnessState#getShouldUseAutoBrightness}.
+ */
+ public Builder setShouldUseAutoBrightness(boolean shouldUseAutoBrightness) {
+ this.mShouldUseAutoBrightness = shouldUseAutoBrightness;
+ return this;
+ }
+
+ /**
+ * See {@link DisplayBrightnessState#getShouldUseAutoBrightness}.
+ */
+ public boolean getShouldUseAutoBrightness() {
+ return mShouldUseAutoBrightness;
+ }
+
+ /**
* This is used to construct an immutable DisplayBrightnessState object from its builder
*/
public DisplayBrightnessState build() {
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index d9cb299..c25b253 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -458,7 +458,7 @@
public static final String QUIRK_CAN_SET_BRIGHTNESS_VIA_HWC = "canSetBrightnessViaHwc";
- static final String DEFAULT_ID = "default";
+ public static final String DEFAULT_ID = "default";
private static final float BRIGHTNESS_DEFAULT = 0.5f;
private static final String ETC_DIR = "etc";
@@ -3127,11 +3127,15 @@
public static class ThermalBrightnessThrottlingData {
public List<ThrottlingLevel> throttlingLevels;
- static class ThrottlingLevel {
+ /**
+ * thermal status to brightness cap holder
+ */
+ public static class ThrottlingLevel {
public @PowerManager.ThermalStatus int thermalStatus;
public float brightness;
- ThrottlingLevel(@PowerManager.ThermalStatus int thermalStatus, float brightness) {
+ public ThrottlingLevel(
+ @PowerManager.ThermalStatus int thermalStatus, float brightness) {
this.thermalStatus = thermalStatus;
this.brightness = brightness;
}
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 1a8e2db..3a0b8b5 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -155,6 +155,7 @@
import com.android.server.display.mode.DisplayModeDirector;
import com.android.server.display.utils.SensorUtils;
import com.android.server.input.InputManagerInternal;
+import com.android.server.utils.FoldSettingWrapper;
import com.android.server.wm.SurfaceAnimationThread;
import com.android.server.wm.WindowManagerInternal;
@@ -540,7 +541,8 @@
mUiHandler = UiThread.getHandler();
mDisplayDeviceRepo = new DisplayDeviceRepository(mSyncRoot, mPersistentDataStore);
mLogicalDisplayMapper = new LogicalDisplayMapper(mContext, mDisplayDeviceRepo,
- new LogicalDisplayListener(), mSyncRoot, mHandler);
+ new LogicalDisplayListener(), mSyncRoot, mHandler,
+ new FoldSettingWrapper(mContext.getContentResolver()));
mDisplayModeDirector = new DisplayModeDirector(context, mHandler);
mBrightnessSynchronizer = new BrightnessSynchronizer(mContext);
Resources resources = mContext.getResources();
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 7fc6cd0..d19e78d 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -141,6 +141,9 @@
private static final int MSG_STATSD_HBM_BRIGHTNESS = 13;
private static final int MSG_SWITCH_USER = 14;
private static final int MSG_BOOT_COMPLETED = 15;
+ private static final int MSG_SET_DWBC_STRONG_MODE = 16;
+ private static final int MSG_SET_DWBC_COLOR_OVERRIDE = 17;
+ private static final int MSG_SET_DWBC_LOGGING_ENABLED = 18;
private static final int PROXIMITY_UNKNOWN = -1;
private static final int PROXIMITY_NEGATIVE = 0;
@@ -436,6 +439,7 @@
private final boolean mSkipScreenOnBrightnessRamp;
// Display white balance components.
+ // Critical methods must be called on DPC handler thread.
@Nullable
private final DisplayWhiteBalanceSettings mDisplayWhiteBalanceSettings;
@Nullable
@@ -680,9 +684,9 @@
DisplayWhiteBalanceController displayWhiteBalanceController = null;
if (mDisplayId == Display.DEFAULT_DISPLAY) {
try {
+ displayWhiteBalanceController = injector.getDisplayWhiteBalanceController(
+ mHandler, mSensorManager, resources);
displayWhiteBalanceSettings = new DisplayWhiteBalanceSettings(mContext, mHandler);
- displayWhiteBalanceController = DisplayWhiteBalanceFactory.create(mHandler,
- mSensorManager, resources);
displayWhiteBalanceSettings.setCallbacks(this);
displayWhiteBalanceController.setCallbacks(this);
} catch (Exception e) {
@@ -1025,10 +1029,6 @@
Message msg = mHandler.obtainMessage(MSG_STOP);
mHandler.sendMessageAtTime(msg, mClock.uptimeMillis());
- if (mDisplayWhiteBalanceController != null) {
- mDisplayWhiteBalanceController.setEnabled(false);
- }
-
if (mAutomaticBrightnessController != null) {
mAutomaticBrightnessController.stop();
}
@@ -1334,9 +1334,11 @@
mAutomaticBrightnessController.switchToInteractiveScreenBrightnessMode();
}
}
- if (mDisplayWhiteBalanceController != null) {
- mDisplayWhiteBalanceController.setStrongModeEnabled(isIdle);
- }
+
+ Message msg = mHandler.obtainMessage();
+ msg.what = MSG_SET_DWBC_STRONG_MODE;
+ msg.arg1 = isIdle ? 1 : 0;
+ mHandler.sendMessageAtTime(msg, mClock.uptimeMillis());
}
private final Animator.AnimatorListener mAnimatorListener = new Animator.AnimatorListener() {
@@ -1405,8 +1407,13 @@
if (mScreenOffBrightnessSensorController != null) {
mScreenOffBrightnessSensorController.stop();
}
+ if (mDisplayWhiteBalanceController != null) {
+ mDisplayWhiteBalanceController.setEnabled(false);
+ }
+
}
+ // Call from handler thread
private void updatePowerState() {
Trace.traceBegin(Trace.TRACE_TAG_POWER,
"DisplayPowerController#updatePowerState");
@@ -2058,6 +2065,32 @@
}
}
+ private void setDwbcOverride(float cct) {
+ if (mDisplayWhiteBalanceController != null) {
+ mDisplayWhiteBalanceController.setAmbientColorTemperatureOverride(cct);
+ // The ambient color temperature override is only applied when the ambient color
+ // temperature changes or is updated, so it doesn't necessarily change the screen color
+ // temperature immediately. So, let's make it!
+ // We can call this directly, since we're already on the handler thread.
+ updatePowerState();
+ }
+ }
+
+ private void setDwbcStrongMode(int arg) {
+ if (mDisplayWhiteBalanceController != null) {
+ final boolean isIdle = (arg == 1);
+ mDisplayWhiteBalanceController.setStrongModeEnabled(isIdle);
+ }
+ }
+
+ private void setDwbcLoggingEnabled(int arg) {
+ if (mDisplayWhiteBalanceController != null) {
+ final boolean shouldEnable = (arg == 1);
+ mDisplayWhiteBalanceController.setLoggingEnabled(shouldEnable);
+ mDisplayWhiteBalanceSettings.setLoggingEnabled(shouldEnable);
+ }
+ }
+
@Override
public void updateBrightness() {
sendUpdatePowerState();
@@ -3332,6 +3365,19 @@
mBootCompleted = true;
updatePowerState();
break;
+
+ case MSG_SET_DWBC_STRONG_MODE:
+ setDwbcStrongMode(msg.arg1);
+ break;
+
+ case MSG_SET_DWBC_COLOR_OVERRIDE:
+ final float cct = Float.intBitsToFloat(msg.arg1);
+ setDwbcOverride(cct);
+ break;
+
+ case MSG_SET_DWBC_LOGGING_ENABLED:
+ setDwbcLoggingEnabled(msg.arg1);
+ break;
}
}
}
@@ -3399,21 +3445,18 @@
@Override
public void setDisplayWhiteBalanceLoggingEnabled(boolean enabled) {
- if (mDisplayWhiteBalanceController != null) {
- mDisplayWhiteBalanceController.setLoggingEnabled(enabled);
- mDisplayWhiteBalanceSettings.setLoggingEnabled(enabled);
- }
+ Message msg = mHandler.obtainMessage();
+ msg.what = MSG_SET_DWBC_LOGGING_ENABLED;
+ msg.arg1 = enabled ? 1 : 0;
+ msg.sendToTarget();
}
@Override
public void setAmbientColorTemperatureOverride(float cct) {
- if (mDisplayWhiteBalanceController != null) {
- mDisplayWhiteBalanceController.setAmbientColorTemperatureOverride(cct);
- // The ambient color temperature override is only applied when the ambient color
- // temperature changes or is updated, so it doesn't necessarily change the screen color
- // temperature immediately. So, let's make it!
- sendUpdatePowerState();
- }
+ Message msg = mHandler.obtainMessage();
+ msg.what = MSG_SET_DWBC_COLOR_OVERRIDE;
+ msg.arg1 = Float.floatToIntBits(cct);
+ msg.sendToTarget();
}
@VisibleForTesting
@@ -3544,6 +3587,12 @@
displayUniqueId, brightnessMin, brightnessMax, hbmData, hdrBrightnessCfg,
hbmChangeCallback, hbmMetadata, context);
}
+
+ DisplayWhiteBalanceController getDisplayWhiteBalanceController(Handler handler,
+ SensorManager sensorManager, Resources resources) {
+ return DisplayWhiteBalanceFactory.create(handler,
+ sensorManager, resources);
+ }
}
static class CachedBrightnessInfo {
diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java
index 37b9d563..2694f55 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController2.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController2.java
@@ -48,6 +48,7 @@
import android.os.UserHandle;
import android.provider.Settings;
import android.util.FloatProperty;
+import android.util.IndentingPrintWriter;
import android.util.Log;
import android.util.MathUtils;
import android.util.MutableFloat;
@@ -64,7 +65,6 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.util.FrameworkStatsLog;
-import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.RingBuffer;
import com.android.server.LocalServices;
import com.android.server.am.BatteryStatsService;
@@ -73,6 +73,7 @@
import com.android.server.display.brightness.BrightnessReason;
import com.android.server.display.brightness.BrightnessUtils;
import com.android.server.display.brightness.DisplayBrightnessController;
+import com.android.server.display.brightness.clamper.BrightnessClamperController;
import com.android.server.display.brightness.strategy.AutomaticBrightnessStrategy;
import com.android.server.display.color.ColorDisplayService.ColorDisplayServiceInternal;
import com.android.server.display.color.ColorDisplayService.ReduceBrightColorsListener;
@@ -141,6 +142,11 @@
private static final int MSG_STATSD_HBM_BRIGHTNESS = 11;
private static final int MSG_SWITCH_USER = 12;
private static final int MSG_BOOT_COMPLETED = 13;
+ private static final int MSG_SET_DWBC_STRONG_MODE = 14;
+ private static final int MSG_SET_DWBC_COLOR_OVERRIDE = 15;
+ private static final int MSG_SET_DWBC_LOGGING_ENABLED = 16;
+
+
private static final int BRIGHTNESS_CHANGE_STATSD_REPORT_INTERVAL_MS = 500;
@@ -367,6 +373,7 @@
private final boolean mSkipScreenOnBrightnessRamp;
// Display white balance components.
+ // Critical methods must be called on DPC2 handler thread.
@Nullable
private final DisplayWhiteBalanceSettings mDisplayWhiteBalanceSettings;
@Nullable
@@ -380,6 +387,8 @@
private final BrightnessThrottler mBrightnessThrottler;
+ private final BrightnessClamperController mBrightnessClamperController;
+
private final Runnable mOnBrightnessChangeRunnable;
private final BrightnessEvent mLastBrightnessEvent;
@@ -492,7 +501,6 @@
mWakelockController, mDisplayDeviceConfig, mHandler.getLooper(),
() -> updatePowerState(), mDisplayId, mSensorManager);
mDisplayStateController = new DisplayStateController(mDisplayPowerProximityStateController);
- mAutomaticBrightnessStrategy = new AutomaticBrightnessStrategy(context, mDisplayId);
mTag = "DisplayPowerController2[" + mDisplayId + "]";
mThermalBrightnessThrottlingDataId =
logicalDisplay.getDisplayInfoLocked().thermalBrightnessThrottlingDataId;
@@ -554,16 +562,25 @@
mDisplayId, mLogicalDisplay.getDisplayInfoLocked().brightnessDefault,
brightnessSetting, () -> postBrightnessChangeRunnable(),
new HandlerExecutor(mHandler));
+
+ mBrightnessClamperController = new BrightnessClamperController(mHandler,
+ modeChangeCallback::run, new BrightnessClamperController.DisplayDeviceData(
+ mUniqueDisplayId,
+ mThermalBrightnessThrottlingDataId,
+ mDisplayDeviceConfig
+ ));
// Seed the cached brightness
saveBrightnessInfo(getScreenBrightnessSetting());
+ mAutomaticBrightnessStrategy =
+ mDisplayBrightnessController.getAutomaticBrightnessStrategy();
DisplayWhiteBalanceSettings displayWhiteBalanceSettings = null;
DisplayWhiteBalanceController displayWhiteBalanceController = null;
if (mDisplayId == Display.DEFAULT_DISPLAY) {
try {
+ displayWhiteBalanceController = mInjector.getDisplayWhiteBalanceController(
+ mHandler, mSensorManager, resources);
displayWhiteBalanceSettings = new DisplayWhiteBalanceSettings(mContext, mHandler);
- displayWhiteBalanceController = DisplayWhiteBalanceFactory.create(mHandler,
- mSensorManager, resources);
displayWhiteBalanceSettings.setCallbacks(this);
displayWhiteBalanceController.setCallbacks(this);
} catch (Exception e) {
@@ -599,7 +616,7 @@
setUpAutoBrightness(resources, handler);
- mColorFadeEnabled = !ActivityManager.isLowRamDeviceStatic();
+ mColorFadeEnabled = mInjector.isColorFadeEnabled();
mColorFadeFadesConfig = resources.getBoolean(
R.bool.config_animateScreenLights);
@@ -782,6 +799,10 @@
final String thermalBrightnessThrottlingDataId =
mLogicalDisplay.getDisplayInfoLocked().thermalBrightnessThrottlingDataId;
+ mBrightnessClamperController.onDisplayChanged(
+ new BrightnessClamperController.DisplayDeviceData(mUniqueDisplayId,
+ mThermalBrightnessThrottlingDataId, config));
+
mHandler.postAtTime(() -> {
boolean changed = false;
if (mDisplayDevice != device) {
@@ -835,10 +856,6 @@
Message msg = mHandler.obtainMessage(MSG_STOP);
mHandler.sendMessageAtTime(msg, mClock.uptimeMillis());
- if (mDisplayWhiteBalanceController != null) {
- mDisplayWhiteBalanceController.setEnabled(false);
- }
-
if (mAutomaticBrightnessController != null) {
mAutomaticBrightnessController.stop();
}
@@ -1149,9 +1166,10 @@
mAutomaticBrightnessController.switchToInteractiveScreenBrightnessMode();
}
}
- if (mDisplayWhiteBalanceController != null) {
- mDisplayWhiteBalanceController.setStrongModeEnabled(isIdle);
- }
+ Message msg = mHandler.obtainMessage();
+ msg.what = MSG_SET_DWBC_STRONG_MODE;
+ msg.arg1 = isIdle ? 1 : 0;
+ mHandler.sendMessageAtTime(msg, mClock.uptimeMillis());
}
private final Animator.AnimatorListener mAnimatorListener = new Animator.AnimatorListener() {
@@ -1187,6 +1205,7 @@
mDisplayPowerProximityStateController.cleanup();
mBrightnessRangeController.stop();
mBrightnessThrottler.stop();
+ mBrightnessClamperController.stop();
mHandler.removeCallbacksAndMessages(null);
// Release any outstanding wakelocks we're still holding because of pending messages.
@@ -1205,8 +1224,13 @@
if (mScreenOffBrightnessSensorController != null) {
mScreenOffBrightnessSensorController.stop();
}
+
+ if (mDisplayWhiteBalanceController != null) {
+ mDisplayWhiteBalanceController.setEnabled(false);
+ }
}
+ // Call from handler thread
private void updatePowerState() {
Trace.traceBegin(Trace.TRACE_TAG_POWER,
"DisplayPowerController#updatePowerState");
@@ -1257,14 +1281,6 @@
int state = mDisplayStateController
.updateDisplayState(mPowerRequest, mIsEnabled, mIsInTransition);
- if (mScreenOffBrightnessSensorController != null) {
- mScreenOffBrightnessSensorController
- .setLightSensorEnabled(mAutomaticBrightnessStrategy.shouldUseAutoBrightness()
- && mIsEnabled && (state == Display.STATE_OFF || (state == Display.STATE_DOZE
- && !mDisplayBrightnessController.isAllowAutoBrightnessWhileDozingConfig()))
- && mLeadDisplayId == Layout.NO_LEAD_DISPLAY);
- }
-
// Initialize things the first time the power state is changed.
if (mustInitialize) {
initialize(readyToUpdateDisplayState() ? state : Display.STATE_UNKNOWN);
@@ -1290,6 +1306,17 @@
slowChange = mBrightnessToFollowSlowChange;
}
+ // Set up the ScreenOff controller used when coming out of SCREEN_OFF and the ALS sensor
+ // doesn't yet have a valid lux value to use with auto-brightness.
+ if (mScreenOffBrightnessSensorController != null) {
+ mScreenOffBrightnessSensorController
+ .setLightSensorEnabled(displayBrightnessState.getShouldUseAutoBrightness()
+ && mIsEnabled && (state == Display.STATE_OFF
+ || (state == Display.STATE_DOZE
+ && !mDisplayBrightnessController.isAllowAutoBrightnessWhileDozingConfig()))
+ && mLeadDisplayId == Layout.NO_LEAD_DISPLAY);
+ }
+
// Take note if the short term model was already active before applying the current
// request changes.
final boolean wasShortTermModelActive =
@@ -1519,6 +1546,8 @@
// allowed range.
float animateValue = clampScreenBrightness(brightnessState);
+ animateValue = mBrightnessClamperController.clamp(animateValue);
+
// If there are any HDR layers on the screen, we have a special brightness value that we
// use instead. We still preserve the calculated brightness for Standard Dynamic Range
// (SDR) layers, but the main brightness value will be the one for HDR.
@@ -1563,7 +1592,7 @@
notifyBrightnessTrackerChanged(brightnessState, userInitiatedChange,
wasShortTermModelActive, mAutomaticBrightnessStrategy.isAutoBrightnessEnabled(),
- brightnessIsTemporary);
+ brightnessIsTemporary, displayBrightnessState.getShouldUseAutoBrightness());
// We save the brightness info *after* the brightness setting has been changed and
// adjustments made so that the brightness info reflects the latest value.
@@ -1607,8 +1636,8 @@
mTempBrightnessEvent.setWasShortTermModelActive(wasShortTermModelActive);
mTempBrightnessEvent.setDisplayBrightnessStrategyName(displayBrightnessState
.getDisplayBrightnessStrategyName());
- mTempBrightnessEvent.setAutomaticBrightnessEnabled(mAutomaticBrightnessStrategy
- .shouldUseAutoBrightness());
+ mTempBrightnessEvent.setAutomaticBrightnessEnabled(
+ displayBrightnessState.getShouldUseAutoBrightness());
// Temporary is what we use during slider interactions. We avoid logging those so that
// we don't spam logcat when the slider is being used.
boolean tempToTempTransition =
@@ -1705,6 +1734,32 @@
}
}
+ private void setDwbcOverride(float cct) {
+ if (mDisplayWhiteBalanceController != null) {
+ mDisplayWhiteBalanceController.setAmbientColorTemperatureOverride(cct);
+ // The ambient color temperature override is only applied when the ambient color
+ // temperature changes or is updated, so it doesn't necessarily change the screen color
+ // temperature immediately. So, let's make it!
+ // We can call this directly, since we're already on the handler thread.
+ updatePowerState();
+ }
+ }
+
+ private void setDwbcStrongMode(int arg) {
+ if (mDisplayWhiteBalanceController != null) {
+ final boolean isIdle = (arg == 1);
+ mDisplayWhiteBalanceController.setStrongModeEnabled(isIdle);
+ }
+ }
+
+ private void setDwbcLoggingEnabled(int arg) {
+ if (mDisplayWhiteBalanceController != null) {
+ final boolean enabled = (arg == 1);
+ mDisplayWhiteBalanceController.setLoggingEnabled(enabled);
+ mDisplayWhiteBalanceSettings.setLoggingEnabled(enabled);
+ }
+ }
+
@Override
public void updateBrightness() {
sendUpdatePowerState();
@@ -2220,7 +2275,7 @@
private void notifyBrightnessTrackerChanged(float brightness, boolean userInitiated,
boolean wasShortTermModelActive, boolean autobrightnessEnabled,
- boolean brightnessIsTemporary) {
+ boolean brightnessIsTemporary, boolean shouldUseAutoBrightness) {
final float brightnessInNits =
mDisplayBrightnessController.convertToAdjustedNits(brightness);
@@ -2235,7 +2290,7 @@
|| mAutomaticBrightnessController.isInIdleMode()
|| !autobrightnessEnabled
|| mBrightnessTracker == null
- || !mAutomaticBrightnessStrategy.shouldUseAutoBrightness()
+ || !shouldUseAutoBrightness
|| brightnessInNits < 0.0f) {
return;
}
@@ -2412,6 +2467,11 @@
if (mDisplayStateController != null) {
mDisplayStateController.dumpsys(pw);
}
+
+ pw.println();
+ if (mBrightnessClamperController != null) {
+ mBrightnessClamperController.dump(ipw);
+ }
}
@@ -2730,6 +2790,19 @@
mBootCompleted = true;
updatePowerState();
break;
+
+ case MSG_SET_DWBC_STRONG_MODE:
+ setDwbcStrongMode(msg.arg1);
+ break;
+
+ case MSG_SET_DWBC_COLOR_OVERRIDE:
+ final float cct = Float.intBitsToFloat(msg.arg1);
+ setDwbcOverride(cct);
+ break;
+
+ case MSG_SET_DWBC_LOGGING_ENABLED:
+ setDwbcLoggingEnabled(msg.arg1);
+ break;
}
}
}
@@ -2780,21 +2853,18 @@
@Override
public void setDisplayWhiteBalanceLoggingEnabled(boolean enabled) {
- if (mDisplayWhiteBalanceController != null) {
- mDisplayWhiteBalanceController.setLoggingEnabled(enabled);
- mDisplayWhiteBalanceSettings.setLoggingEnabled(enabled);
- }
+ Message msg = mHandler.obtainMessage();
+ msg.what = MSG_SET_DWBC_LOGGING_ENABLED;
+ msg.arg1 = enabled ? 1 : 0;
+ msg.sendToTarget();
}
@Override
public void setAmbientColorTemperatureOverride(float cct) {
- if (mDisplayWhiteBalanceController != null) {
- mDisplayWhiteBalanceController.setAmbientColorTemperatureOverride(cct);
- // The ambient color temperature override is only applied when the ambient color
- // temperature changes or is updated, so it doesn't necessarily change the screen color
- // temperature immediately. So, let's make it!
- sendUpdatePowerState();
- }
+ Message msg = mHandler.obtainMessage();
+ msg.what = MSG_SET_DWBC_COLOR_OVERRIDE;
+ msg.arg1 = Float.floatToIntBits(cct);
+ msg.sendToTarget();
}
/** Functional interface for providing time. */
@@ -2917,6 +2987,16 @@
displayUniqueId, brightnessMin, brightnessMax, hbmData, hdrBrightnessCfg,
hbmChangeCallback, hbmMetadata, context);
}
+
+ DisplayWhiteBalanceController getDisplayWhiteBalanceController(Handler handler,
+ SensorManager sensorManager, Resources resources) {
+ return DisplayWhiteBalanceFactory.create(handler,
+ sensorManager, resources);
+ }
+
+ boolean isColorFadeEnabled() {
+ return !ActivityManager.isLowRamDeviceStatic();
+ }
}
static class CachedBrightnessInfo {
diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
index d01b03f..26f8029 100644
--- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java
+++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
@@ -42,6 +42,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.display.layout.DisplayIdProducer;
import com.android.server.display.layout.Layout;
+import com.android.server.utils.FoldSettingWrapper;
import java.io.PrintWriter;
import java.util.Arrays;
@@ -142,6 +143,7 @@
private final Listener mListener;
private final DisplayManagerService.SyncRoot mSyncRoot;
private final LogicalDisplayMapperHandler mHandler;
+ private final FoldSettingWrapper mFoldSettingWrapper;
private final PowerManager mPowerManager;
/**
@@ -189,21 +191,23 @@
LogicalDisplayMapper(@NonNull Context context, @NonNull DisplayDeviceRepository repo,
@NonNull Listener listener, @NonNull DisplayManagerService.SyncRoot syncRoot,
- @NonNull Handler handler) {
+ @NonNull Handler handler, FoldSettingWrapper foldSettingWrapper) {
this(context, repo, listener, syncRoot, handler,
new DeviceStateToLayoutMap((isDefault) -> isDefault ? DEFAULT_DISPLAY
- : sNextNonDefaultDisplayId++));
+ : sNextNonDefaultDisplayId++), foldSettingWrapper);
}
LogicalDisplayMapper(@NonNull Context context, @NonNull DisplayDeviceRepository repo,
@NonNull Listener listener, @NonNull DisplayManagerService.SyncRoot syncRoot,
- @NonNull Handler handler, @NonNull DeviceStateToLayoutMap deviceStateToLayoutMap) {
+ @NonNull Handler handler, @NonNull DeviceStateToLayoutMap deviceStateToLayoutMap,
+ FoldSettingWrapper foldSettingWrapper) {
mSyncRoot = syncRoot;
mPowerManager = context.getSystemService(PowerManager.class);
mInteractive = mPowerManager.isInteractive();
mHandler = new LogicalDisplayMapperHandler(handler.getLooper());
mDisplayDeviceRepo = repo;
mListener = listener;
+ mFoldSettingWrapper = foldSettingWrapper;
mSingleDisplayDemoMode = SystemProperties.getBoolean("persist.demo.singledisplay", false);
mSupportsConcurrentInternalDisplays = context.getResources().getBoolean(
com.android.internal.R.bool.config_supportsConcurrentInternalDisplays);
@@ -531,9 +535,10 @@
* Returns if the device should be put to sleep or not.
*
* Includes a check to verify that the device state that we are moving to, {@code pendingState},
- * is the same as the physical state of the device, {@code baseState}. Different values for
- * these parameters indicate a device state override is active, and we shouldn't put the device
- * to sleep to provide a better user experience.
+ * is the same as the physical state of the device, {@code baseState}. Also if the
+ * 'Stay Awake On Fold' is not enabled. Different values for these parameters indicate a device
+ * state override is active, and we shouldn't put the device to sleep to provide a better user
+ * experience.
*
* @param pendingState device state we are moving to
* @param currentState device state we are currently in
@@ -551,7 +556,7 @@
&& mDeviceStatesOnWhichToSleep.get(pendingState)
&& !mDeviceStatesOnWhichToSleep.get(currentState)
&& !isOverrideActive
- && isInteractive && isBootCompleted;
+ && isInteractive && isBootCompleted && !mFoldSettingWrapper.shouldStayAwakeOnFold();
}
private boolean areAllTransitioningDisplaysOffLocked() {
diff --git a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
index 2f52b70..ffd62a3 100644
--- a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
+++ b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
@@ -29,6 +29,7 @@
import com.android.server.display.AutomaticBrightnessController;
import com.android.server.display.BrightnessSetting;
import com.android.server.display.DisplayBrightnessState;
+import com.android.server.display.brightness.strategy.AutomaticBrightnessStrategy;
import com.android.server.display.brightness.strategy.DisplayBrightnessStrategy;
import java.io.PrintWriter;
@@ -134,11 +135,21 @@
public DisplayBrightnessState updateBrightness(
DisplayManagerInternal.DisplayPowerRequest displayPowerRequest,
int targetDisplayState) {
+
+ DisplayBrightnessState state;
synchronized (mLock) {
mDisplayBrightnessStrategy = mDisplayBrightnessStrategySelector.selectStrategy(
displayPowerRequest, targetDisplayState);
- return mDisplayBrightnessStrategy.updateBrightness(displayPowerRequest);
+ state = mDisplayBrightnessStrategy.updateBrightness(displayPowerRequest);
}
+
+ // This is a temporary measure until AutomaticBrightnessStrategy works as a traditional
+ // strategy.
+ // TODO: Remove when AutomaticBrightnessStrategy is populating the values directly.
+ if (state != null) {
+ state = addAutomaticBrightnessState(state);
+ }
+ return state;
}
/**
@@ -322,6 +333,13 @@
}
/**
+ * TODO(b/253226419): Remove once auto-brightness is a fully-functioning strategy.
+ */
+ public AutomaticBrightnessStrategy getAutomaticBrightnessStrategy() {
+ return mDisplayBrightnessStrategySelector.getAutomaticBrightnessStrategy();
+ }
+
+ /**
* Convert a brightness float scale value to a nit value. Adjustments, such as RBC, are not
* applied. This is used when storing the brightness in nits for the default display and when
* passing the brightness value to follower displays.
@@ -425,6 +443,18 @@
}
}
+ /**
+ * TODO(b/253226419): Remove once auto-brightness is a fully-functioning strategy.
+ */
+ private DisplayBrightnessState addAutomaticBrightnessState(DisplayBrightnessState state) {
+ AutomaticBrightnessStrategy autoStrat = getAutomaticBrightnessStrategy();
+
+ DisplayBrightnessState.Builder builder = DisplayBrightnessState.Builder.from(state);
+ builder.setShouldUseAutoBrightness(
+ autoStrat != null && autoStrat.shouldUseAutoBrightness());
+ return builder.build();
+ }
+
@GuardedBy("mLock")
private void setTemporaryBrightnessLocked(float temporaryBrightness) {
mDisplayBrightnessStrategySelector.getTemporaryDisplayBrightnessStrategy()
diff --git a/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java b/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java
index 02ca2d3..45f1be0 100644
--- a/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java
+++ b/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java
@@ -25,6 +25,7 @@
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.display.brightness.strategy.AutomaticBrightnessStrategy;
import com.android.server.display.brightness.strategy.BoostBrightnessStrategy;
import com.android.server.display.brightness.strategy.DisplayBrightnessStrategy;
import com.android.server.display.brightness.strategy.DozeBrightnessStrategy;
@@ -60,6 +61,8 @@
private final FollowerBrightnessStrategy mFollowerBrightnessStrategy;
// The brightness strategy used to manage the brightness state when the request is invalid.
private final InvalidBrightnessStrategy mInvalidBrightnessStrategy;
+ // Controls brightness when automatic (adaptive) brightness is running.
+ private final AutomaticBrightnessStrategy mAutomaticBrightnessStrategy;
// We take note of the old brightness strategy so that we can know when the strategy changes.
private String mOldBrightnessStrategyName;
@@ -81,6 +84,7 @@
mBoostBrightnessStrategy = injector.getBoostBrightnessStrategy();
mFollowerBrightnessStrategy = injector.getFollowerBrightnessStrategy(displayId);
mInvalidBrightnessStrategy = injector.getInvalidBrightnessStrategy();
+ mAutomaticBrightnessStrategy = injector.getAutomaticBrightnessStrategy(context, displayId);
mAllowAutoBrightnessWhileDozingConfig = context.getResources().getBoolean(
R.bool.config_allowAutoBrightnessWhileDozing);
mOldBrightnessStrategyName = mInvalidBrightnessStrategy.getName();
@@ -130,6 +134,10 @@
return mFollowerBrightnessStrategy;
}
+ public AutomaticBrightnessStrategy getAutomaticBrightnessStrategy() {
+ return mAutomaticBrightnessStrategy;
+ }
+
/**
* Returns a boolean flag indicating if the light sensor is to be used to decide the screen
* brightness when dozing
@@ -198,5 +206,9 @@
InvalidBrightnessStrategy getInvalidBrightnessStrategy() {
return new InvalidBrightnessStrategy();
}
+
+ AutomaticBrightnessStrategy getAutomaticBrightnessStrategy(Context context, int displayId) {
+ return new AutomaticBrightnessStrategy(context, displayId);
+ }
}
}
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java
new file mode 100644
index 0000000..9345a3d
--- /dev/null
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java
@@ -0,0 +1,55 @@
+/*
+ * 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.display.brightness.clamper;
+
+import android.annotation.NonNull;
+import android.os.PowerManager;
+
+import java.io.PrintWriter;
+
+abstract class BrightnessClamper<T> {
+
+ protected float mBrightnessCap = PowerManager.BRIGHTNESS_MAX;
+ protected boolean mIsActive = false;
+
+ float getBrightnessCap() {
+ return mBrightnessCap;
+ }
+
+ boolean isActive() {
+ return mIsActive;
+ }
+
+ void dump(PrintWriter writer) {
+ writer.println("BrightnessClamper:" + getType());
+ writer.println(" mBrightnessCap: " + mBrightnessCap);
+ writer.println(" mIsActive: " + mIsActive);
+ }
+
+ @NonNull
+ abstract Type getType();
+
+ abstract void onDeviceConfigChanged();
+
+ abstract void onDisplayChanged(T displayData);
+
+ abstract void stop();
+
+ enum Type {
+ THERMAL
+ }
+}
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
new file mode 100644
index 0000000..d0f28c3
--- /dev/null
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
@@ -0,0 +1,207 @@
+/*
+ * 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.display.brightness.clamper;
+
+import static com.android.server.display.brightness.clamper.BrightnessClamper.Type;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Handler;
+import android.os.HandlerExecutor;
+import android.os.PowerManager;
+import android.provider.DeviceConfig;
+import android.provider.DeviceConfigInterface;
+import android.util.IndentingPrintWriter;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.display.DisplayDeviceConfig;
+import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData;
+import com.android.server.display.feature.DeviceConfigParameterProvider;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Executor;
+
+/**
+ * Clampers controller, all in DisplayControllerHandler
+ */
+public class BrightnessClamperController {
+
+ private static final boolean ENABLED = false;
+
+ private final DeviceConfigParameterProvider mDeviceConfigParameterProvider;
+ private final Handler mHandler;
+ private final ClamperChangeListener mClamperChangeListenerExternal;
+
+ private final Executor mExecutor;
+ private final List<BrightnessClamper<? super DisplayDeviceData>> mClampers = new ArrayList<>();
+ private final DeviceConfig.OnPropertiesChangedListener mOnPropertiesChangedListener =
+ properties -> mClampers.forEach(BrightnessClamper::onDeviceConfigChanged);
+ private float mBrightnessCap = PowerManager.BRIGHTNESS_MAX;
+ @Nullable
+ private Type mClamperType = null;
+
+ public BrightnessClamperController(Handler handler,
+ ClamperChangeListener clamperChangeListener, DisplayDeviceData data) {
+ this(new Injector(), handler, clamperChangeListener, data);
+ }
+
+ @VisibleForTesting
+ BrightnessClamperController(Injector injector, Handler handler,
+ ClamperChangeListener clamperChangeListener, DisplayDeviceData data) {
+ mDeviceConfigParameterProvider = injector.getDeviceConfigParameterProvider();
+ mHandler = handler;
+ mClamperChangeListenerExternal = clamperChangeListener;
+ mExecutor = new HandlerExecutor(handler);
+
+ Runnable clamperChangeRunnableInternal = this::recalculateBrightnessCap;
+
+ ClamperChangeListener clamperChangeListenerInternal = () -> {
+ if (!mHandler.hasCallbacks(clamperChangeRunnableInternal)) {
+ mHandler.post(clamperChangeRunnableInternal);
+ }
+ };
+
+ if (ENABLED) {
+ mClampers.add(
+ new BrightnessThermalClamper(handler, clamperChangeListenerInternal, data));
+ start();
+ }
+ }
+
+ /**
+ * Should be called when display changed. Forwards the call to individual clampers
+ */
+ public void onDisplayChanged(DisplayDeviceData data) {
+ mClampers.forEach(clamper -> clamper.onDisplayChanged(data));
+ }
+
+ /**
+ * Applies clamping
+ * Called in DisplayControllerHandler
+ */
+ public float clamp(float value) {
+ return Math.min(value, mBrightnessCap);
+ }
+
+ /**
+ * Used to dump ClampersController state.
+ */
+ public void dump(PrintWriter writer) {
+ writer.println("BrightnessClampersController:");
+ writer.println(" mBrightnessCap: " + mBrightnessCap);
+ writer.println(" mClamperType: " + mClamperType);
+ IndentingPrintWriter ipw = new IndentingPrintWriter(writer, " ");
+ mClampers.forEach(clamper -> clamper.dump(ipw));
+ }
+
+ /**
+ * This method should be called when the ClamperController is no longer in use.
+ * Called in DisplayControllerHandler
+ */
+ public void stop() {
+ mDeviceConfigParameterProvider.removeOnPropertiesChangedListener(
+ mOnPropertiesChangedListener);
+ mClampers.forEach(BrightnessClamper::stop);
+ }
+
+
+ // Called in DisplayControllerHandler
+ private void recalculateBrightnessCap() {
+ float brightnessCap = PowerManager.BRIGHTNESS_MAX;
+ Type clamperType = null;
+
+ BrightnessClamper<?> minClamper = mClampers.stream()
+ .filter(BrightnessClamper::isActive)
+ .min((clamper1, clamper2) -> Float.compare(clamper1.getBrightnessCap(),
+ clamper2.getBrightnessCap())).orElse(null);
+
+ if (minClamper != null) {
+ brightnessCap = minClamper.getBrightnessCap();
+ clamperType = minClamper.getType();
+ }
+
+ if (mBrightnessCap != brightnessCap || mClamperType != clamperType) {
+ mBrightnessCap = brightnessCap;
+ mClamperType = clamperType;
+ mClamperChangeListenerExternal.onChanged();
+ }
+ }
+
+ private void start() {
+ mDeviceConfigParameterProvider.addOnPropertiesChangedListener(
+ mExecutor, mOnPropertiesChangedListener);
+ }
+
+ /**
+ * Clampers change listener
+ */
+ public interface ClamperChangeListener {
+ /**
+ * Notifies that clamper state changed
+ */
+ void onChanged();
+ }
+
+ @VisibleForTesting
+ static class Injector {
+ DeviceConfigParameterProvider getDeviceConfigParameterProvider() {
+ return new DeviceConfigParameterProvider(DeviceConfigInterface.REAL);
+ }
+ }
+
+ /**
+ * Data for clampers
+ */
+ public static class DisplayDeviceData implements BrightnessThermalClamper.ThermalData {
+ @NonNull
+ private final String mUniqueDisplayId;
+ @NonNull
+ private final String mThermalThrottlingDataId;
+
+ private final DisplayDeviceConfig mDisplayDeviceConfig;
+
+ public DisplayDeviceData(@NonNull String uniqueDisplayId,
+ @NonNull String thermalThrottlingDataId,
+ @NonNull DisplayDeviceConfig displayDeviceConfig) {
+ mUniqueDisplayId = uniqueDisplayId;
+ mThermalThrottlingDataId = thermalThrottlingDataId;
+ mDisplayDeviceConfig = displayDeviceConfig;
+ }
+
+
+ @NonNull
+ @Override
+ public String getUniqueDisplayId() {
+ return mUniqueDisplayId;
+ }
+
+ @NonNull
+ @Override
+ public String getThermalThrottlingDataId() {
+ return mThermalThrottlingDataId;
+ }
+
+ @Nullable
+ @Override
+ public ThermalBrightnessThrottlingData getThermalBrightnessThrottlingData() {
+ return mDisplayDeviceConfig.getThermalBrightnessThrottlingDataMapByThrottlingId().get(
+ mThermalThrottlingDataId);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessThermalClamper.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessThermalClamper.java
new file mode 100644
index 0000000..8ae962b
--- /dev/null
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessThermalClamper.java
@@ -0,0 +1,272 @@
+/*
+ * 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.display.brightness.clamper;
+
+import static com.android.server.display.DisplayDeviceConfig.DEFAULT_ID;
+import static com.android.server.display.brightness.clamper.BrightnessClamperController.ClamperChangeListener;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.os.Handler;
+import android.os.IThermalEventListener;
+import android.os.IThermalService;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.Temperature;
+import android.provider.DeviceConfigInterface;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData;
+import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel;
+import com.android.server.display.feature.DeviceConfigParameterProvider;
+import com.android.server.display.utils.DeviceConfigParsingUtils;
+
+import java.io.PrintWriter;
+import java.util.List;
+import java.util.Map;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+
+
+class BrightnessThermalClamper extends
+ BrightnessClamper<BrightnessThermalClamper.ThermalData> {
+
+ private static final String TAG = "BrightnessThermalClamper";
+
+ @Nullable
+ private final IThermalService mThermalService;
+ @NonNull
+ private final DeviceConfigParameterProvider mConfigParameterProvider;
+ @NonNull
+ private final Handler mHandler;
+ @NonNull
+ private final ClamperChangeListener mChangelistener;
+ // data from DeviceConfig, for all displays, for all dataSets
+ // mapOf(uniqueDisplayId to mapOf(dataSetId to ThermalBrightnessThrottlingData))
+ @NonNull
+ private Map<String, Map<String, ThermalBrightnessThrottlingData>>
+ mThermalThrottlingDataOverride = Map.of();
+ // data from DisplayDeviceConfig, for particular display+dataSet
+ @Nullable
+ private ThermalBrightnessThrottlingData mThermalThrottlingDataFromDeviceConfig = null;
+ // Active data, if mDataOverride contains data for mUniqueDisplayId, mDataId, then use it,
+ // otherwise mDataFromDeviceConfig
+ @Nullable
+ private ThermalBrightnessThrottlingData mThermalThrottlingDataActive = null;
+ private boolean mStarted = false;
+ @Nullable
+ private String mUniqueDisplayId = null;
+ @Nullable
+ private String mDataId = null;
+ @Temperature.ThrottlingStatus
+ private int mThrottlingStatus = Temperature.THROTTLING_NONE;
+
+ private final IThermalEventListener mThermalEventListener = new IThermalEventListener.Stub() {
+ @Override
+ public void notifyThrottling(Temperature temperature) {
+ @Temperature.ThrottlingStatus int status = temperature.getStatus();
+ mHandler.post(() -> thermalStatusChanged(status));
+ }
+ };
+
+ private final BiFunction<String, String, ThrottlingLevel> mDataPointMapper = (key, value) -> {
+ try {
+ int status = DeviceConfigParsingUtils.parseThermalStatus(key);
+ float brightnessPoint = DeviceConfigParsingUtils.parseBrightness(value);
+ return new ThrottlingLevel(status, brightnessPoint);
+ } catch (IllegalArgumentException iae) {
+ return null;
+ }
+ };
+
+ private final Function<List<ThrottlingLevel>, ThermalBrightnessThrottlingData>
+ mDataSetMapper = ThermalBrightnessThrottlingData::create;
+
+
+ BrightnessThermalClamper(Handler handler, ClamperChangeListener listener,
+ ThermalData thermalData) {
+ this(new Injector(), handler, listener, thermalData);
+ }
+
+ @VisibleForTesting
+ BrightnessThermalClamper(Injector injector, Handler handler,
+ ClamperChangeListener listener, ThermalData thermalData) {
+ mThermalService = injector.getThermalService();
+ mConfigParameterProvider = injector.getDeviceConfigParameterProvider();
+ mHandler = handler;
+ mChangelistener = listener;
+ mHandler.post(() -> {
+ setDisplayData(thermalData);
+ loadOverrideData();
+ start();
+ });
+
+ }
+
+ @Override
+ @NonNull
+ Type getType() {
+ return Type.THERMAL;
+ }
+
+ @Override
+ void onDeviceConfigChanged() {
+ mHandler.post(() -> {
+ loadOverrideData();
+ recalculateActiveData();
+ });
+ }
+
+ @Override
+ void onDisplayChanged(ThermalData data) {
+ mHandler.post(() -> {
+ setDisplayData(data);
+ recalculateActiveData();
+ });
+ }
+
+ @Override
+ void stop() {
+ if (!mStarted) {
+ return;
+ }
+ try {
+ mThermalService.unregisterThermalEventListener(mThermalEventListener);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to unregister thermal status listener", e);
+ }
+ mStarted = false;
+ }
+
+ @Override
+ void dump(PrintWriter writer) {
+ writer.println("BrightnessThermalClamper:");
+ writer.println(" mStarted: " + mStarted);
+ if (mThermalService != null) {
+ writer.println(" ThermalService available");
+ } else {
+ writer.println(" ThermalService not available");
+ }
+ writer.println(" mThrottlingStatus: " + mThrottlingStatus);
+ writer.println(" mUniqueDisplayId: " + mUniqueDisplayId);
+ writer.println(" mDataId: " + mDataId);
+ writer.println(" mDataOverride: " + mThermalThrottlingDataOverride);
+ writer.println(" mDataFromDeviceConfig: " + mThermalThrottlingDataFromDeviceConfig);
+ writer.println(" mDataActive: " + mThermalThrottlingDataActive);
+ super.dump(writer);
+ }
+
+ private void recalculateActiveData() {
+ if (mUniqueDisplayId == null || mDataId == null) {
+ return;
+ }
+ mThermalThrottlingDataActive = mThermalThrottlingDataOverride
+ .getOrDefault(mUniqueDisplayId, Map.of()).getOrDefault(mDataId,
+ mThermalThrottlingDataFromDeviceConfig);
+
+ recalculateBrightnessCap();
+ }
+
+ private void loadOverrideData() {
+ String throttlingDataOverride = mConfigParameterProvider.getBrightnessThrottlingData();
+ mThermalThrottlingDataOverride = DeviceConfigParsingUtils.parseDeviceConfigMap(
+ throttlingDataOverride, mDataPointMapper, mDataSetMapper);
+ }
+
+ private void setDisplayData(@NonNull ThermalData data) {
+ mUniqueDisplayId = data.getUniqueDisplayId();
+ mDataId = data.getThermalThrottlingDataId();
+ mThermalThrottlingDataFromDeviceConfig = data.getThermalBrightnessThrottlingData();
+ if (mThermalThrottlingDataFromDeviceConfig == null && !DEFAULT_ID.equals(mDataId)) {
+ Slog.wtf(TAG,
+ "Thermal throttling data is missing for thermalThrottlingDataId=" + mDataId);
+ }
+ }
+
+ private void recalculateBrightnessCap() {
+ float brightnessCap = PowerManager.BRIGHTNESS_MAX;
+ boolean isActive = false;
+
+ if (mThermalThrottlingDataActive != null) {
+ // Throttling levels are sorted by increasing severity
+ for (ThrottlingLevel level : mThermalThrottlingDataActive.throttlingLevels) {
+ if (level.thermalStatus <= mThrottlingStatus) {
+ brightnessCap = level.brightness;
+ isActive = true;
+ } else {
+ // Throttling levels that are greater than the current status are irrelevant
+ break;
+ }
+ }
+ }
+
+ if (brightnessCap != mBrightnessCap || mIsActive != isActive) {
+ mBrightnessCap = brightnessCap;
+ mIsActive = isActive;
+ mChangelistener.onChanged();
+ }
+ }
+
+ private void thermalStatusChanged(@Temperature.ThrottlingStatus int status) {
+ if (mThrottlingStatus != status) {
+ mThrottlingStatus = status;
+ recalculateBrightnessCap();
+ }
+ }
+
+ private void start() {
+ if (mThermalService == null) {
+ Slog.e(TAG, "Could not observe thermal status. Service not available");
+ return;
+ }
+ try {
+ // We get a callback immediately upon registering so there's no need to query
+ // for the current value.
+ mThermalService.registerThermalEventListenerWithType(mThermalEventListener,
+ Temperature.TYPE_SKIN);
+ mStarted = true;
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to register thermal status listener", e);
+ }
+ }
+
+ interface ThermalData {
+ @NonNull
+ String getUniqueDisplayId();
+
+ @NonNull
+ String getThermalThrottlingDataId();
+
+ @Nullable
+ ThermalBrightnessThrottlingData getThermalBrightnessThrottlingData();
+ }
+
+ @VisibleForTesting
+ static class Injector {
+ IThermalService getThermalService() {
+ return IThermalService.Stub.asInterface(
+ ServiceManager.getService(Context.THERMAL_SERVICE));
+ }
+
+ DeviceConfigParameterProvider getDeviceConfigParameterProvider() {
+ return new DeviceConfigParameterProvider(DeviceConfigInterface.REAL);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/display/utils/DeviceConfigParsingUtils.java b/services/core/java/com/android/server/display/utils/DeviceConfigParsingUtils.java
new file mode 100644
index 0000000..a8034c5
--- /dev/null
+++ b/services/core/java/com/android/server/display/utils/DeviceConfigParsingUtils.java
@@ -0,0 +1,152 @@
+/*
+ * 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.display.utils;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.PowerManager;
+import android.util.Slog;
+
+import com.android.server.display.DisplayDeviceConfig;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+
+/**
+ * Provides utility methods for DeviceConfig string parsing
+ */
+public class DeviceConfigParsingUtils {
+ private static final String TAG = "DeviceConfigParsingUtils";
+
+ /**
+ * Parses map from device config
+ * Data format:
+ * (displayId:String,numberOfPoints:Int,(state:T,value:Float){numberOfPoints},
+ * dataSetId:String)?;)+
+ * result : mapOf(displayId to mapOf(dataSetId to V))
+ */
+ @NonNull
+ public static <T, V> Map<String, Map<String, V>> parseDeviceConfigMap(
+ @Nullable String data,
+ @NonNull BiFunction<String, String, T> dataPointMapper,
+ @NonNull Function<List<T>, V> dataSetMapper) {
+ if (data == null) {
+ return Map.of();
+ }
+ Map<String, Map<String, V>> result = new HashMap<>();
+ String[] dataSets = data.split(";"); // by displayId + dataSetId
+ for (String dataSet : dataSets) {
+ String[] items = dataSet.split(",");
+ int noOfItems = items.length;
+ // Validate number of items, at least: displayId,1,key1,value1
+ if (noOfItems < 4) {
+ Slog.e(TAG, "Invalid dataSet(not enough items):" + dataSet, new Throwable());
+ return Map.of();
+ }
+ int i = 0;
+ String uniqueDisplayId = items[i++];
+
+ String numberOfPointsString = items[i++];
+ int numberOfPoints;
+ try {
+ numberOfPoints = Integer.parseInt(numberOfPointsString);
+ } catch (NumberFormatException nfe) {
+ Slog.e(TAG, "Invalid dataSet(invalid number of points):" + dataSet, nfe);
+ return Map.of();
+ }
+ // Validate number of itmes based on numberOfPoints:
+ // displayId,numberOfPoints,(key,value) x numberOfPoints,dataSetId(optional)
+ int expectedMinItems = 2 + numberOfPoints * 2;
+ if (noOfItems < expectedMinItems || noOfItems > expectedMinItems + 1) {
+ Slog.e(TAG, "Invalid dataSet(wrong number of points):" + dataSet, new Throwable());
+ return Map.of();
+ }
+ // Construct data points
+ List<T> dataPoints = new ArrayList<>();
+ for (int j = 0; j < numberOfPoints; j++) {
+ String key = items[i++];
+ String value = items[i++];
+ T dataPoint = dataPointMapper.apply(key, value);
+ if (dataPoint == null) {
+ Slog.e(TAG,
+ "Invalid dataPoint ,key=" + key + ",value=" + value + ",dataSet="
+ + dataSet, new Throwable());
+ return Map.of();
+ }
+ dataPoints.add(dataPoint);
+ }
+ // Construct dataSet
+ V dataSetMapped = dataSetMapper.apply(dataPoints);
+ if (dataSetMapped == null) {
+ Slog.e(TAG, "Invalid dataSetMapped dataPoints=" + dataPoints + ",dataSet="
+ + dataSet, new Throwable());
+ return Map.of();
+ }
+ // Get dataSetId and dataSets map for displayId
+ String dataSetId = (i < items.length) ? items[i] : DisplayDeviceConfig.DEFAULT_ID;
+ Map<String, V> byDisplayId = result.computeIfAbsent(uniqueDisplayId,
+ k -> new HashMap<>());
+
+ // Try to store dataSet in datasets for display
+ if (byDisplayId.put(dataSetId, dataSetMapped) != null) {
+ Slog.e(TAG, "Duplicate dataSetId=" + dataSetId + ",data=" + data, new Throwable());
+ return Map.of();
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Parses thermal string value from device config
+ */
+ @PowerManager.ThermalStatus
+ public static int parseThermalStatus(@NonNull String value) throws IllegalArgumentException {
+ switch (value) {
+ case "none":
+ return PowerManager.THERMAL_STATUS_NONE;
+ case "light":
+ return PowerManager.THERMAL_STATUS_LIGHT;
+ case "moderate":
+ return PowerManager.THERMAL_STATUS_MODERATE;
+ case "severe":
+ return PowerManager.THERMAL_STATUS_SEVERE;
+ case "critical":
+ return PowerManager.THERMAL_STATUS_CRITICAL;
+ case "emergency":
+ return PowerManager.THERMAL_STATUS_EMERGENCY;
+ case "shutdown":
+ return PowerManager.THERMAL_STATUS_SHUTDOWN;
+ default:
+ throw new IllegalArgumentException("Invalid Thermal Status: " + value);
+ }
+ }
+
+ /**
+ * Parses brightness value from device config
+ */
+ public static float parseBrightness(String stringVal) throws IllegalArgumentException {
+ float value = Float.parseFloat(stringVal);
+ if (value < PowerManager.BRIGHTNESS_MIN || value > PowerManager.BRIGHTNESS_MAX) {
+ throw new IllegalArgumentException("Brightness value out of bounds: " + stringVal);
+ }
+ return value;
+ }
+}
diff --git a/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceController.java b/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceController.java
index 5b772fc..4ad26c4 100644
--- a/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceController.java
+++ b/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceController.java
@@ -37,8 +37,11 @@
* - Uses the AmbientColorTemperatureSensor to detect changes in the ambient color temperature;
* - Uses the AmbientColorTemperatureFilter to average these changes over time, filter out the
* noise, and arrive at an estimate of the actual ambient color temperature;
- * - Uses the DisplayWhiteBalanceThrottler to decide whether the display color tempearture should
+ * - Uses the DisplayWhiteBalanceThrottler to decide whether the display color temperature should
* be updated, suppressing changes that are too frequent or too minor.
+ *
+ * Calls to this class must happen on the DisplayPowerController(2) handler, to ensure
+ * values do not get out of sync.
*/
public class DisplayWhiteBalanceController implements
AmbientSensor.AmbientBrightnessSensor.Callbacks,
diff --git a/services/core/java/com/android/server/graphics/fonts/FontManagerService.java b/services/core/java/com/android/server/graphics/fonts/FontManagerService.java
index 080b947..c6a50ed 100644
--- a/services/core/java/com/android/server/graphics/fonts/FontManagerService.java
+++ b/services/core/java/com/android/server/graphics/fonts/FontManagerService.java
@@ -46,6 +46,7 @@
import com.android.internal.util.DumpUtils;
import com.android.internal.util.Preconditions;
import com.android.server.LocalServices;
+import com.android.server.SystemServerInitThreadPool;
import com.android.server.SystemService;
import java.io.File;
@@ -61,6 +62,7 @@
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.concurrent.CompletableFuture;
/** A service for managing system fonts. */
public final class FontManagerService extends IFontManager.Stub {
@@ -136,10 +138,11 @@
/** Class to manage FontManagerService's lifecycle. */
public static final class Lifecycle extends SystemService {
private final FontManagerService mService;
+ private final CompletableFuture<Void> mServiceStarted = new CompletableFuture<>();
public Lifecycle(@NonNull Context context, boolean safeMode) {
super(context);
- mService = new FontManagerService(context, safeMode);
+ mService = new FontManagerService(context, safeMode, mServiceStarted);
}
@Override
@@ -152,11 +155,20 @@
if (!Typeface.ENABLE_LAZY_TYPEFACE_INITIALIZATION) {
return null;
}
+ mServiceStarted.join();
return mService.getCurrentFontMap();
}
});
publishBinderService(Context.FONT_SERVICE, mService);
}
+
+ @Override
+ public void onBootPhase(int phase) {
+ if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) {
+ // Wait for FontManagerService to start since it will be needed after this point.
+ mServiceStarted.join();
+ }
+ }
}
private static class FsverityUtilImpl implements UpdatableFontDir.FsverityUtil {
@@ -219,25 +231,30 @@
@Nullable
private SharedMemory mSerializedFontMap = null;
- private FontManagerService(Context context, boolean safeMode) {
+ private FontManagerService(
+ Context context, boolean safeMode, CompletableFuture<Void> serviceStarted) {
if (safeMode) {
Slog.i(TAG, "Entering safe mode. Deleting all font updates.");
UpdatableFontDir.deleteAllFiles(new File(FONT_FILES_DIR), new File(CONFIG_XML_FILE));
}
mContext = context;
mIsSafeMode = safeMode;
- initialize();
- // Set system font map only if there is updatable font directory.
- // If there is no updatable font directory, `initialize` will have already loaded the
- // system font map, so there's no need to set the system font map again here.
- if (mUpdatableFontDir != null) {
- try {
- Typeface.setSystemFontMap(getCurrentFontMap());
- } catch (IOException | ErrnoException e) {
- Slog.w(TAG, "Failed to set system font map of system_server");
+ SystemServerInitThreadPool.submit(() -> {
+ initialize();
+
+ // Set system font map only if there is updatable font directory.
+ // If there is no updatable font directory, `initialize` will have already loaded the
+ // system font map, so there's no need to set the system font map again here.
+ if (mUpdatableFontDir != null) {
+ try {
+ Typeface.setSystemFontMap(getCurrentFontMap());
+ } catch (IOException | ErrnoException e) {
+ Slog.w(TAG, "Failed to set system font map of system_server");
+ }
}
- }
+ serviceStarted.complete(null);
+ }, "FontManagerService_create");
}
@Nullable
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index a7e78be..dd45307 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -4855,11 +4855,13 @@
@ServiceThreadOnly
void handleEarcStateChange(int status, int portId) {
assertRunOnServiceThread();
+ int oldEarcStatus = getEarcStatus();
if (!getPortInfo(portId).isEarcSupported()) {
Slog.w(TAG, "Tried to update eARC status on a port that doesn't support eARC.");
+ getAtomWriter().earcStatusChanged(isEarcSupported(), isEarcEnabled(), oldEarcStatus,
+ status, HdmiStatsEnums.LOG_REASON_EARC_STATUS_CHANGED_UNSUPPORTED_PORT);
return;
}
- int oldEarcStatus = getEarcStatus();
if (mEarcLocalDevice != null) {
mEarcLocalDevice.handleEarcStateChange(status);
getAtomWriter().earcStatusChanged(isEarcSupported(), isEarcEnabled(),
@@ -4872,6 +4874,10 @@
startArcAction(true, null);
getAtomWriter().earcStatusChanged(isEarcSupported(), isEarcEnabled(),
oldEarcStatus, status, HdmiStatsEnums.LOG_REASON_EARC_STATUS_CHANGED);
+ } else {
+ getAtomWriter().earcStatusChanged(isEarcSupported(), isEarcEnabled(),
+ oldEarcStatus, status,
+ HdmiStatsEnums.LOG_REASON_EARC_STATUS_CHANGED_WRONG_STATE);
}
}
diff --git a/services/core/java/com/android/server/input/KeyboardLayoutManager.java b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
index 3ac1594..7a8de34 100644
--- a/services/core/java/com/android/server/input/KeyboardLayoutManager.java
+++ b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
@@ -16,6 +16,7 @@
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;
@@ -28,6 +29,7 @@
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
+import android.app.settings.SettingsEnums;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
@@ -1168,6 +1170,8 @@
if (targetDevice != null) {
intent.putExtra(Settings.EXTRA_INPUT_DEVICE_IDENTIFIER, targetDevice.getIdentifier());
+ intent.putExtra(
+ Settings.EXTRA_ENTRYPOINT, SettingsEnums.KEYBOARD_CONFIGURED_NOTIFICATION);
}
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
@@ -1269,7 +1273,7 @@
boolean noLayoutFound = layoutInfo == null || layoutInfo.mDescriptor == null;
configurationEventBuilder.addLayoutSelection(imeInfoList.get(i).mImeSubtype,
noLayoutFound ? null : getKeyboardLayout(layoutInfo.mDescriptor),
- noLayoutFound ? LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD
+ noLayoutFound ? LAYOUT_SELECTION_CRITERIA_DEFAULT
: layoutInfo.mSelectionCriteria);
}
KeyboardMetricsCollector.logKeyboardConfiguredAtom(configurationEventBuilder.build());
diff --git a/services/core/java/com/android/server/input/KeyboardMetricsCollector.java b/services/core/java/com/android/server/input/KeyboardMetricsCollector.java
index 19fa7a8..eb2da34 100644
--- a/services/core/java/com/android/server/input/KeyboardMetricsCollector.java
+++ b/services/core/java/com/android/server/input/KeyboardMetricsCollector.java
@@ -53,7 +53,8 @@
@IntDef(prefix = { "LAYOUT_SELECTION_CRITERIA_" }, value = {
LAYOUT_SELECTION_CRITERIA_USER,
LAYOUT_SELECTION_CRITERIA_DEVICE,
- LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD
+ LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD,
+ LAYOUT_SELECTION_CRITERIA_DEFAULT
})
public @interface LayoutSelectionCriteria {}
@@ -66,9 +67,15 @@
/** Auto-detection based on IME provided language tag and layout type */
public static final int LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD = 2;
+ /** Default selection */
+ public static final int LAYOUT_SELECTION_CRITERIA_DEFAULT = 3;
+
@VisibleForTesting
static final String DEFAULT_LAYOUT = "Default";
+ @VisibleForTesting
+ static final String DEFAULT_LANGUAGE_TAG = "None";
+
/**
* Log keyboard system shortcuts for the proto
* {@link com.android.os.input.KeyboardSystemsEventReported}
@@ -131,6 +138,10 @@
layoutConfiguration.keyboardLayoutName);
proto.write(KeyboardLayoutConfig.LAYOUT_SELECTION_CRITERIA,
layoutConfiguration.layoutSelectionCriteria);
+ proto.write(KeyboardLayoutConfig.IME_LANGUAGE_TAG,
+ layoutConfiguration.imeLanguageTag);
+ proto.write(KeyboardLayoutConfig.IME_LAYOUT_TYPE,
+ layoutConfiguration.imeLayoutType);
proto.end(keyboardLayoutConfigToken);
}
@@ -231,27 +242,29 @@
@LayoutSelectionCriteria int layoutSelectionCriteria =
mLayoutSelectionCriteriaList.get(i);
InputMethodSubtype imeSubtype = mImeSubtypeList.get(i);
- String keyboardLanguageTag;
- String keyboardLayoutStringType;
- if (layoutSelectionCriteria == LAYOUT_SELECTION_CRITERIA_DEVICE) {
- keyboardLanguageTag = mInputDevice.getKeyboardLanguageTag();
- keyboardLayoutStringType = mInputDevice.getKeyboardLayoutType();
- } else {
- ULocale pkLocale = imeSubtype.getPhysicalKeyboardHintLanguageTag();
- keyboardLanguageTag = pkLocale != null ? pkLocale.toLanguageTag()
- : imeSubtype.getCanonicalizedLanguageTag();
- keyboardLayoutStringType = imeSubtype.getPhysicalKeyboardHintLayoutType();
- }
+ String keyboardLanguageTag = mInputDevice.getKeyboardLanguageTag();
+ keyboardLanguageTag = keyboardLanguageTag == null ? DEFAULT_LANGUAGE_TAG
+ : keyboardLanguageTag;
+ int keyboardLayoutType = KeyboardLayout.LayoutType.getLayoutTypeEnumValue(
+ mInputDevice.getKeyboardLayoutType());
+
+ ULocale pkLocale = imeSubtype.getPhysicalKeyboardHintLanguageTag();
+ String canonicalizedLanguageTag =
+ imeSubtype.getCanonicalizedLanguageTag().equals("")
+ ? DEFAULT_LANGUAGE_TAG : imeSubtype.getCanonicalizedLanguageTag();
+ String imeLanguageTag = pkLocale != null ? pkLocale.toLanguageTag()
+ : canonicalizedLanguageTag;
+ int imeLayoutType = KeyboardLayout.LayoutType.getLayoutTypeEnumValue(
+ imeSubtype.getPhysicalKeyboardHintLayoutType());
+
// Sanitize null values
String keyboardLayoutName =
selectedLayout == null ? DEFAULT_LAYOUT : selectedLayout.getLabel();
- keyboardLanguageTag = keyboardLanguageTag == null ? "" : keyboardLanguageTag;
- int keyboardLayoutType = KeyboardLayout.LayoutType.getLayoutTypeEnumValue(
- keyboardLayoutStringType);
configurationList.add(
new LayoutConfiguration(keyboardLayoutType, keyboardLanguageTag,
- keyboardLayoutName, layoutSelectionCriteria));
+ keyboardLayoutName, layoutSelectionCriteria,
+ imeLayoutType, imeLanguageTag));
}
return new KeyboardConfigurationEvent(mInputDevice, mIsFirstConfiguration,
configurationList);
@@ -267,13 +280,18 @@
public final String keyboardLayoutName;
@LayoutSelectionCriteria
public final int layoutSelectionCriteria;
+ public final int imeLayoutType;
+ public final String imeLanguageTag;
private LayoutConfiguration(int keyboardLayoutType, String keyboardLanguageTag,
- String keyboardLayoutName, @LayoutSelectionCriteria int layoutSelectionCriteria) {
+ String keyboardLayoutName, @LayoutSelectionCriteria int layoutSelectionCriteria,
+ int imeLayoutType, String imeLanguageTag) {
this.keyboardLayoutType = keyboardLayoutType;
this.keyboardLanguageTag = keyboardLanguageTag;
this.keyboardLayoutName = keyboardLayoutName;
this.layoutSelectionCriteria = layoutSelectionCriteria;
+ this.imeLayoutType = imeLayoutType;
+ this.imeLanguageTag = imeLanguageTag;
}
@Override
@@ -281,7 +299,9 @@
return "{keyboardLanguageTag = " + keyboardLanguageTag + " keyboardLayoutType = "
+ KeyboardLayout.LayoutType.getLayoutNameFromValue(keyboardLayoutType)
+ " keyboardLayoutName = " + keyboardLayoutName + " layoutSelectionCriteria = "
- + getStringForSelectionCriteria(layoutSelectionCriteria) + "}";
+ + getStringForSelectionCriteria(layoutSelectionCriteria)
+ + "imeLanguageTag = " + imeLanguageTag + " imeLayoutType = "
+ + KeyboardLayout.LayoutType.getLayoutNameFromValue(imeLayoutType) + "}";
}
}
@@ -294,6 +314,8 @@
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";
}
@@ -302,7 +324,8 @@
private static boolean isValidSelectionCriteria(int layoutSelectionCriteria) {
return layoutSelectionCriteria == LAYOUT_SELECTION_CRITERIA_USER
|| layoutSelectionCriteria == LAYOUT_SELECTION_CRITERIA_DEVICE
- || layoutSelectionCriteria == LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD;
+ || layoutSelectionCriteria == LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD
+ || layoutSelectionCriteria == LAYOUT_SELECTION_CRITERIA_DEFAULT;
}
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 02ee96a..7bda2c1 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -1175,6 +1175,8 @@
Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD), false, this, userId);
resolver.registerContentObserver(Settings.Secure.getUriFor(
Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE), false, this, userId);
+ resolver.registerContentObserver(Settings.Secure.getUriFor(
+ STYLUS_HANDWRITING_ENABLED), false, this);
mRegistered = true;
}
@@ -1183,6 +1185,8 @@
Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD);
final Uri accessibilityRequestingNoImeUri = Settings.Secure.getUriFor(
Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE);
+ final Uri stylusHandwritingEnabledUri = Settings.Secure.getUriFor(
+ STYLUS_HANDWRITING_ENABLED);
synchronized (ImfLock.class) {
if (showImeUri.equals(uri)) {
mMenuController.updateKeyboardFromSettingsLocked();
@@ -1200,6 +1204,8 @@
showCurrentInputImplicitLocked(mCurFocusedWindow,
SoftInputShowHideReason.SHOW_SETTINGS_ON_CHANGE);
}
+ } else if (stylusHandwritingEnabledUri.equals(uri)) {
+ InputMethodManager.invalidateLocalStylusHandwritingAvailabilityCaches();
} else {
boolean enabledChanged = false;
String newEnabled = mSettings.getEnabledInputMethodsStr();
@@ -2363,7 +2369,7 @@
mCurVirtualDisplayToScreenMatrix = null;
ImeTracker.forLogging().onFailed(mCurStatsToken, ImeTracker.PHASE_SERVER_WAIT_IME);
mCurStatsToken = null;
-
+ InputMethodManager.invalidateLocalStylusHandwritingAvailabilityCaches();
mMenuController.hideInputMethodMenuLocked();
}
}
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index fd4176a..ecb21d0 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -847,6 +847,10 @@
getAuthSecretHal();
mDeviceProvisionedObserver.onSystemReady();
+ // Work around an issue in PropertyInvalidatedCache where the cache doesn't work until the
+ // first invalidation. This can be removed if PropertyInvalidatedCache is fixed.
+ LockPatternUtils.invalidateCredentialTypeCache();
+
// TODO: maybe skip this for split system user mode.
mStorage.prefetchUser(UserHandle.USER_SYSTEM);
mBiometricDeferredQueue.systemReady(mInjector.getFingerprintManager(),
diff --git a/services/core/java/com/android/server/locksettings/OWNERS b/services/core/java/com/android/server/locksettings/OWNERS
index 55b0cff..5d49863 100644
--- a/services/core/java/com/android/server/locksettings/OWNERS
+++ b/services/core/java/com/android/server/locksettings/OWNERS
@@ -1,3 +1,4 @@
+# Bug component: 1333694
ebiggers@google.com
jaggies@google.com
rubinxu@google.com
diff --git a/services/core/java/com/android/server/logcat/OWNERS b/services/core/java/com/android/server/logcat/OWNERS
index 4ce93aa..33e1873 100644
--- a/services/core/java/com/android/server/logcat/OWNERS
+++ b/services/core/java/com/android/server/logcat/OWNERS
@@ -4,6 +4,5 @@
eunjeongshin@google.com
georgechan@google.com
jsharkey@google.com
-vishwath@google.com
wenhaowang@google.com
xiaozhenl@google.com
diff --git a/services/core/java/com/android/server/media/MediaRoute2Provider.java b/services/core/java/com/android/server/media/MediaRoute2Provider.java
index c59b733..8149847 100644
--- a/services/core/java/com/android/server/media/MediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/MediaRoute2Provider.java
@@ -116,6 +116,7 @@
public void dump(PrintWriter pw, String prefix) {
pw.println(prefix + getDebugString());
prefix += " ";
+
if (mProviderInfo == null) {
pw.println(prefix + "<provider info not received, yet>");
} else if (mProviderInfo.getRoutes().isEmpty()) {
@@ -125,6 +126,17 @@
pw.printf("%s%s | %s\n", prefix, route.getId(), route.getName());
}
}
+
+ pw.println(prefix + "Active routing sessions:");
+ synchronized (mLock) {
+ if (mSessionInfos.isEmpty()) {
+ pw.println(prefix + " <no active routing sessions>");
+ } else {
+ for (RoutingSessionInfo routingSessionInfo : mSessionInfos) {
+ routingSessionInfo.dump(pw, prefix + " ");
+ }
+ }
+ }
}
@Override
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 81bcd2a..2db724f 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -322,7 +322,6 @@
import com.android.server.pm.PackageManagerService;
import com.android.server.pm.UserManagerInternal;
import com.android.server.policy.PermissionPolicyInternal;
-import com.android.server.powerstats.StatsPullAtomCallbackImpl;
import com.android.server.statusbar.StatusBarManagerInternal;
import com.android.server.uri.UriGrantsManagerInternal;
import com.android.server.utils.Slogf;
@@ -1715,7 +1714,6 @@
return;
}
- boolean queryRestart = false;
boolean queryRemove = false;
boolean packageChanged = false;
boolean cancelNotifications = true;
@@ -1727,7 +1725,6 @@
|| (queryRemove=action.equals(Intent.ACTION_PACKAGE_REMOVED))
|| action.equals(Intent.ACTION_PACKAGE_RESTARTED)
|| (packageChanged=action.equals(Intent.ACTION_PACKAGE_CHANGED))
- || (queryRestart=action.equals(Intent.ACTION_QUERY_PACKAGE_RESTART))
|| action.equals(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE)
|| action.equals(Intent.ACTION_PACKAGES_SUSPENDED)
|| action.equals(Intent.ACTION_PACKAGES_UNSUSPENDED)
@@ -1768,10 +1765,6 @@
cancelNotifications = false;
unhideNotifications = true;
}
-
- } else if (queryRestart) {
- pkgList = intent.getStringArrayExtra(Intent.EXTRA_PACKAGES);
- uidList = new int[] {intent.getIntExtra(Intent.EXTRA_UID, -1)};
} else {
Uri uri = intent.getData();
if (uri == null) {
@@ -1809,7 +1802,7 @@
if (cancelNotifications) {
for (String pkgName : pkgList) {
cancelAllNotificationsInt(MY_UID, MY_PID, pkgName, null, 0, 0,
- !queryRestart, changeUserId, reason, null);
+ changeUserId, reason);
}
} else if (hideNotifications && uidList != null && (uidList.length > 0)) {
hideNotificationsForPackages(pkgList, uidList);
@@ -1843,14 +1836,14 @@
} else if (action.equals(Intent.ACTION_USER_STOPPED)) {
int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
if (userHandle >= 0) {
- cancelAllNotificationsInt(MY_UID, MY_PID, null, null, 0, 0, true, userHandle,
- REASON_USER_STOPPED, null);
+ cancelAllNotificationsInt(MY_UID, MY_PID, null, null, 0, 0, userHandle,
+ REASON_USER_STOPPED);
}
} else if (action.equals(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE)) {
int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
if (userHandle >= 0 && !mDpm.isKeepProfilesRunningEnabled()) {
- cancelAllNotificationsInt(MY_UID, MY_PID, null, null, 0, 0, true, userHandle,
- REASON_PROFILE_TURNED_OFF, null);
+ cancelAllNotificationsInt(MY_UID, MY_PID, null, null, 0, 0, userHandle,
+ REASON_PROFILE_TURNED_OFF);
mSnoozeHelper.clearData(userHandle);
}
} else if (action.equals(Intent.ACTION_USER_PRESENT)) {
@@ -2468,7 +2461,6 @@
pkgFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
pkgFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
pkgFilter.addAction(Intent.ACTION_PACKAGE_RESTARTED);
- pkgFilter.addAction(Intent.ACTION_QUERY_PACKAGE_RESTART);
pkgFilter.addDataScheme("package");
getContext().registerReceiverAsUser(mPackageIntentReceiver, UserHandle.ALL, pkgFilter, null,
null);
@@ -2499,6 +2491,16 @@
getContext().registerReceiver(mReviewNotificationPermissionsReceiver,
ReviewNotificationPermissionsReceiver.getFilter(),
Context.RECEIVER_NOT_EXPORTED);
+
+ mAppOps.startWatchingMode(AppOpsManager.OP_POST_NOTIFICATION, null,
+ new AppOpsManager.OnOpChangedInternalListener() {
+ @Override
+ public void onOpChanged(@NonNull String op, @NonNull String packageName,
+ int userId) {
+ mHandler.post(
+ () -> handleNotificationPermissionChange(packageName, userId));
+ }
+ });
}
/**
@@ -2855,17 +2857,17 @@
boolean fromListener) {
if (channel.getImportance() == NotificationManager.IMPORTANCE_NONE) {
// cancel
- cancelAllNotificationsInt(MY_UID, MY_PID, pkg, channel.getId(), 0, 0, true,
- UserHandle.getUserId(uid), REASON_CHANNEL_BANNED,
- null);
+ cancelAllNotificationsInt(MY_UID, MY_PID, pkg, channel.getId(), 0, 0,
+ UserHandle.getUserId(uid), REASON_CHANNEL_BANNED
+ );
if (isUidSystemOrPhone(uid)) {
IntArray profileIds = mUserProfiles.getCurrentProfileIds();
int N = profileIds.size();
for (int i = 0; i < N; i++) {
int profileId = profileIds.get(i);
- cancelAllNotificationsInt(MY_UID, MY_PID, pkg, channel.getId(), 0, 0, true,
- profileId, REASON_CHANNEL_BANNED,
- null);
+ cancelAllNotificationsInt(MY_UID, MY_PID, pkg, channel.getId(), 0, 0,
+ profileId, REASON_CHANNEL_BANNED
+ );
}
}
}
@@ -3539,7 +3541,7 @@
// Don't allow the app to cancel active FGS or UIJ notifications
cancelAllNotificationsInt(Binder.getCallingUid(), Binder.getCallingPid(),
pkg, null, 0, FLAG_FOREGROUND_SERVICE | FLAG_USER_INITIATED_JOB,
- true, userId, REASON_APP_CANCEL_ALL, null);
+ userId, REASON_APP_CANCEL_ALL);
}
@Override
@@ -3558,20 +3560,16 @@
}
mPermissionHelper.setNotificationPermission(
pkg, UserHandle.getUserId(uid), enabled, true);
- sendAppBlockStateChangedBroadcast(pkg, uid, !enabled);
mMetricsLogger.write(new LogMaker(MetricsEvent.ACTION_BAN_APP_NOTES)
.setType(MetricsEvent.TYPE_ACTION)
.setPackageName(pkg)
.setSubtype(enabled ? 1 : 0));
mNotificationChannelLogger.logAppNotificationsAllowed(uid, pkg, enabled);
- // Now, cancel any outstanding notifications that are part of a just-disabled app
- if (!enabled) {
- cancelAllNotificationsInt(MY_UID, MY_PID, pkg, null, 0, 0, true,
- UserHandle.getUserId(uid), REASON_PACKAGE_BANNED, null);
- }
- handleSavePolicyFile();
+ // Outstanding notifications from this package will be cancelled, and the package will
+ // be sent the ACTION_APP_BLOCK_STATE_CHANGED broadcast, as soon as we get the
+ // callback from AppOpsManager.
}
/**
@@ -4030,8 +4028,8 @@
}
enforceDeletingChannelHasNoFgService(pkg, callingUser, channelId);
enforceDeletingChannelHasNoUserInitiatedJob(pkg, callingUser, channelId);
- cancelAllNotificationsInt(MY_UID, MY_PID, pkg, channelId, 0, 0, true,
- callingUser, REASON_CHANNEL_REMOVED, null);
+ cancelAllNotificationsInt(MY_UID, MY_PID, pkg, channelId, 0, 0,
+ callingUser, REASON_CHANNEL_REMOVED);
boolean previouslyExisted = mPreferencesHelper.deleteNotificationChannel(
pkg, callingUid, channelId, callingUid, isSystemOrSystemUi);
if (previouslyExisted) {
@@ -4085,9 +4083,8 @@
for (int i = 0; i < deletedChannels.size(); i++) {
final NotificationChannel deletedChannel = deletedChannels.get(i);
cancelAllNotificationsInt(MY_UID, MY_PID, pkg, deletedChannel.getId(), 0, 0,
- true,
- userId, REASON_CHANNEL_REMOVED,
- null);
+ userId, REASON_CHANNEL_REMOVED
+ );
mListeners.notifyNotificationChannelChanged(pkg,
UserHandle.getUserHandleForUid(callingUid),
deletedChannel,
@@ -4256,8 +4253,8 @@
checkCallerIsSystem();
// Cancel posted notifications
final int userId = UserHandle.getUserId(uid);
- cancelAllNotificationsInt(MY_UID, MY_PID, packageName, null, 0, 0, true,
- UserHandle.getUserId(Binder.getCallingUid()), REASON_CLEAR_DATA, null);
+ cancelAllNotificationsInt(MY_UID, MY_PID, packageName, null, 0, 0,
+ UserHandle.getUserId(Binder.getCallingUid()), REASON_CLEAR_DATA);
// Zen
packagesChanged |=
@@ -5892,6 +5889,21 @@
}
};
+ private void handleNotificationPermissionChange(String pkg, @UserIdInt int userId) {
+ int uid = mPackageManagerInternal.getPackageUid(pkg, 0, userId);
+ if (uid == INVALID_UID) {
+ Log.e(TAG, String.format("No uid found for %s, %s!", pkg, userId));
+ return;
+ }
+ boolean hasPermission = mPermissionHelper.hasPermission(uid);
+ sendAppBlockStateChangedBroadcast(pkg, uid, !hasPermission);
+ if (!hasPermission) {
+ cancelAllNotificationsInt(MY_UID, MY_PID, pkg, /* channelId= */ null,
+ /* mustHaveFlags= */ 0, /* mustNotHaveFlags= */ 0, userId,
+ REASON_PACKAGE_BANNED);
+ }
+ }
+
protected void checkNotificationListenerAccess() {
if (!isCallerSystemOrPhone()) {
getContext().enforceCallingPermission(
@@ -6828,9 +6840,9 @@
mPreferencesHelper.deleteConversations(pkg, uid, shortcuts,
/* callingUid */ Process.SYSTEM_UID, /* is system */ true);
for (String channelId : deletedChannelIds) {
- cancelAllNotificationsInt(MY_UID, MY_PID, pkg, channelId, 0, 0, true,
- UserHandle.getUserId(uid), REASON_CHANNEL_REMOVED,
- null);
+ cancelAllNotificationsInt(MY_UID, MY_PID, pkg, channelId, 0, 0,
+ UserHandle.getUserId(uid), REASON_CHANNEL_REMOVED
+ );
}
handleSavePolicyFile();
}
@@ -7005,7 +7017,7 @@
*/
private boolean canBeNonDismissible(ApplicationInfo ai, Notification notification) {
return notification.isMediaNotification() || isEnterpriseExempted(ai)
- || isCallNotification(ai.packageName, ai.uid, notification)
+ || notification.isStyle(Notification.CallStyle.class)
|| isDefaultSearchSelectorPackage(ai.packageName);
}
@@ -9532,25 +9544,18 @@
/**
* Cancels all notifications from a given package that have all of the
- * {@code mustHaveFlags}.
+ * {@code mustHaveFlags} and none of the {@code mustNotHaveFlags}.
*/
- void cancelAllNotificationsInt(int callingUid, int callingPid, String pkg, String channelId,
- int mustHaveFlags, int mustNotHaveFlags, boolean doit, int userId, int reason,
- ManagedServiceInfo listener) {
+ void cancelAllNotificationsInt(int callingUid, int callingPid, String pkg,
+ @Nullable String channelId, int mustHaveFlags, int mustNotHaveFlags, int userId,
+ int reason) {
final long cancellationElapsedTimeMs = SystemClock.elapsedRealtime();
mHandler.post(new Runnable() {
@Override
public void run() {
- String listenerName = listener == null ? null : listener.component.toShortString();
EventLogTags.writeNotificationCancelAll(callingUid, callingPid,
pkg, userId, mustHaveFlags, mustNotHaveFlags, reason,
- listenerName);
-
- // Why does this parameter exist? Do we actually want to execute the above if doit
- // is false?
- if (!doit) {
- return;
- }
+ /* listener= */ null);
synchronized (mNotificationLock) {
FlagChecker flagChecker = (int flags) -> {
@@ -9562,14 +9567,15 @@
}
return true;
};
- cancelAllNotificationsByListLocked(mNotificationList, callingUid, callingPid,
- pkg, true /*nullPkgIndicatesUserSwitch*/, channelId, flagChecker,
+ cancelAllNotificationsByListLocked(mNotificationList, pkg,
+ true /*nullPkgIndicatesUserSwitch*/, channelId, flagChecker,
false /*includeCurrentProfiles*/, userId, false /*sendDelete*/, reason,
- listenerName, true /* wasPosted */, cancellationElapsedTimeMs);
- cancelAllNotificationsByListLocked(mEnqueuedNotifications, callingUid,
- callingPid, pkg, true /*nullPkgIndicatesUserSwitch*/, channelId,
- flagChecker, false /*includeCurrentProfiles*/, userId,
- false /*sendDelete*/, reason, listenerName, false /* wasPosted */,
+ null /* listenerName */, true /* wasPosted */,
+ cancellationElapsedTimeMs);
+ cancelAllNotificationsByListLocked(mEnqueuedNotifications, pkg,
+ true /*nullPkgIndicatesUserSwitch*/, channelId, flagChecker,
+ false /*includeCurrentProfiles*/, userId, false /*sendDelete*/, reason,
+ null /* listenerName */, false /* wasPosted */,
cancellationElapsedTimeMs);
mSnoozeHelper.cancel(userId, pkg);
}
@@ -9584,9 +9590,9 @@
@GuardedBy("mNotificationLock")
private void cancelAllNotificationsByListLocked(ArrayList<NotificationRecord> notificationList,
- int callingUid, int callingPid, String pkg, boolean nullPkgIndicatesUserSwitch,
- String channelId, FlagChecker flagChecker, boolean includeCurrentProfiles, int userId,
- boolean sendDelete, int reason, String listenerName, boolean wasPosted,
+ @Nullable String pkg, boolean nullPkgIndicatesUserSwitch, @Nullable String channelId,
+ FlagChecker flagChecker, boolean includeCurrentProfiles, int userId, boolean sendDelete,
+ int reason, String listenerName, boolean wasPosted,
@ElapsedRealtimeLong long cancellationElapsedTimeMs) {
Set<String> childNotifications = null;
for (int i = notificationList.size() - 1; i >= 0; --i) {
@@ -9703,12 +9709,12 @@
return true;
};
- cancelAllNotificationsByListLocked(mNotificationList, callingUid, callingPid,
+ cancelAllNotificationsByListLocked(mNotificationList,
null, false /*nullPkgIndicatesUserSwitch*/, null, flagChecker,
includeCurrentProfiles, userId, true /*sendDelete*/, reason,
listenerName, true, cancellationElapsedTimeMs);
- cancelAllNotificationsByListLocked(mEnqueuedNotifications, callingUid,
- callingPid, null, false /*nullPkgIndicatesUserSwitch*/, null,
+ cancelAllNotificationsByListLocked(mEnqueuedNotifications,
+ null, false /*nullPkgIndicatesUserSwitch*/, null,
flagChecker, includeCurrentProfiles, userId, true /*sendDelete*/,
reason, listenerName, false, cancellationElapsedTimeMs);
mSnoozeHelper.cancel(userId, includeCurrentProfiles);
diff --git a/services/core/java/com/android/server/notification/NotificationRecordLogger.java b/services/core/java/com/android/server/notification/NotificationRecordLogger.java
index 5ca882c..b015a72 100644
--- a/services/core/java/com/android/server/notification/NotificationRecordLogger.java
+++ b/services/core/java/com/android/server/notification/NotificationRecordLogger.java
@@ -30,6 +30,7 @@
import android.os.Bundle;
import android.service.notification.NotificationListenerService;
import android.service.notification.NotificationStats;
+import android.util.Log;
import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags;
import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags;
@@ -48,6 +49,8 @@
*/
interface NotificationRecordLogger {
+ static final String TAG = "NotificationRecordLogger";
+
// The high-level interface used by clients.
/**
@@ -228,51 +231,40 @@
@NotificationStats.DismissalSurface int surface) {
// Shouldn't be possible to get a non-dismissed notification here.
if (surface == NotificationStats.DISMISSAL_NOT_DISMISSED) {
- if (NotificationManagerService.DBG) {
- throw new IllegalArgumentException("Unexpected surface " + surface);
- }
+ Log.wtf(TAG, "Unexpected surface: " + surface + " with reason " + reason);
return INVALID;
}
- // Most cancel reasons do not have a meaningful surface. Reason codes map directly
- // to NotificationCancelledEvent codes.
- if (surface == NotificationStats.DISMISSAL_OTHER) {
+
+ // User cancels have a meaningful surface, which we differentiate by. See b/149038335
+ // for caveats.
+ if (reason == REASON_CANCEL) {
+ switch (surface) {
+ case NotificationStats.DISMISSAL_PEEK:
+ return NOTIFICATION_CANCEL_USER_PEEK;
+ case NotificationStats.DISMISSAL_AOD:
+ return NOTIFICATION_CANCEL_USER_AOD;
+ case NotificationStats.DISMISSAL_SHADE:
+ return NOTIFICATION_CANCEL_USER_SHADE;
+ case NotificationStats.DISMISSAL_BUBBLE:
+ return NOTIFICATION_CANCEL_USER_BUBBLE;
+ case NotificationStats.DISMISSAL_LOCKSCREEN:
+ return NOTIFICATION_CANCEL_USER_LOCKSCREEN;
+ case NotificationStats.DISMISSAL_OTHER:
+ return NOTIFICATION_CANCEL_USER_OTHER;
+ default:
+ Log.wtf(TAG, "Unexpected surface: " + surface + " with reason " + reason);
+ return INVALID;
+ }
+ } else {
if ((REASON_CLICK <= reason) && (reason <= REASON_CLEAR_DATA)) {
return NotificationCancelledEvent.values()[reason];
}
if (reason == REASON_ASSISTANT_CANCEL) {
return NotificationCancelledEvent.NOTIFICATION_CANCEL_ASSISTANT;
}
- if (NotificationManagerService.DBG) {
- throw new IllegalArgumentException("Unexpected cancel reason " + reason);
- }
+ Log.wtf(TAG, "Unexpected reason: " + reason + " with surface " + surface);
return INVALID;
}
- // User cancels have a meaningful surface, which we differentiate by. See b/149038335
- // for caveats.
- if (reason != REASON_CANCEL) {
- if (NotificationManagerService.DBG) {
- throw new IllegalArgumentException("Unexpected cancel with surface " + reason);
- }
- return INVALID;
- }
- switch (surface) {
- case NotificationStats.DISMISSAL_PEEK:
- return NOTIFICATION_CANCEL_USER_PEEK;
- case NotificationStats.DISMISSAL_AOD:
- return NOTIFICATION_CANCEL_USER_AOD;
- case NotificationStats.DISMISSAL_SHADE:
- return NOTIFICATION_CANCEL_USER_SHADE;
- case NotificationStats.DISMISSAL_BUBBLE:
- return NOTIFICATION_CANCEL_USER_BUBBLE;
- case NotificationStats.DISMISSAL_LOCKSCREEN:
- return NOTIFICATION_CANCEL_USER_LOCKSCREEN;
- default:
- if (NotificationManagerService.DBG) {
- throw new IllegalArgumentException("Unexpected surface for user-dismiss "
- + reason);
- }
- return INVALID;
- }
}
}
diff --git a/services/core/java/com/android/server/notification/PermissionHelper.java b/services/core/java/com/android/server/notification/PermissionHelper.java
index 93c83e1..85c140c 100644
--- a/services/core/java/com/android/server/notification/PermissionHelper.java
+++ b/services/core/java/com/android/server/notification/PermissionHelper.java
@@ -196,19 +196,20 @@
int uid = mPackageManager.getPackageUid(packageName, 0, userId);
boolean currentlyGranted = hasPermission(uid);
if (grant && !currentlyGranted) {
- mPermManager.grantRuntimePermission(packageName, NOTIFICATION_PERMISSION, userId);
+ mPermManager.grantRuntimePermission(packageName, NOTIFICATION_PERMISSION,
+ Context.DEVICE_ID_DEFAULT, userId);
} else if (!grant && currentlyGranted) {
mPermManager.revokeRuntimePermission(packageName, NOTIFICATION_PERMISSION,
- userId, TAG);
+ Context.DEVICE_ID_DEFAULT, userId, TAG);
}
int flagMask = FLAG_PERMISSION_USER_SET | FLAG_PERMISSION_USER_FIXED;
flagMask = userSet || !grant ? flagMask | FLAG_PERMISSION_GRANTED_BY_DEFAULT : flagMask;
if (userSet) {
- mPermManager.updatePermissionFlags(packageName, NOTIFICATION_PERMISSION,
- flagMask, FLAG_PERMISSION_USER_SET, true, userId);
+ mPermManager.updatePermissionFlags(packageName, NOTIFICATION_PERMISSION, flagMask,
+ FLAG_PERMISSION_USER_SET, true, Context.DEVICE_ID_DEFAULT, userId);
} else {
mPermManager.updatePermissionFlags(packageName, NOTIFICATION_PERMISSION,
- flagMask, 0, true, userId);
+ flagMask, 0, true, Context.DEVICE_ID_DEFAULT, userId);
}
} catch (RemoteException e) {
Slog.e(TAG, "Could not reach system server", e);
@@ -236,7 +237,7 @@
try {
try {
int flags = mPermManager.getPermissionFlags(packageName, NOTIFICATION_PERMISSION,
- userId);
+ Context.DEVICE_ID_DEFAULT, userId);
return (flags & PackageManager.FLAG_PERMISSION_SYSTEM_FIXED) != 0
|| (flags & PackageManager.FLAG_PERMISSION_POLICY_FIXED) != 0;
} catch (RemoteException e) {
@@ -253,7 +254,7 @@
try {
try {
int flags = mPermManager.getPermissionFlags(packageName, NOTIFICATION_PERMISSION,
- userId);
+ Context.DEVICE_ID_DEFAULT, userId);
return (flags & (PackageManager.FLAG_PERMISSION_USER_SET
| PackageManager.FLAG_PERMISSION_USER_FIXED)) != 0;
} catch (RemoteException e) {
@@ -270,7 +271,7 @@
try {
try {
int flags = mPermManager.getPermissionFlags(packageName, NOTIFICATION_PERMISSION,
- userId);
+ Context.DEVICE_ID_DEFAULT, userId);
return (flags & (PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT
| PackageManager.FLAG_PERMISSION_GRANTED_BY_ROLE)) != 0;
} catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java
index e54f12c..7736e2b 100644
--- a/services/core/java/com/android/server/om/OverlayManagerService.java
+++ b/services/core/java/com/android/server/om/OverlayManagerService.java
@@ -1120,6 +1120,21 @@
int callingUid = Binder.getCallingUid();
mActorEnforcer.enforceActor(overlayInfo, methodName, callingUid, realUserId);
}
+
+ /**
+ * @hide
+ */
+ public String getPartitionOrder() {
+ return mImpl.getOverlayConfig().getPartitionOrder();
+ }
+
+ /**
+ * @hide
+ */
+ public boolean isDefaultPartitionOrder() {
+ return mImpl.getOverlayConfig().isDefaultPartitionOrder();
+ }
+
};
private static final class PackageManagerHelperImpl implements PackageManagerHelper {
diff --git a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
index 1beba9f..972c78d 100644
--- a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
+++ b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
@@ -925,4 +925,8 @@
super(message, cause);
}
}
+
+ OverlayConfig getOverlayConfig() {
+ return mOverlayConfig;
+ }
}
diff --git a/services/core/java/com/android/server/om/OverlayManagerShellCommand.java b/services/core/java/com/android/server/om/OverlayManagerShellCommand.java
index 978e436..f77d7898 100644
--- a/services/core/java/com/android/server/om/OverlayManagerShellCommand.java
+++ b/services/core/java/com/android/server/om/OverlayManagerShellCommand.java
@@ -16,6 +16,8 @@
package com.android.server.om;
+import static com.android.internal.content.om.OverlayConfig.PARTITION_ORDER_FILE_PATH;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
@@ -95,6 +97,8 @@
return runLookup();
case "fabricate":
return runFabricate();
+ case "partition-order":
+ return runPartitionOrder();
default:
return handleDefaultCommands(cmd);
}
@@ -147,6 +151,9 @@
out.println(" Create an overlay from a single resource. Caller must be root. Example:");
out.println(" fabricate --target android --name LighterGray \\");
out.println(" android:color/lighter_gray 0x1c 0xffeeeeee");
+ out.println(" partition-order");
+ out.println(" Print the partition order from overlay config and how this order");
+ out.println(" got established, by default or by " + PARTITION_ORDER_FILE_PATH);
}
private int runList() throws RemoteException {
@@ -247,6 +254,14 @@
return 0;
}
+ private int runPartitionOrder() throws RemoteException {
+ final PrintWriter out = getOutPrintWriter();
+ out.println("Partition order (low to high priority): " + mInterface.getPartitionOrder());
+ out.println("Established by " + (mInterface.isDefaultPartitionOrder() ? "default"
+ : PARTITION_ORDER_FILE_PATH));
+ return 0;
+ }
+
private int runFabricate() throws RemoteException {
final PrintWriter err = getErrPrintWriter();
if (Binder.getCallingUid() != Process.ROOT_UID) {
diff --git a/services/core/java/com/android/server/om/TEST_MAPPING b/services/core/java/com/android/server/om/TEST_MAPPING
index e8a2a02..82e7817 100644
--- a/services/core/java/com/android/server/om/TEST_MAPPING
+++ b/services/core/java/com/android/server/om/TEST_MAPPING
@@ -15,12 +15,7 @@
"name": "OverlayHostTests"
},
{
- "name": "CtsAppSecurityHostTestCases",
- "options": [
- {
- "include-filter": "android.appsecurity.cts.OverlayHostTest"
- }
- ]
+ "name": "CtsOverlayHostTestCases"
}
],
"presubmit-large": [
diff --git a/services/core/java/com/android/server/os/NativeTombstoneManager.java b/services/core/java/com/android/server/os/NativeTombstoneManager.java
index f8ee6b0..c7c8136 100644
--- a/services/core/java/com/android/server/os/NativeTombstoneManager.java
+++ b/services/core/java/com/android/server/os/NativeTombstoneManager.java
@@ -372,7 +372,7 @@
return false;
}
- if (Math.abs(exitInfo.getTimestamp() - mTimestampMs) > 5000) {
+ if (Math.abs(exitInfo.getTimestamp() - mTimestampMs) > 10000) {
return false;
}
diff --git a/services/core/java/com/android/server/pm/ApexManager.java b/services/core/java/com/android/server/pm/ApexManager.java
index 5e62b56..2206eac 100644
--- a/services/core/java/com/android/server/pm/ApexManager.java
+++ b/services/core/java/com/android/server/pm/ApexManager.java
@@ -35,7 +35,6 @@
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.Trace;
-import android.sysprop.ApexProperties;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -82,18 +81,12 @@
new Singleton<ApexManager>() {
@Override
protected ApexManager create() {
- if (ApexProperties.updatable().orElse(false)) {
- return new ApexManagerImpl();
- } else {
- return new ApexManagerFlattenedApex();
- }
+ return new ApexManagerImpl();
}
};
/**
- * Returns an instance of either {@link ApexManagerImpl} or {@link ApexManagerFlattenedApex}
- * depending on whether this device supports APEX, i.e. {@link ApexProperties#updatable()}
- * evaluates to {@code true}.
+ * Returns an instance of {@link ApexManagerImpl}
* @hide
*/
public static ApexManager getInstance() {
@@ -991,203 +984,4 @@
}
}
}
-
- /**
- * An implementation of {@link ApexManager} that should be used in case device does not support
- * updating APEX packages.
- */
- @VisibleForTesting
- static final class ApexManagerFlattenedApex extends ApexManager {
- @Override
- ApexInfo[] getAllApexInfos() {
- return null;
- }
-
- @Override
- void notifyScanResult(List<ScanResult> scanResults) {
- // No-op
- }
-
- @Override
- public List<ActiveApexInfo> getActiveApexInfos() {
- // There is no apexd running in case of flattened apex
- // We look up the /apex directory and identify the active APEX modules from there.
- // As "preinstalled" path, we just report /system since in the case of flattened APEX
- // the /apex directory is just a symlink to /system/apex.
- List<ActiveApexInfo> result = new ArrayList<>();
- File apexDir = Environment.getApexDirectory();
- if (apexDir.isDirectory()) {
- File[] files = apexDir.listFiles();
- // listFiles might be null if system server doesn't have permission to read
- // a directory.
- if (files != null) {
- for (File file : files) {
- if (file.isDirectory() && !file.getName().contains("@")
- // In flattened configuration, init special-cases the art directory
- // and bind-mounts com.android.art.debug to com.android.art.
- && !file.getName().equals("com.android.art.debug")) {
- result.add(
- new ActiveApexInfo(file, Environment.getRootDirectory(), file));
- }
- }
- }
- }
- return result;
- }
-
- @Override
- @Nullable
- public String getActiveApexPackageNameContainingPackage(
- @NonNull String containedPackageName) {
- Objects.requireNonNull(containedPackageName);
-
- return null;
- }
-
- @Override
- ApexSessionInfo getStagedSessionInfo(int sessionId) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- SparseArray<ApexSessionInfo> getSessions() {
- return new SparseArray<>(0);
- }
-
- @Override
- ApexInfoList submitStagedSession(ApexSessionParams params)
- throws PackageManagerException {
- throw new PackageManagerException(PackageManager.INSTALL_FAILED_INTERNAL_ERROR,
- "Device doesn't support updating APEX");
- }
-
- @Override
- ApexInfo[] getStagedApexInfos(ApexSessionParams params) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- void markStagedSessionReady(int sessionId) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- void markStagedSessionSuccessful(int sessionId) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- boolean isApexSupported() {
- return false;
- }
-
- @Override
- boolean revertActiveSessions() {
- throw new UnsupportedOperationException();
- }
-
- @Override
- boolean abortStagedSession(int sessionId) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- boolean uninstallApex(String apexPackagePath) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- void registerApkInApex(AndroidPackage pkg) {
- // No-op
- }
-
- @Override
- void reportErrorWithApkInApex(String scanDirPath, String errorMsg) {
- // No-op
- }
-
- @Override
- @Nullable
- String getApkInApexInstallError(String apexPackageName) {
- return null;
- }
-
- @Override
- List<String> getApksInApex(String apexPackageName) {
- return Collections.emptyList();
- }
-
- @Override
- @Nullable
- public String getApexModuleNameForPackageName(String apexPackageName) {
- return null;
- }
-
- @Override
- @Nullable
- public String getActivePackageNameForApexModuleName(String apexModuleName) {
- return null;
- }
-
- @Override
- public boolean snapshotCeData(int userId, int rollbackId, String apexPackageName) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public boolean restoreCeData(int userId, int rollbackId, String apexPackageName) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public boolean destroyDeSnapshots(int rollbackId) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public boolean destroyCeSnapshots(int userId, int rollbackId) {
- return true;
- }
-
- @Override
- public boolean destroyCeSnapshotsNotSpecified(int userId, int[] retainRollbackIds) {
- return true;
- }
-
- @Override
- public void markBootCompleted() {
- // No-op
- }
-
- @Override
- public long calculateSizeForCompressedApex(CompressedApexInfoList infoList) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public void reserveSpaceForCompressedApex(CompressedApexInfoList infoList) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- ApexInfo installPackage(File apexFile) {
- throw new UnsupportedOperationException("APEX updates are not supported");
- }
-
- @Override
- public List<ApexSystemServiceInfo> getApexSystemServices() {
- // TODO(satayev): we can't really support flattened apex use case, and need to migrate
- // the manifest entries into system's manifest asap.
- return Collections.emptyList();
- }
-
- @Override
- void dump(PrintWriter pw) {
- }
-
- @Override
- public File getBackingApexFile(File file) {
- return null;
- }
- }
}
diff --git a/services/core/java/com/android/server/pm/AppsFilterImpl.java b/services/core/java/com/android/server/pm/AppsFilterImpl.java
index 6505e8b..21610c9 100644
--- a/services/core/java/com/android/server/pm/AppsFilterImpl.java
+++ b/services/core/java/com/android/server/pm/AppsFilterImpl.java
@@ -1225,6 +1225,7 @@
setting.getPackageName(), siblingSetting, settings,
users, USER_ALL, settings.size());
}
+ break;
}
}
diff --git a/services/core/java/com/android/server/pm/ArchiveManager.java b/services/core/java/com/android/server/pm/ArchiveManager.java
index 5082be6..99479f0 100644
--- a/services/core/java/com/android/server/pm/ArchiveManager.java
+++ b/services/core/java/com/android/server/pm/ArchiveManager.java
@@ -16,14 +16,28 @@
package com.android.server.pm;
+import static android.content.pm.PackageManager.DELETE_KEEP_DATA;
+
import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
import android.content.IntentSender;
+import android.content.pm.LauncherActivityInfo;
+import android.content.pm.LauncherApps;
import android.content.pm.PackageManager;
+import android.content.pm.VersionedPackage;
import android.os.Binder;
+import android.os.UserHandle;
import android.text.TextUtils;
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.pm.pkg.ArchiveState;
+import com.android.server.pm.pkg.ArchiveState.ArchiveActivityInfo;
import com.android.server.pm.pkg.PackageStateInternal;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Objects;
/**
@@ -35,37 +49,103 @@
*/
final class ArchiveManager {
+ private final Context mContext;
private final PackageManagerService mPm;
- ArchiveManager(PackageManagerService mPm) {
+ @Nullable
+ private LauncherApps mLauncherApps;
+
+ ArchiveManager(Context context, PackageManagerService mPm) {
+ this.mContext = context;
this.mPm = mPm;
}
void archiveApp(
@NonNull String packageName,
@NonNull String callerPackageName,
- int userId,
+ @NonNull UserHandle user,
@NonNull IntentSender intentSender) throws PackageManager.NameNotFoundException {
Objects.requireNonNull(packageName);
Objects.requireNonNull(callerPackageName);
+ Objects.requireNonNull(user);
Objects.requireNonNull(intentSender);
Computer snapshot = mPm.snapshotComputer();
int callingUid = Binder.getCallingUid();
+ int userId = user.getIdentifier();
String callingPackageName = snapshot.getNameForUid(callingUid);
- snapshot.enforceCrossUserPermission(callingUid, userId, true, true, "archiveApp");
+ snapshot.enforceCrossUserPermission(callingUid, userId, true, true,
+ "archiveApp");
verifyCaller(callerPackageName, callingPackageName);
- PackageStateInternal ps = snapshot.getPackageStateInternal(packageName);
+ PackageStateInternal ps = getPackageState(packageName, snapshot, callingUid, user);
+ verifyInstallOwnership(packageName, callingPackageName, ps.getInstallSource());
+
+ List<LauncherActivityInfo> mainActivities = getLauncherApps().getActivityList(
+ ps.getPackageName(),
+ new UserHandle(userId));
+ // TODO(b/291569242) Verify that this list is not empty and return failure with intentsender
+
+ storeArchiveState(ps, mainActivities, userId);
+
+ // TODO(b/278553670) Add special strings for the delete dialog
+ mPm.mInstallerService.uninstall(
+ new VersionedPackage(packageName, PackageManager.VERSION_CODE_HIGHEST),
+ callerPackageName, DELETE_KEEP_DATA, intentSender, userId);
+ }
+
+ @NonNull
+ private static PackageStateInternal getPackageState(String packageName,
+ Computer snapshot, int callingUid, UserHandle user)
+ throws PackageManager.NameNotFoundException {
+ PackageStateInternal ps = snapshot.getPackageStateFiltered(packageName, callingUid,
+ user.getIdentifier());
if (ps == null) {
throw new PackageManager.NameNotFoundException(
TextUtils.formatSimple("Package %s not found.", packageName));
}
+ return ps;
+ }
- verifyInstallOwnership(packageName, callingPackageName, ps);
+ private LauncherApps getLauncherApps() {
+ if (mLauncherApps == null) {
+ mLauncherApps = mContext.getSystemService(LauncherApps.class);
+ }
+ return mLauncherApps;
+ }
- // TODO(b/278553670) Complete implementations
- throw new UnsupportedOperationException("Method not implemented.");
+ private void storeArchiveState(PackageStateInternal ps,
+ List<LauncherActivityInfo> mainActivities, int userId)
+ throws PackageManager.NameNotFoundException {
+ List<ArchiveActivityInfo> activityInfos = new ArrayList<>();
+ for (int i = 0; i < mainActivities.size(); i++) {
+ // TODO(b/278553670) Extract and store launcher icons
+ ArchiveActivityInfo activityInfo = new ArchiveActivityInfo(
+ mainActivities.get(i).getLabel().toString(),
+ Path.of("/TODO"), null);
+ activityInfos.add(activityInfo);
+ }
+ // TODO(b/278553670) Adapt installer check verifyInstallOwnership and check for null there
+ InstallSource installSource = ps.getInstallSource();
+ String installerPackageName = installSource.mUpdateOwnerPackageName != null
+ ? installSource.mUpdateOwnerPackageName : installSource.mInstallerPackageName;
+
+ synchronized (mPm.mLock) {
+ getPackageSetting(ps.getPackageName(), userId).modifyUserState(userId).setArchiveState(
+ new ArchiveState(activityInfos, installerPackageName));
+ }
+ }
+
+ @NonNull
+ @GuardedBy("mPm.mLock")
+ private PackageSetting getPackageSetting(String packageName, int userId)
+ throws PackageManager.NameNotFoundException {
+ PackageSetting ps = mPm.mSettings.getPackageLPr(packageName);
+ if (ps == null || !ps.getUserStateOrDefault(userId).isInstalled()) {
+ throw new PackageManager.NameNotFoundException(
+ TextUtils.formatSimple("Package %s not found.", packageName));
+ }
+ return ps;
}
private static void verifyCaller(String callerPackageName, String callingPackageName) {
@@ -80,14 +160,14 @@
}
private static void verifyInstallOwnership(String packageName, String callingPackageName,
- PackageStateInternal ps) {
- if (!TextUtils.equals(ps.getInstallSource().mInstallerPackageName,
+ InstallSource installSource) {
+ if (!TextUtils.equals(installSource.mInstallerPackageName,
callingPackageName)) {
throw new SecurityException(
TextUtils.formatSimple("Caller is not the installer of record for %s.",
packageName));
}
- String updateOwnerPackageName = ps.getInstallSource().mUpdateOwnerPackageName;
+ String updateOwnerPackageName = installSource.mUpdateOwnerPackageName;
if (updateOwnerPackageName != null
&& !TextUtils.equals(updateOwnerPackageName, callingPackageName)) {
throw new SecurityException(
diff --git a/services/core/java/com/android/server/pm/BackgroundInstallControlService.java b/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
index 1b34c70..7f0aadc 100644
--- a/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
+++ b/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
@@ -234,7 +234,7 @@
// the installers without INSTALL_PACKAGES perm can't perform
// the installation in background. So we can just filter out them.
if (mPermissionManager.checkPermission(installerPackageName,
- android.Manifest.permission.INSTALL_PACKAGES,
+ android.Manifest.permission.INSTALL_PACKAGES, Context.DEVICE_ID_DEFAULT,
userId) != PackageManager.PERMISSION_GRANTED) {
return;
}
@@ -433,7 +433,7 @@
return true;
}
return mPermissionManager.checkPermission(pkgName,
- android.Manifest.permission.INSTALL_PACKAGES,
+ android.Manifest.permission.INSTALL_PACKAGES, Context.DEVICE_ID_DEFAULT,
userId) == PackageManager.PERMISSION_GRANTED;
}
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index 7aa5c65..1b52725 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -2583,7 +2583,7 @@
// NOTE: Can't remove without a major refactor. Keep around for now.
public final int checkUidPermission(String permName, int uid) {
- return mPermissionManager.checkUidPermission(uid, permName);
+ return mPermissionManager.checkUidPermission(uid, permName, Context.DEVICE_ID_DEFAULT);
}
public int getPackageUidInternal(String packageName,
@@ -4528,8 +4528,8 @@
int numMatch = 0;
for (int i=0; i<permissions.length; i++) {
final String permission = permissions[i];
- if (mPermissionManager.checkPermission(ps.getPackageName(), permission, userId)
- == PERMISSION_GRANTED) {
+ if (mPermissionManager.checkPermission(ps.getPackageName(), permission,
+ Context.DEVICE_ID_DEFAULT, userId) == PERMISSION_GRANTED) {
tmp[i] = true;
numMatch++;
} else {
diff --git a/services/core/java/com/android/server/pm/DefaultAppProvider.java b/services/core/java/com/android/server/pm/DefaultAppProvider.java
index c18d0e9..fc61451 100644
--- a/services/core/java/com/android/server/pm/DefaultAppProvider.java
+++ b/services/core/java/com/android/server/pm/DefaultAppProvider.java
@@ -24,14 +24,10 @@
import android.os.UserHandle;
import android.util.Slog;
-import com.android.internal.infra.AndroidFuture;
import com.android.internal.util.CollectionUtils;
import com.android.server.FgThread;
-import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
import java.util.function.Consumer;
import java.util.function.Supplier;
@@ -70,27 +66,19 @@
* Set the package name of the default browser.
*
* @param packageName package name of the default browser, or {@code null} to unset
- * @param async whether the operation should be asynchronous
* @param userId the user ID
- * @return whether the default browser was successfully set.
*/
- public boolean setDefaultBrowser(@Nullable String packageName, boolean async,
- @UserIdInt int userId) {
- if (userId == UserHandle.USER_ALL) {
- return false;
- }
+ public void setDefaultBrowser(@Nullable String packageName, @UserIdInt int userId) {
final RoleManager roleManager = mRoleManagerSupplier.get();
if (roleManager == null) {
- return false;
+ return;
}
final UserHandle user = UserHandle.of(userId);
final Executor executor = FgThread.getExecutor();
- final AndroidFuture<Void> future = new AndroidFuture<>();
final Consumer<Boolean> callback = successful -> {
- if (successful) {
- future.complete(null);
- } else {
- future.completeExceptionally(new RuntimeException());
+ if (!successful) {
+ Slog.e(PackageManagerService.TAG, "Failed to set default browser to "
+ + packageName);
}
};
final long identity = Binder.clearCallingIdentity();
@@ -102,19 +90,9 @@
roleManager.clearRoleHoldersAsUser(RoleManager.ROLE_BROWSER, 0, user, executor,
callback);
}
- if (!async) {
- try {
- future.get(5, TimeUnit.SECONDS);
- } catch (InterruptedException | ExecutionException | TimeoutException e) {
- Slog.e(PackageManagerService.TAG, "Exception while setting default browser: "
- + packageName, e);
- return false;
- }
- }
} finally {
Binder.restoreCallingIdentity(identity);
}
- return true;
}
/**
diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java
index 4963dd4..a5a4594 100644
--- a/services/core/java/com/android/server/pm/DeletePackageHelper.java
+++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java
@@ -633,7 +633,8 @@
null /*harmfulAppWarning*/,
null /*splashScreenTheme*/,
0 /*firstInstallTime*/,
- PackageManager.USER_MIN_ASPECT_RATIO_UNSET);
+ PackageManager.USER_MIN_ASPECT_RATIO_UNSET,
+ null /*archiveState*/);
}
mPm.mSettings.writeKernelMappingLPr(ps);
}
diff --git a/services/core/java/com/android/server/pm/DumpHelper.java b/services/core/java/com/android/server/pm/DumpHelper.java
index b5647d0..f3ea42e 100644
--- a/services/core/java/com/android/server/pm/DumpHelper.java
+++ b/services/core/java/com/android/server/pm/DumpHelper.java
@@ -23,6 +23,7 @@
import android.annotation.NonNull;
import android.content.ComponentName;
+import android.content.Context;
import android.content.pm.FeatureInfo;
import android.content.pm.PackageManager;
import android.os.Binder;
@@ -160,7 +161,8 @@
pkg = snapshot.resolveInternalPackageName(pkg,
PackageManager.VERSION_CODE_HIGHEST);
- pw.println(mPermissionManager.checkPermission(perm, pkg, user));
+ pw.println(mPermissionManager.checkPermission(
+ pkg, perm, Context.DEVICE_ID_DEFAULT, user));
return;
} else if ("l".equals(cmd) || "libraries".equals(cmd)) {
dumpState.setDump(DumpState.DUMP_LIBS);
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 7d47762..ead26cc 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -110,6 +110,7 @@
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
+import android.content.IntentFilter;
import android.content.IntentSender;
import android.content.pm.ApplicationInfo;
import android.content.pm.DataLoaderType;
@@ -119,7 +120,6 @@
import android.content.pm.PackageManager;
import android.content.pm.PermissionGroupInfo;
import android.content.pm.PermissionInfo;
-import android.content.pm.ResolveInfo;
import android.content.pm.SharedLibraryInfo;
import android.content.pm.Signature;
import android.content.pm.SigningDetails;
@@ -184,7 +184,9 @@
import com.android.server.pm.pkg.PackageStateInternal;
import com.android.server.pm.pkg.SharedLibraryWrapper;
import com.android.server.pm.pkg.component.ComponentMutateUtils;
+import com.android.server.pm.pkg.component.ParsedActivity;
import com.android.server.pm.pkg.component.ParsedInstrumentation;
+import com.android.server.pm.pkg.component.ParsedIntentInfo;
import com.android.server.pm.pkg.component.ParsedPermission;
import com.android.server.pm.pkg.component.ParsedPermissionGroup;
import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
@@ -207,6 +209,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -1077,8 +1080,6 @@
final boolean isApex = ((installFlags & PackageManager.INSTALL_APEX) != 0);
final boolean isRollback =
request.getInstallReason() == PackageManager.INSTALL_REASON_ROLLBACK;
- final boolean extractProfile =
- ((installFlags & PackageManager.INSTALL_DONT_EXTRACT_BASELINE_PROFILES) == 0);
@PackageManagerService.ScanFlags int scanFlags = SCAN_NEW_INSTALL | SCAN_UPDATE_SIGNATURE;
if (request.isInstallMove()) {
// moving a complete application; perform an initial scan on the new install location
@@ -1114,9 +1115,7 @@
@ParsingPackageUtils.ParseFlags final int parseFlags =
mPm.getDefParseFlags() | ParsingPackageUtils.PARSE_CHATTY
| ParsingPackageUtils.PARSE_ENFORCE_CODE
- | (onExternal ? ParsingPackageUtils.PARSE_EXTERNAL_STORAGE : 0)
- | (extractProfile
- ? ParsingPackageUtils.PARSE_EXTRACT_BASELINE_PROFILES_FROM_APK : 0);
+ | (onExternal ? ParsingPackageUtils.PARSE_EXTERNAL_STORAGE : 0);
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parsePackage");
final ParsedPackage parsedPackage;
@@ -1509,29 +1508,34 @@
} else {
// Enable SCAN_NO_DEX flag to skip dexopt at a later stage
scanFlags |= SCAN_NO_DEX;
+ // The native libs of Apex is located in apex_payload.img, don't need to parse it from
+ // the original apex file
+ if (!isApex) {
+ try {
+ PackageSetting pkgSetting;
+ synchronized (mPm.mLock) {
+ pkgSetting = mPm.mSettings.getPackageLPr(pkgName);
+ }
+ boolean isUpdatedSystemAppFromExistingSetting = pkgSetting != null
+ && pkgSetting.isUpdatedSystemApp();
+ final String abiOverride = deriveAbiOverride(request.getAbiOverride());
- try {
- PackageSetting pkgSetting;
- synchronized (mPm.mLock) {
- pkgSetting = mPm.mSettings.getPackageLPr(pkgName);
+ // TODO: Are these system flags actually set properly at this stage?
+ boolean isUpdatedSystemAppInferred =
+ pkgSetting != null && pkgSetting.isSystem();
+ final Pair<PackageAbiHelper.Abis, PackageAbiHelper.NativeLibraryPaths>
+ derivedAbi = mPackageAbiHelper.derivePackageAbi(parsedPackage,
+ systemApp, (isUpdatedSystemAppFromExistingSetting
+ || isUpdatedSystemAppInferred), abiOverride,
+ ScanPackageUtils.getAppLib32InstallDir());
+ derivedAbi.first.applyTo(parsedPackage);
+ derivedAbi.second.applyTo(parsedPackage);
+ } catch (PackageManagerException pme) {
+ Slog.e(TAG, "Error deriving application ABI", pme);
+ throw PrepareFailure.ofInternalError(
+ "Error deriving application ABI: " + pme.getMessage(),
+ PackageManagerException.INTERNAL_ERROR_DERIVING_ABI);
}
- boolean isUpdatedSystemAppFromExistingSetting = pkgSetting != null
- && pkgSetting.isUpdatedSystemApp();
- final String abiOverride = deriveAbiOverride(request.getAbiOverride());
-
- // TODO: Are these system flags actually set properly at this stage?
- boolean isUpdatedSystemAppInferred = pkgSetting != null && pkgSetting.isSystem();
- final Pair<PackageAbiHelper.Abis, PackageAbiHelper.NativeLibraryPaths>
- derivedAbi = mPackageAbiHelper.derivePackageAbi(parsedPackage, systemApp,
- isUpdatedSystemAppFromExistingSetting || isUpdatedSystemAppInferred,
- abiOverride, ScanPackageUtils.getAppLib32InstallDir());
- derivedAbi.first.applyTo(parsedPackage);
- derivedAbi.second.applyTo(parsedPackage);
- } catch (PackageManagerException pme) {
- Slog.e(TAG, "Error deriving application ABI", pme);
- throw PrepareFailure.ofInternalError(
- "Error deriving application ABI: " + pme.getMessage(),
- PackageManagerException.INTERNAL_ERROR_DERIVING_ABI);
}
}
@@ -3935,23 +3939,6 @@
}
}
- // If this is a system app we hadn't seen before, and this is a first boot or OTA,
- // we need to unstop it if it doesn't have a launcher entry.
- if (mPm.mShouldStopSystemPackagesByDefault && scanResult.mRequest.mPkgSetting == null
- && ((scanFlags & SCAN_FIRST_BOOT_OR_UPGRADE) != 0)
- && ((scanFlags & SCAN_AS_SYSTEM) != 0)) {
- final Intent launcherIntent = new Intent(Intent.ACTION_MAIN);
- launcherIntent.addCategory(Intent.CATEGORY_LAUNCHER);
- launcherIntent.setPackage(parsedPackage.getPackageName());
- final List<ResolveInfo> launcherActivities =
- mPm.snapshotComputer().queryIntentActivitiesInternal(launcherIntent, null,
- PackageManager.MATCH_DIRECT_BOOT_AWARE
- | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, 0);
- if (launcherActivities.isEmpty()) {
- scanResult.mPkgSetting.setStopped(false, 0);
- }
- }
-
if (mIncrementalManager != null && isIncrementalPath(parsedPackage.getPath())) {
if (scanResult.mPkgSetting != null && scanResult.mPkgSetting.isLoading()) {
// Continue monitoring loading progress of active incremental packages
@@ -4324,6 +4311,7 @@
// - It's an APEX or overlay package since stopped state does not affect them.
// - It is enumerated with a <initial-package-state> tag having the stopped attribute
// set to false
+ // - It doesn't have a launcher entry which means the user doesn't have a way to unstop it
final boolean isApexPkg = (scanFlags & SCAN_AS_APEX) != 0;
if (mPm.mShouldStopSystemPackagesByDefault
&& scanSystemPartition
@@ -4332,8 +4320,9 @@
&& !parsedPackage.isOverlayIsStatic()
) {
String packageName = parsedPackage.getPackageName();
- if (!mPm.mInitialNonStoppedSystemPackages.contains(packageName)
- && !"android".contentEquals(packageName)) {
+ if (!"android".contentEquals(packageName)
+ && !mPm.mInitialNonStoppedSystemPackages.contains(packageName)
+ && hasLauncherEntry(parsedPackage)) {
scanFlags |= SCAN_AS_STOPPED_SYSTEM_APP;
}
}
@@ -4343,6 +4332,22 @@
return new Pair<>(scanResult, shouldHideSystemApp);
}
+ private static boolean hasLauncherEntry(ParsedPackage parsedPackage) {
+ final HashSet<String> categories = new HashSet<>();
+ categories.add(Intent.CATEGORY_LAUNCHER);
+ final List<ParsedActivity> activities = parsedPackage.getActivities();
+ for (int indexActivity = 0; indexActivity < activities.size(); indexActivity++) {
+ final List<ParsedIntentInfo> intents = activities.get(indexActivity).getIntents();
+ for (int indexIntent = 0; indexIntent < intents.size(); indexIntent++) {
+ final IntentFilter intentFilter = intents.get(indexIntent).getIntentFilter();
+ if (intentFilter != null && intentFilter.matchCategories(categories) == null) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
/**
* Returns if forced apk verification can be skipped for the whole package, including splits.
*/
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 23608c5..6136197 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -461,6 +461,9 @@
private IntentSender mRemoteStatusReceiver;
@GuardedBy("mLock")
+ private IntentSender mPreapprovalRemoteStatusReceiver;
+
+ @GuardedBy("mLock")
private PreapprovalDetails mPreapprovalDetails;
/** Fields derived from commit parsing */
@@ -2040,11 +2043,11 @@
* {@link #setPermissionsResult(boolean)} is called and then
* {@link #MSG_PRE_APPROVAL_REQUEST} is handled to come back here to check again.
*/
- if (sendPendingUserActionIntentIfNeeded()) {
+ if (sendPendingUserActionIntentIfNeeded(/* forPreapproval= */true)) {
return;
}
- dispatchSessionPreappoved();
+ dispatchSessionPreapproved();
}
private final class FileSystemConnector extends
@@ -2499,14 +2502,16 @@
* @return true if the session set requires user action for the installation, otherwise false.
*/
@WorkerThread
- private boolean sendPendingUserActionIntentIfNeeded() {
+ private boolean sendPendingUserActionIntentIfNeeded(boolean forPreapproval) {
// To support pre-approval request of atomic install, we allow child session to handle
// the result by itself since it has the status receiver.
if (isCommitted()) {
assertNotChild("PackageInstallerSession#sendPendingUserActionIntentIfNeeded");
}
-
- final IntentSender statusReceiver = getRemoteStatusReceiver();
+ // Since there are separate status receivers for session preapproval and commit,
+ // check whether user action is requested for session preapproval or commit
+ final IntentSender statusReceiver = forPreapproval ? getPreapprovalRemoteStatusReceiver()
+ : getRemoteStatusReceiver();
return sessionContains(s -> checkUserActionRequirement(s, statusReceiver));
}
@@ -2529,7 +2534,8 @@
* install. Since control may come back here more than 1 time, we must ensure that it's
* value is not overwritten.
*/
- boolean wasUserActionIntentSent = sendPendingUserActionIntentIfNeeded();
+ boolean wasUserActionIntentSent =
+ sendPendingUserActionIntentIfNeeded(/* forPreapproval= */false);
if (mUserActionRequired == null) {
mUserActionRequired = wasUserActionIntentSent;
}
@@ -2591,6 +2597,18 @@
}
}
+ private IntentSender getPreapprovalRemoteStatusReceiver() {
+ synchronized (mLock) {
+ return mPreapprovalRemoteStatusReceiver;
+ }
+ }
+
+ private void setPreapprovalRemoteStatusReceiver(IntentSender remoteStatusReceiver) {
+ synchronized (mLock) {
+ mPreapprovalRemoteStatusReceiver = remoteStatusReceiver;
+ }
+ }
+
/**
* Prepares staged directory with any inherited APKs.
*/
@@ -2828,7 +2846,8 @@
private void onVerificationComplete() {
if (isStaged()) {
mStagingManager.commitSession(mStagedSession);
- sendUpdateToRemoteStatusReceiver(INSTALL_SUCCEEDED, "Session staged", null);
+ sendUpdateToRemoteStatusReceiver(INSTALL_SUCCEEDED, "Session staged",
+ /* extras= */ null, /* forPreapproval= */ false);
return;
}
install();
@@ -4585,7 +4604,11 @@
}
private void dispatchSessionFinished(int returnCode, String msg, Bundle extras) {
- sendUpdateToRemoteStatusReceiver(returnCode, msg, extras);
+ // Session can be marked as finished due to user rejecting pre approval or commit request,
+ // any internal error or after successful completion. As such, check whether
+ // the session is in the preapproval stage or the commit stage.
+ sendUpdateToRemoteStatusReceiver(returnCode, msg, extras,
+ /* forPreapproval= */ isPreapprovalRequested() && !isCommitted());
synchronized (mLock) {
mFinalStatus = returnCode;
@@ -4607,8 +4630,10 @@
}
}
- private void sendUpdateToRemoteStatusReceiver(int returnCode, String msg, Bundle extras) {
- final IntentSender statusReceiver = getRemoteStatusReceiver();
+ private void sendUpdateToRemoteStatusReceiver(int returnCode, String msg, Bundle extras,
+ boolean forPreapproval) {
+ final IntentSender statusReceiver = forPreapproval ? getPreapprovalRemoteStatusReceiver()
+ : getRemoteStatusReceiver();
if (statusReceiver != null) {
// Execute observer.onPackageInstalled on different thread as we don't want callers
// inside the system server have to worry about catching the callbacks while they are
@@ -4624,8 +4649,8 @@
}
}
- private void dispatchSessionPreappoved() {
- final IntentSender target = getRemoteStatusReceiver();
+ private void dispatchSessionPreapproved() {
+ final IntentSender target = getPreapprovalRemoteStatusReceiver();
final Intent intent = new Intent();
intent.putExtra(PackageInstaller.EXTRA_SESSION_ID, sessionId);
intent.putExtra(PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_SUCCESS);
@@ -4646,7 +4671,8 @@
if (!mPm.isPreapprovalRequestAvailable()) {
sendUpdateToRemoteStatusReceiver(INSTALL_FAILED_PRE_APPROVAL_NOT_AVAILABLE,
- "Request user pre-approval is currently not available.", null /* extras */);
+ "Request user pre-approval is currently not available.", /* extras= */null,
+ /* preapproval= */true);
return;
}
@@ -4668,7 +4694,7 @@
synchronized (mLock) {
assertPreparedAndNotSealedLocked("request of session " + sessionId);
mPreapprovalDetails = details;
- setRemoteStatusReceiver(statusReceiver);
+ setPreapprovalRemoteStatusReceiver(statusReceiver);
}
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 5a81a1a..9137d44 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -2825,7 +2825,8 @@
// NOTE: Can't remove due to unsupported app usage
public int checkPermission(String permName, String pkgName, int userId) {
- return mPermissionManager.checkPermission(pkgName, permName, userId);
+ return mPermissionManager.checkPermission(pkgName, permName, Context.DEVICE_ID_DEFAULT,
+ userId);
}
public String getSdkSandboxPackageName() {
@@ -2998,14 +2999,14 @@
// action. When the targetPkg is set, it sends the broadcast to specific app, e.g.
// installer app or null for registered apps. The callback only need to send back to the
// registered apps so we check the null condition here.
- notifyPackageMonitor(action, pkg, extras, userIds, instantUserIds);
+ notifyPackageMonitor(action, pkg, extras, userIds, instantUserIds, broadcastAllowList);
}
}
void notifyPackageMonitor(String action, String pkg, Bundle extras, int[] userIds,
- int[] instantUserIds) {
+ int[] instantUserIds, SparseArray<int[]> broadcastAllowList) {
mPackageMonitorCallbackHelper.notifyPackageMonitor(action, pkg, extras, userIds,
- instantUserIds);
+ instantUserIds, broadcastAllowList);
}
void notifyResourcesChanged(boolean mediaStatus, boolean replacing,
@@ -3061,7 +3062,7 @@
mHandler.post(() -> mBroadcastHelper.sendPackageAddedForNewUsers(
packageName, appId, userIds, instantUserIds, dataLoaderType, broadcastAllowList));
mPackageMonitorCallbackHelper.notifyPackageAddedForNewUsers(packageName, appId, userIds,
- instantUserIds, dataLoaderType);
+ instantUserIds, dataLoaderType, broadcastAllowList);
if (sendBootCompleted && !ArrayUtils.isEmpty(userIds)) {
mHandler.post(() -> {
for (int userId : userIds) {
@@ -3425,6 +3426,18 @@
// within these users.
mPermissionManager.restoreDelayedRuntimePermissions(packageName, userId);
+ // Restore default browser setting if it is now installed.
+ String defaultBrowser;
+ synchronized (mLock) {
+ defaultBrowser = mSettings.getPendingDefaultBrowserLPr(userId);
+ }
+ if (Objects.equals(packageName, defaultBrowser)) {
+ mDefaultAppProvider.setDefaultBrowser(packageName, userId);
+ synchronized (mLock) {
+ mSettings.removePendingDefaultBrowserLPw(userId);
+ }
+ }
+
// Persistent preferred activity might have came into effect due to this
// install.
mPreferredActivityHelper.updateDefaultHomeNotLocked(snapshotComputer(), userId);
@@ -4055,7 +4068,7 @@
packageName, dontKillApp, componentNames, packageUid, reason, userIds,
instantUserIds, broadcastAllowList));
mPackageMonitorCallbackHelper.notifyPackageChanged(packageName, dontKillApp, componentNames,
- packageUid, reason, userIds, instantUserIds);
+ packageUid, reason, userIds, instantUserIds, broadcastAllowList);
}
/**
@@ -6225,7 +6238,8 @@
@Override
public void registerPackageMonitorCallback(@NonNull IRemoteCallback callback, int userId) {
- mPackageMonitorCallbackHelper.registerPackageMonitorCallback(callback, userId);
+ int uid = Binder.getCallingUid();
+ mPackageMonitorCallbackHelper.registerPackageMonitorCallback(callback, userId, uid);
}
@Override
@@ -6647,7 +6661,7 @@
@Override
public String removeLegacyDefaultBrowserPackageName(int userId) {
synchronized (mLock) {
- return mSettings.removeDefaultBrowserPackageNameLPw(userId);
+ return mSettings.removePendingDefaultBrowserLPw(userId);
}
}
@@ -7523,8 +7537,8 @@
return mDefaultAppProvider.getDefaultBrowser(userId);
}
- void setDefaultBrowser(@Nullable String packageName, boolean async, @UserIdInt int userId) {
- mDefaultAppProvider.setDefaultBrowser(packageName, async, userId);
+ void setDefaultBrowser(@Nullable String packageName, @UserIdInt int userId) {
+ mDefaultAppProvider.setDefaultBrowser(packageName, userId);
}
PackageUsage getPackageUsage() {
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 5cb480c0..8d64bd9 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -319,6 +319,8 @@
return runCreateUser();
case "remove-user":
return runRemoveUser();
+ case "mark-guest-for-deletion":
+ return runMarkGuestForDeletion();
case "rename-user":
return runRenameUser();
case "set-user-restriction":
@@ -3203,6 +3205,24 @@
}
}
+ private int runMarkGuestForDeletion() throws RemoteException {
+ String arg = getNextArg();
+ if (arg == null) {
+ getErrPrintWriter().println("Error: no user id specified.");
+ return 1;
+ }
+ int userId = resolveUserId(UserHandle.parseUserArg(arg));
+
+ IUserManager um = IUserManager.Stub.asInterface(
+ ServiceManager.getService(Context.USER_SERVICE));
+ if (!um.markGuestForDeletion(userId)) {
+ getErrPrintWriter().println("Error: could not mark guest for deletion");
+ return 1;
+ }
+
+ return 0;
+ }
+
private int runRenameUser() throws RemoteException {
String arg = getNextArg();
if (arg == null) {
@@ -3436,10 +3456,6 @@
sessionParams.installFlags |=
PackageManager.INSTALL_BYPASS_LOW_TARGET_SDK_BLOCK;
break;
- case "--no-profile":
- sessionParams.installFlags |=
- PackageManager.INSTALL_DONT_EXTRACT_BASELINE_PROFILES;
- break;
default:
throw new IllegalArgumentException("Unknown option " + opt);
}
@@ -4328,7 +4344,7 @@
pw.println(" [--install-reason 0/1/2/3/4] [--originating-uri URI]");
pw.println(" [--referrer URI] [--abi ABI_NAME] [--force-sdk]");
pw.println(" [--preload] [--instant] [--full] [--dont-kill]");
- pw.println(" [--enable-rollback] [--no-profile]");
+ pw.println(" [--enable-rollback]");
pw.println(" [--force-uuid internal|UUID] [--pkg PACKAGE] [-S BYTES]");
pw.println(" [--apex] [--force-non-staged] [--staged-ready-timeout TIMEOUT]");
pw.println(" [PATH [SPLIT...]|-]");
@@ -4361,7 +4377,6 @@
pw.println(" --apex: install an .apex file, not an .apk");
pw.println(" --force-non-staged: force the installation to run under a non-staged");
pw.println(" session, which may complete without requiring a reboot");
- pw.println(" --no-profile: don't extract the profiles from the apk");
pw.println(" --staged-ready-timeout: By default, staged sessions wait "
+ DEFAULT_STAGED_READY_TIMEOUT_MS);
pw.println(" milliseconds for pre-reboot verification to complete when");
@@ -4383,7 +4398,7 @@
pw.println(" [--referrer URI] [--abi ABI_NAME] [--force-sdk]");
pw.println(" [--preload] [--instant] [--full] [--dont-kill]");
pw.println(" [--force-uuid internal|UUID] [--pkg PACKAGE] [--apex] [-S BYTES]");
- pw.println(" [--multi-package] [--staged] [--no-profile] [--update-ownership]");
+ pw.println(" [--multi-package] [--staged] [--update-ownership]");
pw.println(" Like \"install\", but starts an install session. Use \"install-write\"");
pw.println(" to push data into the session, and \"install-commit\" to finish.");
pw.println("");
@@ -4516,6 +4531,11 @@
pw.println(" switch or reboot)");
pw.println(" --wait: Wait until user is removed. Ignored if set-ephemeral-if-in-use");
pw.println("");
+ pw.println(" mark-guest-for-deletion USER_ID");
+ pw.println(" Mark the guest user for deletion. After this, it is possible to create a");
+ pw.println(" new guest user and switch to it. This allows resetting the guest user");
+ pw.println(" without switching to another user.");
+ pw.println("");
pw.println(" rename-user USER_ID [USER_NAME]");
pw.println(" Rename USER_ID with USER_NAME (or null when [USER_NAME] is not set)");
pw.println("");
diff --git a/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java b/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java
index 20d1508..a771502 100644
--- a/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java
+++ b/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java
@@ -29,10 +29,13 @@
import android.os.Bundle;
import android.os.Handler;
import android.os.IRemoteCallback;
+import android.os.Process;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.UserHandle;
import android.text.TextUtils;
+import android.util.Slog;
+import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.ArrayUtils;
@@ -42,6 +45,9 @@
/** Helper class to handle PackageMonitorCallback and notify the registered client. This is mainly
* used by PackageMonitor to improve the broadcast latency. */
class PackageMonitorCallbackHelper {
+
+ private static final boolean DEBUG = false;
+
@NonNull
private final Object mLock = new Object();
final IActivityManager mActivityManager = ActivityManager.getService();
@@ -56,9 +62,9 @@
@GuardedBy("mLock")
private final RemoteCallbackList<IRemoteCallback> mCallbacks = new RemoteCallbackList<>();
- public void registerPackageMonitorCallback(IRemoteCallback callback, int userId) {
+ public void registerPackageMonitorCallback(IRemoteCallback callback, int userId, int uid) {
synchronized (mLock) {
- mCallbacks.register(callback, userId);
+ mCallbacks.register(callback, new RegisterUser(userId, uid));
}
}
@@ -69,23 +75,32 @@
}
public void onUserRemoved(int userId) {
- RemoteCallbackList<IRemoteCallback> callbacks;
+ ArrayList<IRemoteCallback> targetUnRegisteredCallbacks = null;
synchronized (mLock) {
- callbacks = mCallbacks;
+ int registerCount = mCallbacks.getRegisteredCallbackCount();
+ for (int i = 0; i < registerCount; i++) {
+ RegisterUser registerUser =
+ (RegisterUser) mCallbacks.getRegisteredCallbackCookie(i);
+ if (registerUser.getUserId() == userId) {
+ IRemoteCallback callback = mCallbacks.getRegisteredCallbackItem(i);
+ if (targetUnRegisteredCallbacks == null) {
+ targetUnRegisteredCallbacks = new ArrayList<>();
+ }
+ targetUnRegisteredCallbacks.add(callback);
+ }
+ }
}
- int registerCount = callbacks.getRegisteredCallbackCount();
- for (int i = 0; i < registerCount; i++) {
- int registerUserId = (int) callbacks.getRegisteredCallbackCookie(i);
- if (registerUserId == userId) {
- IRemoteCallback callback = callbacks.getRegisteredCallbackItem(i);
- unregisterPackageMonitorCallback(callback);
+ if (targetUnRegisteredCallbacks != null && targetUnRegisteredCallbacks.size() > 0) {
+ int count = targetUnRegisteredCallbacks.size();
+ for (int i = 0; i < count; i++) {
+ unregisterPackageMonitorCallback(targetUnRegisteredCallbacks.get(i));
}
}
}
public void notifyPackageAddedForNewUsers(String packageName,
@AppIdInt int appId, @NonNull int[] userIds, @NonNull int[] instantUserIds,
- int dataLoaderType) {
+ int dataLoaderType, SparseArray<int[]> broadcastAllowList) {
Bundle extras = new Bundle(2);
// Set to UID of the first user, EXTRA_UID is automatically updated in sendPackageBroadcast
final int uid = UserHandle.getUid(
@@ -93,7 +108,7 @@
extras.putInt(Intent.EXTRA_UID, uid);
extras.putInt(PackageInstaller.EXTRA_DATA_LOADER_TYPE, dataLoaderType);
notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED, packageName, extras ,
- userIds /* userIds */, instantUserIds);
+ userIds /* userIds */, instantUserIds, broadcastAllowList);
}
public void notifyResourcesChanged(boolean mediaStatus, boolean replacing,
@@ -107,12 +122,12 @@
String action = mediaStatus ? Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE
: Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE;
notifyPackageMonitor(action, null /* pkg */, extras, null /* userIds */,
- null /* instantUserIds */);
+ null /* instantUserIds */, null /* broadcastAllowList */);
}
public void notifyPackageChanged(String packageName, boolean dontKillApp,
ArrayList<String> componentNames, int packageUid, String reason, int[] userIds,
- int[] instantUserIds) {
+ int[] instantUserIds, SparseArray<int[]> broadcastAllowList) {
Bundle extras = new Bundle(4);
extras.putString(Intent.EXTRA_CHANGED_COMPONENT_NAME, componentNames.get(0));
String[] nameList = new String[componentNames.size()];
@@ -124,11 +139,11 @@
extras.putString(Intent.EXTRA_REASON, reason);
}
notifyPackageMonitor(Intent.ACTION_PACKAGE_CHANGED, packageName, extras, userIds,
- instantUserIds);
+ instantUserIds, broadcastAllowList);
}
public void notifyPackageMonitor(String action, String pkg, Bundle extras,
- int[] userIds, int[] instantUserIds) {
+ int[] userIds, int[] instantUserIds, SparseArray<int[]> broadcastAllowList) {
if (!isAllowedCallbackAction(action)) {
return;
}
@@ -142,9 +157,9 @@
}
if (ArrayUtils.isEmpty(instantUserIds)) {
- doNotifyCallbacks(action, pkg, extras, resolvedUserIds);
+ doNotifyCallbacks(action, pkg, extras, resolvedUserIds, broadcastAllowList);
} else {
- doNotifyCallbacks(action, pkg, extras, instantUserIds);
+ doNotifyCallbacks(action, pkg, extras, instantUserIds, broadcastAllowList);
}
} catch (RemoteException e) {
// do nothing
@@ -162,7 +177,8 @@
|| TextUtils.equals(action, Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
}
- private void doNotifyCallbacks(String action, String pkg, Bundle extras, int[] userIds) {
+ private void doNotifyCallbacks(String action, String pkg, Bundle extras, int[] userIds,
+ SparseArray<int[]> broadcastAllowList) {
RemoteCallbackList<IRemoteCallback> callbacks;
synchronized (mLock) {
callbacks = mCallbacks;
@@ -180,9 +196,23 @@
}
intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
+ final int[] allowUids =
+ broadcastAllowList != null ? broadcastAllowList.get(userId) : new int[]{};
+
mHandler.post(() -> callbacks.broadcast((callback, user) -> {
- int registerUserId = (int) user;
- if ((registerUserId != UserHandle.USER_ALL) && (registerUserId != userId)) {
+ RegisterUser registerUser = (RegisterUser) user;
+ if ((registerUser.getUserId() != UserHandle.USER_ALL) && (registerUser.getUserId()
+ != userId)) {
+ return;
+ }
+ int registerUid = registerUser.getUid();
+ if (broadcastAllowList != null && registerUid != Process.SYSTEM_UID
+ && !ArrayUtils.contains(allowUids, registerUid)) {
+ if (DEBUG) {
+ Slog.w("PackageMonitorCallbackHelper",
+ "Skip invoke PackageMonitorCallback for " + action + ", uid "
+ + registerUid);
+ }
return;
}
invokeCallback(callback, intent);
@@ -200,4 +230,22 @@
// do nothing
}
}
+
+ private final class RegisterUser {
+ int mUserId;
+ int mUid;
+
+ RegisterUser(int userId, int uid) {
+ mUid = uid;
+ mUserId = userId;
+ }
+
+ public int getUid() {
+ return mUid;
+ }
+
+ public int getUserId() {
+ return mUserId;
+ }
+ }
}
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index 3e9ccac..494709c 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -48,6 +48,7 @@
import com.android.server.pm.permission.LegacyPermissionDataProvider;
import com.android.server.pm.permission.LegacyPermissionState;
import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.ArchiveState;
import com.android.server.pm.pkg.PackageState;
import com.android.server.pm.pkg.PackageStateInternal;
import com.android.server.pm.pkg.PackageStateUnserialized;
@@ -171,7 +172,7 @@
@NonNull
private InstallSource installSource;
- /** @see PackageState#getVolumeUuid() */
+ /** @see PackageState#getVolumeUuid() */
@Nullable
private String volumeUuid;
@@ -448,6 +449,7 @@
/**
* Notify {@link #onChanged()} if the parameter {@code usesLibraryFiles} is different from
* {@link #getUsesLibraryFiles()}.
+ *
* @param usesLibraryFiles the new uses library files
* @return {@code this}
*/
@@ -875,7 +877,7 @@
ArraySet<String> enabledComponents, ArraySet<String> disabledComponents,
int installReason, int uninstallReason,
String harmfulAppWarning, String splashScreenTheme,
- long firstInstallTime, int aspectRatio) {
+ long firstInstallTime, int aspectRatio, ArchiveState archiveState) {
modifyUserState(userId)
.setSuspendParams(suspendParams)
.setCeDataInode(ceDataInode)
@@ -895,7 +897,8 @@
.setHarmfulAppWarning(harmfulAppWarning)
.setSplashScreenTheme(splashScreenTheme)
.setFirstInstallTimeMillis(firstInstallTime)
- .setMinAspectRatio(aspectRatio);
+ .setMinAspectRatio(aspectRatio)
+ .setArchiveState(archiveState);
onChanged();
}
@@ -913,7 +916,8 @@
? null : otherState.getDisabledComponentsNoCopy().untrackedStorage(),
otherState.getInstallReason(), otherState.getUninstallReason(),
otherState.getHarmfulAppWarning(), otherState.getSplashScreenTheme(),
- otherState.getFirstInstallTimeMillis(), otherState.getMinAspectRatio());
+ otherState.getFirstInstallTimeMillis(), otherState.getMinAspectRatio(),
+ otherState.getArchiveState());
}
WatchedArraySet<String> getEnabledComponents(int userId) {
@@ -1108,10 +1112,36 @@
state.getLastDisableAppCaller());
proto.write(PackageProto.UserInfoProto.FIRST_INSTALL_TIME_MS,
state.getFirstInstallTimeMillis());
+ writeArchiveState(proto, state.getArchiveState());
proto.end(userToken);
}
}
+ private static void writeArchiveState(ProtoOutputStream proto, ArchiveState archiveState) {
+ long archiveStateToken = proto.start(PackageProto.UserInfoProto.ARCHIVE_STATE);
+ for (ArchiveState.ArchiveActivityInfo activityInfo : archiveState.getActivityInfos()) {
+ long activityInfoToken = proto.start(
+ PackageProto.UserInfoProto.ArchiveState.ACTIVITY_INFOS);
+ proto.write(PackageProto.UserInfoProto.ArchiveState.ArchiveActivityInfo.TITLE,
+ activityInfo.getTitle());
+ proto.write(
+ PackageProto.UserInfoProto.ArchiveState.ArchiveActivityInfo.ICON_BITMAP_PATH,
+ activityInfo.getIconBitmap().toAbsolutePath().toString());
+ proto.write(
+ PackageProto
+ .UserInfoProto
+ .ArchiveState
+ .ArchiveActivityInfo
+ .MONOCHROME_ICON_BITMAP_PATH,
+ activityInfo.getMonochromeIconBitmap().toAbsolutePath().toString());
+ proto.end(activityInfoToken);
+ }
+
+ proto.write(PackageProto.UserInfoProto.ArchiveState.INSTALLER_TITLE,
+ archiveState.getInstallerTitle());
+ proto.end(archiveStateToken);
+ }
+
/**
* @see #mPath
*/
@@ -1165,8 +1195,11 @@
}
public PackageSetting setLoadingProgress(float progress) {
- mLoadingProgress = progress;
- onChanged();
+ // To prevent race conditions, we don't allow progress to ever go down
+ if (mLoadingProgress < progress) {
+ mLoadingProgress = progress;
+ onChanged();
+ }
return this;
}
@@ -1599,10 +1632,10 @@
}
@DataClass.Generated(
- time = 1680917079522L,
+ time = 1688743336932L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/services/core/java/com/android/server/pm/PackageSetting.java",
- inputSignatures = "private int mSharedUserAppId\nprivate @android.annotation.Nullable java.util.Map<java.lang.String,java.util.Set<java.lang.String>> mimeGroups\nprivate @java.lang.Deprecated @android.annotation.Nullable java.util.Set<java.lang.String> mOldCodePaths\nprivate @android.annotation.Nullable java.lang.String[] usesSdkLibraries\nprivate @android.annotation.Nullable long[] usesSdkLibrariesVersionsMajor\nprivate @android.annotation.Nullable java.lang.String[] usesStaticLibraries\nprivate @android.annotation.Nullable long[] usesStaticLibrariesVersions\nprivate @android.annotation.Nullable @java.lang.Deprecated java.lang.String legacyNativeLibraryPath\nprivate @android.annotation.NonNull java.lang.String mName\nprivate @android.annotation.Nullable java.lang.String mRealName\nprivate int mAppId\nprivate @android.annotation.Nullable com.android.server.pm.parsing.pkg.AndroidPackageInternal pkg\nprivate @android.annotation.NonNull java.io.File mPath\nprivate @android.annotation.NonNull java.lang.String mPathString\nprivate float mLoadingProgress\nprivate long mLoadingCompletedTime\nprivate @android.annotation.Nullable java.lang.String mPrimaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mSecondaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mCpuAbiOverride\nprivate long mLastModifiedTime\nprivate long lastUpdateTime\nprivate long versionCode\nprivate @android.annotation.NonNull com.android.server.pm.PackageSignatures signatures\nprivate boolean installPermissionsFixed\nprivate @android.annotation.NonNull com.android.server.pm.PackageKeySetData keySetData\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.pkg.PackageUserStateImpl> mUserStates\nprivate @android.annotation.NonNull com.android.server.pm.InstallSource installSource\nprivate @android.annotation.Nullable java.lang.String volumeUuid\nprivate int categoryOverride\nprivate boolean updateAvailable\nprivate boolean forceQueryableOverride\nprivate final @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized pkgState\nprivate @android.annotation.NonNull java.util.UUID mDomainSetId\nprivate @android.annotation.Nullable java.lang.String mAppMetadataFilePath\nprivate final @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> mSnapshot\nprivate com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> makeCache()\npublic com.android.server.pm.PackageSetting snapshot()\npublic void dumpDebug(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\npublic com.android.server.pm.PackageSetting setAppId(int)\npublic com.android.server.pm.PackageSetting setCpuAbiOverride(java.lang.String)\npublic com.android.server.pm.PackageSetting setFirstInstallTimeFromReplaced(com.android.server.pm.pkg.PackageStateInternal,int[])\npublic com.android.server.pm.PackageSetting setFirstInstallTime(long,int)\npublic com.android.server.pm.PackageSetting setForceQueryableOverride(boolean)\npublic com.android.server.pm.PackageSetting setInstallerPackage(java.lang.String,int)\npublic com.android.server.pm.PackageSetting setUpdateOwnerPackage(java.lang.String)\npublic com.android.server.pm.PackageSetting setInstallSource(com.android.server.pm.InstallSource)\n com.android.server.pm.PackageSetting removeInstallerPackage(java.lang.String)\npublic com.android.server.pm.PackageSetting setIsOrphaned(boolean)\npublic com.android.server.pm.PackageSetting setKeySetData(com.android.server.pm.PackageKeySetData)\npublic com.android.server.pm.PackageSetting setLastModifiedTime(long)\npublic com.android.server.pm.PackageSetting setLastUpdateTime(long)\npublic com.android.server.pm.PackageSetting setLongVersionCode(long)\npublic boolean setMimeGroup(java.lang.String,android.util.ArraySet<java.lang.String>)\npublic com.android.server.pm.PackageSetting setPkg(com.android.server.pm.pkg.AndroidPackage)\npublic com.android.server.pm.PackageSetting setPkgStateLibraryFiles(java.util.Collection<java.lang.String>)\npublic com.android.server.pm.PackageSetting setPrimaryCpuAbi(java.lang.String)\npublic com.android.server.pm.PackageSetting setSecondaryCpuAbi(java.lang.String)\npublic com.android.server.pm.PackageSetting setSignatures(com.android.server.pm.PackageSignatures)\npublic com.android.server.pm.PackageSetting setVolumeUuid(java.lang.String)\npublic @java.lang.Override boolean isExternalStorage()\npublic com.android.server.pm.PackageSetting setUpdateAvailable(boolean)\npublic void setSharedUserAppId(int)\npublic @java.lang.Override int getSharedUserAppId()\npublic @java.lang.Override boolean hasSharedUser()\npublic @java.lang.Override java.lang.String toString()\nprotected void copyMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic void updateFrom(com.android.server.pm.PackageSetting)\n com.android.server.pm.PackageSetting updateMimeGroups(java.util.Set<java.lang.String>)\npublic @java.lang.Deprecated @java.lang.Override com.android.server.pm.permission.LegacyPermissionState getLegacyPermissionState()\npublic com.android.server.pm.PackageSetting setInstallPermissionsFixed(boolean)\npublic boolean isPrivileged()\npublic boolean isOem()\npublic boolean isVendor()\npublic boolean isProduct()\npublic @java.lang.Override boolean isRequiredForSystemUser()\npublic boolean isSystemExt()\npublic boolean isOdm()\npublic boolean isSystem()\npublic android.content.pm.SigningDetails getSigningDetails()\npublic com.android.server.pm.PackageSetting setSigningDetails(android.content.pm.SigningDetails)\npublic void copyPackageSetting(com.android.server.pm.PackageSetting,boolean)\n @com.android.internal.annotations.VisibleForTesting com.android.server.pm.pkg.PackageUserStateImpl modifyUserState(int)\npublic com.android.server.pm.pkg.PackageUserStateImpl getOrCreateUserState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateInternal readUserState(int)\n void setEnabled(int,int,java.lang.String)\n int getEnabled(int)\n void setInstalled(boolean,int)\n boolean getInstalled(int)\n int getInstallReason(int)\n void setInstallReason(int,int)\n int getUninstallReason(int)\n void setUninstallReason(int,int)\n @android.annotation.NonNull android.content.pm.overlay.OverlayPaths getOverlayPaths(int)\n boolean setOverlayPathsForLibrary(java.lang.String,android.content.pm.overlay.OverlayPaths,int)\n boolean isAnyInstalled(int[])\n int[] queryInstalledUsers(int[],boolean)\n long getCeDataInode(int)\n void setCeDataInode(long,int)\n boolean getStopped(int)\n void setStopped(boolean,int)\n boolean getNotLaunched(int)\n void setNotLaunched(boolean,int)\n boolean getHidden(int)\n void setHidden(boolean,int)\n int getDistractionFlags(int)\n void setDistractionFlags(int,int)\npublic boolean getInstantApp(int)\n void setInstantApp(boolean,int)\n boolean getVirtualPreload(int)\n void setVirtualPreload(boolean,int)\n void setUserState(int,long,int,boolean,boolean,boolean,boolean,int,android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>,boolean,boolean,java.lang.String,android.util.ArraySet<java.lang.String>,android.util.ArraySet<java.lang.String>,int,int,java.lang.String,java.lang.String,long)\n void setUserState(int,com.android.server.pm.pkg.PackageUserStateInternal)\n com.android.server.utils.WatchedArraySet<java.lang.String> getEnabledComponents(int)\n com.android.server.utils.WatchedArraySet<java.lang.String> getDisabledComponents(int)\n void setEnabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setDisabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setEnabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setDisabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n com.android.server.pm.pkg.PackageUserStateImpl modifyUserStateComponents(int,boolean,boolean)\n void addDisabledComponent(java.lang.String,int)\n void addEnabledComponent(java.lang.String,int)\n boolean enableComponentLPw(java.lang.String,int)\n boolean disableComponentLPw(java.lang.String,int)\n boolean restoreComponentLPw(java.lang.String,int)\n int getCurrentEnabledStateLPr(java.lang.String,int)\n void removeUser(int)\npublic int[] getNotInstalledUserIds()\n void writePackageUserPermissionsProto(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\nprotected void writeUsersInfoToProto(android.util.proto.ProtoOutputStream,long)\n com.android.server.pm.PackageSetting setPath(java.io.File)\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideNonLocalizedLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer,int)\npublic void resetOverrideComponentLabelIcon(int)\npublic @android.annotation.Nullable java.lang.String getSplashScreenTheme(int)\npublic boolean isIncremental()\npublic boolean isLoading()\npublic com.android.server.pm.PackageSetting setLoadingProgress(float)\npublic com.android.server.pm.PackageSetting setLoadingCompletedTime(long)\npublic com.android.server.pm.PackageSetting setAppMetadataFilePath(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override long getVersionCode()\npublic @android.annotation.Nullable @java.lang.Override java.util.Map<java.lang.String,java.util.Set<java.lang.String>> getMimeGroups()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String getPackageName()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.pm.pkg.AndroidPackage getAndroidPackage()\npublic @android.annotation.NonNull android.content.pm.SigningInfo getSigningInfo()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesSdkLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesSdkLibrariesVersionsMajor()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesStaticLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesStaticLibrariesVersions()\npublic @android.annotation.NonNull @java.lang.Override java.util.List<com.android.server.pm.pkg.SharedLibrary> getSharedLibraryDependencies()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryInfo(android.content.pm.SharedLibraryInfo)\npublic @android.annotation.NonNull @java.lang.Override java.util.List<java.lang.String> getUsesLibraryFiles()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryFile(java.lang.String)\npublic @java.lang.Override boolean isHiddenUntilInstalled()\npublic @android.annotation.NonNull @java.lang.Override long[] getLastPackageUsageTime()\npublic @java.lang.Override boolean isUpdatedSystemApp()\npublic @java.lang.Override boolean isApkInUpdatedApex()\npublic @android.annotation.Nullable @java.lang.Override java.lang.String getApexModuleName()\npublic com.android.server.pm.PackageSetting setDomainSetId(java.util.UUID)\npublic com.android.server.pm.PackageSetting setCategoryOverride(int)\npublic com.android.server.pm.PackageSetting setLegacyNativeLibraryPath(java.lang.String)\npublic com.android.server.pm.PackageSetting setMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic com.android.server.pm.PackageSetting setOldCodePaths(java.util.Set<java.lang.String>)\npublic com.android.server.pm.PackageSetting setUsesSdkLibraries(java.lang.String[])\npublic com.android.server.pm.PackageSetting setUsesSdkLibrariesVersionsMajor(long[])\npublic com.android.server.pm.PackageSetting setUsesStaticLibraries(java.lang.String[])\npublic com.android.server.pm.PackageSetting setUsesStaticLibrariesVersions(long[])\npublic com.android.server.pm.PackageSetting setApexModuleName(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageStateUnserialized getTransientState()\npublic @android.annotation.NonNull android.util.SparseArray<? extends PackageUserStateInternal> getUserStates()\npublic com.android.server.pm.PackageSetting addMimeTypes(java.lang.String,java.util.Set<java.lang.String>)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserState getStateForUser(android.os.UserHandle)\npublic @android.annotation.Nullable java.lang.String getPrimaryCpuAbi()\npublic @android.annotation.Nullable java.lang.String getSecondaryCpuAbi()\npublic @android.annotation.Nullable @java.lang.Override java.lang.String getSeInfo()\npublic @android.annotation.Nullable java.lang.String getPrimaryCpuAbiLegacy()\npublic @android.annotation.Nullable java.lang.String getSecondaryCpuAbiLegacy()\npublic @android.content.pm.ApplicationInfo.HiddenApiEnforcementPolicy @java.lang.Override int getHiddenApiEnforcementPolicy()\npublic @java.lang.Override boolean isApex()\nclass PackageSetting extends com.android.server.pm.SettingBase implements [com.android.server.pm.pkg.PackageStateInternal]\n@com.android.internal.util.DataClass(genGetters=true, genConstructor=false, genSetters=false, genBuilder=false)")
+ inputSignatures = "private int mSharedUserAppId\nprivate @android.annotation.Nullable java.util.Map<java.lang.String,java.util.Set<java.lang.String>> mimeGroups\nprivate @java.lang.Deprecated @android.annotation.Nullable java.util.Set<java.lang.String> mOldCodePaths\nprivate @android.annotation.Nullable java.lang.String[] usesSdkLibraries\nprivate @android.annotation.Nullable long[] usesSdkLibrariesVersionsMajor\nprivate @android.annotation.Nullable java.lang.String[] usesStaticLibraries\nprivate @android.annotation.Nullable long[] usesStaticLibrariesVersions\nprivate @android.annotation.Nullable @java.lang.Deprecated java.lang.String legacyNativeLibraryPath\nprivate @android.annotation.NonNull java.lang.String mName\nprivate @android.annotation.Nullable java.lang.String mRealName\nprivate int mAppId\nprivate @android.annotation.Nullable com.android.server.pm.parsing.pkg.AndroidPackageInternal pkg\nprivate @android.annotation.NonNull java.io.File mPath\nprivate @android.annotation.NonNull java.lang.String mPathString\nprivate float mLoadingProgress\nprivate long mLoadingCompletedTime\nprivate @android.annotation.Nullable java.lang.String mPrimaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mSecondaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mCpuAbiOverride\nprivate long mLastModifiedTime\nprivate long lastUpdateTime\nprivate long versionCode\nprivate @android.annotation.NonNull com.android.server.pm.PackageSignatures signatures\nprivate boolean installPermissionsFixed\nprivate @android.annotation.NonNull com.android.server.pm.PackageKeySetData keySetData\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.pkg.PackageUserStateImpl> mUserStates\nprivate @android.annotation.NonNull com.android.server.pm.InstallSource installSource\nprivate @android.annotation.Nullable java.lang.String volumeUuid\nprivate int categoryOverride\nprivate boolean updateAvailable\nprivate boolean forceQueryableOverride\nprivate final @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized pkgState\nprivate @android.annotation.NonNull java.util.UUID mDomainSetId\nprivate @android.annotation.Nullable java.lang.String mAppMetadataFilePath\nprivate final @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> mSnapshot\nprivate com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> makeCache()\npublic com.android.server.pm.PackageSetting snapshot()\npublic void dumpDebug(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\npublic com.android.server.pm.PackageSetting setAppId(int)\npublic com.android.server.pm.PackageSetting setCpuAbiOverride(java.lang.String)\npublic com.android.server.pm.PackageSetting setFirstInstallTimeFromReplaced(com.android.server.pm.pkg.PackageStateInternal,int[])\npublic com.android.server.pm.PackageSetting setFirstInstallTime(long,int)\npublic com.android.server.pm.PackageSetting setForceQueryableOverride(boolean)\npublic com.android.server.pm.PackageSetting setInstallerPackage(java.lang.String,int)\npublic com.android.server.pm.PackageSetting setUpdateOwnerPackage(java.lang.String)\npublic com.android.server.pm.PackageSetting setInstallSource(com.android.server.pm.InstallSource)\n com.android.server.pm.PackageSetting removeInstallerPackage(java.lang.String)\npublic com.android.server.pm.PackageSetting setIsOrphaned(boolean)\npublic com.android.server.pm.PackageSetting setKeySetData(com.android.server.pm.PackageKeySetData)\npublic com.android.server.pm.PackageSetting setLastModifiedTime(long)\npublic com.android.server.pm.PackageSetting setLastUpdateTime(long)\npublic com.android.server.pm.PackageSetting setLongVersionCode(long)\npublic boolean setMimeGroup(java.lang.String,android.util.ArraySet<java.lang.String>)\npublic com.android.server.pm.PackageSetting setPkg(com.android.server.pm.pkg.AndroidPackage)\npublic com.android.server.pm.PackageSetting setPkgStateLibraryFiles(java.util.Collection<java.lang.String>)\npublic com.android.server.pm.PackageSetting setPrimaryCpuAbi(java.lang.String)\npublic com.android.server.pm.PackageSetting setSecondaryCpuAbi(java.lang.String)\npublic com.android.server.pm.PackageSetting setSignatures(com.android.server.pm.PackageSignatures)\npublic com.android.server.pm.PackageSetting setVolumeUuid(java.lang.String)\npublic @java.lang.Override boolean isExternalStorage()\npublic com.android.server.pm.PackageSetting setUpdateAvailable(boolean)\npublic void setSharedUserAppId(int)\npublic @java.lang.Override int getSharedUserAppId()\npublic @java.lang.Override boolean hasSharedUser()\npublic @java.lang.Override java.lang.String toString()\nprotected void copyMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic void updateFrom(com.android.server.pm.PackageSetting)\n com.android.server.pm.PackageSetting updateMimeGroups(java.util.Set<java.lang.String>)\npublic @java.lang.Deprecated @java.lang.Override com.android.server.pm.permission.LegacyPermissionState getLegacyPermissionState()\npublic com.android.server.pm.PackageSetting setInstallPermissionsFixed(boolean)\npublic boolean isPrivileged()\npublic boolean isOem()\npublic boolean isVendor()\npublic boolean isProduct()\npublic @java.lang.Override boolean isRequiredForSystemUser()\npublic boolean isSystemExt()\npublic boolean isOdm()\npublic boolean isSystem()\npublic android.content.pm.SigningDetails getSigningDetails()\npublic com.android.server.pm.PackageSetting setSigningDetails(android.content.pm.SigningDetails)\npublic void copyPackageSetting(com.android.server.pm.PackageSetting,boolean)\n @com.android.internal.annotations.VisibleForTesting com.android.server.pm.pkg.PackageUserStateImpl modifyUserState(int)\npublic com.android.server.pm.pkg.PackageUserStateImpl getOrCreateUserState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateInternal readUserState(int)\n void setEnabled(int,int,java.lang.String)\n int getEnabled(int)\n void setInstalled(boolean,int)\n boolean getInstalled(int)\n int getInstallReason(int)\n void setInstallReason(int,int)\n int getUninstallReason(int)\n void setUninstallReason(int,int)\n @android.annotation.NonNull android.content.pm.overlay.OverlayPaths getOverlayPaths(int)\n boolean setOverlayPathsForLibrary(java.lang.String,android.content.pm.overlay.OverlayPaths,int)\n boolean isAnyInstalled(int[])\n int[] queryInstalledUsers(int[],boolean)\n long getCeDataInode(int)\n void setCeDataInode(long,int)\n boolean getStopped(int)\n void setStopped(boolean,int)\n boolean getNotLaunched(int)\n void setNotLaunched(boolean,int)\n boolean getHidden(int)\n void setHidden(boolean,int)\n int getDistractionFlags(int)\n void setDistractionFlags(int,int)\npublic boolean getInstantApp(int)\n void setInstantApp(boolean,int)\n boolean getVirtualPreload(int)\n void setVirtualPreload(boolean,int)\n void setUserState(int,long,int,boolean,boolean,boolean,boolean,int,android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>,boolean,boolean,java.lang.String,android.util.ArraySet<java.lang.String>,android.util.ArraySet<java.lang.String>,int,int,java.lang.String,java.lang.String,long,com.android.server.pm.pkg.ArchiveState)\n void setUserState(int,com.android.server.pm.pkg.PackageUserStateInternal)\n com.android.server.utils.WatchedArraySet<java.lang.String> getEnabledComponents(int)\n com.android.server.utils.WatchedArraySet<java.lang.String> getDisabledComponents(int)\n void setEnabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setDisabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setEnabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setDisabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n com.android.server.pm.pkg.PackageUserStateImpl modifyUserStateComponents(int,boolean,boolean)\n void addDisabledComponent(java.lang.String,int)\n void addEnabledComponent(java.lang.String,int)\n boolean enableComponentLPw(java.lang.String,int)\n boolean disableComponentLPw(java.lang.String,int)\n boolean restoreComponentLPw(java.lang.String,int)\n int getCurrentEnabledStateLPr(java.lang.String,int)\n void removeUser(int)\npublic int[] getNotInstalledUserIds()\n void writePackageUserPermissionsProto(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\nprotected void writeUsersInfoToProto(android.util.proto.ProtoOutputStream,long)\nprivate static void writeArchiveState(android.util.proto.ProtoOutputStream,com.android.server.pm.pkg.ArchiveState)\n com.android.server.pm.PackageSetting setPath(java.io.File)\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideNonLocalizedLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer,int)\npublic void resetOverrideComponentLabelIcon(int)\npublic @android.annotation.Nullable java.lang.String getSplashScreenTheme(int)\npublic boolean isIncremental()\npublic boolean isLoading()\npublic com.android.server.pm.PackageSetting setLoadingProgress(float)\npublic com.android.server.pm.PackageSetting setLoadingCompletedTime(long)\npublic com.android.server.pm.PackageSetting setAppMetadataFilePath(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override long getVersionCode()\npublic @android.annotation.Nullable @java.lang.Override java.util.Map<java.lang.String,java.util.Set<java.lang.String>> getMimeGroups()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String getPackageName()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.pm.pkg.AndroidPackage getAndroidPackage()\npublic @android.annotation.NonNull android.content.pm.SigningInfo getSigningInfo()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesSdkLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesSdkLibrariesVersionsMajor()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesStaticLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesStaticLibrariesVersions()\npublic @android.annotation.NonNull @java.lang.Override java.util.List<com.android.server.pm.pkg.SharedLibrary> getSharedLibraryDependencies()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryInfo(android.content.pm.SharedLibraryInfo)\npublic @android.annotation.NonNull @java.lang.Override java.util.List<java.lang.String> getUsesLibraryFiles()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryFile(java.lang.String)\npublic @java.lang.Override boolean isHiddenUntilInstalled()\npublic @android.annotation.NonNull @java.lang.Override long[] getLastPackageUsageTime()\npublic @java.lang.Override boolean isUpdatedSystemApp()\npublic @java.lang.Override boolean isApkInUpdatedApex()\npublic @android.annotation.Nullable @java.lang.Override java.lang.String getApexModuleName()\npublic com.android.server.pm.PackageSetting setDomainSetId(java.util.UUID)\npublic com.android.server.pm.PackageSetting setCategoryOverride(int)\npublic com.android.server.pm.PackageSetting setLegacyNativeLibraryPath(java.lang.String)\npublic com.android.server.pm.PackageSetting setMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic com.android.server.pm.PackageSetting setOldCodePaths(java.util.Set<java.lang.String>)\npublic com.android.server.pm.PackageSetting setUsesSdkLibraries(java.lang.String[])\npublic com.android.server.pm.PackageSetting setUsesSdkLibrariesVersionsMajor(long[])\npublic com.android.server.pm.PackageSetting setUsesStaticLibraries(java.lang.String[])\npublic com.android.server.pm.PackageSetting setUsesStaticLibrariesVersions(long[])\npublic com.android.server.pm.PackageSetting setApexModuleName(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageStateUnserialized getTransientState()\npublic @android.annotation.NonNull android.util.SparseArray<? extends PackageUserStateInternal> getUserStates()\npublic com.android.server.pm.PackageSetting addMimeTypes(java.lang.String,java.util.Set<java.lang.String>)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserState getStateForUser(android.os.UserHandle)\npublic @android.annotation.Nullable java.lang.String getPrimaryCpuAbi()\npublic @android.annotation.Nullable java.lang.String getSecondaryCpuAbi()\npublic @android.annotation.Nullable @java.lang.Override java.lang.String getSeInfo()\npublic @android.annotation.Nullable java.lang.String getPrimaryCpuAbiLegacy()\npublic @android.annotation.Nullable java.lang.String getSecondaryCpuAbiLegacy()\npublic @android.content.pm.ApplicationInfo.HiddenApiEnforcementPolicy @java.lang.Override int getHiddenApiEnforcementPolicy()\npublic @java.lang.Override boolean isApex()\nclass PackageSetting extends com.android.server.pm.SettingBase implements [com.android.server.pm.pkg.PackageStateInternal]\n@com.android.internal.util.DataClass(genGetters=true, genConstructor=false, genSetters=false, genBuilder=false)")
@Deprecated
private void __metadata() {}
diff --git a/services/core/java/com/android/server/pm/PreferredActivityHelper.java b/services/core/java/com/android/server/pm/PreferredActivityHelper.java
index 6e273cf..571aab4 100644
--- a/services/core/java/com/android/server/pm/PreferredActivityHelper.java
+++ b/services/core/java/com/android/server/pm/PreferredActivityHelper.java
@@ -585,7 +585,17 @@
(parser1, userId1) -> {
final String defaultBrowser = Settings.readDefaultApps(parser1);
if (defaultBrowser != null) {
- mPm.setDefaultBrowser(defaultBrowser, false, userId1);
+ final PackageStateInternal packageState = mPm.snapshotComputer()
+ .getPackageStateInternal(defaultBrowser);
+ if (packageState != null
+ && packageState.getUserStateOrDefault(userId1).isInstalled()) {
+ mPm.setDefaultBrowser(defaultBrowser, userId1);
+ } else {
+ synchronized (mPm.mLock) {
+ mPm.mSettings.setPendingDefaultBrowserLPw(defaultBrowser,
+ userId1);
+ }
+ }
}
});
} catch (Exception e) {
diff --git a/services/core/java/com/android/server/pm/ScanPackageUtils.java b/services/core/java/com/android/server/pm/ScanPackageUtils.java
index 80277d5..1cd44e6 100644
--- a/services/core/java/com/android/server/pm/ScanPackageUtils.java
+++ b/services/core/java/com/android/server/pm/ScanPackageUtils.java
@@ -149,6 +149,8 @@
String primaryCpuAbiFromSettings = null;
String secondaryCpuAbiFromSettings = null;
boolean needToDeriveAbi = (scanFlags & SCAN_FIRST_BOOT_OR_UPGRADE) != 0;
+ boolean isApex = (scanFlags & SCAN_AS_APEX) != 0;
+
if (!needToDeriveAbi) {
if (pkgSetting != null) {
// TODO(b/154610922): if it is not first boot or upgrade, we should directly use
@@ -240,6 +242,7 @@
usesStaticLibraries, parsedPackage.getUsesStaticLibrariesVersions(),
parsedPackage.getMimeGroups(), newDomainSetId);
}
+
if (createNewPackage && originalPkgSetting != null) {
// This is the initial transition from the original package, so,
// fix up the new package's name now. We must do this after looking
@@ -279,85 +282,91 @@
final boolean isUpdatedSystemApp = pkgSetting.isUpdatedSystemApp();
final File appLib32InstallDir = getAppLib32InstallDir();
- if ((scanFlags & SCAN_NEW_INSTALL) == 0) {
- if (needToDeriveAbi) {
- Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "derivePackageAbi");
- try {
- final Pair<PackageAbiHelper.Abis, PackageAbiHelper.NativeLibraryPaths>
- derivedAbi =
- packageAbiHelper.derivePackageAbi(
- parsedPackage,
- isSystemApp,
- isUpdatedSystemApp,
- cpuAbiOverride,
- appLib32InstallDir);
- derivedAbi.first.applyTo(parsedPackage);
- derivedAbi.second.applyTo(parsedPackage);
- } finally {
- Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
- }
+ // The native libs of Apex is located in apex_payload.img, don't need to parse it from
+ // the original apex file
+ if (!isApex) {
+ if ((scanFlags & SCAN_NEW_INSTALL) == 0) {
+ if (needToDeriveAbi) {
+ Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "derivePackageAbi");
+ try {
+ final Pair<PackageAbiHelper.Abis, PackageAbiHelper.NativeLibraryPaths>
+ derivedAbi =
+ packageAbiHelper.derivePackageAbi(
+ parsedPackage,
+ isSystemApp,
+ isUpdatedSystemApp,
+ cpuAbiOverride,
+ appLib32InstallDir);
+ derivedAbi.first.applyTo(parsedPackage);
+ derivedAbi.second.applyTo(parsedPackage);
+ } finally {
+ Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+ }
- // Some system apps still use directory structure for native libraries
- // in which case we might end up not detecting abi solely based on apk
- // structure. Try to detect abi based on directory structure.
+ // Some system apps still use directory structure for native libraries
+ // in which case we might end up not detecting abi solely based on apk
+ // structure. Try to detect abi based on directory structure.
- String pkgRawPrimaryCpuAbi = AndroidPackageUtils.getRawPrimaryCpuAbi(parsedPackage);
- if (isSystemApp && !isUpdatedSystemApp && pkgRawPrimaryCpuAbi == null) {
- final PackageAbiHelper.Abis abis = packageAbiHelper.getBundledAppAbis(
+ String pkgRawPrimaryCpuAbi = AndroidPackageUtils.getRawPrimaryCpuAbi(
parsedPackage);
- abis.applyTo(parsedPackage);
- abis.applyTo(pkgSetting);
+ if (isSystemApp && !isUpdatedSystemApp && pkgRawPrimaryCpuAbi == null) {
+ final PackageAbiHelper.Abis abis = packageAbiHelper.getBundledAppAbis(
+ parsedPackage);
+ abis.applyTo(parsedPackage);
+ abis.applyTo(pkgSetting);
+ final PackageAbiHelper.NativeLibraryPaths nativeLibraryPaths =
+ packageAbiHelper.deriveNativeLibraryPaths(parsedPackage,
+ isSystemApp, isUpdatedSystemApp, appLib32InstallDir);
+ nativeLibraryPaths.applyTo(parsedPackage);
+ }
+ } else {
+ // This is not a first boot or an upgrade, don't bother deriving the
+ // ABI during the scan. Instead, trust the value that was stored in the
+ // package setting.
+ parsedPackage.setPrimaryCpuAbi(primaryCpuAbiFromSettings)
+ .setSecondaryCpuAbi(secondaryCpuAbiFromSettings);
+
final PackageAbiHelper.NativeLibraryPaths nativeLibraryPaths =
packageAbiHelper.deriveNativeLibraryPaths(parsedPackage, isSystemApp,
isUpdatedSystemApp, appLib32InstallDir);
nativeLibraryPaths.applyTo(parsedPackage);
+
+ if (DEBUG_ABI_SELECTION) {
+ Slog.i(TAG, "Using ABIS and native lib paths from settings : "
+ + parsedPackage.getPackageName() + " "
+ + AndroidPackageUtils.getRawPrimaryCpuAbi(parsedPackage)
+ + ", "
+ + AndroidPackageUtils.getRawSecondaryCpuAbi(parsedPackage));
+ }
}
} else {
- // This is not a first boot or an upgrade, don't bother deriving the
- // ABI during the scan. Instead, trust the value that was stored in the
- // package setting.
- parsedPackage.setPrimaryCpuAbi(primaryCpuAbiFromSettings)
- .setSecondaryCpuAbi(secondaryCpuAbiFromSettings);
+ if ((scanFlags & SCAN_MOVE) != 0) {
+ // We haven't run dex-opt for this move (since we've moved the compiled output
+ // too) but we already have this packages package info in the PackageSetting.
+ // We just use that and derive the native library path based on the new code
+ // path.
+ parsedPackage.setPrimaryCpuAbi(pkgSetting.getPrimaryCpuAbiLegacy())
+ .setSecondaryCpuAbi(pkgSetting.getSecondaryCpuAbiLegacy());
+ }
+ // Set native library paths again. For moves, the path will be updated based on the
+ // ABIs we've determined above. For non-moves, the path will be updated based on the
+ // ABIs we determined during compilation, but the path will depend on the final
+ // package path (after the rename away from the stage path).
final PackageAbiHelper.NativeLibraryPaths nativeLibraryPaths =
packageAbiHelper.deriveNativeLibraryPaths(parsedPackage, isSystemApp,
isUpdatedSystemApp, appLib32InstallDir);
nativeLibraryPaths.applyTo(parsedPackage);
-
- if (DEBUG_ABI_SELECTION) {
- Slog.i(TAG, "Using ABIS and native lib paths from settings : "
- + parsedPackage.getPackageName() + " "
- + AndroidPackageUtils.getRawPrimaryCpuAbi(parsedPackage)
- + ", "
- + AndroidPackageUtils.getRawSecondaryCpuAbi(parsedPackage));
- }
- }
- } else {
- if ((scanFlags & SCAN_MOVE) != 0) {
- // We haven't run dex-opt for this move (since we've moved the compiled output too)
- // but we already have this packages package info in the PackageSetting. We just
- // use that and derive the native library path based on the new code path.
- parsedPackage.setPrimaryCpuAbi(pkgSetting.getPrimaryCpuAbiLegacy())
- .setSecondaryCpuAbi(pkgSetting.getSecondaryCpuAbiLegacy());
}
- // Set native library paths again. For moves, the path will be updated based on the
- // ABIs we've determined above. For non-moves, the path will be updated based on the
- // ABIs we determined during compilation, but the path will depend on the final
- // package path (after the rename away from the stage path).
- final PackageAbiHelper.NativeLibraryPaths nativeLibraryPaths =
- packageAbiHelper.deriveNativeLibraryPaths(parsedPackage, isSystemApp,
- isUpdatedSystemApp, appLib32InstallDir);
- nativeLibraryPaths.applyTo(parsedPackage);
- }
-
- // This is a special case for the "system" package, where the ABI is
- // dictated by the zygote configuration (and init.rc). We should keep track
- // of this ABI so that we can deal with "normal" applications that run under
- // the same UID correctly.
- if (isPlatformPackage) {
- parsedPackage.setPrimaryCpuAbi(VMRuntime.getRuntime().is64Bit()
- ? Build.SUPPORTED_64_BIT_ABIS[0] : Build.SUPPORTED_32_BIT_ABIS[0]);
+ // This is a special case for the "system" package, where the ABI is
+ // dictated by the zygote configuration (and init.rc). We should keep track
+ // of this ABI so that we can deal with "normal" applications that run under
+ // the same UID correctly.
+ if (isPlatformPackage) {
+ parsedPackage.setPrimaryCpuAbi(VMRuntime.getRuntime().is64Bit()
+ ? Build.SUPPORTED_64_BIT_ABIS[0] : Build.SUPPORTED_32_BIT_ABIS[0]);
+ }
}
// If there's a mismatch between the abi-override in the package setting
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 87f9126..e5ad01f 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -107,6 +107,7 @@
import com.android.server.pm.permission.LegacyPermissionState;
import com.android.server.pm.permission.LegacyPermissionState.PermissionState;
import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.ArchiveState;
import com.android.server.pm.pkg.PackageStateInternal;
import com.android.server.pm.pkg.PackageUserState;
import com.android.server.pm.pkg.PackageUserStateInternal;
@@ -153,6 +154,7 @@
import java.io.PrintWriter;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
@@ -316,6 +318,8 @@
private static final String TAG_SUSPEND_PARAMS = "suspend-params";
private static final String TAG_MIME_GROUP = "mime-group";
private static final String TAG_MIME_TYPE = "mime-type";
+ private static final String TAG_ARCHIVE_STATE = "archive-state";
+ private static final String TAG_ARCHIVE_ACTIVITY_INFO = "archive-activity-info";
public static final String ATTR_NAME = "name";
public static final String ATTR_PACKAGE = "package";
@@ -362,6 +366,10 @@
private static final String ATTR_DATABASE_VERSION = "databaseVersion";
private static final String ATTR_VALUE = "value";
private static final String ATTR_FIRST_INSTALL_TIME = "first-install-time";
+ private static final String ATTR_ARCHIVE_ACTIVITY_TITLE = "activity-title";
+ private static final String ATTR_ARCHIVE_INSTALLER_TITLE = "installer-title";
+ private static final String ATTR_ARCHIVE_ICON_PATH = "icon-path";
+ private static final String ATTR_ARCHIVE_MONOCHROME_ICON_PATH = "monochrome-icon-path";
private final Handler mHandler;
@@ -518,9 +526,11 @@
private final WatchedArrayMap<String, String> mRenamedPackages =
new WatchedArrayMap<String, String>();
- // For every user, it is used to find the package name of the default Browser App.
+ // For every user, it is used to find the package name of the default browser app pending to be
+ // applied, either on first boot after upgrade, or after backup & restore but before app is
+ // installed.
@Watched
- final WatchedSparseArray<String> mDefaultBrowserApp = new WatchedSparseArray<String>();
+ final WatchedSparseArray<String> mPendingDefaultBrowser = new WatchedSparseArray<>();
// TODO(b/161161364): This seems unused, and is probably not relevant in the new API, but should
// verify.
@@ -593,7 +603,7 @@
mAppIds.registerObserver(mObserver);
mRenamedPackages.registerObserver(mObserver);
mNextAppLinkGeneration.registerObserver(mObserver);
- mDefaultBrowserApp.registerObserver(mObserver);
+ mPendingDefaultBrowser.registerObserver(mObserver);
mPendingPackages.registerObserver(mObserver);
mPastSignatures.registerObserver(mObserver);
mKeySetRefs.registerObserver(mObserver);
@@ -788,7 +798,7 @@
mRenamedPackages.snapshot(r.mRenamedPackages);
mNextAppLinkGeneration.snapshot(r.mNextAppLinkGeneration);
- mDefaultBrowserApp.snapshot(r.mDefaultBrowserApp);
+ mPendingDefaultBrowser.snapshot(r.mPendingDefaultBrowser);
// mReadMessages
mPendingPackages = r.mPendingPackagesSnapshot.snapshot();
mPendingPackagesSnapshot = new SnapshotCache.Sealed<>();
@@ -1129,7 +1139,8 @@
null /*harmfulAppWarning*/,
null /*splashscreenTheme*/,
0 /*firstInstallTime*/,
- PackageManager.USER_MIN_ASPECT_RATIO_UNSET
+ PackageManager.USER_MIN_ASPECT_RATIO_UNSET,
+ null /*archiveState*/
);
}
}
@@ -1509,8 +1520,16 @@
return cpir;
}
- String removeDefaultBrowserPackageNameLPw(int userId) {
- return (userId == UserHandle.USER_ALL) ? null : mDefaultBrowserApp.removeReturnOld(userId);
+ String getPendingDefaultBrowserLPr(int userId) {
+ return mPendingDefaultBrowser.get(userId);
+ }
+
+ void setPendingDefaultBrowserLPw(String defaultBrowser, int userId) {
+ mPendingDefaultBrowser.put(userId, defaultBrowser);
+ }
+
+ String removePendingDefaultBrowserLPw(int userId) {
+ return mPendingDefaultBrowser.removeReturnOld(userId);
}
private File getUserSystemDirectory(int userId) {
@@ -1695,7 +1714,7 @@
throws XmlPullParserException, IOException {
String defaultBrowser = readDefaultApps(parser);
if (defaultBrowser != null) {
- mDefaultBrowserApp.put(userId, defaultBrowser);
+ mPendingDefaultBrowser.put(userId, defaultBrowser);
}
}
@@ -1797,7 +1816,8 @@
null /*harmfulAppWarning*/,
null /* splashScreenTheme*/,
0 /*firstInstallTime*/,
- PackageManager.USER_MIN_ASPECT_RATIO_UNSET
+ PackageManager.USER_MIN_ASPECT_RATIO_UNSET,
+ null /*archiveState*/
);
}
return;
@@ -1901,6 +1921,7 @@
PersistableBundle suspendedAppExtras = null;
PersistableBundle suspendedLauncherExtras = null;
SuspendDialogInfo oldSuspendDialogInfo = null;
+ ArchiveState archiveState = null;
int packageDepth = parser.getDepth();
ArrayMap<String, SuspendParams> suspendParamsMap = null;
@@ -1942,6 +1963,9 @@
suspendParamsMap.put(suspendingPackage,
SuspendParams.restoreFromXml(parser));
break;
+ case TAG_ARCHIVE_STATE:
+ archiveState = parseArchiveState(parser);
+ break;
default:
Slog.wtf(TAG, "Unknown tag " + parser.getName() + " under tag "
+ TAG_PACKAGE);
@@ -1972,7 +1996,7 @@
uninstallReason, harmfulAppWarning, splashScreenTheme,
firstInstallTime != 0 ? firstInstallTime :
origFirstInstallTimes.getOrDefault(name, 0L),
- minAspectRatio);
+ minAspectRatio, archiveState);
mDomainVerificationManager.setLegacyUserState(name, userId, verifState);
} else if (tagName.equals("preferred-activities")) {
@@ -2001,6 +2025,64 @@
}
}
+ private static ArchiveState parseArchiveState(TypedXmlPullParser parser)
+ throws XmlPullParserException, IOException {
+ String installerTitle = parser.getAttributeValue(null,
+ ATTR_ARCHIVE_INSTALLER_TITLE);
+ List<ArchiveState.ArchiveActivityInfo> activityInfos =
+ parseArchiveActivityInfos(parser);
+
+ if (installerTitle == null) {
+ Slog.wtf(TAG, "parseArchiveState: installerTitle is null");
+ return null;
+ }
+
+ if (activityInfos.size() < 1) {
+ Slog.wtf(TAG, "parseArchiveState: activityInfos is empty");
+ return null;
+ }
+
+ return new ArchiveState(activityInfos, installerTitle);
+ }
+
+ private static List<ArchiveState.ArchiveActivityInfo> parseArchiveActivityInfos(
+ TypedXmlPullParser parser) throws XmlPullParserException, IOException {
+ List<ArchiveState.ArchiveActivityInfo> activityInfos = new ArrayList<>();
+ int type;
+ int outerDepth = parser.getDepth();
+ String tagName;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG
+ || parser.getDepth() > outerDepth)) {
+ if (type == XmlPullParser.END_TAG
+ || type == XmlPullParser.TEXT) {
+ continue;
+ }
+ tagName = parser.getName();
+ if (tagName.equals(TAG_ARCHIVE_ACTIVITY_INFO)) {
+ String title = parser.getAttributeValue(null,
+ ATTR_ARCHIVE_ACTIVITY_TITLE);
+ Path iconPath = Path.of(parser.getAttributeValue(null,
+ ATTR_ARCHIVE_ICON_PATH));
+ Path monochromeIconPath = Path.of(parser.getAttributeValue(null,
+ ATTR_ARCHIVE_MONOCHROME_ICON_PATH));
+
+ if (title == null || iconPath == null) {
+ Slog.wtf(TAG,
+ TextUtils.formatSimple("Missing attributes in tag %s. %s: %s, %s: %s",
+ TAG_ARCHIVE_ACTIVITY_INFO, ATTR_ARCHIVE_ACTIVITY_TITLE, title,
+ ATTR_ARCHIVE_ICON_PATH,
+ iconPath));
+ continue;
+ }
+
+ activityInfos.add(
+ new ArchiveState.ArchiveActivityInfo(title, iconPath, monochromeIconPath));
+ }
+ }
+ return activityInfos;
+ }
+
void setBlockUninstallLPw(int userId, String packageName, boolean blockUninstall) {
ArraySet<String> packages = mBlockUninstallPackages.get(userId);
if (blockUninstall) {
@@ -2105,7 +2187,7 @@
void writeDefaultAppsLPr(XmlSerializer serializer, int userId)
throws IllegalArgumentException, IllegalStateException, IOException {
- String defaultBrowser = mDefaultBrowserApp.get(userId);
+ String defaultBrowser = mPendingDefaultBrowser.get(userId);
writeDefaultApps(serializer, defaultBrowser);
}
@@ -2313,6 +2395,7 @@
}
serializer.endTag(null, TAG_DISABLED_COMPONENTS);
}
+ writeArchiveStateLPr(serializer, ustate.getArchiveState());
serializer.endTag(null, TAG_PACKAGE);
}
@@ -2351,6 +2434,28 @@
}
}
+ private void writeArchiveStateLPr(TypedXmlSerializer serializer, ArchiveState archiveState)
+ throws IOException {
+ if (archiveState == null) {
+ return;
+ }
+
+ serializer.startTag(null, TAG_ARCHIVE_STATE);
+ serializer.attribute(null, ATTR_ARCHIVE_INSTALLER_TITLE, archiveState.getInstallerTitle());
+ for (ArchiveState.ArchiveActivityInfo activityInfo : archiveState.getActivityInfos()) {
+ serializer.startTag(null, TAG_ARCHIVE_ACTIVITY_INFO);
+ serializer.attribute(null, ATTR_ARCHIVE_ACTIVITY_TITLE, activityInfo.getTitle());
+ serializer.attribute(null, ATTR_ARCHIVE_ICON_PATH,
+ activityInfo.getIconBitmap().toAbsolutePath().toString());
+ if (activityInfo.getMonochromeIconBitmap() != null) {
+ serializer.attribute(null, ATTR_ARCHIVE_MONOCHROME_ICON_PATH,
+ activityInfo.getMonochromeIconBitmap().toAbsolutePath().toString());
+ }
+ serializer.endTag(null, TAG_ARCHIVE_ACTIVITY_INFO);
+ }
+ serializer.endTag(null, TAG_ARCHIVE_STATE);
+ }
+
void readInstallPermissionsLPr(TypedXmlPullParser parser,
LegacyPermissionState permissionsState, List<UserInfo> users)
throws IOException, XmlPullParserException {
@@ -5193,6 +5298,10 @@
}
}
}
+ ArchiveState archiveState = userState.getArchiveState();
+ if (archiveState != null) {
+ pw.print(archiveState.toString());
+ }
}
}
diff --git a/services/core/java/com/android/server/pm/SuspendPackageHelper.java b/services/core/java/com/android/server/pm/SuspendPackageHelper.java
index 893bc11a..94e09f1 100644
--- a/services/core/java/com/android/server/pm/SuspendPackageHelper.java
+++ b/services/core/java/com/android/server/pm/SuspendPackageHelper.java
@@ -634,7 +634,7 @@
mPm.snapshotComputer(), callingUid, intentExtras),
options));
mPm.notifyPackageMonitor(intent, null /* pkg */, extras, new int[]{userId},
- null /* instantUserIds */);
+ null /* instantUserIds */, null /* broadcastAllowList */);
}
/**
diff --git a/services/core/java/com/android/server/pm/TEST_MAPPING b/services/core/java/com/android/server/pm/TEST_MAPPING
index 01e0fbd..3c846da 100644
--- a/services/core/java/com/android/server/pm/TEST_MAPPING
+++ b/services/core/java/com/android/server/pm/TEST_MAPPING
@@ -90,7 +90,7 @@
]
},
{
- "name": "CtsAppSecurityHostTestCases",
+ "name": "CtsInstantAppsHostTestCases",
"file_patterns": ["(/|^)PackageMonitorCallbackHelper\\.java"],
"options": [
{
@@ -125,7 +125,7 @@
]
},
{
- "name": "CtsAppSecurityHostTestCases"
+ "name": "CtsPackageManagerHostTestCases"
},
{
"name": "PackageManagerServiceHostTests"
diff --git a/services/core/java/com/android/server/pm/VerifyingSession.java b/services/core/java/com/android/server/pm/VerifyingSession.java
index 7198de2..3a1fd7c 100644
--- a/services/core/java/com/android/server/pm/VerifyingSession.java
+++ b/services/core/java/com/android/server/pm/VerifyingSession.java
@@ -589,27 +589,14 @@
final PackageVerificationResponse response = new PackageVerificationResponse(
verificationCodeAtTimeout, requiredUid);
- if (streaming) {
- // For streaming installations, count verification timeout from the broadcast.
- startVerificationTimeoutCountdown(verificationId, streaming, response,
- verificationTimeout);
- }
+ startVerificationTimeoutCountdown(verificationId, streaming, response,
+ verificationTimeout);
// Send the intent to the required verification agent, but only start the
// verification timeout after the target BroadcastReceivers have run.
mPm.mContext.sendOrderedBroadcastAsUser(requiredIntent, verifierUser,
receiverPermission, AppOpsManager.OP_NONE, options.toBundle(),
- new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (!streaming) {
- // For NON-streaming installations, count verification timeout from
- // the broadcast was processed by all receivers.
- startVerificationTimeoutCountdown(verificationId, streaming,
- response, verificationTimeout);
- }
- }
- }, null, 0, null, null);
+ null, null, 0, null, null);
}
Trace.asyncTraceBegin(TRACE_TAG_PACKAGE_MANAGER, "verification", verificationId);
diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
index 58c31b8..c44b8852 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -26,7 +26,6 @@
import android.app.DownloadManager;
import android.app.SearchManager;
import android.app.admin.DevicePolicyManager;
-import android.companion.CompanionDeviceManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
@@ -902,7 +901,7 @@
// Companion devices
grantSystemFixedPermissionsToSystemPackage(pm,
- CompanionDeviceManager.COMPANION_DEVICE_DISCOVERY_PACKAGE_NAME, userId,
+ getDefaultCompanionDeviceManagerPackage(), userId,
ALWAYS_LOCATION_PERMISSIONS, NEARBY_DEVICES_PERMISSIONS);
// Ringtone Picker
@@ -952,6 +951,10 @@
return mContext.getString(R.string.config_defaultDockManagerPackageName);
}
+ private String getDefaultCompanionDeviceManagerPackage() {
+ return mContext.getString(R.string.config_companionDeviceManagerPackage);
+ }
+
@SafeVarargs
private final void grantPermissionToEachSystemPackage(PackageManagerWrapper pm,
ArrayList<String> packages, int userId, Set<String>... permissions) {
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index dbe2d54..7609073 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -218,9 +218,12 @@
}
}
- private int checkPermission(String pkgName, String permName, @UserIdInt int userId) {
+ @Override
+ @PackageManager.PermissionResult
+ public int checkPermission(String packageName, String permissionName, int deviceId,
+ @UserIdInt int userId) {
// Not using Objects.requireNonNull() here for compatibility reasons.
- if (pkgName == null || permName == null) {
+ if (packageName == null || permissionName == null) {
return PackageManager.PERMISSION_DENIED;
}
@@ -230,15 +233,18 @@
}
if (checkPermissionDelegate == null) {
- return mPermissionManagerServiceImpl.checkPermission(pkgName, permName, userId);
+ return mPermissionManagerServiceImpl.checkPermission(
+ packageName, permissionName, userId);
}
- return checkPermissionDelegate.checkPermission(pkgName, permName, userId,
+ return checkPermissionDelegate.checkPermission(packageName, permissionName, userId,
mPermissionManagerServiceImpl::checkPermission);
}
- private int checkUidPermission(int uid, String permName) {
+ @Override
+ @PackageManager.PermissionResult
+ public int checkUidPermission(int uid, String permissionName, int deviceId) {
// Not using Objects.requireNonNull() here for compatibility reasons.
- if (permName == null) {
+ if (permissionName == null) {
return PackageManager.PERMISSION_DENIED;
}
@@ -248,9 +254,9 @@
}
if (checkPermissionDelegate == null) {
- return mPermissionManagerServiceImpl.checkUidPermission(uid, permName);
+ return mPermissionManagerServiceImpl.checkUidPermission(uid, permissionName);
}
- return checkPermissionDelegate.checkUidPermission(uid, permName,
+ return checkPermissionDelegate.checkUidPermission(uid, permissionName,
mPermissionManagerServiceImpl::checkUidPermission);
}
@@ -502,14 +508,15 @@
}
@Override
- public int getPermissionFlags(String packageName, String permissionName, int userId) {
+ public int getPermissionFlags(String packageName, String permissionName, int deviceId,
+ int userId) {
return mPermissionManagerServiceImpl
.getPermissionFlags(packageName, permissionName, userId);
}
@Override
public void updatePermissionFlags(String packageName, String permissionName, int flagMask,
- int flagValues, boolean checkAdjustPolicyFlagPermission, int userId) {
+ int flagValues, boolean checkAdjustPolicyFlagPermission, int deviceId, int userId) {
mPermissionManagerServiceImpl.updatePermissionFlags(packageName, permissionName, flagMask,
flagValues, checkAdjustPolicyFlagPermission, userId);
}
@@ -551,15 +558,16 @@
}
@Override
- public void grantRuntimePermission(String packageName, String permissionName, int userId) {
+ public void grantRuntimePermission(String packageName, String permissionName, int deviceId,
+ int userId) {
mPermissionManagerServiceImpl.grantRuntimePermission(packageName, permissionName, userId);
}
@Override
- public void revokeRuntimePermission(String packageName, String permissionName, int userId,
- String reason) {
- mPermissionManagerServiceImpl.revokeRuntimePermission(packageName, permissionName, userId,
- reason);
+ public void revokeRuntimePermission(String packageName, String permissionName, int deviceId,
+ int userId, String reason) {
+ mPermissionManagerServiceImpl.revokeRuntimePermission(packageName, permissionName,
+ userId, reason);
}
@Override
@@ -570,14 +578,14 @@
@Override
public boolean shouldShowRequestPermissionRationale(String packageName, String permissionName,
- int userId) {
+ int deviceId, int userId) {
return mPermissionManagerServiceImpl.shouldShowRequestPermissionRationale(packageName,
permissionName, userId);
}
@Override
public boolean isPermissionRevokedByPolicy(String packageName, String permissionName,
- int userId) {
+ int deviceId, int userId) {
return mPermissionManagerServiceImpl
.isPermissionRevokedByPolicy(packageName, permissionName, userId);
}
@@ -592,14 +600,14 @@
private class PermissionManagerServiceInternalImpl implements PermissionManagerServiceInternal {
@Override
public int checkPermission(@NonNull String packageName, @NonNull String permissionName,
- @UserIdInt int userId) {
+ int deviceId, @UserIdInt int userId) {
return PermissionManagerService.this.checkPermission(packageName, permissionName,
- userId);
+ deviceId, userId);
}
@Override
- public int checkUidPermission(int uid, @NonNull String permissionName) {
- return PermissionManagerService.this.checkUidPermission(uid, permissionName);
+ public int checkUidPermission(int uid, @NonNull String permissionName, int deviceId) {
+ return PermissionManagerService.this.checkUidPermission(uid, permissionName, deviceId);
}
@Override
@@ -805,6 +813,7 @@
public void resetRuntimePermissions(@NonNull AndroidPackage pkg, @UserIdInt int userId) {
mPermissionManagerServiceImpl.resetRuntimePermissions(pkg, userId);
}
+
@Override
public void resetRuntimePermissionsForUser(@UserIdInt int userId) {
mPermissionManagerServiceImpl.resetRuntimePermissionsForUser(userId);
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
index cf2b69c..98adeb6 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
@@ -46,12 +46,13 @@
*
* @param packageName the name of the package you are checking against
* @param permissionName the name of the permission you are checking for
+ * @param deviceId the device ID
* @param userId the user ID
* @return {@code PERMISSION_GRANTED} if the permission is granted, or {@code PERMISSION_DENIED}
* otherwise
*/
//@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
- int checkPermission(@NonNull String packageName, @NonNull String permissionName,
+ int checkPermission(@NonNull String packageName, @NonNull String permissionName, int deviceId,
@UserIdInt int userId);
/**
@@ -59,11 +60,12 @@
*
* @param uid the UID
* @param permissionName the name of the permission you are checking for
+ * @param deviceId the device for which you are checking the permission
* @return {@code PERMISSION_GRANTED} if the permission is granted, or {@code PERMISSION_DENIED}
* otherwise
*/
//@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
- int checkUidPermission(int uid, @NonNull String permissionName);
+ int checkUidPermission(int uid, @NonNull String permissionName, int deviceId);
/**
* Get whether permission review is required for a package.
@@ -73,8 +75,7 @@
* @return whether permission review is required
*/
//@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
- boolean isPermissionsReviewRequired(@NonNull String packageName,
- @UserIdInt int userId);
+ boolean isPermissionsReviewRequired(@NonNull String packageName, @UserIdInt int userId);
/**
* Reset the runtime permission state changes for a package.
@@ -85,8 +86,7 @@
* @param userId the user ID
*/
//@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
- void resetRuntimePermissions(@NonNull AndroidPackage pkg,
- @UserIdInt int userId);
+ void resetRuntimePermissions(@NonNull AndroidPackage pkg, @UserIdInt int userId);
/**
* Reset the runtime permission state changes for all packages in a user.
diff --git a/services/core/java/com/android/server/pm/pkg/ArchiveState.java b/services/core/java/com/android/server/pm/pkg/ArchiveState.java
new file mode 100644
index 0000000..d44ae16
--- /dev/null
+++ b/services/core/java/com/android/server/pm/pkg/ArchiveState.java
@@ -0,0 +1,306 @@
+/*
+ * 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.pm.pkg;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import com.android.internal.util.DataClass;
+
+import java.nio.file.Path;
+import java.util.List;
+
+/**
+ * Information about the state of an archived app. All fields are gathered at the time of
+ * archival.
+ *
+ * @hide
+ */
+@DataClass(genEqualsHashCode = true, genToString = true)
+public class ArchiveState {
+
+ /**
+ * Information about main activities.
+ *
+ * <p> This list has at least one entry. In the vast majority of cases, this list has only one
+ * entry.
+ */
+ @NonNull
+ private final List<ArchiveActivityInfo> mActivityInfos;
+
+ /**
+ * Corresponds to android:label of the installer responsible for the unarchival of the app.
+ * Stored in the installer's locale .
+ */
+ @NonNull
+ private final String mInstallerTitle;
+
+ /** Information about a main activity of an archived app. */
+ @DataClass(genEqualsHashCode = true, genToString = true)
+ public static class ArchiveActivityInfo {
+ /** Corresponds to the activity's android:label in the app's locale. */
+ @NonNull
+ private final String mTitle;
+
+ /** The path to the stored icon of the activity in the app's locale. */
+ @NonNull
+ private final Path mIconBitmap;
+
+ /** See {@link #mIconBitmap}. Only set if the app defined a monochrome icon. */
+ @Nullable
+ private final Path mMonochromeIconBitmap;
+
+
+
+ // Code below generated by codegen v1.0.23.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/services/core/java/com/android/server/pm/pkg/ArchiveState.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ /**
+ * Creates a new ArchiveActivityInfo.
+ *
+ * @param title
+ * Corresponds to the activity's android:label in the app's locale.
+ * @param iconBitmap
+ * The path to the stored icon of the activity in the app's locale.
+ * @param monochromeIconBitmap
+ * See {@link #mIconBitmap}. Only set if the app defined a monochrome icon.
+ */
+ @DataClass.Generated.Member
+ public ArchiveActivityInfo(
+ @NonNull String title,
+ @NonNull Path iconBitmap,
+ @Nullable Path monochromeIconBitmap) {
+ this.mTitle = title;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mTitle);
+ this.mIconBitmap = iconBitmap;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mIconBitmap);
+ this.mMonochromeIconBitmap = monochromeIconBitmap;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ /**
+ * Corresponds to the activity's android:label in the app's locale.
+ */
+ @DataClass.Generated.Member
+ public @NonNull String getTitle() {
+ return mTitle;
+ }
+
+ /**
+ * The path to the stored icon of the activity in the app's locale.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Path getIconBitmap() {
+ return mIconBitmap;
+ }
+
+ /**
+ * See {@link #mIconBitmap}. Only set if the app defined a monochrome icon.
+ */
+ @DataClass.Generated.Member
+ public @Nullable Path getMonochromeIconBitmap() {
+ return mMonochromeIconBitmap;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public String toString() {
+ // You can override field toString logic by defining methods like:
+ // String fieldNameToString() { ... }
+
+ return "ArchiveActivityInfo { " +
+ "title = " + mTitle + ", " +
+ "iconBitmap = " + mIconBitmap + ", " +
+ "monochromeIconBitmap = " + mMonochromeIconBitmap +
+ " }";
+ }
+
+ @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(ArchiveActivityInfo other) { ... }
+ // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ @SuppressWarnings("unchecked")
+ ArchiveActivityInfo that = (ArchiveActivityInfo) o;
+ //noinspection PointlessBooleanExpression
+ return true
+ && java.util.Objects.equals(mTitle, that.mTitle)
+ && java.util.Objects.equals(mIconBitmap, that.mIconBitmap)
+ && java.util.Objects.equals(mMonochromeIconBitmap, that.mMonochromeIconBitmap);
+ }
+
+ @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(mTitle);
+ _hash = 31 * _hash + java.util.Objects.hashCode(mIconBitmap);
+ _hash = 31 * _hash + java.util.Objects.hashCode(mMonochromeIconBitmap);
+ return _hash;
+ }
+
+ @DataClass.Generated(
+ time = 1689169065133L,
+ codegenVersion = "1.0.23",
+ sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/ArchiveState.java",
+ inputSignatures = "private final @android.annotation.NonNull java.lang.String mTitle\nprivate final @android.annotation.NonNull java.nio.file.Path mIconBitmap\nprivate final @android.annotation.Nullable java.nio.file.Path mMonochromeIconBitmap\nclass ArchiveActivityInfo extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+ }
+
+
+
+
+
+ // Code below generated by codegen v1.0.23.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/services/core/java/com/android/server/pm/pkg/ArchiveState.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ /**
+ * Creates a new ArchiveState.
+ *
+ * @param activityInfos
+ * Information about main activities.
+ *
+ * <p> This list has at least one entry. In the vast majority of cases, this list has only one
+ * entry.
+ * @param installerTitle
+ * Corresponds to android:label of the installer responsible for the unarchival of the app.
+ * Stored in the installer's locale .
+ */
+ @DataClass.Generated.Member
+ public ArchiveState(
+ @NonNull List<ArchiveActivityInfo> activityInfos,
+ @NonNull String installerTitle) {
+ this.mActivityInfos = activityInfos;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mActivityInfos);
+ this.mInstallerTitle = installerTitle;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mInstallerTitle);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ /**
+ * Information about main activities.
+ *
+ * <p> This list has at least one entry. In the vast majority of cases, this list has only one
+ * entry.
+ */
+ @DataClass.Generated.Member
+ public @NonNull List<ArchiveActivityInfo> getActivityInfos() {
+ return mActivityInfos;
+ }
+
+ /**
+ * Corresponds to android:label of the installer responsible for the unarchival of the app.
+ * Stored in the installer's locale .
+ */
+ @DataClass.Generated.Member
+ public @NonNull String getInstallerTitle() {
+ return mInstallerTitle;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public String toString() {
+ // You can override field toString logic by defining methods like:
+ // String fieldNameToString() { ... }
+
+ return "ArchiveState { " +
+ "activityInfos = " + mActivityInfos + ", " +
+ "installerTitle = " + mInstallerTitle +
+ " }";
+ }
+
+ @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(ArchiveState other) { ... }
+ // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ @SuppressWarnings("unchecked")
+ ArchiveState that = (ArchiveState) o;
+ //noinspection PointlessBooleanExpression
+ return true
+ && java.util.Objects.equals(mActivityInfos, that.mActivityInfos)
+ && java.util.Objects.equals(mInstallerTitle, that.mInstallerTitle);
+ }
+
+ @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(mActivityInfos);
+ _hash = 31 * _hash + java.util.Objects.hashCode(mInstallerTitle);
+ return _hash;
+ }
+
+ @DataClass.Generated(
+ time = 1689169065144L,
+ codegenVersion = "1.0.23",
+ sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/ArchiveState.java",
+ inputSignatures = "private final @android.annotation.NonNull java.util.List<com.android.server.pm.pkg.ArchiveActivityInfo> mActivityInfos\nprivate final @android.annotation.NonNull java.lang.String mInstallerTitle\nclass ArchiveState extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java b/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java
index 3d056e8..ba274e0 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java
@@ -380,6 +380,8 @@
@PackageManager.UserMinAspectRatio
private final int mMinAspectRatio;
private final long mFirstInstallTimeMillis;
+ @Nullable
+ private final ArchiveState mArchiveState;
private UserStateImpl(@NonNull PackageUserState userState) {
mCeDataInode = userState.getCeDataInode();
@@ -403,6 +405,7 @@
setBoolean(Booleans.SUSPENDED, userState.isSuspended());
setBoolean(Booleans.VIRTUAL_PRELOAD, userState.isVirtualPreload());
mFirstInstallTimeMillis = userState.getFirstInstallTimeMillis();
+ mArchiveState = userState.getArchiveState();
}
@Override
@@ -556,16 +559,21 @@
}
@DataClass.Generated.Member
+ public @Nullable ArchiveState getArchiveState() {
+ return mArchiveState;
+ }
+
+ @DataClass.Generated.Member
public @NonNull UserStateImpl setBooleans( int value) {
mBooleans = value;
return this;
}
@DataClass.Generated(
- time = 1687938966108L,
+ time = 1689171425723L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java",
- inputSignatures = "private int mBooleans\nprivate final long mCeDataInode\nprivate final @android.annotation.NonNull android.util.ArraySet<java.lang.String> mDisabledComponents\nprivate final @android.content.pm.PackageManager.DistractionRestriction int mDistractionFlags\nprivate final @android.annotation.NonNull android.util.ArraySet<java.lang.String> mEnabledComponents\nprivate final int mEnabledState\nprivate final @android.annotation.Nullable java.lang.String mHarmfulAppWarning\nprivate final @android.content.pm.PackageManager.InstallReason int mInstallReason\nprivate final @android.annotation.Nullable java.lang.String mLastDisableAppCaller\nprivate final @android.annotation.NonNull android.content.pm.overlay.OverlayPaths mOverlayPaths\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,android.content.pm.overlay.OverlayPaths> mSharedLibraryOverlayPaths\nprivate final @android.content.pm.PackageManager.UninstallReason int mUninstallReason\nprivate final @android.annotation.Nullable java.lang.String mSplashScreenTheme\nprivate final @android.content.pm.PackageManager.UserMinAspectRatio int mMinAspectRatio\nprivate final long mFirstInstallTimeMillis\npublic static com.android.server.pm.pkg.PackageUserState copy(com.android.server.pm.pkg.PackageUserState)\nprivate void setBoolean(int,boolean)\nprivate boolean getBoolean(int)\npublic @java.lang.Override boolean isHidden()\npublic @java.lang.Override boolean isInstalled()\npublic @java.lang.Override boolean isInstantApp()\npublic @java.lang.Override boolean isNotLaunched()\npublic @java.lang.Override boolean isStopped()\npublic @java.lang.Override boolean isSuspended()\npublic @java.lang.Override boolean isVirtualPreload()\npublic @java.lang.Override boolean isComponentEnabled(java.lang.String)\npublic @java.lang.Override boolean isComponentDisabled(java.lang.String)\npublic @java.lang.Override android.content.pm.overlay.OverlayPaths getAllOverlayPaths()\nclass UserStateImpl extends java.lang.Object implements [com.android.server.pm.pkg.PackageUserState]\nprivate static final int HIDDEN\nprivate static final int INSTALLED\nprivate static final int INSTANT_APP\nprivate static final int NOT_LAUNCHED\nprivate static final int STOPPED\nprivate static final int SUSPENDED\nprivate static final int VIRTUAL_PRELOAD\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false)")
+ inputSignatures = "private int mBooleans\nprivate final long mCeDataInode\nprivate final @android.annotation.NonNull android.util.ArraySet<java.lang.String> mDisabledComponents\nprivate final @android.content.pm.PackageManager.DistractionRestriction int mDistractionFlags\nprivate final @android.annotation.NonNull android.util.ArraySet<java.lang.String> mEnabledComponents\nprivate final int mEnabledState\nprivate final @android.annotation.Nullable java.lang.String mHarmfulAppWarning\nprivate final @android.content.pm.PackageManager.InstallReason int mInstallReason\nprivate final @android.annotation.Nullable java.lang.String mLastDisableAppCaller\nprivate final @android.annotation.NonNull android.content.pm.overlay.OverlayPaths mOverlayPaths\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,android.content.pm.overlay.OverlayPaths> mSharedLibraryOverlayPaths\nprivate final @android.content.pm.PackageManager.UninstallReason int mUninstallReason\nprivate final @android.annotation.Nullable java.lang.String mSplashScreenTheme\nprivate final @android.content.pm.PackageManager.UserMinAspectRatio int mMinAspectRatio\nprivate final long mFirstInstallTimeMillis\nprivate final @android.annotation.Nullable com.android.server.pm.pkg.ArchiveState mArchiveState\npublic static com.android.server.pm.pkg.PackageUserState copy(com.android.server.pm.pkg.PackageUserState)\nprivate void setBoolean(int,boolean)\nprivate boolean getBoolean(int)\npublic @java.lang.Override boolean isHidden()\npublic @java.lang.Override boolean isInstalled()\npublic @java.lang.Override boolean isInstantApp()\npublic @java.lang.Override boolean isNotLaunched()\npublic @java.lang.Override boolean isStopped()\npublic @java.lang.Override boolean isSuspended()\npublic @java.lang.Override boolean isVirtualPreload()\npublic @java.lang.Override boolean isComponentEnabled(java.lang.String)\npublic @java.lang.Override boolean isComponentDisabled(java.lang.String)\npublic @java.lang.Override android.content.pm.overlay.OverlayPaths getAllOverlayPaths()\nclass UserStateImpl extends java.lang.Object implements [com.android.server.pm.pkg.PackageUserState]\nprivate static final int HIDDEN\nprivate static final int INSTALLED\nprivate static final int INSTANT_APP\nprivate static final int NOT_LAUNCHED\nprivate static final int STOPPED\nprivate static final int SUSPENDED\nprivate static final int VIRTUAL_PRELOAD\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false)")
@Deprecated
private void __metadata() {}
@@ -737,7 +745,7 @@
}
@DataClass.Generated(
- time = 1671671043929L,
+ time = 1689171425753L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java",
inputSignatures = "private int mBooleans\nprivate final @android.annotation.Nullable com.android.server.pm.pkg.AndroidPackage mAndroidPackage\nprivate final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.Nullable java.lang.String mVolumeUuid\nprivate final int mAppId\nprivate final int mCategoryOverride\nprivate final @android.annotation.Nullable java.lang.String mCpuAbiOverride\nprivate final @android.content.pm.ApplicationInfo.HiddenApiEnforcementPolicy int mHiddenApiEnforcementPolicy\nprivate final long mLastModifiedTime\nprivate final long mLastUpdateTime\nprivate final long mLongVersionCode\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,java.util.Set<java.lang.String>> mMimeGroups\nprivate final @android.annotation.NonNull java.io.File mPath\nprivate final @android.annotation.Nullable java.lang.String mPrimaryCpuAbi\nprivate final @android.annotation.Nullable java.lang.String mSecondaryCpuAbi\nprivate final @android.annotation.Nullable java.lang.String mSeInfo\nprivate final boolean mHasSharedUser\nprivate final int mSharedUserAppId\nprivate final @android.annotation.NonNull java.lang.String[] mUsesSdkLibraries\nprivate final @android.annotation.NonNull long[] mUsesSdkLibrariesVersionsMajor\nprivate final @android.annotation.NonNull java.lang.String[] mUsesStaticLibraries\nprivate final @android.annotation.NonNull long[] mUsesStaticLibrariesVersions\nprivate final @android.annotation.NonNull java.util.List<com.android.server.pm.pkg.SharedLibrary> mUsesLibraries\nprivate final @android.annotation.NonNull java.util.List<java.lang.String> mUsesLibraryFiles\nprivate final @android.annotation.NonNull long[] mLastPackageUsageTime\nprivate final @android.annotation.NonNull android.content.pm.SigningInfo mSigningInfo\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.pkg.PackageUserState> mUserStates\nprivate final @android.annotation.Nullable java.lang.String mApexModuleName\npublic static com.android.server.pm.pkg.PackageState copy(com.android.server.pm.pkg.PackageStateInternal)\nprivate void setBoolean(int,boolean)\nprivate boolean getBoolean(int)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserState getStateForUser(android.os.UserHandle)\npublic @java.lang.Override boolean isExternalStorage()\npublic @java.lang.Override boolean isForceQueryableOverride()\npublic @java.lang.Override boolean isHiddenUntilInstalled()\npublic @java.lang.Override boolean isInstallPermissionsFixed()\npublic @java.lang.Override boolean isOdm()\npublic @java.lang.Override boolean isOem()\npublic @java.lang.Override boolean isPrivileged()\npublic @java.lang.Override boolean isProduct()\npublic @java.lang.Override boolean isRequiredForSystemUser()\npublic @java.lang.Override boolean isSystem()\npublic @java.lang.Override boolean isSystemExt()\npublic @java.lang.Override boolean isUpdateAvailable()\npublic @java.lang.Override boolean isUpdatedSystemApp()\npublic @java.lang.Override boolean isApkInUpdatedApex()\npublic @java.lang.Override boolean isVendor()\npublic @java.lang.Override long getVersionCode()\npublic @java.lang.Override boolean hasSharedUser()\npublic @java.lang.Override boolean isApex()\nclass PackageStateImpl extends java.lang.Object implements [com.android.server.pm.pkg.PackageState]\nprivate static final int SYSTEM\nprivate static final int EXTERNAL_STORAGE\nprivate static final int PRIVILEGED\nprivate static final int OEM\nprivate static final int VENDOR\nprivate static final int PRODUCT\nprivate static final int SYSTEM_EXT\nprivate static final int REQUIRED_FOR_SYSTEM_USER\nprivate static final int ODM\nprivate static final int FORCE_QUERYABLE_OVERRIDE\nprivate static final int HIDDEN_UNTIL_INSTALLED\nprivate static final int INSTALL_PERMISSIONS_FIXED\nprivate static final int UPDATE_AVAILABLE\nprivate static final int UPDATED_SYSTEM_APP\nprivate static final int APK_IN_UPDATED_APEX\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false)")
diff --git a/services/core/java/com/android/server/pm/pkg/PackageUserState.java b/services/core/java/com/android/server/pm/pkg/PackageUserState.java
index f75d214..81915b4 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageUserState.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageUserState.java
@@ -225,4 +225,12 @@
*/
@PackageManager.UserMinAspectRatio
int getMinAspectRatio();
+ /**
+ * Information about the archived state of an app. Set only if an app is archived.
+ *
+ * @hide
+ */
+ @Immutable.Ignore
+ @Nullable
+ ArchiveState getArchiveState();
}
diff --git a/services/core/java/com/android/server/pm/pkg/PackageUserStateDefault.java b/services/core/java/com/android/server/pm/pkg/PackageUserStateDefault.java
index 1fb12a8..cce18a8 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageUserStateDefault.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageUserStateDefault.java
@@ -185,4 +185,10 @@
@NonNull ComponentName componentName) {
return null;
}
+
+ @Nullable
+ @Override
+ public ArchiveState getArchiveState() {
+ return null;
+ }
}
diff --git a/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java b/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java
index d911ac1..6ac7c34 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java
@@ -101,6 +101,9 @@
@Nullable
private Watchable mWatchable;
+ @Nullable
+ private ArchiveState mArchiveState;
+
@NonNull
final SnapshotCache<PackageUserStateImpl> mSnapshot;
@@ -154,6 +157,7 @@
mComponentLabelIconOverrideMap = other.mComponentLabelIconOverrideMap == null
? null : other.mComponentLabelIconOverrideMap.snapshot();
mFirstInstallTimeMillis = other.mFirstInstallTimeMillis;
+ mArchiveState = other.mArchiveState;
mSnapshot = new SnapshotCache.Sealed<>();
}
@@ -563,6 +567,18 @@
return this;
}
+ /**
+ * Sets the value for {@link #getArchiveState()}.
+ *
+ * @hide
+ */
+ @NonNull
+ public PackageUserStateImpl setArchiveState(@NonNull ArchiveState archiveState) {
+ mArchiveState = archiveState;
+ onChanged();
+ return this;
+ }
+
@NonNull
@Override
public Map<String, OverlayPaths> getSharedLibraryOverlayPaths() {
@@ -720,6 +736,11 @@
}
@DataClass.Generated.Member
+ public @Nullable ArchiveState getArchiveState() {
+ return mArchiveState;
+ }
+
+ @DataClass.Generated.Member
public @NonNull SnapshotCache<PackageUserStateImpl> getSnapshot() {
return mSnapshot;
}
@@ -793,6 +814,7 @@
&& Objects.equals(mComponentLabelIconOverrideMap, that.mComponentLabelIconOverrideMap)
&& mFirstInstallTimeMillis == that.mFirstInstallTimeMillis
&& watchableEquals(that.mWatchable)
+ && Objects.equals(mArchiveState, that.mArchiveState)
&& snapshotEquals(that.mSnapshot);
}
@@ -826,15 +848,16 @@
_hash = 31 * _hash + Objects.hashCode(mComponentLabelIconOverrideMap);
_hash = 31 * _hash + Long.hashCode(mFirstInstallTimeMillis);
_hash = 31 * _hash + watchableHashCode();
+ _hash = 31 * _hash + Objects.hashCode(mArchiveState);
_hash = 31 * _hash + snapshotHashCode();
return _hash;
}
@DataClass.Generated(
- time = 1687938397579L,
+ time = 1689171513404L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java",
- inputSignatures = "protected @android.annotation.Nullable com.android.server.utils.WatchedArraySet<java.lang.String> mDisabledComponentsWatched\nprotected @android.annotation.Nullable com.android.server.utils.WatchedArraySet<java.lang.String> mEnabledComponentsWatched\nprivate long mCeDataInode\nprivate boolean mInstalled\nprivate boolean mStopped\nprivate boolean mNotLaunched\nprivate boolean mHidden\nprivate int mDistractionFlags\nprivate boolean mInstantApp\nprivate boolean mVirtualPreload\nprivate @android.content.pm.PackageManager.EnabledState int mEnabledState\nprivate @android.content.pm.PackageManager.InstallReason int mInstallReason\nprivate @android.content.pm.PackageManager.UninstallReason int mUninstallReason\nprivate @android.annotation.Nullable java.lang.String mHarmfulAppWarning\nprivate @android.annotation.Nullable java.lang.String mLastDisableAppCaller\nprivate @android.annotation.Nullable android.content.pm.overlay.OverlayPaths mOverlayPaths\nprotected @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<java.lang.String,android.content.pm.overlay.OverlayPaths> mSharedLibraryOverlayPaths\nprivate @android.annotation.Nullable java.lang.String mSplashScreenTheme\nprivate @android.content.pm.PackageManager.UserMinAspectRatio int mMinAspectRatio\nprivate @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams> mSuspendParams\nprivate @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<android.content.ComponentName,android.util.Pair<java.lang.String,java.lang.Integer>> mComponentLabelIconOverrideMap\nprivate @android.annotation.CurrentTimeMillisLong long mFirstInstallTimeMillis\nprivate @android.annotation.Nullable com.android.server.utils.Watchable mWatchable\nfinal @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl> mSnapshot\nprivate com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl> makeCache()\nprivate void onChanged()\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserStateImpl snapshot()\npublic @android.annotation.Nullable boolean setOverlayPaths(android.content.pm.overlay.OverlayPaths)\npublic boolean setSharedLibraryOverlayPaths(java.lang.String,android.content.pm.overlay.OverlayPaths)\npublic @android.annotation.Nullable @java.lang.Override com.android.server.utils.WatchedArraySet<java.lang.String> getDisabledComponentsNoCopy()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.utils.WatchedArraySet<java.lang.String> getEnabledComponentsNoCopy()\npublic @android.annotation.NonNull @java.lang.Override android.util.ArraySet<java.lang.String> getDisabledComponents()\npublic @android.annotation.NonNull @java.lang.Override android.util.ArraySet<java.lang.String> getEnabledComponents()\npublic @java.lang.Override boolean isComponentEnabled(java.lang.String)\npublic @java.lang.Override boolean isComponentDisabled(java.lang.String)\npublic @java.lang.Override android.content.pm.overlay.OverlayPaths getAllOverlayPaths()\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer)\npublic void resetOverrideComponentLabelIcon()\npublic @android.annotation.Nullable android.util.Pair<java.lang.String,java.lang.Integer> getOverrideLabelIconForComponent(android.content.ComponentName)\npublic @java.lang.Override boolean isSuspended()\npublic com.android.server.pm.pkg.PackageUserStateImpl putSuspendParams(java.lang.String,com.android.server.pm.pkg.SuspendParams)\npublic com.android.server.pm.pkg.PackageUserStateImpl removeSuspension(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDisabledComponents(android.util.ArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledComponents(android.util.ArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDisabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setCeDataInode(long)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstalled(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setStopped(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setNotLaunched(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setHidden(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDistractionFlags(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstantApp(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setVirtualPreload(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstallReason(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setUninstallReason(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setHarmfulAppWarning(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setLastDisableAppCaller(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSharedLibraryOverlayPaths(android.util.ArrayMap<java.lang.String,android.content.pm.overlay.OverlayPaths>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSplashScreenTheme(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setMinAspectRatio(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSuspendParams(android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setComponentLabelIconOverrideMap(android.util.ArrayMap<android.content.ComponentName,android.util.Pair<java.lang.String,java.lang.Integer>>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setFirstInstallTimeMillis(long)\npublic @android.annotation.NonNull @java.lang.Override java.util.Map<java.lang.String,android.content.pm.overlay.OverlayPaths> getSharedLibraryOverlayPaths()\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setWatchable(com.android.server.utils.Watchable)\nprivate boolean watchableEquals(com.android.server.utils.Watchable)\nprivate int watchableHashCode()\nprivate boolean snapshotEquals(com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl>)\nprivate int snapshotHashCode()\nclass PackageUserStateImpl extends com.android.server.utils.WatchableImpl implements [com.android.server.pm.pkg.PackageUserStateInternal, com.android.server.utils.Snappable]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=false, genEqualsHashCode=true)")
+ inputSignatures = "protected @android.annotation.Nullable com.android.server.utils.WatchedArraySet<java.lang.String> mDisabledComponentsWatched\nprotected @android.annotation.Nullable com.android.server.utils.WatchedArraySet<java.lang.String> mEnabledComponentsWatched\nprivate long mCeDataInode\nprivate boolean mInstalled\nprivate boolean mStopped\nprivate boolean mNotLaunched\nprivate boolean mHidden\nprivate int mDistractionFlags\nprivate boolean mInstantApp\nprivate boolean mVirtualPreload\nprivate @android.content.pm.PackageManager.EnabledState int mEnabledState\nprivate @android.content.pm.PackageManager.InstallReason int mInstallReason\nprivate @android.content.pm.PackageManager.UninstallReason int mUninstallReason\nprivate @android.annotation.Nullable java.lang.String mHarmfulAppWarning\nprivate @android.annotation.Nullable java.lang.String mLastDisableAppCaller\nprivate @android.annotation.Nullable android.content.pm.overlay.OverlayPaths mOverlayPaths\nprotected @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<java.lang.String,android.content.pm.overlay.OverlayPaths> mSharedLibraryOverlayPaths\nprivate @android.annotation.Nullable java.lang.String mSplashScreenTheme\nprivate @android.content.pm.PackageManager.UserMinAspectRatio int mMinAspectRatio\nprivate @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams> mSuspendParams\nprivate @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<android.content.ComponentName,android.util.Pair<java.lang.String,java.lang.Integer>> mComponentLabelIconOverrideMap\nprivate @android.annotation.CurrentTimeMillisLong long mFirstInstallTimeMillis\nprivate @android.annotation.Nullable com.android.server.utils.Watchable mWatchable\nprivate @android.annotation.Nullable com.android.server.pm.pkg.ArchiveState mArchiveState\nfinal @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl> mSnapshot\nprivate com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl> makeCache()\nprivate void onChanged()\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserStateImpl snapshot()\npublic @android.annotation.Nullable boolean setOverlayPaths(android.content.pm.overlay.OverlayPaths)\npublic boolean setSharedLibraryOverlayPaths(java.lang.String,android.content.pm.overlay.OverlayPaths)\npublic @android.annotation.Nullable @java.lang.Override com.android.server.utils.WatchedArraySet<java.lang.String> getDisabledComponentsNoCopy()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.utils.WatchedArraySet<java.lang.String> getEnabledComponentsNoCopy()\npublic @android.annotation.NonNull @java.lang.Override android.util.ArraySet<java.lang.String> getDisabledComponents()\npublic @android.annotation.NonNull @java.lang.Override android.util.ArraySet<java.lang.String> getEnabledComponents()\npublic @java.lang.Override boolean isComponentEnabled(java.lang.String)\npublic @java.lang.Override boolean isComponentDisabled(java.lang.String)\npublic @java.lang.Override android.content.pm.overlay.OverlayPaths getAllOverlayPaths()\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer)\npublic void resetOverrideComponentLabelIcon()\npublic @android.annotation.Nullable android.util.Pair<java.lang.String,java.lang.Integer> getOverrideLabelIconForComponent(android.content.ComponentName)\npublic @java.lang.Override boolean isSuspended()\npublic com.android.server.pm.pkg.PackageUserStateImpl putSuspendParams(java.lang.String,com.android.server.pm.pkg.SuspendParams)\npublic com.android.server.pm.pkg.PackageUserStateImpl removeSuspension(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDisabledComponents(android.util.ArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledComponents(android.util.ArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDisabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setCeDataInode(long)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstalled(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setStopped(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setNotLaunched(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setHidden(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDistractionFlags(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstantApp(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setVirtualPreload(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstallReason(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setUninstallReason(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setHarmfulAppWarning(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setLastDisableAppCaller(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSharedLibraryOverlayPaths(android.util.ArrayMap<java.lang.String,android.content.pm.overlay.OverlayPaths>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSplashScreenTheme(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setMinAspectRatio(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSuspendParams(android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setComponentLabelIconOverrideMap(android.util.ArrayMap<android.content.ComponentName,android.util.Pair<java.lang.String,java.lang.Integer>>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setFirstInstallTimeMillis(long)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setArchiveState(com.android.server.pm.pkg.ArchiveState)\npublic @android.annotation.NonNull @java.lang.Override java.util.Map<java.lang.String,android.content.pm.overlay.OverlayPaths> getSharedLibraryOverlayPaths()\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setWatchable(com.android.server.utils.Watchable)\nprivate boolean watchableEquals(com.android.server.utils.Watchable)\nprivate int watchableHashCode()\nprivate boolean snapshotEquals(com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl>)\nprivate int snapshotHashCode()\nclass PackageUserStateImpl extends com.android.server.utils.WatchableImpl implements [com.android.server.pm.pkg.PackageUserStateInternal, com.android.server.utils.Snappable]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=false, genEqualsHashCode=true)")
@Deprecated
private void __metadata() {}
diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
index d737b1c..e2cb87e 100644
--- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
@@ -50,7 +50,6 @@
import android.content.pm.PackageManager.Property;
import android.content.pm.Signature;
import android.content.pm.SigningDetails;
-import android.content.pm.dex.DexMetadataHelper;
import android.content.pm.parsing.ApkLiteParseUtils;
import android.content.pm.parsing.FrameworkParsingPackageUtils;
import android.content.pm.parsing.PackageLite;
@@ -74,7 +73,6 @@
import android.os.Trace;
import android.os.UserHandle;
import android.os.ext.SdkExtensions;
-import android.os.incremental.IncrementalManager;
import android.permission.PermissionManager;
import android.text.TextUtils;
import android.util.ArrayMap;
@@ -242,11 +240,6 @@
public static final int PARSE_IGNORE_OVERLAY_REQUIRED_SYSTEM_PROPERTY = 1 << 7;
public static final int PARSE_APK_IN_APEX = 1 << 9;
- /**
- * This flag is to determine whether to extract the baseline profiles from the apk or not.
- */
- public static final int PARSE_EXTRACT_BASELINE_PROFILES_FROM_APK = 1 << 10;
-
public static final int PARSE_CHATTY = 1 << 31;
/** The total maximum number of activities, services, providers and activity-aliases */
@@ -258,16 +251,14 @@
private static final int MAX_PERMISSION_NAME_LENGTH = 512;
@IntDef(flag = true, prefix = { "PARSE_" }, value = {
- PARSE_APK_IN_APEX,
PARSE_CHATTY,
PARSE_COLLECT_CERTIFICATES,
PARSE_ENFORCE_CODE,
PARSE_EXTERNAL_STORAGE,
- PARSE_EXTRACT_BASELINE_PROFILES_FROM_APK,
- PARSE_IGNORE_OVERLAY_REQUIRED_SYSTEM_PROPERTY,
PARSE_IGNORE_PROCESSES,
PARSE_IS_SYSTEM_DIR,
PARSE_MUST_BE_APK,
+ PARSE_IGNORE_OVERLAY_REQUIRED_SYSTEM_PROPERTY,
})
@Retention(RetentionPolicy.SOURCE)
public @interface ParseFlags {}
@@ -568,26 +559,6 @@
pkg.setSigningDetails(SigningDetails.UNKNOWN);
}
- // 1. The apkFile is an apk file
- // 2. The flags include PARSE_EXTRACT_PROFILE_FROM_APK
- // 3. The apk patch is NOT an incremental path
- // 4. If the .dm file exists in the current apk directory, it means the caller
- // prepares the .dm file. Don't extract the profiles from the apk again.
- if (ApkLiteParseUtils.isApkFile(apkFile)
- && (flags & PARSE_EXTRACT_BASELINE_PROFILES_FROM_APK) != 0
- && !IncrementalManager.isIncrementalPath(apkPath)
- && DexMetadataHelper.findDexMetadataForFile(apkFile) == null) {
- // Extract the baseline profiles from the apk if the profiles exist in the assets
- // directory in the apk.
- boolean extractedResult =
- DexMetadataHelper.extractBaselineProfilesToDexMetadataFileFromApk(assets,
- apkPath);
-
- if (DEBUG_JAR) {
- Slog.d(TAG, "Extract profiles " + (extractedResult ? "success" : "fail"));
- }
- }
-
return input.success(pkg);
} catch (Exception e) {
return input.error(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
diff --git a/services/core/java/com/android/server/policy/PermissionPolicyService.java b/services/core/java/com/android/server/policy/PermissionPolicyService.java
index 2679fce..d6e35e8 100644
--- a/services/core/java/com/android/server/policy/PermissionPolicyService.java
+++ b/services/core/java/com/android/server/policy/PermissionPolicyService.java
@@ -1421,8 +1421,8 @@
}
boolean hasCreatedNotificationChannels = mNotificationManager
.getNumNotificationChannelsForPackage(pkgName, uid, true) > 0;
- boolean granted = mPermissionManagerInternal.checkUidPermission(uid, POST_NOTIFICATIONS)
- == PackageManager.PERMISSION_GRANTED;
+ boolean granted = mPermissionManagerInternal.checkUidPermission(uid, POST_NOTIFICATIONS,
+ Context.DEVICE_ID_DEFAULT) == PackageManager.PERMISSION_GRANTED;
int flags = mPackageManager.getPermissionFlags(POST_NOTIFICATIONS, pkgName, user);
boolean explicitlySet = (flags & PermissionManager.EXPLICIT_SET_FLAGS) != 0;
return !granted && hasCreatedNotificationChannels && !explicitlySet;
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 46b3db8..7bbe878 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -3304,8 +3304,10 @@
minLinearBrightness, maxLinearBrightness);
mDisplayManager.setBrightness(screenDisplayId, adjustedLinearBrightness);
- startActivityAsUser(new Intent(Intent.ACTION_SHOW_BRIGHTNESS_DIALOG),
- UserHandle.CURRENT_OR_SELF);
+ Intent intent = new Intent(Intent.ACTION_SHOW_BRIGHTNESS_DIALOG);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NO_USER_ACTION);
+ intent.putExtra(EXTRA_FROM_BRIGHTNESS_KEY, true);
+ startActivityAsUser(intent, UserHandle.CURRENT_OR_SELF);
}
return true;
case KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_DOWN:
@@ -3535,16 +3537,12 @@
}
private void requestBugreportForTv() {
- if ("1".equals(SystemProperties.get("ro.debuggable"))
- || Settings.Global.getInt(mContext.getContentResolver(),
- Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0) == 1) {
- try {
- if (!ActivityManager.getService().launchBugReportHandlerApp()) {
- ActivityManager.getService().requestInteractiveBugReport();
- }
- } catch (RemoteException e) {
- Slog.e(TAG, "Error taking bugreport", e);
+ try {
+ if (!ActivityManager.getService().launchBugReportHandlerApp()) {
+ ActivityManager.getService().requestInteractiveBugReport();
}
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error taking bugreport", e);
}
}
@@ -6273,10 +6271,10 @@
pw.println(endcallBehaviorToString(mEndcallBehavior));
pw.print(prefix);
// TODO(b/117479243): handle it in InputPolicy
- pw.print("mDisplayHomeButtonHandlers=");
+ pw.println("mDisplayHomeButtonHandlers=");
for (int i = 0; i < mDisplayHomeButtonHandlers.size(); i++) {
final int key = mDisplayHomeButtonHandlers.keyAt(i);
- pw.println(mDisplayHomeButtonHandlers.get(key));
+ pw.print(prefix); pw.print(" "); pw.println(mDisplayHomeButtonHandlers.get(key));
}
pw.print(prefix); pw.print("mKeyguardOccluded="); pw.print(isKeyguardOccluded());
pw.print(" mKeyguardOccludedChanged="); pw.print(mKeyguardOccludedChanged);
@@ -6547,6 +6545,7 @@
private class HdmiVideoExtconUEventObserver extends ExtconStateObserver<Boolean> {
private static final String HDMI_EXIST = "HDMI=1";
+ private static final String DP_EXIST = "DP=1";
private static final String NAME = "hdmi";
private boolean init(ExtconInfo hdmi) {
@@ -6577,7 +6576,8 @@
public Boolean parseState(ExtconInfo extconIfno, String state) {
// extcon event state changes from kernel4.9
// new state will be like STATE=HDMI=1
- return state.contains(HDMI_EXIST);
+ // or like STATE=DP=1 for newer kernel
+ return state.contains(HDMI_EXIST) || state.contains(DP_EXIST);
}
}
diff --git a/services/core/java/com/android/server/policy/role/RoleServicePlatformHelperImpl.java b/services/core/java/com/android/server/policy/role/RoleServicePlatformHelperImpl.java
index 5c4b6f1..5e8b4de 100644
--- a/services/core/java/com/android/server/policy/role/RoleServicePlatformHelperImpl.java
+++ b/services/core/java/com/android/server/policy/role/RoleServicePlatformHelperImpl.java
@@ -347,19 +347,23 @@
}, userId);
try {
String deviceOwner= "";
- if (devicePolicyManagerInternal.getDeviceOwnerUserId() == userId) {
- ComponentName deviceOwnerComponent =
- devicePolicyManagerInternal.getDeviceOwnerComponent(false);
- if (deviceOwnerComponent != null) {
- deviceOwner = deviceOwnerComponent.getPackageName();
+ if (devicePolicyManagerInternal != null) {
+ if (devicePolicyManagerInternal.getDeviceOwnerUserId() == userId) {
+ ComponentName deviceOwnerComponent =
+ devicePolicyManagerInternal.getDeviceOwnerComponent(false);
+ if (deviceOwnerComponent != null) {
+ deviceOwner = deviceOwnerComponent.getPackageName();
+ }
}
}
dataOutputStream.writeUTF(deviceOwner);
String profileOwner = "";
- ComponentName profileOwnerComponent =
- devicePolicyManagerInternal.getProfileOwnerAsUser(userId);
- if (profileOwnerComponent != null) {
- profileOwner = profileOwnerComponent.getPackageName();
+ if (devicePolicyManagerInternal != null) {
+ ComponentName profileOwnerComponent =
+ devicePolicyManagerInternal.getProfileOwnerAsUser(userId);
+ if (profileOwnerComponent != null) {
+ profileOwner = profileOwnerComponent.getPackageName();
+ }
}
dataOutputStream.writeUTF(profileOwner);
dataOutputStream.writeInt(Settings.Global.getInt(mContext.getContentResolver(),
diff --git a/services/core/java/com/android/server/power/LowPowerStandbyController.java b/services/core/java/com/android/server/power/LowPowerStandbyController.java
index 8e78041..3b4b20f 100644
--- a/services/core/java/com/android/server/power/LowPowerStandbyController.java
+++ b/services/core/java/com/android/server/power/LowPowerStandbyController.java
@@ -1369,7 +1369,7 @@
* Otherwise, returns false, and the default policy will be used.
*/
public boolean enableCustomPolicy() {
- return DeviceConfig.getBoolean(NAMESPACE, FEATURE_FLAG_ENABLE_POLICY, false);
+ return DeviceConfig.getBoolean(NAMESPACE, FEATURE_FLAG_ENABLE_POLICY, true);
}
/**
@@ -1377,7 +1377,7 @@
* Otherwise, returns false, and {@link #getActiveStandbyPorts()} will always be empty.
*/
public boolean enableStandbyPorts() {
- return DeviceConfig.getBoolean(NAMESPACE, FEATURE_FLAG_ENABLE_STANDBY_PORTS, false);
+ return DeviceConfig.getBoolean(NAMESPACE, FEATURE_FLAG_ENABLE_STANDBY_PORTS, true);
}
/**
diff --git a/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java b/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java
index 86c4985..375ef61 100644
--- a/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java
+++ b/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java
@@ -52,7 +52,6 @@
import android.os.ShellCallback;
import android.os.SystemProperties;
import android.provider.DeviceConfig;
-import android.sysprop.ApexProperties;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.FastImmutableArraySet;
@@ -921,10 +920,6 @@
return rebootWithLskfImpl(packageName, reason, slotSwitch);
}
- public static boolean isUpdatableApexSupported() {
- return ApexProperties.updatable().orElse(false);
- }
-
// Metadata should be no more than few MB, if it's larger than 100MB something is wrong.
private static final long APEX_INFO_SIZE_LIMIT = 24 * 1024 * 100;
@@ -975,11 +970,6 @@
@Override
public boolean allocateSpaceForUpdate(String packageFile) {
allocateSpaceForUpdate_enforcePermission();
- if (!isUpdatableApexSupported()) {
- Log.i(TAG, "Updatable Apex not supported, "
- + "allocateSpaceForUpdate does nothing.");
- return true;
- }
final long token = Binder.clearCallingIdentity();
try {
CompressedApexInfoList apexInfoList = getCompressedApexInfoList(packageFile);
diff --git a/services/core/java/com/android/server/security/FileIntegrityService.java b/services/core/java/com/android/server/security/FileIntegrityService.java
index 5ae6973..9529621 100644
--- a/services/core/java/com/android/server/security/FileIntegrityService.java
+++ b/services/core/java/com/android/server/security/FileIntegrityService.java
@@ -184,13 +184,7 @@
}
private void loadAllCertificates() {
- // A better alternative to load certificates would be to read from .fs-verity kernel
- // keyring, which fsverity_init loads to during earlier boot time from the same sources
- // below. But since the read operation from keyring is not provided in kernel, we need to
- // duplicate the same loading logic here.
-
// Load certificates trusted by the device manufacturer.
- // NB: Directories need to be synced with system/security/fsverity_init/fsverity_init.cpp.
final String relativeDir = "etc/security/fsverity";
loadCertificatesFromDirectory(Environment.getRootDirectory().toPath()
.resolve(relativeDir));
diff --git a/services/core/java/com/android/server/uri/TEST_MAPPING b/services/core/java/com/android/server/uri/TEST_MAPPING
index a9525f4a..6c36af5 100644
--- a/services/core/java/com/android/server/uri/TEST_MAPPING
+++ b/services/core/java/com/android/server/uri/TEST_MAPPING
@@ -9,7 +9,7 @@
]
},
{
- "name": "CtsAppSecurityHostTestCases",
+ "name": "CtsStorageHostTestCases",
"options": [
{
"include-filter": "android.appsecurity.cts.ExternalStorageHostTest#testGrantUriPermission"
diff --git a/services/core/java/com/android/server/utils/FoldSettingWrapper.java b/services/core/java/com/android/server/utils/FoldSettingWrapper.java
new file mode 100644
index 0000000..97a1ac0
--- /dev/null
+++ b/services/core/java/com/android/server/utils/FoldSettingWrapper.java
@@ -0,0 +1,48 @@
+/*
+ * 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.utils;
+
+import android.content.ContentResolver;
+import android.provider.Settings;
+
+/**
+ * A wrapper class for the {@link Settings.System#STAY_AWAKE_ON_FOLD} setting.
+ *
+ * This class provides a convenient way to access the {@link Settings.System#STAY_AWAKE_ON_FOLD}
+ * setting for testing.
+ */
+public class FoldSettingWrapper {
+ private final ContentResolver mContentResolver;
+
+ public FoldSettingWrapper(ContentResolver contentResolver) {
+ mContentResolver = contentResolver;
+ }
+
+ /**
+ * Returns whether the device should remain awake after folding.
+ */
+ public boolean shouldStayAwakeOnFold() {
+ try {
+ return (Settings.System.getIntForUser(
+ mContentResolver,
+ Settings.System.STAY_AWAKE_ON_FOLD,
+ 0) == 1);
+ } catch (Settings.SettingNotFoundException e) {
+ return false;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/vibrator/OWNERS b/services/core/java/com/android/server/vibrator/OWNERS
index 08f0a90..9afa682 100644
--- a/services/core/java/com/android/server/vibrator/OWNERS
+++ b/services/core/java/com/android/server/vibrator/OWNERS
@@ -1,3 +1,6 @@
+# Bug component: 345036
+
lsandrade@google.com
michaelwr@google.com
-sbowden@google.com
\ No newline at end of file
+sbowden@google.com
+khalilahmad@google.com
\ No newline at end of file
diff --git a/services/core/java/com/android/server/vibrator/SetAmplitudeVibratorStep.java b/services/core/java/com/android/server/vibrator/SetAmplitudeVibratorStep.java
index cce1ef4..6d01123 100644
--- a/services/core/java/com/android/server/vibrator/SetAmplitudeVibratorStep.java
+++ b/services/core/java/com/android/server/vibrator/SetAmplitudeVibratorStep.java
@@ -179,7 +179,9 @@
while (i < segmentCount) {
VibrationEffectSegment segment = segments.get(i);
if (!(segment instanceof StepSegment)
- || ((StepSegment) segment).getAmplitude() == 0) {
+ // play() will ignore segments with zero duration, so it's important that
+ // zero-duration segments don't affect this method.
+ || (segment.getDuration() > 0 && ((StepSegment) segment).getAmplitude() == 0)) {
break;
}
timing += segment.getDuration();
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 25a0a52..7996434 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -3629,10 +3629,15 @@
intent.putExtra(Intent.EXTRA_CLIENT_LABEL,
com.android.internal.R.string.wallpaper_binding_label);
intent.putExtra(Intent.EXTRA_CLIENT_INTENT, clientIntent);
- boolean bindSuccess = mContext.bindServiceAsUser(intent, newConn,
- Context.BIND_AUTO_CREATE | Context.BIND_SHOWING_UI
+ int bindFlags = Context.BIND_AUTO_CREATE | Context.BIND_SHOWING_UI
| Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE
- | Context.BIND_INCLUDE_CAPABILITIES,
+ | Context.BIND_INCLUDE_CAPABILITIES;
+
+ if (mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_wallpaperTopApp)) {
+ bindFlags |= Context.BIND_SCHEDULE_LIKE_TOP_APP;
+ }
+ boolean bindSuccess = mContext.bindServiceAsUser(intent, newConn, bindFlags,
new UserHandle(serviceUserId));
if (!bindSuccess) {
String msg = "Unable to bind service: " + componentName;
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 5165e86..e30673c 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -2129,8 +2129,7 @@
hasBeenLaunched = false;
mTaskSupervisor = supervisor;
- info.taskAffinity = computeTaskAffinity(info.taskAffinity, info.applicationInfo.uid,
- info.launchMode, mActivityComponent);
+ info.taskAffinity = computeTaskAffinity(info.taskAffinity, info.applicationInfo.uid);
taskAffinity = info.taskAffinity;
final String uid = Integer.toString(info.applicationInfo.uid);
if (info.windowLayout != null && info.windowLayout.windowLayoutAffinity != null
@@ -2230,19 +2229,12 @@
*
* @param affinity The affinity of the activity.
* @param uid The user-ID that has been assigned to this application.
- * @param launchMode The activity launch mode
- * @param componentName The activity component name. This is only useful when the given
- * launchMode is {@link ActivityInfo#LAUNCH_SINGLE_INSTANCE}
* @return The task affinity
*/
- static String computeTaskAffinity(String affinity, int uid, int launchMode,
- ComponentName componentName) {
+ static String computeTaskAffinity(String affinity, int uid) {
final String uidStr = Integer.toString(uid);
if (affinity != null && !affinity.startsWith(uidStr)) {
affinity = uidStr + ":" + affinity;
- if (launchMode == LAUNCH_SINGLE_INSTANCE && componentName != null) {
- affinity += ":" + componentName.hashCode();
- }
}
return affinity;
}
@@ -5680,13 +5672,6 @@
final DisplayContent displayContent = getDisplayContent();
if (!visible) {
mImeInsetsFrozenUntilStartInput = true;
- if (usingShellTransitions) {
- final WindowState wallpaperTarget =
- displayContent.mWallpaperController.getWallpaperTarget();
- if (wallpaperTarget != null && wallpaperTarget.mActivityRecord == this) {
- displayContent.mWallpaperController.hideWallpapers(wallpaperTarget);
- }
- }
}
if (!displayContent.mClosingApps.contains(this)
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index d2715b6..0a62c88 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -698,6 +698,10 @@
mRequest.intent, caller, callingUid);
}
+ if (mRequest.intent != null) {
+ mRequest.componentSpecified |= mRequest.intent.getComponent() != null;
+ }
+
// If the caller hasn't already resolved the activity, we're willing
// to do so here. If the caller is already holding the WM lock here,
// and we need to check dynamic Uri permissions, then we're forced
@@ -1590,6 +1594,9 @@
if (forceTransientTransition) {
transitionController.collect(mLastStartActivityRecord);
transitionController.collect(mPriorAboveTask);
+ // If keyguard is active and occluded, the transient target won't be moved to front
+ // to be collected, so set transient again after it is collected.
+ transitionController.setTransientLaunch(mLastStartActivityRecord, mPriorAboveTask);
final DisplayContent dc = mLastStartActivityRecord.getDisplayContent();
// update wallpaper target to TransientHide
dc.mWallpaperController.adjustWallpaperWindows();
@@ -1993,11 +2000,7 @@
}
}
- boolean shouldBlockActivityStart = true;
- // Used for logging/toasts. Would we block the start if target sdk was U and feature was
- // enabled?
- boolean wouldBlockActivityStartIgnoringFlags = true;
-
+ Pair<Boolean, Boolean> pair = null;
if (mSourceRecord != null) {
boolean passesAsmChecks = true;
Task sourceTask = mSourceRecord.getTask();
@@ -2013,14 +2016,25 @@
if (passesAsmChecks) {
Task taskToCheck = taskToFront ? sourceTask : targetTask;
- // first == false means Should Block
- // second == false means Would Block disregarding flags
- Pair<Boolean, Boolean> pair = ActivityTaskSupervisor
+ pair = ActivityTaskSupervisor
.doesTopActivityMatchingUidExistForAsm(taskToCheck, mSourceRecord.getUid(),
mSourceRecord);
- shouldBlockActivityStart = !pair.first;
- wouldBlockActivityStartIgnoringFlags = !pair.second;
}
+ } else if (!taskToFront) {
+ // We don't have a sourceRecord, and we're launching into an existing task.
+ // Allow if callingUid is top of stack.
+ pair = ActivityTaskSupervisor
+ .doesTopActivityMatchingUidExistForAsm(targetTask, mCallingUid,
+ /*sourceRecord*/null);
+ }
+
+ boolean shouldBlockActivityStart = true;
+ if (pair != null) {
+ // We block if feature flag is enabled
+ shouldBlockActivityStart = !pair.first;
+ // Used for logging/toasts. Would we block if target sdk was U and feature was
+ // enabled? If so, we can't return here but we also might not block at the end
+ boolean wouldBlockActivityStartIgnoringFlags = !pair.second;
if (!wouldBlockActivityStartIgnoringFlags) {
return true;
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 738797b..50948e1 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -2186,7 +2186,7 @@
* Processes the activities to be stopped or destroyed. This should be called when the resumed
* activities are idle or drawn.
*/
- private void processStoppingAndFinishingActivities(ActivityRecord launchedActivity,
+ void processStoppingAndFinishingActivities(ActivityRecord launchedActivity,
boolean processPausingActivities, String reason) {
// Stop any activities that are scheduled to do so but have been waiting for the transition
// animation to finish.
@@ -2194,7 +2194,10 @@
ArrayList<ActivityRecord> readyToStopActivities = null;
for (int i = 0; i < mStoppingActivities.size(); i++) {
final ActivityRecord s = mStoppingActivities.get(i);
- final boolean animating = s.isInTransition();
+ // Activity in a force hidden task should not be counted as animating, i.e., we want to
+ // send onStop before any configuration change when removing pip transition is ongoing.
+ final boolean animating = s.isInTransition()
+ && s.getTask() != null && !s.getTask().isForceHidden();
displaySwapping |= s.isDisplaySleepingAndSwapping();
ProtoLog.v(WM_DEBUG_STATES, "Stopping %s: nowVisible=%b animating=%b "
+ "finishing=%s", s, s.nowVisible, animating, s.finishing);
diff --git a/services/core/java/com/android/server/wm/AsyncRotationController.java b/services/core/java/com/android/server/wm/AsyncRotationController.java
index 0115877..4ce21bd 100644
--- a/services/core/java/com/android/server/wm/AsyncRotationController.java
+++ b/services/core/java/com/android/server/wm/AsyncRotationController.java
@@ -93,15 +93,12 @@
/** Whether the start transaction of the transition is committed (by shell). */
private boolean mIsStartTransactionCommitted;
- /** Whether all windows should wait for the start transaction. */
- private boolean mAlwaysWaitForStartTransaction;
-
/** Whether the target windows have been requested to sync their draw transactions. */
private boolean mIsSyncDrawRequested;
private SeamlessRotator mRotator;
- private final int mOriginalRotation;
+ private int mOriginalRotation;
private final boolean mHasScreenRotationAnimation;
AsyncRotationController(DisplayContent displayContent) {
@@ -147,15 +144,6 @@
if (mTransitionOp == OP_LEGACY) {
mIsStartTransactionCommitted = true;
} else if (displayContent.mTransitionController.isCollecting(displayContent)) {
- final Transition transition =
- mDisplayContent.mTransitionController.getCollectingTransition();
- if (transition != null) {
- final BLASTSyncEngine.SyncGroup syncGroup =
- mDisplayContent.mWmService.mSyncEngine.getSyncSet(transition.getSyncId());
- if (syncGroup != null && syncGroup.mSyncMethod == BLASTSyncEngine.METHOD_BLAST) {
- mAlwaysWaitForStartTransaction = true;
- }
- }
keepAppearanceInPreviousRotation();
}
}
@@ -279,10 +267,12 @@
// The previous animation leash will be dropped when preparing fade-in animation, so
// simply apply new animation without restoring the transformation.
fadeWindowToken(true /* show */, windowToken, ANIMATION_TYPE_TOKEN_TRANSFORM);
- } else if (op.mAction == Operation.ACTION_SEAMLESS && mRotator != null
+ } else if (op.mAction == Operation.ACTION_SEAMLESS
&& op.mLeash != null && op.mLeash.isValid()) {
if (DEBUG) Slog.d(TAG, "finishOp undo seamless " + windowToken.getTopChild());
- mRotator.setIdentityMatrix(windowToken.getSyncTransaction(), op.mLeash);
+ final SurfaceControl.Transaction t = windowToken.getSyncTransaction();
+ t.setMatrix(op.mLeash, 1, 0, 0, 1);
+ t.setPosition(op.mLeash, 0, 0);
}
}
@@ -365,6 +355,32 @@
}
}
+ /**
+ * Re-initialize the states if the current display rotation has changed to a different rotation.
+ * This is mainly for seamless rotation to update the transform based on new rotation.
+ */
+ void updateRotation() {
+ if (mRotator == null) return;
+ final int currentRotation = mDisplayContent.getWindowConfiguration().getRotation();
+ if (mOriginalRotation == currentRotation) {
+ return;
+ }
+ Slog.d(TAG, "Update original rotation " + currentRotation);
+ mOriginalRotation = currentRotation;
+ mDisplayContent.forAllWindows(w -> {
+ if (w.mForceSeamlesslyRotate && w.mHasSurface
+ && !mTargetWindowTokens.containsKey(w.mToken)) {
+ final Operation op = new Operation(Operation.ACTION_SEAMLESS);
+ op.mLeash = w.mToken.mSurfaceControl;
+ mTargetWindowTokens.put(w.mToken, op);
+ }
+ }, true /* traverseTopToBottom */);
+ mRotator = null;
+ mIsStartTransactionCommitted = false;
+ mIsSyncDrawRequested = false;
+ keepAppearanceInPreviousRotation();
+ }
+
private void scheduleTimeout() {
if (mTimeoutRunnable == null) {
mTimeoutRunnable = () -> {
@@ -589,7 +605,7 @@
* start transaction of rotation transition is applied.
*/
private boolean canDrawBeforeStartTransaction(Operation op) {
- return !mAlwaysWaitForStartTransaction && op.mAction != Operation.ACTION_SEAMLESS;
+ return op.mAction != Operation.ACTION_SEAMLESS;
}
/** The operation to control the rotation appearance associated with window token. */
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index bfd2a10..349d115 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -522,7 +522,6 @@
boolean mWaitingForConfig;
// TODO(multi-display): remove some of the usages.
- @VisibleForTesting
boolean isDefaultDisplay;
/** Detect user tapping outside of current focused task bounds .*/
@@ -3358,6 +3357,7 @@
mRootWindowContainer.mTaskSupervisor
.getKeyguardController().onDisplayRemoved(mDisplayId);
mWallpaperController.resetLargestDisplay(mDisplay);
+ mWmService.mDisplayWindowSettings.onDisplayRemoved(this);
} finally {
mDisplayReady = false;
}
@@ -3457,6 +3457,11 @@
this, this, null /* remoteTransition */, displayChange);
if (t != null) {
mAtmService.startLaunchPowerMode(POWER_MODE_REASON_CHANGE_DISPLAY);
+ if (mAsyncRotationController != null) {
+ // Give a chance to update the transform if the current rotation is changed when
+ // some windows haven't finished previous rotation.
+ mAsyncRotationController.updateRotation();
+ }
if (mFixedRotationLaunchingApp != null) {
// A fixed-rotation transition is done, then continue to start a seamless display
// transition.
@@ -6399,9 +6404,9 @@
// Don't do recursive work.
return;
}
- mInEnsureActivitiesVisible = true;
mAtmService.mTaskSupervisor.beginActivityVisibilityUpdate();
try {
+ mInEnsureActivitiesVisible = true;
forAllRootTasks(rootTask -> {
rootTask.ensureActivitiesVisible(starting, configChanges, preserveWindows,
notifyClients);
@@ -7032,4 +7037,12 @@
// Display is the root, so it's not rotated relative to anything.
return Surface.ROTATION_0;
}
+
+ public void replaceContent(SurfaceControl sc) {
+ new Transaction().reparent(sc, getSurfaceControl())
+ .reparent(mWindowingLayer, null)
+ .reparent(mOverlayLayer, null)
+ .reparent(mA11yOverlayLayer, null)
+ .apply();
+ }
}
diff --git a/services/core/java/com/android/server/wm/DisplayWindowSettings.java b/services/core/java/com/android/server/wm/DisplayWindowSettings.java
index 4c0435e..e1753d7 100644
--- a/services/core/java/com/android/server/wm/DisplayWindowSettings.java
+++ b/services/core/java/com/android/server/wm/DisplayWindowSettings.java
@@ -33,6 +33,7 @@
import android.view.DisplayInfo;
import android.view.IWindowManager;
import android.view.Surface;
+import android.view.WindowManager;
import android.view.WindowManager.DisplayImePolicy;
import com.android.server.policy.WindowManagerPolicy;
@@ -45,15 +46,20 @@
* delegates the persistence and lookup of settings values to the supplied {@link SettingsProvider}.
*/
class DisplayWindowSettings {
+ @NonNull
private final WindowManagerService mService;
+ @NonNull
private final SettingsProvider mSettingsProvider;
- DisplayWindowSettings(WindowManagerService service, SettingsProvider settingsProvider) {
+ DisplayWindowSettings(@NonNull WindowManagerService service,
+ @NonNull SettingsProvider settingsProvider) {
mService = service;
mSettingsProvider = settingsProvider;
}
- void setUserRotation(DisplayContent displayContent, int rotationMode, int rotation) {
+ void setUserRotation(@NonNull DisplayContent displayContent,
+ @WindowManagerPolicy.UserRotationMode int rotationMode,
+ @Surface.Rotation int rotation) {
final DisplayInfo displayInfo = displayContent.getDisplayInfo();
final SettingsProvider.SettingsEntry overrideSettings =
mSettingsProvider.getOverrideSettings(displayInfo);
@@ -62,7 +68,7 @@
mSettingsProvider.updateOverrideSettings(displayInfo, overrideSettings);
}
- void setForcedSize(DisplayContent displayContent, int width, int height) {
+ void setForcedSize(@NonNull DisplayContent displayContent, int width, int height) {
if (displayContent.isDefaultDisplay) {
final String sizeString = (width == 0 || height == 0) ? "" : (width + "," + height);
Settings.Global.putString(mService.mContext.getContentResolver(),
@@ -77,21 +83,20 @@
mSettingsProvider.updateOverrideSettings(displayInfo, overrideSettings);
}
- void setForcedDensity(DisplayInfo info, int density, int userId) {
+ void setForcedDensity(@NonNull DisplayInfo info, int density, int userId) {
if (info.displayId == Display.DEFAULT_DISPLAY) {
final String densityString = density == 0 ? "" : Integer.toString(density);
Settings.Secure.putStringForUser(mService.mContext.getContentResolver(),
Settings.Secure.DISPLAY_DENSITY_FORCED, densityString, userId);
}
- final DisplayInfo displayInfo = info;
final SettingsProvider.SettingsEntry overrideSettings =
- mSettingsProvider.getOverrideSettings(displayInfo);
+ mSettingsProvider.getOverrideSettings(info);
overrideSettings.mForcedDensity = density;
- mSettingsProvider.updateOverrideSettings(displayInfo, overrideSettings);
+ mSettingsProvider.updateOverrideSettings(info, overrideSettings);
}
- void setForcedScalingMode(DisplayContent displayContent, @ForceScalingMode int mode) {
+ void setForcedScalingMode(@NonNull DisplayContent displayContent, @ForceScalingMode int mode) {
if (displayContent.isDefaultDisplay) {
Settings.Global.putInt(mService.mContext.getContentResolver(),
Settings.Global.DISPLAY_SCALING_FORCE, mode);
@@ -104,7 +109,7 @@
mSettingsProvider.updateOverrideSettings(displayInfo, overrideSettings);
}
- void setFixedToUserRotation(DisplayContent displayContent, int fixedToUserRotation) {
+ void setFixedToUserRotation(@NonNull DisplayContent displayContent, int fixedToUserRotation) {
final DisplayInfo displayInfo = displayContent.getDisplayInfo();
final SettingsProvider.SettingsEntry overrideSettings =
mSettingsProvider.getOverrideSettings(displayInfo);
@@ -112,8 +117,8 @@
mSettingsProvider.updateOverrideSettings(displayInfo, overrideSettings);
}
- void setIgnoreOrientationRequest(
- DisplayContent displayContent, boolean ignoreOrientationRequest) {
+ void setIgnoreOrientationRequest(@NonNull DisplayContent displayContent,
+ boolean ignoreOrientationRequest) {
final DisplayInfo displayInfo = displayContent.getDisplayInfo();
final SettingsProvider.SettingsEntry overrideSettings =
mSettingsProvider.getOverrideSettings(displayInfo);
@@ -121,7 +126,9 @@
mSettingsProvider.updateOverrideSettings(displayInfo, overrideSettings);
}
- private int getWindowingModeLocked(SettingsProvider.SettingsEntry settings, DisplayContent dc) {
+ @WindowConfiguration.WindowingMode
+ private int getWindowingModeLocked(@NonNull SettingsProvider.SettingsEntry settings,
+ @NonNull DisplayContent dc) {
int windowingMode = settings.mWindowingMode;
// This display used to be in freeform, but we don't support freeform anymore, so fall
// back to fullscreen.
@@ -139,13 +146,15 @@
return windowingMode;
}
- int getWindowingModeLocked(DisplayContent dc) {
+ @WindowConfiguration.WindowingMode
+ int getWindowingModeLocked(@NonNull DisplayContent dc) {
final DisplayInfo displayInfo = dc.getDisplayInfo();
final SettingsProvider.SettingsEntry settings = mSettingsProvider.getSettings(displayInfo);
return getWindowingModeLocked(settings, dc);
}
- void setWindowingModeLocked(DisplayContent dc, int mode) {
+ void setWindowingModeLocked(@NonNull DisplayContent dc,
+ @WindowConfiguration.WindowingMode int mode) {
final DisplayInfo displayInfo = dc.getDisplayInfo();
final SettingsProvider.SettingsEntry overrideSettings =
mSettingsProvider.getOverrideSettings(displayInfo);
@@ -157,7 +166,8 @@
mSettingsProvider.updateOverrideSettings(displayInfo, overrideSettings);
}
- int getRemoveContentModeLocked(DisplayContent dc) {
+ @WindowManager.RemoveContentMode
+ int getRemoveContentModeLocked(@NonNull DisplayContent dc) {
final DisplayInfo displayInfo = dc.getDisplayInfo();
final SettingsProvider.SettingsEntry settings = mSettingsProvider.getSettings(displayInfo);
if (settings.mRemoveContentMode == REMOVE_CONTENT_MODE_UNDEFINED) {
@@ -171,7 +181,8 @@
return settings.mRemoveContentMode;
}
- void setRemoveContentModeLocked(DisplayContent dc, int mode) {
+ void setRemoveContentModeLocked(@NonNull DisplayContent dc,
+ @WindowManager.RemoveContentMode int mode) {
final DisplayInfo displayInfo = dc.getDisplayInfo();
final SettingsProvider.SettingsEntry overrideSettings =
mSettingsProvider.getOverrideSettings(displayInfo);
@@ -179,14 +190,14 @@
mSettingsProvider.updateOverrideSettings(displayInfo, overrideSettings);
}
- boolean shouldShowWithInsecureKeyguardLocked(DisplayContent dc) {
+ boolean shouldShowWithInsecureKeyguardLocked(@NonNull DisplayContent dc) {
final DisplayInfo displayInfo = dc.getDisplayInfo();
final SettingsProvider.SettingsEntry settings = mSettingsProvider.getSettings(displayInfo);
return settings.mShouldShowWithInsecureKeyguard != null
? settings.mShouldShowWithInsecureKeyguard : false;
}
- void setShouldShowWithInsecureKeyguardLocked(DisplayContent dc, boolean shouldShow) {
+ void setShouldShowWithInsecureKeyguardLocked(@NonNull DisplayContent dc, boolean shouldShow) {
if (!dc.isPrivate() && shouldShow) {
throw new IllegalArgumentException("Public display can't be allowed to show content"
+ " when locked");
@@ -199,7 +210,7 @@
mSettingsProvider.updateOverrideSettings(displayInfo, overrideSettings);
}
- void setDontMoveToTop(DisplayContent dc, boolean dontMoveToTop) {
+ void setDontMoveToTop(@NonNull DisplayContent dc, boolean dontMoveToTop) {
DisplayInfo displayInfo = dc.getDisplayInfo();
SettingsProvider.SettingsEntry overrideSettings =
mSettingsProvider.getSettings(displayInfo);
@@ -207,7 +218,7 @@
mSettingsProvider.updateOverrideSettings(displayInfo, overrideSettings);
}
- boolean shouldShowSystemDecorsLocked(DisplayContent dc) {
+ boolean shouldShowSystemDecorsLocked(@NonNull DisplayContent dc) {
if (dc.getDisplayId() == Display.DEFAULT_DISPLAY) {
// Default display should show system decors.
return true;
@@ -218,7 +229,7 @@
return settings.mShouldShowSystemDecors != null ? settings.mShouldShowSystemDecors : false;
}
- void setShouldShowSystemDecorsLocked(DisplayContent dc, boolean shouldShow) {
+ void setShouldShowSystemDecorsLocked(@NonNull DisplayContent dc, boolean shouldShow) {
final DisplayInfo displayInfo = dc.getDisplayInfo();
final SettingsProvider.SettingsEntry overrideSettings =
mSettingsProvider.getOverrideSettings(displayInfo);
@@ -226,7 +237,8 @@
mSettingsProvider.updateOverrideSettings(displayInfo, overrideSettings);
}
- @DisplayImePolicy int getImePolicyLocked(DisplayContent dc) {
+ @DisplayImePolicy
+ int getImePolicyLocked(@NonNull DisplayContent dc) {
if (dc.getDisplayId() == Display.DEFAULT_DISPLAY) {
// Default display should show IME.
return DISPLAY_IME_POLICY_LOCAL;
@@ -238,7 +250,7 @@
: DISPLAY_IME_POLICY_FALLBACK_DISPLAY;
}
- void setDisplayImePolicy(DisplayContent dc, @DisplayImePolicy int imePolicy) {
+ void setDisplayImePolicy(@NonNull DisplayContent dc, @DisplayImePolicy int imePolicy) {
final DisplayInfo displayInfo = dc.getDisplayInfo();
final SettingsProvider.SettingsEntry overrideSettings =
mSettingsProvider.getOverrideSettings(displayInfo);
@@ -246,11 +258,11 @@
mSettingsProvider.updateOverrideSettings(displayInfo, overrideSettings);
}
- void applySettingsToDisplayLocked(DisplayContent dc) {
+ void applySettingsToDisplayLocked(@NonNull DisplayContent dc) {
applySettingsToDisplayLocked(dc, /* includeRotationSettings */ true);
}
- void applySettingsToDisplayLocked(DisplayContent dc, boolean includeRotationSettings) {
+ void applySettingsToDisplayLocked(@NonNull DisplayContent dc, boolean includeRotationSettings) {
final DisplayInfo displayInfo = dc.getDisplayInfo();
final SettingsProvider.SettingsEntry settings = mSettingsProvider.getSettings(displayInfo);
@@ -274,9 +286,8 @@
dc.mIsDensityForced = hasDensityOverride;
dc.mIsSizeForced = hasSizeOverride;
- final boolean ignoreDisplayCutout = settings.mIgnoreDisplayCutout != null
+ dc.mIgnoreDisplayCutout = settings.mIgnoreDisplayCutout != null
? settings.mIgnoreDisplayCutout : false;
- dc.mIgnoreDisplayCutout = ignoreDisplayCutout;
final int width = hasSizeOverride ? settings.mForcedWidth : dc.mInitialDisplayWidth;
final int height = hasSizeOverride ? settings.mForcedHeight : dc.mInitialDisplayHeight;
@@ -296,7 +307,7 @@
if (includeRotationSettings) applyRotationSettingsToDisplayLocked(dc);
}
- void applyRotationSettingsToDisplayLocked(DisplayContent dc) {
+ void applyRotationSettingsToDisplayLocked(@NonNull DisplayContent dc) {
final DisplayInfo displayInfo = dc.getDisplayInfo();
final SettingsProvider.SettingsEntry settings = mSettingsProvider.getSettings(displayInfo);
@@ -315,7 +326,7 @@
* @return {@code true} if any settings for this display has changed; {@code false} if nothing
* changed.
*/
- boolean updateSettingsForDisplay(DisplayContent dc) {
+ boolean updateSettingsForDisplay(@NonNull DisplayContent dc) {
final TaskDisplayArea defaultTda = dc.getDefaultTaskDisplayArea();
if (defaultTda != null && defaultTda.getWindowingMode() != getWindowingModeLocked(dc)) {
// For the time being the only thing that may change is windowing mode, so just update
@@ -327,6 +338,13 @@
}
/**
+ * Called when the given {@link DisplayContent} is removed to cleanup.
+ */
+ void onDisplayRemoved(@NonNull DisplayContent dc) {
+ mSettingsProvider.onDisplayRemoved(dc.getDisplayInfo());
+ }
+
+ /**
* Provides the functionality to lookup the {@link SettingsEntry settings} for a given
* {@link DisplayInfo}.
* <p>
@@ -364,19 +382,29 @@
void updateOverrideSettings(@NonNull DisplayInfo info, @NonNull SettingsEntry overrides);
/**
+ * Called when a display is removed to cleanup.
+ */
+ void onDisplayRemoved(@NonNull DisplayInfo info);
+
+ /**
* Settings for a display.
*/
class SettingsEntry {
+ @WindowConfiguration.WindowingMode
int mWindowingMode = WindowConfiguration.WINDOWING_MODE_UNDEFINED;
@Nullable
+ @WindowManagerPolicy.UserRotationMode
Integer mUserRotationMode;
@Nullable
+ @Surface.Rotation
Integer mUserRotation;
int mForcedWidth;
int mForcedHeight;
int mForcedDensity;
@Nullable
+ @ForceScalingMode
Integer mForcedScalingMode;
+ @WindowManager.RemoveContentMode
int mRemoveContentMode = REMOVE_CONTENT_MODE_UNDEFINED;
@Nullable
Boolean mShouldShowWithInsecureKeyguard;
@@ -395,7 +423,7 @@
SettingsEntry() {}
- SettingsEntry(SettingsEntry copyFrom) {
+ SettingsEntry(@NonNull SettingsEntry copyFrom) {
setTo(copyFrom);
}
diff --git a/services/core/java/com/android/server/wm/DisplayWindowSettingsProvider.java b/services/core/java/com/android/server/wm/DisplayWindowSettingsProvider.java
index 1abb0a1..ea668fa 100644
--- a/services/core/java/com/android/server/wm/DisplayWindowSettingsProvider.java
+++ b/services/core/java/com/android/server/wm/DisplayWindowSettingsProvider.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import static android.view.Display.TYPE_VIRTUAL;
import static android.view.WindowManager.DISPLAY_IME_POLICY_FALLBACK_DISPLAY;
import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL;
import static android.view.WindowManager.REMOVE_CONTENT_MODE_UNDEFINED;
@@ -28,6 +29,8 @@
import android.annotation.Nullable;
import android.app.WindowConfiguration;
import android.os.Environment;
+import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.AtomicFile;
import android.util.Slog;
import android.util.Xml;
@@ -49,7 +52,6 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
-import java.util.HashMap;
import java.util.Map;
/**
@@ -86,7 +88,9 @@
void finishWrite(OutputStream os, boolean success);
}
+ @NonNull
private ReadableSettings mBaseSettings;
+ @NonNull
private final WritableSettings mOverrideSettings;
DisplayWindowSettingsProvider() {
@@ -155,6 +159,16 @@
mOverrideSettings.updateSettingsEntry(info, overrides);
}
+ @Override
+ public void onDisplayRemoved(@NonNull DisplayInfo info) {
+ mOverrideSettings.onDisplayRemoved(info);
+ }
+
+ @VisibleForTesting
+ int getOverrideSettingsSize() {
+ return mOverrideSettings.mSettings.size();
+ }
+
/**
* Class that allows reading {@link SettingsEntry entries} from a
* {@link ReadableSettingsStorage}.
@@ -168,14 +182,15 @@
*/
@DisplayIdentifierType
protected int mIdentifierType;
- protected final Map<String, SettingsEntry> mSettings = new HashMap<>();
+ @NonNull
+ protected final ArrayMap<String, SettingsEntry> mSettings = new ArrayMap<>();
- ReadableSettings(ReadableSettingsStorage settingsStorage) {
+ ReadableSettings(@NonNull ReadableSettingsStorage settingsStorage) {
loadSettings(settingsStorage);
}
@Nullable
- final SettingsEntry getSettingsEntry(DisplayInfo info) {
+ final SettingsEntry getSettingsEntry(@NonNull DisplayInfo info) {
final String identifier = getIdentifier(info);
SettingsEntry settings;
// Try to get corresponding settings using preferred identifier for the current config.
@@ -193,7 +208,8 @@
}
/** Gets the identifier of choice for the current config. */
- protected final String getIdentifier(DisplayInfo displayInfo) {
+ @NonNull
+ protected final String getIdentifier(@NonNull DisplayInfo displayInfo) {
if (mIdentifierType == IDENTIFIER_PORT && displayInfo.address != null) {
// Config suggests using port as identifier for physical displays.
if (displayInfo.address instanceof DisplayAddress.Physical) {
@@ -203,7 +219,7 @@
return displayInfo.uniqueId;
}
- private void loadSettings(ReadableSettingsStorage settingsStorage) {
+ private void loadSettings(@NonNull ReadableSettingsStorage settingsStorage) {
FileData fileData = readSettings(settingsStorage);
if (fileData != null) {
mIdentifierType = fileData.mIdentifierType;
@@ -217,15 +233,18 @@
* {@link WritableSettingsStorage}.
*/
private static final class WritableSettings extends ReadableSettings {
+ @NonNull
private final WritableSettingsStorage mSettingsStorage;
+ @NonNull
+ private final ArraySet<String> mVirtualDisplayIdentifiers = new ArraySet<>();
- WritableSettings(WritableSettingsStorage settingsStorage) {
+ WritableSettings(@NonNull WritableSettingsStorage settingsStorage) {
super(settingsStorage);
mSettingsStorage = settingsStorage;
}
@NonNull
- SettingsEntry getOrCreateSettingsEntry(DisplayInfo info) {
+ SettingsEntry getOrCreateSettingsEntry(@NonNull DisplayInfo info) {
final String identifier = getIdentifier(info);
SettingsEntry settings;
// Try to get corresponding settings using preferred identifier for the current config.
@@ -243,21 +262,47 @@
settings = new SettingsEntry();
mSettings.put(identifier, settings);
+ if (info.type == TYPE_VIRTUAL) {
+ // Keep track of virtual display. We don't want to write virtual display settings to
+ // file.
+ mVirtualDisplayIdentifiers.add(identifier);
+ }
return settings;
}
- void updateSettingsEntry(DisplayInfo info, SettingsEntry settings) {
+ void updateSettingsEntry(@NonNull DisplayInfo info, @NonNull SettingsEntry settings) {
final SettingsEntry overrideSettings = getOrCreateSettingsEntry(info);
final boolean changed = overrideSettings.setTo(settings);
- if (changed) {
+ if (changed && info.type != TYPE_VIRTUAL) {
writeSettings();
}
}
+ void onDisplayRemoved(@NonNull DisplayInfo info) {
+ final String identifier = getIdentifier(info);
+ if (!mSettings.containsKey(identifier)) {
+ return;
+ }
+ if (mVirtualDisplayIdentifiers.remove(identifier)
+ || mSettings.get(identifier).isEmpty()) {
+ // Don't keep track of virtual display or empty settings to avoid growing the cached
+ // map.
+ mSettings.remove(identifier);
+ }
+ }
+
private void writeSettings() {
- FileData fileData = new FileData();
+ final FileData fileData = new FileData();
fileData.mIdentifierType = mIdentifierType;
- fileData.mSettings.putAll(mSettings);
+ final int size = mSettings.size();
+ for (int i = 0; i < size; i++) {
+ final String identifier = mSettings.keyAt(i);
+ if (mVirtualDisplayIdentifiers.contains(identifier)) {
+ // Do not write virtual display settings to file.
+ continue;
+ }
+ fileData.mSettings.put(identifier, mSettings.get(identifier));
+ }
DisplayWindowSettingsProvider.writeSettings(mSettingsStorage, fileData);
}
}
@@ -283,7 +328,7 @@
}
@Nullable
- private static FileData readSettings(ReadableSettingsStorage storage) {
+ private static FileData readSettings(@NonNull ReadableSettingsStorage storage) {
InputStream stream;
try {
stream = storage.openRead();
@@ -348,13 +393,14 @@
return fileData;
}
- private static int getIntAttribute(TypedXmlPullParser parser, String name, int defaultValue) {
+ private static int getIntAttribute(@NonNull TypedXmlPullParser parser, @NonNull String name,
+ int defaultValue) {
return parser.getAttributeInt(null, name, defaultValue);
}
@Nullable
- private static Integer getIntegerAttribute(TypedXmlPullParser parser, String name,
- @Nullable Integer defaultValue) {
+ private static Integer getIntegerAttribute(@NonNull TypedXmlPullParser parser,
+ @NonNull String name, @Nullable Integer defaultValue) {
try {
return parser.getAttributeInt(null, name);
} catch (Exception ignored) {
@@ -363,8 +409,8 @@
}
@Nullable
- private static Boolean getBooleanAttribute(TypedXmlPullParser parser, String name,
- @Nullable Boolean defaultValue) {
+ private static Boolean getBooleanAttribute(@NonNull TypedXmlPullParser parser,
+ @NonNull String name, @Nullable Boolean defaultValue) {
try {
return parser.getAttributeBoolean(null, name);
} catch (Exception ignored) {
@@ -372,7 +418,7 @@
}
}
- private static void readDisplay(TypedXmlPullParser parser, FileData fileData)
+ private static void readDisplay(@NonNull TypedXmlPullParser parser, @NonNull FileData fileData)
throws NumberFormatException, XmlPullParserException, IOException {
String name = parser.getAttributeValue(null, "name");
if (name != null) {
@@ -420,7 +466,7 @@
XmlUtils.skipCurrentTag(parser);
}
- private static void readConfig(TypedXmlPullParser parser, FileData fileData)
+ private static void readConfig(@NonNull TypedXmlPullParser parser, @NonNull FileData fileData)
throws NumberFormatException,
XmlPullParserException, IOException {
fileData.mIdentifierType = getIntAttribute(parser, "identifier",
@@ -428,7 +474,8 @@
XmlUtils.skipCurrentTag(parser);
}
- private static void writeSettings(WritableSettingsStorage storage, FileData data) {
+ private static void writeSettings(@NonNull WritableSettingsStorage storage,
+ @NonNull FileData data) {
OutputStream stream;
try {
stream = storage.startWrite();
@@ -525,7 +572,8 @@
private static final class FileData {
int mIdentifierType;
- final Map<String, SettingsEntry> mSettings = new HashMap<>();
+ @NonNull
+ final Map<String, SettingsEntry> mSettings = new ArrayMap<>();
@Override
public String toString() {
@@ -537,6 +585,7 @@
}
private static final class AtomicFileStorage implements WritableSettingsStorage {
+ @NonNull
private final AtomicFile mAtomicFile;
AtomicFileStorage(@NonNull AtomicFile atomicFile) {
diff --git a/services/core/java/com/android/server/wm/LetterboxConfiguration.java b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
index fda22ca..e945bc1 100644
--- a/services/core/java/com/android/server/wm/LetterboxConfiguration.java
+++ b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
@@ -85,6 +85,13 @@
// TODO(b/288142656): Enable user aspect ratio settings by default.
private static final boolean DEFAULT_VALUE_ENABLE_USER_ASPECT_RATIO_SETTINGS = false;
+ // Whether the letterbox wallpaper style is enabled by default
+ private static final String KEY_ENABLE_LETTERBOX_BACKGROUND_WALLPAPER =
+ "enable_letterbox_background_wallpaper";
+
+ // TODO(b/290048978): Enable wallpaper as default letterbox background.
+ private static final boolean DEFAULT_VALUE_ENABLE_LETTERBOX_BACKGROUND_WALLPAPER = false;
+
/**
* Override of aspect ratio for fixed orientation letterboxing that is set via ADB with
* set-fixed-orientation-letterbox-aspect-ratio or via {@link
@@ -101,9 +108,16 @@
/** Enum for Letterbox background type. */
@Retention(RetentionPolicy.SOURCE)
- @IntDef({LETTERBOX_BACKGROUND_SOLID_COLOR, LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND,
- LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING, LETTERBOX_BACKGROUND_WALLPAPER})
+ @IntDef({LETTERBOX_BACKGROUND_OVERRIDE_UNSET,
+ LETTERBOX_BACKGROUND_SOLID_COLOR,
+ LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND,
+ LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING,
+ LETTERBOX_BACKGROUND_WALLPAPER})
@interface LetterboxBackgroundType {};
+
+ /** No letterbox background style set. Using the one defined by DeviceConfig. */
+ static final int LETTERBOX_BACKGROUND_OVERRIDE_UNSET = -1;
+
/** Solid background using color specified in R.color.config_letterboxBackgroundColor. */
static final int LETTERBOX_BACKGROUND_SOLID_COLOR = 0;
@@ -183,14 +197,14 @@
@Nullable private Integer mLetterboxBackgroundColorResourceIdOverride;
@LetterboxBackgroundType
- private int mLetterboxBackgroundType;
+ private final int mLetterboxBackgroundType;
- // Blur radius for LETTERBOX_BACKGROUND_WALLPAPER option in mLetterboxBackgroundType.
+ // Blur radius for LETTERBOX_BACKGROUND_WALLPAPER option from getLetterboxBackgroundType().
// Values <= 0 are ignored and 0 is used instead.
- private int mLetterboxBackgroundWallpaperBlurRadius;
+ private int mLetterboxBackgroundWallpaperBlurRadiusPx;
// Alpha of a black scrim shown over wallpaper letterbox background when
- // LETTERBOX_BACKGROUND_WALLPAPER option is selected for mLetterboxBackgroundType.
+ // LETTERBOX_BACKGROUND_WALLPAPER option is returned from getLetterboxBackgroundType().
// Values < 0 or >= 1 are ignored and 0.0 (transparent) is used instead.
private float mLetterboxBackgroundWallpaperDarkScrimAlpha;
@@ -252,6 +266,11 @@
// Allows to enable user aspect ratio settings ignoring flags.
private boolean mUserAppAspectRatioSettingsOverrideEnabled;
+ // The override for letterbox background type in case it's different from
+ // LETTERBOX_BACKGROUND_OVERRIDE_UNSET
+ @LetterboxBackgroundType
+ private int mLetterboxBackgroundTypeOverride = LETTERBOX_BACKGROUND_OVERRIDE_UNSET;
+
// Whether we should use split screen aspect ratio for the activity when camera compat treatment
// is enabled and activity is connected to the camera in fullscreen.
private final boolean mIsCameraCompatSplitScreenAspectRatioEnabled;
@@ -275,16 +294,15 @@
@NonNull private final SynchedDeviceConfig mDeviceConfig;
LetterboxConfiguration(@NonNull final Context systemUiContext) {
- this(systemUiContext,
- new LetterboxConfigurationPersister(systemUiContext,
- () -> readLetterboxHorizontalReachabilityPositionFromConfig(
- systemUiContext, /* forBookMode */ false),
- () -> readLetterboxVerticalReachabilityPositionFromConfig(
- systemUiContext, /* forTabletopMode */ false),
- () -> readLetterboxHorizontalReachabilityPositionFromConfig(
- systemUiContext, /* forBookMode */ true),
- () -> readLetterboxVerticalReachabilityPositionFromConfig(
- systemUiContext, /* forTabletopMode */ true)));
+ this(systemUiContext, new LetterboxConfigurationPersister(
+ () -> readLetterboxHorizontalReachabilityPositionFromConfig(
+ systemUiContext, /* forBookMode */ false),
+ () -> readLetterboxVerticalReachabilityPositionFromConfig(
+ systemUiContext, /* forTabletopMode */ false),
+ () -> readLetterboxHorizontalReachabilityPositionFromConfig(
+ systemUiContext, /* forBookMode */ true),
+ () -> readLetterboxVerticalReachabilityPositionFromConfig(
+ systemUiContext, /* forTabletopMode */ true)));
}
@VisibleForTesting
@@ -294,10 +312,10 @@
mFixedOrientationLetterboxAspectRatio = mContext.getResources().getFloat(
R.dimen.config_fixedOrientationLetterboxAspectRatio);
+ mLetterboxBackgroundType = readLetterboxBackgroundTypeFromConfig(mContext);
mLetterboxActivityCornersRadius = mContext.getResources().getInteger(
R.integer.config_letterboxActivityCornersRadius);
- mLetterboxBackgroundType = readLetterboxBackgroundTypeFromConfig(mContext);
- mLetterboxBackgroundWallpaperBlurRadius = mContext.getResources().getDimensionPixelSize(
+ mLetterboxBackgroundWallpaperBlurRadiusPx = mContext.getResources().getDimensionPixelSize(
R.dimen.config_letterboxBackgroundWallpaperBlurRadius);
mLetterboxBackgroundWallpaperDarkScrimAlpha = mContext.getResources().getFloat(
R.dimen.config_letterboxBackgroundWallaperDarkScrimAlpha);
@@ -359,6 +377,8 @@
DEFAULT_VALUE_ENABLE_USER_ASPECT_RATIO_SETTINGS,
mContext.getResources().getBoolean(
R.bool.config_appCompatUserAppAspectRatioSettingsIsEnabled))
+ .addDeviceConfigEntry(KEY_ENABLE_LETTERBOX_BACKGROUND_WALLPAPER,
+ DEFAULT_VALUE_ENABLE_LETTERBOX_BACKGROUND_WALLPAPER, /* enabled */ true)
.build();
}
@@ -497,25 +517,39 @@
}
/**
- * Gets {@link LetterboxBackgroundType} specified in {@link
- * com.android.internal.R.integer.config_letterboxBackgroundType} or over via ADB command.
+ * Gets {@link LetterboxBackgroundType} specified via ADB command or the default one.
*/
@LetterboxBackgroundType
int getLetterboxBackgroundType() {
- return mLetterboxBackgroundType;
+ return mLetterboxBackgroundTypeOverride != LETTERBOX_BACKGROUND_OVERRIDE_UNSET
+ ? mLetterboxBackgroundTypeOverride
+ : getDefaultLetterboxBackgroundType();
}
- /** Sets letterbox background type. */
- void setLetterboxBackgroundType(@LetterboxBackgroundType int backgroundType) {
- mLetterboxBackgroundType = backgroundType;
+ /** Overrides the letterbox background type. */
+ void setLetterboxBackgroundTypeOverride(@LetterboxBackgroundType int backgroundType) {
+ mLetterboxBackgroundTypeOverride = backgroundType;
}
/**
- * Resets cletterbox background type to {@link
- * com.android.internal.R.integer.config_letterboxBackgroundType}.
+ * Resets letterbox background type value depending on the
+ * {@link #KEY_ENABLE_LETTERBOX_BACKGROUND_WALLPAPER} built time and runtime flags.
+ *
+ * <p>If enabled, the letterbox background type value is set toZ
+ * {@link #LETTERBOX_BACKGROUND_WALLPAPER}. When disabled the letterbox background type value
+ * comes from {@link R.integer.config_letterboxBackgroundType}.
*/
void resetLetterboxBackgroundType() {
- mLetterboxBackgroundType = readLetterboxBackgroundTypeFromConfig(mContext);
+ mLetterboxBackgroundTypeOverride = LETTERBOX_BACKGROUND_OVERRIDE_UNSET;
+ }
+
+ // Returns KEY_ENABLE_LETTERBOX_BACKGROUND_WALLPAPER if the DeviceConfig flag is enabled
+ // or the value in com.android.internal.R.integer.config_letterboxBackgroundType if the flag
+ // is disabled.
+ @LetterboxBackgroundType
+ private int getDefaultLetterboxBackgroundType() {
+ return mDeviceConfig.getFlagValue(KEY_ENABLE_LETTERBOX_BACKGROUND_WALLPAPER)
+ ? LETTERBOX_BACKGROUND_WALLPAPER : mLetterboxBackgroundType;
}
/** Returns a string representing the given {@link LetterboxBackgroundType}. */
@@ -548,7 +582,7 @@
/**
* Overrides alpha of a black scrim shown over wallpaper for {@link
- * #LETTERBOX_BACKGROUND_WALLPAPER} option in {@link mLetterboxBackgroundType}.
+ * #LETTERBOX_BACKGROUND_WALLPAPER} option returned from {@link getLetterboxBackgroundType()}.
*
* <p>If given value is < 0 or >= 1, both it and a value of {@link
* com.android.internal.R.dimen.config_letterboxBackgroundWallaperDarkScrimAlpha} are ignored
@@ -575,33 +609,33 @@
}
/**
- * Overrides blur radius for {@link #LETTERBOX_BACKGROUND_WALLPAPER} option in
- * {@link mLetterboxBackgroundType}.
+ * Overrides blur radius for {@link #LETTERBOX_BACKGROUND_WALLPAPER} option from
+ * {@link getLetterboxBackgroundType()}.
*
* <p> If given value <= 0, both it and a value of {@link
* com.android.internal.R.dimen.config_letterboxBackgroundWallpaperBlurRadius} are ignored
* and 0 is used instead.
*/
- void setLetterboxBackgroundWallpaperBlurRadius(int radius) {
- mLetterboxBackgroundWallpaperBlurRadius = radius;
+ void setLetterboxBackgroundWallpaperBlurRadiusPx(int radius) {
+ mLetterboxBackgroundWallpaperBlurRadiusPx = radius;
}
/**
- * Resets blur raidus for {@link #LETTERBOX_BACKGROUND_WALLPAPER} option in {@link
- * mLetterboxBackgroundType} to {@link
+ * Resets blur raidus for {@link #LETTERBOX_BACKGROUND_WALLPAPER} option returned by {@link
+ * getLetterboxBackgroundType()} to {@link
* com.android.internal.R.dimen.config_letterboxBackgroundWallpaperBlurRadius}.
*/
- void resetLetterboxBackgroundWallpaperBlurRadius() {
- mLetterboxBackgroundWallpaperBlurRadius = mContext.getResources().getDimensionPixelSize(
+ void resetLetterboxBackgroundWallpaperBlurRadiusPx() {
+ mLetterboxBackgroundWallpaperBlurRadiusPx = mContext.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.config_letterboxBackgroundWallpaperBlurRadius);
}
/**
- * Gets blur raidus for {@link #LETTERBOX_BACKGROUND_WALLPAPER} option in {@link
- * mLetterboxBackgroundType}.
+ * Gets blur raidus for {@link #LETTERBOX_BACKGROUND_WALLPAPER} option returned by {@link
+ * getLetterboxBackgroundType()}.
*/
- int getLetterboxBackgroundWallpaperBlurRadius() {
- return mLetterboxBackgroundWallpaperBlurRadius;
+ int getLetterboxBackgroundWallpaperBlurRadiusPx() {
+ return mLetterboxBackgroundWallpaperBlurRadiusPx;
}
/*
diff --git a/services/core/java/com/android/server/wm/LetterboxConfigurationPersister.java b/services/core/java/com/android/server/wm/LetterboxConfigurationPersister.java
index 7563397..38aa903 100644
--- a/services/core/java/com/android/server/wm/LetterboxConfigurationPersister.java
+++ b/services/core/java/com/android/server/wm/LetterboxConfigurationPersister.java
@@ -23,7 +23,6 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.content.Context;
import android.os.Environment;
import android.os.StrictMode;
import android.os.StrictMode.ThreadPolicy;
@@ -53,10 +52,8 @@
private static final String TAG =
TAG_WITH_CLASS_NAME ? "LetterboxConfigurationPersister" : TAG_WM;
- @VisibleForTesting
- static final String LETTERBOX_CONFIGURATION_FILENAME = "letterbox_config";
+ private static final String LETTERBOX_CONFIGURATION_FILENAME = "letterbox_config";
- private final Context mContext;
private final Supplier<Integer> mDefaultHorizontalReachabilitySupplier;
private final Supplier<Integer> mDefaultVerticalReachabilitySupplier;
private final Supplier<Integer> mDefaultBookModeReachabilitySupplier;
@@ -97,36 +94,32 @@
@NonNull
private final PersisterQueue mPersisterQueue;
- LetterboxConfigurationPersister(Context systemUiContext,
- Supplier<Integer> defaultHorizontalReachabilitySupplier,
- Supplier<Integer> defaultVerticalReachabilitySupplier,
- Supplier<Integer> defaultBookModeReachabilitySupplier,
- Supplier<Integer> defaultTabletopModeReachabilitySupplier) {
- this(systemUiContext, defaultHorizontalReachabilitySupplier,
- defaultVerticalReachabilitySupplier,
- defaultBookModeReachabilitySupplier,
- defaultTabletopModeReachabilitySupplier,
+ LetterboxConfigurationPersister(
+ @NonNull Supplier<Integer> defaultHorizontalReachabilitySupplier,
+ @NonNull Supplier<Integer> defaultVerticalReachabilitySupplier,
+ @NonNull Supplier<Integer> defaultBookModeReachabilitySupplier,
+ @NonNull Supplier<Integer> defaultTabletopModeReachabilitySupplier) {
+ this(defaultHorizontalReachabilitySupplier, defaultVerticalReachabilitySupplier,
+ defaultBookModeReachabilitySupplier, defaultTabletopModeReachabilitySupplier,
Environment.getDataSystemDirectory(), new PersisterQueue(),
- /* completionCallback */ null);
+ /* completionCallback */ null, LETTERBOX_CONFIGURATION_FILENAME);
}
@VisibleForTesting
- LetterboxConfigurationPersister(Context systemUiContext,
- Supplier<Integer> defaultHorizontalReachabilitySupplier,
- Supplier<Integer> defaultVerticalReachabilitySupplier,
- Supplier<Integer> defaultBookModeReachabilitySupplier,
- Supplier<Integer> defaultTabletopModeReachabilitySupplier,
- File configFolder,
- PersisterQueue persisterQueue, @Nullable Consumer<String> completionCallback) {
- mContext = systemUiContext.createDeviceProtectedStorageContext();
+ LetterboxConfigurationPersister(
+ @NonNull Supplier<Integer> defaultHorizontalReachabilitySupplier,
+ @NonNull Supplier<Integer> defaultVerticalReachabilitySupplier,
+ @NonNull Supplier<Integer> defaultBookModeReachabilitySupplier,
+ @NonNull Supplier<Integer> defaultTabletopModeReachabilitySupplier,
+ @NonNull File configFolder, @NonNull PersisterQueue persisterQueue,
+ @Nullable Consumer<String> completionCallback,
+ @NonNull String letterboxConfigurationFileName) {
mDefaultHorizontalReachabilitySupplier = defaultHorizontalReachabilitySupplier;
mDefaultVerticalReachabilitySupplier = defaultVerticalReachabilitySupplier;
- mDefaultBookModeReachabilitySupplier =
- defaultBookModeReachabilitySupplier;
- mDefaultTabletopModeReachabilitySupplier =
- defaultTabletopModeReachabilitySupplier;
+ mDefaultBookModeReachabilitySupplier = defaultBookModeReachabilitySupplier;
+ mDefaultTabletopModeReachabilitySupplier = defaultTabletopModeReachabilitySupplier;
mCompletionCallback = completionCallback;
- final File prefFiles = new File(configFolder, LETTERBOX_CONFIGURATION_FILENAME);
+ final File prefFiles = new File(configFolder, letterboxConfigurationFileName);
mConfigurationFile = new AtomicFile(prefFiles);
mPersisterQueue = persisterQueue;
runWithDiskReadsThreadPolicy(this::readCurrentConfiguration);
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index a816838..39f7570 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -894,7 +894,7 @@
this::shouldLetterboxHaveRoundedCorners,
this::getLetterboxBackgroundColor,
this::hasWallpaperBackgroundForLetterbox,
- this::getLetterboxWallpaperBlurRadius,
+ this::getLetterboxWallpaperBlurRadiusPx,
this::getLetterboxWallpaperDarkScrimAlpha,
this::handleHorizontalDoubleTap,
this::handleVerticalDoubleTap,
@@ -1315,7 +1315,7 @@
case LETTERBOX_BACKGROUND_WALLPAPER:
if (hasWallpaperBackgroundForLetterbox()) {
// Color is used for translucent scrim that dims wallpaper.
- return Color.valueOf(Color.BLACK);
+ return mLetterboxConfiguration.getLetterboxBackgroundColor();
}
Slog.w(TAG, "Wallpaper option is selected for letterbox background but "
+ "blur is not supported by a device or not supported in the current "
@@ -1472,10 +1472,10 @@
// Don't use wallpaper as a background if letterboxed for display cutout.
&& isLetterboxedNotForDisplayCutout(mainWindow)
// Check that dark scrim alpha or blur radius are provided
- && (getLetterboxWallpaperBlurRadius() > 0
+ && (getLetterboxWallpaperBlurRadiusPx() > 0
|| getLetterboxWallpaperDarkScrimAlpha() > 0)
// Check that blur is supported by a device if blur radius is provided.
- && (getLetterboxWallpaperBlurRadius() <= 0
+ && (getLetterboxWallpaperBlurRadiusPx() <= 0
|| isLetterboxWallpaperBlurSupported());
if (mShowWallpaperForLetterboxBackground != wallpaperShouldBeShown) {
mShowWallpaperForLetterboxBackground = wallpaperShouldBeShown;
@@ -1483,9 +1483,9 @@
}
}
- private int getLetterboxWallpaperBlurRadius() {
- int blurRadius = mLetterboxConfiguration.getLetterboxBackgroundWallpaperBlurRadius();
- return blurRadius < 0 ? 0 : blurRadius;
+ private int getLetterboxWallpaperBlurRadiusPx() {
+ int blurRadius = mLetterboxConfiguration.getLetterboxBackgroundWallpaperBlurRadiusPx();
+ return Math.max(blurRadius, 0);
}
private float getLetterboxWallpaperDarkScrimAlpha() {
@@ -1535,7 +1535,7 @@
pw.println(prefix + " letterboxBackgroundWallpaperDarkScrimAlpha="
+ getLetterboxWallpaperDarkScrimAlpha());
pw.println(prefix + " letterboxBackgroundWallpaperBlurRadius="
- + getLetterboxWallpaperBlurRadius());
+ + getLetterboxWallpaperBlurRadiusPx());
}
pw.println(prefix + " isHorizontalReachabilityEnabled="
diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java
index 9ef5ed0..b71d918b 100644
--- a/services/core/java/com/android/server/wm/RecentTasks.java
+++ b/services/core/java/com/android/server/wm/RecentTasks.java
@@ -183,6 +183,8 @@
/** The non-empty tasks that are removed from recent tasks (see {@link #removeForAddTask}). */
private final ArrayList<Task> mHiddenTasks = new ArrayList<>();
+ /** The maximum size that the hidden tasks are cached. */
+ private static final int MAX_HIDDEN_TASK_SIZE = 10;
/** Whether to trim inactive tasks when activities are idle. */
private boolean mCheckTrimmableTasksOnIdle;
@@ -1477,9 +1479,13 @@
return task.compareTo(rootHomeTask) < 0;
}
- /** Remove the tasks that user may not be able to return. */
+ /** Remove the tasks that user may not be able to return when exceeds the cache limit. */
private void removeUnreachableHiddenTasks(int windowingMode) {
- for (int i = mHiddenTasks.size() - 1; i >= 0; i--) {
+ final int size = mHiddenTasks.size();
+ if (size <= MAX_HIDDEN_TASK_SIZE) {
+ return;
+ }
+ for (int i = size - 1; i >= MAX_HIDDEN_TASK_SIZE; i--) {
final Task hiddenTask = mHiddenTasks.get(i);
if (!hiddenTask.hasChild() || hiddenTask.inRecents) {
// The task was removed by other path or it became reachable (added to recents).
@@ -1523,7 +1529,7 @@
// A non-empty task is replaced by a new task. Because the removed task is no longer
// managed by the recent tasks list, add it to the hidden list to prevent the task
// from becoming dangling.
- mHiddenTasks.add(removedTask);
+ mHiddenTasks.add(0, removedTask);
}
notifyTaskRemoved(removedTask, false /* wasTrimmed */, false /* killProcess */);
if (DEBUG_RECENTS_TRIM_TASKS) {
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 48420d2..2f0c303 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -1849,9 +1849,8 @@
// Don't do recursive work.
return;
}
-
+ mTaskSupervisor.beginActivityVisibilityUpdate();
try {
- mTaskSupervisor.beginActivityVisibilityUpdate();
// First the front root tasks. In case any are not fullscreen and are in front of home.
for (int displayNdx = getChildCount() - 1; displayNdx >= 0; --displayNdx) {
final DisplayContent display = getChildAt(displayNdx);
@@ -3226,6 +3225,10 @@
+ "not idle", rootTask.getRootTaskId(), resumedActivity);
return false;
}
+ if (mTransitionController.isTransientLaunch(resumedActivity)) {
+ // Not idle if the transient transition animation is running.
+ return false;
+ }
}
// End power mode launch when idle.
mService.endLaunchPowerMode(ActivityTaskManagerService.POWER_MODE_REASON_START_ACTIVITY);
diff --git a/services/core/java/com/android/server/wm/SafeActivityOptions.java b/services/core/java/com/android/server/wm/SafeActivityOptions.java
index c914fa1..fe3094e 100644
--- a/services/core/java/com/android/server/wm/SafeActivityOptions.java
+++ b/services/core/java/com/android/server/wm/SafeActivityOptions.java
@@ -293,26 +293,7 @@
throw new SecurityException(msg);
}
// Check if the caller is allowed to launch on the specified display area.
- final WindowContainerToken daToken = options.getLaunchTaskDisplayArea();
- TaskDisplayArea taskDisplayArea = daToken != null
- ? (TaskDisplayArea) WindowContainer.fromBinder(daToken.asBinder()) : null;
-
- // If we do not have a task display area token, check if the launch task display area
- // feature id is specified.
- if (taskDisplayArea == null) {
- final int launchTaskDisplayAreaFeatureId = options.getLaunchTaskDisplayAreaFeatureId();
- if (launchTaskDisplayAreaFeatureId != FEATURE_UNDEFINED) {
- final int launchDisplayId = options.getLaunchDisplayId() == INVALID_DISPLAY
- ? DEFAULT_DISPLAY : options.getLaunchDisplayId();
- final DisplayContent dc = supervisor.mRootWindowContainer
- .getDisplayContent(launchDisplayId);
- if (dc != null) {
- taskDisplayArea = dc.getItemFromTaskDisplayAreas(tda ->
- tda.mFeatureId == launchTaskDisplayAreaFeatureId ? tda : null);
- }
- }
- }
-
+ final TaskDisplayArea taskDisplayArea = getLaunchTaskDisplayArea(options, supervisor);
if (aInfo != null && taskDisplayArea != null
&& !supervisor.isCallerAllowedToLaunchOnTaskDisplayArea(callingPid, callingUid,
taskDisplayArea, aInfo)) {
@@ -428,6 +409,32 @@
}
}
+ @VisibleForTesting
+ TaskDisplayArea getLaunchTaskDisplayArea(ActivityOptions options,
+ ActivityTaskSupervisor supervisor) {
+ final WindowContainerToken daToken = options.getLaunchTaskDisplayArea();
+ TaskDisplayArea taskDisplayArea = daToken != null
+ ? (TaskDisplayArea) WindowContainer.fromBinder(daToken.asBinder()) : null;
+ if (taskDisplayArea != null) {
+ return taskDisplayArea;
+ }
+
+ // If we do not have a task display area token, check if the launch task display area
+ // feature id is specified.
+ final int launchTaskDisplayAreaFeatureId = options.getLaunchTaskDisplayAreaFeatureId();
+ if (launchTaskDisplayAreaFeatureId != FEATURE_UNDEFINED) {
+ final int launchDisplayId = options.getLaunchDisplayId() == INVALID_DISPLAY
+ ? DEFAULT_DISPLAY : options.getLaunchDisplayId();
+ final DisplayContent dc = supervisor.mRootWindowContainer
+ .getDisplayContent(launchDisplayId);
+ if (dc != null) {
+ taskDisplayArea = dc.getItemFromTaskDisplayAreas(tda ->
+ tda.mFeatureId == launchTaskDisplayAreaFeatureId ? tda : null);
+ }
+ }
+ return taskDisplayArea;
+ }
+
private boolean isAssistant(ActivityTaskManagerService atmService, int callingUid) {
if (atmService.mActiveVoiceInteractionServiceComponent == null) {
return false;
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 9c23beb..92e90ae 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -5406,8 +5406,7 @@
// Basic case: for simple app-centric recents, we need to recreate
// the task if the affinity has changed.
- final String affinity = ActivityRecord.computeTaskAffinity(destAffinity, srec.getUid(),
- srec.launchMode, srec.mActivityComponent);
+ final String affinity = ActivityRecord.computeTaskAffinity(destAffinity, srec.getUid());
if (srec == null || srec.getTask().affinity == null
|| !srec.getTask().affinity.equals(affinity)) {
return true;
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 0248acc..a143540 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -723,6 +723,14 @@
mFlags |= WindowManager.TRANSIT_FLAG_INVISIBLE;
return;
}
+ // Activity doesn't need to capture snapshot if the starting window has associated to task.
+ if (wc.asActivityRecord() != null) {
+ final ActivityRecord activityRecord = wc.asActivityRecord();
+ if (activityRecord.mStartingData != null
+ && activityRecord.mStartingData.mAssociatedTask != null) {
+ return;
+ }
+ }
if (mContainerFreezer == null) {
mContainerFreezer = new ScreenshotFreezer();
@@ -1183,16 +1191,6 @@
hasParticipatedDisplay = true;
continue;
}
- final WallpaperWindowToken wt = participant.asWallpaperToken();
- if (wt != null) {
- final boolean visibleAtTransitionEnd = mVisibleAtTransitionEndTokens.contains(wt);
- if (!visibleAtTransitionEnd && !wt.isVisibleRequested()) {
- ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
- " Commit wallpaper becoming invisible: %s", wt);
- wt.commitVisibility(false /* visible */);
- }
- continue;
- }
final Task tr = participant.asTask();
if (tr != null && tr.isVisibleRequested() && tr.inPinnedWindowingMode()) {
final ActivityRecord top = tr.getTopNonFinishingActivity();
@@ -1212,6 +1210,20 @@
}
}
}
+ // Commit wallpaper visibility after activity, because usually the wallpaper target token is
+ // an activity, and wallpaper's visibility is depends on activity's visibility.
+ for (int i = mParticipants.size() - 1; i >= 0; --i) {
+ final WallpaperWindowToken wt = mParticipants.valueAt(i).asWallpaperToken();
+ if (wt == null) continue;
+ final WindowState target = wt.mDisplayContent.mWallpaperController.getWallpaperTarget();
+ final boolean isTargetInvisible = target == null || !target.mToken.isVisible();
+ if (isTargetInvisible || (!wt.isVisibleRequested()
+ && !mVisibleAtTransitionEndTokens.contains(wt))) {
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+ " Commit wallpaper becoming invisible: %s", wt);
+ wt.commitVisibility(false /* visible */);
+ }
+ }
if (committedSomeInvisible) {
mController.onCommittedInvisibles();
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 8dc2840..db92191 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import static android.Manifest.permission.ACCESS_SURFACE_FLINGER;
import static android.Manifest.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS;
import static android.Manifest.permission.INPUT_CONSUMER;
import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW;
@@ -24,7 +25,6 @@
import static android.Manifest.permission.MODIFY_TOUCH_MODE_STATE;
import static android.Manifest.permission.READ_FRAME_BUFFER;
import static android.Manifest.permission.REGISTER_WINDOW_MANAGER_LISTENERS;
-import static android.Manifest.permission.RESTRICTED_VR_ACCESS;
import static android.Manifest.permission.START_TASKS_FROM_RECENTS;
import static android.Manifest.permission.STATUS_BAR_SERVICE;
import static android.Manifest.permission.WRITE_SECURE_SETTINGS;
@@ -552,7 +552,9 @@
final PackageManagerInternal mPmInternal;
private final TestUtilityService mTestUtilityService;
+ @NonNull
final DisplayWindowSettingsProvider mDisplayWindowSettingsProvider;
+ @NonNull
final DisplayWindowSettings mDisplayWindowSettings;
/** If the system should display notifications for apps displaying an alert window. */
@@ -9550,4 +9552,23 @@
return List.copyOf(notifiedApps);
}
}
+
+ @RequiresPermission(ACCESS_SURFACE_FLINGER)
+ @Override
+ public boolean replaceContentOnDisplay(int displayId, SurfaceControl sc) {
+ if (!checkCallingPermission(ACCESS_SURFACE_FLINGER,
+ "replaceDisplayContent()")) {
+ throw new SecurityException("Requires ACCESS_SURFACE_FLINGER permission");
+ }
+
+ DisplayContent dc;
+ synchronized (mGlobalLock) {
+ dc = mRoot.getDisplayContentOrCreate(displayId);
+ if (dc == null) {
+ return false;
+ }
+ dc.replaceContent(sc);
+ return true;
+ }
+ }
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
index 05e858d..f4781f9 100644
--- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
+++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
@@ -41,6 +41,7 @@
import android.provider.Settings;
import android.util.DisplayMetrics;
import android.util.Pair;
+import android.util.TypedValue;
import android.view.Display;
import android.view.IWindow;
import android.view.IWindowManager;
@@ -728,7 +729,7 @@
return -1;
}
synchronized (mInternal.mGlobalLock) {
- mLetterboxConfiguration.setLetterboxBackgroundType(backgroundType);
+ mLetterboxConfiguration.setLetterboxBackgroundTypeOverride(backgroundType);
}
return 0;
}
@@ -770,10 +771,10 @@
private int runSetLetterboxBackgroundWallpaperBlurRadius(PrintWriter pw)
throws RemoteException {
- final int radius;
+ final int radiusDp;
try {
String arg = getNextArgRequired();
- radius = Integer.parseInt(arg);
+ radiusDp = Integer.parseInt(arg);
} catch (NumberFormatException e) {
getErrPrintWriter().println("Error: blur radius format " + e);
return -1;
@@ -783,7 +784,9 @@
return -1;
}
synchronized (mInternal.mGlobalLock) {
- mLetterboxConfiguration.setLetterboxBackgroundWallpaperBlurRadius(radius);
+ final int radiusPx = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+ radiusDp, mInternal.mContext.getResources().getDisplayMetrics());
+ mLetterboxConfiguration.setLetterboxBackgroundWallpaperBlurRadiusPx(radiusPx);
}
return 0;
}
@@ -1050,7 +1053,7 @@
mLetterboxConfiguration.resetLetterboxBackgroundColor();
break;
case "wallpaperBlurRadius":
- mLetterboxConfiguration.resetLetterboxBackgroundWallpaperBlurRadius();
+ mLetterboxConfiguration.resetLetterboxBackgroundWallpaperBlurRadiusPx();
break;
case "wallpaperDarkScrimAlpha":
mLetterboxConfiguration.resetLetterboxBackgroundWallpaperDarkScrimAlpha();
@@ -1188,7 +1191,7 @@
mLetterboxConfiguration.resetLetterboxActivityCornersRadius();
mLetterboxConfiguration.resetLetterboxBackgroundType();
mLetterboxConfiguration.resetLetterboxBackgroundColor();
- mLetterboxConfiguration.resetLetterboxBackgroundWallpaperBlurRadius();
+ mLetterboxConfiguration.resetLetterboxBackgroundWallpaperBlurRadiusPx();
mLetterboxConfiguration.resetLetterboxBackgroundWallpaperDarkScrimAlpha();
mLetterboxConfiguration.resetLetterboxHorizontalPositionMultiplier();
mLetterboxConfiguration.resetIsHorizontalReachabilityEnabled();
@@ -1262,7 +1265,7 @@
pw.println(" Background color: " + Integer.toHexString(
mLetterboxConfiguration.getLetterboxBackgroundColor().toArgb()));
pw.println(" Wallpaper blur radius: "
- + mLetterboxConfiguration.getLetterboxBackgroundWallpaperBlurRadius());
+ + mLetterboxConfiguration.getLetterboxBackgroundWallpaperBlurRadiusPx());
pw.println(" Wallpaper dark scrim alpha: "
+ mLetterboxConfiguration.getLetterboxBackgroundWallpaperDarkScrimAlpha());
pw.println("Is letterboxing for translucent activities enabled: "
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 82adccd..0eb452d 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -24,6 +24,7 @@
import static android.window.TaskFragmentOperation.OP_TYPE_CLEAR_ADJACENT_TASK_FRAGMENTS;
import static android.window.TaskFragmentOperation.OP_TYPE_CREATE_TASK_FRAGMENT;
import static android.window.TaskFragmentOperation.OP_TYPE_DELETE_TASK_FRAGMENT;
+import static android.window.TaskFragmentOperation.OP_TYPE_REORDER_TO_FRONT;
import static android.window.TaskFragmentOperation.OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT;
import static android.window.TaskFragmentOperation.OP_TYPE_REQUEST_FOCUS_ON_TASK_FRAGMENT;
import static android.window.TaskFragmentOperation.OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS;
@@ -568,6 +569,13 @@
}
if (forceHiddenForPip) {
wc.asTask().setForceHidden(FLAG_FORCE_HIDDEN_FOR_PINNED_TASK, true /* set */);
+ // When removing pip, make sure that onStop is sent to the app ahead of
+ // onPictureInPictureModeChanged.
+ // See also PinnedStackTests#testStopBeforeMultiWindowCallbacksOnDismiss
+ wc.asTask().ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS);
+ wc.asTask().mTaskSupervisor.processStoppingAndFinishingActivities(
+ null /* launchedActivity */, false /* processPausingActivities */,
+ "force-stop-on-removing-pip");
}
int containerEffect = applyWindowContainerChange(wc, entry.getValue(),
@@ -1335,6 +1343,19 @@
taskFragment.setAnimationParams(animationParams);
break;
}
+ case OP_TYPE_REORDER_TO_FRONT: {
+ final Task task = taskFragment.getTask();
+ if (task != null) {
+ final TaskFragment topTaskFragment = task.getTaskFragment(
+ tf -> tf.asTask() == null);
+ if (topTaskFragment != null && topTaskFragment != taskFragment) {
+ final int index = task.mChildren.indexOf(topTaskFragment);
+ task.mChildren.remove(taskFragment);
+ task.mChildren.add(index, taskFragment);
+ }
+ }
+ break;
+ }
}
return effects;
}
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
index a1199d9..6747cea 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
@@ -91,8 +91,6 @@
CredentialManagerService, CredentialManagerServiceImpl> {
private static final String TAG = "CredManSysService";
- private static final String DEVICE_CONFIG_ENABLE_CREDENTIAL_DESC_API =
- "enable_credential_description_api";
private static final String PERMISSION_DENIED_ERROR = "permission_denied";
private static final String PERMISSION_DENIED_WRITE_SECURE_SETTINGS_ERROR =
"Caller is missing WRITE_SECURE_SETTINGS permission";
@@ -311,14 +309,7 @@
}
public static boolean isCredentialDescriptionApiEnabled() {
- final long origId = Binder.clearCallingIdentity();
- try {
- return DeviceConfig.getBoolean(
- DeviceConfig.NAMESPACE_CREDENTIAL, DEVICE_CONFIG_ENABLE_CREDENTIAL_DESC_API,
- false);
- } finally {
- Binder.restoreCallingIdentity(origId);
- }
+ return true;
}
@SuppressWarnings("GuardedBy") // ErrorProne requires initiateProviderSessionForRequestLocked
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 7857b25..77f8de5 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -24132,7 +24132,7 @@
// When listener is added onSubscriptionsChanged gets called immediately for once
// (even if subscriptions are not changed) and later on when subscriptions changes.
subscriptionManager.addOnSubscriptionsChangedListener(
- mSubscriptionsChangedListener.getHandlerExecutor(),
+ mHandler::post,
mSubscriptionsChangedListener);
} finally {
mInjector.binderRestoreCallingIdentity(id);
diff --git a/services/flags/Android.bp b/services/flags/Android.bp
new file mode 100644
index 0000000..2d0337dc
--- /dev/null
+++ b/services/flags/Android.bp
@@ -0,0 +1,17 @@
+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"],
+}
+
+java_library_static {
+ name: "services.flags",
+ defaults: ["platform_service_defaults"],
+ srcs: [
+ "java/**/*.java",
+ ],
+ libs: ["services.core"],
+}
diff --git a/services/flags/OWNERS b/services/flags/OWNERS
new file mode 100644
index 0000000..3925b5c
--- /dev/null
+++ b/services/flags/OWNERS
@@ -0,0 +1,6 @@
+# Bug component: 1306523
+
+mankoff@google.com
+
+pixel@google.com
+dsandler@android.com
diff --git a/services/flags/java/com/android/server/flags/DynamicFlagBinderDelegate.java b/services/flags/java/com/android/server/flags/DynamicFlagBinderDelegate.java
new file mode 100644
index 0000000..0db3287
--- /dev/null
+++ b/services/flags/java/com/android/server/flags/DynamicFlagBinderDelegate.java
@@ -0,0 +1,280 @@
+/*
+ * 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.flags;
+
+import android.annotation.NonNull;
+import android.flags.IFeatureFlagsCallback;
+import android.flags.SyncableFlag;
+import android.os.Build;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.provider.DeviceConfig;
+import android.util.Slog;
+
+import com.android.internal.os.BackgroundThread;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Function;
+
+/**
+ * Handles DynamicFlags for {@link FeatureFlagsBinder}.
+ *
+ * Dynamic flags are simultaneously simpler and more complicated than process stable flags. We can
+ * return whatever value is last known for a flag is, without too much worry about the flags
+ * changing (they are dynamic after all). However, we have to alert all the relevant clients
+ * about those flag changes, and need to be able to restore to a default value if the flag gets
+ * reset/erased during runtime.
+ */
+class DynamicFlagBinderDelegate {
+
+ private final FlagOverrideStore mFlagStore;
+ private final FlagCache<DynamicFlagData> mDynamicFlags = new FlagCache<>();
+ private final Map<Integer, Set<IFeatureFlagsCallback>> mCallbacks = new HashMap<>();
+ private static final Function<Integer, Set<IFeatureFlagsCallback>> NEW_CALLBACK_SET =
+ k -> new HashSet<>();
+
+ private final DeviceConfig.OnPropertiesChangedListener mDeviceConfigListener =
+ new DeviceConfig.OnPropertiesChangedListener() {
+ @Override
+ public void onPropertiesChanged(@NonNull DeviceConfig.Properties properties) {
+ String ns = properties.getNamespace();
+ for (String name : properties.getKeyset()) {
+ // Don't alert for flags we don't care about.
+ // Don't alert for flags that have been overridden locally.
+ if (!mDynamicFlags.contains(ns, name) || mFlagStore.contains(ns, name)) {
+ continue;
+ }
+ mFlagChangeCallback.onFlagChanged(
+ ns, name, properties.getString(name, null));
+ }
+ }
+ };
+
+ private final FlagOverrideStore.FlagChangeCallback mFlagChangeCallback =
+ (namespace, name, value) -> {
+ // Don't bother with callbacks for non-dynamic flags.
+ if (!mDynamicFlags.contains(namespace, name)) {
+ return;
+ }
+
+ // Don't bother with callbacks if nothing changed.
+ // Handling erasure (null) is special, as we may be restoring back to a value
+ // we were already at.
+ DynamicFlagData data = mDynamicFlags.getOrNull(namespace, name);
+ if (data == null) {
+ return; // shouldn't happen, but better safe than sorry.
+ }
+ if (value == null) {
+ if (data.getValue().equals(data.getDefaultValue())) {
+ return;
+ }
+ value = data.getDefaultValue();
+ } else if (data.getValue().equals(value)) {
+ return;
+ }
+ data.setValue(value);
+
+ final Set<IFeatureFlagsCallback> cbCopy;
+ synchronized (mCallbacks) {
+ cbCopy = new HashSet<>();
+
+ for (Integer pid : mCallbacks.keySet()) {
+ if (data.containsPid(pid)) {
+ cbCopy.addAll(mCallbacks.get(pid));
+ }
+ }
+ }
+ SyncableFlag sFlag = new SyncableFlag(namespace, name, value, true);
+ cbCopy.forEach(cb -> {
+ try {
+ cb.onFlagChange(sFlag);
+ } catch (RemoteException e) {
+ Slog.w(
+ FeatureFlagsService.TAG,
+ "Failed to communicate flag change to client.");
+ }
+ });
+ };
+
+ DynamicFlagBinderDelegate(FlagOverrideStore flagStore) {
+ mFlagStore = flagStore;
+ mFlagStore.setChangeCallback(mFlagChangeCallback);
+ }
+
+ SyncableFlag syncDynamicFlag(int pid, SyncableFlag sf) {
+ if (!sf.isDynamic()) {
+ return sf;
+ }
+
+ String ns = sf.getNamespace();
+ String name = sf.getName();
+
+ // Dynamic flags don't need any special threading or synchronization considerations.
+ // We simply give them whatever the current value is.
+ // However, we do need to keep track of dynamic flags, so that we can alert
+ // about changes coming in from adb, DeviceConfig, or other sources.
+ // And also so that we can keep flags relatively consistent across processes.
+
+ DynamicFlagData data = mDynamicFlags.getOrNull(ns, name);
+ String value = getFlagValue(ns, name, sf.getValue());
+ // DeviceConfig listeners are per-namespace.
+ if (!mDynamicFlags.containsNamespace(ns)) {
+ DeviceConfig.addOnPropertiesChangedListener(
+ ns, BackgroundThread.getExecutor(), mDeviceConfigListener);
+ }
+ data.addClientPid(pid);
+ data.setValue(value);
+ // Store the default value so that if an override gets erased, we can restore
+ // to something.
+ data.setDefaultValue(sf.getValue());
+
+ return new SyncableFlag(sf.getNamespace(), sf.getName(), value, true);
+ }
+
+
+ void registerCallback(int pid, IFeatureFlagsCallback callback) {
+ // Always add callback so that we don't end up with a possible race/leak.
+ // We remove the callback directly if we fail to call #linkToDeath.
+ // If we tried to add the callback after we linked, then we could end up in a
+ // scenario where we link, then the binder dies, firing our BinderGriever which tries
+ // to remove the callback (which has not yet been added), then finally we add the
+ // callback, creating a leak.
+ Set<IFeatureFlagsCallback> callbacks;
+ synchronized (mCallbacks) {
+ callbacks = mCallbacks.computeIfAbsent(pid, NEW_CALLBACK_SET);
+ callbacks.add(callback);
+ }
+ try {
+ callback.asBinder().linkToDeath(new BinderGriever(pid), 0);
+ } catch (RemoteException e) {
+ Slog.e(
+ FeatureFlagsService.TAG,
+ "Failed to link to binder death. Callback not registered.");
+ synchronized (mCallbacks) {
+ callbacks.remove(callback);
+ }
+ }
+ }
+
+ void unregisterCallback(int pid, IFeatureFlagsCallback callback) {
+ // No need to unlink, since the BinderGriever will essentially be a no-op.
+ // We would have to track our BinderGriever's in a map otherwise.
+ synchronized (mCallbacks) {
+ Set<IFeatureFlagsCallback> callbacks =
+ mCallbacks.computeIfAbsent(pid, NEW_CALLBACK_SET);
+ callbacks.remove(callback);
+ }
+ }
+
+ String getFlagValue(String namespace, String name, String defaultValue) {
+ // If we already have a value cached, just use that.
+ String value = null;
+ DynamicFlagData data = mDynamicFlags.getOrNull(namespace, name);
+ if (data != null) {
+ value = data.getValue();
+ } else {
+ // Put the value in the cache for future reference.
+ data = new DynamicFlagData(namespace, name);
+ mDynamicFlags.setIfChanged(namespace, name, data);
+ }
+ // If we're not in a release build, flags can be overridden locally on device.
+ if (!Build.IS_USER && value == null) {
+ value = mFlagStore.get(namespace, name);
+ }
+ // If we still don't have a value, maybe DeviceConfig does?
+ // Fallback to sf.getValue() here as well.
+ if (value == null) {
+ value = DeviceConfig.getString(namespace, name, defaultValue);
+ }
+
+ return value;
+ }
+
+ private static class DynamicFlagData {
+ private final String mNamespace;
+ private final String mName;
+ private final Set<Integer> mPids = new HashSet<>();
+ private String mValue;
+ private String mDefaultValue;
+
+ private DynamicFlagData(String namespace, String name) {
+ mNamespace = namespace;
+ mName = name;
+ }
+
+ String getValue() {
+ return mValue;
+ }
+
+ void setValue(String value) {
+ mValue = value;
+ }
+
+ String getDefaultValue() {
+ return mDefaultValue;
+ }
+
+ void setDefaultValue(String value) {
+ mDefaultValue = value;
+ }
+
+ void addClientPid(int pid) {
+ mPids.add(pid);
+ }
+
+ boolean containsPid(int pid) {
+ return mPids.contains(pid);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == null || !(other instanceof DynamicFlagData)) {
+ return false;
+ }
+
+ DynamicFlagData o = (DynamicFlagData) other;
+
+ return mName.equals(o.mName) && mNamespace.equals(o.mNamespace)
+ && mValue.equals(o.mValue) && mDefaultValue.equals(o.mDefaultValue);
+ }
+
+ @Override
+ public int hashCode() {
+ return mName.hashCode() + mNamespace.hashCode()
+ + mValue.hashCode() + mDefaultValue.hashCode();
+ }
+ }
+
+
+ private class BinderGriever implements IBinder.DeathRecipient {
+ private final int mPid;
+
+ private BinderGriever(int pid) {
+ mPid = pid;
+ }
+
+ @Override
+ public void binderDied() {
+ synchronized (mCallbacks) {
+ mCallbacks.remove(mPid);
+ }
+ }
+ }
+}
diff --git a/services/flags/java/com/android/server/flags/FeatureFlagsBinder.java b/services/flags/java/com/android/server/flags/FeatureFlagsBinder.java
new file mode 100644
index 0000000..1fa8532
--- /dev/null
+++ b/services/flags/java/com/android/server/flags/FeatureFlagsBinder.java
@@ -0,0 +1,168 @@
+/*
+ * 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.flags;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.flags.IFeatureFlags;
+import android.flags.IFeatureFlagsCallback;
+import android.flags.SyncableFlag;
+import android.os.Build;
+import android.os.ParcelFileDescriptor;
+
+import com.android.internal.flags.CoreFlags;
+import com.android.server.flags.FeatureFlagsService.PermissionsChecker;
+
+import java.io.FileOutputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+class FeatureFlagsBinder extends IFeatureFlags.Stub {
+ private final FlagOverrideStore mFlagStore;
+ private final FlagsShellCommand mShellCommand;
+ private final FlagCache<String> mFlagCache = new FlagCache<>();
+ private final DynamicFlagBinderDelegate mDynamicFlagDelegate;
+ private final PermissionsChecker mPermissionsChecker;
+
+ FeatureFlagsBinder(
+ FlagOverrideStore flagStore,
+ FlagsShellCommand shellCommand,
+ PermissionsChecker permissionsChecker) {
+ mFlagStore = flagStore;
+ mShellCommand = shellCommand;
+ mDynamicFlagDelegate = new DynamicFlagBinderDelegate(flagStore);
+ mPermissionsChecker = permissionsChecker;
+ }
+
+ @Override
+ public void registerCallback(IFeatureFlagsCallback callback) {
+ mDynamicFlagDelegate.registerCallback(getCallingPid(), callback);
+ }
+
+ @Override
+ public void unregisterCallback(IFeatureFlagsCallback callback) {
+ mDynamicFlagDelegate.unregisterCallback(getCallingPid(), callback);
+ }
+
+ // Note: The internals of this method should be kept in sync with queryFlags
+ // as they both should return identical results. The difference is that this method
+ // caches any values it receives and/or reads, whereas queryFlags does not.
+
+ @Override
+ public List<SyncableFlag> syncFlags(List<SyncableFlag> incomingFlags) {
+ int pid = getCallingPid();
+ List<SyncableFlag> outputFlags = new ArrayList<>();
+
+ boolean hasFullSyncPrivileges = false;
+ SecurityException permissionFailureException = null;
+ try {
+ assertSyncPermission();
+ hasFullSyncPrivileges = true;
+ } catch (SecurityException e) {
+ permissionFailureException = e;
+ }
+
+ for (SyncableFlag sf : incomingFlags) {
+ if (!hasFullSyncPrivileges && !CoreFlags.isCoreFlag(sf)) {
+ throw permissionFailureException;
+ }
+
+ String ns = sf.getNamespace();
+ String name = sf.getName();
+ SyncableFlag outFlag;
+ if (sf.isDynamic()) {
+ outFlag = mDynamicFlagDelegate.syncDynamicFlag(pid, sf);
+ } else {
+ synchronized (mFlagCache) {
+ String value = mFlagCache.getOrNull(ns, name);
+ if (value == null) {
+ String overrideValue = Build.IS_USER ? null : mFlagStore.get(ns, name);
+ value = overrideValue != null ? overrideValue : sf.getValue();
+ mFlagCache.setIfChanged(ns, name, value);
+ }
+ outFlag = new SyncableFlag(sf.getNamespace(), sf.getName(), value, false);
+ }
+ }
+ outputFlags.add(outFlag);
+ }
+ return outputFlags;
+ }
+
+ @Override
+ public void overrideFlag(SyncableFlag flag) {
+ assertWritePermission();
+ mFlagStore.set(flag.getNamespace(), flag.getName(), flag.getValue());
+ }
+
+ @Override
+ public void resetFlag(SyncableFlag flag) {
+ assertWritePermission();
+ mFlagStore.erase(flag.getNamespace(), flag.getName());
+ }
+
+ @Override
+ public List<SyncableFlag> queryFlags(List<SyncableFlag> incomingFlags) {
+ assertSyncPermission();
+ List<SyncableFlag> outputFlags = new ArrayList<>();
+ for (SyncableFlag sf : incomingFlags) {
+ String ns = sf.getNamespace();
+ String name = sf.getName();
+ String value;
+ String storeValue = mFlagStore.get(ns, name);
+ boolean overridden = storeValue != null;
+
+ if (sf.isDynamic()) {
+ value = mDynamicFlagDelegate.getFlagValue(ns, name, sf.getValue());
+ } else {
+ value = mFlagCache.getOrNull(ns, name);
+ if (value == null) {
+ value = Build.IS_USER ? null : storeValue;
+ if (value == null) {
+ value = sf.getValue();
+ }
+ }
+ }
+ outputFlags.add(new SyncableFlag(
+ sf.getNamespace(), sf.getName(), value, sf.isDynamic(), overridden));
+ }
+
+ return outputFlags;
+ }
+
+ private void assertSyncPermission() {
+ mPermissionsChecker.assertSyncPermission();
+ clearCallingIdentity();
+ }
+
+ private void assertWritePermission() {
+ mPermissionsChecker.assertWritePermission();
+ clearCallingIdentity();
+ }
+
+
+ @SystemApi
+ public int handleShellCommand(
+ @NonNull ParcelFileDescriptor in,
+ @NonNull ParcelFileDescriptor out,
+ @NonNull ParcelFileDescriptor err,
+ @NonNull String[] args) {
+ FileOutputStream fout = new FileOutputStream(out.getFileDescriptor());
+ FileOutputStream ferr = new FileOutputStream(err.getFileDescriptor());
+
+ return mShellCommand.process(args, fout, ferr);
+ }
+}
diff --git a/services/flags/java/com/android/server/flags/FeatureFlagsService.java b/services/flags/java/com/android/server/flags/FeatureFlagsService.java
new file mode 100644
index 0000000..93b9e9e
--- /dev/null
+++ b/services/flags/java/com/android/server/flags/FeatureFlagsService.java
@@ -0,0 +1,113 @@
+/*
+ * 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.flags;
+
+import static android.Manifest.permission.SYNC_FLAGS;
+import static android.Manifest.permission.WRITE_FLAGS;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.flags.FeatureFlags;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.SystemService;
+
+/**
+ * A service that manages syncing {@link android.flags.FeatureFlags} across processes.
+ *
+ * This service holds flags stable for at least the lifetime of a process, meaning that if
+ * a process comes online with a flag set to true, any other process that connects here and
+ * tries to read the same flag will also receive the flag as true. The flag will remain stable
+ * until either all of the interested processes have died, or the device restarts.
+ *
+ * TODO(279054964): Add to dumpsys
+ * @hide
+ */
+public class FeatureFlagsService extends SystemService {
+
+ static final String TAG = "FeatureFlagsService";
+ private final FlagOverrideStore mFlagStore;
+ private final FlagsShellCommand mShellCommand;
+
+ /**
+ * Initializes the system service.
+ *
+ * @param context The system server context.
+ */
+ public FeatureFlagsService(Context context) {
+ super(context);
+ mFlagStore = new FlagOverrideStore(
+ new GlobalSettingsProxy(context.getContentResolver()));
+ mShellCommand = new FlagsShellCommand(mFlagStore);
+ }
+
+ @Override
+ public void onStart() {
+ Slog.d(TAG, "Started Feature Flag Service");
+ FeatureFlagsBinder service = new FeatureFlagsBinder(
+ mFlagStore, mShellCommand, new PermissionsChecker(getContext()));
+ publishBinderService(
+ Context.FEATURE_FLAGS_SERVICE, service);
+ publishLocalService(FeatureFlags.class, new FeatureFlags(service));
+ }
+
+ @Override
+ public void onBootPhase(int phase) {
+ super.onBootPhase(phase);
+
+ if (phase == PHASE_SYSTEM_SERVICES_READY) {
+ // Immediately sync our core flags so that they get locked in. We don't want third-party
+ // apps to override them, and syncing immediately is the easiest way to prevent that.
+ FeatureFlags.getInstance().sync();
+ }
+ }
+
+ /**
+ * Delegate for checking flag permissions.
+ */
+ @VisibleForTesting
+ public static class PermissionsChecker {
+ private final Context mContext;
+
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ public PermissionsChecker(Context context) {
+ mContext = context;
+ }
+
+ /**
+ * Ensures that the caller has {@link SYNC_FLAGS} permission.
+ */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ public void assertSyncPermission() {
+ if (mContext.checkCallingOrSelfPermission(SYNC_FLAGS)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException(
+ "Non-core flag queried. Requires SYNC_FLAGS permission!");
+ }
+ }
+
+ /**
+ * Ensures that the caller has {@link WRITE_FLAGS} permission.
+ */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ public void assertWritePermission() {
+ if (mContext.checkCallingPermission(WRITE_FLAGS) != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires WRITE_FLAGS permission!");
+ }
+ }
+ }
+}
diff --git a/services/flags/java/com/android/server/flags/FlagCache.java b/services/flags/java/com/android/server/flags/FlagCache.java
new file mode 100644
index 0000000..cee1578
--- /dev/null
+++ b/services/flags/java/com/android/server/flags/FlagCache.java
@@ -0,0 +1,103 @@
+/*
+ * 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.flags;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Function;
+
+/**
+ * Threadsafe cache of values that stores the supplied default on cache miss.
+ *
+ * @param <V> The type of value to store.
+ */
+public class FlagCache<V> {
+ private final Function<String, HashMap<String, V>> mNewHashMap = k -> new HashMap<>();
+
+ // Cache is organized first by namespace, then by name. All values are stored as strings.
+ final Map<String, Map<String, V>> mCache = new HashMap<>();
+
+ FlagCache() {
+ }
+
+ /**
+ * Returns true if the namespace exists in the cache already.
+ */
+ boolean containsNamespace(String namespace) {
+ synchronized (mCache) {
+ return mCache.containsKey(namespace);
+ }
+ }
+
+ /**
+ * Returns true if the value is stored in the cache.
+ */
+ boolean contains(String namespace, String name) {
+ synchronized (mCache) {
+ Map<String, V> nsCache = mCache.get(namespace);
+ return nsCache != null && nsCache.containsKey(name);
+ }
+ }
+
+ /**
+ * Sets the value if it is different from what is currently stored.
+ *
+ * If the value is not set, or the current value is null, it will store the value and
+ * return true.
+ *
+ * @return True if the value was set. False if the value is the same.
+ */
+ boolean setIfChanged(String namespace, String name, V value) {
+ synchronized (mCache) {
+ Map<String, V> nsCache = mCache.computeIfAbsent(namespace, mNewHashMap);
+ V curValue = nsCache.get(name);
+ if (curValue == null || !curValue.equals(value)) {
+ nsCache.put(name, value);
+ return true;
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Gets the current value from the cache, setting it if it is currently absent.
+ *
+ * @return The value that is now in the cache after the call to the method.
+ */
+ V getOrSet(String namespace, String name, V defaultValue) {
+ synchronized (mCache) {
+ Map<String, V> nsCache = mCache.computeIfAbsent(namespace, mNewHashMap);
+ V value = nsCache.putIfAbsent(name, defaultValue);
+ return value == null ? defaultValue : value;
+ }
+ }
+
+ /**
+ * Gets the current value from the cache, returning null if not present.
+ *
+ * @return The value that is now in the cache if there is one.
+ */
+ V getOrNull(String namespace, String name) {
+ synchronized (mCache) {
+ Map<String, V> nsCache = mCache.get(namespace);
+ if (nsCache == null) {
+ return null;
+ }
+ return nsCache.get(name);
+ }
+ }
+}
diff --git a/services/flags/java/com/android/server/flags/FlagOverrideStore.java b/services/flags/java/com/android/server/flags/FlagOverrideStore.java
new file mode 100644
index 0000000..b1ddc7e6
--- /dev/null
+++ b/services/flags/java/com/android/server/flags/FlagOverrideStore.java
@@ -0,0 +1,122 @@
+/*
+ * 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.flags;
+
+import android.database.Cursor;
+import android.provider.Settings;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Persistent storage for the {@link FeatureFlagsService}.
+ *
+ * The implementation stores data in Settings.<store> (generally {@link Settings.Global}
+ * is expected).
+ */
+@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+public class FlagOverrideStore {
+ private static final String KEYNAME_PREFIX = "flag|";
+ private static final String NAMESPACE_NAME_SEPARATOR = ".";
+
+ private final SettingsProxy mSettingsProxy;
+
+ private FlagChangeCallback mCallback;
+
+ FlagOverrideStore(SettingsProxy settingsProxy) {
+ mSettingsProxy = settingsProxy;
+ }
+
+ void setChangeCallback(FlagChangeCallback callback) {
+ mCallback = callback;
+ }
+
+ /** Returns true if a non-null value is in the store. */
+ boolean contains(String namespace, String name) {
+ return get(namespace, name) != null;
+ }
+
+ /** Put a value in the store. */
+ @VisibleForTesting
+ public void set(String namespace, String name, String value) {
+ mSettingsProxy.putString(getPropName(namespace, name), value);
+ mCallback.onFlagChanged(namespace, name, value);
+ }
+
+ /** Read a value out of the store. */
+ @VisibleForTesting
+ public String get(String namespace, String name) {
+ return mSettingsProxy.getString(getPropName(namespace, name));
+ }
+
+ /** Erase a value from the store. */
+ @VisibleForTesting
+ public void erase(String namespace, String name) {
+ set(namespace, name, null);
+ }
+
+ Map<String, Map<String, String>> getFlags() {
+ return getFlagsForNamespace(null);
+ }
+
+ Map<String, Map<String, String>> getFlagsForNamespace(String namespace) {
+ Cursor c = mSettingsProxy.getContentResolver().query(
+ Settings.Global.CONTENT_URI,
+ new String[]{Settings.NameValueTable.NAME, Settings.NameValueTable.VALUE},
+ null, // Doesn't support a "LIKE" query
+ null,
+ null
+ );
+
+ if (c == null) {
+ return Map.of();
+ }
+ int keynamePrefixLength = KEYNAME_PREFIX.length();
+ Map<String, Map<String, String>> results = new HashMap<>();
+ while (c.moveToNext()) {
+ String key = c.getString(0);
+ if (!key.startsWith(KEYNAME_PREFIX)
+ || key.indexOf(NAMESPACE_NAME_SEPARATOR, keynamePrefixLength) < 0) {
+ continue;
+ }
+ String value = c.getString(1);
+ if (value == null || value.isEmpty()) {
+ continue;
+ }
+ String ns = key.substring(keynamePrefixLength, key.indexOf(NAMESPACE_NAME_SEPARATOR));
+ if (namespace != null && !namespace.equals(ns)) {
+ continue;
+ }
+ String name = key.substring(key.indexOf(NAMESPACE_NAME_SEPARATOR) + 1);
+ results.putIfAbsent(ns, new HashMap<>());
+ results.get(ns).put(name, value);
+ }
+ c.close();
+ return results;
+ }
+
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ static String getPropName(String namespace, String name) {
+ return KEYNAME_PREFIX + namespace + NAMESPACE_NAME_SEPARATOR + name;
+ }
+
+ interface FlagChangeCallback {
+ void onFlagChanged(String namespace, String name, String value);
+ }
+}
diff --git a/services/flags/java/com/android/server/flags/FlagsShellCommand.java b/services/flags/java/com/android/server/flags/FlagsShellCommand.java
new file mode 100644
index 0000000..b7896ee
--- /dev/null
+++ b/services/flags/java/com/android/server/flags/FlagsShellCommand.java
@@ -0,0 +1,214 @@
+/*
+ * 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.flags;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.FastPrintWriter;
+
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.util.Locale;
+import java.util.Map;
+
+/**
+ * Process command line input for the flags service.
+ */
+@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+public class FlagsShellCommand {
+ private final FlagOverrideStore mFlagStore;
+
+ FlagsShellCommand(FlagOverrideStore flagStore) {
+ mFlagStore = flagStore;
+ }
+
+ /**
+ * Interpret the command supplied in the constructor.
+ *
+ * @return Zero on success or non-zero on error.
+ */
+ public int process(
+ String[] args,
+ OutputStream out,
+ OutputStream err) {
+ PrintWriter outPw = new FastPrintWriter(out);
+ PrintWriter errPw = new FastPrintWriter(err);
+
+ if (args.length == 0) {
+ return printHelp(outPw);
+ }
+ switch (args[0].toLowerCase(Locale.ROOT)) {
+ case "help":
+ return printHelp(outPw);
+ case "list":
+ return listCmd(args, outPw, errPw);
+ case "set":
+ return setCmd(args, outPw, errPw);
+ case "get":
+ return getCmd(args, outPw, errPw);
+ case "erase":
+ return eraseCmd(args, outPw, errPw);
+ default:
+ return unknownCmd(outPw);
+ }
+ }
+
+ private int printHelp(PrintWriter outPw) {
+ outPw.println("Feature Flags command, allowing listing, setting, getting, and erasing of");
+ outPw.println("local flag overrides on a device.");
+ outPw.println();
+ outPw.println("Commands:");
+ outPw.println(" list [namespace]");
+ outPw.println(" List all flag overrides. Namespace is optional.");
+ outPw.println();
+ outPw.println(" get <namespace> <name>");
+ outPw.println(" Return the string value of a specific flag, or <unset>");
+ outPw.println();
+ outPw.println(" set <namespace> <name> <value>");
+ outPw.println(" Set a specific flag");
+ outPw.println();
+ outPw.println(" erase <namespace> <name>");
+ outPw.println(" Unset a specific flag");
+ outPw.flush();
+ return 0;
+ }
+
+ private int listCmd(String[] args, PrintWriter outPw, PrintWriter errPw) {
+ if (!validateNumArguments(args, 0, 1, args[0], errPw)) {
+ errPw.println("Expected `" + args[0] + " [namespace]`");
+ errPw.flush();
+ return -1;
+ }
+ Map<String, Map<String, String>> overrides;
+ if (args.length == 2) {
+ overrides = mFlagStore.getFlagsForNamespace(args[1]);
+ } else {
+ overrides = mFlagStore.getFlags();
+ }
+ if (overrides.isEmpty()) {
+ outPw.println("No overrides set");
+ } else {
+ int longestNamespaceLen = "namespace".length();
+ int longestFlagLen = "flag".length();
+ int longestValLen = "value".length();
+ for (Map.Entry<String, Map<String, String>> namespace : overrides.entrySet()) {
+ longestNamespaceLen = Math.max(longestNamespaceLen, namespace.getKey().length());
+ for (Map.Entry<String, String> flag : namespace.getValue().entrySet()) {
+ longestFlagLen = Math.max(longestFlagLen, flag.getKey().length());
+ longestValLen = Math.max(longestValLen, flag.getValue().length());
+ }
+ }
+ outPw.print(String.format("%-" + longestNamespaceLen + "s", "namespace"));
+ outPw.print(' ');
+ outPw.print(String.format("%-" + longestFlagLen + "s", "flag"));
+ outPw.print(' ');
+ outPw.println("value");
+ for (int i = 0; i < longestNamespaceLen; i++) {
+ outPw.print('=');
+ }
+ outPw.print(' ');
+ for (int i = 0; i < longestFlagLen; i++) {
+ outPw.print('=');
+ }
+ outPw.print(' ');
+ for (int i = 0; i < longestValLen; i++) {
+ outPw.print('=');
+ }
+ outPw.println();
+ for (Map.Entry<String, Map<String, String>> namespace : overrides.entrySet()) {
+ for (Map.Entry<String, String> flag : namespace.getValue().entrySet()) {
+ outPw.print(
+ String.format("%-" + longestNamespaceLen + "s", namespace.getKey()));
+ outPw.print(' ');
+ outPw.print(String.format("%-" + longestFlagLen + "s", flag.getKey()));
+ outPw.print(' ');
+ outPw.println(flag.getValue());
+ }
+ }
+ }
+ outPw.flush();
+ return 0;
+ }
+
+ private int setCmd(String[] args, PrintWriter outPw, PrintWriter errPw) {
+ if (!validateNumArguments(args, 3, args[0], errPw)) {
+ errPw.println("Expected `" + args[0] + " <namespace> <name> <value>`");
+ errPw.flush();
+ return -1;
+ }
+ mFlagStore.set(args[1], args[2], args[3]);
+ outPw.println("Flag " + args[1] + "." + args[2] + " is now " + args[3]);
+ outPw.flush();
+ return 0;
+ }
+
+ private int getCmd(String[] args, PrintWriter outPw, PrintWriter errPw) {
+ if (!validateNumArguments(args, 2, args[0], errPw)) {
+ errPw.println("Expected `" + args[0] + " <namespace> <name>`");
+ errPw.flush();
+ return -1;
+ }
+
+ String value = mFlagStore.get(args[1], args[2]);
+ outPw.print(args[1] + "." + args[2] + " is ");
+ if (value == null || value.isEmpty()) {
+ outPw.println("<unset>");
+ } else {
+ outPw.println("\"" + value.translateEscapes() + "\"");
+ }
+ outPw.flush();
+ return 0;
+ }
+
+ private int eraseCmd(String[] args, PrintWriter outPw, PrintWriter errPw) {
+ if (!validateNumArguments(args, 2, args[0], errPw)) {
+ errPw.println("Expected `" + args[0] + " <namespace> <name>`");
+ errPw.flush();
+ return -1;
+ }
+ mFlagStore.erase(args[1], args[2]);
+ outPw.println("Erased " + args[1] + "." + args[2]);
+ return 0;
+ }
+
+ private int unknownCmd(PrintWriter outPw) {
+ outPw.println("This command is unknown.");
+ printHelp(outPw);
+ outPw.flush();
+ return -1;
+ }
+
+ private boolean validateNumArguments(
+ String[] args, int exactly, String cmdName, PrintWriter errPw) {
+ return validateNumArguments(args, exactly, exactly, cmdName, errPw);
+ }
+
+ private boolean validateNumArguments(
+ String[] args, int min, int max, String cmdName, PrintWriter errPw) {
+ int len = args.length - 1; // Discount the command itself.
+ if (len < min) {
+ errPw.println(
+ "Less than " + min + " arguments provided for \"" + cmdName + "\" command.");
+ return false;
+ } else if (len > max) {
+ errPw.println(
+ "More than " + max + " arguments provided for \"" + cmdName + "\" command.");
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/services/flags/java/com/android/server/flags/GlobalSettingsProxy.java b/services/flags/java/com/android/server/flags/GlobalSettingsProxy.java
new file mode 100644
index 0000000..acb7bb5
--- /dev/null
+++ b/services/flags/java/com/android/server/flags/GlobalSettingsProxy.java
@@ -0,0 +1,68 @@
+/*
+ * 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.flags;
+
+import android.content.ContentResolver;
+import android.net.Uri;
+import android.provider.Settings;
+
+class GlobalSettingsProxy implements SettingsProxy {
+ private final ContentResolver mContentResolver;
+
+ GlobalSettingsProxy(ContentResolver contentResolver) {
+ mContentResolver = contentResolver;
+ }
+
+ @Override
+ public ContentResolver getContentResolver() {
+ return mContentResolver;
+ }
+
+ @Override
+ public Uri getUriFor(String name) {
+ return Settings.Global.getUriFor(name);
+ }
+
+ @Override
+ public String getStringForUser(String name, int userHandle) {
+ return Settings.Global.getStringForUser(mContentResolver, name, userHandle);
+ }
+
+ @Override
+ public boolean putString(String name, String value, boolean overrideableByRestore) {
+ throw new UnsupportedOperationException(
+ "This method only exists publicly for Settings.System and Settings.Secure");
+ }
+
+ @Override
+ public boolean putStringForUser(String name, String value, int userHandle) {
+ return Settings.Global.putStringForUser(mContentResolver, name, value, userHandle);
+ }
+
+ @Override
+ public boolean putStringForUser(String name, String value, String tag, boolean makeDefault,
+ int userHandle, boolean overrideableByRestore) {
+ return Settings.Global.putStringForUser(
+ mContentResolver, name, value, tag, makeDefault, userHandle,
+ overrideableByRestore);
+ }
+
+ @Override
+ public boolean putString(String name, String value, String tag, boolean makeDefault) {
+ return Settings.Global.putString(mContentResolver, name, value, tag, makeDefault);
+ }
+}
diff --git a/services/flags/java/com/android/server/flags/SettingsProxy.java b/services/flags/java/com/android/server/flags/SettingsProxy.java
new file mode 100644
index 0000000..c6e85d5
--- /dev/null
+++ b/services/flags/java/com/android/server/flags/SettingsProxy.java
@@ -0,0 +1,381 @@
+/*
+ * 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.flags;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.content.ContentResolver;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.provider.Settings;
+
+/**
+ * Wrapper class meant to enable hermetic testing of {@link Settings}.
+ *
+ * Implementations of this class are expected to be constructed with a {@link ContentResolver} or,
+ * otherwise have access to an implicit one. All the proxy methods in this class exclude
+ * {@link ContentResolver} from their signature and rely on an internally defined one instead.
+ *
+ * Most methods in the {@link Settings} classes have default implementations defined.
+ * Implementations of this interfac need only concern themselves with getting and putting Strings.
+ * They should also override any methods for a class they are proxying that _are not_ defined, and
+ * throw an appropriate {@link UnsupportedOperationException}. For instance, {@link Settings.Global}
+ * does not define {@link #putString(String, String, boolean)}, so an implementation of this
+ * interface that proxies through to it should throw an exception when that method is called.
+ *
+ * This class adds in the following helpers as well:
+ * - {@link #getBool(String)}
+ * - {@link #putBool(String, boolean)}
+ * - {@link #registerContentObserver(Uri, ContentObserver)}
+ *
+ * ... and similar variations for all of those.
+ */
+public interface SettingsProxy {
+
+ /**
+ * Returns the {@link ContentResolver} this instance uses.
+ */
+ ContentResolver getContentResolver();
+
+ /**
+ * Construct the content URI for a particular name/value pair,
+ * useful for monitoring changes with a ContentObserver.
+ * @param name to look up in the table
+ * @return the corresponding content URI, or null if not present
+ */
+ Uri getUriFor(String name);
+
+ /**See {@link Settings.Secure#getString(ContentResolver, String)} */
+ String getStringForUser(String name, int userHandle);
+
+ /**See {@link Settings.Secure#putString(ContentResolver, String, String, boolean)} */
+ boolean putString(String name, String value, boolean overrideableByRestore);
+
+ /** See {@link Settings.Secure#putStringForUser(ContentResolver, String, String, int)} */
+ boolean putStringForUser(String name, String value, int userHandle);
+
+ /**
+ * See {@link Settings.Secure#putStringForUser(ContentResolver, String, String, String, boolean,
+ * int, boolean)}
+ */
+ boolean putStringForUser(@NonNull String name, @Nullable String value, @Nullable String tag,
+ boolean makeDefault, @UserIdInt int userHandle, boolean overrideableByRestore);
+
+ /** See {@link Settings.Secure#putString(ContentResolver, String, String, String, boolean)} */
+ boolean putString(@NonNull String name, @Nullable String value, @Nullable String tag,
+ boolean makeDefault);
+
+ /**
+ * Returns the user id for the associated {@link ContentResolver}.
+ */
+ default int getUserId() {
+ return getContentResolver().getUserId();
+ }
+
+ /** See {@link Settings.Secure#getString(ContentResolver, String)} */
+ default String getString(String name) {
+ return getStringForUser(name, getUserId());
+ }
+
+ /** See {@link Settings.Secure#putString(ContentResolver, String, String)} */
+ default boolean putString(String name, String value) {
+ return putStringForUser(name, value, getUserId());
+ }
+ /** See {@link Settings.Secure#getIntForUser(ContentResolver, String, int, int)} */
+ default int getIntForUser(String name, int def, int userHandle) {
+ String v = getStringForUser(name, userHandle);
+ try {
+ return v != null ? Integer.parseInt(v) : def;
+ } catch (NumberFormatException e) {
+ return def;
+ }
+ }
+
+ /** See {@link Settings.Secure#getInt(ContentResolver, String)} */
+ default int getInt(String name) throws Settings.SettingNotFoundException {
+ return getIntForUser(name, getUserId());
+ }
+
+ /** See {@link Settings.Secure#getIntForUser(ContentResolver, String, int)} */
+ default int getIntForUser(String name, int userHandle)
+ throws Settings.SettingNotFoundException {
+ String v = getStringForUser(name, userHandle);
+ try {
+ return Integer.parseInt(v);
+ } catch (NumberFormatException e) {
+ throw new Settings.SettingNotFoundException(name);
+ }
+ }
+
+ /** See {@link Settings.Secure#putInt(ContentResolver, String, int)} */
+ default boolean putInt(String name, int value) {
+ return putIntForUser(name, value, getUserId());
+ }
+
+ /** See {@link Settings.Secure#putIntForUser(ContentResolver, String, int, int)} */
+ default boolean putIntForUser(String name, int value, int userHandle) {
+ return putStringForUser(name, Integer.toString(value), userHandle);
+ }
+
+ /**
+ * Convenience function for retrieving a single settings value
+ * as a boolean. Note that internally setting values are always
+ * stored as strings; this function converts the string to a boolean
+ * for you. The default value will be returned if the setting is
+ * not defined or not a boolean.
+ *
+ * @param name The name of the setting to retrieve.
+ * @param def Value to return if the setting is not defined.
+ *
+ * @return The setting's current value, or 'def' if it is not defined
+ * or not a valid boolean.
+ */
+ default boolean getBool(String name, boolean def) {
+ return getBoolForUser(name, def, getUserId());
+ }
+
+ /** See {@link #getBool(String, boolean)}. */
+ default boolean getBoolForUser(String name, boolean def, int userHandle) {
+ return getIntForUser(name, def ? 1 : 0, userHandle) != 0;
+ }
+
+ /**
+ * Convenience function for retrieving a single settings value
+ * as a boolean. Note that internally setting values are always
+ * stored as strings; this function converts the string to a boolean
+ * for you.
+ * <p>
+ * This version does not take a default value. If the setting has not
+ * been set, or the string value is not a number,
+ * it throws {@link Settings.SettingNotFoundException}.
+ *
+ * @param name The name of the setting to retrieve.
+ *
+ * @throws Settings.SettingNotFoundException Thrown if a setting by the given
+ * name can't be found or the setting value is not a boolean.
+ *
+ * @return The setting's current value.
+ */
+ default boolean getBool(String name) throws Settings.SettingNotFoundException {
+ return getBoolForUser(name, getUserId());
+ }
+
+ /** See {@link #getBool(String)}. */
+ default boolean getBoolForUser(String name, int userHandle)
+ throws Settings.SettingNotFoundException {
+ return getIntForUser(name, userHandle) != 0;
+ }
+
+ /**
+ * Convenience function for updating a single settings value as a
+ * boolean. This will either create a new entry in the table if the
+ * given name does not exist, or modify the value of the existing row
+ * with that name. Note that internally setting values are always
+ * stored as strings, so this function converts the given value to a
+ * string before storing it.
+ *
+ * @param name The name of the setting to modify.
+ * @param value The new value for the setting.
+ * @return true if the value was set, false on database errors
+ */
+ default boolean putBool(String name, boolean value) {
+ return putBoolForUser(name, value, getUserId());
+ }
+
+ /** See {@link #putBool(String, boolean)}. */
+ default boolean putBoolForUser(String name, boolean value, int userHandle) {
+ return putIntForUser(name, value ? 1 : 0, userHandle);
+ }
+
+ /** See {@link Settings.Secure#getLong(ContentResolver, String, long)} */
+ default long getLong(String name, long def) {
+ return getLongForUser(name, def, getUserId());
+ }
+
+ /** See {@link Settings.Secure#getLongForUser(ContentResolver, String, long, int)} */
+ default long getLongForUser(String name, long def, int userHandle) {
+ String valString = getStringForUser(name, userHandle);
+ long value;
+ try {
+ value = valString != null ? Long.parseLong(valString) : def;
+ } catch (NumberFormatException e) {
+ value = def;
+ }
+ return value;
+ }
+
+ /** See {@link Settings.Secure#getLong(ContentResolver, String)} */
+ default long getLong(String name) throws Settings.SettingNotFoundException {
+ return getLongForUser(name, getUserId());
+ }
+
+ /** See {@link Settings.Secure#getLongForUser(ContentResolver, String, int)} */
+ default long getLongForUser(String name, int userHandle)
+ throws Settings.SettingNotFoundException {
+ String valString = getStringForUser(name, userHandle);
+ try {
+ return Long.parseLong(valString);
+ } catch (NumberFormatException e) {
+ throw new Settings.SettingNotFoundException(name);
+ }
+ }
+
+ /** See {@link Settings.Secure#putLong(ContentResolver, String, long)} */
+ default boolean putLong(String name, long value) {
+ return putLongForUser(name, value, getUserId());
+ }
+
+ /** See {@link Settings.Secure#putLongForUser(ContentResolver, String, long, int)} */
+ default boolean putLongForUser(String name, long value, int userHandle) {
+ return putStringForUser(name, Long.toString(value), userHandle);
+ }
+
+ /** See {@link Settings.Secure#getFloat(ContentResolver, String, float)} */
+ default float getFloat(String name, float def) {
+ return getFloatForUser(name, def, getUserId());
+ }
+
+ /** See {@link Settings.Secure#getFloatForUser(ContentResolver, String, int)} */
+ default float getFloatForUser(String name, float def, int userHandle) {
+ String v = getStringForUser(name, userHandle);
+ try {
+ return v != null ? Float.parseFloat(v) : def;
+ } catch (NumberFormatException e) {
+ return def;
+ }
+ }
+
+
+ /** See {@link Settings.Secure#getFloat(ContentResolver, String)} */
+ default float getFloat(String name) throws Settings.SettingNotFoundException {
+ return getFloatForUser(name, getUserId());
+ }
+
+ /** See {@link Settings.Secure#getFloatForUser(ContentResolver, String, int)} */
+ default float getFloatForUser(String name, int userHandle)
+ throws Settings.SettingNotFoundException {
+ String v = getStringForUser(name, userHandle);
+ if (v == null) {
+ throw new Settings.SettingNotFoundException(name);
+ }
+ try {
+ return Float.parseFloat(v);
+ } catch (NumberFormatException e) {
+ throw new Settings.SettingNotFoundException(name);
+ }
+ }
+
+ /** See {@link Settings.Secure#putFloat(ContentResolver, String, float)} */
+ default boolean putFloat(String name, float value) {
+ return putFloatForUser(name, value, getUserId());
+ }
+
+ /** See {@link Settings.Secure#putFloatForUser(ContentResolver, String, float, int)} */
+ default boolean putFloatForUser(String name, float value, int userHandle) {
+ return putStringForUser(name, Float.toString(value), userHandle);
+ }
+
+ /**
+ * Convenience wrapper around
+ * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver)}.'
+ *
+ * Implicitly calls {@link #getUriFor(String)} on the passed in name.
+ */
+ default void registerContentObserver(String name, ContentObserver settingsObserver) {
+ registerContentObserver(getUriFor(name), settingsObserver);
+ }
+
+ /**
+ * Convenience wrapper around
+ * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver)}.'
+ */
+ default void registerContentObserver(Uri uri, ContentObserver settingsObserver) {
+ registerContentObserverForUser(uri, settingsObserver, getUserId());
+ }
+
+ /**
+ * Convenience wrapper around
+ * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver)}.
+ *
+ * Implicitly calls {@link #getUriFor(String)} on the passed in name.
+ */
+ default void registerContentObserver(String name, boolean notifyForDescendants,
+ ContentObserver settingsObserver) {
+ registerContentObserver(getUriFor(name), notifyForDescendants, settingsObserver);
+ }
+
+ /**
+ * Convenience wrapper around
+ * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver)}.'
+ */
+ default void registerContentObserver(Uri uri, boolean notifyForDescendants,
+ ContentObserver settingsObserver) {
+ registerContentObserverForUser(uri, notifyForDescendants, settingsObserver, getUserId());
+ }
+
+ /**
+ * Convenience wrapper around
+ * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver, int)}
+ *
+ * Implicitly calls {@link #getUriFor(String)} on the passed in name.
+ */
+ default void registerContentObserverForUser(
+ String name, ContentObserver settingsObserver, int userHandle) {
+ registerContentObserverForUser(
+ getUriFor(name), settingsObserver, userHandle);
+ }
+
+ /**
+ * Convenience wrapper around
+ * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver, int)}
+ */
+ default void registerContentObserverForUser(
+ Uri uri, ContentObserver settingsObserver, int userHandle) {
+ registerContentObserverForUser(
+ uri, false, settingsObserver, userHandle);
+ }
+
+ /**
+ * Convenience wrapper around
+ * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver, int)}
+ *
+ * Implicitly calls {@link #getUriFor(String)} on the passed in name.
+ */
+ default void registerContentObserverForUser(
+ String name, boolean notifyForDescendants, ContentObserver settingsObserver,
+ int userHandle) {
+ registerContentObserverForUser(
+ getUriFor(name), notifyForDescendants, settingsObserver, userHandle);
+ }
+
+ /**
+ * Convenience wrapper around
+ * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver, int)}
+ */
+ default void registerContentObserverForUser(
+ Uri uri, boolean notifyForDescendants, ContentObserver settingsObserver,
+ int userHandle) {
+ getContentResolver().registerContentObserver(
+ uri, notifyForDescendants, settingsObserver, userHandle);
+ }
+
+ /** See {@link ContentResolver#unregisterContentObserver(ContentObserver)}. */
+ default void unregisterContentObserver(ContentObserver settingsObserver) {
+ getContentResolver().unregisterContentObserver(settingsObserver);
+ }
+}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 2f2e1cb..ee4bc12 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -133,6 +133,7 @@
import com.android.server.display.color.ColorDisplayService;
import com.android.server.dreams.DreamManagerService;
import com.android.server.emergency.EmergencyAffordanceService;
+import com.android.server.flags.FeatureFlagsService;
import com.android.server.gpu.GpuService;
import com.android.server.grammaticalinflection.GrammaticalInflectionService;
import com.android.server.graphics.fonts.FontManagerService;
@@ -443,6 +444,10 @@
+ "OnDevicePersonalizationSystemService$Lifecycle";
private static final String UPDATABLE_DEVICE_CONFIG_SERVICE_CLASS =
"com.android.server.deviceconfig.DeviceConfigInit$Lifecycle";
+ private static final String DEVICE_LOCK_SERVICE_CLASS =
+ "com.android.server.devicelock.DeviceLockService";
+ private static final String DEVICE_LOCK_APEX_PATH =
+ "/apex/com.android.devicelock/javalib/service-devicelock.jar";
private static final String TETHERING_CONNECTOR_CLASS = "android.net.ITetheringConnector";
@@ -1113,6 +1118,12 @@
mSystemServiceManager.startService(DeviceIdentifiersPolicyService.class);
t.traceEnd();
+ // Starts a service for reading runtime flag overrides, and keeping processes
+ // in sync with one another.
+ t.traceBegin("StartFeatureFlagsService");
+ mSystemServiceManager.startService(FeatureFlagsService.class);
+ t.traceEnd();
+
// Uri Grants Manager.
t.traceBegin("UriGrantsManagerService");
mSystemServiceManager.startService(UriGrantsManagerService.Lifecycle.class);
@@ -2898,6 +2909,13 @@
mSystemServiceManager.startService(HEALTHCONNECT_MANAGER_SERVICE_CLASS);
t.traceEnd();
+ if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_DEVICE_LOCK)) {
+ t.traceBegin("DeviceLockService");
+ mSystemServiceManager.startServiceFromJar(DEVICE_LOCK_SERVICE_CLASS,
+ DEVICE_LOCK_APEX_PATH);
+ t.traceEnd();
+ }
+
// These are needed to propagate to the runnable below.
final NetworkManagementService networkManagementF = networkManagement;
final NetworkPolicyManagerService networkPolicyF = networkPolicy;
diff --git a/services/midi/java/com/android/server/midi/MidiService.java b/services/midi/java/com/android/server/midi/MidiService.java
index 4aba30a..5e7dd59 100644
--- a/services/midi/java/com/android/server/midi/MidiService.java
+++ b/services/midi/java/com/android/server/midi/MidiService.java
@@ -16,7 +16,9 @@
package com.android.server.midi;
+import android.Manifest;
import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothUuid;
import android.content.BroadcastReceiver;
@@ -48,6 +50,7 @@
import android.os.Process;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.os.UserManager;
import android.util.EventLog;
import android.util.Log;
@@ -81,6 +84,11 @@
// 2. synchronized (mDeviceConnections)
//TODO Introduce a single lock object to lock the whole state and avoid the requirement above.
+// All users should be able to connect to USB and Bluetooth MIDI devices.
+// All users can create can install an app that provides a Virtual MIDI Device Service.
+// Users can not open virtual MIDI devices created by other users.
+// getDevices() surfaces devices that can be opened by that user.
+// openDevice() rejects devices that are cannot be opened by that user.
public class MidiService extends IMidiManager.Stub {
public static class Lifecycle extends SystemService {
@@ -97,10 +105,21 @@
}
@Override
+ @RequiresPermission(allOf = {Manifest.permission.INTERACT_ACROSS_USERS},
+ anyOf = {Manifest.permission.QUERY_USERS,
+ Manifest.permission.CREATE_USERS,
+ Manifest.permission.MANAGE_USERS})
+ public void onUserStarting(@NonNull TargetUser user) {
+ mMidiService.onStartOrUnlockUser(user, false /* matchDirectBootUnaware */);
+ }
+
+ @Override
+ @RequiresPermission(allOf = {Manifest.permission.INTERACT_ACROSS_USERS},
+ anyOf = {Manifest.permission.QUERY_USERS,
+ Manifest.permission.CREATE_USERS,
+ Manifest.permission.MANAGE_USERS})
public void onUserUnlocking(@NonNull TargetUser user) {
- if (user.getUserIdentifier() == UserHandle.USER_SYSTEM) {
- mMidiService.onUnlockUser();
- }
+ mMidiService.onStartOrUnlockUser(user, true /* matchDirectBootUnaware */);
}
}
@@ -134,6 +153,7 @@
private int mNextDeviceId = 1;
private final PackageManager mPackageManager;
+ private final UserManager mUserManager;
private static final String MIDI_LEGACY_STRING = "MIDI 1.0";
private static final String MIDI_UNIVERSAL_STRING = "MIDI 2.0";
@@ -159,21 +179,24 @@
private final HashSet<ParcelUuid> mNonMidiUUIDs = new HashSet<ParcelUuid>();
// PackageMonitor for listening to package changes
+ // uid is the uid of the package so use getChangingUserId() to fetch the userId.
private final PackageMonitor mPackageMonitor = new PackageMonitor() {
@Override
+ @RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS)
public void onPackageAdded(String packageName, int uid) {
- addPackageDeviceServers(packageName);
+ addPackageDeviceServers(packageName, getChangingUserId());
}
@Override
+ @RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS)
public void onPackageModified(String packageName) {
- removePackageDeviceServers(packageName);
- addPackageDeviceServers(packageName);
+ removePackageDeviceServers(packageName, getChangingUserId());
+ addPackageDeviceServers(packageName, getChangingUserId());
}
@Override
public void onPackageRemoved(String packageName, int uid) {
- removePackageDeviceServers(packageName);
+ removePackageDeviceServers(packageName, getChangingUserId());
}
};
@@ -202,6 +225,10 @@
return mUid;
}
+ private int getUserId() {
+ return UserHandle.getUserId(mUid);
+ }
+
public void addListener(IMidiDeviceListener listener) {
if (mListeners.size() >= MAX_LISTENERS_PER_CLIENT) {
throw new SecurityException(
@@ -219,8 +246,12 @@
}
}
- public void addDeviceConnection(Device device, IMidiDeviceOpenCallback callback) {
- Log.d(TAG, "addDeviceConnection() device:" + device);
+ @RequiresPermission(anyOf = {Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+ Manifest.permission.INTERACT_ACROSS_USERS,
+ Manifest.permission.INTERACT_ACROSS_PROFILES})
+ public void addDeviceConnection(Device device, IMidiDeviceOpenCallback callback,
+ int userId) {
+ Log.d(TAG, "addDeviceConnection() device:" + device + " userId:" + userId);
if (mDeviceConnections.size() >= MAX_CONNECTIONS_PER_CLIENT) {
Log.i(TAG, "too many MIDI connections for UID = " + mUid);
throw new SecurityException(
@@ -228,7 +259,7 @@
}
DeviceConnection connection = new DeviceConnection(device, this, callback);
mDeviceConnections.put(connection.getToken(), connection);
- device.addDeviceConnection(connection);
+ device.addDeviceConnection(connection, userId);
}
// called from MidiService.closeDevice()
@@ -251,8 +282,8 @@
}
public void deviceAdded(Device device) {
- // ignore private devices that our client cannot access
- if (!device.isUidAllowed(mUid)) return;
+ // ignore devices that our client cannot access
+ if (!device.isUidAllowed(mUid) || !device.isUserIdAllowed(getUserId())) return;
MidiDeviceInfo deviceInfo = device.getDeviceInfo();
try {
@@ -265,8 +296,8 @@
}
public void deviceRemoved(Device device) {
- // ignore private devices that our client cannot access
- if (!device.isUidAllowed(mUid)) return;
+ // ignore devices that our client cannot access
+ if (!device.isUidAllowed(mUid) || !device.isUserIdAllowed(getUserId())) return;
MidiDeviceInfo deviceInfo = device.getDeviceInfo();
try {
@@ -279,8 +310,8 @@
}
public void deviceStatusChanged(Device device, MidiDeviceStatus status) {
- // ignore private devices that our client cannot access
- if (!device.isUidAllowed(mUid)) return;
+ // ignore devices that our client cannot access
+ if (!device.isUidAllowed(mUid) || !device.isUserIdAllowed(getUserId())) return;
try {
for (IMidiDeviceListener listener : mListeners.values()) {
@@ -354,6 +385,8 @@
private final ServiceInfo mServiceInfo;
// UID of device implementation
private final int mUid;
+ // User Id of the app. Only used for virtual devices
+ private final int mUserId;
// ServiceConnection for implementing Service (virtual devices only)
// mServiceConnection is non-null when connected or attempting to connect to the service
@@ -375,19 +408,24 @@
private AtomicInteger mTotalOutputBytes = new AtomicInteger();
public Device(IMidiDeviceServer server, MidiDeviceInfo deviceInfo,
- ServiceInfo serviceInfo, int uid) {
+ ServiceInfo serviceInfo, int uid, int userId) {
mDeviceInfo = deviceInfo;
mServiceInfo = serviceInfo;
mUid = uid;
+ mUserId = userId;
mBluetoothDevice = (BluetoothDevice)deviceInfo.getProperties().getParcelable(
MidiDeviceInfo.PROPERTY_BLUETOOTH_DEVICE, android.bluetooth.BluetoothDevice.class);;
setDeviceServer(server);
}
+ @RequiresPermission(anyOf = {Manifest.permission.QUERY_USERS,
+ Manifest.permission.CREATE_USERS,
+ Manifest.permission.MANAGE_USERS})
public Device(BluetoothDevice bluetoothDevice) {
mBluetoothDevice = bluetoothDevice;
mServiceInfo = null;
mUid = mBluetoothServiceUid;
+ mUserId = mUserManager.getMainUser().getIdentifier();
}
private void setDeviceServer(IMidiDeviceServer server) {
@@ -468,11 +506,22 @@
return mUid;
}
+ public int getUserId() {
+ return mUserId;
+ }
+
public boolean isUidAllowed(int uid) {
return (!mDeviceInfo.isPrivate() || mUid == uid);
}
- public void addDeviceConnection(DeviceConnection connection) {
+ public boolean isUserIdAllowed(int userId) {
+ return (mDeviceInfo.getType() != MidiDeviceInfo.TYPE_VIRTUAL || mUserId == userId);
+ }
+
+ @RequiresPermission(anyOf = {Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+ Manifest.permission.INTERACT_ACROSS_USERS,
+ Manifest.permission.INTERACT_ACROSS_PROFILES})
+ public void addDeviceConnection(DeviceConnection connection, int userId) {
Log.d(TAG, "addDeviceConnection() [A] connection:" + connection);
synchronized (mDeviceConnections) {
mDeviceConnectionsAdded.incrementAndGet();
@@ -537,8 +586,8 @@
new ComponentName(mServiceInfo.packageName, mServiceInfo.name));
}
- if (!mContext.bindService(intent, mServiceConnection,
- Context.BIND_AUTO_CREATE)) {
+ if (!mContext.bindServiceAsUser(intent, mServiceConnection,
+ Context.BIND_AUTO_CREATE, UserHandle.of(mUserId))) {
Log.e(TAG, "Unable to bind service: " + intent);
setDeviceServer(null);
mServiceConnection = null;
@@ -886,6 +935,8 @@
public MidiService(Context context) {
mContext = context;
mPackageManager = context.getPackageManager();
+ mUserManager = mContext.getSystemService(UserManager.class);
+ mPackageMonitor.register(mContext, null, UserHandle.ALL, true);
// TEMPORARY - Disable BTL-MIDI
//FIXME - b/25689266
@@ -913,32 +964,41 @@
// mNonMidiUUIDs.add(BluetoothUuid.BATTERY);
}
- private void onUnlockUser() {
- mPackageMonitor.register(mContext, null, true);
-
+ @RequiresPermission(allOf = {Manifest.permission.INTERACT_ACROSS_USERS},
+ anyOf = {Manifest.permission.QUERY_USERS,
+ Manifest.permission.CREATE_USERS,
+ Manifest.permission.MANAGE_USERS})
+ private void onStartOrUnlockUser(TargetUser user, boolean matchDirectBootUnaware) {
+ Log.d(TAG, "onStartOrUnlockUser " + user.getUserIdentifier() + " matchDirectBootUnaware: "
+ + matchDirectBootUnaware);
Intent intent = new Intent(MidiDeviceService.SERVICE_INTERFACE);
- List<ResolveInfo> resolveInfos = mPackageManager.queryIntentServices(intent,
- PackageManager.GET_META_DATA);
+ int resolveFlags = PackageManager.GET_META_DATA;
+ if (matchDirectBootUnaware) {
+ resolveFlags |= PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
+ }
+ List<ResolveInfo> resolveInfos = mPackageManager.queryIntentServicesAsUser(intent,
+ resolveFlags, user.getUserIdentifier());
if (resolveInfos != null) {
int count = resolveInfos.size();
for (int i = 0; i < count; i++) {
ServiceInfo serviceInfo = resolveInfos.get(i).serviceInfo;
if (serviceInfo != null) {
- addPackageDeviceServer(serviceInfo);
+ addPackageDeviceServer(serviceInfo, user.getUserIdentifier());
}
}
}
- PackageInfo info;
- try {
- info = mPackageManager.getPackageInfo(MidiManager.BLUETOOTH_MIDI_SERVICE_PACKAGE, 0);
- } catch (PackageManager.NameNotFoundException e) {
- info = null;
- }
- if (info != null && info.applicationInfo != null) {
- mBluetoothServiceUid = info.applicationInfo.uid;
- } else {
- mBluetoothServiceUid = -1;
+ if (user.getUserIdentifier() == mUserManager.getMainUser().getIdentifier()) {
+ PackageInfo info;
+ try {
+ info = mPackageManager.getPackageInfoAsUser(
+ MidiManager.BLUETOOTH_MIDI_SERVICE_PACKAGE, 0, user.getUserIdentifier());
+ } catch (PackageManager.NameNotFoundException e) {
+ info = null;
+ }
+ if (info != null && info.applicationInfo != null) {
+ mBluetoothServiceUid = info.applicationInfo.uid;
+ }
}
}
@@ -960,10 +1020,11 @@
// Inform listener of the status of all known devices.
private void updateStickyDeviceStatus(int uid, IMidiDeviceListener listener) {
+ int userId = UserHandle.getUserId(uid);
synchronized (mDevicesByInfo) {
for (Device device : mDevicesByInfo.values()) {
- // ignore private devices that our client cannot access
- if (device.isUidAllowed(uid)) {
+ // ignore devices that our client cannot access
+ if (device.isUidAllowed(uid) && device.isUserIdAllowed(userId)) {
try {
MidiDeviceStatus status = device.getDeviceStatus();
if (status != null) {
@@ -989,10 +1050,11 @@
public MidiDeviceInfo[] getDevicesForTransport(int transport) {
ArrayList<MidiDeviceInfo> deviceInfos = new ArrayList<MidiDeviceInfo>();
int uid = Binder.getCallingUid();
+ int userId = getCallingUserId();
synchronized (mDevicesByInfo) {
for (Device device : mDevicesByInfo.values()) {
- if (device.isUidAllowed(uid)) {
+ if (device.isUidAllowed(uid) && device.isUserIdAllowed(userId)) {
// UMP devices have protocols that are not PROTOCOL_UNKNOWN
if (transport == MidiManager.TRANSPORT_UNIVERSAL_MIDI_PACKETS) {
if (device.getDeviceInfo().getDefaultProtocol()
@@ -1029,6 +1091,9 @@
if (!device.isUidAllowed(Binder.getCallingUid())) {
throw new SecurityException("Attempt to open private device with wrong UID");
}
+ if (!device.isUserIdAllowed(getCallingUserId())) {
+ throw new SecurityException("Attempt to open virtual device with wrong user id");
+ }
}
if (deviceInfo.getType() == MidiDeviceInfo.TYPE_USB) {
@@ -1044,7 +1109,7 @@
final long identity = Binder.clearCallingIdentity();
try {
Log.i(TAG, "addDeviceConnection() [B] device:" + device);
- client.addDeviceConnection(device, callback);
+ client.addDeviceConnection(device, callback, getCallingUserId());
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -1106,7 +1171,7 @@
final long identity = Binder.clearCallingIdentity();
try {
Log.i(TAG, "addDeviceConnection() [C] device:" + device);
- client.addDeviceConnection(device, callback);
+ client.addDeviceConnection(device, callback, getCallingUserId());
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -1124,6 +1189,7 @@
int numOutputPorts, String[] inputPortNames, String[] outputPortNames,
Bundle properties, int type, int defaultProtocol) {
int uid = Binder.getCallingUid();
+ int userId = getCallingUserId();
if (type == MidiDeviceInfo.TYPE_USB && uid != Process.SYSTEM_UID) {
throw new SecurityException("only system can create USB devices");
} else if (type == MidiDeviceInfo.TYPE_BLUETOOTH && uid != mBluetoothServiceUid) {
@@ -1133,7 +1199,7 @@
synchronized (mDevicesByInfo) {
return addDeviceLocked(type, numInputPorts, numOutputPorts, inputPortNames,
outputPortNames, properties, server, null, false, uid,
- defaultProtocol);
+ defaultProtocol, userId);
}
}
@@ -1210,7 +1276,8 @@
private MidiDeviceInfo addDeviceLocked(int type, int numInputPorts, int numOutputPorts,
String[] inputPortNames, String[] outputPortNames, Bundle properties,
IMidiDeviceServer server, ServiceInfo serviceInfo,
- boolean isPrivate, int uid, int defaultProtocol) {
+ boolean isPrivate, int uid, int defaultProtocol, int userId) {
+ Log.d(TAG, "addDeviceLocked()" + uid + " type:" + type);
// Limit the number of devices per app.
int deviceCountForApp = 0;
@@ -1250,7 +1317,7 @@
}
}
if (device == null) {
- device = new Device(server, deviceInfo, serviceInfo, uid);
+ device = new Device(server, deviceInfo, serviceInfo, uid, userId);
}
mDevicesByInfo.put(deviceInfo, device);
if (bluetoothDevice != null) {
@@ -1281,12 +1348,14 @@
}
}
- private void addPackageDeviceServers(String packageName) {
+ @RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS)
+ private void addPackageDeviceServers(String packageName, int userId) {
PackageInfo info;
try {
- info = mPackageManager.getPackageInfo(packageName,
- PackageManager.GET_SERVICES | PackageManager.GET_META_DATA);
+ info = mPackageManager.getPackageInfoAsUser(packageName,
+ PackageManager.GET_SERVICES | PackageManager.GET_META_DATA
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userId);
} catch (PackageManager.NameNotFoundException e) {
Log.e(TAG, "handlePackageUpdate could not find package " + packageName, e);
return;
@@ -1295,13 +1364,14 @@
ServiceInfo[] services = info.services;
if (services == null) return;
for (int i = 0; i < services.length; i++) {
- addPackageDeviceServer(services[i]);
+ addPackageDeviceServer(services[i], userId);
}
}
private static final String[] EMPTY_STRING_ARRAY = new String[0];
- private void addPackageDeviceServer(ServiceInfo serviceInfo) {
+ private void addPackageDeviceServer(ServiceInfo serviceInfo, int userId) {
+ Log.d(TAG, "addPackageDeviceServer()" + userId);
XmlResourceParser parser = null;
try {
@@ -1404,8 +1474,8 @@
int uid;
try {
- ApplicationInfo appInfo = mPackageManager.getApplicationInfo(
- serviceInfo.packageName, 0);
+ ApplicationInfo appInfo = mPackageManager.getApplicationInfoAsUser(
+ serviceInfo.packageName, 0, userId);
uid = appInfo.uid;
} catch (PackageManager.NameNotFoundException e) {
Log.e(TAG, "could not fetch ApplicationInfo for "
@@ -1419,7 +1489,7 @@
inputPortNames.toArray(EMPTY_STRING_ARRAY),
outputPortNames.toArray(EMPTY_STRING_ARRAY),
properties, null, serviceInfo, isPrivate, uid,
- MidiDeviceInfo.PROTOCOL_UNKNOWN);
+ MidiDeviceInfo.PROTOCOL_UNKNOWN, userId);
}
// setting properties to null signals that we are no longer
// processing a <device>
@@ -1437,12 +1507,13 @@
}
}
- private void removePackageDeviceServers(String packageName) {
+ private void removePackageDeviceServers(String packageName, int userId) {
synchronized (mDevicesByInfo) {
Iterator<Device> iterator = mDevicesByInfo.values().iterator();
while (iterator.hasNext()) {
Device device = iterator.next();
- if (packageName.equals(device.getPackageName())) {
+ if (packageName.equals(device.getPackageName())
+ && (device.getUserId() == userId)) {
iterator.remove();
removeDeviceLocked(device);
}
@@ -1571,4 +1642,11 @@
String extractUsbDeviceTag(String propertyName) {
return propertyName.substring(propertyName.length() - MIDI_LEGACY_STRING.length());
}
+
+ /**
+ * @return the user id of the calling user.
+ */
+ private int getCallingUserId() {
+ return UserHandle.getUserId(Binder.getCallingUid());
+ }
}
diff --git a/services/people/java/com/android/server/people/PeopleService.java b/services/people/java/com/android/server/people/PeopleService.java
index 292320e..885ed35 100644
--- a/services/people/java/com/android/server/people/PeopleService.java
+++ b/services/people/java/com/android/server/people/PeopleService.java
@@ -63,9 +63,8 @@
private static final String TAG = "PeopleService";
- private DataManager mDataManager;
- @VisibleForTesting
- ConversationListenerHelper mConversationListenerHelper;
+ private DataManager mLazyDataManager;
+ private ConversationListenerHelper mLazyConversationListenerHelper;
private PackageManagerInternal mPackageManagerInternal;
@@ -76,17 +75,30 @@
*/
public PeopleService(Context context) {
super(context);
-
- mDataManager = new DataManager(context);
- mConversationListenerHelper = new ConversationListenerHelper();
- mDataManager.addConversationsListener(mConversationListenerHelper);
}
- @Override
- public void onBootPhase(int phase) {
- if (phase == PHASE_SYSTEM_SERVICES_READY) {
- mDataManager.initialize();
+ @VisibleForTesting
+ ConversationListenerHelper getConversationListenerHelper() {
+ if (mLazyConversationListenerHelper == null) {
+ initLazyStuff();
}
+ return mLazyConversationListenerHelper;
+ }
+
+ private synchronized void initLazyStuff() {
+ if (mLazyDataManager == null) {
+ mLazyDataManager = new DataManager(getContext());
+ mLazyDataManager.initialize();
+ mLazyConversationListenerHelper = new ConversationListenerHelper();
+ mLazyDataManager.addConversationsListener(mLazyConversationListenerHelper);
+ }
+ }
+
+ private DataManager getDataManager() {
+ if (mLazyDataManager == null) {
+ initLazyStuff();
+ }
+ return mLazyDataManager;
}
@Override
@@ -105,12 +117,12 @@
@Override
public void onUserUnlocked(@NonNull TargetUser user) {
- mDataManager.onUserUnlocked(user.getUserIdentifier());
+ getDataManager().onUserUnlocked(user.getUserIdentifier());
}
@Override
public void onUserStopping(@NonNull TargetUser user) {
- mDataManager.onUserStopping(user.getUserIdentifier());
+ getDataManager().onUserStopping(user.getUserIdentifier());
}
/**
@@ -171,28 +183,28 @@
public ConversationChannel getConversation(
String packageName, int userId, String shortcutId) {
enforceSystemRootOrSystemUI(getContext(), "get conversation");
- return mDataManager.getConversation(packageName, userId, shortcutId);
+ return getDataManager().getConversation(packageName, userId, shortcutId);
}
@Override
public ParceledListSlice<ConversationChannel> getRecentConversations() {
enforceSystemRootOrSystemUI(getContext(), "get recent conversations");
return new ParceledListSlice<>(
- mDataManager.getRecentConversations(
+ getDataManager().getRecentConversations(
Binder.getCallingUserHandle().getIdentifier()));
}
@Override
public void removeRecentConversation(String packageName, int userId, String shortcutId) {
enforceSystemOrRoot("remove a recent conversation");
- mDataManager.removeRecentConversation(packageName, userId, shortcutId,
+ getDataManager().removeRecentConversation(packageName, userId, shortcutId,
Binder.getCallingUserHandle().getIdentifier());
}
@Override
public void removeAllRecentConversations() {
enforceSystemOrRoot("remove all recent conversations");
- mDataManager.removeAllRecentConversations(
+ getDataManager().removeAllRecentConversations(
Binder.getCallingUserHandle().getIdentifier());
}
@@ -200,7 +212,7 @@
public boolean isConversation(String packageName, int userId, String shortcutId) {
enforceHasReadPeopleDataPermission();
handleIncomingUser(userId);
- return mDataManager.isConversation(packageName, userId, shortcutId);
+ return getDataManager().isConversation(packageName, userId, shortcutId);
}
private void enforceHasReadPeopleDataPermission() throws SecurityException {
@@ -213,7 +225,7 @@
@Override
public long getLastInteraction(String packageName, int userId, String shortcutId) {
enforceSystemRootOrSystemUI(getContext(), "get last interaction");
- return mDataManager.getLastInteraction(packageName, userId, shortcutId);
+ return getDataManager().getLastInteraction(packageName, userId, shortcutId);
}
@Override
@@ -224,7 +236,7 @@
if (status.getStartTimeMillis() > System.currentTimeMillis()) {
throw new IllegalArgumentException("Start time must be in the past");
}
- mDataManager.addOrUpdateStatus(packageName, userId, conversationId, status);
+ getDataManager().addOrUpdateStatus(packageName, userId, conversationId, status);
}
@Override
@@ -232,14 +244,14 @@
String statusId) {
handleIncomingUser(userId);
checkCallerIsSameApp(packageName);
- mDataManager.clearStatus(packageName, userId, conversationId, statusId);
+ getDataManager().clearStatus(packageName, userId, conversationId, statusId);
}
@Override
public void clearStatuses(String packageName, int userId, String conversationId) {
handleIncomingUser(userId);
checkCallerIsSameApp(packageName);
- mDataManager.clearStatuses(packageName, userId, conversationId);
+ getDataManager().clearStatuses(packageName, userId, conversationId);
}
@Override
@@ -250,21 +262,21 @@
checkCallerIsSameApp(packageName);
}
return new ParceledListSlice<>(
- mDataManager.getStatuses(packageName, userId, conversationId));
+ getDataManager().getStatuses(packageName, userId, conversationId));
}
@Override
public void registerConversationListener(
String packageName, int userId, String shortcutId, IConversationListener listener) {
enforceSystemRootOrSystemUI(getContext(), "register conversation listener");
- mConversationListenerHelper.addConversationListener(
+ getConversationListenerHelper().addConversationListener(
new ListenerKey(packageName, userId, shortcutId), listener);
}
@Override
public void unregisterConversationListener(IConversationListener listener) {
enforceSystemRootOrSystemUI(getContext(), "unregister conversation listener");
- mConversationListenerHelper.removeConversationListener(listener);
+ getConversationListenerHelper().removeConversationListener(listener);
}
};
@@ -393,7 +405,7 @@
public void onCreatePredictionSession(AppPredictionContext appPredictionContext,
AppPredictionSessionId sessionId) {
mSessions.put(sessionId,
- new SessionInfo(appPredictionContext, mDataManager, sessionId.getUserId(),
+ new SessionInfo(appPredictionContext, getDataManager(), sessionId.getUserId(),
getContext()));
}
@@ -448,18 +460,18 @@
@Override
public void pruneDataForUser(@UserIdInt int userId, @NonNull CancellationSignal signal) {
- mDataManager.pruneDataForUser(userId, signal);
+ getDataManager().pruneDataForUser(userId, signal);
}
@Nullable
@Override
public byte[] getBackupPayload(@UserIdInt int userId) {
- return mDataManager.getBackupPayload(userId);
+ return getDataManager().getBackupPayload(userId);
}
@Override
public void restore(@UserIdInt int userId, @NonNull byte[] payload) {
- mDataManager.restore(userId, payload);
+ getDataManager().restore(userId, payload);
}
@VisibleForTesting
diff --git a/services/permission/TEST_MAPPING b/services/permission/TEST_MAPPING
new file mode 100644
index 0000000..579d4e3
--- /dev/null
+++ b/services/permission/TEST_MAPPING
@@ -0,0 +1,71 @@
+{
+ "presubmit": [
+ {
+ "name": "CtsPermissionTestCases",
+ "options": [
+ {
+ "include-filter": "android.permission.cts.BackgroundPermissionsTest"
+ },
+ {
+ "include-filter": "android.permission.cts.SplitPermissionTest"
+ },
+ {
+ "include-filter": "android.permission.cts.PermissionFlagsTest"
+ },
+ {
+ "include-filter": "android.permission.cts.SharedUidPermissionsTest"
+ }
+ ]
+ },
+ {
+ "name": "CtsAppSecurityHostTestCases",
+ "options": [
+ {
+ "include-filter": "android.appsecurity.cts.AppSecurityTests#rebootWithDuplicatePermission"
+ }
+ ]
+ },
+ {
+ "name": "CtsPermissionPolicyTestCases",
+ "options": [
+ {
+ "include-filter": "android.permissionpolicy.cts.RestrictedPermissionsTest"
+ },
+ {
+ "include-filter": "android.permission.cts.PermissionMaxSdkVersionTest"
+ }
+ ]
+ },
+ {
+ "name": "CtsStatsdHostTestCases",
+ "options": [
+ {
+ "include-filter": "android.cts.statsd.atom.UidAtomTests#testDangerousPermissionState"
+ }
+ ]
+ }
+ ],
+ "postsubmit": [
+ {
+ "name": "CtsAppSecurityHostTestCases",
+ "options": [
+ {
+ "include-filter": "android.appsecurity.cts.AppSecurityTests#testPermissionDiffCert"
+ }
+ ]
+ },
+ {
+ "name": "CtsPermissionTestCases",
+ "options": [
+ {
+ "include-filter": "android.permission.cts.PermissionUpdateListenerTest"
+ }
+ ]
+ }
+ ],
+ "imports": [
+ {
+ "path": "vendor/xts/gts-tests/tests/permission"
+ }
+ ]
+}
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
index 0ed863e..ffd7332 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
@@ -126,6 +126,7 @@
protected InputMethodManagerService mInputMethodManagerService;
protected ServiceThread mServiceThread;
protected boolean mIsLargeScreen;
+ private InputManagerGlobal.TestSession mInputManagerGlobalSession;
@BeforeClass
public static void setupClass() {
@@ -186,7 +187,7 @@
// Injecting and mocked InputMethodBindingController and InputMethod.
mMockInputMethodInvoker = IInputMethodInvoker.create(mMockInputMethod);
- InputManagerGlobal.resetInstance(mMockIInputManager);
+ mInputManagerGlobalSession = InputManagerGlobal.createTestSession(mMockIInputManager);
synchronized (ImfLock.class) {
when(mMockInputMethodBindingController.getCurMethod())
.thenReturn(mMockInputMethodInvoker);
@@ -262,6 +263,10 @@
if (mMockingSession != null) {
mMockingSession.finishMocking();
}
+
+ if (mInputManagerGlobalSession != null) {
+ mInputManagerGlobalSession.close();
+ }
LocalServices.removeServiceForTest(InputMethodManagerInternal.class);
}
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
index 16fb012..dc2fbfc 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
@@ -68,6 +68,7 @@
import com.android.server.pm.parsing.pkg.ParsedPackage;
import com.android.server.pm.permission.LegacyPermissionDataProvider;
import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.ArchiveState;
import com.android.server.pm.pkg.PackageUserState;
import com.android.server.pm.pkg.PackageUserStateInternal;
import com.android.server.pm.pkg.SuspendParams;
@@ -864,6 +865,34 @@
}
@Test
+ public void testWriteReadArchiveState() {
+ Settings settings = makeSettings();
+ PackageSetting packageSetting = createPackageSetting(PACKAGE_NAME_1);
+ packageSetting.setAppId(Process.FIRST_APPLICATION_UID);
+ packageSetting.setPkg(PackageImpl.forTesting(PACKAGE_NAME_1).hideAsParsed()
+ .setUid(packageSetting.getAppId())
+ .hideAsFinal());
+
+ ArchiveState archiveState = new ArchiveState(
+ List.of(new ArchiveState.ArchiveActivityInfo("title1", Path.of("/path1"),
+ Path.of("/monochromePath1")),
+ new ArchiveState.ArchiveActivityInfo("title2", Path.of("/path2"),
+ Path.of("/monochromePath2"))),
+ "installerTitle");
+ packageSetting.modifyUserState(UserHandle.SYSTEM.getIdentifier()).setArchiveState(
+ archiveState);
+ settings.mPackages.put(PACKAGE_NAME_1, packageSetting);
+
+ settings.writeLPr(computer, /*sync=*/true);
+ settings.mPackages.clear();
+
+ assertThat(settings.readLPw(computer, createFakeUsers()), is(true));
+
+ Truth.assertThat(settings.getPackageLPr(PACKAGE_NAME_1).getStateForUser(
+ UserHandle.SYSTEM).getArchiveState()).isEqualTo(archiveState);
+ }
+
+ @Test
public void testPackageRestrictionsDistractionFlagsDefault() {
final PackageSetting defaultSetting = createPackageSetting(PACKAGE_NAME_1);
assertThat(defaultSetting.getDistractionFlags(0), is(PackageManager.RESTRICTION_NONE));
@@ -971,7 +1000,7 @@
origPkgSetting01.setUserState(0, 100, 1, true, false, false, false, 0, null, false,
false, "lastDisabledCaller", new ArraySet<>(new String[]{"enabledComponent1"}),
new ArraySet<>(new String[]{"disabledComponent1"}), 0, 0, "harmfulAppWarning",
- "splashScreenTheme", 1000L, PackageManager.USER_MIN_ASPECT_RATIO_UNSET);
+ "splashScreenTheme", 1000L, PackageManager.USER_MIN_ASPECT_RATIO_UNSET, null);
final PersistableBundle appExtras1 = createPersistableBundle(
PACKAGE_NAME_1, 1L, 0.01, true, "appString1");
final PersistableBundle launcherExtras1 = createPersistableBundle(
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageUserStateTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageUserStateTest.java
index 9ad503c..58ae740 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageUserStateTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageUserStateTest.java
@@ -35,6 +35,7 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.server.pm.pkg.ArchiveState;
import com.android.server.pm.pkg.PackageStateUnserialized;
import com.android.server.pm.pkg.PackageUserStateImpl;
import com.android.server.pm.pkg.SuspendParams;
@@ -44,6 +45,9 @@
import org.junit.runner.RunWith;
import org.mockito.Mockito;
+import java.nio.file.Path;
+import java.util.List;
+
@Presubmit
@RunWith(AndroidJUnit4.class)
@SmallTest
@@ -394,4 +398,14 @@
assertFalse(testState.setSharedLibraryOverlayPaths(LIB_ONE, null));
}
+ @Test
+ public void archiveState() {
+ PackageUserStateImpl packageUserState = new PackageUserStateImpl();
+ ArchiveState.ArchiveActivityInfo archiveActivityInfo = new ArchiveState.ArchiveActivityInfo(
+ "appTitle", Path.of("/path1"), Path.of("/path2"));
+ ArchiveState archiveState = new ArchiveState(List.of(archiveActivityInfo),
+ "installerTitle");
+ packageUserState.setArchiveState(archiveState);
+ assertEquals(archiveState, packageUserState.getArchiveState());
+ }
}
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ScanTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ScanTests.java
index b5bd869..e2939c1 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ScanTests.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ScanTests.java
@@ -21,6 +21,7 @@
import static android.content.pm.SharedLibraryInfo.TYPE_STATIC;
import static android.content.pm.SharedLibraryInfo.VERSION_UNDEFINED;
+import static com.android.server.pm.PackageManagerService.SCAN_AS_APEX;
import static com.android.server.pm.PackageManagerService.SCAN_AS_FULL_APP;
import static com.android.server.pm.PackageManagerService.SCAN_AS_INSTANT_APP;
import static com.android.server.pm.PackageManagerService.SCAN_FIRST_BOOT_OR_UPGRADE;
@@ -365,6 +366,46 @@
}
@Test
+ public void scanFirstBoot_apexDontDeriveAbis() throws Exception {
+ final PackageSetting pkgSetting =
+ createBasicPackageSettingBuilder(DUMMY_PACKAGE_NAME)
+ .setPkgFlags(ApplicationInfo.FLAG_SYSTEM).build();
+
+ final String codePath = "/data/apex/" + DUMMY_PACKAGE_NAME + ".apex";
+
+ // Create the ParsedPackage for the apex
+ final ParsedPackage basicPackage =
+ ((ParsedPackage) new PackageImpl(DUMMY_PACKAGE_NAME, codePath, codePath,
+ mock(TypedArray.class), false)
+ .setVolumeUuid(UUID_ONE.toString())
+ .hideAsParsed())
+ .setVersionCodeMajor(1)
+ .setVersionCode(2345)
+ .setSystem(true);
+
+ when(mMockInjector.getAbiHelper()).thenReturn(new PackageAbiHelperImpl());
+
+ // prepare the scan request with the scanflag (SCAN_FIRST_BOOT_OR_UPGRADE | SCAN_AS_APEX)
+ final ScanResult scanResult = executeScan(new ScanRequestBuilder(basicPackage)
+ .setPkgSetting(pkgSetting)
+ .addScanFlag(SCAN_FIRST_BOOT_OR_UPGRADE | SCAN_AS_APEX)
+ .build());
+
+ final PackageSetting resultSetting = scanResult.mPkgSetting;
+ final ApplicationInfo applicationInfo = PackageInfoUtils.generateApplicationInfo(
+ resultSetting.getPkg(), 0, pkgSetting.getUserStateOrDefault(0), 0, resultSetting);
+ assertThat(applicationInfo.primaryCpuAbi, nullValue());
+ assertThat(applicationInfo.primaryCpuAbi, nullValue());
+ assertThat(applicationInfo.secondaryCpuAbi, nullValue());
+
+ assertThat(applicationInfo.nativeLibraryRootDir, nullValue());
+ assertThat(pkgSetting.getLegacyNativeLibraryPath(), nullValue());
+ assertThat(applicationInfo.nativeLibraryRootRequiresIsa, is(false));
+ assertThat(applicationInfo.nativeLibraryDir, nullValue());
+ assertThat(applicationInfo.secondaryNativeLibraryDir, nullValue());
+ }
+
+ @Test
public void scanFirstBoot_derivesAbis() throws Exception {
final PackageSetting pkgSetting =
createBasicPackageSettingBuilder(DUMMY_PACKAGE_NAME).build();
diff --git a/services/tests/displayservicetests/Android.bp b/services/tests/displayservicetests/Android.bp
index f1ff338..4f555d9 100644
--- a/services/tests/displayservicetests/Android.bp
+++ b/services/tests/displayservicetests/Android.bp
@@ -28,14 +28,15 @@
static_libs: [
"androidx.test.ext.junit",
- "display-core-libs",
"frameworks-base-testutils",
"junit",
"junit-params",
+ "mockingservicestests-utils-mockito",
"platform-compat-test-rules",
"platform-test-annotations",
"services.core",
"servicestests-utils",
+ "testables",
],
defaults: [
@@ -56,10 +57,3 @@
enabled: false,
},
}
-
-java_library {
- name: "display-core-libs",
- srcs: [
- "src/com/android/server/display/TestUtils.java",
- ],
-}
diff --git a/services/tests/displayservicetests/AndroidManifest.xml b/services/tests/displayservicetests/AndroidManifest.xml
index d2bd10d..55fde00 100644
--- a/services/tests/displayservicetests/AndroidManifest.xml
+++ b/services/tests/displayservicetests/AndroidManifest.xml
@@ -17,10 +17,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.frameworks.displayservicetests">
- <!--
- Insert permissions here. eg:
- <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
- -->
+ <!-- Permissions -->
<uses-permission android:name="android.permission.CONTROL_DISPLAY_BRIGHTNESS" />
<uses-permission android:name="android.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS" />
<uses-permission android:name="android.permission.DEVICE_POWER" />
@@ -32,6 +29,10 @@
<uses-permission android:name="android.permission.READ_DEVICE_CONFIG" />
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
+ <!-- Permissions needed for DisplayTransformManagerTest -->
+ <uses-permission android:name="android.permission.CHANGE_CONFIGURATION" />
+ <uses-permission android:name="android.permission.HARDWARE_TEST"/>
+
<application android:debuggable="true"
android:testOnly="true">
<uses-library android:name="android.test.mock" android:required="true" />
diff --git a/services/tests/displayservicetests/src/com/android/server/display/OWNERS b/services/tests/displayservicetests/OWNERS
similarity index 100%
rename from services/tests/displayservicetests/src/com/android/server/display/OWNERS
rename to services/tests/displayservicetests/OWNERS
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/BrightnessSynchronizerTest.java b/services/tests/displayservicetests/src/com/android/server/display/BrightnessSynchronizerTest.java
similarity index 100%
rename from services/tests/mockingservicestests/src/com/android/server/display/BrightnessSynchronizerTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/BrightnessSynchronizerTest.java
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DensityMappingTest.java b/services/tests/displayservicetests/src/com/android/server/display/DensityMappingTest.java
similarity index 100%
rename from services/tests/mockingservicestests/src/com/android/server/display/DensityMappingTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/DensityMappingTest.java
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java
new file mode 100644
index 0000000..95c62ae
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java
@@ -0,0 +1,92 @@
+/*
+ * 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.display;
+
+import static org.junit.Assert.assertEquals;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.display.brightness.BrightnessReason;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class DisplayBrightnessStateTest {
+ private static final float FLOAT_DELTA = 0.001f;
+
+ private DisplayBrightnessState.Builder mDisplayBrightnessStateBuilder;
+
+ @Before
+ public void before() {
+ mDisplayBrightnessStateBuilder = new DisplayBrightnessState.Builder();
+ }
+
+ @Test
+ public void validateAllDisplayBrightnessStateFieldsAreSetAsExpected() {
+ float brightness = 0.3f;
+ float sdrBrightness = 0.2f;
+ boolean shouldUseAutoBrightness = true;
+ BrightnessReason brightnessReason = new BrightnessReason();
+ brightnessReason.setReason(BrightnessReason.REASON_AUTOMATIC);
+ brightnessReason.setModifier(BrightnessReason.MODIFIER_DIMMED);
+ DisplayBrightnessState displayBrightnessState = mDisplayBrightnessStateBuilder
+ .setBrightness(brightness)
+ .setSdrBrightness(sdrBrightness)
+ .setBrightnessReason(brightnessReason)
+ .setShouldUseAutoBrightness(shouldUseAutoBrightness)
+ .build();
+
+ assertEquals(displayBrightnessState.getBrightness(), brightness, FLOAT_DELTA);
+ assertEquals(displayBrightnessState.getSdrBrightness(), sdrBrightness, FLOAT_DELTA);
+ assertEquals(displayBrightnessState.getBrightnessReason(), brightnessReason);
+ assertEquals(displayBrightnessState.getShouldUseAutoBrightness(), shouldUseAutoBrightness);
+ assertEquals(displayBrightnessState.toString(), getString(displayBrightnessState));
+ }
+
+ @Test
+ public void testFrom() {
+ BrightnessReason reason = new BrightnessReason();
+ reason.setReason(BrightnessReason.REASON_MANUAL);
+ reason.setModifier(BrightnessReason.MODIFIER_DIMMED);
+ DisplayBrightnessState state1 = new DisplayBrightnessState.Builder()
+ .setBrightnessReason(reason)
+ .setBrightness(0.26f)
+ .setSdrBrightness(0.23f)
+ .setShouldUseAutoBrightness(false)
+ .build();
+ DisplayBrightnessState state2 = DisplayBrightnessState.Builder.from(state1).build();
+ assertEquals(state1, state2);
+ }
+
+ private String getString(DisplayBrightnessState displayBrightnessState) {
+ StringBuilder sb = new StringBuilder();
+ sb.append("DisplayBrightnessState:")
+ .append("\n brightness:")
+ .append(displayBrightnessState.getBrightness())
+ .append("\n sdrBrightness:")
+ .append(displayBrightnessState.getSdrBrightness())
+ .append("\n brightnessReason:")
+ .append(displayBrightnessState.getBrightnessReason())
+ .append("\n shouldUseAutoBrightness:")
+ .append(displayBrightnessState.getShouldUseAutoBrightness());
+ return sb.toString();
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java
similarity index 98%
rename from services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
rename to services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java
index b5f03ba..8a9a851 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java
@@ -121,7 +121,8 @@
private PowerManager mPowerManagerMock;
@Mock
private ColorDisplayService.ColorDisplayServiceInternal mCdsiMock;
-
+ @Mock
+ private DisplayWhiteBalanceController mDisplayWhiteBalanceControllerMock;
@Captor
private ArgumentCaptor<SensorEventListener> mSensorEventListenerCaptor;
@@ -714,6 +715,8 @@
Settings.System.SCREEN_BRIGHTNESS_MODE,
Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+ when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_OFF);
+
DisplayPowerRequest dpr = new DisplayPowerRequest();
dpr.policy = DisplayPowerRequest.POLICY_OFF;
mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
@@ -751,6 +754,7 @@
Settings.System.SCREEN_BRIGHTNESS_MODE,
Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+ when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_DOZE);
DisplayPowerRequest dpr = new DisplayPowerRequest();
dpr.policy = DisplayPowerRequest.POLICY_DOZE;
mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
@@ -1096,6 +1100,18 @@
verify(mHolder.brightnessSetting).setBrightness(clampedBrightness);
}
+ @Test
+ public void testDwbcCallsHappenOnHandler() {
+ mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
+
+ mHolder.dpc.setAutomaticScreenBrightnessMode(true);
+ verify(mDisplayWhiteBalanceControllerMock, never()).setStrongModeEnabled(true);
+
+ // dispatch handler looper
+ advanceTime(1);
+ verify(mDisplayWhiteBalanceControllerMock, times(1)).setStrongModeEnabled(true);
+ }
+
/**
* Creates a mock and registers it to {@link LocalServices}.
*/
@@ -1385,5 +1401,11 @@
Context context) {
return mHighBrightnessModeController;
}
+
+ @Override
+ DisplayWhiteBalanceController getDisplayWhiteBalanceController(Handler handler,
+ SensorManager sensorManager, Resources resources) {
+ return mDisplayWhiteBalanceControllerMock;
+ }
}
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
similarity index 98%
rename from services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
index f3e3d34..113c46b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -121,7 +121,8 @@
private PowerManager mPowerManagerMock;
@Mock
private ColorDisplayService.ColorDisplayServiceInternal mCdsiMock;
-
+ @Mock
+ private DisplayWhiteBalanceController mDisplayWhiteBalanceControllerMock;
@Captor
private ArgumentCaptor<SensorEventListener> mSensorEventListenerCaptor;
@@ -1102,6 +1103,18 @@
verify(mHolder.brightnessSetting).setBrightness(clampedBrightness);
}
+ @Test
+ public void testDwbcCallsHappenOnHandler() {
+ mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
+
+ mHolder.dpc.setAutomaticScreenBrightnessMode(true);
+ verify(mDisplayWhiteBalanceControllerMock, never()).setStrongModeEnabled(true);
+
+ // dispatch handler looper
+ advanceTime(1);
+ verify(mDisplayWhiteBalanceControllerMock, times(1)).setStrongModeEnabled(true);
+ }
+
/**
* Creates a mock and registers it to {@link LocalServices}.
*/
@@ -1361,5 +1374,11 @@
Context context) {
return mHighBrightnessModeController;
}
+
+ @Override
+ DisplayWhiteBalanceController getDisplayWhiteBalanceController(Handler handler,
+ SensorManager sensorManager, Resources resources) {
+ return mDisplayWhiteBalanceControllerMock;
+ }
}
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerProximityStateControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerProximityStateControllerTest.java
similarity index 100%
rename from services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerProximityStateControllerTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/DisplayPowerProximityStateControllerTest.java
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/HighBrightnessModeMetadataMapperTest.java b/services/tests/displayservicetests/src/com/android/server/display/HighBrightnessModeMetadataMapperTest.java
similarity index 100%
rename from services/tests/mockingservicestests/src/com/android/server/display/HighBrightnessModeMetadataMapperTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/HighBrightnessModeMetadataMapperTest.java
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
similarity index 100%
rename from services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
diff --git a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java
index 3e695c9..0fe6e64 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java
@@ -65,6 +65,7 @@
import com.android.server.display.layout.DisplayIdProducer;
import com.android.server.display.layout.Layout;
+import com.android.server.utils.FoldSettingWrapper;
import org.junit.Before;
import org.junit.Test;
@@ -100,6 +101,7 @@
@Mock LogicalDisplayMapper.Listener mListenerMock;
@Mock Context mContextMock;
+ @Mock FoldSettingWrapper mFoldSettingWrapperMock;
@Mock Resources mResourcesMock;
@Mock IPowerManager mIPowerManagerMock;
@Mock IThermalService mIThermalServiceMock;
@@ -139,6 +141,7 @@
when(mContextMock.getSystemServiceName(PowerManager.class))
.thenReturn(Context.POWER_SERVICE);
+ when(mFoldSettingWrapperMock.shouldStayAwakeOnFold()).thenReturn(false);
when(mContextMock.getSystemService(PowerManager.class)).thenReturn(mPowerManager);
when(mContextMock.getResources()).thenReturn(mResourcesMock);
when(mResourcesMock.getBoolean(
@@ -155,7 +158,7 @@
mHandler = new Handler(mLooper.getLooper());
mLogicalDisplayMapper = new LogicalDisplayMapper(mContextMock, mDisplayDeviceRepo,
mListenerMock, new DisplayManagerService.SyncRoot(), mHandler,
- mDeviceStateToLayoutMapSpy);
+ mDeviceStateToLayoutMapSpy, mFoldSettingWrapperMock);
}
@@ -571,6 +574,17 @@
}
@Test
+ public void testDeviceShouldNotSleepWhenFoldSettingTrue() {
+ when(mFoldSettingWrapperMock.shouldStayAwakeOnFold()).thenReturn(true);
+
+ assertFalse(mLogicalDisplayMapper.shouldDeviceBePutToSleep(DEVICE_STATE_CLOSED,
+ DEVICE_STATE_OPEN,
+ /* isOverrideActive= */false,
+ /* isInteractive= */true,
+ /* isBootCompleted= */true));
+ }
+
+ @Test
public void testDeviceShouldNotBePutToSleep() {
assertFalse(mLogicalDisplayMapper.shouldDeviceBePutToSleep(DEVICE_STATE_OPEN,
DEVICE_STATE_CLOSED,
@@ -978,4 +992,3 @@
}
}
}
-
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/WakelockControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/WakelockControllerTest.java
similarity index 100%
rename from services/tests/mockingservicestests/src/com/android/server/display/WakelockControllerTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/WakelockControllerTest.java
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java
index a9e616d..8497dab 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java
@@ -18,17 +18,23 @@
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
+import android.content.ContentResolver;
import android.content.Context;
+import android.content.ContextWrapper;
import android.content.res.Resources;
import android.hardware.display.DisplayManagerInternal;
import android.view.Display;
+import androidx.test.core.app.ApplicationProvider;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.internal.R;
+import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.internal.util.test.FakeSettingsProviderRule;
import com.android.server.display.brightness.strategy.BoostBrightnessStrategy;
import com.android.server.display.brightness.strategy.DozeBrightnessStrategy;
import com.android.server.display.brightness.strategy.FollowerBrightnessStrategy;
@@ -38,6 +44,7 @@
import com.android.server.display.brightness.strategy.TemporaryBrightnessStrategy;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -64,15 +71,20 @@
@Mock
private FollowerBrightnessStrategy mFollowerBrightnessStrategy;
@Mock
- private Context mContext;
- @Mock
private Resources mResources;
private DisplayBrightnessStrategySelector mDisplayBrightnessStrategySelector;
+ private Context mContext;
+
+ @Rule
+ public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
@Before
public void before() {
MockitoAnnotations.initMocks(this);
+ mContext = spy(new ContextWrapper(ApplicationProvider.getApplicationContext()));
+ ContentResolver contentResolver = mSettingsProviderRule.mockContentResolver(mContext);
+ when(mContext.getContentResolver()).thenReturn(contentResolver);
when(mContext.getResources()).thenReturn(mResources);
when(mInvalidBrightnessStrategy.getName()).thenReturn("InvalidBrightnessStrategy");
DisplayBrightnessStrategySelector.Injector injector =
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessThermalClamperTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessThermalClamperTest.java
new file mode 100644
index 0000000..37d0f62
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessThermalClamperTest.java
@@ -0,0 +1,251 @@
+/*
+ * 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.display.brightness.clamper;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+
+import android.hardware.display.DisplayManager;
+import android.os.IThermalEventListener;
+import android.os.IThermalService;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.Temperature;
+import android.provider.DeviceConfig;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.internal.annotations.Keep;
+import com.android.server.display.DisplayDeviceConfig;
+import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData;
+import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel;
+import com.android.server.display.feature.DeviceConfigParameterProvider;
+import com.android.server.testutils.FakeDeviceConfigInterface;
+import com.android.server.testutils.TestHandler;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.List;
+
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
+@RunWith(JUnitParamsRunner.class)
+public class BrightnessThermalClamperTest {
+
+ private static final float FLOAT_TOLERANCE = 0.001f;
+
+ private static final String DISPLAY_ID = "displayId";
+ @Mock
+ private IThermalService mMockThermalService;
+ @Mock
+ private BrightnessClamperController.ClamperChangeListener mMockClamperChangeListener;
+
+ private final FakeDeviceConfigInterface mFakeDeviceConfigInterface =
+ new FakeDeviceConfigInterface();
+ private final TestHandler mTestHandler = new TestHandler(null);
+ private BrightnessThermalClamper mClamper;
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mClamper = new BrightnessThermalClamper(new TestInjector(), mTestHandler,
+ mMockClamperChangeListener, new TestThermalData());
+ mTestHandler.flush();
+ }
+
+ @Test
+ public void testTypeIsThermal() {
+ assertEquals(BrightnessClamper.Type.THERMAL, mClamper.getType());
+ }
+
+ @Test
+ public void testNoThrottlingData() {
+ assertFalse(mClamper.isActive());
+ assertEquals(PowerManager.BRIGHTNESS_MAX, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
+ }
+
+ @Keep
+ private static Object[][] testThrottlingData() {
+ // throttlingLevels, throttlingStatus, expectedActive, expectedBrightness
+ return new Object[][] {
+ // no throttling data
+ {List.of(), Temperature.THROTTLING_LIGHT, false, PowerManager.BRIGHTNESS_MAX},
+ // throttlingStatus < min throttling data
+ {List.of(
+ new ThrottlingLevel(PowerManager.THERMAL_STATUS_MODERATE, 0.5f),
+ new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, 0.1f)),
+ Temperature.THROTTLING_LIGHT, false, PowerManager.BRIGHTNESS_MAX},
+ // throttlingStatus = min throttling data
+ {List.of(
+ new ThrottlingLevel(PowerManager.THERMAL_STATUS_MODERATE, 0.5f),
+ new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, 0.1f)),
+ Temperature.THROTTLING_MODERATE, true, 0.5f},
+ // throttlingStatus between min and max throttling data
+ {List.of(
+ new ThrottlingLevel(PowerManager.THERMAL_STATUS_MODERATE, 0.5f),
+ new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, 0.1f)),
+ Temperature.THROTTLING_SEVERE, true, 0.5f},
+ // throttlingStatus = max throttling data
+ {List.of(
+ new ThrottlingLevel(PowerManager.THERMAL_STATUS_MODERATE, 0.5f),
+ new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, 0.1f)),
+ Temperature.THROTTLING_CRITICAL, true, 0.1f},
+ // throttlingStatus > max throttling data
+ {List.of(
+ new ThrottlingLevel(PowerManager.THERMAL_STATUS_MODERATE, 0.5f),
+ new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, 0.1f)),
+ Temperature.THROTTLING_EMERGENCY, true, 0.1f},
+ };
+ }
+ @Test
+ @Parameters(method = "testThrottlingData")
+ public void testNotifyThrottlingAfterOnDisplayChange(List<ThrottlingLevel> throttlingLevels,
+ @Temperature.ThrottlingStatus int throttlingStatus,
+ boolean expectedActive, float expectedBrightness) throws RemoteException {
+ IThermalEventListener thermalEventListener = captureThermalEventListener();
+ mClamper.onDisplayChanged(new TestThermalData(throttlingLevels));
+ mTestHandler.flush();
+ assertFalse(mClamper.isActive());
+ assertEquals(PowerManager.BRIGHTNESS_MAX, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
+
+ thermalEventListener.notifyThrottling(createTemperature(throttlingStatus));
+ mTestHandler.flush();
+ assertEquals(expectedActive, mClamper.isActive());
+ assertEquals(expectedBrightness, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
+ }
+
+ @Test
+ @Parameters(method = "testThrottlingData")
+ public void testOnDisplayChangeAfterNotifyThrottlng(List<ThrottlingLevel> throttlingLevels,
+ @Temperature.ThrottlingStatus int throttlingStatus,
+ boolean expectedActive, float expectedBrightness) throws RemoteException {
+ IThermalEventListener thermalEventListener = captureThermalEventListener();
+ thermalEventListener.notifyThrottling(createTemperature(throttlingStatus));
+ mTestHandler.flush();
+ assertFalse(mClamper.isActive());
+ assertEquals(PowerManager.BRIGHTNESS_MAX, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
+
+ mClamper.onDisplayChanged(new TestThermalData(throttlingLevels));
+ mTestHandler.flush();
+ assertEquals(expectedActive, mClamper.isActive());
+ assertEquals(expectedBrightness, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
+ }
+
+ @Test
+ public void testOverrideData() throws RemoteException {
+ IThermalEventListener thermalEventListener = captureThermalEventListener();
+ thermalEventListener.notifyThrottling(createTemperature(Temperature.THROTTLING_SEVERE));
+ mTestHandler.flush();
+ assertFalse(mClamper.isActive());
+ assertEquals(PowerManager.BRIGHTNESS_MAX, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
+
+ mClamper.onDisplayChanged(new TestThermalData(
+ List.of(new ThrottlingLevel(PowerManager.THERMAL_STATUS_SEVERE, 0.5f))));
+ mTestHandler.flush();
+ assertTrue(mClamper.isActive());
+ assertEquals(0.5f, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
+
+ overrideThrottlingData("displayId,1,emergency,0.4");
+ mClamper.onDeviceConfigChanged();
+ mTestHandler.flush();
+
+ assertFalse(mClamper.isActive());
+ assertEquals(PowerManager.BRIGHTNESS_MAX, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
+
+ overrideThrottlingData("displayId,1,moderate,0.4");
+ mClamper.onDeviceConfigChanged();
+ mTestHandler.flush();
+
+ assertTrue(mClamper.isActive());
+ assertEquals(0.4f, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
+ }
+
+ private IThermalEventListener captureThermalEventListener() throws RemoteException {
+ ArgumentCaptor<IThermalEventListener> captor = ArgumentCaptor.forClass(
+ IThermalEventListener.class);
+ verify(mMockThermalService).registerThermalEventListenerWithType(captor.capture(), eq(
+ Temperature.TYPE_SKIN));
+ return captor.getValue();
+ }
+
+ private Temperature createTemperature(@Temperature.ThrottlingStatus int status) {
+ return new Temperature(100, Temperature.TYPE_SKIN, "test_temperature", status);
+ }
+
+ private void overrideThrottlingData(String data) {
+ mFakeDeviceConfigInterface.putProperty(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+ DisplayManager.DeviceConfig.KEY_BRIGHTNESS_THROTTLING_DATA, data);
+ }
+
+ private class TestInjector extends BrightnessThermalClamper.Injector {
+ @Override
+ IThermalService getThermalService() {
+ return mMockThermalService;
+ }
+
+ @Override
+ DeviceConfigParameterProvider getDeviceConfigParameterProvider() {
+ return new DeviceConfigParameterProvider(mFakeDeviceConfigInterface);
+ }
+ }
+
+ private static class TestThermalData implements BrightnessThermalClamper.ThermalData {
+
+ private final String mUniqueDisplayId;
+ private final String mDataId;
+ private final ThermalBrightnessThrottlingData mData;
+
+ private TestThermalData() {
+ this(DISPLAY_ID, DisplayDeviceConfig.DEFAULT_ID, null);
+ }
+
+ private TestThermalData(List<ThrottlingLevel> data) {
+ this(DISPLAY_ID, DisplayDeviceConfig.DEFAULT_ID, data);
+ }
+ private TestThermalData(String uniqueDisplayId, String dataId, List<ThrottlingLevel> data) {
+ mUniqueDisplayId = uniqueDisplayId;
+ mDataId = dataId;
+ mData = ThermalBrightnessThrottlingData.create(data);
+ }
+ @NonNull
+ @Override
+ public String getUniqueDisplayId() {
+ return mUniqueDisplayId;
+ }
+
+ @NonNull
+ @Override
+ public String getThermalThrottlingDataId() {
+ return mDataId;
+ }
+
+ @Nullable
+ @Override
+ public ThermalBrightnessThrottlingData getThermalBrightnessThrottlingData() {
+ return mData;
+ }
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/color/DisplayTransformManagerTest.java b/services/tests/displayservicetests/src/com/android/server/display/color/DisplayTransformManagerTest.java
similarity index 100%
rename from services/tests/mockingservicestests/src/com/android/server/display/color/DisplayTransformManagerTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/color/DisplayTransformManagerTest.java
diff --git a/services/tests/displayservicetests/src/com/android/server/display/color/DisplayWhiteBalanceTintControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/color/DisplayWhiteBalanceTintControllerTest.java
index e0bef1a..c280349 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/color/DisplayWhiteBalanceTintControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/color/DisplayWhiteBalanceTintControllerTest.java
@@ -16,22 +16,49 @@
package com.android.server.display.color;
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+
import static com.google.common.truth.Truth.assertWithMessage;
+import static org.junit.Assert.assertArrayEquals;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import android.content.Context;
+import android.content.res.Resources;
import android.hardware.display.DisplayManagerInternal;
+import android.os.Binder;
+import android.os.IBinder;
+import android.view.SurfaceControl;
import androidx.test.InstrumentationRegistry;
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import com.android.internal.R;
+
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
import java.util.Arrays;
public class DisplayWhiteBalanceTintControllerTest {
+ @Mock
+ private Context mMockedContext;
+ @Mock
+ private Resources mMockedResources;
+ @Mock
+ private DisplayManagerInternal mDisplayManagerInternal;
- private DisplayWhiteBalanceTintController mDisplayWhiteBalanceTintController;
+ private MockitoSession mSession;
+ private Resources mResources;
+ IBinder mDisplayToken;
+ DisplayWhiteBalanceTintController mDisplayWhiteBalanceTintController;
@Before
public void setUp() {
@@ -40,6 +67,47 @@
new DisplayWhiteBalanceTintController(displayManagerInternal);
mDisplayWhiteBalanceTintController.setUp(InstrumentationRegistry.getContext(), true);
mDisplayWhiteBalanceTintController.setActivated(true);
+
+ mSession = ExtendedMockito.mockitoSession()
+ .initMocks(this)
+ .mockStatic(SurfaceControl.class)
+ .strictness(Strictness.LENIENT)
+ .startMocking();
+
+ mResources = InstrumentationRegistry.getContext().getResources();
+ // These Resources are common to all tests.
+ doReturn(4000)
+ .when(mMockedResources)
+ .getInteger(R.integer.config_displayWhiteBalanceColorTemperatureMin);
+ doReturn(8000)
+ .when(mMockedResources)
+ .getInteger(R.integer.config_displayWhiteBalanceColorTemperatureMax);
+ doReturn(6500)
+ .when(mMockedResources)
+ .getInteger(R.integer.config_displayWhiteBalanceColorTemperatureDefault);
+ doReturn(new String[] {"0.950456", "1.000000", "1.089058"})
+ .when(mMockedResources)
+ .getStringArray(R.array.config_displayWhiteBalanceDisplayNominalWhite);
+ doReturn(6500)
+ .when(mMockedResources)
+ .getInteger(R.integer.config_displayWhiteBalanceDisplayNominalWhiteCct);
+ doReturn(new int[] {0})
+ .when(mMockedResources)
+ .getIntArray(R.array.config_displayWhiteBalanceDisplaySteps);
+ doReturn(new int[] {20})
+ .when(mMockedResources)
+ .getIntArray(R.array.config_displayWhiteBalanceDisplayRangeMinimums);
+
+ doReturn(mMockedResources).when(mMockedContext).getResources();
+
+ mDisplayToken = new Binder();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ if (mSession != null) {
+ mSession.finishMocking();
+ }
}
@Test
@@ -98,4 +166,204 @@
})
).isTrue();
}
+
+
+ /**
+ * Setup should succeed when SurfaceControl setup results in a valid color transform.
+ */
+ @Test
+ public void displayWhiteBalance_setupWithSurfaceControl() {
+ // Make SurfaceControl return sRGB primaries
+ SurfaceControl.DisplayPrimaries displayPrimaries = new SurfaceControl.DisplayPrimaries();
+ displayPrimaries.red = new SurfaceControl.CieXyz();
+ displayPrimaries.red.X = 0.412315f;
+ displayPrimaries.red.Y = 0.212600f;
+ displayPrimaries.red.Z = 0.019327f;
+ displayPrimaries.green = new SurfaceControl.CieXyz();
+ displayPrimaries.green.X = 0.357600f;
+ displayPrimaries.green.Y = 0.715200f;
+ displayPrimaries.green.Z = 0.119200f;
+ displayPrimaries.blue = new SurfaceControl.CieXyz();
+ displayPrimaries.blue.X = 0.180500f;
+ displayPrimaries.blue.Y = 0.072200f;
+ displayPrimaries.blue.Z = 0.950633f;
+ displayPrimaries.white = new SurfaceControl.CieXyz();
+ displayPrimaries.white.X = 0.950456f;
+ displayPrimaries.white.Y = 1.000000f;
+ displayPrimaries.white.Z = 1.089058f;
+ when(mDisplayManagerInternal.getDisplayNativePrimaries(DEFAULT_DISPLAY))
+ .thenReturn(displayPrimaries);
+
+ setUpTintController();
+ assertWithMessage("Setup with valid SurfaceControl failed")
+ .that(mDisplayWhiteBalanceTintController.mSetUp)
+ .isTrue();
+ }
+
+ /**
+ * Setup should fail when SurfaceControl setup results in an invalid color transform.
+ */
+ @Test
+ public void displayWhiteBalance_setupWithInvalidSurfaceControlData() {
+ // Make SurfaceControl return invalid display primaries
+ SurfaceControl.DisplayPrimaries displayPrimaries = new SurfaceControl.DisplayPrimaries();
+ displayPrimaries.red = new SurfaceControl.CieXyz();
+ displayPrimaries.green = new SurfaceControl.CieXyz();
+ displayPrimaries.blue = new SurfaceControl.CieXyz();
+ displayPrimaries.white = new SurfaceControl.CieXyz();
+ when(mDisplayManagerInternal.getDisplayNativePrimaries(DEFAULT_DISPLAY))
+ .thenReturn(displayPrimaries);
+
+ setUpTintController();
+ assertWithMessage("Setup with invalid SurfaceControl succeeded")
+ .that(mDisplayWhiteBalanceTintController.mSetUp)
+ .isFalse();
+ }
+
+ /**
+ * Setup should succeed when SurfaceControl setup fails and Resources result in a valid color
+ * transform.
+ */
+ @Test
+ public void displayWhiteBalance_setupWithResources() {
+ // Use default (valid) Resources
+ doReturn(mResources.getStringArray(R.array.config_displayWhiteBalanceDisplayPrimaries))
+ .when(mMockedResources)
+ .getStringArray(R.array.config_displayWhiteBalanceDisplayPrimaries);
+ // Make SurfaceControl setup fail
+ when(mDisplayManagerInternal.getDisplayNativePrimaries(DEFAULT_DISPLAY)).thenReturn(null);
+
+ setUpTintController();
+ assertWithMessage("Setup with valid Resources failed")
+ .that(mDisplayWhiteBalanceTintController.mSetUp)
+ .isTrue();
+ }
+
+ /**
+ * Setup should fail when SurfaceControl setup fails and Resources result in an invalid color
+ * transform.
+ */
+ @Test
+ public void displayWhiteBalance_setupWithInvalidResources() {
+ // Use Resources with invalid color data
+ doReturn(new String[] {
+ "0", "0", "0", // Red X, Y, Z
+ "0", "0", "0", // Green X, Y, Z
+ "0", "0", "0", // Blue X, Y, Z
+ "0", "0", "0", // White X, Y, Z
+ })
+ .when(mMockedResources)
+ .getStringArray(R.array.config_displayWhiteBalanceDisplayPrimaries);
+ // Make SurfaceControl setup fail
+ when(mDisplayManagerInternal.getDisplayNativePrimaries(DEFAULT_DISPLAY)).thenReturn(null);
+
+ setUpTintController();
+ assertWithMessage("Setup with invalid Resources succeeded")
+ .that(mDisplayWhiteBalanceTintController.mSetUp)
+ .isFalse();
+ }
+
+ /**
+ * Matrix should match the precalculated one for given cct and display primaries.
+ */
+ @Test
+ public void displayWhiteBalance_getAndSetMatrix_validateTransformMatrix() {
+ SurfaceControl.DisplayPrimaries displayPrimaries = new SurfaceControl.DisplayPrimaries();
+ displayPrimaries.red = new SurfaceControl.CieXyz();
+ displayPrimaries.red.X = 0.412315f;
+ displayPrimaries.red.Y = 0.212600f;
+ displayPrimaries.red.Z = 0.019327f;
+ displayPrimaries.green = new SurfaceControl.CieXyz();
+ displayPrimaries.green.X = 0.357600f;
+ displayPrimaries.green.Y = 0.715200f;
+ displayPrimaries.green.Z = 0.119200f;
+ displayPrimaries.blue = new SurfaceControl.CieXyz();
+ displayPrimaries.blue.X = 0.180500f;
+ displayPrimaries.blue.Y = 0.072200f;
+ displayPrimaries.blue.Z = 0.950633f;
+ displayPrimaries.white = new SurfaceControl.CieXyz();
+ displayPrimaries.white.X = 0.950456f;
+ displayPrimaries.white.Y = 1.000000f;
+ displayPrimaries.white.Z = 1.089058f;
+ when(mDisplayManagerInternal.getDisplayNativePrimaries(DEFAULT_DISPLAY))
+ .thenReturn(displayPrimaries);
+
+ setUpTintController();
+ assertWithMessage("Setup with valid SurfaceControl failed")
+ .that(mDisplayWhiteBalanceTintController.mSetUp)
+ .isTrue();
+
+ final int cct = 6500;
+ mDisplayWhiteBalanceTintController.setMatrix(cct);
+ mDisplayWhiteBalanceTintController.setAppliedCct(
+ mDisplayWhiteBalanceTintController.getTargetCct());
+
+ assertWithMessage("Failed to set temperature")
+ .that(mDisplayWhiteBalanceTintController.mCurrentColorTemperature)
+ .isEqualTo(cct);
+ float[] matrixDwb = mDisplayWhiteBalanceTintController.getMatrix();
+ final float[] expectedMatrixDwb = {
+ 0.971848f, -0.001421f, 0.000491f, 0.0f,
+ 0.028193f, 0.945798f, 0.003207f, 0.0f,
+ -0.000042f, -0.000989f, 0.988659f, 0.0f,
+ 0.0f, 0.0f, 0.0f, 1.0f
+ };
+ assertArrayEquals("Unexpected DWB matrix", expectedMatrixDwb, matrixDwb,
+ 1e-6f /* tolerance */);
+ }
+
+ /**
+ * Matrix should match the precalculated one for given cct and display primaries.
+ */
+ @Test
+ public void displayWhiteBalance_targetApplied_validateTransformMatrix() {
+ SurfaceControl.DisplayPrimaries displayPrimaries = new SurfaceControl.DisplayPrimaries();
+ displayPrimaries.red = new SurfaceControl.CieXyz();
+ displayPrimaries.red.X = 0.412315f;
+ displayPrimaries.red.Y = 0.212600f;
+ displayPrimaries.red.Z = 0.019327f;
+ displayPrimaries.green = new SurfaceControl.CieXyz();
+ displayPrimaries.green.X = 0.357600f;
+ displayPrimaries.green.Y = 0.715200f;
+ displayPrimaries.green.Z = 0.119200f;
+ displayPrimaries.blue = new SurfaceControl.CieXyz();
+ displayPrimaries.blue.X = 0.180500f;
+ displayPrimaries.blue.Y = 0.072200f;
+ displayPrimaries.blue.Z = 0.950633f;
+ displayPrimaries.white = new SurfaceControl.CieXyz();
+ displayPrimaries.white.X = 0.950456f;
+ displayPrimaries.white.Y = 1.000000f;
+ displayPrimaries.white.Z = 1.089058f;
+ when(mDisplayManagerInternal.getDisplayNativePrimaries(DEFAULT_DISPLAY))
+ .thenReturn(displayPrimaries);
+
+ setUpTintController();
+ assertWithMessage("Setup with valid SurfaceControl failed")
+ .that(mDisplayWhiteBalanceTintController.mSetUp)
+ .isTrue();
+
+ final int cct = 6500;
+ mDisplayWhiteBalanceTintController.setTargetCct(cct);
+ final float[] matrixDwb = mDisplayWhiteBalanceTintController.computeMatrixForCct(cct);
+ mDisplayWhiteBalanceTintController.setAppliedCct(cct);
+
+ assertWithMessage("Failed to set temperature")
+ .that(mDisplayWhiteBalanceTintController.mCurrentColorTemperature)
+ .isEqualTo(cct);
+ final float[] expectedMatrixDwb = {
+ 0.971848f, -0.001421f, 0.000491f, 0.0f,
+ 0.028193f, 0.945798f, 0.003207f, 0.0f,
+ -0.000042f, -0.000989f, 0.988659f, 0.0f,
+ 0.0f, 0.0f, 0.0f, 1.0f
+ };
+ assertArrayEquals("Unexpected DWB matrix", expectedMatrixDwb, matrixDwb,
+ 1e-6f /* tolerance */);
+ }
+
+ private void setUpTintController() {
+ mDisplayWhiteBalanceTintController = new DisplayWhiteBalanceTintController(
+ mDisplayManagerInternal);
+ mDisplayWhiteBalanceTintController.setUp(mMockedContext, true);
+ mDisplayWhiteBalanceTintController.setActivated(true);
+ }
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/state/DisplayStateControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/state/DisplayStateControllerTest.java
similarity index 100%
rename from services/tests/mockingservicestests/src/com/android/server/display/state/DisplayStateControllerTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/state/DisplayStateControllerTest.java
diff --git a/services/tests/displayservicetests/src/com/android/server/display/utils/DeviceConfigParsingUtilsTest.java b/services/tests/displayservicetests/src/com/android/server/display/utils/DeviceConfigParsingUtilsTest.java
new file mode 100644
index 0000000..5e28e63
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/utils/DeviceConfigParsingUtilsTest.java
@@ -0,0 +1,161 @@
+/*
+ * 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.display.utils;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+
+import android.os.PowerManager;
+import android.util.Pair;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.annotations.Keep;
+import com.android.server.display.DisplayDeviceConfig;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+import java.util.Map;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
+@SmallTest
+@RunWith(JUnitParamsRunner.class)
+public class DeviceConfigParsingUtilsTest {
+ private static final String VALID_DATA_STRING = "display1,1,key1,value1";
+ private static final float FLOAT_TOLERANCE = 0.001f;
+
+ private final BiFunction<String, String, Pair<String, String>> mDataPointToPair = Pair::create;
+ private final Function<List<Pair<String, String>>, List<Pair<String, String>>>
+ mDataSetIdentity = (dataSet) -> dataSet;
+
+ @Keep
+ private static Object[][] parseDeviceConfigMapData() {
+ // dataString, expectedMap
+ return new Object[][]{
+ // null
+ {null, Map.of()},
+ // empty string
+ {"", Map.of()},
+ // 1 display, 1 incomplete data point
+ {"display1,1,key1", Map.of()},
+ // 1 display,2 data points required, only 1 present
+ {"display1,2,key1,value1", Map.of()},
+ // 1 display, 1 data point, dataSetId and some extra data
+ {"display1,1,key1,value1,setId1,extraData", Map.of()},
+ // 1 display, random string instead of number of data points
+ {"display1,one,key1,value1", Map.of()},
+ // 1 display, 1 data point no dataSetId
+ {VALID_DATA_STRING, Map.of("display1", Map.of(DisplayDeviceConfig.DEFAULT_ID,
+ List.of(Pair.create("key1", "value1"))))},
+ // 1 display, 1 data point, dataSetId
+ {"display1,1,key1,value1,setId1", Map.of("display1", Map.of("setId1",
+ List.of(Pair.create("key1", "value1"))))},
+ // 1 display, 2 data point, dataSetId
+ {"display1,2,key1,value1,key2,value2,setId1", Map.of("display1", Map.of("setId1",
+ List.of(Pair.create("key1", "value1"), Pair.create("key2", "value2"))))},
+ };
+ }
+
+ @Test
+ @Parameters(method = "parseDeviceConfigMapData")
+ public void testParseDeviceConfigMap(String dataString,
+ Map<String, Map<String, List<Pair<String, String>>>> expectedMap) {
+ Map<String, Map<String, List<Pair<String, String>>>> result =
+ DeviceConfigParsingUtils.parseDeviceConfigMap(dataString, mDataPointToPair,
+ mDataSetIdentity);
+
+ assertEquals(expectedMap, result);
+ }
+
+ @Test
+ public void testDataPointMapperReturnsNull() {
+ Map<String, Map<String, List<Pair<String, String>>>> result =
+ DeviceConfigParsingUtils.parseDeviceConfigMap(VALID_DATA_STRING, (s1, s2) -> null,
+ mDataSetIdentity);
+
+ assertEquals(Map.of(), result);
+ }
+
+ @Test
+ public void testDataSetMapperReturnsNull() {
+ Map<String, Map<String, List<Pair<String, String>>>> result =
+ DeviceConfigParsingUtils.parseDeviceConfigMap(VALID_DATA_STRING, mDataPointToPair,
+ (dataSet) -> null);
+
+ assertEquals(Map.of(), result);
+ }
+
+ @Keep
+ private static Object[][] parseThermalStatusData() {
+ // thermalStatusString, expectedThermalStatus
+ return new Object[][]{
+ {"none", PowerManager.THERMAL_STATUS_NONE},
+ {"light", PowerManager.THERMAL_STATUS_LIGHT},
+ {"moderate", PowerManager.THERMAL_STATUS_MODERATE},
+ {"severe", PowerManager.THERMAL_STATUS_SEVERE},
+ {"critical", PowerManager.THERMAL_STATUS_CRITICAL},
+ {"emergency", PowerManager.THERMAL_STATUS_EMERGENCY},
+ {"shutdown", PowerManager.THERMAL_STATUS_SHUTDOWN},
+ };
+ }
+
+ @Test
+ @Parameters(method = "parseThermalStatusData")
+ public void testParseThermalStatus(String thermalStatusString,
+ @PowerManager.ThermalStatus int expectedThermalStatus) {
+ int result = DeviceConfigParsingUtils.parseThermalStatus(thermalStatusString);
+
+ assertEquals(expectedThermalStatus, result);
+ }
+
+ @Test
+ public void testParseThermalStatus_illegalStatus() {
+ Throwable result = assertThrows(IllegalArgumentException.class,
+ () -> DeviceConfigParsingUtils.parseThermalStatus("invalid_status"));
+
+ assertEquals("Invalid Thermal Status: invalid_status", result.getMessage());
+ }
+
+ @Test
+ public void testParseBrightness() {
+ float result = DeviceConfigParsingUtils.parseBrightness("0.65");
+
+ assertEquals(0.65, result, FLOAT_TOLERANCE);
+ }
+
+ @Test
+ public void testParseBrightness_lessThanMin() {
+ Throwable result = assertThrows(IllegalArgumentException.class,
+ () -> DeviceConfigParsingUtils.parseBrightness("-0.65"));
+
+ assertEquals("Brightness value out of bounds: -0.65", result.getMessage());
+ }
+
+ @Test
+ public void testParseBrightness_moreThanMax() {
+ Throwable result = assertThrows(IllegalArgumentException.class,
+ () -> DeviceConfigParsingUtils.parseBrightness("1.65"));
+
+ assertEquals("Brightness value out of bounds: 1.65", result.getMessage());
+ }
+}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/SensorUtilsTest.java b/services/tests/displayservicetests/src/com/android/server/display/utils/SensorUtilsTest.java
similarity index 100%
rename from services/tests/displayservicetests/src/com/android/server/display/SensorUtilsTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/utils/SensorUtilsTest.java
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java
index fe23eee..bb91939 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java
@@ -2435,10 +2435,11 @@
private void setPermissionState(String packageName, int uid, String perm, boolean granted) {
doReturn(granted ? PERMISSION_GRANTED : PERMISSION_DENIED)
.when(mPermissionManagerServiceInternal)
- .checkUidPermission(uid, perm);
+ .checkUidPermission(uid, perm, Context.DEVICE_ID_DEFAULT);
doReturn(granted ? PERMISSION_GRANTED : PERMISSION_DENIED)
.when(mPermissionManagerServiceInternal)
- .checkPermission(packageName, perm, UserHandle.getUserId(uid));
+ .checkPermission(
+ packageName, perm, Context.DEVICE_ID_DEFAULT, UserHandle.getUserId(uid));
try {
doReturn(granted ? PERMISSION_GRANTED : PERMISSION_DENIED)
.when(mIActivityManager)
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
index ff04728..08f5d03 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
@@ -620,6 +620,28 @@
assertEquals(BroadcastProcessQueue.REASON_CORE_UID, queue.getRunnableAtReason());
}
+ @Test
+ public void testRunnableAt_freezableCoreUid() {
+ final BroadcastProcessQueue queue = new BroadcastProcessQueue(mConstants,
+ "com.android.bluetooth", Process.BLUETOOTH_UID);
+
+ // Mark the process as freezable
+ queue.setProcessAndUidState(mProcess, false, true);
+ final Intent timeTick = new Intent(Intent.ACTION_TIME_TICK);
+ final BroadcastOptions options = BroadcastOptions.makeWithDeferUntilActive(true);
+ final BroadcastRecord timeTickRecord = makeBroadcastRecord(timeTick, options,
+ List.of(makeMockRegisteredReceiver()), false);
+ enqueueOrReplaceBroadcast(queue, timeTickRecord, 0);
+
+ assertEquals(Long.MAX_VALUE, queue.getRunnableAt());
+ assertEquals(BroadcastProcessQueue.REASON_CACHED_INFINITE_DEFER,
+ queue.getRunnableAtReason());
+
+ queue.setProcessAndUidState(mProcess, false, false);
+ assertThat(queue.getRunnableAt()).isEqualTo(timeTickRecord.enqueueTime);
+ assertEquals(BroadcastProcessQueue.REASON_CORE_UID, queue.getRunnableAtReason());
+ }
+
/**
* Verify that a cached process that would normally be delayed becomes
* immediately runnable when the given broadcast is enqueued.
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
index 73eb237..410ae35 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -125,6 +125,7 @@
import java.util.Map;
import java.util.Objects;
import java.util.Set;
+import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.UnaryOperator;
@@ -327,7 +328,7 @@
eq(ActivityManager.PROCESS_STATE_LAST_ACTIVITY), any());
mConstants = new BroadcastConstants(Settings.Global.BROADCAST_FG_CONSTANTS);
- mConstants.TIMEOUT = 100;
+ mConstants.TIMEOUT = 200;
mConstants.ALLOW_BG_ACTIVITY_START_TIMEOUT = 0;
mConstants.PENDING_COLD_START_CHECK_INTERVAL_MILLIS = 500;
@@ -707,6 +708,9 @@
private void waitForIdle() throws Exception {
mLooper.release();
mQueue.waitForIdle(LOG_WRITER_INFO);
+ final CountDownLatch latch = new CountDownLatch(1);
+ mHandlerThread.getThreadHandler().post(latch::countDown);
+ latch.await();
mLooper = Objects.requireNonNull(InstrumentationRegistry.getInstrumentation()
.acquireLooperManager(mHandlerThread.getLooper()));
}
@@ -2342,6 +2346,7 @@
mUidObserver.onUidStateChanged(receiverGreenApp.info.uid,
ActivityManager.PROCESS_STATE_TOP, 0, ActivityManager.PROCESS_CAPABILITY_NONE);
+ waitForIdle();
final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
final Intent timeTick = new Intent(Intent.ACTION_TIME_TICK);
@@ -2375,6 +2380,7 @@
mUidObserver.onUidStateChanged(receiverGreenApp.info.uid,
ActivityManager.PROCESS_STATE_TOP, 0, ActivityManager.PROCESS_CAPABILITY_NONE);
+ waitForIdle();
final Intent timeTick = new Intent(Intent.ACTION_TIME_TICK);
diff --git a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
index 212a243..cd3a78e 100644
--- a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
@@ -52,6 +52,7 @@
import android.app.GameModeInfo;
import android.app.GameState;
import android.app.IGameModeListener;
+import android.app.IGameStateListener;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.ContextWrapper;
@@ -1578,6 +1579,71 @@
assertFalse(gameManagerService.mHandler.hasMessages(SET_GAME_STATE));
}
+ @Test
+ public void testAddGameStateListener() throws Exception {
+ mockModifyGameModeGranted();
+ GameManagerService gameManagerService =
+ new GameManagerService(mMockContext, mTestLooper.getLooper());
+ mockDeviceConfigAll();
+ startUser(gameManagerService, USER_ID_1);
+
+ IGameStateListener mockListener = Mockito.mock(IGameStateListener.class);
+ IBinder binder = Mockito.mock(IBinder.class);
+ when(mockListener.asBinder()).thenReturn(binder);
+ gameManagerService.addGameStateListener(mockListener);
+ verify(binder).linkToDeath(mDeathRecipientCaptor.capture(), anyInt());
+
+ mockAppCategory(mPackageName, ApplicationInfo.CATEGORY_AUDIO);
+ GameState gameState = new GameState(true, GameState.MODE_NONE);
+ gameManagerService.setGameState(mPackageName, gameState, USER_ID_1);
+ assertFalse(gameManagerService.mHandler.hasMessages(SET_GAME_STATE));
+ mTestLooper.dispatchAll();
+ verify(mockListener, never()).onGameStateChanged(anyString(), any(), anyInt());
+
+ mockAppCategory(mPackageName, ApplicationInfo.CATEGORY_GAME);
+ gameState = new GameState(true, GameState.MODE_NONE);
+ gameManagerService.setGameState(mPackageName, gameState, USER_ID_1);
+ assertTrue(gameManagerService.mHandler.hasMessages(SET_GAME_STATE));
+ mTestLooper.dispatchAll();
+ verify(mockListener).onGameStateChanged(mPackageName, gameState, USER_ID_1);
+ reset(mockListener);
+
+ gameState = new GameState(false, GameState.MODE_CONTENT);
+ gameManagerService.setGameState(mPackageName, gameState, USER_ID_1);
+ mTestLooper.dispatchAll();
+ verify(mockListener).onGameStateChanged(mPackageName, gameState, USER_ID_1);
+ reset(mockListener);
+
+ mDeathRecipientCaptor.getValue().binderDied();
+ verify(binder).unlinkToDeath(eq(mDeathRecipientCaptor.getValue()), anyInt());
+ gameManagerService.setGameState(mPackageName, gameState, USER_ID_1);
+ assertTrue(gameManagerService.mHandler.hasMessages(SET_GAME_STATE));
+ mTestLooper.dispatchAll();
+ verify(mockListener, never()).onGameStateChanged(anyString(), any(), anyInt());
+ }
+
+ @Test
+ public void testRemoveGameStateListener() throws Exception {
+ mockModifyGameModeGranted();
+ GameManagerService gameManagerService =
+ new GameManagerService(mMockContext, mTestLooper.getLooper());
+ mockDeviceConfigAll();
+ startUser(gameManagerService, USER_ID_1);
+
+ IGameStateListener mockListener = Mockito.mock(IGameStateListener.class);
+ IBinder binder = Mockito.mock(IBinder.class);
+ when(mockListener.asBinder()).thenReturn(binder);
+
+ gameManagerService.addGameStateListener(mockListener);
+ gameManagerService.removeGameStateListener(mockListener);
+ mockAppCategory(mPackageName, ApplicationInfo.CATEGORY_GAME);
+ GameState gameState = new GameState(false, GameState.MODE_CONTENT);
+ gameManagerService.setGameState(mPackageName, gameState, USER_ID_1);
+ assertTrue(gameManagerService.mHandler.hasMessages(SET_GAME_STATE));
+ mTestLooper.dispatchAll();
+ verify(mockListener, never()).onGameStateChanged(anyString(), any(), anyInt());
+ }
+
private List<String> readGameModeInterventionList() throws Exception {
final File interventionFile = new File(InstrumentationRegistry.getContext().getFilesDir(),
"system/game_mode_intervention.list");
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DisplayBrightnessStateTest.java b/services/tests/mockingservicestests/src/com/android/server/display/DisplayBrightnessStateTest.java
deleted file mode 100644
index 50996d7..0000000
--- a/services/tests/mockingservicestests/src/com/android/server/display/DisplayBrightnessStateTest.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.display;
-
-import static org.junit.Assert.assertEquals;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.server.display.brightness.BrightnessReason;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class DisplayBrightnessStateTest {
- private static final float FLOAT_DELTA = 0.001f;
-
- private DisplayBrightnessState.Builder mDisplayBrightnessStateBuilder;
-
- @Before
- public void before() {
- mDisplayBrightnessStateBuilder = new DisplayBrightnessState.Builder();
- }
-
- @Test
- public void validateAllDisplayBrightnessStateFieldsAreSetAsExpected() {
- float brightness = 0.3f;
- float sdrBrightness = 0.2f;
- BrightnessReason brightnessReason = new BrightnessReason();
- brightnessReason.setReason(BrightnessReason.REASON_AUTOMATIC);
- brightnessReason.setModifier(BrightnessReason.MODIFIER_DIMMED);
- DisplayBrightnessState displayBrightnessState =
- mDisplayBrightnessStateBuilder.setBrightness(brightness).setSdrBrightness(
- sdrBrightness).setBrightnessReason(brightnessReason).build();
-
- assertEquals(displayBrightnessState.getBrightness(), brightness, FLOAT_DELTA);
- assertEquals(displayBrightnessState.getSdrBrightness(), sdrBrightness, FLOAT_DELTA);
- assertEquals(displayBrightnessState.getBrightnessReason(), brightnessReason);
- assertEquals(displayBrightnessState.toString(), getString(displayBrightnessState));
- }
-
- private String getString(DisplayBrightnessState displayBrightnessState) {
- StringBuilder sb = new StringBuilder();
- sb.append("DisplayBrightnessState:");
- sb.append("\n brightness:" + displayBrightnessState.getBrightness());
- sb.append("\n sdrBrightness:" + displayBrightnessState.getSdrBrightness());
- sb.append("\n brightnessReason:" + displayBrightnessState.getBrightnessReason());
- return sb.toString();
- }
-}
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/OWNERS b/services/tests/mockingservicestests/src/com/android/server/display/OWNERS
deleted file mode 100644
index 6ce1ee4..0000000
--- a/services/tests/mockingservicestests/src/com/android/server/display/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-include /services/core/java/com/android/server/display/OWNERS
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/color/DisplayWhiteBalanceTintControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/display/color/DisplayWhiteBalanceTintControllerTest.java
deleted file mode 100644
index 3faf394..0000000
--- a/services/tests/mockingservicestests/src/com/android/server/display/color/DisplayWhiteBalanceTintControllerTest.java
+++ /dev/null
@@ -1,307 +0,0 @@
-/*
- * Copyright (C) 2019 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.display.color;
-
-import static android.view.Display.DEFAULT_DISPLAY;
-
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
-
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import static org.junit.Assert.assertArrayEquals;
-import static org.mockito.Mockito.when;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.hardware.display.DisplayManagerInternal;
-import android.os.Binder;
-import android.os.IBinder;
-import android.view.SurfaceControl;
-import android.view.SurfaceControl.CieXyz;
-import android.view.SurfaceControl.DisplayPrimaries;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.dx.mockito.inline.extended.ExtendedMockito;
-import com.android.internal.R;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoSession;
-import org.mockito.quality.Strictness;
-
-@RunWith(AndroidJUnit4.class)
-public class DisplayWhiteBalanceTintControllerTest {
- @Mock
- private Context mMockedContext;
- @Mock
- private Resources mMockedResources;
- @Mock
- private DisplayManagerInternal mDisplayManagerInternal;
-
- private MockitoSession mSession;
- private Resources mResources;
- IBinder mDisplayToken;
- DisplayWhiteBalanceTintController mDisplayWhiteBalanceTintController;
-
- @Before
- public void setUp() {
- mSession = ExtendedMockito.mockitoSession()
- .initMocks(this)
- .mockStatic(SurfaceControl.class)
- .strictness(Strictness.LENIENT)
- .startMocking();
-
- mResources = InstrumentationRegistry.getContext().getResources();
- // These Resources are common to all tests.
- doReturn(4000)
- .when(mMockedResources)
- .getInteger(R.integer.config_displayWhiteBalanceColorTemperatureMin);
- doReturn(8000)
- .when(mMockedResources)
- .getInteger(R.integer.config_displayWhiteBalanceColorTemperatureMax);
- doReturn(6500)
- .when(mMockedResources)
- .getInteger(R.integer.config_displayWhiteBalanceColorTemperatureDefault);
- doReturn(new String[] {"0.950456", "1.000000", "1.089058"})
- .when(mMockedResources)
- .getStringArray(R.array.config_displayWhiteBalanceDisplayNominalWhite);
- doReturn(6500)
- .when(mMockedResources)
- .getInteger(R.integer.config_displayWhiteBalanceDisplayNominalWhiteCct);
- doReturn(new int[] {0})
- .when(mMockedResources)
- .getIntArray(R.array.config_displayWhiteBalanceDisplaySteps);
- doReturn(new int[] {20})
- .when(mMockedResources)
- .getIntArray(R.array.config_displayWhiteBalanceDisplayRangeMinimums);
-
- doReturn(mMockedResources).when(mMockedContext).getResources();
-
- mDisplayToken = new Binder();
- }
-
- @After
- public void tearDown() throws Exception {
- if (mSession != null) {
- mSession.finishMocking();
- }
- }
-
- /**
- * Setup should succeed when SurfaceControl setup results in a valid color transform.
- */
- @Test
- public void displayWhiteBalance_setupWithSurfaceControl() {
- // Make SurfaceControl return sRGB primaries
- DisplayPrimaries displayPrimaries = new DisplayPrimaries();
- displayPrimaries.red = new CieXyz();
- displayPrimaries.red.X = 0.412315f;
- displayPrimaries.red.Y = 0.212600f;
- displayPrimaries.red.Z = 0.019327f;
- displayPrimaries.green = new CieXyz();
- displayPrimaries.green.X = 0.357600f;
- displayPrimaries.green.Y = 0.715200f;
- displayPrimaries.green.Z = 0.119200f;
- displayPrimaries.blue = new CieXyz();
- displayPrimaries.blue.X = 0.180500f;
- displayPrimaries.blue.Y = 0.072200f;
- displayPrimaries.blue.Z = 0.950633f;
- displayPrimaries.white = new CieXyz();
- displayPrimaries.white.X = 0.950456f;
- displayPrimaries.white.Y = 1.000000f;
- displayPrimaries.white.Z = 1.089058f;
- when(mDisplayManagerInternal.getDisplayNativePrimaries(DEFAULT_DISPLAY))
- .thenReturn(displayPrimaries);
-
- setUpTintController();
- assertWithMessage("Setup with valid SurfaceControl failed")
- .that(mDisplayWhiteBalanceTintController.mSetUp)
- .isTrue();
- }
-
- /**
- * Setup should fail when SurfaceControl setup results in an invalid color transform.
- */
- @Test
- public void displayWhiteBalance_setupWithInvalidSurfaceControlData() {
- // Make SurfaceControl return invalid display primaries
- DisplayPrimaries displayPrimaries = new DisplayPrimaries();
- displayPrimaries.red = new CieXyz();
- displayPrimaries.green = new CieXyz();
- displayPrimaries.blue = new CieXyz();
- displayPrimaries.white = new CieXyz();
- when(mDisplayManagerInternal.getDisplayNativePrimaries(DEFAULT_DISPLAY))
- .thenReturn(displayPrimaries);
-
- setUpTintController();
- assertWithMessage("Setup with invalid SurfaceControl succeeded")
- .that(mDisplayWhiteBalanceTintController.mSetUp)
- .isFalse();
- }
-
- /**
- * Setup should succeed when SurfaceControl setup fails and Resources result in a valid color
- * transform.
- */
- @Test
- public void displayWhiteBalance_setupWithResources() {
- // Use default (valid) Resources
- doReturn(mResources.getStringArray(R.array.config_displayWhiteBalanceDisplayPrimaries))
- .when(mMockedResources)
- .getStringArray(R.array.config_displayWhiteBalanceDisplayPrimaries);
- // Make SurfaceControl setup fail
- when(mDisplayManagerInternal.getDisplayNativePrimaries(DEFAULT_DISPLAY)).thenReturn(null);
-
- setUpTintController();
- assertWithMessage("Setup with valid Resources failed")
- .that(mDisplayWhiteBalanceTintController.mSetUp)
- .isTrue();
- }
-
- /**
- * Setup should fail when SurfaceControl setup fails and Resources result in an invalid color
- * transform.
- */
- @Test
- public void displayWhiteBalance_setupWithInvalidResources() {
- // Use Resources with invalid color data
- doReturn(new String[] {
- "0", "0", "0", // Red X, Y, Z
- "0", "0", "0", // Green X, Y, Z
- "0", "0", "0", // Blue X, Y, Z
- "0", "0", "0", // White X, Y, Z
- })
- .when(mMockedResources)
- .getStringArray(R.array.config_displayWhiteBalanceDisplayPrimaries);
- // Make SurfaceControl setup fail
- when(mDisplayManagerInternal.getDisplayNativePrimaries(DEFAULT_DISPLAY)).thenReturn(null);
-
- setUpTintController();
- assertWithMessage("Setup with invalid Resources succeeded")
- .that(mDisplayWhiteBalanceTintController.mSetUp)
- .isFalse();
- }
-
- /**
- * Matrix should match the precalculated one for given cct and display primaries.
- */
- @Test
- public void displayWhiteBalance_getAndSetMatrix_validateTransformMatrix() {
- DisplayPrimaries displayPrimaries = new DisplayPrimaries();
- displayPrimaries.red = new CieXyz();
- displayPrimaries.red.X = 0.412315f;
- displayPrimaries.red.Y = 0.212600f;
- displayPrimaries.red.Z = 0.019327f;
- displayPrimaries.green = new CieXyz();
- displayPrimaries.green.X = 0.357600f;
- displayPrimaries.green.Y = 0.715200f;
- displayPrimaries.green.Z = 0.119200f;
- displayPrimaries.blue = new CieXyz();
- displayPrimaries.blue.X = 0.180500f;
- displayPrimaries.blue.Y = 0.072200f;
- displayPrimaries.blue.Z = 0.950633f;
- displayPrimaries.white = new CieXyz();
- displayPrimaries.white.X = 0.950456f;
- displayPrimaries.white.Y = 1.000000f;
- displayPrimaries.white.Z = 1.089058f;
- when(mDisplayManagerInternal.getDisplayNativePrimaries(DEFAULT_DISPLAY))
- .thenReturn(displayPrimaries);
-
- setUpTintController();
- assertWithMessage("Setup with valid SurfaceControl failed")
- .that(mDisplayWhiteBalanceTintController.mSetUp)
- .isTrue();
-
- final int cct = 6500;
- mDisplayWhiteBalanceTintController.setMatrix(cct);
- mDisplayWhiteBalanceTintController.setAppliedCct(
- mDisplayWhiteBalanceTintController.getTargetCct());
-
- assertWithMessage("Failed to set temperature")
- .that(mDisplayWhiteBalanceTintController.mCurrentColorTemperature)
- .isEqualTo(cct);
- float[] matrixDwb = mDisplayWhiteBalanceTintController.getMatrix();
- final float[] expectedMatrixDwb = {
- 0.971848f, -0.001421f, 0.000491f, 0.0f,
- 0.028193f, 0.945798f, 0.003207f, 0.0f,
- -0.000042f, -0.000989f, 0.988659f, 0.0f,
- 0.0f, 0.0f, 0.0f, 1.0f
- };
- assertArrayEquals("Unexpected DWB matrix", expectedMatrixDwb, matrixDwb,
- 1e-6f /* tolerance */);
- }
-
- /**
- * Matrix should match the precalculated one for given cct and display primaries.
- */
- @Test
- public void displayWhiteBalance_targetApplied_validateTransformMatrix() {
- DisplayPrimaries displayPrimaries = new DisplayPrimaries();
- displayPrimaries.red = new CieXyz();
- displayPrimaries.red.X = 0.412315f;
- displayPrimaries.red.Y = 0.212600f;
- displayPrimaries.red.Z = 0.019327f;
- displayPrimaries.green = new CieXyz();
- displayPrimaries.green.X = 0.357600f;
- displayPrimaries.green.Y = 0.715200f;
- displayPrimaries.green.Z = 0.119200f;
- displayPrimaries.blue = new CieXyz();
- displayPrimaries.blue.X = 0.180500f;
- displayPrimaries.blue.Y = 0.072200f;
- displayPrimaries.blue.Z = 0.950633f;
- displayPrimaries.white = new CieXyz();
- displayPrimaries.white.X = 0.950456f;
- displayPrimaries.white.Y = 1.000000f;
- displayPrimaries.white.Z = 1.089058f;
- when(mDisplayManagerInternal.getDisplayNativePrimaries(DEFAULT_DISPLAY))
- .thenReturn(displayPrimaries);
-
- setUpTintController();
- assertWithMessage("Setup with valid SurfaceControl failed")
- .that(mDisplayWhiteBalanceTintController.mSetUp)
- .isTrue();
-
- final int cct = 6500;
- mDisplayWhiteBalanceTintController.setTargetCct(cct);
- final float[] matrixDwb = mDisplayWhiteBalanceTintController.computeMatrixForCct(cct);
- mDisplayWhiteBalanceTintController.setAppliedCct(cct);
-
- assertWithMessage("Failed to set temperature")
- .that(mDisplayWhiteBalanceTintController.mCurrentColorTemperature)
- .isEqualTo(cct);
- final float[] expectedMatrixDwb = {
- 0.971848f, -0.001421f, 0.000491f, 0.0f,
- 0.028193f, 0.945798f, 0.003207f, 0.0f,
- -0.000042f, -0.000989f, 0.988659f, 0.0f,
- 0.0f, 0.0f, 0.0f, 1.0f
- };
- assertArrayEquals("Unexpected DWB matrix", expectedMatrixDwb, matrixDwb,
- 1e-6f /* tolerance */);
- }
-
- private void setUpTintController() {
- mDisplayWhiteBalanceTintController = new DisplayWhiteBalanceTintController(
- mDisplayManagerInternal);
- mDisplayWhiteBalanceTintController.setUp(mMockedContext, true);
- mDisplayWhiteBalanceTintController.setActivated(true);
- }
-}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/ApexManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/ApexManagerTest.java
index 5b0e2f3..1ae9124 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/ApexManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/ApexManagerTest.java
@@ -474,15 +474,6 @@
}
@Test
- public void testGetBackingApexFiles_flattenedApex() {
- ApexManager flattenedApexManager = new ApexManager.ApexManagerFlattenedApex();
- final File backingApexFile = flattenedApexManager.getBackingApexFile(
- new File(mMockSystem.system().getApexDirectory(),
- "com.android.apex.cts.shim/app/CtsShim/CtsShim.apk"));
- assertThat(backingApexFile).isNull();
- }
-
- @Test
public void testActiveApexChanged() throws RemoteException {
ApexInfo apex1 = createApexInfo(
"com.apex1", 37, true, true, new File("/data/apex/active/com.apex@37.apex"));
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/ArchiveManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/ArchiveManagerTest.java
new file mode 100644
index 0000000..7b16545
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/ArchiveManagerTest.java
@@ -0,0 +1,250 @@
+/*
+ * 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.pm;
+
+import static android.content.pm.PackageManager.DELETE_KEEP_DATA;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.IntentSender;
+import android.content.pm.LauncherActivityInfo;
+import android.content.pm.LauncherApps;
+import android.content.pm.PackageManager;
+import android.content.pm.VersionedPackage;
+import android.os.Binder;
+import android.os.Build;
+import android.os.Process;
+import android.os.UserHandle;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.pm.pkg.ArchiveState;
+import com.android.server.pm.pkg.PackageStateInternal;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
+
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class ArchiveManagerTest {
+
+ private static final String PACKAGE = "com.example";
+ private static final String CALLER_PACKAGE = "com.vending";
+
+ @Rule
+ public final MockSystemRule mMockSystem = new MockSystemRule();
+
+ @Mock
+ private IntentSender mIntentSender;
+ @Mock
+ private Computer mComputer;
+ @Mock
+ private Context mContext;
+ @Mock
+ private LauncherApps mLauncherApps;
+ @Mock
+ private PackageInstallerService mInstallerService;
+ @Mock
+ private PackageStateInternal mPackageState;
+
+ private final InstallSource mInstallSource =
+ InstallSource.create(
+ CALLER_PACKAGE,
+ CALLER_PACKAGE,
+ CALLER_PACKAGE,
+ Binder.getCallingUid(),
+ CALLER_PACKAGE,
+ /* installerAttributionTag= */ null,
+ /* packageSource= */ 0);
+
+ private final List<LauncherActivityInfo> mLauncherActivityInfos = createLauncherActivities();
+
+ private PackageSetting mPackageSetting;
+
+ private PackageManagerService mPm;
+ private ArchiveManager mArchiveManager;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ mMockSystem.system().stageNominalSystemState();
+ when(mMockSystem.mocks().getInjector().getPackageInstallerService()).thenReturn(
+ mInstallerService);
+ mPm = spy(new PackageManagerService(mMockSystem.mocks().getInjector(),
+ /* factoryTest= */false,
+ MockSystem.Companion.getDEFAULT_VERSION_INFO().fingerprint,
+ /* isEngBuild= */ false,
+ /* isUserDebugBuild= */false,
+ Build.VERSION_CODES.CUR_DEVELOPMENT,
+ Build.VERSION.INCREMENTAL));
+
+ when(mComputer.getPackageStateFiltered(eq(PACKAGE), anyInt(), anyInt())).thenReturn(
+ mPackageState);
+ when(mPackageState.getPackageName()).thenReturn(PACKAGE);
+ when(mPackageState.getInstallSource()).thenReturn(mInstallSource);
+ mPackageSetting = createBasicPackageSetting();
+ when(mMockSystem.mocks().getSettings().getPackageLPr(eq(PACKAGE))).thenReturn(
+ mPackageSetting);
+ when(mContext.getSystemService(LauncherApps.class)).thenReturn(mLauncherApps);
+ when(mLauncherApps.getActivityList(eq(PACKAGE), eq(UserHandle.CURRENT))).thenReturn(
+ mLauncherActivityInfos);
+ doReturn(mComputer).when(mPm).snapshotComputer();
+ when(mComputer.getNameForUid(eq(Binder.getCallingUid()))).thenReturn(CALLER_PACKAGE);
+ mArchiveManager = new ArchiveManager(mContext, mPm);
+ }
+
+ @Test
+ public void archiveApp_callerPackageNameIncorrect() {
+ Exception e = assertThrows(
+ SecurityException.class,
+ () -> mArchiveManager.archiveApp(PACKAGE, "different", UserHandle.CURRENT,
+ mIntentSender)
+ );
+ assertThat(e).hasMessageThat().isEqualTo(
+ String.format(
+ "The callerPackageName %s set by the caller doesn't match the "
+ + "caller's own package name %s.",
+ "different",
+ CALLER_PACKAGE));
+ }
+
+ @Test
+ public void archiveApp_packageNotInstalled() {
+ when(mMockSystem.mocks().getSettings().getPackageLPr(eq(PACKAGE))).thenReturn(
+ null);
+
+ Exception e = assertThrows(
+ PackageManager.NameNotFoundException.class,
+ () -> mArchiveManager.archiveApp(PACKAGE, CALLER_PACKAGE, UserHandle.CURRENT,
+ mIntentSender)
+ );
+ assertThat(e).hasMessageThat().isEqualTo(String.format("Package %s not found.", PACKAGE));
+ }
+
+ @Test
+ public void archiveApp_packageNotInstalledForUser() {
+ mPackageSetting.modifyUserState(UserHandle.CURRENT.getIdentifier()).setInstalled(false);
+
+ Exception e = assertThrows(
+ PackageManager.NameNotFoundException.class,
+ () -> mArchiveManager.archiveApp(PACKAGE, CALLER_PACKAGE, UserHandle.CURRENT,
+ mIntentSender)
+ );
+ assertThat(e).hasMessageThat().isEqualTo(String.format("Package %s not found.", PACKAGE));
+ }
+
+ @Test
+ public void archiveApp_callerNotInstallerOfRecord() {
+ InstallSource otherInstallSource =
+ InstallSource.create(
+ CALLER_PACKAGE,
+ CALLER_PACKAGE,
+ /* installerPackageName= */ "different",
+ Binder.getCallingUid(),
+ CALLER_PACKAGE,
+ /* installerAttributionTag= */ null,
+ /* packageSource= */ 0);
+ when(mPackageState.getInstallSource()).thenReturn(otherInstallSource);
+
+ Exception e = assertThrows(
+ SecurityException.class,
+ () -> mArchiveManager.archiveApp(PACKAGE, CALLER_PACKAGE, Process.myUserHandle(),
+ mIntentSender)
+ );
+ assertThat(e).hasMessageThat().isEqualTo(
+ String.format("Caller is not the installer of record for %s.", PACKAGE));
+ }
+
+ @Test
+ public void archiveApp_callerNotUpdateOwner() {
+ InstallSource otherInstallSource =
+ InstallSource.create(
+ CALLER_PACKAGE,
+ CALLER_PACKAGE,
+ CALLER_PACKAGE,
+ Binder.getCallingUid(),
+ /* updateOwnerPackageName= */ "different",
+ /* installerAttributionTag= */ null,
+ /* packageSource= */ 0);
+ when(mPackageState.getInstallSource()).thenReturn(otherInstallSource);
+
+ Exception e = assertThrows(
+ SecurityException.class,
+ () -> mArchiveManager.archiveApp(PACKAGE, CALLER_PACKAGE, UserHandle.CURRENT,
+ mIntentSender)
+ );
+ assertThat(e).hasMessageThat().isEqualTo(
+ String.format("Caller is not the update owner for %s.", PACKAGE));
+ }
+
+ @Test
+ public void archiveApp_success() throws PackageManager.NameNotFoundException {
+ List<ArchiveState.ArchiveActivityInfo> activityInfos = new ArrayList<>();
+ for (LauncherActivityInfo mainActivity : createLauncherActivities()) {
+ // TODO(b/278553670) Extract and store launcher icons
+ ArchiveState.ArchiveActivityInfo activityInfo = new ArchiveState.ArchiveActivityInfo(
+ mainActivity.getLabel().toString(),
+ Path.of("/TODO"), null);
+ activityInfos.add(activityInfo);
+ }
+
+ mArchiveManager.archiveApp(PACKAGE, CALLER_PACKAGE, UserHandle.CURRENT, mIntentSender);
+ verify(mInstallerService).uninstall(
+ eq(new VersionedPackage(PACKAGE, PackageManager.VERSION_CODE_HIGHEST)),
+ eq(CALLER_PACKAGE), eq(DELETE_KEEP_DATA), eq(mIntentSender),
+ eq(UserHandle.CURRENT.getIdentifier()));
+ assertThat(mPackageSetting.readUserState(
+ UserHandle.CURRENT.getIdentifier()).getArchiveState()).isEqualTo(
+ new ArchiveState(activityInfos, CALLER_PACKAGE));
+ }
+
+ private static List<LauncherActivityInfo> createLauncherActivities() {
+ LauncherActivityInfo activity1 = mock(LauncherActivityInfo.class);
+ when(activity1.getLabel()).thenReturn("activity1");
+ LauncherActivityInfo activity2 = mock(LauncherActivityInfo.class);
+ when(activity2.getLabel()).thenReturn("activity2");
+ return List.of(activity1, activity2);
+ }
+
+ private PackageSetting createBasicPackageSetting() {
+ return new PackageSettingBuilder()
+ .setName(PACKAGE).setCodePath("/data/app/" + PACKAGE + "-randompath")
+ .setInstallState(UserHandle.CURRENT.getIdentifier(), /* installed= */ true)
+ .build();
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageMonitorCallbackHelperTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/PackageMonitorCallbackHelperTest.java
index 6f2cca5..f206333 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageMonitorCallbackHelperTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageMonitorCallbackHelperTest.java
@@ -28,10 +28,13 @@
import android.content.Intent;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
+import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
import android.os.IRemoteCallback;
import android.os.Looper;
+import android.os.Process;
+import android.util.SparseArray;
import org.junit.After;
import org.junit.Before;
@@ -78,7 +81,7 @@
mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED,
FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{0} /* userIds */,
- null /* instantUserIds */);
+ null /* instantUserIds */, null /* broadcastAllowList */);
verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).never()).sendResult(any());
}
@@ -87,9 +90,11 @@
public void testUnregisterPackageMonitorCallback_callbackShouldNotCalled() throws Exception {
IRemoteCallback callback = createMockPackageMonitorCallback();
- mPackageMonitorCallbackHelper.registerPackageMonitorCallback(callback, 0 /* userId */);
+ mPackageMonitorCallbackHelper.registerPackageMonitorCallback(callback, 0 /* userId */,
+ Binder.getCallingUid());
mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED,
- FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{0}, null /* instantUserIds */);
+ FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{0}, null /* instantUserIds */,
+ null /* broadcastAllowList */);
verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).times(1)).sendResult(any());
@@ -97,7 +102,7 @@
mPackageMonitorCallbackHelper.unregisterPackageMonitorCallback(callback);
mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED,
FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{0} /* userIds */,
- null /* instantUserIds */);
+ null /* instantUserIds */, null /* broadcastAllowList */);
verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).never()).sendResult(any());
}
@@ -106,10 +111,11 @@
public void testRegisterPackageMonitorCallback_callbackCalled() throws Exception {
IRemoteCallback callback = createMockPackageMonitorCallback();
- mPackageMonitorCallbackHelper.registerPackageMonitorCallback(callback, 0 /* userId */);
+ mPackageMonitorCallbackHelper.registerPackageMonitorCallback(callback, 0 /* userId */,
+ Binder.getCallingUid());
mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED,
FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{0} /* userIds */,
- null /* instantUserIds */);
+ null /* instantUserIds */, null /* broadcastAllowList */);
ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).times(1)).sendResult(
@@ -128,11 +134,12 @@
IRemoteCallback callback = createMockPackageMonitorCallback();
// Register for user 0
- mPackageMonitorCallbackHelper.registerPackageMonitorCallback(callback, 0 /* userId */);
+ mPackageMonitorCallbackHelper.registerPackageMonitorCallback(callback, 0 /* userId */,
+ Binder.getCallingUid());
// Notify for user 10
mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED,
FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{10} /* userIds */,
- null /* instantUserIds */);
+ null /* instantUserIds */, null /* broadcastAllowList */);
verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).never()).sendResult(any());
}
@@ -144,10 +151,12 @@
ArrayList<String> components = new ArrayList<>();
String component1 = FAKE_PACKAGE_NAME + "/.Component1";
components.add(component1);
- mPackageMonitorCallbackHelper.registerPackageMonitorCallback(callback, 0 /* userId */);
+ mPackageMonitorCallbackHelper.registerPackageMonitorCallback(callback, 0 /* userId */,
+ Binder.getCallingUid());
mPackageMonitorCallbackHelper.notifyPackageChanged(FAKE_PACKAGE_NAME,
false /* dontKillApp */, components, FAKE_PACKAGE_UID, null /* reason */,
- new int[]{0} /* userIds */, null /* instantUserIds */);
+ new int[]{0} /* userIds */, null /* instantUserIds */,
+ null /* broadcastAllowList */);
ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).times(1)).sendResult(
@@ -171,10 +180,11 @@
public void testNotifyPackageAddedForNewUsers_callbackCalled() throws Exception {
IRemoteCallback callback = createMockPackageMonitorCallback();
- mPackageMonitorCallbackHelper.registerPackageMonitorCallback(callback, 0 /* userId */);
+ mPackageMonitorCallbackHelper.registerPackageMonitorCallback(callback, 0 /* userId */,
+ Binder.getCallingUid());
mPackageMonitorCallbackHelper.notifyPackageAddedForNewUsers(FAKE_PACKAGE_NAME,
FAKE_PACKAGE_UID, new int[]{0} /* userIds */, new int[0],
- PackageInstaller.DATA_LOADER_TYPE_STREAMING);
+ PackageInstaller.DATA_LOADER_TYPE_STREAMING, null /* broadcastAllowList */);
ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).times(1)).sendResult(
@@ -194,7 +204,8 @@
public void testNotifyResourcesChanged_callbackCalled() throws Exception {
IRemoteCallback callback = createMockPackageMonitorCallback();
- mPackageMonitorCallbackHelper.registerPackageMonitorCallback(callback, 0 /* userId */);
+ mPackageMonitorCallbackHelper.registerPackageMonitorCallback(callback, 0 /* userId */,
+ Binder.getCallingUid());
mPackageMonitorCallbackHelper.notifyResourcesChanged(true /* mediaStatus */,
true /* replacing */, new String[]{FAKE_PACKAGE_NAME},
new int[]{FAKE_PACKAGE_UID} /* uids */);
@@ -221,6 +232,68 @@
assertThat(pkgNames[0]).isEqualTo(FAKE_PACKAGE_NAME);
}
+ @Test
+ public void testPackageMonitorCallback_onUserRemoved_callbackNotCalled() throws Exception {
+ IRemoteCallback callback = createMockPackageMonitorCallback();
+ mPackageMonitorCallbackHelper.registerPackageMonitorCallback(callback, 10 /* userId */,
+ Binder.getCallingUid());
+
+ mPackageMonitorCallbackHelper.onUserRemoved(10);
+ mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED,
+ FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{10} /* userIds */,
+ null /* instantUserIds */, null /* broadcastAllowList */);
+
+ verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).never()).sendResult(any());
+ }
+
+ @Test
+ public void testRegisterPackageMonitorCallbackInAllowList_callbackShouldCalled()
+ throws Exception {
+ IRemoteCallback callback = createMockPackageMonitorCallback();
+ SparseArray<int[]> broadcastAllowList = new SparseArray<>();
+ broadcastAllowList.put(0, new int[] {Binder.getCallingUid()});
+
+ mPackageMonitorCallbackHelper.registerPackageMonitorCallback(callback, 0 /* userId */,
+ Binder.getCallingUid());
+ mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED,
+ FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{0} /* userIds */,
+ null /* instantUserIds */, broadcastAllowList);
+
+ verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).times(1)).sendResult(any());
+ }
+
+ @Test
+ public void testRegisterPackageMonitorCallbackNotInAllowList_callbackShouldNotCalled()
+ throws Exception {
+ IRemoteCallback callback = createMockPackageMonitorCallback();
+ SparseArray<int[]> broadcastAllowList = new SparseArray<>();
+ broadcastAllowList.put(0, new int[] {12345});
+
+ mPackageMonitorCallbackHelper.registerPackageMonitorCallback(callback, 0 /* userId */,
+ Binder.getCallingUid());
+ mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED,
+ FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{0} /* userIds */,
+ null /* instantUserIds */, broadcastAllowList);
+
+ verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).never()).sendResult(any());
+ }
+
+ @Test
+ public void testRegisterPackageMonitorCallbackNotInAllowListSystemUid_callbackShouldCalled()
+ throws Exception {
+ IRemoteCallback callback = createMockPackageMonitorCallback();
+ SparseArray<int[]> broadcastAllowList = new SparseArray<>();
+ broadcastAllowList.put(0, new int[] {12345});
+
+ mPackageMonitorCallbackHelper.registerPackageMonitorCallback(callback, 0 /* userId */,
+ Process.SYSTEM_UID);
+ mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED,
+ FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{0} /* userIds */,
+ null /* instantUserIds */, broadcastAllowList);
+
+ verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).times(1)).sendResult(any());
+ }
+
private IRemoteCallback createMockPackageMonitorCallback() {
return spy(new IRemoteCallback.Stub() {
@Override
diff --git a/services/tests/mockingservicestests/src/com/android/server/power/PowerManagerServiceMockingTest.java b/services/tests/mockingservicestests/src/com/android/server/power/PowerManagerServiceMockingTest.java
deleted file mode 100644
index 0f2c27c..0000000
--- a/services/tests/mockingservicestests/src/com/android/server/power/PowerManagerServiceMockingTest.java
+++ /dev/null
@@ -1,316 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.power;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.app.ActivityManagerInternal;
-import android.attention.AttentionManagerInternal;
-import android.content.Context;
-import android.content.ContextWrapper;
-import android.content.res.Resources;
-import android.hardware.SensorManager;
-import android.hardware.devicestate.DeviceStateManager;
-import android.hardware.devicestate.DeviceStateManager.DeviceStateCallback;
-import android.hardware.display.AmbientDisplayConfiguration;
-import android.hardware.display.DisplayManagerInternal;
-import android.os.BatteryManagerInternal;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.PowerManager;
-import android.os.PowerSaveState;
-import android.os.test.TestLooper;
-import android.provider.Settings;
-import android.service.dreams.DreamManagerInternal;
-import android.test.mock.MockContentResolver;
-import android.view.Display;
-import android.view.DisplayInfo;
-
-import androidx.test.InstrumentationRegistry;
-
-import com.android.internal.app.IBatteryStats;
-import com.android.internal.util.test.FakeSettingsProvider;
-import com.android.server.LocalServices;
-import com.android.server.SystemService;
-import com.android.server.lights.LightsManager;
-import com.android.server.policy.WindowManagerPolicy;
-import com.android.server.power.PowerManagerService.BatteryReceiver;
-import com.android.server.power.PowerManagerService.Injector;
-import com.android.server.power.PowerManagerService.NativeWrapper;
-import com.android.server.power.PowerManagerService.UserSwitchedReceiver;
-import com.android.server.power.batterysaver.BatterySaverController;
-import com.android.server.power.batterysaver.BatterySaverPolicy;
-import com.android.server.power.batterysaver.BatterySaverStateMachine;
-import com.android.server.power.batterysaver.BatterySavingStats;
-import com.android.server.testutils.OffsettableClock;
-
-import java.util.concurrent.Executor;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-/**
- * Tests for {@link com.android.server.power.PowerManagerService}.
- *
- * Build/Install/Run:
- * atest FrameworksServicesTests:PowerManagerServiceMockingTest
- */
-public class PowerManagerServiceMockingTest {
- private static final String SYSTEM_PROPERTY_QUIESCENT = "ro.boot.quiescent";
- private static final String SYSTEM_PROPERTY_REBOOT_REASON = "sys.boot.reason";
-
- private static final float BRIGHTNESS_FACTOR = 0.7f;
- private static final boolean BATTERY_SAVER_ENABLED = true;
-
- @Mock private BatterySaverController mBatterySaverControllerMock;
- @Mock private BatterySaverPolicy mBatterySaverPolicyMock;
- @Mock private BatterySaverStateMachine mBatterySaverStateMachineMock;
- @Mock private LightsManager mLightsManagerMock;
- @Mock private DisplayManagerInternal mDisplayManagerInternalMock;
- @Mock private BatteryManagerInternal mBatteryManagerInternalMock;
- @Mock private ActivityManagerInternal mActivityManagerInternalMock;
- @Mock private AttentionManagerInternal mAttentionManagerInternalMock;
- @Mock private DreamManagerInternal mDreamManagerInternalMock;
- @Mock private PowerManagerService.NativeWrapper mNativeWrapperMock;
- @Mock private Notifier mNotifierMock;
- @Mock private WirelessChargerDetector mWirelessChargerDetectorMock;
- @Mock private AmbientDisplayConfiguration mAmbientDisplayConfigurationMock;
- @Mock private SystemPropertiesWrapper mSystemPropertiesMock;
- @Mock private DeviceStateManager mDeviceStateManagerMock;
-
- @Mock
- private InattentiveSleepWarningController mInattentiveSleepWarningControllerMock;
-
- private PowerManagerService mService;
- private PowerSaveState mPowerSaveState;
- private ContextWrapper mContextSpy;
- private BatteryReceiver mBatteryReceiver;
- private UserSwitchedReceiver mUserSwitchedReceiver;
- private Resources mResourcesSpy;
- private OffsettableClock mClock;
- private TestLooper mTestLooper;
-
- @Before
- public void setUp() throws Exception {
- MockitoAnnotations.initMocks(this);
- FakeSettingsProvider.clearSettingsProvider();
-
- mPowerSaveState = new PowerSaveState.Builder()
- .setBatterySaverEnabled(BATTERY_SAVER_ENABLED)
- .setBrightnessFactor(BRIGHTNESS_FACTOR)
- .build();
- when(mBatterySaverPolicyMock.getBatterySaverPolicy(
- eq(PowerManager.ServiceType.SCREEN_BRIGHTNESS)))
- .thenReturn(mPowerSaveState);
- when(mBatteryManagerInternalMock.isPowered(anyInt())).thenReturn(false);
- when(mInattentiveSleepWarningControllerMock.isShown()).thenReturn(false);
- when(mDisplayManagerInternalMock.requestPowerState(anyInt(), any(), anyBoolean()))
- .thenReturn(true);
- when(mSystemPropertiesMock.get(eq(SYSTEM_PROPERTY_QUIESCENT), anyString())).thenReturn("");
- when(mAmbientDisplayConfigurationMock.ambientDisplayAvailable()).thenReturn(true);
-
- addLocalServiceMock(LightsManager.class, mLightsManagerMock);
- addLocalServiceMock(DisplayManagerInternal.class, mDisplayManagerInternalMock);
- addLocalServiceMock(BatteryManagerInternal.class, mBatteryManagerInternalMock);
- addLocalServiceMock(ActivityManagerInternal.class, mActivityManagerInternalMock);
- addLocalServiceMock(AttentionManagerInternal.class, mAttentionManagerInternalMock);
- addLocalServiceMock(DreamManagerInternal.class, mDreamManagerInternalMock);
-
- mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getContext()));
- mResourcesSpy = spy(mContextSpy.getResources());
- when(mContextSpy.getResources()).thenReturn(mResourcesSpy);
-
- MockContentResolver cr = new MockContentResolver(mContextSpy);
- cr.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
- when(mContextSpy.getContentResolver()).thenReturn(cr);
-
- when(mContextSpy.getSystemService(DeviceStateManager.class))
- .thenReturn(mDeviceStateManagerMock);
-
- Settings.Global.putInt(mContextSpy.getContentResolver(),
- Settings.Global.STAY_ON_WHILE_PLUGGED_IN, 0);
-
- mClock = new OffsettableClock.Stopped();
- mTestLooper = new TestLooper(mClock::now);
- }
-
- private PowerManagerService createService() {
- mService = new PowerManagerService(mContextSpy, new Injector() {
- @Override
- Notifier createNotifier(Looper looper, Context context, IBatteryStats batteryStats,
- SuspendBlocker suspendBlocker, WindowManagerPolicy policy,
- FaceDownDetector faceDownDetector, ScreenUndimDetector screenUndimDetector,
- Executor executor) {
- return mNotifierMock;
- }
-
- @Override
- SuspendBlocker createSuspendBlocker(PowerManagerService service, String name) {
- return super.createSuspendBlocker(service, name);
- }
-
- @Override
- BatterySaverPolicy createBatterySaverPolicy(
- Object lock, Context context, BatterySavingStats batterySavingStats) {
- return mBatterySaverPolicyMock;
- }
-
- @Override
- BatterySaverController createBatterySaverController(
- Object lock, Context context, BatterySaverPolicy batterySaverPolicy,
- BatterySavingStats batterySavingStats) {
- return mBatterySaverControllerMock;
- }
-
- @Override
- BatterySaverStateMachine createBatterySaverStateMachine(Object lock, Context context,
- BatterySaverController batterySaverController) {
- return mBatterySaverStateMachineMock;
- }
-
- @Override
- NativeWrapper createNativeWrapper() {
- return mNativeWrapperMock;
- }
-
- @Override
- WirelessChargerDetector createWirelessChargerDetector(
- SensorManager sensorManager, SuspendBlocker suspendBlocker, Handler handler) {
- return mWirelessChargerDetectorMock;
- }
-
- @Override
- AmbientDisplayConfiguration createAmbientDisplayConfiguration(Context context) {
- return mAmbientDisplayConfigurationMock;
- }
-
- @Override
- InattentiveSleepWarningController createInattentiveSleepWarningController() {
- return mInattentiveSleepWarningControllerMock;
- }
-
- @Override
- public SystemPropertiesWrapper createSystemPropertiesWrapper() {
- return mSystemPropertiesMock;
- }
-
- @Override
- PowerManagerService.Clock createClock() {
- return new PowerManagerService.Clock() {
- @Override
- public long uptimeMillis() {
- return mClock.now();
- }
-
- @Override
- public long elapsedRealtime() {
- return mClock.now();
- }
- };
- }
-
- @Override
- Handler createHandler(Looper looper, Handler.Callback callback) {
- return new Handler(mTestLooper.getLooper(), callback);
- }
-
- @Override
- void invalidateIsInteractiveCaches() {
- // Avoids an SELinux failure.
- }
- });
- return mService;
- }
-
- @After
- public void tearDown() throws Exception {
- LocalServices.removeServiceForTest(LightsManager.class);
- LocalServices.removeServiceForTest(DisplayManagerInternal.class);
- LocalServices.removeServiceForTest(BatteryManagerInternal.class);
- LocalServices.removeServiceForTest(ActivityManagerInternal.class);
- LocalServices.removeServiceForTest(AttentionManagerInternal.class);
- LocalServices.removeServiceForTest(DreamManagerInternal.class);
- FakeSettingsProvider.clearSettingsProvider();
- }
-
- /**
- * Creates a mock and registers it to {@link LocalServices}.
- */
- private static <T> void addLocalServiceMock(Class<T> clazz, T mock) {
- LocalServices.removeServiceForTest(clazz);
- LocalServices.addService(clazz, mock);
- }
-
- private void advanceTime(long timeMs) {
- mClock.fastForward(timeMs);
- mTestLooper.dispatchAll();
- }
-
- @Test
- public void testUserActivityOnDeviceStateChange() {
- createService();
- mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY);
- mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
-
- final DisplayInfo info = new DisplayInfo();
- info.displayGroupId = Display.DEFAULT_DISPLAY_GROUP;
- when(mDisplayManagerInternalMock.getDisplayInfo(Display.DEFAULT_DISPLAY)).thenReturn(info);
-
- final ArgumentCaptor<DeviceStateCallback> deviceStateCallbackCaptor =
- ArgumentCaptor.forClass(DeviceStateCallback.class);
- verify(mDeviceStateManagerMock).registerCallback(any(),
- deviceStateCallbackCaptor.capture());
-
- // Advance the time 10001 and verify that the device thinks it has been idle
- // for just less than that.
- mService.onUserActivity();
- advanceTime(10001);
- assertThat(mService.wasDeviceIdleForInternal(10000)).isTrue();
-
- // Send a display state change event and advance the clock 10.
- final DeviceStateCallback deviceStateCallback = deviceStateCallbackCaptor.getValue();
- deviceStateCallback.onStateChanged(1);
- final long timeToAdvance = 10;
- advanceTime(timeToAdvance);
-
- // Ensure that the device has been idle for only 10 (doesn't include the idle time
- // before the display state event).
- assertThat(mService.wasDeviceIdleForInternal(timeToAdvance - 1)).isTrue();
- assertThat(mService.wasDeviceIdleForInternal(timeToAdvance)).isFalse();
-
- // Send the same state and ensure that does not trigger an update.
- deviceStateCallback.onStateChanged(1);
- advanceTime(timeToAdvance);
- final long newTime = timeToAdvance * 2;
-
- assertThat(mService.wasDeviceIdleForInternal(newTime - 1)).isTrue();
- assertThat(mService.wasDeviceIdleForInternal(newTime)).isFalse();
- }
-}
diff --git a/services/tests/powerservicetests/Android.bp b/services/tests/powerservicetests/Android.bp
new file mode 100644
index 0000000..9384015
--- /dev/null
+++ b/services/tests/powerservicetests/Android.bp
@@ -0,0 +1,43 @@
+package {
+ // See: http://go/android-license-faq
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test {
+ name: "PowerServiceTests",
+
+ srcs: [
+ "src/**/*.java",
+ ],
+
+ static_libs: [
+ "frameworks-base-testutils",
+ "platform-compat-test-rules",
+ "platform-test-annotations",
+ "services.core",
+ "servicestests-utils",
+ "testables",
+ ],
+
+ libs: [
+ "android.test.mock",
+ ],
+
+ defaults: [
+ "modules-utils-testable-device-config-defaults",
+ ],
+
+ platform_apis: true,
+ test_suites: [
+ "device-tests",
+ "automotive-tests",
+ ],
+
+ certificate: "platform",
+
+ dxflags: ["--multi-dex"],
+
+ optimize: {
+ enabled: false,
+ },
+}
diff --git a/services/tests/powerservicetests/AndroidManifest.xml b/services/tests/powerservicetests/AndroidManifest.xml
new file mode 100644
index 0000000..26d9eec
--- /dev/null
+++ b/services/tests/powerservicetests/AndroidManifest.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.frameworks.powerservicetests">
+
+ <!-- Permissions -->
+ <uses-permission android:name="android.permission.DEVICE_POWER"/>
+ <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS"/>
+ <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL"/>
+ <uses-permission android:name="android.permission.READ_DEVICE_CONFIG"/>
+ <uses-permission android:name="android.permission.READ_DREAM_STATE"/>
+ <uses-permission android:name="android.permission.READ_DREAM_SUPPRESSION"/>
+ <uses-permission android:name="android.permission.STATUS_BAR_SERVICE"/>
+ <uses-permission android:name="android.permission.UPDATE_APP_OPS_STATS"/>
+ <uses-permission android:name="android.permission.UPDATE_DEVICE_STATS"/>
+ <uses-permission android:name="android.permission.WAKE_LOCK"/>
+ <uses-permission android:name="android.permission.WRITE_DREAM_STATE"/>
+
+ <application android:debuggable="true"
+ android:testOnly="true">
+ <uses-library android:name="android.test.mock" android:required="true" />
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:label="Power Service Tests"
+ android:targetPackage="com.android.frameworks.powerservicetests" />
+</manifest>
diff --git a/services/tests/powerservicetests/AndroidTest.xml b/services/tests/powerservicetests/AndroidTest.xml
new file mode 100644
index 0000000..fa6645b
--- /dev/null
+++ b/services/tests/powerservicetests/AndroidTest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<configuration description="Runs Power Service 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="PowerServiceTests.apk" />
+ </target_preparer>
+
+ <option name="test-tag" value="PowerServiceTests" />
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+ <option name="package" value="com.android.frameworks.powerservicetests" />
+ <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+ <option name="hidden-api-checks" value="false" />
+ </test>
+</configuration>
diff --git a/services/tests/powerservicetests/OWNERS b/services/tests/powerservicetests/OWNERS
new file mode 100644
index 0000000..aff0930
--- /dev/null
+++ b/services/tests/powerservicetests/OWNERS
@@ -0,0 +1,4 @@
+# Bug component: 46788
+
+include /services/core/java/com/android/server/power/OWNERS
+
diff --git a/services/tests/servicestests/src/com/android/server/power/LowPowerStandbyControllerTest.java b/services/tests/powerservicetests/src/com/android/server/power/LowPowerStandbyControllerTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/power/LowPowerStandbyControllerTest.java
rename to services/tests/powerservicetests/src/com/android/server/power/LowPowerStandbyControllerTest.java
diff --git a/services/tests/servicestests/src/com/android/server/power/NotifierTest.java b/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/power/NotifierTest.java
rename to services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java
diff --git a/services/tests/servicestests/src/com/android/server/power/PowerGroupTest.java b/services/tests/powerservicetests/src/com/android/server/power/PowerGroupTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/power/PowerGroupTest.java
rename to services/tests/powerservicetests/src/com/android/server/power/PowerGroupTest.java
diff --git a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java
similarity index 97%
rename from services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
rename to services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java
index c80ff45..9463b2d 100644
--- a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
+++ b/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java
@@ -60,6 +60,8 @@
import android.content.PermissionChecker;
import android.content.res.Resources;
import android.hardware.SensorManager;
+import android.hardware.devicestate.DeviceStateManager;
+import android.hardware.devicestate.DeviceStateManager.DeviceStateCallback;
import android.hardware.display.AmbientDisplayConfiguration;
import android.hardware.display.DisplayManagerInternal;
import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
@@ -160,6 +162,7 @@
@Mock private InattentiveSleepWarningController mInattentiveSleepWarningControllerMock;
@Mock private PowerManagerService.PermissionCheckerWrapper mPermissionCheckerWrapperMock;
@Mock private PowerManagerService.PowerPropertiesWrapper mPowerPropertiesWrapper;
+ @Mock private DeviceStateManager mDeviceStateManagerMock;
@Rule public TestRule compatChangeRule = new PlatformCompatChangeRule();
@@ -2710,4 +2713,48 @@
verify(mNotifierMock, never()).onUserActivity(anyInt(), anyInt(), anyInt());
}
+ @Test
+ public void testUserActivityOnDeviceStateChange() {
+ when(mContextSpy.getSystemService(DeviceStateManager.class))
+ .thenReturn(mDeviceStateManagerMock);
+
+ createService();
+ mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY);
+ mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
+
+ final DisplayInfo info = new DisplayInfo();
+ info.displayGroupId = Display.DEFAULT_DISPLAY_GROUP;
+ when(mDisplayManagerInternalMock.getDisplayInfo(Display.DEFAULT_DISPLAY)).thenReturn(info);
+
+ final ArgumentCaptor<DeviceStateCallback> deviceStateCallbackCaptor =
+ ArgumentCaptor.forClass(DeviceStateCallback.class);
+ verify(mDeviceStateManagerMock).registerCallback(any(),
+ deviceStateCallbackCaptor.capture());
+
+ // Advance the time 10001 and verify that the device thinks it has been idle
+ // for just less than that.
+ mService.onUserActivity();
+ advanceTime(10001);
+ assertThat(mService.wasDeviceIdleForInternal(10000)).isTrue();
+
+ // Send a display state change event and advance the clock 10.
+ final DeviceStateCallback deviceStateCallback = deviceStateCallbackCaptor.getValue();
+ deviceStateCallback.onStateChanged(1);
+ final long timeToAdvance = 10;
+ advanceTime(timeToAdvance);
+
+ // Ensure that the device has been idle for only 10 (doesn't include the idle time
+ // before the display state event).
+ assertThat(mService.wasDeviceIdleForInternal(timeToAdvance - 1)).isTrue();
+ assertThat(mService.wasDeviceIdleForInternal(timeToAdvance)).isFalse();
+
+ // Send the same state and ensure that does not trigger an update.
+ deviceStateCallback.onStateChanged(1);
+ advanceTime(timeToAdvance);
+ final long newTime = timeToAdvance * 2;
+
+ assertThat(mService.wasDeviceIdleForInternal(newTime - 1)).isTrue();
+ assertThat(mService.wasDeviceIdleForInternal(newTime)).isFalse();
+ }
+
}
diff --git a/services/tests/servicestests/src/com/android/server/power/ShutdownCheckPointsTest.java b/services/tests/powerservicetests/src/com/android/server/power/ShutdownCheckPointsTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/power/ShutdownCheckPointsTest.java
rename to services/tests/powerservicetests/src/com/android/server/power/ShutdownCheckPointsTest.java
diff --git a/services/tests/servicestests/src/com/android/server/power/ShutdownThreadTest.java b/services/tests/powerservicetests/src/com/android/server/power/ShutdownThreadTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/power/ShutdownThreadTest.java
rename to services/tests/powerservicetests/src/com/android/server/power/ShutdownThreadTest.java
diff --git a/services/tests/servicestests/src/com/android/server/power/WakeLockLogTest.java b/services/tests/powerservicetests/src/com/android/server/power/WakeLockLogTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/power/WakeLockLogTest.java
rename to services/tests/powerservicetests/src/com/android/server/power/WakeLockLogTest.java
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index 4edb167..173c5f5 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -34,6 +34,7 @@
"services.core",
"services.credentials",
"services.devicepolicy",
+ "services.flags",
"services.net",
"services.people",
"services.usage",
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
index 2d4bf14..32d0c98 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
@@ -44,8 +44,10 @@
import android.graphics.PointF;
import android.os.Handler;
import android.os.Message;
+import android.os.UserHandle;
import android.os.VibrationEffect;
import android.os.Vibrator;
+import android.provider.Settings;
import android.testing.TestableContext;
import android.util.DebugUtils;
import android.view.InputDevice;
@@ -140,8 +142,6 @@
@Mock
WindowMagnificationPromptController mWindowMagnificationPromptController;
@Mock
- AccessibilityManagerService mMockAccessibilityManagerService;
- @Mock
AccessibilityTraceManager mMockTraceManager;
@Rule
@@ -153,6 +153,8 @@
private long mLastDownTime = Integer.MIN_VALUE;
+ private float mOriginalMagnificationPersistedScale;
+
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
@@ -166,6 +168,13 @@
when(mockController.newValueAnimator()).thenReturn(new ValueAnimator());
when(mockController.getAnimationDuration()).thenReturn(1000L);
when(mockWindowManager.setMagnificationCallbacks(eq(DISPLAY_0), any())).thenReturn(true);
+ mOriginalMagnificationPersistedScale = Settings.Secure.getFloatForUser(
+ mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, 2.0f,
+ UserHandle.USER_SYSTEM);
+ Settings.Secure.putFloatForUser(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, 2.0f,
+ UserHandle.USER_SYSTEM);
mFullScreenMagnificationController = new FullScreenMagnificationController(
mockController,
new Object(),
@@ -192,6 +201,10 @@
mMgh.onDestroy();
mFullScreenMagnificationController.unregister(DISPLAY_0);
verify(mWindowMagnificationPromptController).onDestroy();
+ Settings.Secure.putFloatForUser(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE,
+ mOriginalMagnificationPersistedScale,
+ UserHandle.USER_SYSTEM);
}
@NonNull
@@ -525,10 +538,9 @@
final float threshold = FullScreenMagnificationGestureHandler.PanningScalingState
.CHECK_DETECTING_PASS_PERSISTED_SCALE_THRESHOLD;
final float persistedScale = (1.0f + threshold) * scale + 1.0f;
- mFullScreenMagnificationController.setScale(DISPLAY_0, persistedScale, DEFAULT_X,
- DEFAULT_Y, /* animate= */ false,
- AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
- mFullScreenMagnificationController.persistScale(DISPLAY_0);
+ Settings.Secure.putFloatForUser(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, persistedScale,
+ UserHandle.USER_SYSTEM);
mFullScreenMagnificationController.setScale(DISPLAY_0, scale, DEFAULT_X,
DEFAULT_Y, /* animate= */ false,
AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
@@ -547,10 +559,9 @@
final float threshold = FullScreenMagnificationGestureHandler.PanningScalingState
.CHECK_DETECTING_PASS_PERSISTED_SCALE_THRESHOLD;
final float persistedScale = (1.0f + threshold) * scale - 0.1f;
- mFullScreenMagnificationController.setScale(DISPLAY_0, persistedScale, DEFAULT_X,
- DEFAULT_Y, /* animate= */ false,
- AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
- mFullScreenMagnificationController.persistScale(DISPLAY_0);
+ Settings.Secure.putFloatForUser(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, persistedScale,
+ UserHandle.USER_SYSTEM);
mFullScreenMagnificationController.setScale(DISPLAY_0, scale, DEFAULT_X,
DEFAULT_Y, /* animate= */ false,
AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java
index 27e6ef1..bbbab21 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java
@@ -66,7 +66,6 @@
import com.android.server.statusbar.StatusBarManagerInternal;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.Mockito;
@@ -308,7 +307,6 @@
MagnificationScaleProvider.MAX_SCALE);
}
- @Ignore("b/278816260: We could refer to b/182561174#comment4 for solution.")
@Test
public void logTrackingTypingFocus_processScroll_logDuration() {
WindowMagnificationManager spyWindowMagnificationManager = spy(mWindowMagnificationManager);
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
index 8346050..0cfddd3 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
@@ -106,7 +106,7 @@
@Mock private KeyStore mKeyStore;
@Mock private AuthSession.ClientDeathReceiver mClientDeathReceiver;
@Mock private BiometricFrameworkStatsLogger mBiometricFrameworkStatsLogger;
- @Mock BiometricSensorPrivacy mBiometricSensorPrivacy;
+ @Mock private BiometricCameraManager mBiometricCameraManager;
private Random mRandom;
private IBinder mToken;
@@ -609,7 +609,7 @@
TEST_PACKAGE,
checkDevicePolicyManager,
mContext,
- mBiometricSensorPrivacy);
+ mBiometricCameraManager);
}
private AuthSession createAuthSession(List<BiometricSensor> sensors,
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
index 41f7dbc..fc62e75 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
@@ -16,6 +16,7 @@
package com.android.server.biometrics;
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
import static android.hardware.biometrics.BiometricManager.Authenticators;
import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS;
@@ -113,6 +114,10 @@
private static final String ERROR_UNABLE_TO_PROCESS = "error_unable_to_process";
private static final String ERROR_USER_CANCELED = "error_user_canceled";
private static final String ERROR_LOCKOUT = "error_lockout";
+ private static final String FACE_SUBTITLE = "face_subtitle";
+ private static final String FINGERPRINT_SUBTITLE = "fingerprint_subtitle";
+ private static final String DEFAULT_SUBTITLE = "default_subtitle";
+
private static final String FINGERPRINT_ACQUIRED_SENSOR_DIRTY = "sensor_dirty";
@@ -151,6 +156,8 @@
private AuthSessionCoordinator mAuthSessionCoordinator;
@Mock
private UserManager mUserManager;
+ @Mock
+ private BiometricCameraManager mBiometricCameraManager;
BiometricContextProvider mBiometricContextProvider;
@@ -177,6 +184,7 @@
when(mInjector.getDevicePolicyManager(any())).thenReturn(mDevicePolicyManager);
when(mInjector.getRequestGenerator()).thenReturn(() -> TEST_REQUEST_ID);
when(mInjector.getUserManager(any())).thenReturn(mUserManager);
+ when(mInjector.getBiometricCameraManager(any())).thenReturn(mBiometricCameraManager);
when(mResources.getString(R.string.biometric_error_hw_unavailable))
.thenReturn(ERROR_HW_UNAVAILABLE);
@@ -188,6 +196,12 @@
.thenReturn(ERROR_NOT_RECOGNIZED);
when(mResources.getString(R.string.biometric_error_user_canceled))
.thenReturn(ERROR_USER_CANCELED);
+ when(mContext.getString(R.string.biometric_dialog_face_subtitle))
+ .thenReturn(FACE_SUBTITLE);
+ when(mContext.getString(R.string.biometric_dialog_fingerprint_subtitle))
+ .thenReturn(FINGERPRINT_SUBTITLE);
+ when(mContext.getString(R.string.biometric_dialog_default_subtitle))
+ .thenReturn(DEFAULT_SUBTITLE);
when(mWindowManager.getDefaultDisplay()).thenReturn(
new Display(DisplayManagerGlobal.getInstance(), Display.DEFAULT_DISPLAY,
@@ -208,7 +222,7 @@
@Test
public void testClientBinderDied_whenPaused() throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
+ setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
true /* requireConfirmation */, null /* authenticators */);
@@ -235,7 +249,7 @@
@Test
public void testClientBinderDied_whenAuthenticating() throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
+ setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
true /* requireConfirmation */, null /* authenticators */);
@@ -371,7 +385,7 @@
final int[] modalities = new int[] {
TYPE_FINGERPRINT,
- BiometricAuthenticator.TYPE_FACE,
+ TYPE_FACE,
};
final int[] strengths = new int[] {
@@ -424,9 +438,56 @@
}
@Test
+ public void testAuthenticateFace_shouldShowSubtitleForFace() throws Exception {
+ setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
+
+ invokeAuthenticate(mBiometricService.mImpl, mReceiver1,
+ false /* requireConfirmation */,
+ null);
+ waitForIdle();
+
+ assertEquals(FACE_SUBTITLE, mBiometricService.mAuthSession.mPromptInfo.getSubtitle());
+ }
+
+ @Test
+ public void testAuthenticateFingerprint_shouldShowSubtitleForFingerprint() throws Exception {
+ setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
+
+ invokeAuthenticate(mBiometricService.mImpl, mReceiver1,
+ false /* requireConfirmation */,
+ null);
+ waitForIdle();
+
+ assertEquals(FINGERPRINT_SUBTITLE,
+ mBiometricService.mAuthSession.mPromptInfo.getSubtitle());
+ }
+
+ @Test
+ public void testAuthenticateBothFpAndFace_shouldShowDefaultSubtitle() throws Exception {
+ final int[] modalities = new int[] {
+ TYPE_FINGERPRINT,
+ TYPE_FACE,
+ };
+
+ final int[] strengths = new int[] {
+ Authenticators.BIOMETRIC_WEAK,
+ Authenticators.BIOMETRIC_STRONG,
+ };
+
+ setupAuthForMultiple(modalities, strengths);
+
+ invokeAuthenticate(mBiometricService.mImpl, mReceiver1,
+ false /* requireConfirmation */,
+ null);
+ waitForIdle();
+
+ assertEquals(DEFAULT_SUBTITLE, mBiometricService.mAuthSession.mPromptInfo.getSubtitle());
+ }
+
+ @Test
public void testAuthenticateFace_respectsUserSetting()
throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
+ setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
// Disabled in user settings receives onError
when(mBiometricService.mSettingObserver.getEnabledForApps(anyInt())).thenReturn(false);
@@ -565,7 +626,7 @@
@Test
public void testAuthenticate_noBiometrics_credentialAllowed() throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
+ setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
when(mFaceAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(false);
when(mTrustManager.isDeviceSecure(anyInt(), anyInt()))
.thenReturn(true);
@@ -592,13 +653,13 @@
@Test
public void testAuthenticate_happyPathWithConfirmation_strongBiometric() throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
+ setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
testAuthenticate_happyPathWithConfirmation(true /* isStrongBiometric */);
}
@Test
public void testAuthenticate_happyPathWithConfirmation_weakBiometric() throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_WEAK);
+ setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_WEAK);
testAuthenticate_happyPathWithConfirmation(false /* isStrongBiometric */);
}
@@ -634,7 +695,7 @@
@Test
public void testAuthenticate_no_Biometrics_noCredential() throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
+ setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
when(mFaceAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(false);
when(mTrustManager.isDeviceSecure(anyInt(), anyInt()))
.thenReturn(false);
@@ -652,7 +713,7 @@
@Test
public void testRejectFace_whenAuthenticating_notifiesSystemUIAndClient_thenPaused()
throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
+ setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
false /* requireConfirmation */, null /* authenticators */);
@@ -660,7 +721,7 @@
waitForIdle();
verify(mBiometricService.mStatusBarService).onBiometricError(
- eq(BiometricAuthenticator.TYPE_FACE),
+ eq(TYPE_FACE),
eq(BiometricConstants.BIOMETRIC_PAUSED_REJECTED),
eq(0 /* vendorCode */));
verify(mReceiver1).onAuthenticationFailed();
@@ -688,7 +749,7 @@
@Test
public void testRequestAuthentication_whenAlreadyAuthenticating() throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
+ setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
false /* requireConfirmation */, null /* authenticators */);
@@ -697,7 +758,7 @@
waitForIdle();
verify(mReceiver1).onError(
- eq(BiometricAuthenticator.TYPE_FACE),
+ eq(TYPE_FACE),
eq(BiometricPrompt.BIOMETRIC_ERROR_CANCELED),
eq(0) /* vendorCode */);
verify(mBiometricService.mStatusBarService).hideAuthenticationDialog(eq(TEST_REQUEST_ID));
@@ -707,7 +768,7 @@
@Test
public void testErrorHalTimeout_whenAuthenticating_entersPausedState() throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
+ setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
false /* requireConfirmation */, null /* authenticators */);
@@ -720,7 +781,7 @@
assertEquals(STATE_AUTH_PAUSED, mBiometricService.mAuthSession.getState());
verify(mBiometricService.mStatusBarService).onBiometricError(
- eq(BiometricAuthenticator.TYPE_FACE),
+ eq(TYPE_FACE),
eq(BiometricConstants.BIOMETRIC_ERROR_TIMEOUT),
eq(0 /* vendorCode */));
// Timeout does not count as fail as per BiometricPrompt documentation.
@@ -756,7 +817,7 @@
@Test
public void testErrorFromHal_whenPaused_notifiesSystemUIAndClient() throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
+ setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
false /* requireConfirmation */, null /* authenticators */);
@@ -774,7 +835,7 @@
// Client receives error immediately
verify(mReceiver1).onError(
- eq(BiometricAuthenticator.TYPE_FACE),
+ eq(TYPE_FACE),
eq(BiometricConstants.BIOMETRIC_ERROR_CANCELED),
eq(0 /* vendorCode */));
// Dialog is hidden immediately
@@ -923,7 +984,7 @@
int biometricPromptError) throws Exception {
final int[] modalities = new int[] {
TYPE_FINGERPRINT,
- BiometricAuthenticator.TYPE_FACE,
+ TYPE_FACE,
};
final int[] strengths = new int[] {
@@ -1120,7 +1181,7 @@
@Test
public void testDismissedReasonNegative_whilePaused_invokeHalCancel() throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
+ setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
false /* requireConfirmation */, null /* authenticators */);
@@ -1139,7 +1200,7 @@
@Test
public void testDismissedReasonUserCancel_whilePaused_invokesHalCancel() throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
+ setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
false /* requireConfirmation */, null /* authenticators */);
@@ -1158,7 +1219,7 @@
@Test
public void testDismissedReasonUserCancel_whenPendingConfirmation() throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
+ setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
true /* requireConfirmation */, null /* authenticators */);
@@ -1172,7 +1233,7 @@
verify(mBiometricService.mSensors.get(0).impl)
.cancelAuthenticationFromService(any(), any(), anyLong());
verify(mReceiver1).onError(
- eq(BiometricAuthenticator.TYPE_FACE),
+ eq(TYPE_FACE),
eq(BiometricConstants.BIOMETRIC_ERROR_USER_CANCELED),
eq(0 /* vendorCode */));
verify(mBiometricService.mKeyStore, never()).addAuthToken(any(byte[].class));
@@ -1293,7 +1354,7 @@
@Test
public void testCanAuthenticate_whenBiometricsNotEnabledForApps() throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
+ setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
when(mBiometricService.mSettingObserver.getEnabledForApps(anyInt())).thenReturn(false);
when(mTrustManager.isDeviceSecure(anyInt(), anyInt()))
.thenReturn(true);
@@ -1587,7 +1648,7 @@
@Test
public void testWorkAuthentication_faceWorksIfNotDisabledByDevicePolicyManager()
throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
+ setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
when(mDevicePolicyManager
.getKeyguardDisabledFeatures(any() /* admin*/, anyInt() /* userHandle */))
.thenReturn(~DevicePolicyManager.KEYGUARD_DISABLE_FACE);
@@ -1680,7 +1741,7 @@
mFingerprintAuthenticator);
}
- if ((modality & BiometricAuthenticator.TYPE_FACE) != 0) {
+ if ((modality & TYPE_FACE) != 0) {
when(mFaceAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(enrolled);
when(mFaceAuthenticator.isHardwareDetected(any())).thenReturn(true);
when(mFaceAuthenticator.getLockoutModeForUser(anyInt()))
@@ -1712,7 +1773,7 @@
strength, mFingerprintAuthenticator);
}
- if ((modality & BiometricAuthenticator.TYPE_FACE) != 0) {
+ if ((modality & TYPE_FACE) != 0) {
when(mFaceAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(true);
when(mFaceAuthenticator.isHardwareDetected(any())).thenReturn(true);
mBiometricService.mImpl.registerAuthenticator(SENSOR_ID_FACE, modality,
@@ -1795,6 +1856,7 @@
boolean checkDevicePolicy) {
final PromptInfo promptInfo = new PromptInfo();
promptInfo.setConfirmationRequested(requireConfirmation);
+ promptInfo.setUseDefaultSubtitle(true);
if (authenticators != null) {
promptInfo.setAuthenticators(authenticators);
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java b/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java
index 0c98c8d..c2bdf50 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java
@@ -67,7 +67,7 @@
@Mock
BiometricService.SettingObserver mSettingObserver;
@Mock
- BiometricSensorPrivacy mBiometricSensorPrivacyUtil;
+ BiometricCameraManager mBiometricCameraManager;
@Before
public void setup() throws RemoteException {
@@ -79,11 +79,13 @@
when(mFaceAuthenticator.isHardwareDetected(any())).thenReturn(true);
when(mFaceAuthenticator.getLockoutModeForUser(anyInt()))
.thenReturn(LOCKOUT_NONE);
+ when(mBiometricCameraManager.isCameraPrivacyEnabled()).thenReturn(false);
+ when(mBiometricCameraManager.isAnyCameraUnavailable()).thenReturn(false);
}
@Test
public void testFaceAuthentication_whenCameraPrivacyIsEnabled() throws Exception {
- when(mBiometricSensorPrivacyUtil.isCameraPrivacyEnabled()).thenReturn(true);
+ when(mBiometricCameraManager.isCameraPrivacyEnabled()).thenReturn(true);
BiometricSensor sensor = new BiometricSensor(mContext, SENSOR_ID_FACE, TYPE_FACE,
BiometricManager.Authenticators.BIOMETRIC_STRONG, mFaceAuthenticator) {
@@ -104,15 +106,14 @@
PreAuthInfo preAuthInfo = PreAuthInfo.create(mTrustManager, mDevicePolicyManager,
mSettingObserver, List.of(sensor),
0 /* userId */, promptInfo, TEST_PACKAGE_NAME,
- false /* checkDevicePolicyManager */, mContext, mBiometricSensorPrivacyUtil);
+ false /* checkDevicePolicyManager */, mContext, mBiometricCameraManager);
assertThat(preAuthInfo.eligibleSensors).isEmpty();
}
@Test
- public void testFaceAuthentication_whenCameraPrivacyIsDisabled() throws Exception {
- when(mBiometricSensorPrivacyUtil.isCameraPrivacyEnabled()).thenReturn(false);
-
+ public void testFaceAuthentication_whenCameraPrivacyIsDisabledAndCameraIsAvailable()
+ throws Exception {
BiometricSensor sensor = new BiometricSensor(mContext, SENSOR_ID_FACE, TYPE_FACE,
BiometricManager.Authenticators.BIOMETRIC_STRONG, mFaceAuthenticator) {
@Override
@@ -132,8 +133,35 @@
PreAuthInfo preAuthInfo = PreAuthInfo.create(mTrustManager, mDevicePolicyManager,
mSettingObserver, List.of(sensor),
0 /* userId */, promptInfo, TEST_PACKAGE_NAME,
- false /* checkDevicePolicyManager */, mContext, mBiometricSensorPrivacyUtil);
+ false /* checkDevicePolicyManager */, mContext, mBiometricCameraManager);
assertThat(preAuthInfo.eligibleSensors).hasSize(1);
}
+
+ @Test
+ public void testFaceAuthentication_whenCameraIsUnavailable() throws RemoteException {
+ when(mBiometricCameraManager.isAnyCameraUnavailable()).thenReturn(true);
+ BiometricSensor sensor = new BiometricSensor(mContext, SENSOR_ID_FACE, TYPE_FACE,
+ BiometricManager.Authenticators.BIOMETRIC_STRONG, mFaceAuthenticator) {
+ @Override
+ boolean confirmationAlwaysRequired(int userId) {
+ return false;
+ }
+
+ @Override
+ boolean confirmationSupported() {
+ return false;
+ }
+ };
+ PromptInfo promptInfo = new PromptInfo();
+ promptInfo.setConfirmationRequested(false /* requireConfirmation */);
+ promptInfo.setAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG);
+ promptInfo.setDisallowBiometricsIfPolicyExists(false /* checkDevicePolicy */);
+ PreAuthInfo preAuthInfo = PreAuthInfo.create(mTrustManager, mDevicePolicyManager,
+ mSettingObserver, List.of(sensor),
+ 0 /* userId */, promptInfo, TEST_PACKAGE_NAME,
+ false /* checkDevicePolicyManager */, mContext, mBiometricCameraManager);
+
+ assertThat(preAuthInfo.eligibleSensors).hasSize(0);
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java
index 6c6b608..7e6883b 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java
@@ -44,6 +44,7 @@
import com.android.server.LocalServices;
import com.android.server.input.InputManagerInternal;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -93,6 +94,11 @@
threadVerifier);
}
+ @After
+ public void tearDown() {
+ mInputManagerMockHelper.tearDown();
+ }
+
@Test
public void registerInputDevice_deviceCreation_hasDeviceId() {
final IBinder device1Token = new Binder("device1");
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/InputManagerMockHelper.java b/services/tests/servicestests/src/com/android/server/companion/virtual/InputManagerMockHelper.java
index 5cadc0a..3722247 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/InputManagerMockHelper.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/InputManagerMockHelper.java
@@ -46,6 +46,7 @@
private final TestableLooper mTestableLooper;
private final InputController.NativeWrapper mNativeWrapperMock;
private final IInputManager mIInputManagerMock;
+ private final InputManagerGlobal.TestSession mInputManagerGlobalSession;
private final List<InputDevice> mDevices = new ArrayList<>();
private IInputDevicesChangedListener mDevicesChangedListener;
@@ -77,7 +78,13 @@
// Set a new instance of InputManager for testing that uses the IInputManager mock as the
// interface to the server.
- InputManagerGlobal.resetInstance(mIInputManagerMock);
+ mInputManagerGlobalSession = InputManagerGlobal.createTestSession(mIInputManagerMock);
+ }
+
+ public void tearDown() {
+ if (mInputManagerGlobalSession != null) {
+ mInputManagerGlobalSession.close();
+ }
}
private long handleNativeOpenInputDevice(InvocationOnMock inv) {
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
index 4b801bc..d76d615 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -376,6 +376,7 @@
@After
public void tearDown() {
mDeviceImpl.close();
+ mInputManagerMockHelper.tearDown();
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index ad1c60e..7478778 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -46,7 +46,6 @@
import static android.app.admin.DevicePolicyManager.WIFI_SECURITY_OPEN;
import static android.app.admin.DevicePolicyManager.WIFI_SECURITY_PERSONAL;
import static android.app.admin.DevicePolicyManager.WIPE_EUICC;
-import static android.app.admin.PasswordMetrics.computeForPasswordOrPin;
import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_DIRECT_BOOT_AWARE;
import static android.location.LocationManager.FUSED_PROVIDER;
import static android.location.LocationManager.GPS_PROVIDER;
@@ -5479,8 +5478,7 @@
reset(mContext.spiedContext);
- PasswordMetrics passwordMetricsNoSymbols = computeForPasswordOrPin(
- "abcdXYZ5".getBytes(), /* isPin */ false);
+ PasswordMetrics passwordMetricsNoSymbols = metricsForPassword("abcdXYZ5");
setActivePasswordState(passwordMetricsNoSymbols);
assertThat(dpm.isActivePasswordSufficient()).isTrue();
@@ -5507,8 +5505,7 @@
reset(mContext.spiedContext);
assertThat(dpm.isActivePasswordSufficient()).isFalse();
- PasswordMetrics passwordMetricsWithSymbols = computeForPasswordOrPin(
- "abcd.XY5".getBytes(), /* isPin */ false);
+ PasswordMetrics passwordMetricsWithSymbols = metricsForPassword("abcd.XY5");
setActivePasswordState(passwordMetricsWithSymbols);
assertThat(dpm.isActivePasswordSufficient()).isTrue();
@@ -5564,7 +5561,7 @@
parentDpm.setRequiredPasswordComplexity(PASSWORD_COMPLEXITY_MEDIUM);
when(getServices().lockSettingsInternal.getUserPasswordMetrics(UserHandle.USER_SYSTEM))
- .thenReturn(computeForPasswordOrPin("184342".getBytes(), /* isPin */ true));
+ .thenReturn(metricsForPin("184342"));
// Numeric password is compliant with current requirement (QUALITY_NUMERIC set explicitly
// on the parent admin)
@@ -5685,7 +5682,7 @@
// Set a work challenge and verify profile.isActivePasswordSufficient is now true
when(getServices().lockSettingsInternal.getUserPasswordMetrics(managedProfileUserId))
- .thenReturn(computeForPasswordOrPin("abcdXYZ5".getBytes(), /* isPin */ false));
+ .thenReturn(metricsForPassword("abcdXYZ5"));
assertThat(dpm.isActivePasswordSufficient()).isTrue();
assertThat(parentDpm.isActivePasswordSufficient()).isTrue();
}
@@ -5710,7 +5707,7 @@
// Set a work challenge and verify profile.isActivePasswordSufficient is now true
when(getServices().lockSettingsInternal.getUserPasswordMetrics(managedProfileUserId))
- .thenReturn(computeForPasswordOrPin("5156".getBytes(), /* isPin */ true));
+ .thenReturn(metricsForPin("5156"));
assertThat(dpm.isActivePasswordSufficient()).isTrue();
assertThat(parentDpm.isActivePasswordSufficient()).isTrue();
}
@@ -5735,7 +5732,7 @@
// Set a device lockscreen and verify parent.isActivePasswordSufficient is now true
when(getServices().lockSettingsInternal.getUserPasswordMetrics(UserHandle.USER_SYSTEM))
- .thenReturn(computeForPasswordOrPin("acbdXYZ5".getBytes(), /* isPin */ false));
+ .thenReturn(metricsForPassword("acbdXYZ5"));
assertThat(dpm.isActivePasswordSufficient()).isTrue();
assertThat(parentDpm.isActivePasswordSufficient()).isTrue();
}
@@ -5758,7 +5755,7 @@
// Set a device lockscreen and verify parent.isActivePasswordSufficient is now true
when(getServices().lockSettingsInternal.getUserPasswordMetrics(UserHandle.USER_SYSTEM))
- .thenReturn(computeForPasswordOrPin("1234".getBytes(), /* isPin */ true));
+ .thenReturn(metricsForPin("1234"));
assertThat(dpm.isActivePasswordSufficient()).isTrue();
assertThat(parentDpm.isActivePasswordSufficient()).isTrue();
}
@@ -5783,7 +5780,7 @@
// Set a device lockscreen and verify {profile, parent}.isActivePasswordSufficient is true
when(getServices().lockSettingsInternal.getUserPasswordMetrics(UserHandle.USER_SYSTEM))
- .thenReturn(computeForPasswordOrPin("abcdXYZ5".getBytes(), /* isPin */ false));
+ .thenReturn(metricsForPassword("abcdXYZ5"));
assertThat(dpm.isActivePasswordSufficient()).isTrue();
assertThat(parentDpm.isActivePasswordSufficient()).isTrue();
}
@@ -5806,7 +5803,7 @@
// Set a device lockscreen and verify {profile, parent}.isActivePasswordSufficient is true
when(getServices().lockSettingsInternal.getUserPasswordMetrics(UserHandle.USER_SYSTEM))
- .thenReturn(computeForPasswordOrPin("51567548".getBytes(), /* isPin */ true));
+ .thenReturn(metricsForPin("51567548"));
assertThat(dpm.isActivePasswordSufficient()).isTrue();
assertThat(parentDpm.isActivePasswordSufficient()).isTrue();
}
@@ -5831,7 +5828,7 @@
// Set a device lockscreen and verify {profile, parent}.isActivePasswordSufficient is true
when(getServices().lockSettingsInternal.getUserPasswordMetrics(UserHandle.USER_SYSTEM))
- .thenReturn(computeForPasswordOrPin("abcdXYZ5".getBytes(), /* isPin */ false));
+ .thenReturn(metricsForPassword("abcdXYZ5"));
assertThat(dpm.isActivePasswordSufficient()).isTrue();
assertThat(parentDpm.isActivePasswordSufficient()).isTrue();
}
@@ -5854,7 +5851,7 @@
// Set a device lockscreen and verify {profile, parent}.isActivePasswordSufficient is true
when(getServices().lockSettingsInternal.getUserPasswordMetrics(UserHandle.USER_SYSTEM))
- .thenReturn(computeForPasswordOrPin("5156".getBytes(), /* isPin */ true));
+ .thenReturn(metricsForPin("5156"));
assertThat(dpm.isActivePasswordSufficient()).isTrue();
assertThat(parentDpm.isActivePasswordSufficient()).isTrue();
}
@@ -6909,7 +6906,7 @@
.thenReturn(CALLER_USER_HANDLE);
when(getServices().lockSettingsInternal
.getUserPasswordMetrics(CALLER_USER_HANDLE))
- .thenReturn(computeForPasswordOrPin("asdf".getBytes(), /* isPin */ false));
+ .thenReturn(metricsForPassword("asdf"));
assertThat(dpm.getPasswordComplexity()).isEqualTo(PASSWORD_COMPLEXITY_MEDIUM);
}
@@ -6929,10 +6926,10 @@
when(getServices().lockSettingsInternal
.getUserPasswordMetrics(CALLER_USER_HANDLE))
- .thenReturn(computeForPasswordOrPin("asdf".getBytes(), /* isPin */ false));
+ .thenReturn(metricsForPassword("asdf"));
when(getServices().lockSettingsInternal
.getUserPasswordMetrics(parentUser.id))
- .thenReturn(computeForPasswordOrPin("parentUser".getBytes(), /* isPin */ false));
+ .thenReturn(metricsForPassword("parentUser"));
assertThat(dpm.getPasswordComplexity()).isEqualTo(PASSWORD_COMPLEXITY_HIGH);
}
@@ -7654,15 +7651,13 @@
assertThat(dpm.getPasswordComplexity()).isEqualTo(PASSWORD_COMPLEXITY_NONE);
reset(mContext.spiedContext);
- PasswordMetrics passwordMetricsNoSymbols = computeForPasswordOrPin(
- "1234".getBytes(), /* isPin */ true);
+ PasswordMetrics passwordMetricsNoSymbols = metricsForPin("1234");
setActivePasswordState(passwordMetricsNoSymbols);
assertThat(dpm.getPasswordComplexity()).isEqualTo(PASSWORD_COMPLEXITY_LOW);
assertThat(dpm.isActivePasswordSufficient()).isFalse();
reset(mContext.spiedContext);
- passwordMetricsNoSymbols = computeForPasswordOrPin(
- "84125312943a".getBytes(), /* isPin */ false);
+ passwordMetricsNoSymbols = metricsForPassword("84125312943a");
setActivePasswordState(passwordMetricsNoSymbols);
assertThat(dpm.getPasswordComplexity()).isEqualTo(PASSWORD_COMPLEXITY_HIGH);
// using isActivePasswordSufficient
@@ -8837,4 +8832,12 @@
assumeTrue("device doesn't support deprecated password APIs",
isDeprecatedPasswordApisSupported());
}
+
+ private static PasswordMetrics metricsForPassword(String password) {
+ return PasswordMetrics.computeForCredential(LockscreenCredential.createPassword(password));
+ }
+
+ private static PasswordMetrics metricsForPin(String pin) {
+ return PasswordMetrics.computeForCredential(LockscreenCredential.createPin(pin));
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/flags/FeatureFlagsServiceTest.java b/services/tests/servicestests/src/com/android/server/flags/FeatureFlagsServiceTest.java
new file mode 100644
index 0000000..df4731f
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/flags/FeatureFlagsServiceTest.java
@@ -0,0 +1,293 @@
+/*
+ * 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.flags;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.flags.IFeatureFlagsCallback;
+import android.flags.SyncableFlag;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.List;
+
+@Presubmit
+@SmallTest
+public class FeatureFlagsServiceTest {
+ private static final String NS = "ns";
+ private static final String NAME = "name";
+ private static final String PROP_NAME = FlagOverrideStore.getPropName(NS, NAME);
+
+ @Rule
+ public final MockitoRule mockito = MockitoJUnit.rule();
+
+ @Mock
+ private FlagOverrideStore mFlagStore;
+ @Mock
+ private FlagsShellCommand mFlagCommand;
+ @Mock
+ private IFeatureFlagsCallback mIFeatureFlagsCallback;
+ @Mock
+ private IBinder mIFeatureFlagsCallbackAsBinder;
+ @Mock
+ private FeatureFlagsService.PermissionsChecker mPermissionsChecker;
+
+ private FeatureFlagsBinder mFeatureFlagsService;
+
+ @Before
+ public void setup() {
+ when(mIFeatureFlagsCallback.asBinder()).thenReturn(mIFeatureFlagsCallbackAsBinder);
+ mFeatureFlagsService = new FeatureFlagsBinder(
+ mFlagStore, mFlagCommand, mPermissionsChecker);
+ }
+
+ @Test
+ public void testRegisterCallback() {
+ mFeatureFlagsService.registerCallback(mIFeatureFlagsCallback);
+ try {
+ verify(mIFeatureFlagsCallbackAsBinder).linkToDeath(any(), eq(0));
+ } catch (RemoteException e) {
+ fail("Our mock threw a Remote Exception?");
+ }
+ }
+
+ @Test
+ public void testOverrideFlag_requiresWritePermission() {
+ SecurityException exc = new SecurityException("not allowed");
+ doThrow(exc).when(mPermissionsChecker).assertWritePermission();
+
+ SyncableFlag f = new SyncableFlag(NS, "a", "false", false);
+
+ try {
+ mFeatureFlagsService.overrideFlag(f);
+ fail("Should have thrown exception");
+ } catch (SecurityException e) {
+ assertThat(exc).isEqualTo(e);
+ } catch (Exception e) {
+ fail("should have thrown a security exception");
+ }
+ }
+
+ @Test
+ public void testResetFlag_requiresWritePermission() {
+ SecurityException exc = new SecurityException("not allowed");
+ doThrow(exc).when(mPermissionsChecker).assertWritePermission();
+
+ SyncableFlag f = new SyncableFlag(NS, "a", "false", false);
+
+ try {
+ mFeatureFlagsService.resetFlag(f);
+ fail("Should have thrown exception");
+ } catch (SecurityException e) {
+ assertThat(exc).isEqualTo(e);
+ } catch (Exception e) {
+ fail("should have thrown a security exception");
+ }
+ }
+
+ @Test
+ public void testSyncFlags_noOverrides() {
+ List<SyncableFlag> inputFlags = List.of(
+ new SyncableFlag(NS, "a", "false", false),
+ new SyncableFlag(NS, "b", "true", false),
+ new SyncableFlag(NS, "c", "false", false)
+ );
+
+ List<SyncableFlag> outputFlags = mFeatureFlagsService.syncFlags(inputFlags);
+
+ assertThat(inputFlags.size()).isEqualTo(outputFlags.size());
+
+ for (SyncableFlag inpF: inputFlags) {
+ boolean found = false;
+ for (SyncableFlag outF : outputFlags) {
+ if (compareSyncableFlagsNames(inpF, outF)) {
+ found = true;
+ break;
+ }
+ }
+ assertWithMessage("Failed to find input flag " + inpF + " in the output")
+ .that(found).isTrue();
+ }
+ }
+
+ @Test
+ public void testSyncFlags_withSomeOverrides() {
+ List<SyncableFlag> inputFlags = List.of(
+ new SyncableFlag(NS, "a", "false", false),
+ new SyncableFlag(NS, "b", "true", false),
+ new SyncableFlag(NS, "c", "false", false)
+ );
+
+ assertThat(mFlagStore).isNotNull();
+ when(mFlagStore.get(NS, "c")).thenReturn("true");
+ List<SyncableFlag> outputFlags = mFeatureFlagsService.syncFlags(inputFlags);
+
+ assertThat(inputFlags.size()).isEqualTo(outputFlags.size());
+
+ for (SyncableFlag inpF: inputFlags) {
+ boolean found = false;
+ for (SyncableFlag outF : outputFlags) {
+ if (compareSyncableFlagsNames(inpF, outF)) {
+ found = true;
+
+ // Once we've found "c", do an extra check
+ if (outF.getName().equals("c")) {
+ assertWithMessage("Flag " + outF + "was not returned with an override")
+ .that(outF.getValue()).isEqualTo("true");
+ }
+ break;
+ }
+ }
+ assertWithMessage("Failed to find input flag " + inpF + " in the output")
+ .that(found).isTrue();
+ }
+ }
+
+ @Test
+ public void testSyncFlags_twoCallsWithDifferentDefaults() {
+ List<SyncableFlag> inputFlagsFirst = List.of(
+ new SyncableFlag(NS, "a", "false", false)
+ );
+ List<SyncableFlag> inputFlagsSecond = List.of(
+ new SyncableFlag(NS, "a", "true", false),
+ new SyncableFlag(NS, "b", "false", false)
+ );
+
+ List<SyncableFlag> outputFlagsFirst = mFeatureFlagsService.syncFlags(inputFlagsFirst);
+ List<SyncableFlag> outputFlagsSecond = mFeatureFlagsService.syncFlags(inputFlagsSecond);
+
+ assertThat(inputFlagsFirst.size()).isEqualTo(outputFlagsFirst.size());
+ assertThat(inputFlagsSecond.size()).isEqualTo(outputFlagsSecond.size());
+
+ // This test only cares that the "a" flag passed in the second time came out with the
+ // same value that was passed in the first time.
+
+ boolean found = false;
+ for (SyncableFlag second : outputFlagsSecond) {
+ if (compareSyncableFlagsNames(second, inputFlagsFirst.get(0))) {
+ found = true;
+ assertThat(second.getValue()).isEqualTo(inputFlagsFirst.get(0).getValue());
+ break;
+ }
+ }
+
+ assertWithMessage(
+ "Failed to find flag " + inputFlagsFirst.get(0) + " in the second calls output")
+ .that(found).isTrue();
+ }
+
+ @Test
+ public void testQueryFlags_onlyOnce() {
+ List<SyncableFlag> inputFlags = List.of(
+ new SyncableFlag(NS, "a", "false", false),
+ new SyncableFlag(NS, "b", "true", false),
+ new SyncableFlag(NS, "c", "false", false)
+ );
+
+ List<SyncableFlag> outputFlags = mFeatureFlagsService.queryFlags(inputFlags);
+
+ assertThat(inputFlags.size()).isEqualTo(outputFlags.size());
+
+ for (SyncableFlag inpF: inputFlags) {
+ boolean found = false;
+ for (SyncableFlag outF : outputFlags) {
+ if (compareSyncableFlagsNames(inpF, outF)) {
+ found = true;
+ break;
+ }
+ }
+ assertWithMessage("Failed to find input flag " + inpF + " in the output")
+ .that(found).isTrue();
+ }
+ }
+
+ @Test
+ public void testQueryFlags_twoCallsWithDifferentDefaults() {
+ List<SyncableFlag> inputFlagsFirst = List.of(
+ new SyncableFlag(NS, "a", "false", false)
+ );
+ List<SyncableFlag> inputFlagsSecond = List.of(
+ new SyncableFlag(NS, "a", "true", false),
+ new SyncableFlag(NS, "b", "false", false)
+ );
+
+ List<SyncableFlag> outputFlagsFirst = mFeatureFlagsService.queryFlags(inputFlagsFirst);
+ List<SyncableFlag> outputFlagsSecond = mFeatureFlagsService.queryFlags(inputFlagsSecond);
+
+ assertThat(inputFlagsFirst.size()).isEqualTo(outputFlagsFirst.size());
+ assertThat(inputFlagsSecond.size()).isEqualTo(outputFlagsSecond.size());
+
+ // This test only cares that the "a" flag passed in the second time came out with the
+ // same value that was passed in (i.e. it wasn't cached).
+
+ boolean found = false;
+ for (SyncableFlag second : outputFlagsSecond) {
+ if (compareSyncableFlagsNames(second, inputFlagsSecond.get(0))) {
+ found = true;
+ assertThat(second.getValue()).isEqualTo(inputFlagsSecond.get(0).getValue());
+ break;
+ }
+ }
+
+ assertWithMessage(
+ "Failed to find flag " + inputFlagsSecond.get(0) + " in the second calls output")
+ .that(found).isTrue();
+ }
+
+ @Test
+ public void testOverrideFlag() {
+ SyncableFlag f = new SyncableFlag(NS, "a", "false", false);
+
+ mFeatureFlagsService.overrideFlag(f);
+
+ verify(mFlagStore).set(f.getNamespace(), f.getName(), f.getValue());
+ }
+
+ @Test
+ public void testResetFlag() {
+ SyncableFlag f = new SyncableFlag(NS, "a", "false", false);
+
+ mFeatureFlagsService.resetFlag(f);
+
+ verify(mFlagStore).erase(f.getNamespace(), f.getName());
+ }
+
+
+ private static boolean compareSyncableFlagsNames(SyncableFlag a, SyncableFlag b) {
+ return a.getNamespace().equals(b.getNamespace())
+ && a.getName().equals(b.getName())
+ && a.isDynamic() == b.isDynamic();
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/flags/FlagCacheTest.java b/services/tests/servicestests/src/com/android/server/flags/FlagCacheTest.java
new file mode 100644
index 0000000..c2cf540
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/flags/FlagCacheTest.java
@@ -0,0 +1,86 @@
+/*
+ * 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.flags;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class FlagCacheTest {
+ private static final String NS = "ns";
+ private static final String NAME = "name";
+
+ FlagCache mFlagCache = new FlagCache();
+
+ @Test
+ public void testGetOrNull_unset() {
+ assertThat(mFlagCache.getOrNull(NS, NAME)).isNull();
+ }
+
+ @Test
+ public void testGetOrSet_unset() {
+ assertThat(mFlagCache.getOrSet(NS, NAME, "value")).isEqualTo("value");
+ }
+
+ @Test
+ public void testGetOrSet_alreadySet() {
+ mFlagCache.setIfChanged(NS, NAME, "value");
+ assertThat(mFlagCache.getOrSet(NS, NAME, "newvalue")).isEqualTo("value");
+ }
+
+ @Test
+ public void testSetIfChanged_unset() {
+ assertThat(mFlagCache.setIfChanged(NS, NAME, "value")).isTrue();
+ }
+
+ @Test
+ public void testSetIfChanged_noChange() {
+ mFlagCache.setIfChanged(NS, NAME, "value");
+ assertThat(mFlagCache.setIfChanged(NS, NAME, "value")).isFalse();
+ }
+
+ @Test
+ public void testSetIfChanged_changing() {
+ mFlagCache.setIfChanged(NS, NAME, "value");
+ assertThat(mFlagCache.setIfChanged(NS, NAME, "newvalue")).isTrue();
+ }
+
+ @Test
+ public void testContainsNamespace_unset() {
+ assertThat(mFlagCache.containsNamespace(NS)).isFalse();
+ }
+
+ @Test
+ public void testContainsNamespace_set() {
+ mFlagCache.setIfChanged(NS, NAME, "value");
+ assertThat(mFlagCache.containsNamespace(NS)).isTrue();
+ }
+
+ @Test
+ public void testContains_unset() {
+ assertThat(mFlagCache.contains(NS, NAME)).isFalse();
+ }
+
+ @Test
+ public void testContains_set() {
+ mFlagCache.setIfChanged(NS, NAME, "value");
+ assertThat(mFlagCache.contains(NS, NAME)).isTrue();
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/flags/FlagOverrideStoreTest.java b/services/tests/servicestests/src/com/android/server/flags/FlagOverrideStoreTest.java
new file mode 100644
index 0000000..6cc3acf
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/flags/FlagOverrideStoreTest.java
@@ -0,0 +1,111 @@
+/*
+ * 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.flags;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+@Presubmit
+@SmallTest
+public class FlagOverrideStoreTest {
+ private static final String NS = "ns";
+ private static final String NAME = "name";
+ private static final String PROP_NAME = FlagOverrideStore.getPropName(NS, NAME);
+
+ @Rule
+ public final MockitoRule mockito = MockitoJUnit.rule();
+
+ @Mock
+ private SettingsProxy mSettingsProxy;
+ @Mock
+ private FlagOverrideStore.FlagChangeCallback mCallback;
+
+ private FlagOverrideStore mFlagStore;
+
+ @Before
+ public void setup() {
+ mFlagStore = new FlagOverrideStore(mSettingsProxy);
+ mFlagStore.setChangeCallback(mCallback);
+ }
+
+ @Test
+ public void testSet_unset() {
+ mFlagStore.set(NS, NAME, "value");
+ verify(mSettingsProxy).putString(PROP_NAME, "value");
+ }
+
+ @Test
+ public void testSet_setTwice() {
+ mFlagStore.set(NS, NAME, "value");
+ mFlagStore.set(NS, NAME, "newvalue");
+ verify(mSettingsProxy).putString(PROP_NAME, "value");
+ verify(mSettingsProxy).putString(PROP_NAME, "newvalue");
+ }
+
+ @Test
+ public void testGet_unset() {
+ assertThat(mFlagStore.get(NS, NAME)).isNull();
+ }
+
+ @Test
+ public void testGet_set() {
+ when(mSettingsProxy.getString(PROP_NAME)).thenReturn("value");
+ assertThat(mFlagStore.get(NS, NAME)).isEqualTo("value");
+ }
+
+ @Test
+ public void testErase() {
+ mFlagStore.erase(NS, NAME);
+ verify(mSettingsProxy).putString(PROP_NAME, null);
+ }
+
+ @Test
+ public void testContains_unset() {
+ assertThat(mFlagStore.contains(NS, NAME)).isFalse();
+ }
+
+ @Test
+ public void testContains_set() {
+ when(mSettingsProxy.getString(PROP_NAME)).thenReturn("value");
+ assertThat(mFlagStore.contains(NS, NAME)).isTrue();
+ }
+
+ @Test
+ public void testCallback_onSet() {
+ mFlagStore.set(NS, NAME, "value");
+ verify(mCallback).onFlagChanged(NS, NAME, "value");
+ }
+
+ @Test
+ public void testCallback_onErase() {
+ mFlagStore.erase(NS, NAME);
+ verify(mCallback).onFlagChanged(NS, NAME, null);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/flags/OWNERS b/services/tests/servicestests/src/com/android/server/flags/OWNERS
new file mode 100644
index 0000000..7ed369e
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/flags/OWNERS
@@ -0,0 +1 @@
+include /services/flags/OWNERS
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTvTest.java
index 2405757..30ce961 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTvTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTvTest.java
@@ -19,7 +19,9 @@
import static com.android.server.hdmi.Constants.HDMI_EARC_STATUS_UNKNOWN;
import static com.android.server.hdmi.HdmiControlService.WAKE_UP_SCREEN_ON;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
@@ -206,4 +208,45 @@
.earcStatusChanged(false, false, HDMI_EARC_STATUS_UNKNOWN,
HDMI_EARC_STATUS_UNKNOWN, HdmiStatsEnums.LOG_REASON_WAKE);
}
+
+ @Test
+ public void testEarcStatusChanged_handleEarcStateChange_unSupportedPort_writesAtom() {
+ // Initialize HDMI port with eARC not supported.
+ HdmiPortInfo[] hdmiPortInfos = new HdmiPortInfo[1];
+ hdmiPortInfos[0] =
+ new HdmiPortInfo.Builder(EARC_PORT_ID, HdmiPortInfo.PORT_OUTPUT, 0x0000)
+ .setCecSupported(true)
+ .setMhlSupported(false)
+ .setArcSupported(false)
+ .setEarcSupported(false)
+ .build();
+ mNativeWrapper.setPortInfo(hdmiPortInfos);
+ mNativeWrapper.setPortConnectionStatus(EARC_PORT_ID, true);
+ mHdmiControlServiceSpy.initService();
+ mTestLooper.dispatchAll();
+
+ mHdmiControlServiceSpy.setEarcEnabled(HdmiControlManager.EARC_FEATURE_ENABLED);
+ mTestLooper.dispatchAll();
+ Mockito.clearInvocations(mHdmiCecAtomWriterSpy);
+
+ mHdmiControlServiceSpy.handleEarcStateChange(HDMI_EARC_STATUS_EARC_PENDING, EARC_PORT_ID);
+ verify(mHdmiCecAtomWriterSpy, times(1))
+ .earcStatusChanged(eq(false), eq(true), anyInt(),
+ eq(HDMI_EARC_STATUS_EARC_PENDING),
+ eq(HdmiStatsEnums.LOG_REASON_EARC_STATUS_CHANGED_UNSUPPORTED_PORT));
+ }
+
+ @Test
+ public void testEarcStatusChanged_handleEarcStateChange_wrongState_writesAtom() {
+ mHdmiControlServiceSpy.setEarcEnabled(HdmiControlManager.EARC_FEATURE_DISABLED);
+ mTestLooper.dispatchAll();
+ Mockito.clearInvocations(mHdmiCecAtomWriterSpy);
+
+ // mEarcLocalDevice should be empty since eARC is disabled.
+ mHdmiControlServiceSpy.handleEarcStateChange(HDMI_EARC_STATUS_EARC_PENDING, EARC_PORT_ID);
+ verify(mHdmiCecAtomWriterSpy, times(1))
+ .earcStatusChanged(eq(true), eq(false), anyInt(),
+ eq(HDMI_EARC_STATUS_EARC_PENDING),
+ eq(HdmiStatsEnums.LOG_REASON_EARC_STATUS_CHANGED_WRONG_STATE));
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/input/OWNERS b/services/tests/servicestests/src/com/android/server/input/OWNERS
deleted file mode 100644
index 6e9aa1d..0000000
--- a/services/tests/servicestests/src/com/android/server/input/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-include /services/core/java/com/android/server/input/OWNERS
-
diff --git a/services/tests/servicestests/src/com/android/server/people/PeopleServiceTest.java b/services/tests/servicestests/src/com/android/server/people/PeopleServiceTest.java
index 5066240..aed8491 100644
--- a/services/tests/servicestests/src/com/android/server/people/PeopleServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/PeopleServiceTest.java
@@ -41,8 +41,10 @@
import android.app.prediction.AppTarget;
import android.app.prediction.IPredictionCallback;
import android.content.Context;
+import android.content.pm.PackageManagerInternal;
import android.content.pm.ParceledListSlice;
import android.content.pm.ShortcutInfo;
+import android.content.pm.ShortcutServiceInternal;
import android.os.Binder;
import android.os.Bundle;
import android.os.RemoteException;
@@ -56,6 +58,7 @@
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.server.LocalServices;
+import com.android.server.notification.NotificationManagerInternal;
import org.junit.After;
import org.junit.Before;
@@ -87,6 +90,13 @@
private AppPredictionContext mPredictionContext;
@Mock
+ ShortcutServiceInternal mShortcutServiceInternal;
+ @Mock
+ PackageManagerInternal mPackageManagerInternal;
+ @Mock
+ NotificationManagerInternal mNotificationManagerInternal;
+
+ @Mock
private Context mMockContext;
@Rule
@@ -110,6 +120,10 @@
public void setUp() {
MockitoAnnotations.initMocks(this);
+ LocalServices.addService(ShortcutServiceInternal.class, mShortcutServiceInternal);
+ LocalServices.addService(PackageManagerInternal.class, mPackageManagerInternal);
+ LocalServices.addService(NotificationManagerInternal.class, mNotificationManagerInternal);
+
mPeopleService = new TestablePeopleService(mContext);
mTestableLooper = TestableLooper.get(this);
mIPeopleManager = ((IPeopleManager) mPeopleService.mService);
@@ -137,6 +151,9 @@
@After
public void tearDown() {
LocalServices.removeServiceForTest(PeopleServiceInternal.class);
+ LocalServices.removeServiceForTest(ShortcutServiceInternal.class);
+ LocalServices.removeServiceForTest(PackageManagerInternal.class);
+ LocalServices.removeServiceForTest(NotificationManagerInternal.class);
}
@Test
@@ -167,25 +184,29 @@
@Test
public void testRegisterConversationListener() throws Exception {
assertEquals(0,
- mPeopleService.mConversationListenerHelper.mListeners.getRegisteredCallbackCount());
+ mPeopleService.getConversationListenerHelper()
+ .mListeners.getRegisteredCallbackCount());
mIPeopleManager.registerConversationListener(TEST_PACKAGE_NAME, 0, CONVERSATION_ID_1,
new TestableConversationListener());
mTestableLooper.processAllMessages();
assertEquals(1,
- mPeopleService.mConversationListenerHelper.mListeners.getRegisteredCallbackCount());
+ mPeopleService.getConversationListenerHelper()
+ .mListeners.getRegisteredCallbackCount());
mIPeopleManager.registerConversationListener(TEST_PACKAGE_NAME, 0, CONVERSATION_ID_1,
new TestableConversationListener());
mTestableLooper.processAllMessages();
assertEquals(2,
- mPeopleService.mConversationListenerHelper.mListeners.getRegisteredCallbackCount());
+ mPeopleService.getConversationListenerHelper()
+ .mListeners.getRegisteredCallbackCount());
mIPeopleManager.registerConversationListener(TEST_PACKAGE_NAME, 0, CONVERSATION_ID_2,
new TestableConversationListener());
mTestableLooper.processAllMessages();
assertEquals(3,
- mPeopleService.mConversationListenerHelper.mListeners.getRegisteredCallbackCount());
+ mPeopleService.getConversationListenerHelper()
+ .mListeners.getRegisteredCallbackCount());
}
@Test
@@ -201,20 +222,24 @@
listener3);
mTestableLooper.processAllMessages();
assertEquals(3,
- mPeopleService.mConversationListenerHelper.mListeners.getRegisteredCallbackCount());
+ mPeopleService.getConversationListenerHelper()
+ .mListeners.getRegisteredCallbackCount());
mIPeopleManager.unregisterConversationListener(
listener2);
assertEquals(2,
- mPeopleService.mConversationListenerHelper.mListeners.getRegisteredCallbackCount());
+ mPeopleService.getConversationListenerHelper()
+ .mListeners.getRegisteredCallbackCount());
mIPeopleManager.unregisterConversationListener(
listener1);
assertEquals(1,
- mPeopleService.mConversationListenerHelper.mListeners.getRegisteredCallbackCount());
+ mPeopleService.getConversationListenerHelper()
+ .mListeners.getRegisteredCallbackCount());
mIPeopleManager.unregisterConversationListener(
listener3);
assertEquals(0,
- mPeopleService.mConversationListenerHelper.mListeners.getRegisteredCallbackCount());
+ mPeopleService.getConversationListenerHelper()
+ .mListeners.getRegisteredCallbackCount());
}
@Test
@@ -229,12 +254,13 @@
PeopleManager.ConversationListener.class);
registerListener(CONVERSATION_ID_2, listenerForConversation2);
assertEquals(3,
- mPeopleService.mConversationListenerHelper.mListeners.getRegisteredCallbackCount());
+ mPeopleService.getConversationListenerHelper()
+ .mListeners.getRegisteredCallbackCount());
// Update conversation with two listeners.
ConversationStatus status = new ConversationStatus.Builder(CONVERSATION_ID_1,
ACTIVITY_GAME).build();
- mPeopleService.mConversationListenerHelper.onConversationsUpdate(
+ mPeopleService.getConversationListenerHelper().onConversationsUpdate(
Arrays.asList(getConversation(CONVERSATION_ID_1, status)));
mTestLooper.dispatchAll();
diff --git a/services/tests/servicestests/src/com/android/server/pm/ArchiveManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/ArchiveManagerTest.java
deleted file mode 100644
index 73a6bb7..0000000
--- a/services/tests/servicestests/src/com/android/server/pm/ArchiveManagerTest.java
+++ /dev/null
@@ -1,144 +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.pm;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.junit.Assert.assertThrows;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.when;
-
-import android.content.IntentSender;
-import android.content.pm.PackageManager;
-import android.os.Binder;
-import android.platform.test.annotations.Presubmit;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.server.pm.pkg.PackageStateInternal;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-@SmallTest
-@Presubmit
-@RunWith(AndroidJUnit4.class)
-public class ArchiveManagerTest {
-
- private static final String PACKAGE = "com.example";
- private static final String CALLER_PACKAGE = "com.vending";
- private static final int USER_ID = 1;
-
- @Mock private IntentSender mIntentSender;
- @Mock private PackageManagerService mPm;
- @Mock private Computer mComputer;
- @Mock private PackageStateInternal mPackageState;
- private InstallSource mInstallSource =
- InstallSource.create(
- CALLER_PACKAGE,
- CALLER_PACKAGE,
- CALLER_PACKAGE,
- Binder.getCallingUid(),
- CALLER_PACKAGE,
- /* installerAttributionTag= */ null,
- /* packageSource= */ 0);
-
- private ArchiveManager mArchiveManager;
- private int mCallingUid;
-
- @Before
- public void setUp() throws Exception {
- MockitoAnnotations.initMocks(this);
- mArchiveManager = new ArchiveManager(mPm);
- mCallingUid = Binder.getCallingUid();
- when(mPm.snapshotComputer()).thenReturn(mComputer);
- when(mComputer.getNameForUid(eq(mCallingUid))).thenReturn(CALLER_PACKAGE);
- when(mComputer.getPackageStateInternal(eq(PACKAGE))).thenReturn(mPackageState);
- when(mPackageState.getInstallSource()).thenReturn(mInstallSource);
- }
-
- @Test
- public void archiveApp_callerPackageNameIncorrect() {
- Exception e = assertThrows(
- SecurityException.class,
- () -> mArchiveManager.archiveApp(PACKAGE, "different", USER_ID, mIntentSender)
- );
- assertThat(e).hasMessageThat().isEqualTo(
- String.format(
- "The callerPackageName %s set by the caller doesn't match the "
- + "caller's own package name %s.",
- "different",
- CALLER_PACKAGE));
- }
-
- @Test
- public void archiveApp_packageNotInstalled() {
- when(mComputer.getPackageStateInternal(eq(PACKAGE))).thenReturn(null);
-
- Exception e = assertThrows(
- PackageManager.NameNotFoundException.class,
- () -> mArchiveManager.archiveApp(PACKAGE, CALLER_PACKAGE, USER_ID, mIntentSender)
- );
- assertThat(e).hasMessageThat().isEqualTo(String.format("Package %s not found.", PACKAGE));
- }
-
- @Test
- public void archiveApp_callerNotInstallerOfRecord() {
- InstallSource otherInstallSource =
- InstallSource.create(
- CALLER_PACKAGE,
- CALLER_PACKAGE,
- /* installerPackageName= */ "different",
- Binder.getCallingUid(),
- CALLER_PACKAGE,
- /* installerAttributionTag= */ null,
- /* packageSource= */ 0);
- when(mPackageState.getInstallSource()).thenReturn(otherInstallSource);
-
- Exception e = assertThrows(
- SecurityException.class,
- () -> mArchiveManager.archiveApp(PACKAGE, CALLER_PACKAGE, USER_ID, mIntentSender)
- );
- assertThat(e).hasMessageThat().isEqualTo(
- String.format("Caller is not the installer of record for %s.", PACKAGE));
- }
-
- @Test
- public void archiveApp_callerNotUpdateOwner() {
- InstallSource otherInstallSource =
- InstallSource.create(
- CALLER_PACKAGE,
- CALLER_PACKAGE,
- CALLER_PACKAGE,
- Binder.getCallingUid(),
- /* updateOwnerPackageName= */ "different",
- /* installerAttributionTag= */ null,
- /* packageSource= */ 0);
- when(mPackageState.getInstallSource()).thenReturn(otherInstallSource);
-
- Exception e = assertThrows(
- SecurityException.class,
- () -> mArchiveManager.archiveApp(PACKAGE, CALLER_PACKAGE, USER_ID, mIntentSender)
- );
- assertThat(e).hasMessageThat().isEqualTo(
- String.format("Caller is not the update owner for %s.", PACKAGE));
- }
-}
diff --git a/services/tests/servicestests/src/com/android/server/pm/BackgroundInstallControlServiceTest.java b/services/tests/servicestests/src/com/android/server/pm/BackgroundInstallControlServiceTest.java
index ba91647..daf18ed 100644
--- a/services/tests/servicestests/src/com/android/server/pm/BackgroundInstallControlServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/BackgroundInstallControlServiceTest.java
@@ -402,7 +402,7 @@
assertEquals(0,
mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps());
doReturn(PackageManager.PERMISSION_DENIED).when(mPermissionManager).checkPermission(
- anyString(), anyString(), anyInt());
+ anyString(), anyString(), anyInt(), anyInt());
generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED,
USER_ID_1, INSTALLER_NAME_1, 0);
mTestLooper.dispatchAll();
@@ -415,7 +415,7 @@
assertEquals(0,
mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps());
doReturn(PackageManager.PERMISSION_GRANTED).when(mPermissionManager).checkPermission(
- anyString(), anyString(), anyInt());
+ anyString(), anyString(), anyInt(), anyInt());
generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED,
USER_ID_1, INSTALLER_NAME_1, 0);
mTestLooper.dispatchAll();
@@ -428,7 +428,7 @@
assertEquals(0,
mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps());
doReturn(PackageManager.PERMISSION_GRANTED).when(mPermissionManager).checkPermission(
- anyString(), anyString(), anyInt());
+ anyString(), anyString(), anyInt(), anyInt());
generateUsageEvent(UsageEvents.Event.USER_INTERACTION,
USER_ID_1, INSTALLER_NAME_1, 0);
mTestLooper.dispatchAll();
@@ -441,7 +441,7 @@
assertEquals(0,
mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps());
doReturn(PackageManager.PERMISSION_GRANTED).when(mPermissionManager).checkPermission(
- anyString(), anyString(), anyInt());
+ anyString(), anyString(), anyInt(), anyInt());
generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED,
USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_1);
mTestLooper.dispatchAll();
@@ -464,7 +464,7 @@
assertEquals(0,
mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps());
doReturn(PackageManager.PERMISSION_GRANTED).when(mPermissionManager).checkPermission(
- anyString(), anyString(), anyInt());
+ anyString(), anyString(), anyInt(), anyInt());
generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED,
USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_1);
generateUsageEvent(Event.ACTIVITY_STOPPED,
@@ -489,7 +489,7 @@
assertEquals(0,
mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps());
doReturn(PackageManager.PERMISSION_GRANTED).when(mPermissionManager).checkPermission(
- anyString(), anyString(), anyInt());
+ anyString(), anyString(), anyInt(), anyInt());
generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED,
USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_1);
generateUsageEvent(Event.ACTIVITY_STOPPED,
@@ -520,7 +520,7 @@
assertEquals(0,
mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps());
doReturn(PackageManager.PERMISSION_GRANTED).when(mPermissionManager).checkPermission(
- anyString(), anyString(), anyInt());
+ anyString(), anyString(), anyInt(), anyInt());
generateUsageEvent(Event.ACTIVITY_STOPPED,
USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_1);
mTestLooper.dispatchAll();
@@ -605,7 +605,7 @@
// So it's not a background install. Thus, it's null for the return of
// mBackgroundInstallControlService.getBackgroundInstalledPackages()
doReturn(PackageManager.PERMISSION_GRANTED).when(mPermissionManager).checkPermission(
- anyString(), anyString(), anyInt());
+ anyString(), anyString(), anyInt(), anyInt());
generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED,
USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_1);
generateUsageEvent(Event.ACTIVITY_STOPPED,
@@ -651,7 +651,7 @@
// it's a background install. Thus, it's not null for the return of
// mBackgroundInstallControlService.getBackgroundInstalledPackages()
doReturn(PackageManager.PERMISSION_GRANTED).when(mPermissionManager).checkPermission(
- anyString(), anyString(), anyInt());
+ anyString(), anyString(), anyInt(), anyInt());
generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED,
USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_2);
generateUsageEvent(Event.ACTIVITY_STOPPED,
@@ -701,7 +701,7 @@
// it's a background install. Thus, it's not null for the return of
// mBackgroundInstallControlService.getBackgroundInstalledPackages()
doReturn(PackageManager.PERMISSION_GRANTED).when(mPermissionManager).checkPermission(
- anyString(), anyString(), anyInt());
+ anyString(), anyString(), anyInt(), anyInt());
generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED,
USER_ID_2, INSTALLER_NAME_2, USAGE_EVENT_TIMESTAMP_2);
generateUsageEvent(Event.ACTIVITY_STOPPED,
@@ -752,7 +752,7 @@
// as a background install. Since we do not want to treat side-loaded apps as background
// install getBackgroundInstalledPackages() is expected to return null
doReturn(PackageManager.PERMISSION_GRANTED).when(mPermissionManager).checkPermission(
- anyString(), anyString(), anyInt());
+ anyString(), anyString(), anyInt(), anyInt());
generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED,
USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_2);
generateUsageEvent(Event.ACTIVITY_STOPPED,
@@ -801,7 +801,7 @@
// as a background install. Since we do not want to treat side-loaded apps as background
// install getBackgroundInstalledPackages() is expected to return null
doReturn(PackageManager.PERMISSION_GRANTED).when(mPermissionManager).checkPermission(
- anyString(), anyString(), anyInt());
+ anyString(), anyString(), anyInt(), anyInt());
generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED,
USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_2);
generateUsageEvent(Event.ACTIVITY_STOPPED,
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserLifecycleStressTest.java b/services/tests/servicestests/src/com/android/server/pm/UserLifecycleStressTest.java
index c9f00d7..43bf537 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserLifecycleStressTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserLifecycleStressTest.java
@@ -16,16 +16,19 @@
package com.android.server.pm;
import static android.os.UserHandle.USER_NULL;
-import static android.os.UserHandle.USER_SYSTEM;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
+import static org.junit.Assume.assumeFalse;
+
import android.app.ActivityManager;
import android.app.IStopUserCallback;
import android.content.Context;
+import android.content.Intent;
import android.content.pm.UserInfo;
import android.os.RemoteException;
+import android.os.UserHandle;
import android.os.UserManager;
import android.platform.test.annotations.Postsubmit;
import android.provider.Settings;
@@ -35,6 +38,8 @@
import androidx.test.filters.LargeTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.compatibility.common.util.BlockingBroadcastReceiver;
+import com.android.compatibility.common.util.ShellUtils;
import com.android.internal.util.FunctionalUtils;
import org.junit.After;
@@ -46,6 +51,7 @@
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
/**
* To run the test:
@@ -58,14 +64,20 @@
private static final String TAG = "UserLifecycleStressTest";
// TODO: Make this smaller once we have improved it.
private static final int TIMEOUT_IN_SECOND = 40;
+ private static final int CHECK_USER_REMOVED_INTERVAL_MS = 500;
+
private static final int NUM_ITERATIONS = 8;
private static final int WAIT_BEFORE_STOP_USER_IN_SECOND = 3;
+ /** Name of users/profiles in the test. Users with this name may be freely removed. */
+ private static final String TEST_USER_NAME = "UserLifecycleStressTest_test_user";
+
private Context mContext;
private UserManager mUserManager;
private ActivityManager mActivityManager;
private UserSwitchWaiter mUserSwitchWaiter;
private String mRemoveGuestOnExitOriginalValue;
+ private int mOriginalCurrentUserId;
@Before
public void setup() throws RemoteException {
@@ -75,13 +87,17 @@
mUserSwitchWaiter = new UserSwitchWaiter(TAG, TIMEOUT_IN_SECOND);
mRemoveGuestOnExitOriginalValue = Settings.Global.getString(mContext.getContentResolver(),
Settings.Global.REMOVE_GUEST_ON_EXIT);
+ waitForBroadcastBarrier(); // isolate tests from each other
+ mOriginalCurrentUserId = ActivityManager.getCurrentUser();
}
@After
public void tearDown() throws IOException {
+ switchUser(mOriginalCurrentUserId);
mUserSwitchWaiter.close();
Settings.Global.putString(mContext.getContentResolver(),
Settings.Global.REMOVE_GUEST_ON_EXIT, mRemoveGuestOnExitOriginalValue);
+ waitForBroadcastBarrier(); // isolate tests from each other
}
/**
@@ -90,9 +106,15 @@
*/
@Test
public void stopManagedProfileStressTest() throws RemoteException, InterruptedException {
+ UserHandle mainUser = mUserManager.getMainUser();
+ assumeFalse("There is no main user", mainUser == null);
+ switchUser(mainUser.getIdentifier());
+
for (int i = 0; i < NUM_ITERATIONS; i++) {
- final UserInfo userInfo = mUserManager.createProfileForUser("TestUser",
- UserManager.USER_TYPE_PROFILE_MANAGED, 0, mActivityManager.getCurrentUser());
+ logIteration(i, "stopManagedProfileStressTest");
+
+ final UserInfo userInfo = mUserManager.createProfileForUser(TEST_USER_NAME,
+ UserManager.USER_TYPE_PROFILE_MANAGED, 0, ActivityManager.getCurrentUser());
assertThat(userInfo).isNotNull();
try {
assertWithMessage("Failed to start the profile")
@@ -109,6 +131,35 @@
}
/**
+ * Create a user, and then remove it immediately after starting it in background
+ * {@link #NUM_ITERATIONS} times in a row.
+ * Check device is not crashed when user data directory is deleted while some other processes
+ * might still be trying to access those deleted files.
+ */
+ @Test
+ public void removeRecentlyStartedUserStressTest() throws RemoteException, InterruptedException {
+ for (int i = 0; i < NUM_ITERATIONS; i++) {
+ logIteration(i, "removeRecentlyStartedUserStressTest");
+
+ Log.d(TAG, "Creating a new user");
+ final UserInfo userInfo = mUserManager.createUser(TEST_USER_NAME,
+ UserManager.USER_TYPE_FULL_SECONDARY, 0);
+ assertWithMessage("Failed to create the user")
+ .that(userInfo)
+ .isNotNull();
+ try {
+ Log.d(TAG, "Starting user " + userInfo.id);
+ startUserInBackgroundAndWaitForUserStartedBroadcast(userInfo.id);
+ } finally {
+ Log.d(TAG, "Removing user " + userInfo.id);
+ assertWithMessage("Failed to remove the user " + userInfo.id)
+ .that(removeUser(userInfo.id))
+ .isTrue();
+ }
+ }
+ }
+
+ /**
* Starts over the guest user {@link #NUM_ITERATIONS} times in a row.
*
* Starting over the guest means the following:
@@ -122,18 +173,13 @@
Settings.Global.putString(mContext.getContentResolver(),
Settings.Global.REMOVE_GUEST_ON_EXIT, "0");
- if (ActivityManager.getCurrentUser() != USER_SYSTEM) {
- switchUser(USER_SYSTEM);
- }
-
final List<UserInfo> guestUsers = mUserManager.getGuestUsers();
int nextGuestId = guestUsers.isEmpty() ? USER_NULL : guestUsers.get(0).id;
for (int i = 0; i < NUM_ITERATIONS; i++) {
- final int currentGuestId = nextGuestId;
+ logIteration(i, "switchToExistingGuestAndStartOverStressTest");
- Log.d(TAG, "switchToExistingGuestAndStartOverStressTest"
- + " - Run " + (i + 1) + " / " + NUM_ITERATIONS);
+ final int currentGuestId = nextGuestId;
if (currentGuestId != USER_NULL) {
Log.d(TAG, "Switching to the existing guest");
@@ -161,8 +207,8 @@
.isTrue();
}
- Log.d(TAG, "Switching back to the system user");
- switchUser(USER_SYSTEM);
+ Log.d(TAG, "Switching back to the initial user");
+ switchUser(mOriginalCurrentUserId);
nextGuestId = newGuest.id;
}
@@ -173,6 +219,25 @@
Log.d(TAG, "testSwitchToExistingGuestAndStartOver - End");
}
+ private boolean removeUser(int userId) {
+ if (!mUserManager.removeUser(userId)) {
+ return false;
+ }
+ try {
+ final long startTime = System.currentTimeMillis();
+ final long timeoutInMs = TIMEOUT_IN_SECOND * 1000;
+ while (mUserManager.getUserInfo(userId) != null
+ && System.currentTimeMillis() - startTime < timeoutInMs) {
+ TimeUnit.MILLISECONDS.sleep(CHECK_USER_REMOVED_INTERVAL_MS);
+ }
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ } catch (Exception e) {
+ // Ignore
+ }
+ return mUserManager.getUserInfo(userId) == null;
+ }
+
/** Stops the given user and waits for the stop to finish. */
private void stopUser(int userId) throws RemoteException, InterruptedException {
runWithLatch("stop user", countDownLatch -> {
@@ -193,6 +258,10 @@
/** Starts the given user in the foreground and waits for the switch to finish. */
private void switchUser(int userId) {
+ if (ActivityManager.getCurrentUser() == userId) {
+ Log.d(TAG, "No need to switch, current user is already user " + userId);
+ return;
+ }
Log.d(TAG, "Switching to user " + userId);
mUserSwitchWaiter.runThenWaitUntilSwitchCompleted(userId, () -> {
@@ -204,6 +273,40 @@
}
/**
+ * Start user in background and wait for {@link Intent#ACTION_USER_STARTED} broadcast.
+ * <p> To start in foreground instead, see {@link #switchUser(int)}.
+ * <p> This should always be used for profiles since profiles cannot be started in foreground.
+ */
+ private void startUserInBackgroundAndWaitForUserStartedBroadcast(int userId) {
+ runWithBlockingBroadcastReceiver("start user and wait for ACTION_USER_STARTED broadcast",
+ userId, Intent.ACTION_USER_STARTED,
+ () -> ActivityManager.getService().startUserInBackground(userId));
+ }
+
+ /**
+ * Calls the given runnable and expects the given broadcast to be received before timeout,
+ * or fails the test otherwise.
+ * @param tag tag for logging
+ * @param userId id of the user to register the broadcast receiver with
+ * see {@link Context#registerReceiverAsUser}
+ * @param action action of the broadcast intent filter i.e. {@link Intent#ACTION_USER_STARTED}
+ * @param runnable this will be called after registering the broadcast receiver
+ */
+ private void runWithBlockingBroadcastReceiver(String tag, int userId, String action,
+ FunctionalUtils.ThrowingRunnable runnable) {
+ try (BlockingBroadcastReceiver blockingBroadcastReceiver = new BlockingBroadcastReceiver(
+ mContext, action,
+ intent -> intent.getIntExtra(Intent.EXTRA_USER_HANDLE, USER_NULL) == userId)) {
+ blockingBroadcastReceiver.setTimeout(TIMEOUT_IN_SECOND);
+ blockingBroadcastReceiver.registerForAllUsers();
+ runnable.run();
+ assertWithMessage("Took more than " + TIMEOUT_IN_SECOND + "s to " + tag)
+ .that(blockingBroadcastReceiver.awaitForBroadcast())
+ .isNotNull();
+ }
+ }
+
+ /**
* Calls the given consumer with a CountDownLatch parameter, and expects it's countDown() method
* to be called before timeout, or fails the test otherwise.
*/
@@ -222,5 +325,20 @@
final long elapsedTime = System.currentTimeMillis() - startTime;
Log.d(TAG, tag + " takes " + elapsedTime + " ms");
}
+
+ private void logIteration(int iteration, String testMethodName) {
+ Log.d(TAG, testMethodName + " - Iteration " + (iteration + 1) + " / " + NUM_ITERATIONS);
+ }
+
+ private static void waitForBroadcastBarrier() {
+ try {
+ Log.d(TAG, "Starting to waitForBroadcastBarrier");
+ ShellUtils.runShellCommandWithTimeout("am wait-for-broadcast-barrier",
+ TIMEOUT_IN_SECOND);
+ Log.d(TAG, "waitForBroadcastBarrier is finished");
+ } catch (TimeoutException e) {
+ Log.e(TAG, "Timeout while running waitForBroadcastBarrier", e);
+ }
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySaverPolicyTest.java b/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySaverPolicyTest.java
index 5bd7116..835ccf0 100644
--- a/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySaverPolicyTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySaverPolicyTest.java
@@ -30,6 +30,7 @@
import android.provider.Settings.Global;
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.SmallTest;
+import android.test.suitebuilder.annotation.Suppress;
import android.util.ArrayMap;
import com.android.frameworks.servicestests.R;
@@ -111,6 +112,7 @@
testServiceDefaultValue_On(ServiceType.NULL);
}
+ @Suppress
@SmallTest
public void testGetBatterySaverPolicy_PolicyVibration_DefaultValueCorrect() {
testDefaultValue(
@@ -217,6 +219,7 @@
ServiceType.QUICK_DOZE);
}
+ @Suppress
@SmallTest
public void testUpdateConstants_getCorrectData() {
mBatterySaverPolicy.updateConstantsLocked(BATTERY_SAVER_CONSTANTS, "");
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 30180e8..a109d5c 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -73,6 +73,7 @@
import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ALERTING;
import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_CONVERSATIONS;
import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ONGOING;
+import static android.service.notification.NotificationListenerService.REASON_CANCEL;
import static android.service.notification.NotificationListenerService.REASON_LOCKDOWN;
import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE;
import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL;
@@ -405,6 +406,7 @@
UriGrantsManagerInternal mUgmInternal;
@Mock
AppOpsManager mAppOpsManager;
+ private AppOpsManager.OnOpChangedListener mOnPermissionChangeListener;
@Mock
private TestableNotificationManagerService.NotificationAssistantAccessGrantedCallback
mNotificationAssistantAccessGrantedCallback;
@@ -604,6 +606,12 @@
tr.addOverride(com.android.internal.R.string.config_defaultSearchSelectorPackageName,
SEARCH_SELECTOR_PKG);
+ doAnswer(invocation -> {
+ mOnPermissionChangeListener = invocation.getArgument(2);
+ return null;
+ }).when(mAppOpsManager).startWatchingMode(eq(AppOpsManager.OP_POST_NOTIFICATION), any(),
+ any());
+
mWorkerHandler = spy(mService.new WorkerHandler(mTestableLooper.getLooper()));
mService.init(mWorkerHandler, mRankingHandler, mPackageManager, mPackageManagerClient,
mockLightsManager, mListeners, mAssistants, mConditionProviders, mCompanionMgr,
@@ -2002,10 +2010,10 @@
final StatusBarNotification sbn = generateNotificationRecord(null).getSbn();
mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", sbn.getId(),
sbn.getNotification(), sbn.getUserId());
- Thread.sleep(1); // make sure the system clock advances before the next step
+ mTestableLooper.moveTimeForward(1);
// THEN it is canceled
mBinderService.cancelNotificationWithTag(PKG, PKG, "tag", sbn.getId(), sbn.getUserId());
- Thread.sleep(1); // here too
+ mTestableLooper.moveTimeForward(1);
// THEN it is posted again (before the cancel has a chance to finish)
mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", sbn.getId(),
sbn.getNotification(), sbn.getUserId());
@@ -2295,8 +2303,8 @@
mTestNotificationChannel, 1, "group", true);
notif.getNotification().flags |= Notification.FLAG_NO_CLEAR;
mService.addNotification(notif);
- mService.cancelAllNotificationsInt(mUid, 0, PKG, null, 0, 0, true,
- notif.getUserId(), 0, null);
+ mService.cancelAllNotificationsInt(mUid, 0, PKG, null, 0, 0,
+ notif.getUserId(), REASON_CANCEL);
waitForIdle();
StatusBarNotification[] notifs =
mBinderService.getActiveNotifications(notif.getSbn().getPackageName());
@@ -3034,7 +3042,7 @@
notif.getNotification().flags |= Notification.FLAG_NO_CLEAR;
mService.addNotification(notif);
mService.cancelAllNotificationsInt(mUid, 0, PKG, null, 0,
- Notification.FLAG_ONGOING_EVENT, true, notif.getUserId(), 0, null);
+ Notification.FLAG_ONGOING_EVENT, notif.getUserId(), REASON_CANCEL);
waitForIdle();
StatusBarNotification[] notifs =
mBinderService.getActiveNotifications(notif.getSbn().getPackageName());
@@ -3061,8 +3069,8 @@
mTestNotificationChannel, 1, "group", true);
notif.getNotification().flags |= Notification.FLAG_ONGOING_EVENT;
mService.addNotification(notif);
- mService.cancelAllNotificationsInt(mUid, 0, PKG, null, 0, 0, true,
- notif.getUserId(), 0, null);
+ mService.cancelAllNotificationsInt(mUid, 0, PKG, null, 0, 0,
+ notif.getUserId(), REASON_CANCEL);
waitForIdle();
StatusBarNotification[] notifs =
mBinderService.getActiveNotifications(notif.getSbn().getPackageName());
@@ -3216,48 +3224,6 @@
}
@Test
- public void testUpdateAppNotifyCreatorBlock() throws Exception {
- when(mPermissionHelper.hasPermission(mUid)).thenReturn(true);
-
- mBinderService.setNotificationsEnabledForPackage(PKG, mUid, false);
- Thread.sleep(500);
- waitForIdle();
-
- ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
- verify(mContext, times(1)).sendBroadcastAsUser(captor.capture(), any(), eq(null));
-
- assertEquals(NotificationManager.ACTION_APP_BLOCK_STATE_CHANGED,
- captor.getValue().getAction());
- assertEquals(PKG, captor.getValue().getPackage());
- assertTrue(captor.getValue().getBooleanExtra(EXTRA_BLOCKED_STATE, true));
- }
-
- @Test
- public void testUpdateAppNotifyCreatorBlock_notIfMatchesExistingSetting() throws Exception {
- when(mPermissionHelper.hasPermission(mUid)).thenReturn(false);
-
- mBinderService.setNotificationsEnabledForPackage(PKG, 0, false);
- verify(mContext, never()).sendBroadcastAsUser(any(), any(), eq(null));
- }
-
- @Test
- public void testUpdateAppNotifyCreatorUnblock() throws Exception {
- when(mPermissionHelper.hasPermission(mUid)).thenReturn(false);
-
- mBinderService.setNotificationsEnabledForPackage(PKG, mUid, true);
- Thread.sleep(500);
- waitForIdle();
-
- ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
- verify(mContext, times(1)).sendBroadcastAsUser(captor.capture(), any(), eq(null));
-
- assertEquals(NotificationManager.ACTION_APP_BLOCK_STATE_CHANGED,
- captor.getValue().getAction());
- assertEquals(PKG, captor.getValue().getPackage());
- assertFalse(captor.getValue().getBooleanExtra(EXTRA_BLOCKED_STATE, true));
- }
-
- @Test
public void testUpdateChannelNotifyCreatorBlock() throws Exception {
mService.setPreferencesHelper(mPreferencesHelper);
when(mPreferencesHelper.getNotificationChannel(eq(PKG), anyInt(),
@@ -11269,7 +11235,6 @@
// Given: a call notification has the flag FLAG_ONGOING_EVENT set
// feature flag: ALLOW_DISMISS_ONGOING is on
mTestFlagResolver.setFlagOverride(ALLOW_DISMISS_ONGOING, true);
- when(mTelecomManager.isInManagedCall()).thenReturn(true);
Person person = new Person.Builder()
.setName("caller")
@@ -12174,6 +12139,134 @@
any(), eq(FLAG_ACTIVITY_SENDER | FLAG_BROADCAST_SENDER | FLAG_SERVICE_SENDER));
}
+ @Test
+ public void onOpChanged_permissionRevoked_cancelsAllNotificationsFromPackage()
+ throws RemoteException {
+ // Have preexisting posted notifications from revoked package and other packages.
+ mService.addNotification(new NotificationRecord(mContext,
+ generateSbn("revoked", 1001, 1, 0), mTestNotificationChannel));
+ mService.addNotification(new NotificationRecord(mContext,
+ generateSbn("other", 1002, 2, 0), mTestNotificationChannel));
+ // Have preexisting enqueued notifications from revoked package and other packages.
+ mService.addEnqueuedNotification(new NotificationRecord(mContext,
+ generateSbn("revoked", 1001, 3, 0), mTestNotificationChannel));
+ mService.addEnqueuedNotification(new NotificationRecord(mContext,
+ generateSbn("other", 1002, 4, 0), mTestNotificationChannel));
+ assertThat(mService.mNotificationList).hasSize(2);
+ assertThat(mService.mEnqueuedNotifications).hasSize(2);
+
+ when(mPackageManagerInternal.getPackageUid("revoked", 0, 0)).thenReturn(1001);
+ when(mPermissionHelper.hasPermission(eq(1001))).thenReturn(false);
+
+ mOnPermissionChangeListener.onOpChanged(
+ AppOpsManager.OPSTR_POST_NOTIFICATION, "revoked", 0);
+ waitForIdle();
+
+ assertThat(mService.mNotificationList).hasSize(1);
+ assertThat(mService.mNotificationList.get(0).getSbn().getPackageName()).isEqualTo("other");
+ assertThat(mService.mEnqueuedNotifications).hasSize(1);
+ assertThat(mService.mEnqueuedNotifications.get(0).getSbn().getPackageName()).isEqualTo(
+ "other");
+ }
+
+ @Test
+ public void onOpChanged_permissionStillGranted_notificationsAreNotAffected()
+ throws RemoteException {
+ // NOTE: This combination (receiving the onOpChanged broadcast for a package, the permission
+ // being now granted, AND having previously posted notifications from said package) should
+ // never happen (if we trust the broadcasts are correct). So this test is for a what-if
+ // scenario, to verify we still handle it reasonably.
+
+ // Have preexisting posted notifications from specific package and other packages.
+ mService.addNotification(new NotificationRecord(mContext,
+ generateSbn("granted", 1001, 1, 0), mTestNotificationChannel));
+ mService.addNotification(new NotificationRecord(mContext,
+ generateSbn("other", 1002, 2, 0), mTestNotificationChannel));
+ // Have preexisting enqueued notifications from specific package and other packages.
+ mService.addEnqueuedNotification(new NotificationRecord(mContext,
+ generateSbn("granted", 1001, 3, 0), mTestNotificationChannel));
+ mService.addEnqueuedNotification(new NotificationRecord(mContext,
+ generateSbn("other", 1002, 4, 0), mTestNotificationChannel));
+ assertThat(mService.mNotificationList).hasSize(2);
+ assertThat(mService.mEnqueuedNotifications).hasSize(2);
+
+ when(mPackageManagerInternal.getPackageUid("granted", 0, 0)).thenReturn(1001);
+ when(mPermissionHelper.hasPermission(eq(1001))).thenReturn(true);
+
+ mOnPermissionChangeListener.onOpChanged(
+ AppOpsManager.OPSTR_POST_NOTIFICATION, "granted", 0);
+ waitForIdle();
+
+ assertThat(mService.mNotificationList).hasSize(2);
+ assertThat(mService.mEnqueuedNotifications).hasSize(2);
+ }
+
+ @Test
+ public void onOpChanged_permissionGranted_notifiesAppUnblocked() throws Exception {
+ when(mPackageManagerInternal.getPackageUid(PKG, 0, 0)).thenReturn(1001);
+ when(mPermissionHelper.hasPermission(eq(1001))).thenReturn(true);
+
+ mOnPermissionChangeListener.onOpChanged(
+ AppOpsManager.OPSTR_POST_NOTIFICATION, PKG, 0);
+ waitForIdle();
+ mTestableLooper.moveTimeForward(500);
+ waitForIdle();
+
+ ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
+ verify(mContext).sendBroadcastAsUser(captor.capture(), any(), eq(null));
+ assertThat(captor.getValue().getAction()).isEqualTo(
+ NotificationManager.ACTION_APP_BLOCK_STATE_CHANGED);
+ assertThat(captor.getValue().getPackage()).isEqualTo(PKG);
+ assertThat(captor.getValue().getBooleanExtra(EXTRA_BLOCKED_STATE, true)).isFalse();
+ }
+
+ @Test
+ public void onOpChanged_permissionRevoked_notifiesAppBlocked() throws Exception {
+ when(mPackageManagerInternal.getPackageUid(PKG, 0, 0)).thenReturn(1001);
+ when(mPermissionHelper.hasPermission(eq(1001))).thenReturn(false);
+
+ mOnPermissionChangeListener.onOpChanged(
+ AppOpsManager.OPSTR_POST_NOTIFICATION, PKG, 0);
+ waitForIdle();
+ mTestableLooper.moveTimeForward(500);
+ waitForIdle();
+
+ ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
+ verify(mContext).sendBroadcastAsUser(captor.capture(), any(), eq(null));
+ assertThat(captor.getValue().getAction()).isEqualTo(
+ NotificationManager.ACTION_APP_BLOCK_STATE_CHANGED);
+ assertThat(captor.getValue().getPackage()).isEqualTo(PKG);
+ assertThat(captor.getValue().getBooleanExtra(EXTRA_BLOCKED_STATE, false)).isTrue();
+ }
+
+ @Test
+ public void setNotificationsEnabledForPackage_disabling_clearsNotifications() throws Exception {
+ mService.addNotification(new NotificationRecord(mContext,
+ generateSbn("package", 1001, 1, 0), mTestNotificationChannel));
+ assertThat(mService.mNotificationList).hasSize(1);
+ when(mPackageManagerInternal.getPackageUid("package", 0, 0)).thenReturn(1001);
+ when(mPermissionHelper.hasRequestedPermission(any(), eq("package"), anyInt())).thenReturn(
+ true);
+
+ // Start with granted permission and simulate effect of revoking it.
+ when(mPermissionHelper.hasPermission(1001)).thenReturn(true);
+ doAnswer(invocation -> {
+ when(mPermissionHelper.hasPermission(1001)).thenReturn(false);
+ mOnPermissionChangeListener.onOpChanged(
+ AppOpsManager.OPSTR_POST_NOTIFICATION, "package", 0);
+ return null;
+ }).when(mPermissionHelper).setNotificationPermission("package", 0, false, true);
+
+ mBinderService.setNotificationsEnabledForPackage("package", 1001, false);
+ waitForIdle();
+
+ assertThat(mService.mNotificationList).hasSize(0);
+
+ mTestableLooper.moveTimeForward(500);
+ waitForIdle();
+ verify(mContext).sendBroadcastAsUser(any(), eq(UserHandle.of(0)), eq(null));
+ }
+
private static <T extends Parcelable> T parcelAndUnparcel(T source,
Parcelable.Creator<T> creator) {
Parcel parcel = Parcel.obtain();
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerTest.java
index 0b147c3..b522cab 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerTest.java
@@ -18,6 +18,14 @@
import static android.app.Notification.FLAG_FOREGROUND_SERVICE;
import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
+import static android.service.notification.NotificationListenerService.REASON_CANCEL;
+import static android.service.notification.NotificationListenerService.REASON_GROUP_SUMMARY_CANCELED;
+import static android.service.notification.NotificationStats.DISMISSAL_BUBBLE;
+import static android.service.notification.NotificationStats.DISMISSAL_OTHER;
+
+import static com.android.server.notification.NotificationRecordLogger.NotificationCancelledEvent.NOTIFICATION_CANCEL_CLICK;
+import static com.android.server.notification.NotificationRecordLogger.NotificationCancelledEvent.NOTIFICATION_CANCEL_GROUP_SUMMARY_CANCELED;
+import static com.android.server.notification.NotificationRecordLogger.NotificationCancelledEvent.NOTIFICATION_CANCEL_USER_OTHER;
import static com.android.server.notification.NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_POSTED;
import static com.android.server.notification.NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_UPDATED;
@@ -208,4 +216,18 @@
/* eventType= */ NOTIFICATION_POSTED);
assertEquals(FrameworkStatsLog.NOTIFICATION_REPORTED__FSI_STATE__NO_FSI, fsiState);
}
+
+ @Test
+ public void testBubbleGroupSummaryDismissal() {
+ assertEquals(NOTIFICATION_CANCEL_GROUP_SUMMARY_CANCELED,
+ NotificationRecordLogger.NotificationCancelledEvent.fromCancelReason(
+ REASON_GROUP_SUMMARY_CANCELED, DISMISSAL_BUBBLE));
+ }
+
+ @Test
+ public void testOtherNotificationCancel() {
+ assertEquals(NOTIFICATION_CANCEL_USER_OTHER,
+ NotificationRecordLogger.NotificationCancelledEvent.fromCancelReason(
+ REASON_CANCEL, DISMISSAL_OTHER));
+ }
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java
index 539f329..318f932 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java
@@ -246,10 +246,10 @@
mPermissionHelper.setNotificationPermission("pkg", 10, true, true);
verify(mPermManager).grantRuntimePermission(
- "pkg", Manifest.permission.POST_NOTIFICATIONS, 10);
+ "pkg", Manifest.permission.POST_NOTIFICATIONS, Context.DEVICE_ID_DEFAULT, 10);
verify(mPermManager).updatePermissionFlags("pkg", Manifest.permission.POST_NOTIFICATIONS,
USER_FLAG_MASK | FLAG_PERMISSION_GRANTED_BY_DEFAULT,
- FLAG_PERMISSION_USER_SET, true, 10);
+ FLAG_PERMISSION_USER_SET, true, Context.DEVICE_ID_DEFAULT, 10);
}
@Test
@@ -259,16 +259,16 @@
.thenReturn(PERMISSION_DENIED);
when(mPermManager.getPermissionFlags(anyString(),
eq(Manifest.permission.POST_NOTIFICATIONS),
- anyInt())).thenReturn(FLAG_PERMISSION_GRANTED_BY_DEFAULT);
+ anyInt(), anyInt())).thenReturn(FLAG_PERMISSION_GRANTED_BY_DEFAULT);
PermissionHelper.PackagePermission pkgPerm = new PermissionHelper.PackagePermission(
"pkg", 10, true, false);
mPermissionHelper.setNotificationPermission(pkgPerm);
verify(mPermManager).grantRuntimePermission(
- "pkg", Manifest.permission.POST_NOTIFICATIONS, 10);
+ "pkg", Manifest.permission.POST_NOTIFICATIONS, Context.DEVICE_ID_DEFAULT, 10);
verify(mPermManager).updatePermissionFlags("pkg", Manifest.permission.POST_NOTIFICATIONS,
USER_FLAG_MASK | FLAG_PERMISSION_GRANTED_BY_DEFAULT,
- FLAG_PERMISSION_USER_SET, true, 10);
+ FLAG_PERMISSION_USER_SET, true, Context.DEVICE_ID_DEFAULT, 10);
}
@Test
@@ -279,10 +279,11 @@
mPermissionHelper.setNotificationPermission("pkg", 10, false, true);
verify(mPermManager).revokeRuntimePermission(
- eq("pkg"), eq(Manifest.permission.POST_NOTIFICATIONS), eq(10), anyString());
+ eq("pkg"), eq(Manifest.permission.POST_NOTIFICATIONS),
+ eq(Context.DEVICE_ID_DEFAULT), eq(10), anyString());
verify(mPermManager).updatePermissionFlags("pkg", Manifest.permission.POST_NOTIFICATIONS,
USER_FLAG_MASK | FLAG_PERMISSION_GRANTED_BY_DEFAULT,
- FLAG_PERMISSION_USER_SET, true, 10);
+ FLAG_PERMISSION_USER_SET, true, Context.DEVICE_ID_DEFAULT, 10);
}
@Test
@@ -293,9 +294,9 @@
mPermissionHelper.setNotificationPermission("pkg", 10, true, false);
verify(mPermManager).grantRuntimePermission(
- "pkg", Manifest.permission.POST_NOTIFICATIONS, 10);
+ "pkg", Manifest.permission.POST_NOTIFICATIONS, Context.DEVICE_ID_DEFAULT, 10);
verify(mPermManager).updatePermissionFlags("pkg", Manifest.permission.POST_NOTIFICATIONS,
- USER_FLAG_MASK, 0, true, 10);
+ USER_FLAG_MASK, 0, true, Context.DEVICE_ID_DEFAULT, 10);
}
@Test
@@ -306,36 +307,37 @@
mPermissionHelper.setNotificationPermission("pkg", 10, false, false);
verify(mPermManager).revokeRuntimePermission(
- eq("pkg"), eq(Manifest.permission.POST_NOTIFICATIONS), eq(10), anyString());
+ eq("pkg"), eq(Manifest.permission.POST_NOTIFICATIONS),
+ eq(Context.DEVICE_ID_DEFAULT), eq(10), anyString());
verify(mPermManager).updatePermissionFlags("pkg", Manifest.permission.POST_NOTIFICATIONS,
USER_FLAG_MASK | FLAG_PERMISSION_GRANTED_BY_DEFAULT, 0,
- true, 10);
+ true, Context.DEVICE_ID_DEFAULT, 10);
}
@Test
public void testSetNotificationPermission_SystemFixedPermNotSet() throws Exception {
when(mPermManager.getPermissionFlags(anyString(),
eq(Manifest.permission.POST_NOTIFICATIONS),
- anyInt())).thenReturn(FLAG_PERMISSION_SYSTEM_FIXED);
+ anyInt(), anyInt())).thenReturn(FLAG_PERMISSION_SYSTEM_FIXED);
mPermissionHelper.setNotificationPermission("pkg", 10, false, true);
verify(mPermManager, never()).revokeRuntimePermission(
- anyString(), anyString(), anyInt(), anyString());
+ anyString(), anyString(), anyInt(), anyInt(), anyString());
verify(mPermManager, never()).updatePermissionFlags(
- anyString(), anyString(), anyInt(), anyInt(), anyBoolean(), anyInt());
+ anyString(), anyString(), anyInt(), anyInt(), anyBoolean(), anyInt(), anyInt());
}
@Test
public void testSetNotificationPermission_PolicyFixedPermNotSet() throws Exception {
when(mPermManager.getPermissionFlags(anyString(),
eq(Manifest.permission.POST_NOTIFICATIONS),
- anyInt())).thenReturn(FLAG_PERMISSION_POLICY_FIXED);
+ anyInt(), anyInt())).thenReturn(FLAG_PERMISSION_POLICY_FIXED);
mPermissionHelper.setNotificationPermission("pkg", 10, false, true);
verify(mPermManager, never()).revokeRuntimePermission(
- anyString(), anyString(), anyInt(), anyString());
+ anyString(), anyString(), anyInt(), anyInt(), anyString());
verify(mPermManager, never()).updatePermissionFlags(
- anyString(), anyString(), anyInt(), anyInt(), anyBoolean(), anyInt());
+ anyString(), anyString(), anyInt(), anyInt(), anyBoolean(), anyInt(), anyInt());
}
@Test
@@ -345,7 +347,7 @@
mPermissionHelper.setNotificationPermission("pkg", 10, true, false);
verify(mPermManager, never()).grantRuntimePermission(
- "pkg", Manifest.permission.POST_NOTIFICATIONS, 10);
+ "pkg", Manifest.permission.POST_NOTIFICATIONS, Context.DEVICE_ID_DEFAULT, 10);
}
@Test
@@ -355,7 +357,8 @@
mPermissionHelper.setNotificationPermission("pkg", 10, false, false);
verify(mPermManager, never()).revokeRuntimePermission(
- eq("pkg"), eq(Manifest.permission.POST_NOTIFICATIONS), eq(10), anyString());
+ eq("pkg"), eq(Manifest.permission.POST_NOTIFICATIONS),
+ eq(Context.DEVICE_ID_DEFAULT), eq(10), anyString());
}
@Test
@@ -374,26 +377,27 @@
verify(mContext, never()).checkPermission(
eq(Manifest.permission.POST_NOTIFICATIONS), eq(-1), eq(testUid));
verify(mPermManager, never()).revokeRuntimePermission(
- eq("pkg"), eq(Manifest.permission.POST_NOTIFICATIONS), eq(10), anyString());
+ eq("pkg"), eq(Manifest.permission.POST_NOTIFICATIONS),
+ eq(Context.DEVICE_ID_DEFAULT), eq(10), anyString());
}
@Test
public void testIsPermissionFixed() throws Exception {
when(mPermManager.getPermissionFlags(anyString(),
eq(Manifest.permission.POST_NOTIFICATIONS),
- anyInt())).thenReturn(FLAG_PERMISSION_USER_SET);
+ anyInt(), anyInt())).thenReturn(FLAG_PERMISSION_USER_SET);
assertThat(mPermissionHelper.isPermissionFixed("pkg", 0)).isFalse();
when(mPermManager.getPermissionFlags(anyString(),
- eq(Manifest.permission.POST_NOTIFICATIONS),
+ eq(Manifest.permission.POST_NOTIFICATIONS), anyInt(),
anyInt())).thenReturn(FLAG_PERMISSION_USER_SET|FLAG_PERMISSION_POLICY_FIXED);
assertThat(mPermissionHelper.isPermissionFixed("pkg", 0)).isTrue();
when(mPermManager.getPermissionFlags(anyString(),
eq(Manifest.permission.POST_NOTIFICATIONS),
- anyInt())).thenReturn(FLAG_PERMISSION_SYSTEM_FIXED);
+ anyInt(), anyInt())).thenReturn(FLAG_PERMISSION_SYSTEM_FIXED);
assertThat(mPermissionHelper.isPermissionFixed("pkg", 0)).isTrue();
}
@@ -434,13 +438,14 @@
.thenReturn(requesting);
// 2 and 3 are user-set permissions
- when(mPermManager.getPermissionFlags(
- "first", Manifest.permission.POST_NOTIFICATIONS, userId)).thenReturn(0);
- when(mPermManager.getPermissionFlags(
- "second", Manifest.permission.POST_NOTIFICATIONS, userId))
+ when(mPermManager.getPermissionFlags("first", Manifest.permission.POST_NOTIFICATIONS,
+ Context.DEVICE_ID_DEFAULT, userId))
+ .thenReturn(0);
+ when(mPermManager.getPermissionFlags("second", Manifest.permission.POST_NOTIFICATIONS,
+ Context.DEVICE_ID_DEFAULT, userId))
.thenReturn(FLAG_PERMISSION_USER_SET);
- when(mPermManager.getPermissionFlags(
- "third", Manifest.permission.POST_NOTIFICATIONS, userId))
+ when(mPermManager.getPermissionFlags("third", Manifest.permission.POST_NOTIFICATIONS,
+ Context.DEVICE_ID_DEFAULT, userId))
.thenReturn(FLAG_PERMISSION_USER_SET);
Map<Pair<Integer, String>, Pair<Boolean, Boolean>> expected =
diff --git a/services/tests/vibrator/Android.bp b/services/tests/vibrator/Android.bp
new file mode 100644
index 0000000..ca5cfa5
--- /dev/null
+++ b/services/tests/vibrator/Android.bp
@@ -0,0 +1,62 @@
+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: "FrameworksVibratorServicesTests",
+
+ srcs: [
+ "src/**/*.java",
+ ],
+
+ libs: [
+ "android.hardware.vibrator-V2-java",
+ "android.test.mock",
+ "android.test.base",
+ "android.test.runner",
+ ],
+
+ static_libs: [
+ "androidx.test.core",
+ "androidx.test.ext.truth",
+ "androidx.test.runner",
+ "androidx.test.rules",
+ "androidx.test.ext.junit",
+ "frameworks-base-testutils",
+ "frameworks-services-vibrator-testutils",
+ "junit",
+ "mockito-target-minus-junit4",
+ "platform-test-annotations",
+ "service-permission.stubs.system_server",
+ "services.core",
+ ],
+
+ platform_apis: true,
+ certificate: "platform",
+ dxflags: ["--multi-dex"],
+
+ test_suites: [
+ "device-tests",
+ "automotive-tests",
+ ],
+
+ optimize: {
+ enabled: false,
+ },
+}
+
+java_library {
+ name: "frameworks-services-vibrator-testutils",
+ visibility: [":__subpackages__"],
+ srcs: [
+ "utils/**/*.java",
+ ],
+ static_libs: [
+ "services.core",
+ ],
+}
diff --git a/services/tests/vibrator/AndroidManifest.xml b/services/tests/vibrator/AndroidManifest.xml
new file mode 100644
index 0000000..2a15c15
--- /dev/null
+++ b/services/tests/vibrator/AndroidManifest.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.framework.services.tests.vibrator">
+
+ <!-- Required to set user settings -->
+ <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
+ <!-- Required to register uid observer -->
+ <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />
+ <!-- Required to acquire wake locks during vibrations -->
+ <uses-permission android:name="android.permission.WAKE_LOCK" />
+ <uses-permission android:name="android.permission.UPDATE_DEVICE_STATS" />
+ <!-- Required to request vibrations -->
+ <uses-permission android:name="android.permission.VIBRATE" />
+ <!-- Required to listen to the vibrator state -->
+ <uses-permission android:name="android.permission.ACCESS_VIBRATOR_STATE" />
+ <!-- Required to set always-on vibrations -->
+ <uses-permission android:name="android.permission.VIBRATE_ALWAYS_ON" />
+
+ <application android:debuggable="true"
+ android:testOnly="true">
+ <uses-library android:name="android.test.mock" android:required="true" />
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:label="Vibrator Service Tests"
+ android:targetPackage="com.android.framework.services.tests.vibrator" />
+
+</manifest>
diff --git a/services/tests/vibrator/AndroidTest.xml b/services/tests/vibrator/AndroidTest.xml
new file mode 100644
index 0000000..d5ee3af
--- /dev/null
+++ b/services/tests/vibrator/AndroidTest.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<configuration description="Runs Frameworks Vibrator Services 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="FrameworksVibratorServicesTests.apk" />
+ </target_preparer>
+
+ <option name="test-tag" value="FrameworksVibratorServicesTests" />
+
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+ <option name="package" value="com.android.framework.services.tests.vibrator" />
+ <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+ <option name="hidden-api-checks" value="false" />
+ </test>
+</configuration>
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/OWNERS b/services/tests/vibrator/OWNERS
similarity index 71%
rename from services/tests/servicestests/src/com/android/server/vibrator/OWNERS
rename to services/tests/vibrator/OWNERS
index cc63ceb..93b44f4 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/OWNERS
+++ b/services/tests/vibrator/OWNERS
@@ -1 +1,3 @@
+# Bug component: 345036
+
include /services/core/java/com/android/server/vibrator/OWNERS
diff --git a/services/tests/vibrator/TEST_MAPPING b/services/tests/vibrator/TEST_MAPPING
new file mode 100644
index 0000000..f0a7e47
--- /dev/null
+++ b/services/tests/vibrator/TEST_MAPPING
@@ -0,0 +1,21 @@
+{
+ "presubmit": [
+ {
+ "name": "FrameworksVibratorServicesTests",
+ "options": [
+ {"exclude-annotation": "android.platform.test.annotations.LargeTest"},
+ {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
+ {"exclude-annotation": "androidx.test.filters.FlakyTest"},
+ {"exclude-annotation": "org.junit.Ignore"}
+ ]
+ }
+ ],
+ "postsubmit": [
+ {
+ "name": "FrameworksVibratorServicesTests",
+ "options": [
+ {"exclude-annotation": "org.junit.Ignore"}
+ ]
+ }
+ ]
+}
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/DeviceAdapterTest.java b/services/tests/vibrator/src/com/android/server/vibrator/DeviceAdapterTest.java
similarity index 98%
rename from services/tests/servicestests/src/com/android/server/vibrator/DeviceAdapterTest.java
rename to services/tests/vibrator/src/com/android/server/vibrator/DeviceAdapterTest.java
index 27ed507..3013ed0 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/DeviceAdapterTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/DeviceAdapterTest.java
@@ -32,7 +32,6 @@
import android.os.vibrator.RampSegment;
import android.os.vibrator.StepSegment;
import android.os.vibrator.VibrationEffectSegment;
-import android.platform.test.annotations.Presubmit;
import android.util.SparseArray;
import androidx.test.InstrumentationRegistry;
@@ -48,13 +47,6 @@
import java.util.Arrays;
-/**
- * Tests for {@link DeviceAdapter}.
- *
- * Build/Install/Run:
- * atest FrameworksServicesTests:DeviceAdapterTest
- */
-@Presubmit
public class DeviceAdapterTest {
private static final int EMPTY_VIBRATOR_ID = 1;
private static final int PWLE_VIBRATOR_ID = 2;
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/InputDeviceDelegateTest.java b/services/tests/vibrator/src/com/android/server/vibrator/InputDeviceDelegateTest.java
similarity index 97%
rename from services/tests/servicestests/src/com/android/server/vibrator/InputDeviceDelegateTest.java
rename to services/tests/vibrator/src/com/android/server/vibrator/InputDeviceDelegateTest.java
index 2ab79fc..f3ecfcc 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/InputDeviceDelegateTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/InputDeviceDelegateTest.java
@@ -41,7 +41,6 @@
import android.os.VibrationAttributes;
import android.os.VibrationEffect;
import android.os.test.TestLooper;
-import android.platform.test.annotations.Presubmit;
import android.view.InputDevice;
import androidx.test.InstrumentationRegistry;
@@ -54,13 +53,6 @@
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
-/**
- * Tests for {@link InputDeviceDelegate}.
- *
- * Build/Install/Run:
- * atest FrameworksServicesTests:InputDeviceDelegateTest
- */
-@Presubmit
public class InputDeviceDelegateTest {
private static final int UID = Process.ROOT_UID;
@@ -77,6 +69,7 @@
private TestLooper mTestLooper;
private ContextWrapper mContextSpy;
+ private InputManagerGlobal.TestSession mInputManagerGlobalSession;
private InputManager mInputManager;
private InputDeviceDelegate mInputDeviceDelegate;
private IInputDevicesChangedListener mIInputDevicesChangedListener;
@@ -84,7 +77,7 @@
@Before
public void setUp() throws Exception {
mTestLooper = new TestLooper();
- InputManagerGlobal.resetInstance(mIInputManagerMock);
+ mInputManagerGlobalSession = InputManagerGlobal.createTestSession(mIInputManagerMock);
mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getContext()));
mInputManager = new InputManager(mContextSpy);
@@ -100,7 +93,9 @@
@After
public void tearDown() throws Exception {
- InputManagerGlobal.clearInstance();
+ if (mInputManagerGlobalSession != null) {
+ mInputManagerGlobalSession.close();
+ }
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/RampDownAdapterTest.java b/services/tests/vibrator/src/com/android/server/vibrator/RampDownAdapterTest.java
similarity index 98%
rename from services/tests/servicestests/src/com/android/server/vibrator/RampDownAdapterTest.java
rename to services/tests/vibrator/src/com/android/server/vibrator/RampDownAdapterTest.java
index 78ded09..141da7cb 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/RampDownAdapterTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/RampDownAdapterTest.java
@@ -25,7 +25,6 @@
import android.os.vibrator.RampSegment;
import android.os.vibrator.StepSegment;
import android.os.vibrator.VibrationEffectSegment;
-import android.platform.test.annotations.Presubmit;
import org.junit.Before;
import org.junit.Test;
@@ -34,13 +33,6 @@
import java.util.Arrays;
import java.util.List;
-/**
- * Tests for {@link RampDownAdapter}.
- *
- * Build/Install/Run:
- * atest FrameworksServicesTests:RampDownAdapterTest
- */
-@Presubmit
public class RampDownAdapterTest {
private static final int TEST_RAMP_DOWN_DURATION = 20;
private static final int TEST_STEP_DURATION = 5;
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/RampToStepAdapterTest.java b/services/tests/vibrator/src/com/android/server/vibrator/RampToStepAdapterTest.java
similarity index 96%
rename from services/tests/servicestests/src/com/android/server/vibrator/RampToStepAdapterTest.java
rename to services/tests/vibrator/src/com/android/server/vibrator/RampToStepAdapterTest.java
index f9c47fa..8bb21b3 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/RampToStepAdapterTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/RampToStepAdapterTest.java
@@ -26,7 +26,6 @@
import android.os.vibrator.RampSegment;
import android.os.vibrator.StepSegment;
import android.os.vibrator.VibrationEffectSegment;
-import android.platform.test.annotations.Presubmit;
import org.junit.Before;
import org.junit.Test;
@@ -36,13 +35,6 @@
import java.util.List;
import java.util.stream.IntStream;
-/**
- * Tests for {@link RampToStepAdapter}.
- *
- * Build/Install/Run:
- * atest FrameworksServicesTests:RampToStepAdapterTest
- */
-@Presubmit
public class RampToStepAdapterTest {
private static final int TEST_STEP_DURATION = 5;
private static final float[] TEST_AMPLITUDE_MAP = new float[]{
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/StepToRampAdapterTest.java b/services/tests/vibrator/src/com/android/server/vibrator/StepToRampAdapterTest.java
similarity index 97%
rename from services/tests/servicestests/src/com/android/server/vibrator/StepToRampAdapterTest.java
rename to services/tests/vibrator/src/com/android/server/vibrator/StepToRampAdapterTest.java
index b22efa2..58deeec 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/StepToRampAdapterTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/StepToRampAdapterTest.java
@@ -26,7 +26,6 @@
import android.os.vibrator.RampSegment;
import android.os.vibrator.StepSegment;
import android.os.vibrator.VibrationEffectSegment;
-import android.platform.test.annotations.Presubmit;
import org.junit.Before;
import org.junit.Test;
@@ -36,13 +35,6 @@
import java.util.List;
import java.util.stream.IntStream;
-/**
- * Tests for {@link StepToRampAdapter}.
- *
- * Build/Install/Run:
- * atest FrameworksServicesTests:StepToRampAdapterTest
- */
-@Presubmit
public class StepToRampAdapterTest {
private static final float[] TEST_AMPLITUDE_MAP = new float[]{
/* 50Hz= */ 0.1f, 0.2f, 0.4f, 0.8f, /* 150Hz= */ 1f, 0.9f, /* 200Hz= */ 0.8f};
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibrationScalerTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibrationScalerTest.java
similarity index 98%
rename from services/tests/servicestests/src/com/android/server/vibrator/VibrationScalerTest.java
rename to services/tests/vibrator/src/com/android/server/vibrator/VibrationScalerTest.java
index 7c321d4..bbca704e 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibrationScalerTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationScalerTest.java
@@ -48,7 +48,6 @@
import android.os.vibrator.StepSegment;
import android.os.vibrator.VibrationConfig;
import android.os.vibrator.VibrationEffectSegment;
-import android.platform.test.annotations.Presubmit;
import android.provider.Settings;
import androidx.test.InstrumentationRegistry;
@@ -65,13 +64,6 @@
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
-/**
- * Tests for {@link VibrationScaler}.
- *
- * Build/Install/Run:
- * atest FrameworksServicesTests:VibrationScalerTest
- */
-@Presubmit
public class VibrationScalerTest {
@Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibrationSettingsTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java
similarity index 99%
rename from services/tests/servicestests/src/com/android/server/vibrator/VibrationSettingsTest.java
rename to services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java
index b6f1271..1ae0966 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibrationSettingsTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java
@@ -70,7 +70,6 @@
import android.os.Vibrator;
import android.os.test.TestLooper;
import android.os.vibrator.VibrationConfig;
-import android.platform.test.annotations.Presubmit;
import android.provider.Settings;
import android.util.ArraySet;
import android.view.Display;
@@ -94,13 +93,6 @@
import java.util.HashSet;
import java.util.Set;
-/**
- * Tests for {@link VibrationSettings}.
- *
- * Build/Install/Run:
- * atest FrameworksServicesTests:VibrationSettingsTest
- */
-@Presubmit
public class VibrationSettingsTest {
private static final int UID = 1;
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibrationTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibrationTest.java
similarity index 86%
rename from services/tests/servicestests/src/com/android/server/vibrator/VibrationTest.java
rename to services/tests/vibrator/src/com/android/server/vibrator/VibrationTest.java
index b469299..84f8412 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibrationTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationTest.java
@@ -20,19 +20,10 @@
import static java.util.stream.Collectors.toList;
-import android.platform.test.annotations.Presubmit;
-
import org.junit.Test;
import java.util.Arrays;
-/**
- * Tests for {@link Vibration}.
- *
- * Build/Install/Run:
- * atest FrameworksServicesTests:VibrationTest
- */
-@Presubmit
public class VibrationTest {
@Test
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
similarity index 95%
rename from services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java
rename to services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
index d9a51a01..aa3bee4 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
@@ -19,6 +19,8 @@
import static android.os.VibrationEffect.VibrationParameter.targetAmplitude;
import static android.os.VibrationEffect.VibrationParameter.targetFrequency;
+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.assertTrue;
@@ -59,7 +61,6 @@
import android.os.vibrator.VibrationConfig;
import android.os.vibrator.VibrationEffectSegment;
import android.platform.test.annotations.LargeTest;
-import android.platform.test.annotations.Presubmit;
import android.util.SparseArray;
import androidx.test.InstrumentationRegistry;
@@ -68,6 +69,7 @@
import org.junit.After;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.mockito.InOrder;
@@ -83,13 +85,6 @@
import java.util.function.BooleanSupplier;
import java.util.stream.Collectors;
-/**
- * Tests for {@link VibrationThread}.
- *
- * Build/Install/Run:
- * atest FrameworksServicesTests:VibrationThreadTest
- */
-@Presubmit
public class VibrationThreadTest {
private static final int TEST_TIMEOUT_MILLIS = 900;
@@ -305,6 +300,80 @@
}
@Test
+ public void vibrate_singleVibratorPatternWithZeroDurationSteps_skipsZeroDurationSteps() {
+ mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+
+ VibrationEffect effect = VibrationEffect.createWaveform(
+ /* timings= */ new long[]{0, 100, 50, 100, 0, 0, 0, 50}, /* repeat= */ -1);
+ VibrationStepConductor conductor = startThreadAndDispatcher(effect);
+ long vibrationId = conductor.getVibration().id;
+ waitForCompletion();
+
+ verify(mManagerHooks).noteVibratorOn(eq(UID), eq(300L));
+ verify(mManagerHooks).noteVibratorOff(eq(UID));
+
+ verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
+ assertThat(mControllers.get(VIBRATOR_ID).isVibrating()).isFalse();
+
+ assertThat(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId))
+ .isEqualTo(expectedOneShots(100L, 150L));
+ }
+
+ @Test
+ public void vibrate_singleVibratorPatternWithZeroDurationAndAmplitude_skipsZeroDurationSteps() {
+ mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+
+ int[] amplitudes = new int[]{1, 2, 0, 3, 4, 5, 0, 6};
+ VibrationEffect effect = VibrationEffect.createWaveform(
+ /* timings= */ new long[]{0, 100, 0, 50, 50, 0, 100, 50}, amplitudes,
+ /* repeat= */ -1);
+ VibrationStepConductor conductor = startThreadAndDispatcher(effect);
+ long vibrationId = conductor.getVibration().id;
+ waitForCompletion();
+
+ verify(mManagerHooks).noteVibratorOn(eq(UID), eq(350L));
+ verify(mManagerHooks).noteVibratorOff(eq(UID));
+
+ verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
+ assertThat(mControllers.get(VIBRATOR_ID).isVibrating()).isFalse();
+
+ assertThat(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId))
+ .isEqualTo(expectedOneShots(200L, 50L));
+ }
+
+ @LargeTest
+ @Test
+ public void vibrate_singleVibratorRepeatingPatternWithZeroDurationSteps_repeatsEffectCorrectly()
+ throws Exception {
+ FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
+ fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+
+ VibrationEffect effect = VibrationEffect.createWaveform(
+ /* timings= */ new long[]{0, 200, 50, 100, 0, 50, 50, 100}, /* repeat= */ 0);
+ VibrationStepConductor conductor = startThreadAndDispatcher(effect);
+ long vibrationId = conductor.getVibration().id;
+ // We are expect this test to repeat the vibration effect twice, which would result in 5
+ // segments being played:
+ // 200ms ON
+ // 150ms ON (100ms + 50ms, skips 0ms)
+ // 300ms ON (100ms + 200ms looping to the start and skipping first 0ms)
+ // 150ms ON (100ms + 50ms, skips 0ms)
+ // 300ms ON (100ms + 200ms looping to the start and skipping first 0ms)
+ assertTrue(waitUntil(() -> fakeVibrator.getEffectSegments(vibrationId).size() >= 5,
+ 5000L + TEST_TIMEOUT_MILLIS));
+ conductor.notifyCancelled(
+ new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_USER),
+ /* immediate= */ false);
+ waitForCompletion();
+
+ verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_USER);
+ assertThat(mControllers.get(VIBRATOR_ID).isVibrating()).isFalse();
+
+ assertThat(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId).subList(0, 5))
+ .isEqualTo(expectedOneShots(200L, 150L, 300L, 150L, 300L));
+ }
+
+ @Test
public void vibrate_singleVibratorRepeatingPwle_generatesLargestPwles() throws Exception {
FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
@@ -392,6 +461,7 @@
fakeVibrator.getEffectSegments(vibrationId));
}
+ @Ignore("b/290940400")
@LargeTest
@Test
public void vibrate_singleVibratorRepeatingAlwaysOnWaveform_turnsVibratorBackOn()
@@ -1640,6 +1710,12 @@
/* frequencyHz= */ 0, (int) millis);
}
+ private List<VibrationEffectSegment> expectedOneShots(long... millis) {
+ return Arrays.stream(millis)
+ .mapToObj(this::expectedOneShot)
+ .collect(Collectors.toList());
+ }
+
private VibrationEffectSegment expectedPrebaked(int effectId) {
return new PrebakedSegment(effectId, false, VibrationEffect.EFFECT_STRENGTH_MEDIUM);
}
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibratorControllerTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControllerTest.java
similarity index 98%
rename from services/tests/servicestests/src/com/android/server/vibrator/VibratorControllerTest.java
rename to services/tests/vibrator/src/com/android/server/vibrator/VibratorControllerTest.java
index f2c1874..0d13be6 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibratorControllerTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControllerTest.java
@@ -45,7 +45,6 @@
import android.os.vibrator.PrebakedSegment;
import android.os.vibrator.PrimitiveSegment;
import android.os.vibrator.RampSegment;
-import android.platform.test.annotations.Presubmit;
import androidx.test.InstrumentationRegistry;
@@ -61,13 +60,6 @@
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
-/**
- * Tests for {@link VibratorController}.
- *
- * Build/Install/Run:
- * atest FrameworksServicesTests:VibratorControllerTest
- */
-@Presubmit
public class VibratorControllerTest {
private static final int VIBRATOR_ID = 0;
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibratorFrameworkStatsLoggerTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorFrameworkStatsLoggerTest.java
similarity index 95%
rename from services/tests/servicestests/src/com/android/server/vibrator/VibratorFrameworkStatsLoggerTest.java
rename to services/tests/vibrator/src/com/android/server/vibrator/VibratorFrameworkStatsLoggerTest.java
index c1ab1db..3466bbb 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibratorFrameworkStatsLoggerTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorFrameworkStatsLoggerTest.java
@@ -21,7 +21,6 @@
import android.os.Handler;
import android.os.test.TestLooper;
-import android.platform.test.annotations.Presubmit;
import org.junit.Before;
import org.junit.Rule;
@@ -29,13 +28,6 @@
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
-/**
- * Tests for {@link VibratorFrameworkStatsLogger}.
- *
- * Build/Install/Run:
- * atest FrameworksServicesTests:VibratorFrameworkStatsLoggerTest
- */
-@Presubmit
public class VibratorFrameworkStatsLoggerTest {
@Rule public MockitoRule rule = MockitoJUnit.rule();
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
similarity index 99%
rename from services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
rename to services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
index c805fc5..c6cd078 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
@@ -70,13 +70,13 @@
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.os.VibratorInfo;
+import android.os.test.FakeVibrator;
import android.os.test.TestLooper;
import android.os.vibrator.PrebakedSegment;
import android.os.vibrator.PrimitiveSegment;
import android.os.vibrator.StepSegment;
import android.os.vibrator.VibrationConfig;
import android.os.vibrator.VibrationEffectSegment;
-import android.platform.test.annotations.Presubmit;
import android.provider.Settings;
import android.util.ArraySet;
import android.util.SparseBooleanArray;
@@ -111,13 +111,6 @@
import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;
-/**
- * Tests for {@link VibratorManagerService}.
- *
- * Build/Install/Run:
- * atest FrameworksServicesTests:VibratorManagerServiceTest
- */
-@Presubmit
public class VibratorManagerServiceTest {
private static final int TEST_TIMEOUT_MILLIS = 1_000;
@@ -186,14 +179,14 @@
private VirtualDeviceManagerInternal.VirtualDisplayListener mRegisteredVirtualDisplayListener;
private VirtualDeviceManagerInternal.AppsOnVirtualDeviceListener
mRegisteredAppsOnVirtualDeviceListener;
-
+ private InputManagerGlobal.TestSession mInputManagerGlobalSession;
private InputManager mInputManager;
@Before
public void setUp() throws Exception {
mTestLooper = new TestLooper();
mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getContext()));
- InputManagerGlobal.resetInstance(mIInputManagerMock);
+ mInputManagerGlobalSession = InputManagerGlobal.createTestSession(mIInputManagerMock);
mVibrationConfig = new VibrationConfig(mContextSpy.getResources());
ContentResolver contentResolver = mSettingsProviderRule.mockContentResolver(mContextSpy);
@@ -268,7 +261,9 @@
LocalServices.removeServiceForTest(PowerManagerInternal.class);
// Ignore potential exceptions about the looper having never dispatched any messages.
mTestLooper.stopAutoDispatchAndIgnoreExceptions();
- InputManagerGlobal.clearInstance();
+ if (mInputManagerGlobalSession != null) {
+ mInputManagerGlobalSession.close();
+ }
}
private VibratorManagerService createSystemReadyService() {
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/FakeVibrator.java b/services/tests/vibrator/utils/android/os/test/FakeVibrator.java
similarity index 91%
rename from services/tests/servicestests/src/com/android/server/vibrator/FakeVibrator.java
rename to services/tests/vibrator/utils/android/os/test/FakeVibrator.java
index 4556a4a..56f49d4e 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/FakeVibrator.java
+++ b/services/tests/vibrator/utils/android/os/test/FakeVibrator.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.vibrator;
+package android.os.test;
import android.annotation.NonNull;
import android.content.Context;
@@ -23,9 +23,9 @@
import android.os.Vibrator;
/** Fake implementation of {@link Vibrator} for service tests. */
-final class FakeVibrator extends Vibrator {
+public final class FakeVibrator extends Vibrator {
- FakeVibrator(Context context) {
+ public FakeVibrator(Context context) {
super(context);
}
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java b/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorControllerProvider.java
similarity index 98%
rename from services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java
rename to services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorControllerProvider.java
index c484f45..12815fa 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java
+++ b/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorControllerProvider.java
@@ -37,10 +37,10 @@
import java.util.TreeMap;
/**
- * Provides {@link VibratorController} with controlled vibrator hardware capabilities and
- * interactions.
+ * Provides {@link VibratorController} with configurable vibrator hardware capabilities and
+ * fake interactions for tests.
*/
-final class FakeVibratorControllerProvider {
+public final class FakeVibratorControllerProvider {
private static final int EFFECT_DURATION = 20;
private final Map<Long, PrebakedSegment> mEnabledAlwaysOnEffects = new HashMap<>();
diff --git a/services/tests/wmtests/AndroidManifest.xml b/services/tests/wmtests/AndroidManifest.xml
index 9c79375..554b0f4 100644
--- a/services/tests/wmtests/AndroidManifest.xml
+++ b/services/tests/wmtests/AndroidManifest.xml
@@ -109,10 +109,6 @@
</intent-filter>
</activity>
- <service android:name="android.view.cts.surfacevalidator.LocalMediaProjectionService"
- android:foregroundServiceType="mediaProjection"
- android:enabled="true">
- </service>
</application>
<instrumentation
diff --git a/services/tests/wmtests/OWNERS b/services/tests/wmtests/OWNERS
index 7a128fc..cece37f 100644
--- a/services/tests/wmtests/OWNERS
+++ b/services/tests/wmtests/OWNERS
@@ -1,3 +1,4 @@
+# Bug template url: https://b.corp.google.com/issues/new?component=316125&template=1018199
include /services/core/java/com/android/server/wm/OWNERS
# Voice Interaction
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java b/services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java
index fe1ea0d..f6f3f03 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java
@@ -22,11 +22,17 @@
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.verify;
import android.app.Activity;
import android.app.ActivityManager.RunningTaskInfo;
@@ -41,6 +47,7 @@
import android.os.Bundle;
import android.os.IBinder;
import android.platform.test.annotations.Presubmit;
+import android.util.Log;
import android.util.Rational;
import android.view.SurfaceControl;
import android.window.TaskOrganizer;
@@ -48,7 +55,10 @@
import androidx.test.filters.MediumTest;
import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import java.util.ArrayList;
+import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -166,6 +176,122 @@
}
}
+ /**
+ * Tests if any unknown key is being used in the ActivityOptions bundle. If so, please review
+ * if the newly added bundle should be protected with permissions to avoid malicious attacks.
+ *
+ * @see SafeActivityOptionsTest#test_getOptions
+ */
+ @Test
+ public void testActivityOptionsFromBundle() {
+ // Spy on a bundle that is generated from a basic ActivityOptions.
+ final ActivityOptions options = ActivityOptions.makeBasic();
+ Bundle bundle = options.toBundle();
+ spyOn(bundle);
+
+ // Create a new ActivityOptions from the bundle
+ new ActivityOptions(bundle);
+
+ // Verify the keys that are being used.
+ final ArgumentCaptor<String> stringCaptor = ArgumentCaptor.forClass(String.class);
+ verify(bundle, atLeastOnce()).getString(stringCaptor.capture());
+ verify(bundle, atLeastOnce()).getBoolean(stringCaptor.capture());
+ verify(bundle, atLeastOnce()).getParcelable(stringCaptor.capture(), any());
+ verify(bundle, atLeastOnce()).getInt(stringCaptor.capture(), anyInt());
+ verify(bundle, atLeastOnce()).getBinder(stringCaptor.capture());
+ verify(bundle, atLeastOnce()).getBundle(stringCaptor.capture());
+ final List<String> keys = stringCaptor.getAllValues();
+ final List<String> unknownKeys = new ArrayList<>();
+ for (String key : keys) {
+ switch (key) {
+ case ActivityOptions.KEY_PACKAGE_NAME:
+ case ActivityOptions.KEY_LAUNCH_BOUNDS:
+ case ActivityOptions.KEY_ANIM_TYPE:
+ case ActivityOptions.KEY_ANIM_ENTER_RES_ID:
+ case ActivityOptions.KEY_ANIM_EXIT_RES_ID:
+ case ActivityOptions.KEY_ANIM_IN_PLACE_RES_ID:
+ case ActivityOptions.KEY_ANIM_BACKGROUND_COLOR:
+ case ActivityOptions.KEY_ANIM_THUMBNAIL:
+ case ActivityOptions.KEY_ANIM_START_X:
+ case ActivityOptions.KEY_ANIM_START_Y:
+ case ActivityOptions.KEY_ANIM_WIDTH:
+ case ActivityOptions.KEY_ANIM_HEIGHT:
+ case ActivityOptions.KEY_ANIM_START_LISTENER:
+ case ActivityOptions.KEY_SPLASH_SCREEN_THEME:
+ case ActivityOptions.KEY_LEGACY_PERMISSION_PROMPT_ELIGIBLE:
+ case ActivityOptions.KEY_LAUNCH_ROOT_TASK_TOKEN:
+ case ActivityOptions.KEY_LAUNCH_TASK_FRAGMENT_TOKEN:
+ case ActivityOptions.KEY_TRANSIENT_LAUNCH:
+ case "android:activity.animationFinishedListener":
+ // KEY_ANIMATION_FINISHED_LISTENER
+ case "android:activity.animSpecs": // KEY_ANIM_SPECS
+ case "android:activity.lockTaskMode": // KEY_LOCK_TASK_MODE
+ case "android:activity.shareIdentity": // KEY_SHARE_IDENTITY
+ case "android.activity.launchDisplayId": // KEY_LAUNCH_DISPLAY_ID
+ case "android.activity.callerDisplayId": // KEY_CALLER_DISPLAY_ID
+ case "android.activity.launchTaskDisplayAreaToken":
+ // KEY_LAUNCH_TASK_DISPLAY_AREA_TOKEN
+ case "android.activity.launchTaskDisplayAreaFeatureId":
+ // KEY_LAUNCH_TASK_DISPLAY_AREA_FEATURE_ID
+ case "android.activity.windowingMode": // KEY_LAUNCH_WINDOWING_MODE
+ case "android.activity.activityType": // KEY_LAUNCH_ACTIVITY_TYPE
+ case "android.activity.launchTaskId": // KEY_LAUNCH_TASK_ID
+ case "android.activity.disableStarting": // KEY_DISABLE_STARTING_WINDOW
+ case "android.activity.pendingIntentLaunchFlags":
+ // KEY_PENDING_INTENT_LAUNCH_FLAGS
+ case "android.activity.alwaysOnTop": // KEY_TASK_ALWAYS_ON_TOP
+ case "android.activity.taskOverlay": // KEY_TASK_OVERLAY
+ case "android.activity.taskOverlayCanResume": // KEY_TASK_OVERLAY_CAN_RESUME
+ case "android.activity.avoidMoveToFront": // KEY_AVOID_MOVE_TO_FRONT
+ case "android.activity.freezeRecentTasksReordering":
+ // KEY_FREEZE_RECENT_TASKS_REORDERING
+ case "android:activity.disallowEnterPictureInPictureWhileLaunching":
+ // KEY_DISALLOW_ENTER_PICTURE_IN_PICTURE_WHILE_LAUNCHING
+ case "android:activity.applyActivityFlagsForBubbles":
+ // KEY_APPLY_ACTIVITY_FLAGS_FOR_BUBBLES
+ case "android:activity.applyMultipleTaskFlagForShortcut":
+ // KEY_APPLY_MULTIPLE_TASK_FLAG_FOR_SHORTCUT
+ case "android:activity.applyNoUserActionFlagForShortcut":
+ // KEY_APPLY_NO_USER_ACTION_FLAG_FOR_SHORTCUT
+ case "android:activity.transitionCompleteListener":
+ // KEY_TRANSITION_COMPLETE_LISTENER
+ case "android:activity.transitionIsReturning": // KEY_TRANSITION_IS_RETURNING
+ case "android:activity.sharedElementNames": // KEY_TRANSITION_SHARED_ELEMENTS
+ case "android:activity.resultData": // KEY_RESULT_DATA
+ case "android:activity.resultCode": // KEY_RESULT_CODE
+ case "android:activity.exitCoordinatorIndex": // KEY_EXIT_COORDINATOR_INDEX
+ case "android.activity.sourceInfo": // KEY_SOURCE_INFO
+ case "android:activity.usageTimeReport": // KEY_USAGE_TIME_REPORT
+ case "android:activity.rotationAnimationHint": // KEY_ROTATION_ANIMATION_HINT
+ case "android:instantapps.installerbundle": // KEY_INSTANT_APP_VERIFICATION_BUNDLE
+ case "android:activity.specsFuture": // KEY_SPECS_FUTURE
+ case "android:activity.remoteAnimationAdapter": // KEY_REMOTE_ANIMATION_ADAPTER
+ case "android:activity.remoteTransition": // KEY_REMOTE_TRANSITION
+ case "android:activity.overrideTaskTransition": // KEY_OVERRIDE_TASK_TRANSITION
+ case "android.activity.removeWithTaskOrganizer": // KEY_REMOVE_WITH_TASK_ORGANIZER
+ case "android.activity.launchTypeBubble": // KEY_LAUNCHED_FROM_BUBBLE
+ case "android.activity.splashScreenStyle": // KEY_SPLASH_SCREEN_STYLE
+ case "android.activity.launchIntoPipParams": // KEY_LAUNCH_INTO_PIP_PARAMS
+ case "android.activity.dismissKeyguard": // KEY_DISMISS_KEYGUARD
+ case "android.activity.pendingIntentCreatorBackgroundActivityStartMode":
+ // KEY_PENDING_INTENT_CREATOR_BACKGROUND_ACTIVITY_START_MODE
+ case "android.activity.launchCookie": // KEY_LAUNCH_COOKIE
+ // Existing keys
+ break;
+ default:
+ unknownKeys.add(key);
+ break;
+ }
+ }
+
+ // Report if any unknown key exists.
+ for (String key : unknownKeys) {
+ Log.e("ActivityOptionsTests", "Unknown key " + key + " is found. "
+ + "Please review if the given bundle should be protected with permissions.");
+ }
+ assertTrue(unknownKeys.isEmpty());
+ }
+
public static class TrampolineActivity extends Activity {
static int sTaskId;
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index d179338..858a384 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -1332,6 +1332,10 @@
starter.setReason("testNoActivityInfo").setIntent(intent)
.setActivityInfo(null).execute();
verify(starter.mRequest).resolveActivity(any());
+
+ // Also verifies the value of Request#componentSpecified should be true even the
+ // ActivityStarter#setComponentSpecified is not explicitly set.
+ assertTrue(starter.mRequest.componentSpecified);
}
@Test
@@ -1425,6 +1429,30 @@
}
@Test
+ public void testTransientLaunchWithKeyguard() {
+ final ActivityStarter starter = prepareStarter(0 /* flags */);
+ final ActivityRecord target = new ActivityBuilder(mAtm).setCreateTask(true).build();
+ final ActivityRecord top = new ActivityBuilder(mAtm).setCreateTask(true).build();
+ final KeyguardController keyguard = mSupervisor.getKeyguardController();
+ doReturn(true).when(keyguard).isKeyguardLocked(anyInt());
+ doReturn(true).when(keyguard).isDisplayOccluded(anyInt());
+ registerTestTransitionPlayer();
+ starter.setReason("testTransientLaunchWithKeyguard")
+ .setActivityOptions(ActivityOptions.makeBasic().setTransientLaunch().toBundle())
+ .setIntent(target.intent)
+ .execute();
+ final TransitionController controller = mRootWindowContainer.mTransitionController;
+ final Transition transition = controller.getCollectingTransition();
+ final Transition.ChangeInfo targetChangeInfo = transition.mChanges.get(target);
+
+ assertThat(targetChangeInfo).isNotNull();
+ assertThat(targetChangeInfo.hasChanged()).isTrue();
+ assertThat(controller.isCollecting(top.getTask())).isTrue();
+ assertThat(transition.isTransientLaunch(target)).isTrue();
+ assertThat(transition.isInTransientHide(top.getTask())).isTrue();
+ }
+
+ @Test
public void testActivityStart_expectAddedToRecentTask() {
RecentTasks recentTasks = mock(RecentTasks.class);
mAtm.mTaskSupervisor.setRecentTasks(recentTasks);
@@ -1745,8 +1773,7 @@
public void testLaunchActivityWithoutDisplayCategory() {
final ActivityInfo info = new ActivityInfo();
info.applicationInfo = new ApplicationInfo();
- info.taskAffinity = ActivityRecord.computeTaskAffinity("test", DEFAULT_FAKE_UID,
- 0 /* launchMode */, null /* componentName */);
+ info.taskAffinity = ActivityRecord.computeTaskAffinity("test", DEFAULT_FAKE_UID);
info.requiredDisplayCategory = "automotive";
final Task task = new TaskBuilder(mSupervisor).setCreateActivity(true).setActivityInfo(info)
.build();
@@ -1771,8 +1798,7 @@
public void testLaunchActivityWithDifferentDisplayCategory() {
final ActivityInfo info = new ActivityInfo();
info.applicationInfo = new ApplicationInfo();
- info.taskAffinity = ActivityRecord.computeTaskAffinity("test", DEFAULT_FAKE_UID,
- 0 /* launchMode */, null /* componentName */);
+ info.taskAffinity = ActivityRecord.computeTaskAffinity("test", DEFAULT_FAKE_UID);
info.requiredDisplayCategory = "automotive";
final Task task = new TaskBuilder(mSupervisor).setCreateActivity(true).setActivityInfo(info)
.build();
@@ -1797,8 +1823,7 @@
public void testLaunchActivityWithSameDisplayCategory() {
final ActivityInfo info = new ActivityInfo();
info.applicationInfo = new ApplicationInfo();
- info.taskAffinity = ActivityRecord.computeTaskAffinity("test", DEFAULT_FAKE_UID,
- 0 /* launchMode */, null /* componentName */);
+ info.taskAffinity = ActivityRecord.computeTaskAffinity("test", DEFAULT_FAKE_UID);
info.requiredDisplayCategory = "automotive";
final Task task = new TaskBuilder(mSupervisor).setCreateActivity(true).setActivityInfo(info)
.build();
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsProviderTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsProviderTests.java
index 9d839fc..7d9fdd5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsProviderTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsProviderTests.java
@@ -19,6 +19,7 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.view.Display.TYPE_VIRTUAL;
import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
@@ -26,6 +27,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
+import static org.testng.Assert.assertFalse;
import android.annotation.Nullable;
import android.platform.test.annotations.Presubmit;
@@ -233,6 +235,22 @@
}
@Test
+ public void testDoNotWriteVirtualDisplaySettingsToStorage() throws Exception {
+ final DisplayInfo secondaryDisplayInfo = mSecondaryDisplay.getDisplayInfo();
+ secondaryDisplayInfo.type = TYPE_VIRTUAL;
+
+ // No write to storage on virtual display change.
+ final DisplayWindowSettingsProvider provider = new DisplayWindowSettingsProvider(
+ mDefaultVendorSettingsStorage, mOverrideSettingsStorage);
+ final SettingsEntry virtualSettings = provider.getOverrideSettings(secondaryDisplayInfo);
+ virtualSettings.mShouldShowSystemDecors = true;
+ virtualSettings.mImePolicy = DISPLAY_IME_POLICY_LOCAL;
+ virtualSettings.mDontMoveToTop = true;
+ provider.updateOverrideSettings(secondaryDisplayInfo, virtualSettings);
+ assertFalse(mOverrideSettingsStorage.wasWriteSuccessful());
+ }
+
+ @Test
public void testWritingDisplaySettingsToStorage_UsePortAsId() throws Exception {
prepareOverrideDisplaySettings(null /* displayIdentifier */, true /* usePortAsId */);
@@ -260,6 +278,54 @@
getStoredDisplayAttributeValue(mOverrideSettingsStorage, "imePolicy"));
}
+ @Test
+ public void testCleanUpEmptyDisplaySettingsOnDisplayRemoved() {
+ final DisplayWindowSettingsProvider provider = new DisplayWindowSettingsProvider(
+ mDefaultVendorSettingsStorage, mOverrideSettingsStorage);
+ final int initialSize = provider.getOverrideSettingsSize();
+
+ // Size + 1 when query for a new display.
+ final DisplayInfo secondaryDisplayInfo = mSecondaryDisplay.getDisplayInfo();
+ final SettingsEntry overrideSettings = provider.getOverrideSettings(secondaryDisplayInfo);
+
+ assertEquals(initialSize + 1, provider.getOverrideSettingsSize());
+
+ // When a display is removed, its override Settings is not removed if there is any override.
+ overrideSettings.mShouldShowSystemDecors = true;
+ provider.updateOverrideSettings(secondaryDisplayInfo, overrideSettings);
+ provider.onDisplayRemoved(secondaryDisplayInfo);
+
+ assertEquals(initialSize + 1, provider.getOverrideSettingsSize());
+
+ // When a display is removed, its override Settings is removed if there is no override.
+ provider.updateOverrideSettings(secondaryDisplayInfo, new SettingsEntry());
+ provider.onDisplayRemoved(secondaryDisplayInfo);
+
+ assertEquals(initialSize, provider.getOverrideSettingsSize());
+ }
+
+ @Test
+ public void testCleanUpVirtualDisplaySettingsOnDisplayRemoved() {
+ final DisplayWindowSettingsProvider provider = new DisplayWindowSettingsProvider(
+ mDefaultVendorSettingsStorage, mOverrideSettingsStorage);
+ final int initialSize = provider.getOverrideSettingsSize();
+
+ // Size + 1 when query for a new display.
+ final DisplayInfo secondaryDisplayInfo = mSecondaryDisplay.getDisplayInfo();
+ secondaryDisplayInfo.type = TYPE_VIRTUAL;
+ final SettingsEntry overrideSettings = provider.getOverrideSettings(secondaryDisplayInfo);
+
+ assertEquals(initialSize + 1, provider.getOverrideSettingsSize());
+
+ // When a virtual display is removed, its override Settings is removed even if it has
+ // override.
+ overrideSettings.mShouldShowSystemDecors = true;
+ provider.updateOverrideSettings(secondaryDisplayInfo, overrideSettings);
+ provider.onDisplayRemoved(secondaryDisplayInfo);
+
+ assertEquals(initialSize, provider.getOverrideSettingsSize());
+ }
+
/**
* Prepares display settings and stores in {@link #mOverrideSettingsStorage}. Uses provided
* display identifier and stores windowingMode=WINDOWING_MODE_PINNED.
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java
index 1cec0ef..e54b8e5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java
@@ -484,6 +484,18 @@
assertTrue(dcIgnoreOrientation.getIgnoreOrientationRequest());
}
+ @Test
+ public void testDisplayRemoval() {
+ spyOn(mWm.mDisplayWindowSettings);
+ spyOn(mWm.mDisplayWindowSettingsProvider);
+
+ mPrivateDisplay.removeImmediately();
+
+ verify(mWm.mDisplayWindowSettings).onDisplayRemoved(mPrivateDisplay);
+ verify(mWm.mDisplayWindowSettingsProvider).onDisplayRemoved(
+ mPrivateDisplay.getDisplayInfo());
+ }
+
public final class TestSettingsProvider implements DisplayWindowSettings.SettingsProvider {
Map<DisplayInfo, SettingsEntry> mOverrideSettingsCache = new HashMap<>();
@@ -513,5 +525,10 @@
overrideSettings.setTo(settings);
}
+
+ @Override
+ public void onDisplayRemoved(@NonNull DisplayInfo info) {
+ mOverrideSettingsCache.remove(info);
+ }
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationPersisterTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationPersisterTest.java
index 51a7e74..06033c7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationPersisterTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationPersisterTest.java
@@ -20,7 +20,6 @@
import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT;
import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP;
-import static com.android.server.wm.LetterboxConfigurationPersister.LETTERBOX_CONFIGURATION_FILENAME;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -42,13 +41,26 @@
import java.io.File;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
+import java.util.function.Supplier;
+/**
+ * Tests for the {@link LetterboxConfigurationPersister} class.
+ *
+ * Build/Install/Run:
+ * atest WmTests:LetterboxConfigurationPersisterTest
+ */
@SmallTest
@Presubmit
public class LetterboxConfigurationPersisterTest {
private static final long TIMEOUT = 2000L; // 2 secs
+ private static final int DEFAULT_REACHABILITY_TEST = -1;
+ private static final Supplier<Integer> DEFAULT_REACHABILITY_SUPPLIER_TEST =
+ () -> DEFAULT_REACHABILITY_TEST;
+
+ private static final String LETTERBOX_CONFIGURATION_TEST_FILENAME = "letterbox_config_test";
+
private LetterboxConfigurationPersister mLetterboxConfigurationPersister;
private Context mContext;
private PersisterQueue mPersisterQueue;
@@ -62,7 +74,7 @@
mConfigFolder = mContext.getFilesDir();
mPersisterQueue = new PersisterQueue();
mQueueState = new QueueState();
- mLetterboxConfigurationPersister = new LetterboxConfigurationPersister(mContext,
+ mLetterboxConfigurationPersister = new LetterboxConfigurationPersister(
() -> mContext.getResources().getInteger(
R.integer.config_letterboxDefaultPositionForHorizontalReachability),
() -> mContext.getResources().getInteger(
@@ -72,7 +84,8 @@
() -> mContext.getResources().getInteger(
R.integer.config_letterboxDefaultPositionForTabletopModeReachability
),
- mConfigFolder, mPersisterQueue, mQueueState);
+ mConfigFolder, mPersisterQueue, mQueueState,
+ LETTERBOX_CONFIGURATION_TEST_FILENAME);
mQueueListener = queueEmpty -> mQueueState.onItemAdded();
mPersisterQueue.addListener(mQueueListener);
mLetterboxConfigurationPersister.start();
@@ -127,8 +140,10 @@
public void test_whenUpdatedWithNewValues_valuesAreReadAfterRestart() {
final PersisterQueue firstPersisterQueue = new PersisterQueue();
final LetterboxConfigurationPersister firstPersister = new LetterboxConfigurationPersister(
- mContext, () -> -1, () -> -1, () -> -1, () -> -1, mContext.getFilesDir(),
- firstPersisterQueue, mQueueState);
+ DEFAULT_REACHABILITY_SUPPLIER_TEST, DEFAULT_REACHABILITY_SUPPLIER_TEST,
+ DEFAULT_REACHABILITY_SUPPLIER_TEST, DEFAULT_REACHABILITY_SUPPLIER_TEST,
+ mContext.getFilesDir(), firstPersisterQueue, mQueueState,
+ LETTERBOX_CONFIGURATION_TEST_FILENAME);
firstPersister.start();
firstPersister.setLetterboxPositionForHorizontalReachability(false,
LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT);
@@ -138,8 +153,10 @@
stopPersisterSafe(firstPersisterQueue);
final PersisterQueue secondPersisterQueue = new PersisterQueue();
final LetterboxConfigurationPersister secondPersister = new LetterboxConfigurationPersister(
- mContext, () -> -1, () -> -1, () -> -1, () -> -1, mContext.getFilesDir(),
- secondPersisterQueue, mQueueState);
+ DEFAULT_REACHABILITY_SUPPLIER_TEST, DEFAULT_REACHABILITY_SUPPLIER_TEST,
+ DEFAULT_REACHABILITY_SUPPLIER_TEST, DEFAULT_REACHABILITY_SUPPLIER_TEST,
+ mContext.getFilesDir(), secondPersisterQueue, mQueueState,
+ LETTERBOX_CONFIGURATION_TEST_FILENAME);
secondPersister.start();
final int newPositionForHorizontalReachability =
secondPersister.getLetterboxPositionForHorizontalReachability(false);
@@ -156,37 +173,46 @@
@Test
public void test_whenUpdatedWithNewValuesAndDeleted_valuesAreDefaults() {
- mLetterboxConfigurationPersister.setLetterboxPositionForHorizontalReachability(false,
+ final PersisterQueue firstPersisterQueue = new PersisterQueue();
+ final LetterboxConfigurationPersister firstPersister = new LetterboxConfigurationPersister(
+ DEFAULT_REACHABILITY_SUPPLIER_TEST, DEFAULT_REACHABILITY_SUPPLIER_TEST,
+ DEFAULT_REACHABILITY_SUPPLIER_TEST, DEFAULT_REACHABILITY_SUPPLIER_TEST,
+ mContext.getFilesDir(), firstPersisterQueue, mQueueState,
+ LETTERBOX_CONFIGURATION_TEST_FILENAME);
+ firstPersister.start();
+ firstPersister.setLetterboxPositionForHorizontalReachability(false,
LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT);
- mLetterboxConfigurationPersister.setLetterboxPositionForVerticalReachability(false,
+ firstPersister.setLetterboxPositionForVerticalReachability(false,
LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP);
waitForCompletion(mPersisterQueue);
final int newPositionForHorizontalReachability =
- mLetterboxConfigurationPersister.getLetterboxPositionForHorizontalReachability(
- false);
+ firstPersister.getLetterboxPositionForHorizontalReachability(false);
final int newPositionForVerticalReachability =
- mLetterboxConfigurationPersister.getLetterboxPositionForVerticalReachability(false);
+ firstPersister.getLetterboxPositionForVerticalReachability(false);
Assert.assertEquals(LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT,
newPositionForHorizontalReachability);
Assert.assertEquals(LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP,
newPositionForVerticalReachability);
- deleteConfiguration(mLetterboxConfigurationPersister, mPersisterQueue);
- waitForCompletion(mPersisterQueue);
+ deleteConfiguration(firstPersister, firstPersisterQueue);
+ waitForCompletion(firstPersisterQueue);
+ stopPersisterSafe(firstPersisterQueue);
+
+ final PersisterQueue secondPersisterQueue = new PersisterQueue();
+ final LetterboxConfigurationPersister secondPersister = new LetterboxConfigurationPersister(
+ DEFAULT_REACHABILITY_SUPPLIER_TEST, DEFAULT_REACHABILITY_SUPPLIER_TEST,
+ DEFAULT_REACHABILITY_SUPPLIER_TEST, DEFAULT_REACHABILITY_SUPPLIER_TEST,
+ mContext.getFilesDir(), secondPersisterQueue, mQueueState,
+ LETTERBOX_CONFIGURATION_TEST_FILENAME);
+ secondPersister.start();
final int positionForHorizontalReachability =
- mLetterboxConfigurationPersister.getLetterboxPositionForHorizontalReachability(
- false);
- final int defaultPositionForHorizontalReachability =
- mContext.getResources().getInteger(
- R.integer.config_letterboxDefaultPositionForHorizontalReachability);
- Assert.assertEquals(defaultPositionForHorizontalReachability,
- positionForHorizontalReachability);
+ secondPersister.getLetterboxPositionForHorizontalReachability(false);
final int positionForVerticalReachability =
- mLetterboxConfigurationPersister.getLetterboxPositionForVerticalReachability(false);
- final int defaultPositionForVerticalReachability =
- mContext.getResources().getInteger(
- R.integer.config_letterboxDefaultPositionForVerticalReachability);
- Assert.assertEquals(defaultPositionForVerticalReachability,
- positionForVerticalReachability);
+ secondPersister.getLetterboxPositionForVerticalReachability(false);
+ Assert.assertEquals(DEFAULT_REACHABILITY_TEST, positionForHorizontalReachability);
+ Assert.assertEquals(DEFAULT_REACHABILITY_TEST, positionForVerticalReachability);
+ deleteConfiguration(secondPersister, secondPersisterQueue);
+ waitForCompletion(secondPersisterQueue);
+ stopPersisterSafe(secondPersisterQueue);
}
private void stopPersisterSafe(PersisterQueue persisterQueue) {
@@ -222,7 +248,7 @@
private void deleteConfiguration(LetterboxConfigurationPersister persister,
PersisterQueue persisterQueue) {
final AtomicFile fileToDelete = new AtomicFile(
- new File(mConfigFolder, LETTERBOX_CONFIGURATION_FILENAME));
+ new File(mConfigFolder, LETTERBOX_CONFIGURATION_TEST_FILENAME));
persisterQueue.addItem(
new DeleteFileCommand(fileToDelete, mQueueState.andThen(
s -> persister.useDefaultValue())), true);
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
index b02b774..f23e56d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
@@ -27,8 +27,6 @@
import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
-import static android.content.pm.ActivityInfo.LAUNCH_MULTIPLE;
-import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_INSTANCE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.os.Process.NOBODY_UID;
@@ -451,25 +449,15 @@
final int uid = 10123;
final Task task1 = createTaskBuilder(".Task1").build();
final ComponentName componentName = getUniqueComponentName();
- task1.affinity = ActivityRecord.computeTaskAffinity(taskAffinity, uid, LAUNCH_MULTIPLE,
- componentName);
+ task1.affinity = ActivityRecord.computeTaskAffinity(taskAffinity, uid);
mRecentTasks.add(task1);
// Add another task to recents, and make sure the previous task was removed.
final Task task2 = createTaskBuilder(".Task2").build();
- task2.affinity = ActivityRecord.computeTaskAffinity(taskAffinity, uid, LAUNCH_MULTIPLE,
- componentName);
+ task2.affinity = ActivityRecord.computeTaskAffinity(taskAffinity, uid);
mRecentTasks.add(task2);
assertEquals(1, mRecentTasks.getRecentTasks(MAX_VALUE, 0 /* flags */,
true /* getTasksAllowed */, TEST_USER_0_ID, 0).getList().size());
-
- // Add another single-instance task to recents, and make sure no task is removed.
- final Task task3 = createTaskBuilder(".Task3").build();
- task3.affinity = ActivityRecord.computeTaskAffinity(taskAffinity, uid,
- LAUNCH_SINGLE_INSTANCE, componentName);
- mRecentTasks.add(task3);
- assertEquals(2, mRecentTasks.getRecentTasks(MAX_VALUE, 0 /* flags */,
- true /* getTasksAllowed */, TEST_USER_0_ID, 0).getList().size());
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/SafeActivityOptionsTest.java b/services/tests/wmtests/src/com/android/server/wm/SafeActivityOptionsTest.java
index 24e932f..6c48a69 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SafeActivityOptionsTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SafeActivityOptionsTest.java
@@ -16,17 +16,36 @@
package com.android.server.wm;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.content.pm.PackageManager.PERMISSION_DENIED;
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.mock;
import android.app.ActivityOptions;
+import android.content.pm.ActivityInfo;
+import android.os.Looper;
import android.platform.test.annotations.Presubmit;
+import android.view.RemoteAnimationAdapter;
+import android.window.RemoteTransition;
import android.window.WindowContainerToken;
import androidx.test.filters.MediumTest;
import org.junit.Test;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
/**
* Build/Install/Run:
@@ -73,4 +92,119 @@
assertSame(clone.getOriginalOptions().getLaunchRootTask(), token);
}
+
+ @Test
+ public void test_getOptions() {
+ // Mock everything necessary
+ MockitoSession mockingSession = mockitoSession()
+ .mockStatic(ActivityTaskManagerService.class)
+ .strictness(Strictness.LENIENT)
+ .startMocking();
+ doReturn(PERMISSION_DENIED).when(() -> ActivityTaskManagerService.checkPermission(
+ any(), anyInt(), anyInt()));
+
+ final LockTaskController lockTaskController = mock(LockTaskController.class);
+ doReturn(false).when(lockTaskController).isPackageAllowlisted(anyInt(), any());
+
+ final ActivityTaskManagerService atm = mock(ActivityTaskManagerService.class);
+ doReturn(lockTaskController).when(atm).getLockTaskController();
+
+ final ActivityTaskSupervisor taskSupervisor =
+ new ActivityTaskSupervisor(atm, mock(Looper.class));
+ spyOn(taskSupervisor);
+ doReturn(false).when(taskSupervisor).isCallerAllowedToLaunchOnDisplay(anyInt(),
+ anyInt(), anyInt(), any());
+ doReturn(false).when(taskSupervisor).isCallerAllowedToLaunchOnTaskDisplayArea(anyInt(),
+ anyInt(), any(), any());
+
+ taskSupervisor.mRecentTasks = mock(RecentTasks.class);
+ doReturn(false).when(taskSupervisor.mRecentTasks).isCallerRecents(anyInt());
+
+ // Ensure exceptions are thrown when lack of permissions.
+ ActivityOptions activityOptions = ActivityOptions.makeBasic();
+ try {
+ activityOptions.setLaunchTaskId(100);
+ verifySecureExceptionThrown(activityOptions, taskSupervisor);
+
+ activityOptions = ActivityOptions.makeBasic();
+ activityOptions.setDisableStartingWindow(true);
+ verifySecureExceptionThrown(activityOptions, taskSupervisor);
+
+ activityOptions = ActivityOptions.makeBasic();
+ activityOptions.setTransientLaunch();
+ verifySecureExceptionThrown(activityOptions, taskSupervisor);
+
+ activityOptions = ActivityOptions.makeBasic();
+ activityOptions.setDismissKeyguard();
+ verifySecureExceptionThrown(activityOptions, taskSupervisor);
+
+ activityOptions = ActivityOptions.makeBasic();
+ activityOptions.setLaunchActivityType(ACTIVITY_TYPE_STANDARD);
+ verifySecureExceptionThrown(activityOptions, taskSupervisor);
+
+ activityOptions = ActivityOptions.makeBasic();
+ activityOptions.setLaunchedFromBubble(true);
+ verifySecureExceptionThrown(activityOptions, taskSupervisor);
+
+ activityOptions = ActivityOptions.makeBasic();
+ activityOptions.setLaunchDisplayId(DEFAULT_DISPLAY);
+ verifySecureExceptionThrown(activityOptions, taskSupervisor);
+
+ activityOptions = ActivityOptions.makeBasic();
+ activityOptions.setLockTaskEnabled(true);
+ verifySecureExceptionThrown(activityOptions, taskSupervisor);
+
+ activityOptions = ActivityOptions.makeCustomTaskAnimation(
+ getInstrumentation().getContext(), 0, 0, null, null, null);
+ verifySecureExceptionThrown(activityOptions, taskSupervisor);
+
+ RemoteAnimationAdapter remoteAnimationAdapter = mock(RemoteAnimationAdapter.class);
+ RemoteTransition remoteTransition = mock(RemoteTransition.class);
+ activityOptions = ActivityOptions.makeRemoteAnimation(remoteAnimationAdapter);
+ verifySecureExceptionThrown(activityOptions, taskSupervisor);
+
+ activityOptions = ActivityOptions.makeRemoteAnimation(remoteAnimationAdapter,
+ remoteTransition);
+ verifySecureExceptionThrown(activityOptions, taskSupervisor);
+
+ activityOptions = ActivityOptions.makeBasic();
+ activityOptions.setRemoteAnimationAdapter(remoteAnimationAdapter);
+ verifySecureExceptionThrown(activityOptions, taskSupervisor);
+
+ activityOptions = ActivityOptions.makeRemoteTransition(remoteTransition);
+ verifySecureExceptionThrown(activityOptions, taskSupervisor);
+
+ activityOptions = ActivityOptions.makeBasic();
+ activityOptions.setRemoteTransition(remoteTransition);
+ verifySecureExceptionThrown(activityOptions, taskSupervisor);
+
+ verifySecureExceptionThrown(activityOptions, taskSupervisor,
+ mock(TaskDisplayArea.class));
+ } finally {
+ mockingSession.finishMocking();
+ }
+ }
+
+ private void verifySecureExceptionThrown(ActivityOptions activityOptions,
+ ActivityTaskSupervisor taskSupervisor) {
+ verifySecureExceptionThrown(activityOptions, taskSupervisor, null /* mockTda */);
+ }
+
+ private void verifySecureExceptionThrown(ActivityOptions activityOptions,
+ ActivityTaskSupervisor taskSupervisor, TaskDisplayArea mockTda) {
+ SafeActivityOptions safeActivityOptions = new SafeActivityOptions(activityOptions);
+ if (mockTda != null) {
+ spyOn(safeActivityOptions);
+ doReturn(mockTda).when(safeActivityOptions).getLaunchTaskDisplayArea(any(), any());
+ }
+
+ boolean isExceptionThrow = false;
+ final ActivityInfo aInfo = mock(ActivityInfo.class);
+ try {
+ safeActivityOptions.getOptions(null, aInfo, null, taskSupervisor);
+ } catch (SecurityException ex) {
+ isExceptionThrow = true;
+ }
+ assertTrue(isExceptionThrow);
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/SurfaceViewSyncContinuousTest.java b/services/tests/wmtests/src/com/android/server/wm/SurfaceViewSyncContinuousTest.java
index ad7314c..f958e6f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SurfaceViewSyncContinuousTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SurfaceViewSyncContinuousTest.java
@@ -18,7 +18,6 @@
import static android.server.wm.UiDeviceUtils.pressUnlockButton;
import static android.server.wm.UiDeviceUtils.pressWakeupButton;
-import static android.server.wm.WindowManagerState.getLogicalDisplaySize;
import android.app.KeyguardManager;
import android.os.PowerManager;
@@ -46,7 +45,6 @@
@Before
public void setup() {
mCapturedActivity = mActivityRule.getActivity();
- mCapturedActivity.setLogicalDisplaySize(getLogicalDisplaySize());
final KeyguardManager km = mCapturedActivity.getSystemService(KeyguardManager.class);
if (km != null && km.isKeyguardLocked() || !Objects.requireNonNull(
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
index 99ab715..54b9351 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
@@ -26,6 +26,7 @@
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.window.TaskFragmentOperation.OP_TYPE_CREATE_TASK_FRAGMENT;
import static android.window.TaskFragmentOperation.OP_TYPE_DELETE_TASK_FRAGMENT;
+import static android.window.TaskFragmentOperation.OP_TYPE_REORDER_TO_FRONT;
import static android.window.TaskFragmentOperation.OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT;
import static android.window.TaskFragmentOperation.OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS;
import static android.window.TaskFragmentOperation.OP_TYPE_SET_ANIMATION_PARAMS;
@@ -1590,6 +1591,46 @@
assertEquals(taskFragmentBounds, mTaskFragment.getBounds());
}
+ @Test
+ public void testApplyTransaction_reorderTaskFragmentToFront() {
+ final Task task = createTask(mDisplayContent);
+ // Create a TaskFragment.
+ final IBinder token0 = new Binder();
+ final TaskFragment tf0 = new TaskFragmentBuilder(mAtm)
+ .setParentTask(task)
+ .setFragmentToken(token0)
+ .setOrganizer(mOrganizer)
+ .createActivityCount(1)
+ .build();
+ // Create another TaskFragment
+ final IBinder token1 = new Binder();
+ final TaskFragment tf1 = new TaskFragmentBuilder(mAtm)
+ .setParentTask(task)
+ .setFragmentToken(token1)
+ .setOrganizer(mOrganizer)
+ .createActivityCount(1)
+ .build();
+ // Create a non-embedded Activity on top.
+ final ActivityRecord topActivity = new ActivityBuilder(mAtm)
+ .setTask(task)
+ .build();
+ mWindowOrganizerController.mLaunchTaskFragments.put(token0, tf0);
+ mWindowOrganizerController.mLaunchTaskFragments.put(token1, tf1);
+
+ // Reorder TaskFragment to front
+ final TaskFragmentOperation operation = new TaskFragmentOperation.Builder(
+ OP_TYPE_REORDER_TO_FRONT).build();
+ mTransaction.addTaskFragmentOperation(token0, operation);
+ assertApplyTransactionAllowed(mTransaction);
+
+ // Ensure the non-embedded activity still on top.
+ assertEquals(topActivity, task.getTopChild().asActivityRecord());
+
+ // Ensure the TaskFragment is moved to front.
+ final TaskFragment frontMostTaskFragment = task.getTaskFragment(tf -> tf.asTask() == null);
+ assertEquals(frontMostTaskFragment, tf0);
+ }
+
/**
* Creates a {@link TaskFragment} with the {@link WindowContainerTransaction}. Calls
* {@link WindowOrganizerController#applyTransaction(WindowContainerTransaction)} to apply the
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestDisplayWindowSettingsProvider.java b/services/tests/wmtests/src/com/android/server/wm/TestDisplayWindowSettingsProvider.java
index b2e44b1..e11df98 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestDisplayWindowSettingsProvider.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestDisplayWindowSettingsProvider.java
@@ -52,6 +52,12 @@
overrideSettings.setTo(overrides);
}
+ @Override
+ public void onDisplayRemoved(@NonNull DisplayInfo info) {
+ final String identifier = getIdentifier(info);
+ mOverrideSettingsMap.remove(identifier);
+ }
+
@NonNull
private SettingsEntry getOrCreateOverrideSettingsEntry(DisplayInfo info) {
final String identifier = getIdentifier(info);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index 7351e81..ffecafb 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -61,6 +61,7 @@
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
@@ -1429,6 +1430,15 @@
// No need to wait for the activity in transient hide task.
assertEquals(WindowContainer.SYNC_STATE_NONE, activity1.mSyncState);
+ // An active transient launch overrides idle state to avoid clearing power mode before the
+ // transition is finished.
+ spyOn(mRootWindowContainer.mTransitionController);
+ doAnswer(invocation -> controller.isTransientLaunch(invocation.getArgument(0))).when(
+ mRootWindowContainer.mTransitionController).isTransientLaunch(any());
+ activity2.getTask().setResumedActivity(activity2, "test");
+ activity2.idle = true;
+ assertFalse(mRootWindowContainer.allResumedActivitiesIdle());
+
activity1.setVisibleRequested(false);
activity2.setVisibleRequested(true);
activity2.setVisible(true);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
index 5c1b262..45331f7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -1036,7 +1036,7 @@
private boolean setupLetterboxConfigurationWithBackgroundType(
@LetterboxConfiguration.LetterboxBackgroundType int letterboxBackgroundType) {
- mWm.mLetterboxConfiguration.setLetterboxBackgroundType(letterboxBackgroundType);
+ mWm.mLetterboxConfiguration.setLetterboxBackgroundTypeOverride(letterboxBackgroundType);
return mWm.isLetterboxBackgroundMultiColored();
}
}
diff --git a/services/wallpapereffectsgeneration/OWNERS b/services/wallpapereffectsgeneration/OWNERS
index d2d3e2c0..a351032 100644
--- a/services/wallpapereffectsgeneration/OWNERS
+++ b/services/wallpapereffectsgeneration/OWNERS
@@ -1,4 +1,3 @@
susharon@google.com
shanh@google.com
-huiwu@google.com
srazdan@google.com
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index f86d2b5..ed1c41f 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -9407,6 +9407,37 @@
"missed_incoming_call_sms_pattern_string_array";
/**
+ * Indicate the satellite services supported per provider by a carrier.
+ *
+ * Key is the PLMN of a satellite provider. Value should be an integer array of supported
+ * services with the following value:
+ * <ul>
+ * <li>1 = {@link android.telephony.NetworkRegistrationInfo#SERVICE_TYPE_VOICE}</li>
+ * <li>2 = {@link android.telephony.NetworkRegistrationInfo#SERVICE_TYPE_DATA}</li>
+ * <li>3 = {@link android.telephony.NetworkRegistrationInfo#SERVICE_TYPE_SMS}</li>
+ * <li>4 = {@link android.telephony.NetworkRegistrationInfo#SERVICE_TYPE_VIDEO}</li>
+ * <li>5 = {@link android.telephony.NetworkRegistrationInfo#SERVICE_TYPE_EMERGENCY}</li>
+ * </ul>
+ * <p>
+ * If this carrier config is not present, the overlay config
+ * {@code config_satellite_services_supported_by_providers} will be used. If the carrier config
+ * is present, the supported satellite services will be identified as follows:
+ * <ul>
+ * <li>For the PLMN that exists in both provider supported satellite services and carrier
+ * supported satellite services, the supported services will be the intersection of the two
+ * sets.</li>
+ * <li>For the PLMN that is present in provider supported satellite services but not in carrier
+ * supported satellite services, the provider supported satellite services will be used.</li>
+ * <li>For the PLMN that is present in carrier supported satellite services but not in provider
+ * supported satellite services, the PLMN will be ignored.</li>
+ * </ul>
+ *
+ * This config is empty by default.
+ */
+ public static final String KEY_CARRIER_SUPPORTED_SATELLITE_SERVICES_PER_PROVIDER_BUNDLE =
+ "carrier_supported_satellite_services_per_provider_bundle";
+
+ /**
* Indicating whether DUN APN should be disabled when the device is roaming. In that case,
* the default APN (i.e. internet) will be used for tethering.
*
@@ -9628,6 +9659,7 @@
*
* @see TelephonyManager#PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_CANCELED
* @see TelephonyManager#PURCHASE_PREMIUM_CAPABILITY_RESULT_TIMEOUT
+ * @see TelephonyManager#PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_DISABLED
*/
public static final String
KEY_PREMIUM_CAPABILITY_NOTIFICATION_BACKOFF_HYSTERESIS_TIME_MILLIS_LONG =
@@ -10412,6 +10444,9 @@
});
sDefaults.putBoolean(KEY_DELAY_IMS_TEAR_DOWN_UNTIL_CALL_END_BOOL, false);
sDefaults.putStringArray(KEY_MISSED_INCOMING_CALL_SMS_PATTERN_STRING_ARRAY, new String[0]);
+ sDefaults.putPersistableBundle(
+ KEY_CARRIER_SUPPORTED_SATELLITE_SERVICES_PER_PROVIDER_BUNDLE,
+ PersistableBundle.EMPTY);
sDefaults.putBoolean(KEY_DISABLE_DUN_APN_WHILE_ROAMING_WITH_PRESET_APN_BOOL, false);
sDefaults.putString(KEY_DEFAULT_PREFERRED_APN_NAME_STRING, "");
sDefaults.putBoolean(KEY_SUPPORTS_CALL_COMPOSER_BOOL, false);
diff --git a/telephony/java/android/telephony/NetworkRegistrationInfo.java b/telephony/java/android/telephony/NetworkRegistrationInfo.java
index a2fbc95..6258b9c 100644
--- a/telephony/java/android/telephony/NetworkRegistrationInfo.java
+++ b/telephony/java/android/telephony/NetworkRegistrationInfo.java
@@ -203,6 +203,12 @@
*/
public static final int SERVICE_TYPE_EMERGENCY = 5;
+ /** @hide */
+ public static final int FIRST_SERVICE_TYPE = SERVICE_TYPE_VOICE;
+
+ /** @hide */
+ public static final int LAST_SERVICE_TYPE = SERVICE_TYPE_EMERGENCY;
+
@Domain
private final int mDomain;
@@ -240,7 +246,7 @@
private final boolean mEmergencyOnly;
@ServiceType
- private final ArrayList<Integer> mAvailableServices;
+ private ArrayList<Integer> mAvailableServices;
@Nullable
private CellIdentity mCellIdentity;
@@ -604,6 +610,16 @@
}
/**
+ * Set available service types.
+ *
+ * @param availableServices The list of available services for this network.
+ * @hide
+ */
+ public void setAvailableServices(@NonNull @ServiceType List<Integer> availableServices) {
+ mAvailableServices = new ArrayList<>(availableServices);
+ }
+
+ /**
* @return The access network technology {@link NetworkType}.
*/
public @NetworkType int getAccessNetworkTechnology() {
@@ -772,6 +788,18 @@
}
}
+ /**
+ * Convert isNonTerrestrialNetwork to string
+ *
+ * @param isNonTerrestrialNetwork boolean indicating whether network is a non-terrestrial
+ * network
+ * @return string format of isNonTerrestrialNetwork.
+ * @hide
+ */
+ public static String isNonTerrestrialNetworkToString(boolean isNonTerrestrialNetwork) {
+ return isNonTerrestrialNetwork ? "NON-TERRESTRIAL" : "TERRESTRIAL";
+ }
+
@NonNull
@Override
public String toString() {
@@ -797,7 +825,8 @@
? nrStateToString(mNrState) : "****")
.append(" rRplmn=").append(mRplmn)
.append(" isUsingCarrierAggregation=").append(mIsUsingCarrierAggregation)
- .append(" isNonTerrestrialNetwork=").append(mIsNonTerrestrialNetwork)
+ .append(" isNonTerrestrialNetwork=").append(
+ isNonTerrestrialNetworkToString(mIsNonTerrestrialNetwork))
.append("}").toString();
}
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 64e4356..c0f4008 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -35,6 +35,9 @@
import android.annotation.SystemService;
import android.app.PendingIntent;
import android.app.PropertyInvalidatedCache;
+import android.compat.Compatibility;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledAfter;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.Intent;
@@ -85,6 +88,7 @@
import java.util.List;
import java.util.Locale;
import java.util.Map;
+import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
@@ -1322,41 +1326,61 @@
* for #onSubscriptionsChanged to be invoked.
*/
public static class OnSubscriptionsChangedListener {
- private class OnSubscriptionsChangedListenerHandler extends Handler {
- OnSubscriptionsChangedListenerHandler() {
- super();
- }
-
- OnSubscriptionsChangedListenerHandler(Looper looper) {
- super(looper);
- }
- }
/**
- * Posted executor callback on the handler associated with a given looper.
- * The looper can be the calling thread's looper or the looper passed from the
- * constructor {@link #OnSubscriptionsChangedListener(Looper)}.
+ * After {@link Build.VERSION_CODES.Q}, it is no longer necessary to instantiate a
+ * Handler inside of the OnSubscriptionsChangedListener in all cases, so it will only
+ * be done for callers that do not supply an Executor.
*/
- private final HandlerExecutor mExecutor;
+ @ChangeId
+ @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
+ private static final long LAZY_INITIALIZE_SUBSCRIPTIONS_CHANGED_HANDLER = 278814050L;
+
+ /**
+ * For backwards compatibility reasons, stashes the Looper associated with the thread
+ * context in which this listener was created.
+ */
+ private final Looper mCreatorLooper;
/**
* @hide
*/
- public HandlerExecutor getHandlerExecutor() {
- return mExecutor;
+ public Looper getCreatorLooper() {
+ return mCreatorLooper;
}
+ /**
+ * Create an OnSubscriptionsChangedListener.
+ *
+ * For callers targeting {@link Build.VERSION_CODES.P} or earlier, this can only be called
+ * on a thread that already has a prepared Looper. Callers targeting Q or later should
+ * subsequently use {@link SubscriptionManager#addOnSubscriptionsChangedListener(
+ * Executor, OnSubscriptionsChangedListener)}.
+ *
+ * On OS versions prior to {@link Build.VERSION_CODES.V} callers should assume that this
+ * call will fail if invoked on a thread that does not already have a prepared looper.
+ */
public OnSubscriptionsChangedListener() {
- mExecutor = new HandlerExecutor(new OnSubscriptionsChangedListenerHandler());
+ mCreatorLooper = Looper.myLooper();
+ if (mCreatorLooper == null
+ && !Compatibility.isChangeEnabled(
+ LAZY_INITIALIZE_SUBSCRIPTIONS_CHANGED_HANDLER)) {
+ // matches the implementation of Handler
+ throw new RuntimeException(
+ "Can't create handler inside thread "
+ + Thread.currentThread()
+ + " that has not called Looper.prepare()");
+ }
}
/**
* Allow a listener to be created with a custom looper
- * @param looper the looper that the underlining handler should run on
+ * @param looper the non-null Looper that the underlining handler should run on
* @hide
*/
- public OnSubscriptionsChangedListener(Looper looper) {
- mExecutor = new HandlerExecutor(new OnSubscriptionsChangedListenerHandler(looper));
+ public OnSubscriptionsChangedListener(@NonNull Looper looper) {
+ Objects.requireNonNull(looper);
+ mCreatorLooper = looper;
}
/**
@@ -1423,7 +1447,9 @@
@Deprecated
public void addOnSubscriptionsChangedListener(OnSubscriptionsChangedListener listener) {
if (listener == null) return;
- addOnSubscriptionsChangedListener(listener.mExecutor, listener);
+
+ addOnSubscriptionsChangedListener(
+ new HandlerExecutor(new Handler(listener.getCreatorLooper())), listener);
}
/**
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index a3099db..f1ee76e 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -5268,7 +5268,11 @@
try {
ITelephony telephony = getITelephony();
if (telephony != null) {
- return telephony.getMergedImsisFromGroup(getSubId(), getOpPackageName());
+ String[] mergedImsisFromGroup =
+ telephony.getMergedImsisFromGroup(getSubId(), getOpPackageName());
+ if (mergedImsisFromGroup != null) {
+ return mergedImsisFromGroup;
+ }
}
} catch (RemoteException ex) {
}
@@ -17602,6 +17606,15 @@
public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_PENDING_NETWORK_SETUP = 15;
/**
+ * Purchase premium capability failed because the user disabled the feature.
+ * Subsequent attempts will be throttled for the amount of time specified by
+ * {@link CarrierConfigManager
+ * #KEY_PREMIUM_CAPABILITY_NOTIFICATION_BACKOFF_HYSTERESIS_TIME_MILLIS_LONG}
+ * and return {@link #PURCHASE_PREMIUM_CAPABILITY_RESULT_THROTTLED}.
+ */
+ public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_DISABLED = 16;
+
+ /**
* Results of the purchase premium capability request.
* @hide
*/
@@ -17620,7 +17633,8 @@
PURCHASE_PREMIUM_CAPABILITY_RESULT_NETWORK_NOT_AVAILABLE,
PURCHASE_PREMIUM_CAPABILITY_RESULT_ENTITLEMENT_CHECK_FAILED,
PURCHASE_PREMIUM_CAPABILITY_RESULT_NOT_DEFAULT_DATA_SUBSCRIPTION,
- PURCHASE_PREMIUM_CAPABILITY_RESULT_PENDING_NETWORK_SETUP})
+ PURCHASE_PREMIUM_CAPABILITY_RESULT_PENDING_NETWORK_SETUP,
+ PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_DISABLED})
public @interface PurchasePremiumCapabilityResult {}
/**
diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java
index decf2d4f..72e4389 100644
--- a/telephony/java/com/android/internal/telephony/RILConstants.java
+++ b/telephony/java/com/android/internal/telephony/RILConstants.java
@@ -583,6 +583,7 @@
int RIL_REQUEST_GET_MAX_CHARACTERS_PER_SATELLITE_TEXT_MESSAGE = 257;
int RIL_REQUEST_GET_TIME_FOR_NEXT_SATELLITE_VISIBILITY = 258;
int RIL_REQUEST_IS_NULL_CIPHER_AND_INTEGRITY_ENABLED = 259;
+ int RIL_REQUEST_SET_SATELLITE_PLMN = 260;
/* Responses begin */
int RIL_RESPONSE_ACKNOWLEDGEMENT = 800;
diff --git a/telephony/java/com/android/telephony/Rlog.java b/telephony/java/com/android/telephony/Rlog.java
index 9d6c930..e3e6c60 100644
--- a/telephony/java/com/android/telephony/Rlog.java
+++ b/telephony/java/com/android/telephony/Rlog.java
@@ -15,6 +15,10 @@
*/
package com.android.telephony;
+import android.net.Uri;
+import android.os.Build;
+import android.telecom.PhoneAccount;
+import android.telephony.PhoneNumberUtils;
import android.text.TextUtils;
import android.util.Base64;
import android.util.Log;
@@ -128,6 +132,78 @@
}
/**
+ * Generates an obfuscated string for a calling handle in {@link Uri} format, or a raw phone
+ * phone number in {@link String} format.
+ * @param pii The information to obfuscate.
+ * @return The obfuscated string.
+ */
+ public static String piiHandle(Object pii) {
+ StringBuilder sb = new StringBuilder();
+ if (pii instanceof Uri) {
+ Uri uri = (Uri) pii;
+ String scheme = uri.getScheme();
+
+ if (!TextUtils.isEmpty(scheme)) {
+ sb.append(scheme).append(":");
+ }
+
+ String textToObfuscate = uri.getSchemeSpecificPart();
+ if (PhoneAccount.SCHEME_TEL.equals(scheme)) {
+ obfuscatePhoneNumber(sb, textToObfuscate);
+ } else if (PhoneAccount.SCHEME_SIP.equals(scheme)) {
+ for (int i = 0; i < textToObfuscate.length(); i++) {
+ char c = textToObfuscate.charAt(i);
+ if (c != '@' && c != '.') {
+ c = '*';
+ }
+ sb.append(c);
+ }
+ } else {
+ sb.append("***");
+ }
+ } else if (pii instanceof String) {
+ String number = (String) pii;
+ obfuscatePhoneNumber(sb, number);
+ }
+
+ return sb.toString();
+ }
+
+ /**
+ * Obfuscates a phone number, allowing NUM_DIALABLE_DIGITS_TO_LOG digits to be exposed for the
+ * phone number.
+ * @param sb String buffer to write obfuscated number to.
+ * @param phoneNumber The number to obfuscate.
+ */
+ private static void obfuscatePhoneNumber(StringBuilder sb, String phoneNumber) {
+ int numDigitsToLog = USER_BUILD ? 0 : 2;
+ int numDigitsToObfuscate = getDialableCount(phoneNumber) - numDigitsToLog;
+ for (int i = 0; i < phoneNumber.length(); i++) {
+ char c = phoneNumber.charAt(i);
+ boolean isDialable = PhoneNumberUtils.isDialable(c);
+ if (isDialable) {
+ numDigitsToObfuscate--;
+ }
+ sb.append(isDialable && numDigitsToObfuscate >= 0 ? "*" : c);
+ }
+ }
+
+ /**
+ * Determines the number of dialable characters in a string.
+ * @param toCount The string to count dialable characters in.
+ * @return The count of dialable characters.
+ */
+ private static int getDialableCount(String toCount) {
+ int numDialable = 0;
+ for (char c : toCount.toCharArray()) {
+ if (PhoneNumberUtils.isDialable(c)) {
+ numDialable++;
+ }
+ }
+ return numDialable;
+ }
+
+ /**
* Returns a secure hash (using the SHA1 algorithm) of the provided input.
*
* @return "****" if the build type is user, otherwise the hash
diff --git a/tests/ChoreographerTests/src/main/java/android/view/choreographertests/AttachedChoreographerTest.java b/tests/ChoreographerTests/src/main/java/android/view/choreographertests/AttachedChoreographerTest.java
index 48d050c..bb0d30a 100644
--- a/tests/ChoreographerTests/src/main/java/android/view/choreographertests/AttachedChoreographerTest.java
+++ b/tests/ChoreographerTests/src/main/java/android/view/choreographertests/AttachedChoreographerTest.java
@@ -22,9 +22,9 @@
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
import android.Manifest;
-import android.app.compat.CompatChanges;
import android.hardware.display.DisplayManager;
import android.os.Looper;
import android.support.test.uiautomator.UiDevice;
@@ -34,6 +34,7 @@
import android.view.SurfaceControl;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
+import android.view.WindowManager;
import androidx.lifecycle.Lifecycle;
import androidx.test.core.app.ActivityScenario;
@@ -52,7 +53,7 @@
public class AttachedChoreographerTest {
private static final String TAG = "AttachedChoreographerTest";
private static final long DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE_CHANGEID = 170503758;
- private static final long THRESHOLD_MS = 10;
+ private static final long THRESHOLD_MS = 4;
private static final int FRAME_ITERATIONS = 21;
private static final int CALLBACK_MISSED_THRESHOLD = 2;
@@ -66,7 +67,7 @@
private SurfaceView mSurfaceView;
private SurfaceHolder mSurfaceHolder;
private Choreographer mChoreographer;
- private boolean mIsFirstCallback = true;
+ private long mExpectedPresentTime;
private int mCallbackMissedCounter = 0;
@Before
@@ -74,6 +75,13 @@
mScenario = ActivityScenario.launch(GraphicsActivity.class);
mScenario.moveToState(Lifecycle.State.CREATED);
mScenario.onActivity(activity -> {
+ // Lock the display frame rate. This will not allow SurfaceFlinger to use the frame rate
+ // override feature that throttles down the global choreographer for this test.
+ float refreshRate = activity.getDisplay().getMode().getRefreshRate();
+ WindowManager.LayoutParams attrs = activity.getWindow().getAttributes();
+ attrs.preferredRefreshRate = refreshRate;
+ activity.getWindow().setAttributes(attrs);
+
mSurfaceView = activity.findViewById(R.id.surface);
mSurfaceHolder = mSurfaceView.getHolder();
mSurfaceHolder.addCallback(new SurfaceHolder.Callback() {
@@ -95,6 +103,12 @@
});
UiDevice uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+
+ // TODO(b/290634611): clean this up once SF new front end is enabled by default
+ boolean sfNewFeEnabled = uiDevice.executeShellCommand("dumpsys SurfaceFlinger")
+ .indexOf("SurfaceFlinger New Frontend Enabled:true") != -1;
+ assumeTrue(sfNewFeEnabled);
+
uiDevice.wakeUp();
uiDevice.executeShellCommand("wm dismiss-keyguard");
mScenario.moveToState(Lifecycle.State.RESUMED);
@@ -112,18 +126,16 @@
mDisplayManager.setRefreshRateSwitchingType(
DisplayManager.SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY);
mDisplayManager.setShouldAlwaysRespectAppRequestedMode(true);
- boolean changeIsEnabled =
- CompatChanges.isChangeEnabled(
- DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE_CHANGEID);
- Log.i(TAG, "DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE_CHANGE_ID is "
- + (changeIsEnabled ? "enabled" : "disabled"));
});
}
@After
public void tearDown() {
- mDisplayManager.setRefreshRateSwitchingType(mInitialMatchContentFrameRate);
- mDisplayManager.setShouldAlwaysRespectAppRequestedMode(false);
+ if (mDisplayManager != null) {
+ mDisplayManager.setRefreshRateSwitchingType(mInitialMatchContentFrameRate);
+ mDisplayManager.setShouldAlwaysRespectAppRequestedMode(false);
+ }
+
InstrumentationRegistry.getInstrumentation().getUiAutomation()
.dropShellPermissionIdentity();
}
@@ -407,18 +419,141 @@
}
}
+ @Test
+ public void testChoreographerAttachedAfterSetFrameRate() {
+ Log.i(TAG, "adyabr: starting testChoreographerAttachedAfterSetFrameRate");
+
+ class TransactionGenerator implements SurfaceControl.TransactionCommittedListener {
+ private SurfaceControl mSc;
+ private int mFrame;
+ private static final int NUM_FRAMES = 50;
+ private final CountDownLatch mCompleteLatch = new CountDownLatch(1);
+
+ TransactionGenerator(SurfaceControl sc) {
+ mSc = sc;
+
+ }
+
+ @Override
+ public void onTransactionCommitted() {
+ mFrame++;
+ if (mFrame >= NUM_FRAMES) {
+ mCompleteLatch.countDown();
+ return;
+ }
+
+ SurfaceControl.Transaction transaction = new SurfaceControl.Transaction();
+ transaction.setAlpha(mSc, 1.0f / mFrame)
+ .addTransactionCommittedListener(Runnable::run, this)
+ .apply();
+
+ }
+
+ public void startAndWaitForCompletion() {
+ onTransactionCommitted();
+ if (waitForCountDown(mCompleteLatch, /* timeoutInSeconds */ 10L)) {
+ fail("failed to send new transactions");
+ }
+ }
+ }
+
+
+ for (int divisor : new int[]{2, 3}) {
+ CountDownLatch choreographerLatch = new CountDownLatch(1);
+ mScenario.onActivity(activity -> {
+ if (waitForCountDown(mSurfaceCreationCountDown, /* timeoutInSeconds */ 1L)) {
+ fail("Unable to create surface within 1 Second");
+ }
+
+ SurfaceControl sc = mSurfaceView.getSurfaceControl();
+ SurfaceControl.Transaction transaction = new SurfaceControl.Transaction();
+ float displayRefreshRate = activity.getDisplay().getMode().getRefreshRate();
+ float fps = displayRefreshRate / divisor;
+ long callbackDurationMs = Math.round(1000 / fps);
+ mCallbackMissedCounter = 0;
+ transaction.setFrameRate(sc, fps, Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE)
+ .apply();
+
+
+ new TransactionGenerator(sc).startAndWaitForCompletion();
+
+ Choreographer choreographer = sc.getChoreographer();
+ verifyVsyncCallbacks(choreographer, callbackDurationMs,
+ choreographerLatch, FRAME_ITERATIONS);
+ });
+ // wait for the previous callbacks to finish before moving to the next divisor
+ if (waitForCountDown(choreographerLatch, /* timeoutInSeconds */ 5L)) {
+ fail("Test not finished in 5 Seconds");
+ }
+ }
+ }
+
+ @Test
+ public void testChoreographerNonDivisorFixedSourceRefreshRate() {
+ CountDownLatch continueLatch = new CountDownLatch(1);
+ mScenario.onActivity(activity -> {
+ if (waitForCountDown(mSurfaceCreationCountDown, /* timeoutInSeconds */ 1L)) {
+ fail("Unable to create surface within 1 Second");
+ }
+ SurfaceControl sc = mSurfaceView.getSurfaceControl();
+ Choreographer choreographer = sc.getChoreographer();
+ SurfaceControl.Transaction transaction = new SurfaceControl.Transaction();
+ float displayRefreshRate = activity.getDisplay().getMode().getRefreshRate();
+ float fps = 61.7f; // hopefully not a divisor
+ long callbackDurationMs = Math.round(1000 / displayRefreshRate);
+ mCallbackMissedCounter = 0;
+ transaction.setFrameRate(sc, fps, Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE)
+ .addTransactionCommittedListener(Runnable::run,
+ () -> verifyVsyncCallbacks(choreographer,
+ callbackDurationMs, continueLatch, FRAME_ITERATIONS))
+ .apply();
+ });
+ // wait for the previous callbacks to finish before moving to the next divisor
+ if (waitForCountDown(continueLatch, /* timeoutInSeconds */ 5L)) {
+ fail("Test not finished in 5 Seconds");
+ }
+ }
+
+ @Test
+ public void testChoreographerNonDivisorRefreshRate() {
+ CountDownLatch continueLatch = new CountDownLatch(1);
+ mScenario.onActivity(activity -> {
+ if (waitForCountDown(mSurfaceCreationCountDown, /* timeoutInSeconds */ 1L)) {
+ fail("Unable to create surface within 1 Second");
+ }
+ SurfaceControl sc = mSurfaceView.getSurfaceControl();
+ Choreographer choreographer = sc.getChoreographer();
+ SurfaceControl.Transaction transaction = new SurfaceControl.Transaction();
+ float displayRefreshRate = activity.getDisplay().getMode().getRefreshRate();
+ float fps = 61.7f; // hopefully not a divisor
+ float expectedFps = displayRefreshRate / Math.round(displayRefreshRate / fps);
+ long callbackDurationMs = Math.round(1000 / expectedFps);
+ mCallbackMissedCounter = 0;
+ transaction.setFrameRate(sc, fps, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT)
+ .addTransactionCommittedListener(Runnable::run,
+ () -> verifyVsyncCallbacks(choreographer,
+ callbackDurationMs, continueLatch, FRAME_ITERATIONS))
+ .apply();
+ });
+ // wait for the previous callbacks to finish before moving to the next divisor
+ if (waitForCountDown(continueLatch, /* timeoutInSeconds */ 5L)) {
+ fail("Test not finished in 5 Seconds");
+ }
+ }
+
private void verifyVsyncCallbacks(Choreographer choreographer, long callbackDurationMs,
CountDownLatch continueLatch, int frameCount) {
- long callbackRequestedTimeNs = System.nanoTime();
choreographer.postVsyncCallback(frameData -> {
+ long expectedPresentTime =
+ frameData.getPreferredFrameTimeline().getExpectedPresentationTimeNanos();
if (frameCount > 0) {
- if (!mIsFirstCallback) {
- // Skip the first callback as it takes 1 frame
- // to reflect the new refresh rate
- long callbackDurationDiffMs = getCallbackDurationDiffInMs(
- frameData.getFrameTimeNanos(),
- callbackRequestedTimeNs, callbackDurationMs);
- if (callbackDurationDiffMs < 0 || callbackDurationDiffMs > THRESHOLD_MS) {
+ if (mExpectedPresentTime > 0) {
+ long callbackDurationDiffMs =
+ TimeUnit.NANOSECONDS.toMillis(
+ expectedPresentTime - mExpectedPresentTime);
+ if (callbackDurationDiffMs < 0
+ || Math.abs(callbackDurationMs - callbackDurationDiffMs)
+ > THRESHOLD_MS) {
mCallbackMissedCounter++;
Log.e(TAG, "Frame #" + Math.abs(frameCount - FRAME_ITERATIONS)
+ " vsync callback failed, expected callback in "
@@ -427,25 +562,19 @@
+ " but actual duration difference is " + callbackDurationDiffMs);
}
}
- mIsFirstCallback = false;
+ mExpectedPresentTime = expectedPresentTime;
verifyVsyncCallbacks(choreographer, callbackDurationMs,
continueLatch, frameCount - 1);
} else {
- assertTrue("Missed timeline for " + mCallbackMissedCounter + " callbacks, while "
- + CALLBACK_MISSED_THRESHOLD + " missed callbacks are allowed",
+ assertTrue("Missed timeline for " + mCallbackMissedCounter
+ + " callbacks, while " + CALLBACK_MISSED_THRESHOLD
+ + " missed callbacks are allowed",
mCallbackMissedCounter <= CALLBACK_MISSED_THRESHOLD);
continueLatch.countDown();
}
});
}
- private long getCallbackDurationDiffInMs(long callbackTimeNs, long requestedTimeNs,
- long expectedCallbackMs) {
- long actualTimeMs = TimeUnit.NANOSECONDS.toMillis(callbackTimeNs)
- - TimeUnit.NANOSECONDS.toMillis(requestedTimeNs);
- return Math.abs(expectedCallbackMs - actualTimeMs);
- }
-
private boolean waitForCountDown(CountDownLatch countDownLatch, long timeoutInSeconds) {
try {
return !countDownLatch.await(timeoutInSeconds, TimeUnit.SECONDS);
diff --git a/tests/CompanionDeviceMultiDeviceTests/OWNERS b/tests/CompanionDeviceMultiDeviceTests/OWNERS
new file mode 100644
index 0000000..7517836
--- /dev/null
+++ b/tests/CompanionDeviceMultiDeviceTests/OWNERS
@@ -0,0 +1,6 @@
+# Bug component: 708992
+evanxinchen@google.com
+guojing@google.com
+jeremyns@google.com
+raphk@google.com
+yukl@google.com
diff --git a/tests/CompanionDeviceMultiDeviceTests/README.md b/tests/CompanionDeviceMultiDeviceTests/README.md
new file mode 100644
index 0000000..6cf735a
--- /dev/null
+++ b/tests/CompanionDeviceMultiDeviceTests/README.md
@@ -0,0 +1,17 @@
+## CDM Multi-device Tests
+
+### Device Setup
+To test on physical devices, connect _two_ devices locally and enable USB debugging setting on both devices.
+
+When running on a cloudtop or other remote setups, use pontis to connect the devices on remote set up by running `pontis start`.
+Verify that pontis client is connected via `pontis status` and confirm that both devices are in "connected" state via `adb devices`.
+
+See go/pontis for more details regarding this workflow.
+
+To test on virtual devices, follow instructions to [set up netsim on cuttlefish](https://g3doc.corp.google.com/ambient/d2di/sim/g3doc/guide/cuttlefish.md?cl=head).
+Launch _two_ instances of virtual devices by specifying `--num_instances=2` parameter.
+
+### Running the Test
+```
+atest CompanionDeviceManagerMultiDeviceTestCases
+```
diff --git a/tests/CompanionDeviceMultiDeviceTests/client/Android.bp b/tests/CompanionDeviceMultiDeviceTests/client/Android.bp
new file mode 100644
index 0000000..1e68c9d
--- /dev/null
+++ b/tests/CompanionDeviceMultiDeviceTests/client/Android.bp
@@ -0,0 +1,50 @@
+// 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 {
+ // 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: "cdm_snippet",
+ srcs: ["src/**/*.kt"],
+ manifest: "AndroidManifest.xml",
+
+ platform_apis: true,
+ target_sdk_version: "current",
+
+ static_libs: [
+ "androidx.test.ext.junit",
+ "androidx.test.uiautomator_uiautomator",
+ "compatibility-device-util-axt",
+ "cts-companion-common",
+ "cts-companion-uicommon",
+ "kotlin-stdlib",
+ "mobly-snippet-lib",
+ ],
+ libs: [
+ "android.test.base",
+ "android.test.runner",
+ ],
+
+ optimize: {
+ proguard_compatibility: true,
+ proguard_flags_files: ["proguard.flags"],
+ },
+}
diff --git a/tests/CompanionDeviceMultiDeviceTests/client/AndroidManifest.xml b/tests/CompanionDeviceMultiDeviceTests/client/AndroidManifest.xml
new file mode 100644
index 0000000..11dc763
--- /dev/null
+++ b/tests/CompanionDeviceMultiDeviceTests/client/AndroidManifest.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.companion.multidevices">
+
+ <uses-permission android:name="android.permission.INTERNET" />
+ <uses-permission android:name="android.permission.REQUEST_COMPANION_SELF_MANAGED" />
+ <uses-permission android:name="android.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE" />
+ <uses-permission android:name="android.permission.REQUEST_COMPANION_PROFILE_WATCH" />
+ <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+ <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+ <uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
+ <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
+ <uses-permission android:name="android.permission.DELIVER_COMPANION_MESSAGES" />
+
+ <uses-feature android:name="android.hardware.bluetooth" android:required="true"/>
+ <uses-feature android:name="android.software.companion_device_setup" />
+
+ <application>
+ <!-- Add any classes that implement the Snippet interface as meta-data, whose
+ value is a comma-separated string, each section being the package path
+ of a snippet class -->
+ <meta-data
+ android:name="mobly-snippets"
+ android:value="android.companion.multidevices.CompanionDeviceManagerSnippet" />
+ </application>
+
+ <!-- Add an instrumentation tag so that the app can be launched through an
+ instrument command. The runner `com.google.android.mobly.snippet.SnippetRunner`
+ is derived from `AndroidJUnitRunner`, and is required to use the
+ Mobly Snippet Lib. -->
+ <instrumentation
+ android:name="com.google.android.mobly.snippet.SnippetRunner"
+ android:targetPackage="android.companion.multidevices" />
+</manifest>
diff --git a/tests/CompanionDeviceMultiDeviceTests/client/proguard.flags b/tests/CompanionDeviceMultiDeviceTests/client/proguard.flags
new file mode 100644
index 0000000..1c70253a
--- /dev/null
+++ b/tests/CompanionDeviceMultiDeviceTests/client/proguard.flags
@@ -0,0 +1,24 @@
+# Keep all companion classes.
+-keep class android.companion.** {
+ *;
+}
+
+# Do not touch Mobly.
+-keep class com.google.android.mobly.** {
+ *;
+}
+
+# Keep names for easy debugging.
+-dontobfuscate
+
+# Necessary to allow debugging.
+-keepattributes *
+
+# By default, proguard leaves all classes in their original package, which
+# needlessly repeats com.google.android.apps.etc.
+-repackageclasses ""
+
+# Allows proguard to make private and protected methods and fields public as
+# part of optimization. This lets proguard inline trivial getter/setter
+# methods.
+-allowaccessmodification
\ No newline at end of file
diff --git a/tests/CompanionDeviceMultiDeviceTests/client/src/android/companion/multidevices/CallbackUtils.kt b/tests/CompanionDeviceMultiDeviceTests/client/src/android/companion/multidevices/CallbackUtils.kt
new file mode 100644
index 0000000..3e4944a
--- /dev/null
+++ b/tests/CompanionDeviceMultiDeviceTests/client/src/android/companion/multidevices/CallbackUtils.kt
@@ -0,0 +1,107 @@
+/*
+ * 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 android.companion.multidevices
+
+import android.companion.AssociationInfo
+import android.companion.CompanionDeviceManager
+import android.companion.CompanionException
+import android.content.IntentSender
+import android.os.OutcomeReceiver
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit.SECONDS
+import java.util.concurrent.TimeoutException
+
+/** Blocking callbacks for Wi-Fi Aware and Connectivity Manager. */
+object CallbackUtils {
+ private const val TAG = "CDM_CallbackUtils"
+ private const val CALLBACK_TIMEOUT_SEC = 30L
+ private const val MESSAGE_CALLBACK_TIMEOUT_SEC = 5L
+
+ class AssociationCallback : CompanionDeviceManager.Callback() {
+ private val pending = CountDownLatch(1)
+ private val created = CountDownLatch(1)
+
+ private var pendingIntent: IntentSender? = null
+ private var associationInfo: AssociationInfo? = null
+ private var error: String? = null
+
+ override fun onAssociationPending(intentSender: IntentSender) {
+ this.pendingIntent = intentSender
+ pending.countDown()
+ }
+
+ override fun onAssociationCreated(associationInfo: AssociationInfo) {
+ this.associationInfo = associationInfo
+ created.countDown()
+ }
+
+ override fun onFailure(error: CharSequence?) {
+ this.error = error?.toString() ?: "There was an unexpected failure."
+ pending.countDown()
+ created.countDown()
+ }
+
+ fun waitForPendingIntent(): IntentSender? {
+ if (!pending.await(CALLBACK_TIMEOUT_SEC, SECONDS)) {
+ throw TimeoutException("Pending association request timed out.")
+ }
+
+ error?.let {
+ throw CompanionException(it)
+ }
+
+ return pendingIntent
+ }
+
+ fun waitForAssociation(): AssociationInfo? {
+ if (!created.await(CALLBACK_TIMEOUT_SEC, SECONDS)) {
+ throw TimeoutException("Association request timed out.")
+ }
+
+ error?.let {
+ throw CompanionException(it)
+ }
+
+ return associationInfo
+ }
+ }
+
+ class SystemDataTransferCallback : OutcomeReceiver<Void, CompanionException> {
+ private val completed = CountDownLatch(1)
+
+ private var error: CompanionException? = null
+
+ override fun onResult(result: Void?) {
+ completed.countDown()
+ }
+
+ override fun onError(error: CompanionException) {
+ this.error = error
+ completed.countDown()
+ }
+
+ fun waitForCompletion() {
+ if (!completed.await(CALLBACK_TIMEOUT_SEC, SECONDS)) {
+ throw TimeoutException("System data transfer timed out.")
+ }
+
+ error?.let {
+ throw it
+ }
+ }
+ }
+}
diff --git a/tests/CompanionDeviceMultiDeviceTests/client/src/android/companion/multidevices/CompanionDeviceManagerSnippet.kt b/tests/CompanionDeviceMultiDeviceTests/client/src/android/companion/multidevices/CompanionDeviceManagerSnippet.kt
new file mode 100644
index 0000000..ee587f5
--- /dev/null
+++ b/tests/CompanionDeviceMultiDeviceTests/client/src/android/companion/multidevices/CompanionDeviceManagerSnippet.kt
@@ -0,0 +1,183 @@
+/*
+ * 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 android.companion.multidevices
+
+import android.app.Instrumentation
+import android.bluetooth.BluetoothAdapter
+import android.bluetooth.BluetoothManager
+import android.companion.AssociationInfo
+import android.companion.AssociationRequest
+import android.companion.BluetoothDeviceFilter
+import android.companion.CompanionDeviceManager
+import android.companion.CompanionException
+import android.companion.cts.common.CompanionActivity
+import android.companion.multidevices.CallbackUtils.AssociationCallback
+import android.companion.multidevices.CallbackUtils.SystemDataTransferCallback
+import android.companion.multidevices.bluetooth.BluetoothConnector
+import android.companion.multidevices.bluetooth.BluetoothController
+import android.companion.cts.uicommon.CompanionDeviceManagerUi
+import android.content.Context
+import android.os.Handler
+import android.os.HandlerExecutor
+import android.os.HandlerThread
+import android.util.Log
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.google.android.mobly.snippet.Snippet
+import com.google.android.mobly.snippet.event.EventCache
+import com.google.android.mobly.snippet.rpc.Rpc
+import java.util.concurrent.Executor
+import java.util.regex.Pattern
+
+/**
+ * Snippet class that exposes Android APIs in CompanionDeviceManager.
+ */
+class CompanionDeviceManagerSnippet : Snippet {
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()!!
+ private val context: Context = instrumentation.targetContext
+
+ private val btAdapter: BluetoothAdapter by lazy {
+ (context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager).adapter
+ }
+ private val companionDeviceManager: CompanionDeviceManager by lazy {
+ context.getSystemService(Context.COMPANION_DEVICE_SERVICE) as CompanionDeviceManager
+ }
+ private val btConnector: BluetoothConnector by lazy {
+ BluetoothConnector(btAdapter, companionDeviceManager)
+ }
+
+ private val uiDevice by lazy { UiDevice.getInstance(instrumentation) }
+ private val confirmationUi by lazy { CompanionDeviceManagerUi(uiDevice) }
+ private val btController by lazy { BluetoothController(context, btAdapter, uiDevice) }
+
+ private val eventCache = EventCache.getInstance()
+ private val handlerThread = HandlerThread("Snippet-Aware")
+ private val handler: Handler
+ private val executor: Executor
+
+ init {
+ handlerThread.start()
+ handler = Handler(handlerThread.looper)
+ executor = HandlerExecutor(handler)
+ }
+
+ /**
+ * Make device discoverable to other devices via BLE and return device name.
+ */
+ @Rpc(description = "Start advertising device to be discoverable.")
+ fun becomeDiscoverable(): String {
+ btController.becomeDiscoverable()
+ return btAdapter.name
+ }
+
+ /**
+ * Associate with a nearby device with given name and return newly-created association ID.
+ */
+ @Rpc(description = "Start device association flow.")
+ @Throws(Exception::class)
+ fun associate(deviceName: String): Int {
+ val filter = BluetoothDeviceFilter.Builder()
+ .setNamePattern(Pattern.compile(deviceName))
+ .build()
+ val request = AssociationRequest.Builder()
+ .setSingleDevice(true)
+ .addDeviceFilter(filter)
+ .build()
+ val callback = AssociationCallback()
+ companionDeviceManager.associate(request, callback, handler)
+ val pendingConfirmation = callback.waitForPendingIntent()
+ ?: throw CompanionException("Association is pending but intent sender is null.")
+ CompanionActivity.launchAndWait(context)
+ CompanionActivity.startIntentSender(pendingConfirmation)
+ confirmationUi.waitUntilVisible()
+ confirmationUi.waitUntilPositiveButtonIsEnabledAndClick()
+ confirmationUi.waitUntilGone()
+
+ val (_, result) = CompanionActivity.waitForActivityResult()
+ if (result == null) {
+ throw CompanionException("Association result can't be null.")
+ }
+
+ val association = result.getParcelableExtra(
+ CompanionDeviceManager.EXTRA_ASSOCIATION,
+ AssociationInfo::class.java
+ )
+ val remoteDevice = association.associatedDevice?.getBluetoothDevice()!!
+
+ // Register associated device
+ btConnector.registerDevice(association.id, remoteDevice)
+
+ return association.id
+ }
+
+ /**
+ * Disassociate an association with given ID.
+ */
+ @Rpc(description = "Disassociate device.")
+ @Throws(Exception::class)
+ fun disassociate(associationId: Int) {
+ companionDeviceManager.disassociate(associationId)
+ }
+
+ /**
+ * Consent to system data transfer and carry it out using Bluetooth socket.
+ */
+ @Rpc(description = "Start permissions sync.")
+ fun startPermissionsSync(associationId: Int) {
+ val pendingIntent = companionDeviceManager
+ .buildPermissionTransferUserConsentIntent(associationId)
+ CompanionActivity.launchAndWait(context)
+ CompanionActivity.startIntentSender(pendingIntent)
+ confirmationUi.waitUntilSystemDataTransferConfirmationVisible()
+ confirmationUi.clickPositiveButton()
+ confirmationUi.waitUntilGone()
+
+ CompanionActivity.waitForActivityResult()
+
+ val callback = SystemDataTransferCallback()
+ companionDeviceManager.startSystemDataTransfer(associationId, executor, callback)
+ callback.waitForCompletion()
+ }
+
+ @Rpc(description = "Attach transport to the BT client socket.")
+ fun attachClientSocket(id: Int) {
+ btConnector.attachClientSocket(id)
+ }
+
+ @Rpc(description = "Attach transport to the BT server socket.")
+ fun attachServerSocket(id: Int) {
+ btConnector.attachServerSocket(id)
+ }
+
+ @Rpc(description = "Close all open sockets.")
+ fun closeAllSockets() {
+ // Close all open sockets
+ btConnector.closeAllSockets()
+ }
+
+ @Rpc(description = "Disassociate all associations.")
+ fun disassociateAll() {
+ companionDeviceManager.myAssociations.forEach {
+ Log.d(TAG, "Disassociating id=${it.id}.")
+ companionDeviceManager.disassociate(it.id)
+ }
+ }
+
+ companion object {
+ private const val TAG = "CDM_CompanionDeviceManagerSnippet"
+ }
+}
diff --git a/tests/CompanionDeviceMultiDeviceTests/client/src/android/companion/multidevices/bluetooth/BluetoothConnector.kt b/tests/CompanionDeviceMultiDeviceTests/client/src/android/companion/multidevices/bluetooth/BluetoothConnector.kt
new file mode 100644
index 0000000..c7312d2
--- /dev/null
+++ b/tests/CompanionDeviceMultiDeviceTests/client/src/android/companion/multidevices/bluetooth/BluetoothConnector.kt
@@ -0,0 +1,155 @@
+/*
+ * 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 android.companion.multidevices.bluetooth
+
+import android.bluetooth.BluetoothAdapter
+import android.bluetooth.BluetoothDevice
+import android.bluetooth.BluetoothServerSocket
+import android.bluetooth.BluetoothSocket
+import android.companion.CompanionDeviceManager
+import android.util.Log
+import java.io.IOException
+import java.util.UUID
+
+class BluetoothConnector(
+ private val adapter: BluetoothAdapter,
+ private val cdm: CompanionDeviceManager
+) {
+ companion object {
+ private const val TAG = "CDM_BluetoothServer"
+
+ private val SERVICE_NAME = "CDM_BluetoothChannel"
+ private val SERVICE_UUID = UUID.fromString("435fe1d9-56c5-455d-a516-d5e6b22c52f9")
+
+ // Registry of bluetooth server threads
+ private val serverThreads = mutableMapOf<Int, BluetoothServerThread>()
+
+ // Registry of remote bluetooth devices
+ private val remoteDevices = mutableMapOf<Int, BluetoothDevice>()
+
+ // Set of connected client sockets
+ private val clientSockets = mutableMapOf<Int, BluetoothSocket>()
+ }
+
+ fun attachClientSocket(associationId: Int) {
+ try {
+ val device = remoteDevices[associationId]!!
+ val socket = device.createRfcommSocketToServiceRecord(SERVICE_UUID)
+ if (clientSockets.containsKey(associationId)) {
+ detachClientSocket(associationId)
+ clientSockets[associationId] = socket
+ } else {
+ clientSockets += associationId to socket
+ }
+
+ socket.connect()
+ Log.d(TAG, "Attaching client socket $socket.")
+ cdm.attachSystemDataTransport(
+ associationId,
+ socket.inputStream,
+ socket.outputStream
+ )
+ } catch (e: IOException) {
+ Log.e(TAG, "Failed to attach client socket.", e)
+ throw RuntimeException(e)
+ }
+ }
+
+ fun attachServerSocket(associationId: Int) {
+ val serverThread: BluetoothServerThread
+ if (serverThreads.containsKey(associationId)) {
+ serverThread = serverThreads[associationId]!!
+ } else {
+ serverThread = BluetoothServerThread(associationId)
+ serverThreads += associationId to serverThread
+ }
+
+ // Start thread
+ if (!serverThread.isOpen) {
+ serverThread.start()
+ }
+ }
+
+ fun closeAllSockets() {
+ val iter = clientSockets.keys.iterator()
+ while (iter.hasNext()) {
+ detachClientSocket(iter.next())
+ }
+ for (thread in serverThreads.values) {
+ thread.shutdown()
+ }
+ serverThreads.clear()
+ }
+
+ fun registerDevice(associationId: Int, remoteDevice: BluetoothDevice) {
+ remoteDevices[associationId] = remoteDevice
+ }
+
+ private fun detachClientSocket(associationId: Int) {
+ try {
+ Log.d(TAG, "Detaching client socket.")
+ cdm.detachSystemDataTransport(associationId)
+ clientSockets[associationId]?.close()
+ } catch (e: IOException) {
+ Log.e(TAG, "Failed to detach client socket.", e)
+ throw RuntimeException(e)
+ }
+ }
+
+ inner class BluetoothServerThread(
+ private val associationId: Int
+ ) : Thread() {
+ private lateinit var mServerSocket: BluetoothServerSocket
+
+ var isOpen = false
+
+ override fun run() {
+ try {
+ Log.d(TAG, "Listening for remote connections...")
+ mServerSocket = adapter.listenUsingRfcommWithServiceRecord(
+ SERVICE_NAME,
+ SERVICE_UUID
+ )
+ isOpen = true
+ do {
+ val socket = mServerSocket.accept()
+ Log.d(TAG, "Attaching server socket $socket.")
+ cdm.attachSystemDataTransport(
+ associationId,
+ socket.inputStream,
+ socket.outputStream
+ )
+ } while (isOpen)
+ } catch (e: IOException) {
+ throw RuntimeException(e)
+ }
+ }
+
+ fun shutdown() {
+ if (!isOpen || !this::mServerSocket.isInitialized) return
+
+ try {
+ Log.d(TAG, "Closing server socket.")
+ cdm.detachSystemDataTransport(associationId)
+ mServerSocket.close()
+ isOpen = false
+ } catch (e: IOException) {
+ throw RuntimeException(e)
+ }
+ }
+ }
+}
diff --git a/tests/CompanionDeviceMultiDeviceTests/client/src/android/companion/multidevices/bluetooth/BluetoothController.kt b/tests/CompanionDeviceMultiDeviceTests/client/src/android/companion/multidevices/bluetooth/BluetoothController.kt
new file mode 100644
index 0000000..c4d2026
--- /dev/null
+++ b/tests/CompanionDeviceMultiDeviceTests/client/src/android/companion/multidevices/bluetooth/BluetoothController.kt
@@ -0,0 +1,109 @@
+/*
+ * 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 android.companion.multidevices.bluetooth
+
+import android.bluetooth.BluetoothAdapter
+import android.bluetooth.BluetoothDevice
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.os.SystemClock
+import android.util.Log
+import androidx.test.uiautomator.UiDevice
+import java.util.concurrent.TimeoutException
+import kotlin.time.Duration
+import kotlin.time.Duration.Companion.seconds
+
+/** Controls the local Bluetooth adapter for testing. */
+class BluetoothController(
+ private val context: Context,
+ private val adapter: BluetoothAdapter,
+ private val ui: UiDevice
+) {
+ companion object {
+ private const val TAG = "CDM_BluetoothController"
+ }
+
+ private val bluetoothUi by lazy { BluetoothUi(ui) }
+
+ init {
+ Log.d(TAG, "Registering pairing listener.")
+ context.registerReceiver(
+ PairingBroadcastReceiver(),
+ IntentFilter(BluetoothDevice.ACTION_PAIRING_REQUEST)
+ )
+ }
+
+ val isEnabled: Boolean
+ get() = adapter.isEnabled
+
+ /** Turns on the local Bluetooth adapter */
+ fun enableBluetooth() {
+ if (isEnabled) return
+
+ val intent = Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE)
+ intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
+ context.startActivity(intent)
+ bluetoothUi.clickAllowButton()
+ waitFor { adapter.state == BluetoothAdapter.STATE_ON }
+ }
+
+ /** Become discoverable for specified duration */
+ fun becomeDiscoverable(duration: Duration = 15.seconds) {
+ enableBluetooth()
+
+ val intent = Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE)
+ intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
+ intent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, duration.inWholeSeconds)
+ context.startActivity(intent)
+ bluetoothUi.clickAllowButton()
+ }
+
+ /** Unpair all devices for cleanup */
+ fun unpairAllDevices() {
+ for (device in adapter.bondedDevices) {
+ Log.d(TAG, "Unpairing $device.")
+ if (!device.removeBond()) continue
+ waitFor { device.bondState == BluetoothDevice.BOND_NONE }
+ }
+ }
+
+ private fun waitFor(
+ interval: Duration = 1.seconds,
+ timeout: Duration = 5.seconds,
+ condition: () -> Boolean
+ ) {
+ var elapsed = 0L
+ while (elapsed < timeout.inWholeMilliseconds) {
+ if (condition.invoke()) return
+ SystemClock.sleep(interval.inWholeMilliseconds)
+ elapsed += interval.inWholeMilliseconds
+ }
+ throw TimeoutException("Bluetooth did not become an expected state.")
+ }
+
+ inner class PairingBroadcastReceiver : BroadcastReceiver() {
+ override fun onReceive(context: Context, intent: Intent) {
+ Log.d(TAG, "Received broadcast for ${intent.action}")
+
+ // onReceive() somehow blocks pairing prompt from launching
+ Thread { bluetoothUi.confirmPairingRequest() }.start()
+ context.unregisterReceiver(this)
+ }
+ }
+}
diff --git a/tests/CompanionDeviceMultiDeviceTests/client/src/android/companion/multidevices/bluetooth/BluetoothUi.kt b/tests/CompanionDeviceMultiDeviceTests/client/src/android/companion/multidevices/bluetooth/BluetoothUi.kt
new file mode 100644
index 0000000..6983cb0
--- /dev/null
+++ b/tests/CompanionDeviceMultiDeviceTests/client/src/android/companion/multidevices/bluetooth/BluetoothUi.kt
@@ -0,0 +1,66 @@
+/*
+ * 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 android.companion.multidevices.bluetooth
+
+import android.companion.cts.uicommon.CompanionDeviceManagerUi
+import android.util.Log
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.UiDevice
+import androidx.test.uiautomator.Until
+import java.util.regex.Pattern
+
+class BluetoothUi(private val ui: UiDevice) : CompanionDeviceManagerUi(ui) {
+ fun clickAllowButton() = click(ALLOW_BUTTON, "Allow button")
+
+ fun confirmPairingRequest(): Boolean {
+ if (ui.hasObject(PAIRING_PIN_ENTRY)) {
+ // It is prompting for a custom user pin entry
+ Log.d(TAG, "Is user entry prompt.")
+ ui.findObject(PAIRING_PIN_ENTRY).text = "0000"
+ click(OK_BUTTON, "Ok button")
+ } else {
+ // It just needs user consent
+ Log.d(TAG, "Looking for pair button.")
+ val button = ui.wait(Until.findObject(PAIR_BUTTON), 1_000)
+ if (button != null) {
+ Log.d(TAG, "Pair button found.")
+ button.click()
+ return true
+ }
+ Log.d(TAG, "Pair button not found.")
+ }
+ return false
+ }
+
+ companion object {
+ private const val TAG = "CDM_BluetoothUi"
+
+ private val ALLOW_TEXT_PATTERN = caseInsensitive("allow")
+ private val ALLOW_BUTTON = By.text(ALLOW_TEXT_PATTERN).clickable(true)
+
+ private val PAIRING_PIN_ENTRY = By.clazz(".EditText")
+
+ private val OK_TEXT_PATTERN = caseInsensitive("ok")
+ private val OK_BUTTON = By.text(OK_TEXT_PATTERN).clickable(true)
+
+ private val PAIR_TEXT_PATTERN = caseInsensitive("pair")
+ private val PAIR_BUTTON = By.text(PAIR_TEXT_PATTERN).clickable(true)
+
+ private fun caseInsensitive(text: String): Pattern =
+ Pattern.compile(text, Pattern.CASE_INSENSITIVE)
+ }
+}
diff --git a/tests/CompanionDeviceMultiDeviceTests/host/Android.bp b/tests/CompanionDeviceMultiDeviceTests/host/Android.bp
new file mode 100644
index 0000000..1167a3e
--- /dev/null
+++ b/tests/CompanionDeviceMultiDeviceTests/host/Android.bp
@@ -0,0 +1,51 @@
+// 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 {
+ // 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"],
+}
+
+python_test_host {
+ name: "CompanionDeviceManagerMultiDeviceTestCases",
+ main: "cdm_transport_test.py",
+ srcs: ["*.py"],
+ libs: [
+ "mobly",
+ ],
+ test_suites: [
+ "general-tests",
+ ],
+ test_options: {
+ unit_test: false,
+ tags: ["mobly"],
+ },
+ data: [
+ ":cdm_snippet",
+ "requirements.txt",
+ ],
+ version: {
+ py2: {
+ enabled: false,
+ },
+ py3: {
+ enabled: true,
+ embedded_launcher: true,
+ },
+ },
+}
diff --git a/tests/CompanionDeviceMultiDeviceTests/host/AndroidTest.xml b/tests/CompanionDeviceMultiDeviceTests/host/AndroidTest.xml
new file mode 100644
index 0000000..9d1813f
--- /dev/null
+++ b/tests/CompanionDeviceMultiDeviceTests/host/AndroidTest.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<configuration description="Config for CDM multi-device test cases">
+ <option name="test-tag" value="CompanionDeviceMultiDeviceTests" />
+ <option name="config-descriptor:metadata" key="component" value="framework" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+
+ <object class="com.android.tradefed.testtype.suite.module.DeviceFeatureModuleController"
+ type="module_controller">
+ <option name="required-feature" value="android.software.companion_device_setup" />
+ </object>
+
+ <device name="device1">
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="test-file-name" value="cdm_snippet.apk" />
+ </target_preparer>
+ </device>
+ <device name="device2">
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="test-file-name" value="cdm_snippet.apk" />
+ </target_preparer>
+ </device>
+
+ <test class="com.android.tradefed.testtype.mobly.MoblyBinaryHostTest">
+ <!-- The mobly-par-file-name should match the module name -->
+ <option name="mobly-par-file-name" value="CompanionDeviceManagerMultiDeviceTestCases" />
+ <!-- Timeout limit in milliseconds for all test cases of the python binary -->
+ <option name="mobly-test-timeout" value="60000" />
+ </test>
+</configuration>
diff --git a/tests/CompanionDeviceMultiDeviceTests/host/cdm_base_test.py b/tests/CompanionDeviceMultiDeviceTests/host/cdm_base_test.py
new file mode 100644
index 0000000..bb10658
--- /dev/null
+++ b/tests/CompanionDeviceMultiDeviceTests/host/cdm_base_test.py
@@ -0,0 +1,63 @@
+# Lint as: python3
+"""
+Base class for setting up devices for CDM functionalities.
+"""
+
+from mobly import base_test
+from mobly import utils
+from mobly.controllers import android_device
+
+CDM_SNIPPET_PACKAGE = 'android.companion.multidevices'
+
+
+class BaseTestClass(base_test.BaseTestClass):
+
+ def setup_class(self):
+ # Declare that two Android devices are needed.
+ self.sender, self.receiver = self.register_controller(
+ android_device, min_number=2)
+ self.sender_id = None
+ self.receiver_id = None
+
+ def _setup_device(device):
+ device.load_snippet('cdm', CDM_SNIPPET_PACKAGE)
+ device.adb.shell('input keyevent KEYCODE_WAKEUP')
+ device.adb.shell('input keyevent KEYCODE_MENU')
+ device.adb.shell('input keyevent KEYCODE_HOME')
+
+ # Clean up existing associations
+ device.cdm.disassociateAll()
+
+ # Sets up devices in parallel to save time.
+ utils.concurrent_exec(
+ _setup_device,
+ ((self.sender,), (self.receiver,)),
+ max_workers=2,
+ raise_on_exception=True)
+
+ def associate_devices(self) -> tuple[int, int]:
+ """Associate devices with each other and return association IDs for both"""
+ # If association already exists, don't need another
+ if self.sender_id and self.receiver_id:
+ return (self.sender_id, self.receiver_id)
+
+ receiver_name = self.receiver.cdm.becomeDiscoverable()
+ self.receiver_id = self.sender.cdm.associate(receiver_name)
+
+ sender_name = self.sender.cdm.becomeDiscoverable()
+ self.sender_id = self.receiver.cdm.associate(sender_name)
+
+ return (self.sender_id, self.receiver_id)
+
+ def attach_transports(self):
+ """Attach transports to both devices"""
+ self.associate_devices()
+
+ self.receiver.cdm.attachServerSocket(self.sender_id)
+ self.sender.cdm.attachClientSocket(self.receiver_id)
+
+ def teardown_class(self):
+ """Clean up the opened sockets"""
+ self.sender.cdm.closeAllSockets()
+ self.receiver.cdm.closeAllSockets()
+
diff --git a/tests/CompanionDeviceMultiDeviceTests/host/cdm_transport_test.py b/tests/CompanionDeviceMultiDeviceTests/host/cdm_transport_test.py
new file mode 100644
index 0000000..9cb2d10
--- /dev/null
+++ b/tests/CompanionDeviceMultiDeviceTests/host/cdm_transport_test.py
@@ -0,0 +1,36 @@
+# Lint as: python3
+"""
+Test E2E CDM functions on mobly.
+"""
+
+import cdm_base_test
+import sys
+
+from mobly import asserts
+from mobly import test_runner
+
+CDM_SNIPPET_PACKAGE = 'android.companion.multidevices'
+
+
+class TransportTestClass(cdm_base_test.BaseTestClass):
+
+ def test_permissions_sync(self):
+ """This tests permissions sync from one device to another."""
+
+ # associate and attach transports
+ self.attach_transports()
+
+ # start permissions sync
+ self.sender.cdm.startPermissionsSync(self.receiver_id)
+
+
+if __name__ == '__main__':
+ try:
+ # Take test args and remove standalone '--' from the list
+ index = sys.argv.index('--')
+ sys.argv = sys.argv[:1] + sys.argv[index + 1:]
+ except ValueError:
+ # Ignore if '--' is not in args
+ pass
+
+ test_runner.main()
\ No newline at end of file
diff --git a/tests/CompanionDeviceMultiDeviceTests/host/requirements.txt b/tests/CompanionDeviceMultiDeviceTests/host/requirements.txt
new file mode 100644
index 0000000..86a11aa
--- /dev/null
+++ b/tests/CompanionDeviceMultiDeviceTests/host/requirements.txt
@@ -0,0 +1 @@
+mobly==1.12.1
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
index b18a1a8..4d36111 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
@@ -19,6 +19,8 @@
package com.android.server.wm.flicker
import android.tools.common.PlatformConsts
+import android.tools.common.Position
+import android.tools.common.flicker.subject.layers.LayerTraceEntrySubject
import android.tools.common.flicker.subject.region.RegionSubject
import android.tools.common.traces.component.ComponentNameMatcher
import android.tools.common.traces.component.IComponentNameMatcher
@@ -169,12 +171,7 @@
*/
fun LegacyFlickerTest.navBarLayerPositionAtStart() {
assertLayersStart {
- val display =
- this.entry.displays.firstOrNull { !it.isVirtual } ?: error("There is no display!")
- this.visibleRegion(ComponentNameMatcher.NAV_BAR)
- .coversExactly(
- WindowUtils.getNavigationBarPosition(display, scenario.isGesturalNavigation)
- )
+ assertNavBarPosition(this, scenario.isGesturalNavigation)
}
}
@@ -184,13 +181,36 @@
*/
fun LegacyFlickerTest.navBarLayerPositionAtEnd() {
assertLayersEnd {
- val display =
- this.entry.displays.minByOrNull { it.id }
- ?: throw RuntimeException("There is no display!")
- this.visibleRegion(ComponentNameMatcher.NAV_BAR)
- .coversExactly(
- WindowUtils.getNavigationBarPosition(display, scenario.isGesturalNavigation)
- )
+ assertNavBarPosition(this, scenario.isGesturalNavigation)
+ }
+}
+
+private fun assertNavBarPosition(sfState: LayerTraceEntrySubject, isGesturalNavigation: Boolean) {
+ val display =
+ sfState.entry.displays.filterNot { it.isOff }.minByOrNull { it.id }
+ ?: error("There is no display!")
+ val displayArea = display.layerStackSpace
+ val navBarPosition = display.navBarPosition(isGesturalNavigation)
+ val navBarRegion = sfState.visibleRegion(ComponentNameMatcher.NAV_BAR)
+
+ when (navBarPosition) {
+ Position.TOP ->
+ navBarRegion.hasSameTopPosition(displayArea)
+ .hasSameLeftPosition(displayArea)
+ .hasSameRightPosition(displayArea)
+ Position.BOTTOM ->
+ navBarRegion.hasSameBottomPosition(displayArea)
+ .hasSameLeftPosition(displayArea)
+ .hasSameRightPosition(displayArea)
+ Position.LEFT ->
+ navBarRegion.hasSameLeftPosition(displayArea)
+ .hasSameTopPosition(displayArea)
+ .hasSameBottomPosition(displayArea)
+ Position.RIGHT ->
+ navBarRegion.hasSameRightPosition(displayArea)
+ .hasSameTopPosition(displayArea)
+ .hasSameBottomPosition(displayArea)
+ else -> error("Unknown position $navBarPosition")
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenTrampolineActivityTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenTrampolineActivityTest.kt
index c05dc32..6722cc8 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenTrampolineActivityTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenTrampolineActivityTest.kt
@@ -18,7 +18,6 @@
import android.platform.test.annotations.Presubmit
import android.tools.common.datatypes.Rect
-import android.tools.common.datatypes.Region
import android.tools.common.flicker.subject.region.RegionSubject
import android.tools.common.traces.component.ComponentNameMatcher
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
@@ -38,8 +37,8 @@
*
* Setup: Launch Activity A in fullscreen.
*
- * Transitions: From A launch a trampoline Activity T, T launches secondary Activity B and
- * finishes itself, end up in split A|B.
+ * Transitions: From A launch a trampoline Activity T, T launches secondary Activity B and finishes
+ * itself, end up in split A|B.
*
* To run this test: `atest FlickerTests:OpenTrampolineActivityTest`
*/
@@ -53,28 +52,16 @@
tapl.setExpectedRotationCheckEnabled(false)
testApp.launchViaIntent(wmHelper)
startDisplayBounds =
- wmHelper.currentState.layerState.physicalDisplayBounds
- ?: error("Can't get display bounds")
+ wmHelper.currentState.layerState.physicalDisplayBounds
+ ?: error("Can't get display bounds")
}
- transitions {
- testApp.launchTrampolineActivity(wmHelper)
- }
+ transitions { testApp.launchTrampolineActivity(wmHelper) }
teardown {
tapl.goHome()
testApp.exit(wmHelper)
}
}
- /** Assert the background animation layer is never visible during bounds change transition. */
- @Presubmit
- @Test
- fun backgroundLayerNeverVisible() {
- val backgroundColorLayer = ComponentNameMatcher("", "Animation Background")
- flicker.assertLayers {
- isInvisible(backgroundColorLayer)
- }
- }
-
/** Trampoline activity should finish itself before the end of this test. */
@Presubmit
@Test
@@ -96,9 +83,7 @@
@Presubmit
@Test
fun mainActivityWindowAlwaysVisible() {
- flicker.assertWm {
- isAppWindowVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
- }
+ flicker.assertWm { isAppWindowVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT) }
}
// TODO(b/289140963): After this is fixed, assert the main Activity window is visible
@@ -107,12 +92,8 @@
@Presubmit
@Test
fun mainActivityLayerAlwaysVisible() {
- flicker.assertLayersStart {
- isVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
- }
- flicker.assertLayersEnd {
- isVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
- }
+ flicker.assertLayersStart { isVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT) }
+ flicker.assertLayersEnd { isVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT) }
}
/** Secondary activity is launched from the trampoline activity. */
@@ -121,10 +102,10 @@
fun secondaryActivityWindowLaunchedFromTrampoline() {
flicker.assertWm {
notContains(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
- .then()
- .isAppWindowInvisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
- .then()
- .isAppWindowVisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+ .then()
+ .isAppWindowInvisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+ .then()
+ .isAppWindowVisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
}
}
@@ -134,8 +115,8 @@
fun secondaryActivityLayerLaunchedFromTrampoline() {
flicker.assertLayers {
isInvisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
- .then()
- .isVisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+ .then()
+ .isVisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
}
}
@@ -145,36 +126,38 @@
fun mainActivityWindowGoesFromFullscreenToSplit() {
flicker.assertWm {
this.invoke("mainActivityStartsInFullscreen") {
- it.visibleRegion(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+ it.visibleRegion(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
.coversExactly(startDisplayBounds)
- }
- // Begin of transition.
- .then()
- .isAppWindowInvisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
- .then()
- .invoke("mainAndSecondaryInSplit") {
- val mainActivityRegion =
- RegionSubject(
- it.visibleRegion(
- ActivityEmbeddingAppHelper
- .MAIN_ACTIVITY_COMPONENT).region,
- it.timestamp)
- val secondaryActivityRegion =
- RegionSubject(
- it.visibleRegion(
- ActivityEmbeddingAppHelper
- .SECONDARY_ACTIVITY_COMPONENT).region,
- it.timestamp)
- check { "height" }
- .that(mainActivityRegion.region.height)
- .isEqual(secondaryActivityRegion.region.height)
- check { "width" }
- .that(mainActivityRegion.region.width)
- .isEqual(secondaryActivityRegion.region.width)
- mainActivityRegion
- .plus(secondaryActivityRegion.region)
- .coversExactly(startDisplayBounds)
- }
+ }
+ // Begin of transition.
+ .then()
+ .isAppWindowInvisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+ .then()
+ .invoke("mainAndSecondaryInSplit") {
+ val mainActivityRegion =
+ RegionSubject(
+ it.visibleRegion(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+ .region,
+ it.timestamp
+ )
+ val secondaryActivityRegion =
+ RegionSubject(
+ it.visibleRegion(
+ ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT
+ )
+ .region,
+ it.timestamp
+ )
+ check { "height" }
+ .that(mainActivityRegion.region.height)
+ .isEqual(secondaryActivityRegion.region.height)
+ check { "width" }
+ .that(mainActivityRegion.region.width)
+ .isEqual(secondaryActivityRegion.region.width)
+ mainActivityRegion
+ .plus(secondaryActivityRegion.region)
+ .coversExactly(startDisplayBounds)
+ }
}
}
@@ -184,27 +167,26 @@
fun mainActivityLayerGoesFromFullscreenToSplit() {
flicker.assertLayers {
this.invoke("mainActivityStartsInFullscreen") {
- it.visibleRegion(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+ it.visibleRegion(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
.coversExactly(startDisplayBounds)
- }
- .then()
- .isInvisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
- .then()
- .isVisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+ }
+ .then()
+ .isInvisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+ .then()
+ .isVisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
}
flicker.assertLayersEnd {
- val leftLayerRegion = visibleRegion(
- ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+ val leftLayerRegion = visibleRegion(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
val rightLayerRegion =
- visibleRegion(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+ visibleRegion(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
// Compare dimensions of two splits, given we're using default split attributes,
// both activities take up the same visible size on the display.
check { "height" }
- .that(leftLayerRegion.region.height)
- .isEqual(rightLayerRegion.region.height)
+ .that(leftLayerRegion.region.height)
+ .isEqual(rightLayerRegion.region.height)
check { "width" }
- .that(leftLayerRegion.region.width)
- .isEqual(rightLayerRegion.region.width)
+ .that(leftLayerRegion.region.width)
+ .isEqual(rightLayerRegion.region.width)
leftLayerRegion.notOverlaps(rightLayerRegion.region)
// Layers of two activities sum to be fullscreen size on display.
leftLayerRegion.plus(rightLayerRegion.region).coversExactly(startDisplayBounds)
@@ -225,4 +207,4 @@
@JvmStatic
fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
}
-}
\ No newline at end of file
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt
index eac8813..a72ec1e 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt
@@ -82,17 +82,17 @@
*/
fun launchTrampolineActivity(wmHelper: WindowManagerStateHelper) {
val launchButton =
- uiDevice.wait(
- Until.findObject(By.res(getPackage(), "launch_trampoline_button")),
- FIND_TIMEOUT
- )
+ uiDevice.wait(
+ Until.findObject(By.res(getPackage(), "launch_trampoline_button")),
+ FIND_TIMEOUT
+ )
require(launchButton != null) { "Can't find launch trampoline activity button on screen." }
launchButton.click()
wmHelper
- .StateSyncBuilder()
- .withActivityState(SECONDARY_ACTIVITY_COMPONENT, PlatformConsts.STATE_RESUMED)
- .withActivityRemoved(TRAMPOLINE_ACTIVITY_COMPONENT)
- .waitForAndVerify()
+ .StateSyncBuilder()
+ .withActivityState(SECONDARY_ACTIVITY_COMPONENT, PlatformConsts.STATE_RESUMED)
+ .withActivityRemoved(TRAMPOLINE_ACTIVITY_COMPONENT)
+ .waitForAndVerify()
}
/**
@@ -217,7 +217,7 @@
.toFlickerComponent()
val TRAMPOLINE_ACTIVITY_COMPONENT =
- ActivityOptions.ActivityEmbedding.TrampolineActivity.COMPONENT.toFlickerComponent()
+ ActivityOptions.ActivityEmbedding.TrampolineActivity.COMPONENT.toFlickerComponent()
@JvmStatic
fun getWindowExtensions(): WindowExtensions? {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest.kt
index c1db423..ea7c7c4 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest.kt
@@ -25,6 +25,7 @@
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.FlakyTest
import com.android.server.wm.flicker.BaseTest
import com.android.server.wm.flicker.helpers.ImeShownOnAppStartHelper
import com.android.server.wm.flicker.helpers.setRotation
@@ -78,6 +79,7 @@
flicker.snapshotStartingWindowLayerCoversExactlyOnApp(imeTestApp)
}
+ @FlakyTest(bugId = 290767483)
@Postsubmit
@Test
fun imeLayerAlphaOneAfterSnapshotStartingWindowRemoval() {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
index 842ece3..1987a68 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
@@ -23,7 +23,6 @@
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
-import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.helpers.SimpleAppHelper
import org.junit.FixMethodOrder
import org.junit.Test
@@ -81,11 +80,10 @@
* apps are running before setup
* ```
*/
-@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class ChangeAppRotationTest(flicker: LegacyFlickerTest) : RotationTransition(flicker) {
+class ChangeAppRotationTest(flicker: LegacyFlickerTest) : RotationTransition(flicker) {
override val testApp = SimpleAppHelper(instrumentation)
override val transition: FlickerBuilder.() -> Unit
get() = {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTestCfArm.kt
deleted file mode 100644
index 1ab5c5a..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTestCfArm.kt
+++ /dev/null
@@ -1,42 +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.wm.flicker.rotation
-
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.LegacyFlickerTest
-import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
-import org.junit.FixMethodOrder
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class ChangeAppRotationTestCfArm(flicker: LegacyFlickerTest) : ChangeAppRotationTest(flicker) {
- companion object {
- /**
- * Creates the test configurations.
- *
- * See [LegacyFlickerTestFactory.rotationTests] for configuring screen orientation and
- * navigation modes.
- */
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams() = LegacyFlickerTestFactory.rotationTests()
- }
-}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
index b6ad3cc7..5b127c8 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
@@ -26,7 +26,6 @@
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import android.view.WindowManager
-import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.helpers.SeamlessRotationAppHelper
import com.android.server.wm.flicker.testapp.ActivityOptions
import org.junit.FixMethodOrder
@@ -88,11 +87,10 @@
* apps are running before setup
* ```
*/
-@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class SeamlessAppRotationTest(flicker: LegacyFlickerTest) : RotationTransition(flicker) {
+class SeamlessAppRotationTest(flicker: LegacyFlickerTest) : RotationTransition(flicker) {
override val testApp = SeamlessRotationAppHelper(instrumentation)
/** {@inheritDoc} */
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTestCfArm.kt
deleted file mode 100644
index 592be05..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTestCfArm.kt
+++ /dev/null
@@ -1,51 +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.wm.flicker.rotation
-
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.LegacyFlickerTest
-import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
-import com.android.server.wm.flicker.testapp.ActivityOptions
-import org.junit.FixMethodOrder
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/** This test should fail because of b/264518826 */
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class SeamlessAppRotationTestCfArm(flicker: LegacyFlickerTest) :
- SeamlessAppRotationTest(flicker) {
- companion object {
- /**
- * Creates the test configurations for seamless rotation based on the default rotation tests
- * from [LegacyFlickerTestFactory.rotationTests], but adding a flag (
- * [ActivityOptions.SeamlessRotation.EXTRA_STARVE_UI_THREAD]) to indicate if the app should
- * starve the UI thread of not
- */
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams() =
- LegacyFlickerTestFactory.rotationTests().flatMap { sourceCfg ->
- val legacyCfg = sourceCfg as LegacyFlickerTest
- val defaultRun = createConfig(legacyCfg, starveUiThread = false)
- val busyUiRun = createConfig(legacyCfg, starveUiThread = true)
- listOf(defaultRun, busyUiRun)
- }
- }
-}
diff --git a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
index 68ae806..92c5f17 100644
--- a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
@@ -102,6 +102,24 @@
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
+ <activity android:name=".LaunchTransparentActivity"
+ android:resizeableActivity="false"
+ android:screenOrientation="portrait"
+ android:theme="@android:style/Theme"
+ android:taskAffinity="com.android.server.wm.flicker.testapp.LaunchTransparentActivity"
+ android:label="LaunchTransparentActivity"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
+ <activity android:name=".TransparentActivity"
+ android:theme="@style/TransparentTheme"
+ android:taskAffinity="com.android.server.wm.flicker.testapp.TransparentActivity"
+ android:label="TransparentActivity"
+ android:exported="false">
+ </activity>
<activity android:name=".LaunchNewActivity"
android:taskAffinity="com.android.server.wm.flicker.testapp.LaunchNewActivity"
android:theme="@style/CutoutShortEdges"
diff --git a/core/res/res/color/letterbox_background.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_transparent.xml
similarity index 72%
rename from core/res/res/color/letterbox_background.xml
rename to tests/FlickerTests/test-apps/flickerapp/res/layout/activity_transparent.xml
index 955948a..0730ded 100644
--- a/core/res/res/color/letterbox_background.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_transparent.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- ~ Copyright (C) 2022 The Android Open Source Project
+ ~ 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.
@@ -14,6 +14,8 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:color="@color/system_neutral1_500" android:lStar="5" />
-</selector>
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+</FrameLayout>
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_transparent_launch.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_transparent_launch.xml
new file mode 100644
index 0000000..ff4ead9
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_transparent_launch.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:background="@android:color/black">
+
+ <Button
+ android:id="@+id/button_launch_transparent"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_centerHorizontal="true"
+ android:layout_centerVertical="true"
+ android:text="Launch Transparent" />
+ <Button
+ android:id="@+id/button_request_permission"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_centerHorizontal="true"
+ android:layout_centerVertical="true"
+ android:text="Request Permission" />
+</LinearLayout>
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml b/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml
index 1d21fd5..e51ed29 100644
--- a/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml
@@ -43,6 +43,13 @@
<item name="android:windowSoftInputMode">stateUnchanged</item>
</style>
+ <style name="TransparentTheme" parent="@android:style/Theme.DeviceDefault">
+ <item name="android:windowIsTranslucent">true</item>
+ <item name="android:windowBackground">@android:color/transparent</item>
+ <item name="android:windowContentOverlay">@null</item>
+ <item name="android:backgroundDimEnabled">false</item>
+ </style>
+
<style name="no_starting_window" parent="@android:style/Theme.DeviceDefault">
<item name="android:windowDisablePreview">true</item>
</style>
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
index 95c86ac..2795a6c 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
@@ -73,6 +73,18 @@
FLICKER_APP_PACKAGE + ".NonResizeablePortraitActivity");
}
+ public static class TransparentActivity {
+ public static final String LABEL = "TransparentActivity";
+ public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
+ FLICKER_APP_PACKAGE + ".TransparentActivity");
+ }
+
+ public static class LaunchTransparentActivity {
+ public static final String LABEL = "LaunchTransparentActivity";
+ public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
+ FLICKER_APP_PACKAGE + ".LaunchTransparentActivity");
+ }
+
public static class DialogThemedActivity {
public static final String LABEL = "DialogThemedActivity";
public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/LaunchTransparentActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/LaunchTransparentActivity.java
new file mode 100644
index 0000000..7c161fd
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/LaunchTransparentActivity.java
@@ -0,0 +1,36 @@
+/*
+ * 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.wm.flicker.testapp;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+
+public class LaunchTransparentActivity extends Activity {
+
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ setContentView(R.layout.activity_transparent_launch);
+ findViewById(R.id.button_launch_transparent)
+ .setOnClickListener(v -> launchTransparentActivity());
+ }
+
+ private void launchTransparentActivity() {
+ startActivity(new Intent(this, TransparentActivity.class));
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/BiometricSensorPrivacy.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/TransparentActivity.java
similarity index 66%
copy from services/core/java/com/android/server/biometrics/BiometricSensorPrivacy.java
copy to tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/TransparentActivity.java
index 6727fbc..1bac8bd 100644
--- a/services/core/java/com/android/server/biometrics/BiometricSensorPrivacy.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/TransparentActivity.java
@@ -14,12 +14,16 @@
* limitations under the License.
*/
-package com.android.server.biometrics;
+package com.android.server.wm.flicker.testapp;
-/**
- * Interface for biometric operations to get camera privacy state.
- */
-public interface BiometricSensorPrivacy {
- /* Returns true if privacy is enabled and camera access is disabled. */
- boolean isCameraPrivacyEnabled();
+import android.app.Activity;
+import android.os.Bundle;
+
+public class TransparentActivity extends Activity {
+
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ setContentView(R.layout.activity_transparent);
+ }
}
diff --git a/tests/Input/Android.bp b/tests/Input/Android.bp
index 4fa6fbe..96b685d 100644
--- a/tests/Input/Android.bp
+++ b/tests/Input/Android.bp
@@ -19,13 +19,26 @@
platform_apis: true,
certificate: "platform",
static_libs: [
+ "androidx.test.core",
"androidx.test.ext.junit",
+ "androidx.test.ext.truth",
"androidx.test.rules",
+ "androidx.test.runner",
+ "androidx.test.uiautomator_uiautomator",
+ "servicestests-utils",
+ "frameworks-base-testutils",
+ "hamcrest-library",
+ "kotlin-test",
"mockito-target-minus-junit4",
+ "platform-test-annotations",
"services.core.unboosted",
"testables",
+ "testng",
"truth-prebuilt",
- "androidx.test.uiautomator_uiautomator",
+ ],
+ libs: [
+ "android.test.mock",
+ "android.test.base",
],
test_suites: ["device-tests"],
}
diff --git a/tests/Input/AndroidManifest.xml b/tests/Input/AndroidManifest.xml
index 20f564e..3b723dd 100644
--- a/tests/Input/AndroidManifest.xml
+++ b/tests/Input/AndroidManifest.xml
@@ -16,11 +16,14 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.test.input">
- <uses-permission android:name="android.permission.MONITOR_INPUT"/>
- <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"/>
- <uses-permission android:name="android.permission.INJECT_EVENTS"/>
- <application android:label="InputTest">
+ <uses-permission android:name="android.permission.INJECT_EVENTS"/>
+ <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL"/>
+ <uses-permission android:name="android.permission.MONITOR_INPUT"/>
+ <uses-permission android:name="android.permission.READ_DEVICE_CONFIG"/>
+ <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"/>
+
+ <application android:label="InputTest" android:debuggable="true">
<activity android:name=".UnresponsiveGestureMonitorActivity"
android:label="Unresponsive gesture monitor"
diff --git a/tests/Input/OWNERS b/tests/Input/OWNERS
index d701f23..3cffce9 100644
--- a/tests/Input/OWNERS
+++ b/tests/Input/OWNERS
@@ -1 +1,2 @@
+# Bug component: 136048
include /core/java/android/hardware/input/OWNERS
diff --git a/services/tests/servicestests/res/raw/dummy_keyboard_layout.kcm b/tests/Input/res/raw/dummy_keyboard_layout.kcm
similarity index 100%
rename from services/tests/servicestests/res/raw/dummy_keyboard_layout.kcm
rename to tests/Input/res/raw/dummy_keyboard_layout.kcm
diff --git a/services/tests/servicestests/res/raw/input_port_associations.xml b/tests/Input/res/raw/input_port_associations.xml
similarity index 100%
rename from services/tests/servicestests/res/raw/input_port_associations.xml
rename to tests/Input/res/raw/input_port_associations.xml
diff --git a/services/tests/servicestests/res/raw/input_port_associations_bad_displayport.xml b/tests/Input/res/raw/input_port_associations_bad_displayport.xml
similarity index 100%
rename from services/tests/servicestests/res/raw/input_port_associations_bad_displayport.xml
rename to tests/Input/res/raw/input_port_associations_bad_displayport.xml
diff --git a/services/tests/servicestests/res/raw/input_port_associations_bad_xml.xml b/tests/Input/res/raw/input_port_associations_bad_xml.xml
similarity index 100%
rename from services/tests/servicestests/res/raw/input_port_associations_bad_xml.xml
rename to tests/Input/res/raw/input_port_associations_bad_xml.xml
diff --git a/services/tests/servicestests/res/xml/keyboard_layouts.xml b/tests/Input/res/xml/keyboard_layouts.xml
similarity index 100%
rename from services/tests/servicestests/res/xml/keyboard_layouts.xml
rename to tests/Input/res/xml/keyboard_layouts.xml
diff --git a/core/tests/coretests/src/android/hardware/input/InputDeviceBatteryListenerTest.kt b/tests/Input/src/android/hardware/input/InputDeviceBatteryListenerTest.kt
similarity index 96%
rename from core/tests/coretests/src/android/hardware/input/InputDeviceBatteryListenerTest.kt
rename to tests/Input/src/android/hardware/input/InputDeviceBatteryListenerTest.kt
index fdcc7c9..90dff47 100644
--- a/core/tests/coretests/src/android/hardware/input/InputDeviceBatteryListenerTest.kt
+++ b/tests/Input/src/android/hardware/input/InputDeviceBatteryListenerTest.kt
@@ -46,7 +46,7 @@
* Tests for [InputManager.InputDeviceBatteryListener].
*
* Build/Install/Run:
- * atest FrameworksCoreTests:InputDeviceBatteryListenerTest
+ * atest InputTests:InputDeviceBatteryListenerTest
*/
@Presubmit
@RunWith(MockitoJUnitRunner::class)
@@ -63,6 +63,7 @@
@Mock
private lateinit var iInputManagerMock: IInputManager
+ private lateinit var inputManagerGlobalSession: InputManagerGlobal.TestSession
@Before
fun setUp() {
@@ -71,7 +72,7 @@
executor = HandlerExecutor(Handler(testLooper.looper))
registeredListener = null
monitoredDevices.clear()
- InputManagerGlobal.resetInstance(iInputManagerMock)
+ inputManagerGlobalSession = InputManagerGlobal.createTestSession(iInputManagerMock)
inputManager = InputManager(context)
`when`(context.getSystemService(Mockito.eq(Context.INPUT_SERVICE)))
.thenReturn(inputManager)
@@ -112,7 +113,9 @@
@After
fun tearDown() {
- InputManagerGlobal.clearInstance()
+ if (this::inputManagerGlobalSession.isInitialized) {
+ inputManagerGlobalSession.close()
+ }
}
private fun notifyBatteryStateChanged(
diff --git a/core/tests/coretests/src/android/hardware/input/InputDeviceLightsManagerTest.java b/tests/Input/src/android/hardware/input/InputDeviceLightsManagerTest.java
similarity index 95%
rename from core/tests/coretests/src/android/hardware/input/InputDeviceLightsManagerTest.java
rename to tests/Input/src/android/hardware/input/InputDeviceLightsManagerTest.java
index 1e505ab..080186e 100644
--- a/core/tests/coretests/src/android/hardware/input/InputDeviceLightsManagerTest.java
+++ b/tests/Input/src/android/hardware/input/InputDeviceLightsManagerTest.java
@@ -43,7 +43,7 @@
import android.util.ArrayMap;
import android.view.InputDevice;
-import androidx.test.InstrumentationRegistry;
+import androidx.test.platform.app.InstrumentationRegistry;
import org.junit.After;
import org.junit.Before;
@@ -63,7 +63,7 @@
* Tests for {@link InputDeviceLightsManager}.
*
* Build/Install/Run:
- * atest FrameworksCoreTests:InputDeviceLightsManagerTest
+ * atest InputTests:InputDeviceLightsManagerTest
*/
@Presubmit
@RunWith(MockitoJUnitRunner.class)
@@ -78,16 +78,18 @@
private InputManager mInputManager;
@Mock private IInputManager mIInputManagerMock;
+ private InputManagerGlobal.TestSession mInputManagerGlobalSession;
@Before
public void setUp() throws Exception {
- final Context context = spy(new ContextWrapper(InstrumentationRegistry.getContext()));
+ final Context context = spy(
+ new ContextWrapper(InstrumentationRegistry.getInstrumentation().getContext()));
when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{DEVICE_ID});
when(mIInputManagerMock.getInputDevice(eq(DEVICE_ID))).thenReturn(
createInputDevice(DEVICE_ID));
- InputManagerGlobal.resetInstance(mIInputManagerMock);
+ mInputManagerGlobalSession = InputManagerGlobal.createTestSession(mIInputManagerMock);
mInputManager = new InputManager(context);
when(context.getSystemService(eq(Context.INPUT_SERVICE))).thenReturn(mInputManager);
@@ -114,7 +116,9 @@
@After
public void tearDown() {
- InputManagerGlobal.clearInstance();
+ if (mInputManagerGlobalSession != null) {
+ mInputManagerGlobalSession.close();
+ }
}
private InputDevice createInputDevice(int id) {
diff --git a/core/tests/coretests/src/android/hardware/input/InputDeviceSensorManagerTest.java b/tests/Input/src/android/hardware/input/InputDeviceSensorManagerTest.java
similarity index 94%
rename from core/tests/coretests/src/android/hardware/input/InputDeviceSensorManagerTest.java
rename to tests/Input/src/android/hardware/input/InputDeviceSensorManagerTest.java
index b33cfdd..0e3c200 100644
--- a/core/tests/coretests/src/android/hardware/input/InputDeviceSensorManagerTest.java
+++ b/tests/Input/src/android/hardware/input/InputDeviceSensorManagerTest.java
@@ -38,7 +38,7 @@
import android.platform.test.annotations.Presubmit;
import android.view.InputDevice;
-import androidx.test.InstrumentationRegistry;
+import androidx.test.platform.app.InstrumentationRegistry;
import com.android.internal.annotations.GuardedBy;
@@ -61,7 +61,7 @@
* Tests for {@link InputDeviceSensorManager}.
*
* Build/Install/Run:
- * atest FrameworksCoreTests:InputDeviceSensorManagerTest
+ * atest InputTests:InputDeviceSensorManagerTest
*/
@Presubmit
@RunWith(MockitoJUnitRunner.class)
@@ -77,11 +77,13 @@
private final Object mLock = new Object();
@Mock private IInputManager mIInputManagerMock;
+ private InputManagerGlobal.TestSession mInputManagerGlobalSession;
@Before
public void setUp() throws Exception {
- final Context context = spy(new ContextWrapper(InstrumentationRegistry.getContext()));
- InputManagerGlobal.resetInstance(mIInputManagerMock);
+ final Context context = spy(
+ new ContextWrapper(InstrumentationRegistry.getInstrumentation().getContext()));
+ mInputManagerGlobalSession = InputManagerGlobal.createTestSession(mIInputManagerMock);
mInputManager = new InputManager(context);
when(context.getSystemService(eq(Context.INPUT_SERVICE))).thenReturn(mInputManager);
@@ -102,7 +104,9 @@
@After
public void tearDown() {
- InputManagerGlobal.clearInstance();
+ if (mInputManagerGlobalSession != null) {
+ mInputManagerGlobalSession.close();
+ }
}
private class InputTestSensorEventListener implements SensorEventListener {
diff --git a/core/tests/coretests/src/android/hardware/input/InputManagerTest.kt b/tests/Input/src/android/hardware/input/InputManagerTest.kt
similarity index 93%
rename from core/tests/coretests/src/android/hardware/input/InputManagerTest.kt
rename to tests/Input/src/android/hardware/input/InputManagerTest.kt
index 2ebe362..152dde9 100644
--- a/core/tests/coretests/src/android/hardware/input/InputManagerTest.kt
+++ b/tests/Input/src/android/hardware/input/InputManagerTest.kt
@@ -41,7 +41,7 @@
* Tests for [InputManager].
*
* Build/Install/Run:
- * atest FrameworksCoreTests:InputManagerTest
+ * atest InputTests:InputManagerTest
*/
@Presubmit
@RunWith(MockitoJUnitRunner::class)
@@ -63,11 +63,12 @@
@Mock
private lateinit var iInputManager: IInputManager
+ private lateinit var inputManagerGlobalSession: InputManagerGlobal.TestSession
@Before
fun setUp() {
context = Mockito.spy(ContextWrapper(ApplicationProvider.getApplicationContext()))
- InputManagerGlobal.resetInstance(iInputManager)
+ inputManagerGlobalSession = InputManagerGlobal.createTestSession(iInputManager)
inputManager = InputManager(context)
`when`(context.getSystemService(eq(Context.INPUT_SERVICE))).thenReturn(inputManager)
`when`(iInputManager.inputDeviceIds).then {
@@ -77,7 +78,9 @@
@After
fun tearDown() {
- InputManagerGlobal.clearInstance()
+ if (this::inputManagerGlobalSession.isInitialized) {
+ inputManagerGlobalSession.close()
+ }
}
private fun notifyDeviceChanged(
diff --git a/core/tests/coretests/src/android/hardware/input/KeyboardBacklightListenerTest.kt b/tests/Input/src/android/hardware/input/KeyboardBacklightListenerTest.kt
similarity index 94%
rename from core/tests/coretests/src/android/hardware/input/KeyboardBacklightListenerTest.kt
rename to tests/Input/src/android/hardware/input/KeyboardBacklightListenerTest.kt
index ce816ab..23135b2 100644
--- a/core/tests/coretests/src/android/hardware/input/KeyboardBacklightListenerTest.kt
+++ b/tests/Input/src/android/hardware/input/KeyboardBacklightListenerTest.kt
@@ -45,7 +45,7 @@
* Tests for [InputManager.KeyboardBacklightListener].
*
* Build/Install/Run:
- * atest FrameworksCoreTests:KeyboardBacklightListenerTest
+ * atest InputTests:KeyboardBacklightListenerTest
*/
@Presubmit
@RunWith(MockitoJUnitRunner::class)
@@ -58,6 +58,7 @@
private lateinit var executor: Executor
private lateinit var context: Context
private lateinit var inputManager: InputManager
+ private lateinit var inputManagerGlobalSession: InputManagerGlobal.TestSession
@Mock
private lateinit var iInputManagerMock: IInputManager
@@ -65,7 +66,7 @@
@Before
fun setUp() {
context = Mockito.spy(ContextWrapper(ApplicationProvider.getApplicationContext()))
- InputManagerGlobal.resetInstance(iInputManagerMock)
+ inputManagerGlobalSession = InputManagerGlobal.createTestSession(iInputManagerMock)
testLooper = TestLooper()
executor = HandlerExecutor(Handler(testLooper.looper))
registeredListener = null
@@ -99,7 +100,9 @@
@After
fun tearDown() {
- InputManagerGlobal.clearInstance()
+ if (this::inputManagerGlobalSession.isInitialized) {
+ inputManagerGlobalSession.close()
+ }
}
private fun notifyKeyboardBacklightChanged(
diff --git a/core/tests/coretests/src/android/hardware/input/VirtualKeyEventTest.java b/tests/Input/src/android/hardware/input/VirtualKeyEventTest.java
similarity index 100%
rename from core/tests/coretests/src/android/hardware/input/VirtualKeyEventTest.java
rename to tests/Input/src/android/hardware/input/VirtualKeyEventTest.java
diff --git a/core/tests/coretests/src/android/hardware/input/VirtualMouseButtonEventTest.java b/tests/Input/src/android/hardware/input/VirtualMouseButtonEventTest.java
similarity index 100%
rename from core/tests/coretests/src/android/hardware/input/VirtualMouseButtonEventTest.java
rename to tests/Input/src/android/hardware/input/VirtualMouseButtonEventTest.java
diff --git a/core/tests/coretests/src/android/hardware/input/VirtualMouseRelativeEventTest.java b/tests/Input/src/android/hardware/input/VirtualMouseRelativeEventTest.java
similarity index 100%
rename from core/tests/coretests/src/android/hardware/input/VirtualMouseRelativeEventTest.java
rename to tests/Input/src/android/hardware/input/VirtualMouseRelativeEventTest.java
diff --git a/core/tests/coretests/src/android/hardware/input/VirtualMouseScrollEventTest.java b/tests/Input/src/android/hardware/input/VirtualMouseScrollEventTest.java
similarity index 100%
rename from core/tests/coretests/src/android/hardware/input/VirtualMouseScrollEventTest.java
rename to tests/Input/src/android/hardware/input/VirtualMouseScrollEventTest.java
diff --git a/core/tests/coretests/src/android/hardware/input/VirtualTouchEventTest.java b/tests/Input/src/android/hardware/input/VirtualTouchEventTest.java
similarity index 100%
rename from core/tests/coretests/src/android/hardware/input/VirtualTouchEventTest.java
rename to tests/Input/src/android/hardware/input/VirtualTouchEventTest.java
diff --git a/services/tests/servicestests/src/com/android/server/input/AmbientKeyboardBacklightControllerTests.kt b/tests/Input/src/com/android/server/input/AmbientKeyboardBacklightControllerTests.kt
similarity index 99%
rename from services/tests/servicestests/src/com/android/server/input/AmbientKeyboardBacklightControllerTests.kt
rename to tests/Input/src/com/android/server/input/AmbientKeyboardBacklightControllerTests.kt
index 98b4628..ad481df 100644
--- a/services/tests/servicestests/src/com/android/server/input/AmbientKeyboardBacklightControllerTests.kt
+++ b/tests/Input/src/com/android/server/input/AmbientKeyboardBacklightControllerTests.kt
@@ -55,7 +55,7 @@
* Tests for {@link AmbientKeyboardBacklightController}.
*
* Build/Install/Run:
- * atest FrameworksServicesTests:AmbientKeyboardBacklightControllerTests
+ * atest InputTests:AmbientKeyboardBacklightControllerTests
*/
@Presubmit
class AmbientKeyboardBacklightControllerTests {
diff --git a/services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt b/tests/Input/src/com/android/server/input/BatteryControllerTests.kt
similarity index 98%
rename from services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt
rename to tests/Input/src/com/android/server/input/BatteryControllerTests.kt
index c36122b..f2724e6 100644
--- a/services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt
+++ b/tests/Input/src/com/android/server/input/BatteryControllerTests.kt
@@ -167,7 +167,7 @@
* Tests for {@link InputDeviceBatteryController}.
*
* Build/Install/Run:
- * atest FrameworksServicesTests:InputDeviceBatteryControllerTests
+ * atest InputTests:InputDeviceBatteryControllerTests
*/
@Presubmit
class BatteryControllerTests {
@@ -198,13 +198,14 @@
private lateinit var context: TestableContext
private lateinit var testLooper: TestLooper
private lateinit var devicesChangedListener: IInputDevicesChangedListener
+ private lateinit var inputManagerGlobalSession: InputManagerGlobal.TestSession
private val deviceGenerationMap = mutableMapOf<Int /*deviceId*/, Int /*generation*/>()
@Before
fun setup() {
context = TestableContext(ApplicationProvider.getApplicationContext())
testLooper = TestLooper()
- InputManagerGlobal.resetInstance(iInputManager)
+ inputManagerGlobalSession = InputManagerGlobal.createTestSession(iInputManager)
val inputManager = InputManager(context)
context.addMockSystemService(InputManager::class.java, inputManager)
`when`(iInputManager.inputDeviceIds).then {
@@ -222,6 +223,13 @@
testLooper.dispatchAll()
}
+ @After
+ fun tearDown() {
+ if (this::inputManagerGlobalSession.isInitialized) {
+ inputManagerGlobalSession.close()
+ }
+ }
+
private fun notifyDeviceChanged(
deviceId: Int,
hasBattery: Boolean = true,
@@ -253,11 +261,6 @@
.adapter.getRemoteDevice(address)
}
- @After
- fun tearDown() {
- InputManagerGlobal.clearInstance()
- }
-
@Test
fun testRegisterAndUnregisterBinderLifecycle() {
val listener = createMockListener()
diff --git a/services/tests/servicestests/src/com/android/server/input/ConfigurationProcessorTest.java b/tests/Input/src/com/android/server/input/ConfigurationProcessorTest.java
similarity index 87%
rename from services/tests/servicestests/src/com/android/server/input/ConfigurationProcessorTest.java
rename to tests/Input/src/com/android/server/input/ConfigurationProcessorTest.java
index 2bd4a3a..9a49d91 100644
--- a/services/tests/servicestests/src/com/android/server/input/ConfigurationProcessorTest.java
+++ b/tests/Input/src/com/android/server/input/ConfigurationProcessorTest.java
@@ -22,7 +22,7 @@
import android.content.Context;
-import androidx.test.InstrumentationRegistry;
+import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
@@ -43,12 +43,12 @@
@Before
public void setUp() throws Exception {
- mContext = InstrumentationRegistry.getContext();
+ mContext = InstrumentationRegistry.getInstrumentation().getContext();
}
@Test
public void testGetInputPortAssociations() {
- final int res = com.android.frameworks.servicestests.R.raw.input_port_associations;
+ final int res = com.android.test.input.R.raw.input_port_associations;
InputStream xml = mContext.getResources().openRawResource(res);
Map<String, Integer> associations = null;
try {
@@ -65,7 +65,7 @@
@Test
public void testGetInputPortAssociationsBadDisplayport() {
final int res =
- com.android.frameworks.servicestests.R.raw.input_port_associations_bad_displayport;
+ com.android.test.input.R.raw.input_port_associations_bad_displayport;
InputStream xml = mContext.getResources().openRawResource(res);
Map<String, Integer> associations = null;
try {
@@ -79,7 +79,7 @@
@Test
public void testGetInputPortAssociationsEmptyConfig() {
- final int res = com.android.frameworks.servicestests.R.raw.input_port_associations_bad_xml;
+ final int res = com.android.test.input.R.raw.input_port_associations_bad_xml;
InputStream xml = mContext.getResources().openRawResource(res);
try {
ConfigurationProcessor.processInputPortAssociations(xml);
diff --git a/services/tests/servicestests/src/com/android/server/input/InputManagerServiceTests.kt b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
similarity index 94%
rename from services/tests/servicestests/src/com/android/server/input/InputManagerServiceTests.kt
rename to tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
index 498776d..93a5582 100644
--- a/services/tests/servicestests/src/com/android/server/input/InputManagerServiceTests.kt
+++ b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
@@ -20,6 +20,8 @@
import android.content.Context
import android.content.ContextWrapper
import android.hardware.display.DisplayViewport
+import android.hardware.input.InputManager
+import android.hardware.input.InputManagerGlobal
import android.os.IInputConstants
import android.os.test.TestLooper
import android.platform.test.annotations.Presubmit
@@ -27,9 +29,10 @@
import android.test.mock.MockContentResolver
import android.view.Display
import android.view.PointerIcon
-import androidx.test.InstrumentationRegistry
+import androidx.test.platform.app.InstrumentationRegistry
import com.android.internal.util.test.FakeSettingsProvider
import com.google.common.truth.Truth.assertThat
+import org.junit.After
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
@@ -59,7 +62,7 @@
* Tests for {@link InputManagerService}.
*
* Build/Install/Run:
- * atest FrameworksServicesTests:InputManagerServiceTests
+ * atest InputTests:InputManagerServiceTests
*/
@Presubmit
class InputManagerServiceTests {
@@ -84,10 +87,11 @@
private lateinit var context: Context
private lateinit var testLooper: TestLooper
private lateinit var contentResolver: MockContentResolver
+ private lateinit var inputManagerGlobalSession: InputManagerGlobal.TestSession
@Before
fun setup() {
- context = spy(ContextWrapper(InstrumentationRegistry.getContext()))
+ context = spy(ContextWrapper(InstrumentationRegistry.getInstrumentation().getContext()))
contentResolver = MockContentResolver(context)
contentResolver.addProvider(Settings.AUTHORITY, FakeSettingsProvider())
whenever(context.contentResolver).thenReturn(contentResolver)
@@ -105,10 +109,22 @@
localService = service!!
}
})
+ inputManagerGlobalSession = InputManagerGlobal.createTestSession(service)
+ val inputManager = InputManager(context)
+ whenever(context.getSystemService(InputManager::class.java)).thenReturn(inputManager)
+ whenever(context.getSystemService(Context.INPUT_SERVICE)).thenReturn(inputManager)
+
assertTrue("Local service must be registered", this::localService.isInitialized)
service.setWindowManagerCallbacks(wmCallbacks)
}
+ @After
+ fun tearDown() {
+ if (this::inputManagerGlobalSession.isInitialized) {
+ inputManagerGlobalSession.close()
+ }
+ }
+
@Test
fun testStart() {
verifyZeroInteractions(native)
diff --git a/services/tests/servicestests/src/com/android/server/input/KeyRemapperTests.kt b/tests/Input/src/com/android/server/input/KeyRemapperTests.kt
similarity index 94%
rename from services/tests/servicestests/src/com/android/server/input/KeyRemapperTests.kt
rename to tests/Input/src/com/android/server/input/KeyRemapperTests.kt
index c10f651..f74fd72 100644
--- a/services/tests/servicestests/src/com/android/server/input/KeyRemapperTests.kt
+++ b/tests/Input/src/com/android/server/input/KeyRemapperTests.kt
@@ -54,7 +54,7 @@
* Tests for {@link KeyRemapper}.
*
* Build/Install/Run:
- * atest FrameworksServicesTests:KeyRemapperTests
+ * atest InputTests:KeyRemapperTests
*/
@Presubmit
class KeyRemapperTests {
@@ -81,6 +81,7 @@
private lateinit var context: Context
private lateinit var dataStore: PersistentDataStore
private lateinit var testLooper: TestLooper
+ private lateinit var inputManagerGlobalSession: InputManagerGlobal.TestSession
@Before
fun setup() {
@@ -103,7 +104,7 @@
dataStore,
testLooper.looper
)
- InputManagerGlobal.resetInstance(iInputManager)
+ inputManagerGlobalSession = InputManagerGlobal.createTestSession(iInputManager)
val inputManager = InputManager(context)
Mockito.`when`(context.getSystemService(Mockito.eq(Context.INPUT_SERVICE)))
.thenReturn(inputManager)
@@ -112,7 +113,9 @@
@After
fun tearDown() {
- InputManagerGlobal.clearInstance()
+ if (this::inputManagerGlobalSession.isInitialized) {
+ inputManagerGlobalSession.close()
+ }
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/input/KeyboardBacklightControllerTests.kt b/tests/Input/src/com/android/server/input/KeyboardBacklightControllerTests.kt
similarity index 98%
rename from services/tests/servicestests/src/com/android/server/input/KeyboardBacklightControllerTests.kt
rename to tests/Input/src/com/android/server/input/KeyboardBacklightControllerTests.kt
index 3f4a4fb..59aa96c 100644
--- a/services/tests/servicestests/src/com/android/server/input/KeyboardBacklightControllerTests.kt
+++ b/tests/Input/src/com/android/server/input/KeyboardBacklightControllerTests.kt
@@ -87,7 +87,7 @@
* Tests for {@link KeyboardBacklightController}.
*
* Build/Install/Run:
- * atest FrameworksServicesTests:KeyboardBacklightControllerTests
+ * atest InputTests:KeyboardBacklightControllerTests
*/
@Presubmit
class KeyboardBacklightControllerTests {
@@ -111,6 +111,7 @@
private lateinit var context: Context
private lateinit var dataStore: PersistentDataStore
private lateinit var testLooper: TestLooper
+ private lateinit var inputManagerGlobalSession: InputManagerGlobal.TestSession
private var lightColorMap: HashMap<Int, Int> = HashMap()
private var lastBacklightState: KeyboardBacklightState? = null
private var sysfsNodeChanges = 0
@@ -133,7 +134,7 @@
testLooper = TestLooper()
keyboardBacklightController = KeyboardBacklightController(context, native, dataStore,
testLooper.looper, FakeAnimatorFactory(), uEventManager)
- InputManagerGlobal.resetInstance(iInputManager)
+ inputManagerGlobalSession = InputManagerGlobal.createTestSession(iInputManager)
val inputManager = InputManager(context)
`when`(context.getSystemService(eq(Context.INPUT_SERVICE))).thenReturn(inputManager)
`when`(iInputManager.inputDeviceIds).thenReturn(intArrayOf(DEVICE_ID))
@@ -153,7 +154,9 @@
@After
fun tearDown() {
- InputManagerGlobal.clearInstance()
+ if (this::inputManagerGlobalSession.isInitialized) {
+ inputManagerGlobalSession.close()
+ }
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/input/KeyboardLayoutManagerTests.kt b/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt
similarity index 98%
rename from services/tests/servicestests/src/com/android/server/input/KeyboardLayoutManagerTests.kt
rename to tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt
index 55c45df..b6477510 100644
--- a/services/tests/servicestests/src/com/android/server/input/KeyboardLayoutManagerTests.kt
+++ b/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt
@@ -37,6 +37,7 @@
import android.view.inputmethod.InputMethodSubtype
import androidx.test.core.R
import androidx.test.core.app.ApplicationProvider
+import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotEquals
import org.junit.Assert.assertNull
@@ -77,7 +78,7 @@
* Tests for {@link Default UI} and {@link New UI}.
*
* Build/Install/Run:
- * atest FrameworksServicesTests:KeyboardLayoutManagerTests
+ * atest InputTests:KeyboardLayoutManagerTests
*/
@Presubmit
class KeyboardLayoutManagerTests {
@@ -120,6 +121,7 @@
private lateinit var context: Context
private lateinit var dataStore: PersistentDataStore
private lateinit var testLooper: TestLooper
+ private lateinit var inputManagerGlobalSession: InputManagerGlobal.TestSession
// Devices
private lateinit var keyboardDevice: InputDevice
@@ -130,6 +132,7 @@
@Before
fun setup() {
context = Mockito.spy(ContextWrapper(ApplicationProvider.getApplicationContext()))
+ inputManagerGlobalSession = InputManagerGlobal.createTestSession(iInputManager)
dataStore = PersistentDataStore(object : PersistentDataStore.Injector() {
override fun openRead(): InputStream? {
throw FileNotFoundException()
@@ -148,8 +151,14 @@
setupIme()
}
+ @After
+ fun tearDown() {
+ if (this::inputManagerGlobalSession.isInitialized) {
+ inputManagerGlobalSession.close()
+ }
+ }
+
private fun setupInputDevices() {
- InputManagerGlobal.resetInstance(iInputManager)
val inputManager = InputManager(context)
Mockito.`when`(context.getSystemService(Mockito.eq(Context.INPUT_SERVICE)))
.thenReturn(inputManager)
diff --git a/services/tests/servicestests/src/com/android/server/input/KeyboardMetricsCollectorTests.kt b/tests/Input/src/com/android/server/input/KeyboardMetricsCollectorTests.kt
similarity index 72%
rename from services/tests/servicestests/src/com/android/server/input/KeyboardMetricsCollectorTests.kt
rename to tests/Input/src/com/android/server/input/KeyboardMetricsCollectorTests.kt
index c9724a3..56b0b9a 100644
--- a/services/tests/servicestests/src/com/android/server/input/KeyboardMetricsCollectorTests.kt
+++ b/tests/Input/src/com/android/server/input/KeyboardMetricsCollectorTests.kt
@@ -48,17 +48,17 @@
private fun createImeSubtype(
imeSubtypeId: Int,
- languageTag: String,
+ languageTag: ULocale?,
layoutType: String
): InputMethodSubtype =
InputMethodSubtype.InputMethodSubtypeBuilder().setSubtypeId(imeSubtypeId)
- .setPhysicalKeyboardHint(ULocale.forLanguageTag(languageTag), layoutType).build()
+ .setPhysicalKeyboardHint(languageTag, layoutType).build()
/**
* Tests for {@link KeyboardMetricsCollector}.
*
* Build/Install/Run:
- * atest FrameworksServicesTests:KeyboardMetricsCollectorTests
+ * atest InputTests:KeyboardMetricsCollectorTests
*/
@Presubmit
class KeyboardMetricsCollectorTests {
@@ -95,7 +95,8 @@
null,
null
)
- ).addLayoutSelection(createImeSubtype(1, "en-US", "qwerty"), null, 123).build()
+ ).addLayoutSelection(createImeSubtype(1, ULocale.forLanguageTag("en-US"), "qwerty"),
+ null, 123).build()
}
}
@@ -111,15 +112,19 @@
)
)
val event = builder.addLayoutSelection(
- createImeSubtype(1, "en-US", "qwerty"),
+ createImeSubtype(1, ULocale.forLanguageTag("en-US"), "qwerty"),
KeyboardLayout(null, "English(US)(Qwerty)", null, 0, null, 0, 0, 0),
KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD
).addLayoutSelection(
- createImeSubtype(2, "en-US", "azerty"),
- null,
+ createImeSubtype(2, ULocale.forLanguageTag("en-US"), "azerty"),
+ null, // Default layout type
KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_USER
).addLayoutSelection(
- createImeSubtype(3, "en-US", "qwerty"),
+ createImeSubtype(3, ULocale.forLanguageTag("en-US"), "qwerty"),
+ KeyboardLayout(null, "German", null, 0, null, 0, 0, 0),
+ KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEVICE
+ ).addLayoutSelection(
+ createImeSubtype(4, null, "qwerty"), // Default language tag
KeyboardLayout(null, "German", null, 0, null, 0, 0, 0),
KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEVICE
).setIsFirstTimeConfiguration(true).build()
@@ -137,43 +142,62 @@
assertTrue(event.isFirstConfiguration)
assertEquals(
- "KeyboardConfigurationEvent should contain 3 configurations provided",
- 3,
+ "KeyboardConfigurationEvent should contain 4 configurations provided",
+ 4,
event.layoutConfigurations.size
)
assertExpectedLayoutConfiguration(
event.layoutConfigurations[0],
+ "de-CH",
+ KeyboardLayout.LayoutType.getLayoutTypeEnumValue("qwertz"),
+ "English(US)(Qwerty)",
+ KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD,
"en-US",
KeyboardLayout.LayoutType.getLayoutTypeEnumValue("qwerty"),
- "English(US)(Qwerty)",
- KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD
)
assertExpectedLayoutConfiguration(
event.layoutConfigurations[1],
+ "de-CH",
+ KeyboardLayout.LayoutType.getLayoutTypeEnumValue("qwertz"),
+ KeyboardMetricsCollector.DEFAULT_LAYOUT,
+ KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_USER,
"en-US",
KeyboardLayout.LayoutType.getLayoutTypeEnumValue("azerty"),
- KeyboardMetricsCollector.DEFAULT_LAYOUT,
- KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_USER
)
assertExpectedLayoutConfiguration(
event.layoutConfigurations[2],
"de-CH",
KeyboardLayout.LayoutType.getLayoutTypeEnumValue("qwertz"),
"German",
- KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEVICE
+ KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEVICE,
+ "en-US",
+ KeyboardLayout.LayoutType.getLayoutTypeEnumValue("qwerty"),
+ )
+ assertExpectedLayoutConfiguration(
+ event.layoutConfigurations[3],
+ "de-CH",
+ KeyboardLayout.LayoutType.getLayoutTypeEnumValue("qwertz"),
+ "German",
+ KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEVICE,
+ KeyboardMetricsCollector.DEFAULT_LANGUAGE_TAG,
+ KeyboardLayout.LayoutType.getLayoutTypeEnumValue("qwerty"),
)
}
private fun assertExpectedLayoutConfiguration(
configuration: KeyboardMetricsCollector.LayoutConfiguration,
- expectedLanguageTag: String,
- expectedLayoutType: Int,
+ expectedKeyboardLanguageTag: String,
+ expectedKeyboardLayoutType: Int,
expectedSelectedLayout: String,
- expectedLayoutSelectionCriteria: Int
+ expectedLayoutSelectionCriteria: Int,
+ expectedImeLanguageTag: String,
+ expectedImeLayoutType: Int
) {
- assertEquals(expectedLanguageTag, configuration.keyboardLanguageTag)
- assertEquals(expectedLayoutType, configuration.keyboardLayoutType)
+ assertEquals(expectedKeyboardLanguageTag, configuration.keyboardLanguageTag)
+ assertEquals(expectedKeyboardLayoutType, configuration.keyboardLayoutType)
assertEquals(expectedSelectedLayout, configuration.keyboardLayoutName)
assertEquals(expectedLayoutSelectionCriteria, configuration.layoutSelectionCriteria)
+ assertEquals(expectedImeLanguageTag, configuration.imeLanguageTag)
+ assertEquals(expectedImeLayoutType, configuration.imeLayoutType)
}
-}
\ No newline at end of file
+}
diff --git a/tests/InputMethodStressTest/TEST_MAPPING b/tests/InputMethodStressTest/TEST_MAPPING
index 06e2ce8..d982dd8 100644
--- a/tests/InputMethodStressTest/TEST_MAPPING
+++ b/tests/InputMethodStressTest/TEST_MAPPING
@@ -1,5 +1,5 @@
{
- "presubmit-large": [
+ "postsubmit": [
{
"name": "InputMethodStressTest"
}
diff --git a/tests/Internal/src/android/service/wallpaper/OWNERS b/tests/Internal/src/android/service/wallpaper/OWNERS
new file mode 100644
index 0000000..5a26d0e
--- /dev/null
+++ b/tests/Internal/src/android/service/wallpaper/OWNERS
@@ -0,0 +1,4 @@
+dupin@google.com
+santie@google.com
+pomini@google.com
+poultney@google.com
\ No newline at end of file
diff --git a/tests/Internal/src/android/service/wallpaper/WallpaperServiceTest.java b/tests/Internal/src/android/service/wallpaper/WallpaperServiceTest.java
index 153ca79..0c5e8d48 100644
--- a/tests/Internal/src/android/service/wallpaper/WallpaperServiceTest.java
+++ b/tests/Internal/src/android/service/wallpaper/WallpaperServiceTest.java
@@ -85,4 +85,17 @@
assertEquals("onAmbientModeChanged should have been called", 2, zoomChangedCount[0]);
}
+ @Test
+ public void testNotifyColorsOfDestroyedEngine_doesntCrash() {
+ WallpaperService service = new WallpaperService() {
+ @Override
+ public Engine onCreateEngine() {
+ return new Engine();
+ }
+ };
+ WallpaperService.Engine engine = service.onCreateEngine();
+ engine.detach();
+
+ engine.notifyColorsChanged();
+ }
}
diff --git a/tests/StagedInstallTest/OWNERS b/tests/StagedInstallTest/OWNERS
index aac68e9..d7301dc 100644
--- a/tests/StagedInstallTest/OWNERS
+++ b/tests/StagedInstallTest/OWNERS
@@ -1,3 +1,5 @@
+# Bug component: 36137
+
include /services/core/java/com/android/server/pm/OWNERS
dariofreni@google.com
diff --git a/tests/SurfaceViewBufferTests/AndroidManifest.xml b/tests/SurfaceViewBufferTests/AndroidManifest.xml
index c910ecd..78415e8 100644
--- a/tests/SurfaceViewBufferTests/AndroidManifest.xml
+++ b/tests/SurfaceViewBufferTests/AndroidManifest.xml
@@ -43,7 +43,7 @@
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
- <service android:name="android.view.cts.surfacevalidator.LocalMediaProjectionService"
+ <service android:name="com.android.test.LocalMediaProjectionService"
android:foregroundServiceType="mediaProjection"
android:enabled="true">
</service>
diff --git a/tests/SurfaceViewBufferTests/src/com/android/test/LocalMediaProjectionService.java b/tests/SurfaceViewBufferTests/src/com/android/test/LocalMediaProjectionService.java
new file mode 100644
index 0000000..7339a6b8
--- /dev/null
+++ b/tests/SurfaceViewBufferTests/src/com/android/test/LocalMediaProjectionService.java
@@ -0,0 +1,100 @@
+/*
+ * 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.test;
+
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.drawable.Icon;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
+
+public class LocalMediaProjectionService extends Service {
+
+ private Bitmap mTestBitmap;
+
+ private static final String NOTIFICATION_CHANNEL_ID = "Surfacevalidator";
+ private static final String CHANNEL_NAME = "ProjectionService";
+
+ static final int MSG_START_FOREGROUND_DONE = 1;
+ static final String EXTRA_MESSENGER = "messenger";
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ startForeground(intent);
+ return super.onStartCommand(intent, flags, startId);
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return null;
+ }
+
+ @Override
+ public void onDestroy() {
+ if (mTestBitmap != null) {
+ mTestBitmap.recycle();
+ mTestBitmap = null;
+ }
+ super.onDestroy();
+ }
+
+ private Icon createNotificationIcon() {
+ mTestBitmap = Bitmap.createBitmap(50, 50, Bitmap.Config.ARGB_8888);
+ final Canvas canvas = new Canvas(mTestBitmap);
+ canvas.drawColor(Color.BLUE);
+ return Icon.createWithBitmap(mTestBitmap);
+ }
+
+ private void startForeground(Intent intent) {
+ final NotificationChannel channel = new NotificationChannel(NOTIFICATION_CHANNEL_ID,
+ CHANNEL_NAME, NotificationManager.IMPORTANCE_NONE);
+ channel.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
+
+ final NotificationManager notificationManager =
+ (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+ notificationManager.createNotificationChannel(channel);
+
+ final Notification.Builder notificationBuilder =
+ new Notification.Builder(this, NOTIFICATION_CHANNEL_ID);
+
+ final Notification notification = notificationBuilder.setOngoing(true)
+ .setContentTitle("App is running")
+ .setSmallIcon(createNotificationIcon())
+ .setCategory(Notification.CATEGORY_SERVICE)
+ .setContentText("Context")
+ .build();
+
+ startForeground(2, notification);
+
+ final Messenger messenger = intent.getParcelableExtra(EXTRA_MESSENGER);
+ final Message msg = Message.obtain();
+ msg.what = MSG_START_FOREGROUND_DONE;
+ try {
+ messenger.send(msg);
+ } catch (RemoteException e) {
+ }
+ }
+
+}
diff --git a/tests/SurfaceViewBufferTests/src/com/android/test/ScreenRecordTestBase.kt b/tests/SurfaceViewBufferTests/src/com/android/test/ScreenRecordTestBase.kt
index df3d30e..e80dd8e 100644
--- a/tests/SurfaceViewBufferTests/src/com/android/test/ScreenRecordTestBase.kt
+++ b/tests/SurfaceViewBufferTests/src/com/android/test/ScreenRecordTestBase.kt
@@ -19,7 +19,6 @@
import android.content.Context
import android.content.Intent
import android.graphics.Rect
-import android.server.wm.WindowManagerState.getLogicalDisplaySize
import android.view.cts.surfacevalidator.CapturedActivity
import android.view.cts.surfacevalidator.ISurfaceValidatorTestCase
import android.view.cts.surfacevalidator.PixelChecker
@@ -44,8 +43,6 @@
mActivity = mActivityRule.launchActivity(Intent())
lateinit var surfaceReadyLatch: CountDownLatch
runOnUiThread {
- it.dismissPermissionDialog()
- it.setLogicalDisplaySize(getLogicalDisplaySize())
surfaceReadyLatch = it.addSurfaceView(defaultBufferSize)
}
surfaceReadyLatch.await()
diff --git a/tests/testables/src/android/testing/TestableLooper.java b/tests/testables/src/android/testing/TestableLooper.java
index edd6dd3..82e40b1 100644
--- a/tests/testables/src/android/testing/TestableLooper.java
+++ b/tests/testables/src/android/testing/TestableLooper.java
@@ -32,6 +32,7 @@
import java.lang.annotation.Target;
import java.lang.reflect.Field;
import java.util.Map;
+import java.util.concurrent.atomic.AtomicBoolean;
/**
* This is a wrapper around {@link TestLooperManager} to make it easier to manage
@@ -55,7 +56,6 @@
private MessageHandler mMessageHandler;
private Handler mHandler;
- private Runnable mEmptyMessage;
private TestLooperManager mQueueWrapper;
static {
@@ -121,8 +121,12 @@
* @param num Number of messages to parse
*/
public int processMessages(int num) {
+ return processMessagesInternal(num, null);
+ }
+
+ private int processMessagesInternal(int num, Runnable barrierRunnable) {
for (int i = 0; i < num; i++) {
- if (!parseMessageInt()) {
+ if (!processSingleMessage(barrierRunnable)) {
return i + 1;
}
}
@@ -130,6 +134,27 @@
}
/**
+ * Process up to a certain number of messages, not blocking if the queue has less messages than
+ * that
+ * @param num the maximum number of messages to process
+ * @return the number of messages processed. This will be at most {@code num}.
+ */
+
+ public int processMessagesNonBlocking(int num) {
+ final AtomicBoolean reachedBarrier = new AtomicBoolean(false);
+ Runnable barrierRunnable = () -> {
+ reachedBarrier.set(true);
+ };
+ mHandler.post(barrierRunnable);
+ waitForMessage(mQueueWrapper, mHandler, barrierRunnable);
+ try {
+ return processMessagesInternal(num, barrierRunnable) + (reachedBarrier.get() ? -1 : 0);
+ } finally {
+ mHandler.removeCallbacks(barrierRunnable);
+ }
+ }
+
+ /**
* Process messages in the queue until no more are found.
*/
public void processAllMessages() {
@@ -165,19 +190,20 @@
private int processQueuedMessages() {
int count = 0;
- mEmptyMessage = () -> { };
- mHandler.post(mEmptyMessage);
- waitForMessage(mQueueWrapper, mHandler, mEmptyMessage);
- while (parseMessageInt()) count++;
+ Runnable barrierRunnable = () -> { };
+ mHandler.post(barrierRunnable);
+ waitForMessage(mQueueWrapper, mHandler, barrierRunnable);
+ while (processSingleMessage(barrierRunnable)) count++;
return count;
}
- private boolean parseMessageInt() {
+ private boolean processSingleMessage(Runnable barrierRunnable) {
try {
Message result = mQueueWrapper.next();
if (result != null) {
// This is a break message.
- if (result.getCallback() == mEmptyMessage) {
+ if (result.getCallback() == barrierRunnable) {
+ mQueueWrapper.execute(result);
mQueueWrapper.recycle(result);
return false;
}
diff --git a/tests/testables/tests/src/android/testing/TestableLooperTest.java b/tests/testables/tests/src/android/testing/TestableLooperTest.java
index 0f491b8..a02eb6b 100644
--- a/tests/testables/tests/src/android/testing/TestableLooperTest.java
+++ b/tests/testables/tests/src/android/testing/TestableLooperTest.java
@@ -27,12 +27,6 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.InOrder;
-
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
@@ -40,6 +34,11 @@
import android.testing.TestableLooper.MessageHandler;
import android.testing.TestableLooper.RunWithLooper;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InOrder;
+
@SmallTest
@RunWith(AndroidTestingRunner.class)
@RunWithLooper
@@ -240,4 +239,33 @@
inOrder.verify(handler).dispatchMessage(messageC);
}
+ @Test
+ public void testProcessMessagesNonBlocking_onlyArgNumber() {
+ Handler h = new Handler(mTestableLooper.getLooper());
+ Runnable r = mock(Runnable.class);
+
+ h.post(r);
+ h.post(r);
+ h.post(r);
+
+ int processed = mTestableLooper.processMessagesNonBlocking(2);
+
+ verify(r, times(2)).run();
+ assertEquals(2, processed);
+ }
+
+ @Test
+ public void testProcessMessagesNonBlocking_lessMessagesThanArg() {
+ Handler h = new Handler(mTestableLooper.getLooper());
+ Runnable r = mock(Runnable.class);
+
+ h.post(r);
+ h.post(r);
+ h.post(r);
+
+ int processed = mTestableLooper.processMessagesNonBlocking(5);
+
+ verify(r, times(3)).run();
+ assertEquals(3, processed);
+ }
}
diff --git a/tools/aapt2/Android.bp b/tools/aapt2/Android.bp
index 0d6dc35..7323b0f 100644
--- a/tools/aapt2/Android.bp
+++ b/tools/aapt2/Android.bp
@@ -47,6 +47,7 @@
"-Wno-missing-field-initializers",
"-fno-exceptions",
"-fno-rtti",
+ "-Wno-deprecated-declarations",
],
target: {
windows: {
diff --git a/tools/aapt2/LoadedApk.h b/tools/aapt2/LoadedApk.h
index 4cd7eae..27c354a 100644
--- a/tools/aapt2/LoadedApk.h
+++ b/tools/aapt2/LoadedApk.h
@@ -40,10 +40,8 @@
};
// Info about an APK loaded in memory.
-class LoadedApk {
+class LoadedApk final {
public:
- virtual ~LoadedApk() = default;
-
// Loads both binary and proto APKs from disk.
static std::unique_ptr<LoadedApk> LoadApkFromPath(android::StringPiece path,
android::IDiagnostics* diag);
@@ -96,8 +94,8 @@
* Writes the APK on disk at the given path, while also removing the resource
* files that are not referenced in the resource table.
*/
- virtual bool WriteToArchive(IAaptContext* context, const TableFlattenerOptions& options,
- IArchiveWriter* writer);
+ bool WriteToArchive(IAaptContext* context, const TableFlattenerOptions& options,
+ IArchiveWriter* writer);
/**
* Writes the APK on disk at the given path, while also removing the resource files that are not
@@ -108,9 +106,9 @@
* original manifest will be written. The manifest is only required if the contents of the new APK
* have been modified in a way that require the AndroidManifest.xml to also be modified.
*/
- virtual bool WriteToArchive(IAaptContext* context, ResourceTable* split_table,
- const TableFlattenerOptions& options, FilterChain* filters,
- IArchiveWriter* writer, xml::XmlResource* manifest = nullptr);
+ bool WriteToArchive(IAaptContext* context, ResourceTable* split_table,
+ const TableFlattenerOptions& options, FilterChain* filters,
+ IArchiveWriter* writer, xml::XmlResource* manifest = nullptr);
/** Loads the file as an xml document. */
std::unique_ptr<xml::XmlResource> LoadXml(const std::string& file_path,
diff --git a/tools/aapt2/SdkConstants.cpp b/tools/aapt2/SdkConstants.cpp
index a766bd4..83f2eb3 100644
--- a/tools/aapt2/SdkConstants.cpp
+++ b/tools/aapt2/SdkConstants.cpp
@@ -16,20 +16,24 @@
#include "SdkConstants.h"
+#include <stdint.h>
+
#include <algorithm>
#include <string>
-#include <unordered_set>
-#include <vector>
+#include <string_view>
using android::StringPiece;
+using namespace std::literals;
namespace aapt {
-static ApiVersion sDevelopmentSdkLevel = 10000;
-static const auto sDevelopmentSdkCodeNames = std::unordered_set<StringPiece>(
- {"Q", "R", "S", "Sv2", "Tiramisu", "UpsideDownCake", "VanillaIceCream"});
+static constexpr ApiVersion sDevelopmentSdkLevel = 10000;
+static constexpr StringPiece sDevelopmentSdkCodeNames[] = {
+ "Q"sv, "R"sv, "S"sv, "Sv2"sv, "Tiramisu"sv, "UpsideDownCake"sv, "VanillaIceCream"sv};
-static const std::vector<std::pair<uint16_t, ApiVersion>> sAttrIdMap = {
+static constexpr auto sPrivacySandboxSuffix = "PrivacySandbox"sv;
+
+static constexpr std::pair<uint16_t, ApiVersion> sAttrIdMap[] = {
{0x021c, 1},
{0x021d, 2},
{0x0269, SDK_CUPCAKE},
@@ -62,25 +66,37 @@
{0x064c, SDK_S_V2},
};
-static bool less_entry_id(const std::pair<uint16_t, ApiVersion>& p, uint16_t entryId) {
- return p.first < entryId;
-}
+static_assert(std::is_sorted(std::begin(sAttrIdMap), std::end(sAttrIdMap),
+ [](auto&& l, auto&& r) { return l.first < r.first; }));
ApiVersion FindAttributeSdkLevel(const ResourceId& id) {
if (id.package_id() != 0x01 || id.type_id() != 0x01) {
return 0;
}
- auto iter = std::lower_bound(sAttrIdMap.begin(), sAttrIdMap.end(), id.entry_id(), less_entry_id);
- if (iter == sAttrIdMap.end()) {
+ const auto it =
+ std::lower_bound(std::begin(sAttrIdMap), std::end(sAttrIdMap), id.entry_id(),
+ [](const auto& pair, uint16_t entryId) { return pair.first < entryId; });
+ if (it == std::end(sAttrIdMap)) {
return SDK_LOLLIPOP_MR1;
}
- return iter->second;
+ return it->second;
}
std::optional<ApiVersion> GetDevelopmentSdkCodeNameVersion(StringPiece code_name) {
- return (sDevelopmentSdkCodeNames.find(code_name) == sDevelopmentSdkCodeNames.end())
- ? std::optional<ApiVersion>()
- : sDevelopmentSdkLevel;
+ const auto it =
+ std::find_if(std::begin(sDevelopmentSdkCodeNames), std::end(sDevelopmentSdkCodeNames),
+ [code_name](const auto& item) { return code_name.starts_with(item); });
+ if (it == std::end(sDevelopmentSdkCodeNames)) {
+ return {};
+ }
+ if (code_name.size() == it->size()) {
+ return sDevelopmentSdkLevel;
+ }
+ if (code_name.size() == it->size() + sPrivacySandboxSuffix.size() &&
+ code_name.ends_with(sPrivacySandboxSuffix)) {
+ return sDevelopmentSdkLevel;
+ }
+ return {};
}
} // namespace aapt
diff --git a/tools/aapt2/SdkConstants_test.cpp b/tools/aapt2/SdkConstants_test.cpp
index 61f4d71..0f645ff 100644
--- a/tools/aapt2/SdkConstants_test.cpp
+++ b/tools/aapt2/SdkConstants_test.cpp
@@ -28,4 +28,24 @@
EXPECT_EQ(0, FindAttributeSdkLevel(ResourceId(0x7f010345)));
}
+TEST(SdkConstantsTest, GetDevelopmentSdkCodeNameVersionValid) {
+ EXPECT_EQ(std::optional<ApiVersion>(10000), GetDevelopmentSdkCodeNameVersion("Q"));
+ EXPECT_EQ(std::optional<ApiVersion>(10000), GetDevelopmentSdkCodeNameVersion("VanillaIceCream"));
+}
+
+TEST(SdkConstantsTest, GetDevelopmentSdkCodeNameVersionPrivacySandbox) {
+ EXPECT_EQ(std::optional<ApiVersion>(10000), GetDevelopmentSdkCodeNameVersion("QPrivacySandbox"));
+ EXPECT_EQ(std::optional<ApiVersion>(10000),
+ GetDevelopmentSdkCodeNameVersion("VanillaIceCreamPrivacySandbox"));
+}
+
+TEST(SdkConstantsTest, GetDevelopmentSdkCodeNameVersionInvalid) {
+ EXPECT_EQ(std::optional<ApiVersion>(), GetDevelopmentSdkCodeNameVersion("A"));
+ EXPECT_EQ(std::optional<ApiVersion>(), GetDevelopmentSdkCodeNameVersion("Sv3"));
+ EXPECT_EQ(std::optional<ApiVersion>(),
+ GetDevelopmentSdkCodeNameVersion("VanillaIceCream_PrivacySandbox"));
+ EXPECT_EQ(std::optional<ApiVersion>(), GetDevelopmentSdkCodeNameVersion("PrivacySandbox"));
+ EXPECT_EQ(std::optional<ApiVersion>(), GetDevelopmentSdkCodeNameVersion("QQQQQQQQQQQQQQQ"));
+}
+
} // namespace aapt
diff --git a/tools/aapt2/optimize/Obfuscator.cpp b/tools/aapt2/optimize/Obfuscator.cpp
index 8f12f735..903cdf8 100644
--- a/tools/aapt2/optimize/Obfuscator.cpp
+++ b/tools/aapt2/optimize/Obfuscator.cpp
@@ -40,9 +40,9 @@
collapse_key_stringpool_(optimizeOptions.table_flattener_options.collapse_key_stringpool) {
}
-std::string ShortenFileName(android::StringPiece file_path, int output_length) {
+std::string Obfuscator::ShortenFileName(android::StringPiece file_path, int output_length) {
std::size_t hash_num = std::hash<android::StringPiece>{}(file_path);
- std::string result = "";
+ std::string result;
// Convert to (modified) base64 so that it is a proper file path.
for (int i = 0; i < output_length; i++) {
uint8_t sextet = hash_num & 0x3f;
@@ -52,10 +52,33 @@
return result;
}
+static std::string RenameDisallowedFileNames(const std::string& file_name) {
+ // We are renaming shortened file names to make sure they not a reserved file name in Windows.
+ // See: https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file. We are renaming
+ // "COM" and "LPT" too because we are appending a number in case of hash collisions; "COM1",
+ // "COM2", etc. are reserved names.
+ static const char* const reserved_windows_names[] = {"CON", "PRN", "AUX", "NUL", "COM", "LPT"};
+ if (file_name.length() == 3) {
+ // Need to convert the file name to uppercase as Windows is case insensitive. E.g., "NuL",
+ // "nul", and "NUl" are also reserved.
+ std::string result_upper_cased(3, 0);
+ std::transform(file_name.begin(), file_name.end(), result_upper_cased.begin(),
+ [](unsigned char c) { return std::toupper(c); });
+ for (auto reserved_windows_name : reserved_windows_names) {
+ if (result_upper_cased == reserved_windows_name) {
+ // Simple solution to make it a non-reserved name is to add an underscore
+ return "_" + file_name;
+ }
+ }
+ }
+
+ return file_name;
+}
+
// Return the optimal hash length such that at most 10% of resources collide in
// their shortened path.
// Reference: http://matt.might.net/articles/counting-hash-collisions/
-int OptimalShortenedLength(int num_resources) {
+static int OptimalShortenedLength(int num_resources) {
if (num_resources > 4000) {
return 3;
} else {
@@ -63,8 +86,8 @@
}
}
-std::string GetShortenedPath(android::StringPiece shortened_filename,
- android::StringPiece extension, int collision_count) {
+static std::string GetShortenedPath(android::StringPiece shortened_filename,
+ android::StringPiece extension, int collision_count) {
std::string shortened_path = std::string("res/") += shortened_filename;
if (collision_count > 0) {
shortened_path += std::to_string(collision_count);
@@ -82,9 +105,9 @@
}
};
-static bool HandleShortenFilePaths(ResourceTable* table,
- std::map<std::string, std::string>& shortened_path_map,
- const std::set<ResourceName>& path_shorten_exemptions) {
+bool Obfuscator::HandleShortenFilePaths(ResourceTable* table,
+ std::map<std::string, std::string>& shortened_path_map,
+ const std::set<ResourceName>& path_shorten_exemptions) {
// used to detect collisions
std::unordered_set<std::string> shortened_paths;
std::set<FileReference*, PathComparator> file_refs;
@@ -112,7 +135,8 @@
// Android detects ColorStateLists via pathname, skip res/color*
if (util::StartsWith(res_subdir, "res/color")) continue;
- std::string shortened_filename = ShortenFileName(*file_ref->path, num_chars);
+ std::string shortened_filename =
+ RenameDisallowedFileNames(ShortenFileName(*file_ref->path, num_chars));
int collision_count = 0;
std::string shortened_path = GetShortenedPath(shortened_filename, extension, collision_count);
while (shortened_paths.find(shortened_path) != shortened_paths.end()) {
diff --git a/tools/aapt2/optimize/Obfuscator.h b/tools/aapt2/optimize/Obfuscator.h
index 5ccf5438..79d7e08 100644
--- a/tools/aapt2/optimize/Obfuscator.h
+++ b/tools/aapt2/optimize/Obfuscator.h
@@ -53,7 +53,14 @@
const ResourceNamedType& type_name, const ResourceTableEntryView& entry,
const android::base::function_ref<void(Result, const ResourceName&)> onObfuscate);
+ protected:
+ virtual std::string ShortenFileName(android::StringPiece file_path, int output_length);
+
private:
+ bool HandleShortenFilePaths(ResourceTable* table,
+ std::map<std::string, std::string>& shortened_path_map,
+ const std::set<ResourceName>& path_shorten_exemptions);
+
TableFlattenerOptions& options_;
const bool shorten_resource_paths_;
const bool collapse_key_stringpool_;
diff --git a/tools/aapt2/optimize/Obfuscator_test.cpp b/tools/aapt2/optimize/Obfuscator_test.cpp
index b3a915c..c3429e0 100644
--- a/tools/aapt2/optimize/Obfuscator_test.cpp
+++ b/tools/aapt2/optimize/Obfuscator_test.cpp
@@ -19,6 +19,7 @@
#include <map>
#include <memory>
#include <string>
+#include <utility>
#include "ResourceTable.h"
#include "android-base/file.h"
@@ -26,6 +27,7 @@
using ::aapt::test::GetValue;
using ::testing::AnyOf;
+using ::testing::Contains;
using ::testing::Eq;
using ::testing::HasSubstr;
using ::testing::IsFalse;
@@ -33,6 +35,10 @@
using ::testing::Not;
using ::testing::NotNull;
+namespace aapt {
+
+namespace {
+
android::StringPiece GetExtension(android::StringPiece path) {
auto iter = std::find(path.begin(), path.end(), '.');
return android::StringPiece(iter, path.end() - iter);
@@ -45,7 +51,22 @@
}
}
-namespace aapt {
+class FakeObfuscator : public Obfuscator {
+ public:
+ explicit FakeObfuscator(OptimizeOptions& optimize_options,
+ const std::unordered_map<std::string, std::string>& shortened_name_map)
+ : Obfuscator(optimize_options), shortened_name_map_(shortened_name_map) {
+ }
+
+ protected:
+ std::string ShortenFileName(android::StringPiece file_path, int output_length) override {
+ return shortened_name_map_[std::string(file_path)];
+ }
+
+ private:
+ std::unordered_map<std::string, std::string> shortened_name_map_;
+ DISALLOW_COPY_AND_ASSIGN(FakeObfuscator);
+};
TEST(ObfuscatorTest, FileRefPathsChangedInResourceTable) {
std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
@@ -127,7 +148,7 @@
EXPECT_THAT(path_map.find("res/drawables/xmlfile2.xml"), Not(Eq(path_map.end())));
FileReference* ref = GetValue<FileReference>(table.get(), "android:drawable/xmlfile");
- EXPECT_THAT(ref, NotNull());
+ ASSERT_THAT(ref, NotNull());
ASSERT_THAT(HasFailure(), IsFalse());
// The path of first drawable in exemption was not changed
EXPECT_THAT("res/drawables/xmlfile.xml", Eq(*ref->path));
@@ -161,13 +182,78 @@
ASSERT_THAT(path_map.find("res/drawable/xmlfile.xml"), Not(Eq(path_map.end())));
ASSERT_THAT(path_map.find("res/drawable/pngfile.png"), Not(Eq(path_map.end())));
- auto shortend_xml_path = path_map[original_xml_path];
- auto shortend_png_path = path_map[original_png_path];
-
EXPECT_THAT(GetExtension(path_map[original_xml_path]), Eq(android::StringPiece(".xml")));
EXPECT_THAT(GetExtension(path_map[original_png_path]), Eq(android::StringPiece(".png")));
}
+TEST(ObfuscatorTest, ShortenedToReservedWindowsNames) {
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
+
+ std::string original_path_1 = "res/drawable/pngfile_1.png";
+ std::string original_path_2 = "res/drawable/pngfile_2.png";
+ std::string original_path_3 = "res/drawable/pngfile_3.png";
+ std::string original_path_4 = "res/drawable/pngfile_4.png";
+ std::string original_path_5 = "res/drawable/pngfile_5.png";
+ std::string original_path_6 = "res/drawable/pngfile_6.png";
+ std::string original_path_7 = "res/drawable/pngfile_7.png";
+ std::string original_path_8 = "res/drawable/pngfile_8.png";
+ std::string original_path_9 = "res/drawable/pngfile_9.png";
+
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .AddFileReference("android:drawable/pngfile_1", original_path_1)
+ .AddFileReference("android:drawable/pngfile_2", original_path_2)
+ .AddFileReference("android:drawable/pngfile_3", original_path_3)
+ .AddFileReference("android:drawable/pngfile_4", original_path_4)
+ .AddFileReference("android:drawable/pngfile_5", original_path_5)
+ .AddFileReference("android:drawable/pngfile_6", original_path_6)
+ .AddFileReference("android:drawable/pngfile_7", original_path_7)
+ .AddFileReference("android:drawable/pngfile_8", original_path_8)
+ .AddFileReference("android:drawable/pngfile_9", original_path_9)
+ .Build();
+
+ OptimizeOptions options{.shorten_resource_paths = true};
+ std::map<std::string, std::string>& path_map = options.table_flattener_options.shortened_path_map;
+ auto obfuscator = FakeObfuscator(
+ options,
+ {
+ {original_path_1, "CON"},
+ {original_path_2, "Prn"},
+ {original_path_3, "AuX"},
+ {original_path_4, "nul"},
+ {original_path_5, "cOM"},
+ {original_path_6, "lPt"},
+ {original_path_7, "lPt"},
+ {original_path_8, "lPt"}, // 6, 7, and 8 will be appended with a number to disambiguate
+ {original_path_9, "F0o"}, // This one is not reserved
+ });
+ ASSERT_TRUE(obfuscator.Consume(context.get(), table.get()));
+
+ // Expect that the path map is populated
+ ASSERT_THAT(path_map.find(original_path_1), Not(Eq(path_map.end())));
+ ASSERT_THAT(path_map.find(original_path_2), Not(Eq(path_map.end())));
+ ASSERT_THAT(path_map.find(original_path_3), Not(Eq(path_map.end())));
+ ASSERT_THAT(path_map.find(original_path_4), Not(Eq(path_map.end())));
+ ASSERT_THAT(path_map.find(original_path_5), Not(Eq(path_map.end())));
+ ASSERT_THAT(path_map.find(original_path_6), Not(Eq(path_map.end())));
+ ASSERT_THAT(path_map.find(original_path_7), Not(Eq(path_map.end())));
+ ASSERT_THAT(path_map.find(original_path_8), Not(Eq(path_map.end())));
+ ASSERT_THAT(path_map.find(original_path_9), Not(Eq(path_map.end())));
+
+ EXPECT_THAT(path_map[original_path_1], Eq("res/_CON.png"));
+ EXPECT_THAT(path_map[original_path_2], Eq("res/_Prn.png"));
+ EXPECT_THAT(path_map[original_path_3], Eq("res/_AuX.png"));
+ EXPECT_THAT(path_map[original_path_4], Eq("res/_nul.png"));
+ EXPECT_THAT(path_map[original_path_5], Eq("res/_cOM.png"));
+ EXPECT_THAT(path_map[original_path_9], Eq("res/F0o.png"));
+
+ std::set<std::string> lpt_shortened_names{path_map[original_path_6], path_map[original_path_7],
+ path_map[original_path_8]};
+ EXPECT_THAT(lpt_shortened_names, Contains("res/_lPt.png"));
+ EXPECT_THAT(lpt_shortened_names, Contains("res/_lPt1.png"));
+ EXPECT_THAT(lpt_shortened_names, Contains("res/_lPt2.png"));
+}
+
TEST(ObfuscatorTest, DeterministicallyHandleCollisions) {
std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
@@ -247,7 +333,9 @@
ASSERT_TRUE(Obfuscator(options).Consume(context.get(), table.get()));
// Expect that the id resource name map is populated
+ ASSERT_THAT(id_resource_map.find(0x7f020000), Not(Eq(id_resource_map.end())));
EXPECT_THAT(id_resource_map.at(0x7f020000), Eq("mycolor"));
+ ASSERT_THAT(id_resource_map.find(0x7f030000), Not(Eq(id_resource_map.end())));
EXPECT_THAT(id_resource_map.at(0x7f030000), Eq("mystring"));
EXPECT_THAT(id_resource_map.find(0x7f030001), Eq(id_resource_map.end()));
EXPECT_THAT(id_resource_map.find(0x7f030002), Eq(id_resource_map.end()));
@@ -310,8 +398,8 @@
EXPECT_THAT(pbOut, HasSubstr("mycolor"));
EXPECT_THAT(pbOut, HasSubstr("mystring"));
pb::ResourceMappings resourceMappings;
- EXPECT_THAT(resourceMappings.ParseFromString(pbOut), IsTrue());
- EXPECT_THAT(resourceMappings.collapsed_names().resource_names_size(), Eq(2));
+ ASSERT_THAT(resourceMappings.ParseFromString(pbOut), IsTrue());
+ ASSERT_THAT(resourceMappings.collapsed_names().resource_names_size(), Eq(2));
auto& resource_names = resourceMappings.collapsed_names().resource_names();
EXPECT_THAT(resource_names.at(0).name(), AnyOf(Eq("mycolor"), Eq("mystring")));
EXPECT_THAT(resource_names.at(1).name(), AnyOf(Eq("mycolor"), Eq("mystring")));
@@ -337,4 +425,6 @@
ASSERT_THAT(pbOut, Eq(""));
}
+} // namespace
+
} // namespace aapt
diff --git a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/Constants.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/Constants.kt
index e03d92ab..f1727b7 100644
--- a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/Constants.kt
+++ b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/Constants.kt
@@ -30,6 +30,7 @@
const val IINTERFACE_INTERFACE = "android.os.IInterface"
const val AIDL_PERMISSION_HELPER_SUFFIX = "_enforcePermission"
+const val PERMISSION_PREFIX_LITERAL = "android.permission."
/**
* If a non java (e.g. c++) backend is enabled, the @EnforcePermission
diff --git a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt
index 0a66cd5..3a95df9 100644
--- a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt
+++ b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt
@@ -95,7 +95,7 @@
val v1 = ConstantEvaluator.evaluate(context, value1)
val v2 = ConstantEvaluator.evaluate(context, value2)
if (v1 != null && v2 != null) {
- if (v1 != v2) {
+ if (v1 != v2 && !isOneShortPermissionOfOther(v1, v2)) {
return false
}
} else {
@@ -107,7 +107,7 @@
for (j in children1.indices) {
val c1 = ConstantEvaluator.evaluate(context, children1[j])
val c2 = ConstantEvaluator.evaluate(context, children2[j])
- if (c1 != c2) {
+ if (c1 != c2 && !isOneShortPermissionOfOther(c1, c2)) {
return false
}
}
@@ -116,6 +116,12 @@
return true
}
+ private fun isOneShortPermissionOfOther(
+ permission1: Any?,
+ permission2: Any?
+ ): Boolean = permission1 == (permission2 as? String)?.removePrefix(PERMISSION_PREFIX_LITERAL) ||
+ permission2 == (permission1 as? String)?.removePrefix(PERMISSION_PREFIX_LITERAL)
+
private fun compareMethods(
context: JavaContext,
element: UElement,
diff --git a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionDetectorTest.kt b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionDetectorTest.kt
index 75b0073..b3dacbd 100644
--- a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionDetectorTest.kt
+++ b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionDetectorTest.kt
@@ -316,20 +316,55 @@
overrides the method Stub.testMethod which is annotated with @EnforcePermission. The same annotation must be used on Default.testMethod [MissingEnforcePermissionAnnotation]
public void testMethod() {}
~~~~~~~~~~
- 1 errors, 0 warnings
+ 1 errors, 0 warnings
""".addLineContinuation()
)
}
- fun testDoesDetectIssuesShortStringsNotAllowed() {
+ fun testDoesNotDetectIssuesShortStringsAllowedInChildAndParent() {
lint().files(java(
"""
package test.pkg;
import android.annotation.EnforcePermission;
public class TestClass121 extends IFooMethod.Stub {
@Override
+ @EnforcePermission("READ_PHONE_STATE")
+ public void testMethod() {}
+ @Override
+ @EnforcePermission(android.Manifest.permission.READ_PHONE_STATE)
+ public void testMethodParentShortPermission() {}
+ @Override
@EnforcePermission(anyOf={"INTERNET", "READ_PHONE_STATE"})
public void testMethodAnyLiteral() {}
+ @Override
+ @EnforcePermission(anyOf={android.Manifest.permission.INTERNET, android.Manifest.permission.READ_PHONE_STATE})
+ public void testMethodAnyLiteralParentsShortPermission() {}
+ }
+ """).indented(),
+ *stubs
+ )
+ .run()
+ .expectClean()
+ }
+
+ fun testDoesDetectIssuesWrongShortStringsInChildAndParent() {
+ lint().files(java(
+ """
+ package test.pkg;
+ import android.annotation.EnforcePermission;
+ public class TestClass121 extends IFooMethod.Stub {
+ @Override
+ @EnforcePermission("READ_WRONG_PHONE_STATE")
+ public void testMethod() {}
+ @Override
+ @EnforcePermission(android.Manifest.permission.READ_WRONG_PHONE_STATE)
+ public void testMethodParentShortPermission() {}
+ @Override
+ @EnforcePermission(anyOf={"WRONG_INTERNET", "READ_PHONE_STATE"})
+ public void testMethodAnyLiteral() {}
+ @Override
+ @EnforcePermission(anyOf={android.Manifest.permission.INTERNET, android.Manifest.permission.READ_WRONG_PHONE_STATE})
+ public void testMethodAnyLiteralParentsShortPermission() {}
}
""").indented(),
*stubs
@@ -337,14 +372,19 @@
.run()
.expect(
"""
- src/test/pkg/TestClass121.java:6: Error: The method \
- TestClass121.testMethodAnyLiteral is annotated with @EnforcePermission(anyOf={"INTERNET", "READ_PHONE_STATE"}) \
- which differs from the overridden method Stub.testMethodAnyLiteral: \
- @android.annotation.EnforcePermission(anyOf={android.Manifest.permission.INTERNET, "android.permission.READ_PHONE_STATE"}). \
- The same annotation must be used for both methods. [MismatchingEnforcePermissionAnnotation]
- public void testMethodAnyLiteral() {}
- ~~~~~~~~~~~~~~~~~~~~
- 1 errors, 0 warnings
+ src/test/pkg/TestClass121.java:6: Error: The method TestClass121.testMethod is annotated with @EnforcePermission("READ_WRONG_PHONE_STATE") which differs from the overridden method Stub.testMethod: @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE). The same annotation must be used for both methods. [MismatchingEnforcePermissionAnnotation]
+ public void testMethod() {}
+ ~~~~~~~~~~
+ src/test/pkg/TestClass121.java:9: Error: The method TestClass121.testMethodParentShortPermission is annotated with @EnforcePermission(android.Manifest.permission.READ_WRONG_PHONE_STATE) which differs from the overridden method Stub.testMethodParentShortPermission: @android.annotation.EnforcePermission("READ_PHONE_STATE"). The same annotation must be used for both methods. [MismatchingEnforcePermissionAnnotation]
+ public void testMethodParentShortPermission() {}
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ src/test/pkg/TestClass121.java:12: Error: The method TestClass121.testMethodAnyLiteral is annotated with @EnforcePermission(anyOf={"WRONG_INTERNET", "READ_PHONE_STATE"}) which differs from the overridden method Stub.testMethodAnyLiteral: @android.annotation.EnforcePermission(anyOf={android.Manifest.permission.INTERNET, "android.permission.READ_PHONE_STATE"}). The same annotation must be used for both methods. [MismatchingEnforcePermissionAnnotation]
+ public void testMethodAnyLiteral() {}
+ ~~~~~~~~~~~~~~~~~~~~
+ src/test/pkg/TestClass121.java:15: Error: The method TestClass121.testMethodAnyLiteralParentsShortPermission is annotated with @EnforcePermission(anyOf={android.Manifest.permission.INTERNET, android.Manifest.permission.READ_WRONG_PHONE_STATE}) which differs from the overridden method Stub.testMethodAnyLiteralParentsShortPermission: @android.annotation.EnforcePermission(anyOf={"INTERNET", "READ_PHONE_STATE"}). The same annotation must be used for both methods. [MismatchingEnforcePermissionAnnotation]
+ public void testMethodAnyLiteralParentsShortPermission() {}
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ 4 errors, 0 warnings
""".addLineContinuation()
)
}
@@ -360,12 +400,18 @@
@android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE)
public void testMethod() {}
@Override
+ @android.annotation.EnforcePermission("READ_PHONE_STATE")
+ public void testMethodParentShortPermission() {}
+ @Override
@android.annotation.EnforcePermission(anyOf={android.Manifest.permission.INTERNET, android.Manifest.permission.READ_PHONE_STATE})
public void testMethodAny() {}
@Override
@android.annotation.EnforcePermission(anyOf={android.Manifest.permission.INTERNET, "android.permission.READ_PHONE_STATE"})
public void testMethodAnyLiteral() {}
@Override
+ @android.annotation.EnforcePermission(anyOf={"INTERNET", "READ_PHONE_STATE"})
+ public void testMethodAnyLiteralParentsShortPermission() {}
+ @Override
@android.annotation.EnforcePermission(allOf={android.Manifest.permission.INTERNET, android.Manifest.permission.READ_PHONE_STATE})
public void testMethodAll() {}
@Override
@@ -374,10 +420,14 @@
}
@android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE)
public void testMethod();
+ @android.annotation.EnforcePermission("READ_PHONE_STATE")
+ public void testMethodParentShortPermission();
@android.annotation.EnforcePermission(anyOf={android.Manifest.permission.INTERNET, android.Manifest.permission.READ_PHONE_STATE})
public void testMethodAny() {}
@android.annotation.EnforcePermission(anyOf={android.Manifest.permission.INTERNET, "android.permission.READ_PHONE_STATE"})
public void testMethodAnyLiteral() {}
+ @android.annotation.EnforcePermission(anyOf={"INTERNET", "READ_PHONE_STATE"})
+ public void testMethodAnyLiteralParentsShortPermission() {}
@android.annotation.EnforcePermission(allOf={android.Manifest.permission.INTERNET, android.Manifest.permission.READ_PHONE_STATE})
public void testMethodAll() {}
@android.annotation.EnforcePermission(allOf={android.Manifest.permission.INTERNET, "android.permission.READ_PHONE_STATE"})
@@ -404,6 +454,7 @@
package android.Manifest;
class permission {
public static final String READ_PHONE_STATE = "android.permission.READ_PHONE_STATE";
+ public static final String READ_WRONG_PHONE_STATE = "android.permission.READ_WRONG_PHONE_STATE";
public static final String NFC = "android.permission.NFC";
public static final String INTERNET = "android.permission.INTERNET";
}