Merge "Handle multi-user cases on Live Caption toggle in Volume rocker" into udc-qpr-dev
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 41c58ef..1571fdd 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -34,7 +34,6 @@
import static android.window.ConfigurationHelper.freeTextLayoutCachesIfNeeded;
import static android.window.ConfigurationHelper.isDifferentDisplay;
import static android.window.ConfigurationHelper.shouldUpdateResources;
-import static android.window.ConfigurationHelper.shouldUpdateWindowMetricsBounds;
import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
import static com.android.internal.os.SafeZipPathValidatorCallback.VALIDATE_ZIP_PATH_FOR_PATH_TRAVERSAL;
@@ -6118,11 +6117,6 @@
public static boolean shouldReportChange(@Nullable Configuration currentConfig,
@NonNull Configuration newConfig, @Nullable SizeConfigurationBuckets sizeBuckets,
int handledConfigChanges, boolean alwaysReportChange) {
- // Always report changes in window configuration bounds
- if (shouldUpdateWindowMetricsBounds(currentConfig, newConfig)) {
- return true;
- }
-
final int publicDiff = currentConfig.diffPublicOnly(newConfig);
// Don't report the change if there's no public diff between current and new config.
if (publicDiff == 0) {
diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java
index d8cedb8..7ee1332 100644
--- a/core/java/android/app/NotificationChannel.java
+++ b/core/java/android/app/NotificationChannel.java
@@ -26,12 +26,14 @@
import android.content.Intent;
import android.content.pm.ShortcutInfo;
import android.media.AudioAttributes;
+import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
import android.provider.Settings;
import android.service.notification.NotificationListenerService;
import android.text.TextUtils;
+import android.util.Log;
import android.util.proto.ProtoOutputStream;
import com.android.internal.util.Preconditions;
@@ -54,6 +56,7 @@
* A representation of settings that apply to a collection of similarly themed notifications.
*/
public final class NotificationChannel implements Parcelable {
+ private static final String TAG = "NotificationChannel";
/**
* The id of the default channel for an app. This id is reserved by the system. All
@@ -959,8 +962,11 @@
setLockscreenVisibility(safeInt(parser, ATT_VISIBILITY, DEFAULT_VISIBILITY));
Uri sound = safeUri(parser, ATT_SOUND);
- setSound(forRestore ? restoreSoundUri(context, sound, pkgInstalled) : sound,
- safeAudioAttributes(parser));
+
+ final AudioAttributes audioAttributes = safeAudioAttributes(parser);
+ final int usage = audioAttributes.getUsage();
+ setSound(forRestore ? restoreSoundUri(context, sound, pkgInstalled, usage) : sound,
+ audioAttributes);
enableLights(safeBool(parser, ATT_LIGHTS, false));
setLightColor(safeInt(parser, ATT_LIGHT_COLOR, DEFAULT_LIGHT_COLOR));
@@ -1010,18 +1016,34 @@
if (ContentResolver.SCHEME_FILE.equals(uri.getScheme())) {
return uri;
}
-
return contentResolver.canonicalize(uri);
}
@Nullable
- private Uri getUncanonicalizedSoundUri(ContentResolver contentResolver, @NonNull Uri uri) {
+ private Uri getUncanonicalizedSoundUri(
+ ContentResolver contentResolver, @NonNull Uri uri, int usage) {
if (Settings.System.DEFAULT_NOTIFICATION_URI.equals(uri)
|| ContentResolver.SCHEME_ANDROID_RESOURCE.equals(uri.getScheme())
|| ContentResolver.SCHEME_FILE.equals(uri.getScheme())) {
return uri;
}
- return contentResolver.uncanonicalize(uri);
+ int ringtoneType = 0;
+
+ // Consistent with UI(SoundPreferenceController.handlePreferenceTreeClick).
+ if (AudioAttributes.USAGE_ALARM == usage) {
+ ringtoneType = RingtoneManager.TYPE_ALARM;
+ } else if (AudioAttributes.USAGE_NOTIFICATION_RINGTONE == usage) {
+ ringtoneType = RingtoneManager.TYPE_RINGTONE;
+ } else {
+ ringtoneType = RingtoneManager.TYPE_NOTIFICATION;
+ }
+ try {
+ return RingtoneManager.getRingtoneUriForRestore(
+ contentResolver, uri.toString(), ringtoneType);
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to uncanonicalized sound uri for " + uri + " " + e);
+ return Settings.System.DEFAULT_NOTIFICATION_URI;
+ }
}
/**
@@ -1033,7 +1055,8 @@
* @hide
*/
@Nullable
- public Uri restoreSoundUri(Context context, @Nullable Uri uri, boolean pkgInstalled) {
+ public Uri restoreSoundUri(
+ Context context, @Nullable Uri uri, boolean pkgInstalled, int usage) {
if (uri == null || Uri.EMPTY.equals(uri)) {
return null;
}
@@ -1060,7 +1083,7 @@
}
}
mSoundRestored = true;
- return getUncanonicalizedSoundUri(contentResolver, canonicalizedUri);
+ return getUncanonicalizedSoundUri(contentResolver, canonicalizedUri, usage);
}
/**
diff --git a/core/java/android/app/SharedPreferencesImpl.java b/core/java/android/app/SharedPreferencesImpl.java
index 1ebf565..a87187b 100644
--- a/core/java/android/app/SharedPreferencesImpl.java
+++ b/core/java/android/app/SharedPreferencesImpl.java
@@ -55,6 +55,11 @@
import java.util.Set;
import java.util.WeakHashMap;
import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executors;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
final class SharedPreferencesImpl implements SharedPreferences {
private static final String TAG = "SharedPreferencesImpl";
@@ -119,6 +124,10 @@
private final ExponentiallyBucketedHistogram mSyncTimes = new ExponentiallyBucketedHistogram(16);
private int mNumSync = 0;
+ private static final ThreadPoolExecutor sLoadExecutor = new ThreadPoolExecutor(0, 1, 10L,
+ TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(),
+ new SharedPreferencesThreadFactory());
+
@UnsupportedAppUsage
SharedPreferencesImpl(File file, int mode) {
mFile = file;
@@ -135,11 +144,10 @@
synchronized (mLock) {
mLoaded = false;
}
- new Thread("SharedPreferencesImpl-load") {
- public void run() {
- loadFromDisk();
- }
- }.start();
+
+ sLoadExecutor.execute(() -> {
+ loadFromDisk();
+ });
}
private void loadFromDisk() {
@@ -874,4 +882,14 @@
}
mcr.setDiskWriteResult(false, false);
}
+
+
+ private static final class SharedPreferencesThreadFactory implements ThreadFactory {
+ @Override
+ public Thread newThread(Runnable runnable) {
+ Thread thread = Executors.defaultThreadFactory().newThread(runnable);
+ thread.setName("SharedPreferences");
+ return thread;
+ }
+ }
}
diff --git a/core/java/android/content/pm/TEST_MAPPING b/core/java/android/content/pm/TEST_MAPPING
index 3ffbe1d..2ea6513 100644
--- a/core/java/android/content/pm/TEST_MAPPING
+++ b/core/java/android/content/pm/TEST_MAPPING
@@ -101,20 +101,6 @@
],
"presubmit-large":[
{
- "name":"CtsContentTestCases",
- "options":[
- {
- "exclude-annotation":"androidx.test.filters.FlakyTest"
- },
- {
- "exclude-annotation":"org.junit.Ignore"
- },
- {
- "include-filter":"android.content.pm.cts"
- }
- ]
- },
- {
"name":"CtsUsesNativeLibraryTest",
"options":[
{
@@ -156,6 +142,20 @@
],
"postsubmit":[
{
+ "name":"CtsContentTestCases",
+ "options":[
+ {
+ "exclude-annotation":"androidx.test.filters.FlakyTest"
+ },
+ {
+ "exclude-annotation":"org.junit.Ignore"
+ },
+ {
+ "include-filter":"android.content.pm.cts"
+ }
+ ]
+ },
+ {
"name":"CtsAppSecurityHostTestCases",
"options":[
{
diff --git a/core/java/android/credentials/CreateCredentialRequest.java b/core/java/android/credentials/CreateCredentialRequest.java
index fc3dc79..946b5f3 100644
--- a/core/java/android/credentials/CreateCredentialRequest.java
+++ b/core/java/android/credentials/CreateCredentialRequest.java
@@ -261,7 +261,10 @@
/**
* @param type the type of the credential to be stored
- * @param credentialData the full credential creation request data
+ * @param credentialData the full credential creation request data, which must at minimum
+ * contain the required fields observed at the
+ * {@link androidx.credentials.CreateCredentialRequest} Bundle conversion static methods,
+ * because they are required for properly displaying the system credential selector UI
* @param candidateQueryData the partial request data that will be sent to the provider
* during the initial creation candidate query stage
*/
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index c2fe080..c80124c 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -31,6 +31,7 @@
import android.content.pm.PackageManager;
import android.graphics.Point;
import android.hardware.CameraExtensionSessionStats;
+import android.hardware.CameraIdRemapping;
import android.hardware.CameraStatus;
import android.hardware.ICameraService;
import android.hardware.ICameraServiceListener;
@@ -1730,6 +1731,17 @@
}
/**
+ * Remaps Camera Ids in the CameraService.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.CAMERA_INJECT_EXTERNAL_CAMERA)
+ public void remapCameraIds(@NonNull CameraIdRemapping cameraIdRemapping)
+ throws CameraAccessException, SecurityException, IllegalArgumentException {
+ CameraManagerGlobal.get().remapCameraIds(cameraIdRemapping);
+ }
+
+ /**
* Reports {@link CameraExtensionSessionStats} to the {@link ICameraService} to be logged for
* currently active session. Validation is done downstream.
*
@@ -1802,6 +1814,13 @@
private final Object mLock = new Object();
+ /**
+ * The active CameraIdRemapping. This will be used to refresh the cameraIdRemapping state
+ * in the CameraService every time we connect to it, including when the CameraService
+ * Binder dies and we reconnect to it.
+ */
+ @Nullable private CameraIdRemapping mActiveCameraIdRemapping;
+
// Access only through getCameraService to deal with binder death
private ICameraService mCameraService;
private boolean mHasOpenCloseListenerPermission = false;
@@ -1944,6 +1963,41 @@
} catch (RemoteException e) {
// Camera service died in all probability
}
+
+ if (mActiveCameraIdRemapping != null) {
+ try {
+ cameraService.remapCameraIds(mActiveCameraIdRemapping);
+ } catch (ServiceSpecificException e) {
+ // Unexpected failure, ignore and continue.
+ Log.e(TAG, "Unable to remap camera Ids in the camera service");
+ } catch (RemoteException e) {
+ // Camera service died in all probability
+ }
+ }
+ }
+
+ /** Updates the cameraIdRemapping state in the CameraService. */
+ public void remapCameraIds(@NonNull CameraIdRemapping cameraIdRemapping)
+ throws CameraAccessException, SecurityException {
+ synchronized (mLock) {
+ ICameraService cameraService = getCameraService();
+ if (cameraService == null) {
+ throw new CameraAccessException(
+ CameraAccessException.CAMERA_DISCONNECTED,
+ "Camera service is currently unavailable.");
+ }
+
+ try {
+ cameraService.remapCameraIds(cameraIdRemapping);
+ mActiveCameraIdRemapping = cameraIdRemapping;
+ } catch (ServiceSpecificException e) {
+ throwAsPublicException(e);
+ } catch (RemoteException e) {
+ throw new CameraAccessException(
+ CameraAccessException.CAMERA_DISCONNECTED,
+ "Camera service is currently unavailable.");
+ }
+ }
}
private String[] extractCameraIdListLocked() {
diff --git a/core/java/android/hardware/usb/UsbConfiguration.java b/core/java/android/hardware/usb/UsbConfiguration.java
index 66269cb..b25f47b 100644
--- a/core/java/android/hardware/usb/UsbConfiguration.java
+++ b/core/java/android/hardware/usb/UsbConfiguration.java
@@ -172,7 +172,8 @@
String name = in.readString();
int attributes = in.readInt();
int maxPower = in.readInt();
- Parcelable[] interfaces = in.readParcelableArray(UsbInterface.class.getClassLoader());
+ Parcelable[] interfaces = in.readParcelableArray(
+ UsbInterface.class.getClassLoader(), UsbInterface.class);
UsbConfiguration configuration = new UsbConfiguration(id, name, attributes, maxPower);
configuration.setInterfaces(interfaces);
return configuration;
diff --git a/core/java/android/view/HandwritingInitiator.java b/core/java/android/view/HandwritingInitiator.java
index ab58306b..4cfec99 100644
--- a/core/java/android/view/HandwritingInitiator.java
+++ b/core/java/android/view/HandwritingInitiator.java
@@ -208,6 +208,7 @@
candidateView.getHandwritingDelegatorCallback().run();
mState.mHasPreparedHandwritingDelegation = true;
} else {
+ mState.mPendingConnectedView = new WeakReference<>(candidateView);
requestFocusWithoutReveal(candidateView);
}
}
@@ -269,8 +270,9 @@
mShowHoverIconForConnectedView = false;
return;
}
- if (mState != null && mState.mShouldInitHandwriting) {
- tryStartHandwriting();
+ if (mState != null && mState.mPendingConnectedView != null
+ && mState.mPendingConnectedView.get() == view) {
+ startHandwriting(view);
}
}
}
@@ -295,40 +297,6 @@
}
}
- /**
- * Try to initiate handwriting. For this method to successfully send startHandwriting signal,
- * the following 3 conditions should meet:
- * a) The stylus movement exceeds the touchSlop.
- * b) A View has built InputConnection with IME.
- * c) The stylus event lands into the connected View's boundary.
- * This method will immediately fail without any side effect if condition a or b is not met.
- * However, if both condition a and b are met but the condition c is not met, it will reset the
- * internal states. And HandwritingInitiator won't attempt to call startHandwriting until the
- * next ACTION_DOWN.
- */
- private void tryStartHandwriting() {
- if (!mState.mExceedHandwritingSlop) {
- return;
- }
- final View connectedView = getConnectedView();
- if (connectedView == null) {
- return;
- }
-
- if (!connectedView.isAutoHandwritingEnabled()) {
- clearConnectedView();
- return;
- }
-
- final Rect handwritingArea = getViewHandwritingArea(connectedView);
- if (isInHandwritingArea(
- handwritingArea, mState.mStylusDownX, mState.mStylusDownY, connectedView)) {
- startHandwriting(connectedView);
- } else {
- mState.mShouldInitHandwriting = false;
- }
- }
-
/** Starts a stylus handwriting session for the view. */
@VisibleForTesting
public void startHandwriting(@NonNull View view) {
@@ -631,6 +599,7 @@
private boolean mHasInitiatedHandwriting;
private boolean mHasPreparedHandwritingDelegation;
+
/**
* Whether the current ongoing stylus MotionEvent sequence already exceeds the
* handwriting slop.
@@ -639,6 +608,12 @@
*/
private boolean mExceedHandwritingSlop;
+ /**
+ * A view which has requested focus and is pending input connection creation. When an input
+ * connection is created for the view, a handwriting session should be started for the view.
+ */
+ private WeakReference<View> mPendingConnectedView = null;
+
/** The pointer id of the stylus pointer that is being tracked. */
private final int mStylusPointerId;
/** The time stamp when the stylus pointer goes down. */
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index b369c45..6edf0e2 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -1808,6 +1808,9 @@
// Request to update light center.
mAttachInfo.mNeedsUpdateLightCenter = true;
}
+ if ((changes & WindowManager.LayoutParams.COLOR_MODE_CHANGED) != 0) {
+ invalidate();
+ }
if (mWindowAttributes.packageName == null) {
mWindowAttributes.packageName = mBasePackageName;
}
@@ -5513,6 +5516,7 @@
if (desiredRatio != mDesiredHdrSdrRatio) {
mDesiredHdrSdrRatio = desiredRatio;
updateRenderHdrSdrRatio();
+ invalidate();
if (mDesiredHdrSdrRatio < 1.01f) {
mDisplay.unregisterHdrSdrRatioChangedListener(mHdrSdrRatioChangedListener);
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 65677cd..9195509 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -913,7 +913,6 @@
* </application>
* </pre>
*/
- // TODO(b/263984287): Add CTS tests.
String PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION =
"android.window.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION";
@@ -983,7 +982,6 @@
* </application>
* </pre>
*/
- // TODO(b/263984287): Make this public API.
String PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS =
"android.window.PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS";
@@ -1018,7 +1016,6 @@
* </application>
* </pre>
*/
- // TODO(b/263984287): Add CTS tests.
String PROPERTY_COMPAT_ENABLE_FAKE_FOCUS = "android.window.PROPERTY_COMPAT_ENABLE_FAKE_FOCUS";
/**
@@ -1056,7 +1053,6 @@
* </application>
* </pre>
*/
- // TODO(b/263984287): Add CTS tests.
String PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION =
"android.window.PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION";
@@ -1102,7 +1098,6 @@
* </application>
* </pre>
*/
- // TODO(b/263984287): Add CTS tests.
String PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH =
"android.window.PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH";
@@ -1151,7 +1146,6 @@
* </application>
* </pre>
*/
- // TODO(b/263984287): Add CTS tests.
String PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE =
"android.window.PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE";
@@ -1189,7 +1183,6 @@
* </application>
* </pre>
*/
- // TODO(b/263984287): Add CTS tests.
String PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE =
"android.window.PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE";
@@ -1233,7 +1226,6 @@
* </application>
* </pre>
*/
- // TODO(b/263984287): Add CTS tests.
String PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE =
"android.window.PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE";
@@ -1300,6 +1292,102 @@
"android.window.PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES";
/**
+ * Application level
+ * {@link android.content.pm.PackageManager.Property PackageManager.Property}
+ * tag that (when set to false) informs the system the app has opted out of the
+ * user-facing aspect ratio compatibility override.
+ *
+ * <p>The compatibility override enables device users to set the app's aspect
+ * ratio or force the app to fill the display regardless of the aspect
+ * ratio or orientation specified in the app manifest.
+ *
+ * <p>The aspect ratio compatibility override is exposed to users in device
+ * settings. A menu in device settings lists all apps that have not opted out of
+ * the compatibility override. Users select apps from the menu and set the
+ * app aspect ratio on a per-app basis. Typically, the menu is available
+ * only on large screen devices.
+ *
+ * <p>When users apply the aspect ratio override, the minimum aspect ratio
+ * specified in the app manifest is overridden. If users choose a
+ * full-screen aspect ratio, the orientation of the activity is forced to
+ * {@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_USER};
+ * see {@link #PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE} to
+ * disable the full-screen option only.
+ *
+ * <p>The user override is intended to improve the app experience on devices
+ * that have the ignore orientation request display setting enabled by OEMs
+ * (enables compatibility mode for fixed orientation on Android 12 (API
+ * level 31) or higher; see
+ * <a href="https://developer.android.com/guide/topics/large-screens/large-screen-app-compatibility">
+ * Large screen app compatibility</a>
+ * for more details).
+ *
+ * <p>To opt out of the user aspect ratio compatibility override, add this property
+ * to your app manifest and set the value to {@code false}. Your app will be excluded
+ * from the list of apps in device settings, and users will not be able to override
+ * the app's aspect ratio.
+ *
+ * <p>Not setting this property at all, or setting this property to {@code true} has no effect.
+ *
+ * <p><b>Syntax:</b>
+ * <pre>
+ * <application>
+ * <property
+ * android:name="android.window.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE"
+ * android:value="false"/>
+ * </application>
+ * </pre>
+ * @hide
+ */
+ // TODO(b/294227289): Make this public API
+ String PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE =
+ "android.window.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE";
+
+ /**
+ * Application level
+ * {@link android.content.pm.PackageManager.Property PackageManager.Property}
+ * tag that (when set to false) informs the system the app has opted out of the
+ * full-screen option of the user aspect ratio compatibility override settings. (For
+ * background information about the user aspect ratio compatibility override, see
+ * {@link #PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE}.)
+ *
+ * <p>When users apply the full-screen compatibility override, the orientation
+ * of the activity is forced to {@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_USER}.
+ *
+ * <p>The user override is intended to improve the app experience on devices
+ * that have the ignore orientation request display setting enabled by OEMs
+ * (enables compatibility mode for fixed orientation on Android 12 (API
+ * level 31) or higher; see
+ * <a href="https://developer.android.com/guide/topics/large-screens/large-screen-app-compatibility">
+ * Large screen app compatibility</a>
+ * for more details).
+ *
+ * <p>To opt out of the full-screen option of the user aspect ratio compatibility
+ * override, add this property to your app manifest and set the value to {@code false}.
+ * Your app will have full-screen option removed from the list of user aspect ratio
+ * override options in device settings, and users will not be able to apply
+ * full-screen override to your app.
+ *
+ * <p><b>Note:</b> If {@link #PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE} is
+ * {@code false}, this property has no effect.
+ *
+ * <p>Not setting this property at all, or setting this property to {@code true} has no effect.
+ *
+ * <p><b>Syntax:</b>
+ * <pre>
+ * <application>
+ * <property
+ * android:name="android.window.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE"
+ * android:value="false"/>
+ * </application>
+ * </pre>
+ * @hide
+ */
+ // TODO(b/294227289): Make this public API
+ String PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE =
+ "android.window.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE";
+
+ /**
* @hide
*/
public static final String PARCEL_KEY_SHORTCUTS_ARRAY = "shortcuts_array";
diff --git a/core/java/android/widget/HorizontalScrollView.java b/core/java/android/widget/HorizontalScrollView.java
index 1d6778b..55b2251 100644
--- a/core/java/android/widget/HorizontalScrollView.java
+++ b/core/java/android/widget/HorizontalScrollView.java
@@ -1498,6 +1498,11 @@
* @return The unconsumed delta after the EdgeEffects have had an opportunity to consume.
*/
private int consumeFlingInStretch(int unconsumed) {
+ int scrollX = getScrollX();
+ if (scrollX < 0 || scrollX > getScrollRange()) {
+ // We've overscrolled, so don't stretch
+ return unconsumed;
+ }
if (unconsumed > 0 && mEdgeGlowLeft != null && mEdgeGlowLeft.getDistance() != 0f) {
int size = getWidth();
float deltaDistance = -unconsumed * FLING_DESTRETCH_FACTOR / size;
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index a2f95fa..fea3b78 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -337,7 +337,7 @@
*
* @hide
*/
- private static final int MAX_ADAPTER_CONVERSION_WAITING_TIME_MS = 2000;
+ private static final int MAX_ADAPTER_CONVERSION_WAITING_TIME_MS = 5000;
/**
* Application that hosts the remote views.
@@ -4820,7 +4820,7 @@
public static boolean isAdapterConversionEnabled() {
return AppGlobals.getIntCoreSetting(
SystemUiDeviceConfigFlags.REMOTEVIEWS_ADAPTER_CONVERSION,
- SystemUiDeviceConfigFlags.REMOTEVIEWS_ADAPTER_CONVERSION_DEFAULT ? 1 : 0) == 1;
+ SystemUiDeviceConfigFlags.REMOTEVIEWS_ADAPTER_CONVERSION_DEFAULT ? 1 : 0) != 0;
}
/**
diff --git a/core/java/android/widget/RemoteViewsService.java b/core/java/android/widget/RemoteViewsService.java
index d4f4d19..a250a86 100644
--- a/core/java/android/widget/RemoteViewsService.java
+++ b/core/java/android/widget/RemoteViewsService.java
@@ -47,7 +47,7 @@
*
* @hide
*/
- private static final int MAX_NUM_ENTRY = 25;
+ private static final int MAX_NUM_ENTRY = 10;
/**
* An interface for an adapter between a remote collection view (ListView, GridView, etc) and
diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java
index cb5dbe6..e591e9e 100644
--- a/core/java/android/widget/ScrollView.java
+++ b/core/java/android/widget/ScrollView.java
@@ -1545,6 +1545,11 @@
* @return The unconsumed delta after the EdgeEffects have had an opportunity to consume.
*/
private int consumeFlingInStretch(int unconsumed) {
+ int scrollY = getScrollY();
+ if (scrollY < 0 || scrollY > getScrollRange()) {
+ // We've overscrolled, so don't stretch
+ return unconsumed;
+ }
if (unconsumed > 0 && mEdgeGlowTop != null && mEdgeGlowTop.getDistance() != 0f) {
int size = getHeight();
float deltaDistance = -unconsumed * FLING_DESTRETCH_FACTOR / size;
diff --git a/core/java/android/window/ConfigurationHelper.java b/core/java/android/window/ConfigurationHelper.java
index 269ce08..e32adcf 100644
--- a/core/java/android/window/ConfigurationHelper.java
+++ b/core/java/android/window/ConfigurationHelper.java
@@ -106,7 +106,7 @@
* @see WindowManager#getCurrentWindowMetrics()
* @see WindowManager#getMaximumWindowMetrics()
*/
- public static boolean shouldUpdateWindowMetricsBounds(@NonNull Configuration currentConfig,
+ private static boolean shouldUpdateWindowMetricsBounds(@NonNull Configuration currentConfig,
@NonNull Configuration newConfig) {
final Rect currentBounds = currentConfig.windowConfiguration.getBounds();
final Rect newBounds = newConfig.windowConfiguration.getBounds();
diff --git a/core/java/com/android/internal/app/AssistUtils.java b/core/java/com/android/internal/app/AssistUtils.java
index 0ea8014..4261a0f 100644
--- a/core/java/com/android/internal/app/AssistUtils.java
+++ b/core/java/com/android/internal/app/AssistUtils.java
@@ -234,6 +234,23 @@
}
/**
+ * Allows subscription to {@link android.service.voice.VisualQueryDetectionService} service
+ * status.
+ *
+ * @param listener to receive visual service start/stop events.
+ */
+ public void subscribeVisualQueryRecognitionStatus(IVisualQueryRecognitionStatusListener
+ listener) {
+ try {
+ if (mVoiceInteractionManagerService != null) {
+ mVoiceInteractionManagerService.subscribeVisualQueryRecognitionStatus(listener);
+ }
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to register visual query detection start listener", e);
+ }
+ }
+
+ /**
* Enables visual detection service.
*
* @param listener to receive visual attention gained/lost events.
diff --git a/core/java/com/android/internal/app/IVisualQueryRecognitionStatusListener.aidl b/core/java/com/android/internal/app/IVisualQueryRecognitionStatusListener.aidl
new file mode 100644
index 0000000..cc49a75
--- /dev/null
+++ b/core/java/com/android/internal/app/IVisualQueryRecognitionStatusListener.aidl
@@ -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.internal.app;
+
+
+ oneway interface IVisualQueryRecognitionStatusListener {
+ /**
+ * Called when {@link VisualQueryDetectionService#onStartDetection} is scheduled from the system
+ * server via {@link VoiceInteractionManagerService#StartPerceiving}.
+ */
+ void onStartPerceiving();
+
+ /**
+ * Called when {@link VisualQueryDetectionService#onStopDetection} is scheduled from the system
+ * server via {@link VoiceInteractionManagerService#StopPerceiving}.
+ */
+ void onStopPerceiving();
+ }
\ No newline at end of file
diff --git a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
index 24d5afc..314ed69 100644
--- a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
+++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
@@ -40,6 +40,7 @@
import com.android.internal.app.IVoiceInteractionSoundTriggerSession;
import com.android.internal.app.IVoiceInteractor;
import com.android.internal.app.IVisualQueryDetectionAttentionListener;
+import com.android.internal.app.IVisualQueryRecognitionStatusListener;
interface IVoiceInteractionManagerService {
void showSession(in Bundle sessionArgs, int flags, String attributionTag);
@@ -325,6 +326,9 @@
void shutdownHotwordDetectionService();
@EnforcePermission("ACCESS_VOICE_INTERACTION_SERVICE")
+ void subscribeVisualQueryRecognitionStatus(in IVisualQueryRecognitionStatusListener listener);
+
+ @EnforcePermission("ACCESS_VOICE_INTERACTION_SERVICE")
void enableVisualQueryDetection(in IVisualQueryDetectionAttentionListener Listener);
@EnforcePermission("ACCESS_VOICE_INTERACTION_SERVICE")
diff --git a/core/java/com/android/internal/app/LocaleStore.java b/core/java/com/android/internal/app/LocaleStore.java
index 43d263b..b3b0603 100644
--- a/core/java/com/android/internal/app/LocaleStore.java
+++ b/core/java/com/android/internal/app/LocaleStore.java
@@ -390,12 +390,17 @@
public static Set<LocaleInfo> transformImeLanguageTagToLocaleInfo(
List<InputMethodSubtype> list) {
Set<LocaleInfo> imeLocales = new HashSet<>();
+ Set<String> languageTagSet = new HashSet<>();
for (InputMethodSubtype subtype : list) {
- Locale locale = Locale.forLanguageTag(subtype.getLanguageTag());
- LocaleInfo cacheInfo = getLocaleInfo(locale, sLocaleCache);
- LocaleInfo localeInfo = new LocaleInfo(cacheInfo);
- localeInfo.mSuggestionFlags |= LocaleInfo.SUGGESTION_TYPE_IME_LANGUAGE;
- imeLocales.add(localeInfo);
+ String languageTag = subtype.getLanguageTag();
+ if (!languageTagSet.contains(languageTag)) {
+ languageTagSet.add(languageTag);
+ Locale locale = Locale.forLanguageTag(languageTag);
+ LocaleInfo cacheInfo = getLocaleInfo(locale, sLocaleCache);
+ LocaleInfo localeInfo = new LocaleInfo(cacheInfo);
+ localeInfo.mSuggestionFlags |= LocaleInfo.SUGGESTION_TYPE_IME_LANGUAGE;
+ imeLocales.add(localeInfo);
+ }
}
return imeLocales;
}
diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
index 0ba271f..3aa554a 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
@@ -534,7 +534,14 @@
/**
* (boolean) Whether to enable the adapter conversion in RemoteViews
*/
- public static final String REMOTEVIEWS_ADAPTER_CONVERSION = "remoteviews_adapter_conversion";
+ public static final String REMOTEVIEWS_ADAPTER_CONVERSION =
+ "CursorControlFeature__remoteviews_adapter_conversion";
+
+ /**
+ * The key name used in app core settings for {@link #REMOTEVIEWS_ADAPTER_CONVERSION}
+ */
+ public static final String KEY_REMOTEVIEWS_ADAPTER_CONVERSION =
+ "systemui__remoteviews_adapter_conversion";
/**
* Default value for whether the adapter conversion is enabled or not. This is set for
diff --git a/core/java/com/android/internal/widget/ConversationLayout.java b/core/java/com/android/internal/widget/ConversationLayout.java
index 635adca..7dda91d 100644
--- a/core/java/com/android/internal/widget/ConversationLayout.java
+++ b/core/java/com/android/internal/widget/ConversationLayout.java
@@ -383,7 +383,11 @@
updateContentEndPaddings();
}
- @RemotableViewMethod
+ /**
+ * Set conversation data
+ * @param extras Bundle contains conversation data
+ */
+ @RemotableViewMethod(asyncImpl = "setDataAsync")
public void setData(Bundle extras) {
Parcelable[] messages = extras.getParcelableArray(Notification.EXTRA_MESSAGES);
List<Notification.MessagingStyle.Message> newMessages
@@ -393,8 +397,7 @@
= Notification.MessagingStyle.Message.getMessagesFromBundleArray(histMessages);
// mUser now set (would be nice to avoid the side effect but WHATEVER)
- setUser(extras.getParcelable(Notification.EXTRA_MESSAGING_PERSON, android.app.Person.class));
-
+ final Person user = extras.getParcelable(Notification.EXTRA_MESSAGING_PERSON, Person.class);
// Append remote input history to newMessages (again, side effect is lame but WHATEVS)
RemoteInputHistoryItem[] history = (RemoteInputHistoryItem[])
extras.getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS, android.app.RemoteInputHistoryItem.class);
@@ -402,11 +405,30 @@
boolean showSpinner =
extras.getBoolean(Notification.EXTRA_SHOW_REMOTE_INPUT_SPINNER, false);
- // bind it, baby
- bind(newMessages, newHistoricMessages, showSpinner);
-
int unreadCount = extras.getInt(Notification.EXTRA_CONVERSATION_UNREAD_MESSAGE_COUNT);
- setUnreadCount(unreadCount);
+
+ // convert MessagingStyle.Message to MessagingMessage, re-using ones from a previous binding
+ // if they exist
+ final List<MessagingMessage> newMessagingMessages =
+ createMessages(newMessages, false /* isHistoric */);
+ final List<MessagingMessage> newHistoricMessagingMessages =
+ createMessages(newHistoricMessages, true /* isHistoric */);
+ // bind it, baby
+ bindViews(user, showSpinner, unreadCount,
+ newMessagingMessages,
+ newHistoricMessagingMessages);
+ }
+
+ /**
+ * RemotableViewMethod's asyncImpl of {@link #setData(Bundle)}.
+ * This should be called on a background thread, and returns a Runnable which is then must be
+ * called on the main thread to complete the operation and set text.
+ * @param extras Bundle contains conversation data
+ * @hide
+ */
+ @NonNull
+ public Runnable setDataAsync(Bundle extras) {
+ return () -> setData(extras);
}
@Override
@@ -436,15 +458,17 @@
}
}
- private void bind(List<Notification.MessagingStyle.Message> newMessages,
- List<Notification.MessagingStyle.Message> newHistoricMessages,
- boolean showSpinner) {
- // convert MessagingStyle.Message to MessagingMessage, re-using ones from a previous binding
- // if they exist
- List<MessagingMessage> historicMessages = createMessages(newHistoricMessages,
- true /* isHistoric */);
- List<MessagingMessage> messages = createMessages(newMessages, false /* isHistoric */);
+ private void bindViews(Person user,
+ boolean showSpinner, int unreadCount, List<MessagingMessage> newMessagingMessages,
+ List<MessagingMessage> newHistoricMessagingMessages) {
+ setUser(user);
+ setUnreadCount(unreadCount);
+ bind(showSpinner, newMessagingMessages, newHistoricMessagingMessages);
+ }
+
+ private void bind(boolean showSpinner, List<MessagingMessage> messages,
+ List<MessagingMessage> historicMessages) {
// Copy our groups, before they get clobbered
ArrayList<MessagingGroup> oldGroups = new ArrayList<>(mGroups);
diff --git a/core/java/com/android/internal/widget/MessagingLayout.java b/core/java/com/android/internal/widget/MessagingLayout.java
index 9d142f6..8345c5c 100644
--- a/core/java/com/android/internal/widget/MessagingLayout.java
+++ b/core/java/com/android/internal/widget/MessagingLayout.java
@@ -156,7 +156,11 @@
mConversationTitle = conversationTitle;
}
- @RemotableViewMethod
+ /**
+ * Set Messaging data
+ * @param extras Bundle contains messaging data
+ */
+ @RemotableViewMethod(asyncImpl = "setDataAsync")
public void setData(Bundle extras) {
Parcelable[] messages = extras.getParcelableArray(Notification.EXTRA_MESSAGES);
List<Notification.MessagingStyle.Message> newMessages
@@ -168,9 +172,28 @@
RemoteInputHistoryItem[] history = (RemoteInputHistoryItem[])
extras.getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS, android.app.RemoteInputHistoryItem.class);
addRemoteInputHistoryToMessages(newMessages, history);
+
+ final Person user = extras.getParcelable(Notification.EXTRA_MESSAGING_PERSON, Person.class);
boolean showSpinner =
extras.getBoolean(Notification.EXTRA_SHOW_REMOTE_INPUT_SPINNER, false);
- bind(newMessages, newHistoricMessages, showSpinner);
+
+ final List<MessagingMessage> historicMessagingMessages = createMessages(newHistoricMessages,
+ true /* isHistoric */);
+ final List<MessagingMessage> newMessagingMessages =
+ createMessages(newMessages, false /* isHistoric */);
+ bindViews(user, showSpinner, historicMessagingMessages, newMessagingMessages);
+ }
+
+ /**
+ * RemotableViewMethod's asyncImpl of {@link #setData(Bundle)}.
+ * This should be called on a background thread, and returns a Runnable which is then must be
+ * called on the main thread to complete the operation and set text.
+ * @param extras Bundle contains messaging data
+ * @hide
+ */
+ @NonNull
+ public Runnable setDataAsync(Bundle extras) {
+ return () -> setData(extras);
}
@Override
@@ -195,14 +218,15 @@
}
}
- private void bind(List<Notification.MessagingStyle.Message> newMessages,
- List<Notification.MessagingStyle.Message> newHistoricMessages,
- boolean showSpinner) {
+ private void bindViews(Person user, boolean showSpinner,
+ List<MessagingMessage> historicMessagingMessages,
+ List<MessagingMessage> newMessagingMessages) {
+ setUser(user);
+ bind(showSpinner, historicMessagingMessages, newMessagingMessages);
+ }
- List<MessagingMessage> historicMessages = createMessages(newHistoricMessages,
- true /* isHistoric */);
- List<MessagingMessage> messages = createMessages(newMessages, false /* isHistoric */);
-
+ private void bind(boolean showSpinner, List<MessagingMessage> historicMessages,
+ List<MessagingMessage> messages) {
ArrayList<MessagingGroup> oldGroups = new ArrayList<>(mGroups);
addMessagesToGroups(historicMessages, messages, showSpinner);
diff --git a/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp b/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp
index a95b6e3..76f5c10 100644
--- a/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp
+++ b/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp
@@ -127,16 +127,17 @@
}
}
-static void throwReadRE(JNIEnv *env, binder_status_t status) {
+static void throwReadException(JNIEnv *env, binder_status_t status) {
ALOGE("Could not read LongArrayMultiStateCounter from Parcel, status = %d", status);
- jniThrowRuntimeException(env, "Could not read LongArrayMultiStateCounter from Parcel");
+ jniThrowException(env, "android.os.BadParcelableException",
+ "Could not read LongArrayMultiStateCounter from Parcel");
}
#define THROW_AND_RETURN_ON_READ_ERROR(expr) \
{ \
binder_status_t status = expr; \
if (status != STATUS_OK) { \
- throwReadRE(env, status); \
+ throwReadException(env, status); \
return 0L; \
} \
}
@@ -147,6 +148,11 @@
int32_t stateCount;
THROW_AND_RETURN_ON_READ_ERROR(AParcel_readInt32(parcel.get(), &stateCount));
+ if (stateCount < 0 || stateCount > 0xEFFF) {
+ throwReadException(env, STATUS_INVALID_OPERATION);
+ return 0L;
+ }
+
int32_t arrayLength;
THROW_AND_RETURN_ON_READ_ERROR(AParcel_readInt32(parcel.get(), &arrayLength));
diff --git a/core/jni/com_android_internal_os_LongMultiStateCounter.cpp b/core/jni/com_android_internal_os_LongMultiStateCounter.cpp
index 1712b3a8..ddf7a67 100644
--- a/core/jni/com_android_internal_os_LongMultiStateCounter.cpp
+++ b/core/jni/com_android_internal_os_LongMultiStateCounter.cpp
@@ -131,16 +131,17 @@
}
}
-static void throwReadRE(JNIEnv *env, binder_status_t status) {
+static void throwReadException(JNIEnv *env, binder_status_t status) {
ALOGE("Could not read LongMultiStateCounter from Parcel, status = %d", status);
- jniThrowRuntimeException(env, "Could not read LongMultiStateCounter from Parcel");
+ jniThrowException(env, "android.os.BadParcelableException",
+ "Could not read LongMultiStateCounter from Parcel");
}
#define THROW_AND_RETURN_ON_READ_ERROR(expr) \
{ \
binder_status_t status = expr; \
if (status != STATUS_OK) { \
- throwReadRE(env, status); \
+ throwReadException(env, status); \
return 0L; \
} \
}
@@ -151,6 +152,11 @@
int32_t stateCount;
THROW_AND_RETURN_ON_READ_ERROR(AParcel_readInt32(parcel.get(), &stateCount));
+ if (stateCount < 0 || stateCount > 0xEFFF) {
+ throwReadException(env, STATUS_INVALID_OPERATION);
+ return 0L;
+ }
+
auto counter = std::make_unique<battery::LongMultiStateCounter>(stateCount, 0);
for (battery::state_t state = 0; state < stateCount; state++) {
diff --git a/core/proto/android/server/windowmanagertransitiontrace.proto b/core/proto/android/server/windowmanagertransitiontrace.proto
index a950a79..34ccb48 100644
--- a/core/proto/android/server/windowmanagertransitiontrace.proto
+++ b/core/proto/android/server/windowmanagertransitiontrace.proto
@@ -56,6 +56,7 @@
repeated Target targets = 8;
optional int32 flags = 9;
optional int64 abort_time_ns = 10;
+ optional int64 starting_window_remove_time_ns = 11;
}
message Target {
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 130648c..cbc6c2a 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -5336,6 +5336,12 @@
<!-- Default value for performant auth feature. -->
<bool name="config_performantAuthDefault">false</bool>
+ <!-- Threshold for false rejection rate (FRR) of biometric authentication. Applies for both
+ fingerprint and face. If a dual-modality device only enrolled a single biometric and
+ experiences high FRR (above threshold), system notification will be sent to encourage user
+ to enroll the other eligible biometric. -->
+ <fraction name="config_biometricNotificationFrrThreshold">30%</fraction>
+
<!-- The component name for the default profile supervisor, which can be set as a profile owner
even after user setup is complete. The defined component should be used for supervision purposes
only. The component must be part of a system app. -->
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index f795bd7..33c18c2 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1831,6 +1831,8 @@
<string name="fingerprint_error_not_match">Fingerprint not recognized</string>
<!-- Message shown when UDFPS fails to match -->
<string name="fingerprint_udfps_error_not_match">Fingerprint not recognized</string>
+ <!-- Message shown to inform the user a face cannot be recognized and fingerprint should instead be used.[CHAR LIMIT=50] -->
+ <string name="fingerprint_dialog_use_fingerprint_instead">Can\u2019t recognize face. Use fingerprint instead.</string>
<!-- Accessibility message announced when a fingerprint has been authenticated [CHAR LIMIT=NONE] -->
<string name="fingerprint_authenticated">Fingerprint authenticated</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 3610ead..8de1304 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2581,6 +2581,9 @@
<java-symbol type="string" name="biometric_error_device_not_secured" />
<java-symbol type="string" name="biometric_error_generic" />
+ <!-- Biometric FRR config -->
+ <java-symbol type="fraction" name="config_biometricNotificationFrrThreshold" />
+
<!-- Device credential strings for BiometricManager -->
<java-symbol type="string" name="screen_lock_app_setting_name" />
<java-symbol type="string" name="screen_lock_dialog_default_subtitle" />
@@ -2594,6 +2597,7 @@
<java-symbol type="string" name="fingerprint_error_vendor_unknown" />
<java-symbol type="string" name="fingerprint_error_not_match" />
<java-symbol type="string" name="fingerprint_udfps_error_not_match" />
+ <java-symbol type="string" name="fingerprint_dialog_use_fingerprint_instead" />
<java-symbol type="string" name="fingerprint_acquired_partial" />
<java-symbol type="string" name="fingerprint_acquired_insufficient" />
<java-symbol type="string" name="fingerprint_acquired_imager_dirty" />
diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml
index 129de64..31755ef 100644
--- a/core/tests/coretests/AndroidManifest.xml
+++ b/core/tests/coretests/AndroidManifest.xml
@@ -231,6 +231,28 @@
</intent-filter>
</activity>
+ <activity android:name="android.widget.HorizontalScrollViewActivity"
+ android:label="HorizontalScrollViewActivity"
+ android:screenOrientation="portrait"
+ android:exported="true"
+ android:theme="@android:style/Theme.Material.Light">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.widget.ScrollViewActivity"
+ android:label="ScrollViewActivity"
+ android:screenOrientation="portrait"
+ android:exported="true"
+ android:theme="@android:style/Theme.Material.Light">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
<activity android:name="android.widget.DatePickerActivity"
android:label="DatePickerActivity"
android:screenOrientation="portrait"
diff --git a/core/tests/coretests/res/layout/activity_horizontal_scroll_view.xml b/core/tests/coretests/res/layout/activity_horizontal_scroll_view.xml
new file mode 100644
index 0000000..866e1a9
--- /dev/null
+++ b/core/tests/coretests/res/layout/activity_horizontal_scroll_view.xml
@@ -0,0 +1,118 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2015 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
+ -->
+
+<HorizontalScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:id="@+id/horizontal_scroll_view">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+
+ <View
+ android:background="#F00"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
+
+ <View
+ android:background="#880"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
+
+ <View
+ android:background="#0F0"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
+
+ <View
+ android:background="#088"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
+
+ <View
+ android:background="#00F"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
+
+ <View
+ android:background="#808"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
+
+ <View
+ android:background="#F00"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
+
+ <View
+ android:background="#880"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
+
+ <View
+ android:background="#0F0"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
+
+ <View
+ android:background="#088"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
+
+ <View
+ android:background="#00F"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
+
+ <View
+ android:background="#808"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
+
+ <View
+ android:background="#F00"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
+
+ <View
+ android:background="#880"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
+
+ <View
+ android:background="#0F0"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
+
+ <View
+ android:background="#088"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
+
+ <View
+ android:background="#00F"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
+
+ <View
+ android:background="#808"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
+
+ </LinearLayout>
+</HorizontalScrollView>
diff --git a/core/tests/coretests/res/layout/activity_scroll_view.xml b/core/tests/coretests/res/layout/activity_scroll_view.xml
new file mode 100644
index 0000000..61fabf8
--- /dev/null
+++ b/core/tests/coretests/res/layout/activity_scroll_view.xml
@@ -0,0 +1,118 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2015 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
+ -->
+
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:id="@+id/scroll_view">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <View
+ android:background="#F00"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
+
+ <View
+ android:background="#880"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
+
+ <View
+ android:background="#0F0"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
+
+ <View
+ android:background="#088"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
+
+ <View
+ android:background="#00F"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
+
+ <View
+ android:background="#808"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
+
+ <View
+ android:background="#F00"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
+
+ <View
+ android:background="#880"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
+
+ <View
+ android:background="#0F0"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
+
+ <View
+ android:background="#088"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
+
+ <View
+ android:background="#00F"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
+
+ <View
+ android:background="#808"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
+
+ <View
+ android:background="#F00"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
+
+ <View
+ android:background="#880"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
+
+ <View
+ android:background="#0F0"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
+
+ <View
+ android:background="#088"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
+
+ <View
+ android:background="#00F"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
+
+ <View
+ android:background="#808"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
+
+ </LinearLayout>
+</ScrollView>
diff --git a/core/tests/coretests/src/android/app/NotificationChannelTest.java b/core/tests/coretests/src/android/app/NotificationChannelTest.java
index 647bfe8..d8305f0 100644
--- a/core/tests/coretests/src/android/app/NotificationChannelTest.java
+++ b/core/tests/coretests/src/android/app/NotificationChannelTest.java
@@ -16,19 +16,52 @@
package android.app;
+import static com.google.common.truth.Truth.assertThat;
+
import static junit.framework.TestCase.assertEquals;
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.content.AttributionSource;
+import android.content.ContentProvider;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.IContentProvider;
+import android.content.pm.ApplicationInfo;
+import android.database.MatrixCursor;
+import android.media.AudioAttributes;
import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Bundle;
import android.os.Parcel;
+import android.os.RemoteCallback;
+import android.os.RemoteException;
+import android.provider.MediaStore.Audio.AudioColumns;
+import android.test.mock.MockContentResolver;
+import android.util.Xml;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
import com.google.common.base.Strings;
+import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
import java.lang.reflect.Field;
@RunWith(AndroidJUnit4.class)
@@ -36,6 +69,88 @@
public class NotificationChannelTest {
private final String CLASS = "android.app.NotificationChannel";
+ Context mContext;
+ ContentProvider mContentProvider;
+ IContentProvider mIContentProvider;
+
+ @Before
+ public void setUp() throws Exception {
+ mContext = mock(Context.class);
+ when(mContext.getApplicationInfo()).thenReturn(new ApplicationInfo());
+ MockContentResolver mContentResolver = new MockContentResolver(mContext);
+ when(mContext.getContentResolver()).thenReturn(mContentResolver);
+ mContentProvider = mock(ContentProvider.class);
+ mIContentProvider = mock(IContentProvider.class);
+ when(mContentProvider.getIContentProvider()).thenReturn(mIContentProvider);
+ doAnswer(
+ invocation -> {
+ AttributionSource attributionSource = invocation.getArgument(0);
+ Uri uri = invocation.getArgument(1);
+ RemoteCallback cb = invocation.getArgument(2);
+ IContentProvider mock = (IContentProvider) (invocation.getMock());
+ AsyncTask.SERIAL_EXECUTOR.execute(
+ () -> {
+ final Bundle bundle = new Bundle();
+ try {
+ bundle.putParcelable(
+ ContentResolver.REMOTE_CALLBACK_RESULT,
+ mock.canonicalize(attributionSource, uri));
+ } catch (RemoteException e) {
+ /* consume */
+ }
+ cb.sendResult(bundle);
+ });
+ return null;
+ })
+ .when(mIContentProvider)
+ .canonicalizeAsync(any(), any(), any());
+ doAnswer(
+ invocation -> {
+ AttributionSource attributionSource = invocation.getArgument(0);
+ Uri uri = invocation.getArgument(1);
+ RemoteCallback cb = invocation.getArgument(2);
+ IContentProvider mock = (IContentProvider) (invocation.getMock());
+ AsyncTask.SERIAL_EXECUTOR.execute(
+ () -> {
+ final Bundle bundle = new Bundle();
+ try {
+ bundle.putParcelable(
+ ContentResolver.REMOTE_CALLBACK_RESULT,
+ mock.uncanonicalize(attributionSource, uri));
+ } catch (RemoteException e) {
+ /* consume */
+ }
+ cb.sendResult(bundle);
+ });
+ return null;
+ })
+ .when(mIContentProvider)
+ .uncanonicalizeAsync(any(), any(), any());
+ doAnswer(
+ invocation -> {
+ Uri uri = invocation.getArgument(0);
+ RemoteCallback cb = invocation.getArgument(1);
+ IContentProvider mock = (IContentProvider) (invocation.getMock());
+ AsyncTask.SERIAL_EXECUTOR.execute(
+ () -> {
+ final Bundle bundle = new Bundle();
+ try {
+ bundle.putString(
+ ContentResolver.REMOTE_CALLBACK_RESULT,
+ mock.getType(uri));
+ } catch (RemoteException e) {
+ /* consume */
+ }
+ cb.sendResult(bundle);
+ });
+ return null;
+ })
+ .when(mIContentProvider)
+ .getTypeAsync(any(), any());
+
+ mContentResolver.addProvider("media", mContentProvider);
+ }
+
@Test
public void testLongStringFields() {
NotificationChannel channel = new NotificationChannel("id", "name", 3);
@@ -103,4 +218,139 @@
assertEquals(NotificationChannel.MAX_TEXT_LENGTH,
fromParcel.getSound().toString().length());
}
+
+ @Test
+ public void testRestoreSoundUri_customLookup() throws Exception {
+ Uri uriToBeRestoredUncanonicalized = Uri.parse("content://media/1");
+ Uri uriToBeRestoredCanonicalized = Uri.parse("content://media/1?title=Song&canonical=1");
+ Uri uriAfterRestoredUncanonicalized = Uri.parse("content://media/100");
+ Uri uriAfterRestoredCanonicalized = Uri.parse("content://media/100?title=Song&canonical=1");
+
+ NotificationChannel channel = new NotificationChannel("id", "name", 3);
+
+ MatrixCursor cursor = new MatrixCursor(new String[] {"_id"});
+ cursor.addRow(new Object[] {100L});
+
+ when(mIContentProvider.canonicalize(any(), eq(uriToBeRestoredUncanonicalized)))
+ .thenReturn(uriToBeRestoredCanonicalized);
+
+ // Mock the failure of regular uncanonicalize.
+ when(mIContentProvider.uncanonicalize(any(), eq(uriToBeRestoredCanonicalized)))
+ .thenReturn(null);
+
+ // Mock the custom lookup in RingtoneManager.getRingtoneUriForRestore.
+ when(mIContentProvider.query(any(), any(), any(), any(), any())).thenReturn(cursor);
+
+ // Mock the canonicalize in RingtoneManager.getRingtoneUriForRestore.
+ when(mIContentProvider.canonicalize(any(), eq(uriAfterRestoredUncanonicalized)))
+ .thenReturn(uriAfterRestoredCanonicalized);
+
+ assertThat(
+ channel.restoreSoundUri(
+ mContext,
+ uriToBeRestoredUncanonicalized,
+ true,
+ AudioAttributes.USAGE_NOTIFICATION))
+ .isEqualTo(uriAfterRestoredCanonicalized);
+ }
+
+ @Test
+ public void testWriteXmlForBackup_customLookup_notificationUsage() throws Exception {
+ testWriteXmlForBackup_customLookup(
+ AudioAttributes.USAGE_NOTIFICATION, AudioColumns.IS_NOTIFICATION);
+ }
+
+ @Test
+ public void testWriteXmlForBackup_customLookup_alarmUsage() throws Exception {
+ testWriteXmlForBackup_customLookup(AudioAttributes.USAGE_ALARM, AudioColumns.IS_ALARM);
+ }
+
+ @Test
+ public void testWriteXmlForBackup_customLookup_ringtoneUsage() throws Exception {
+ testWriteXmlForBackup_customLookup(
+ AudioAttributes.USAGE_NOTIFICATION_RINGTONE, AudioColumns.IS_RINGTONE);
+ }
+
+ @Test
+ public void testWriteXmlForBackup_customLookup_unknownUsage() throws Exception {
+ testWriteXmlForBackup_customLookup(
+ AudioAttributes.USAGE_UNKNOWN, AudioColumns.IS_NOTIFICATION);
+ }
+
+ private void testWriteXmlForBackup_customLookup(int usage, String customQuerySelection)
+ throws Exception {
+ Uri uriToBeRestoredUncanonicalized = Uri.parse("content://media/1");
+ Uri uriToBeRestoredCanonicalized = Uri.parse("content://media/1?title=Song&canonical=1");
+ Uri uriAfterRestoredUncanonicalized = Uri.parse("content://media/100");
+ Uri uriAfterRestoredCanonicalized = Uri.parse("content://media/100?title=Song&canonical=1");
+
+ AudioAttributes mAudioAttributes =
+ new AudioAttributes.Builder()
+ .setContentType(AudioAttributes.CONTENT_TYPE_UNKNOWN)
+ .setUsage(usage)
+ .setFlags(AudioAttributes.FLAG_AUDIBILITY_ENFORCED)
+ .build();
+
+ NotificationChannel channel = new NotificationChannel("id", "name", 3);
+ channel.setSound(uriToBeRestoredCanonicalized, mAudioAttributes);
+
+ TypedXmlSerializer serializer = Xml.newFastSerializer();
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ serializer.setOutput(new BufferedOutputStream(baos), "utf-8");
+ serializer.startDocument(null, true);
+
+ // mock the canonicalize in writeXmlForBackup -> getSoundForBackup
+ when(mIContentProvider.canonicalize(any(), eq(uriToBeRestoredUncanonicalized)))
+ .thenReturn(uriToBeRestoredCanonicalized);
+ when(mIContentProvider.canonicalize(any(), eq(uriToBeRestoredCanonicalized)))
+ .thenReturn(uriToBeRestoredCanonicalized);
+
+ channel.writeXmlForBackup(serializer, mContext);
+ serializer.endDocument();
+ serializer.flush();
+
+ TypedXmlPullParser parser = Xml.newFastPullParser();
+ byte[] byteArray = baos.toByteArray();
+ parser.setInput(new BufferedInputStream(new ByteArrayInputStream(byteArray)), null);
+ parser.nextTag();
+
+ NotificationChannel targetChannel = new NotificationChannel("id", "name", 3);
+
+ MatrixCursor cursor = new MatrixCursor(new String[] {"_id"});
+ cursor.addRow(new Object[] {100L});
+
+ when(mIContentProvider.canonicalize(any(), eq(uriToBeRestoredCanonicalized)))
+ .thenReturn(uriToBeRestoredCanonicalized);
+
+ // Mock the failure of regular uncanonicalize.
+ when(mIContentProvider.uncanonicalize(any(), eq(uriToBeRestoredCanonicalized)))
+ .thenReturn(null);
+
+ Bundle expectedBundle =
+ ContentResolver.createSqlQueryBundle(
+ customQuerySelection + "=1 AND title=?", new String[] {"Song"}, null);
+
+ // Mock the custom lookup in RingtoneManager.getRingtoneUriForRestore.
+ when(mIContentProvider.query(
+ any(),
+ any(),
+ any(),
+ // any(),
+ argThat(
+ queryBundle -> {
+ return queryBundle != null
+ && expectedBundle
+ .toString()
+ .equals(queryBundle.toString());
+ }),
+ any()))
+ .thenReturn(cursor);
+
+ // Mock the canonicalize in RingtoneManager.getRingtoneUriForRestore.
+ when(mIContentProvider.canonicalize(any(), eq(uriAfterRestoredUncanonicalized)))
+ .thenReturn(uriAfterRestoredCanonicalized);
+
+ targetChannel.populateFromXmlForRestore(parser, true, mContext);
+ assertThat(targetChannel.getSound()).isEqualTo(uriAfterRestoredCanonicalized);
+ }
}
diff --git a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
index 4857741..f9a7148 100644
--- a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
+++ b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
@@ -451,8 +451,10 @@
final Rect bounds = activity.getWindowManager().getCurrentWindowMetrics().getBounds();
assertEquals(activityConfigPortrait.windowConfiguration.getBounds(), bounds);
- // Ensure changes in window configuration bounds are reported
- assertEquals(numOfConfig + 1, activity.mNumOfConfigChanges);
+ // Ensure that Activity#onConfigurationChanged() not be called because the changes in
+ // WindowConfiguration shouldn't be reported, and we only apply the latest Configuration
+ // update in transaction.
+ assertEquals(numOfConfig, activity.mNumOfConfigChanges);
}
@Test
diff --git a/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java b/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
index 8028b14..f1eef75 100644
--- a/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
+++ b/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
@@ -221,8 +221,10 @@
@Test
public void onTouchEvent_startHandwriting_inputConnectionBuilt_stylusMoveInExtendedHWArea() {
+ // The stylus down point is between mTestView1 and mTestView2, but it is within the
+ // extended handwriting area of both views. It is closer to mTestView1.
final int x1 = sHwArea1.right + HW_BOUNDS_OFFSETS_RIGHT_PX / 2;
- final int y1 = sHwArea1.bottom + HW_BOUNDS_OFFSETS_BOTTOM_PX / 2;
+ final int y1 = sHwArea1.bottom + (sHwArea2.top - sHwArea1.bottom) / 3;
MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0);
mHandwritingInitiator.onTouchEvent(stylusEvent1);
@@ -231,10 +233,14 @@
MotionEvent stylusEvent2 = createStylusEvent(ACTION_MOVE, x2, y2, 0);
mHandwritingInitiator.onTouchEvent(stylusEvent2);
- // InputConnection is created after stylus movement.
- mHandwritingInitiator.onInputConnectionCreated(mTestView1);
+ // First create InputConnection for mTestView2 and verify that handwriting is not started.
+ mHandwritingInitiator.onInputConnectionCreated(mTestView2);
+ verify(mHandwritingInitiator, never()).startHandwriting(mTestView2);
- verify(mHandwritingInitiator, times(1)).startHandwriting(mTestView1);
+ // Next create InputConnection for mTextView1. Handwriting is started for this view since
+ // the stylus down point is closest to this view.
+ mHandwritingInitiator.onInputConnectionCreated(mTestView1);
+ verify(mHandwritingInitiator).startHandwriting(mTestView1);
}
@Test
diff --git a/core/tests/coretests/src/android/widget/HorizontalScrollViewActivity.java b/core/tests/coretests/src/android/widget/HorizontalScrollViewActivity.java
new file mode 100644
index 0000000..2101354
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/HorizontalScrollViewActivity.java
@@ -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 android.widget;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+import com.android.frameworks.coretests.R;
+
+/**
+ * An activity for testing the TextView widget.
+ */
+public class HorizontalScrollViewActivity extends Activity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_horizontal_scroll_view);
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/HorizontalScrollViewFunctionalTest.java b/core/tests/coretests/src/android/widget/HorizontalScrollViewFunctionalTest.java
new file mode 100644
index 0000000..86f26e5
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/HorizontalScrollViewFunctionalTest.java
@@ -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 android.widget;
+
+import static org.junit.Assert.assertEquals;
+
+import android.platform.test.annotations.Presubmit;
+import android.util.PollingCheck;
+
+import androidx.test.filters.MediumTest;
+import androidx.test.rule.ActivityTestRule;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.frameworks.coretests.R;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@MediumTest
+@Presubmit
+public class HorizontalScrollViewFunctionalTest {
+ private HorizontalScrollViewActivity mActivity;
+ private HorizontalScrollView mHorizontalScrollView;
+ @Rule
+ public ActivityTestRule<HorizontalScrollViewActivity> mActivityRule = new ActivityTestRule<>(
+ HorizontalScrollViewActivity.class);
+
+ @Before
+ public void setUp() throws Exception {
+ mActivity = mActivityRule.getActivity();
+ mHorizontalScrollView = mActivity.findViewById(R.id.horizontal_scroll_view);
+ }
+
+ @Test
+ public void testScrollAfterFlingTop() {
+ mHorizontalScrollView.scrollTo(100, 0);
+ mHorizontalScrollView.fling(-10000);
+ PollingCheck.waitFor(() -> mHorizontalScrollView.mEdgeGlowLeft.getDistance() > 0);
+ PollingCheck.waitFor(() -> mHorizontalScrollView.mEdgeGlowLeft.getDistance() == 0f);
+ assertEquals(0, mHorizontalScrollView.getScrollX());
+ }
+
+ @Test
+ public void testScrollAfterFlingBottom() {
+ int childWidth = mHorizontalScrollView.getChildAt(0).getWidth();
+ int maxScroll = childWidth - mHorizontalScrollView.getWidth();
+ mHorizontalScrollView.scrollTo(maxScroll - 100, 0);
+ mHorizontalScrollView.fling(10000);
+ PollingCheck.waitFor(() -> mHorizontalScrollView.mEdgeGlowRight.getDistance() > 0);
+ PollingCheck.waitFor(() -> mHorizontalScrollView.mEdgeGlowRight.getDistance() == 0f);
+ assertEquals(maxScroll, mHorizontalScrollView.getScrollX());
+ }
+}
+
diff --git a/core/tests/coretests/src/android/widget/ScrollViewActivity.java b/core/tests/coretests/src/android/widget/ScrollViewActivity.java
new file mode 100644
index 0000000..899d631
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/ScrollViewActivity.java
@@ -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 android.widget;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+import com.android.frameworks.coretests.R;
+
+/**
+ * An activity for testing the TextView widget.
+ */
+public class ScrollViewActivity extends Activity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_scroll_view);
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/ScrollViewFunctionalTest.java b/core/tests/coretests/src/android/widget/ScrollViewFunctionalTest.java
new file mode 100644
index 0000000..a49bb6a
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/ScrollViewFunctionalTest.java
@@ -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 android.widget;
+
+import static org.junit.Assert.assertEquals;
+
+import android.platform.test.annotations.Presubmit;
+import android.util.PollingCheck;
+
+import androidx.test.filters.MediumTest;
+import androidx.test.rule.ActivityTestRule;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.frameworks.coretests.R;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@MediumTest
+@Presubmit
+public class ScrollViewFunctionalTest {
+ private ScrollViewActivity mActivity;
+ private ScrollView mScrollView;
+ @Rule
+ public ActivityTestRule<ScrollViewActivity> mActivityRule = new ActivityTestRule<>(
+ ScrollViewActivity.class);
+
+ @Before
+ public void setUp() throws Exception {
+ mActivity = mActivityRule.getActivity();
+ mScrollView = mActivity.findViewById(R.id.scroll_view);
+ }
+
+ @Test
+ public void testScrollAfterFlingTop() {
+ mScrollView.scrollTo(0, 100);
+ mScrollView.fling(-10000);
+ PollingCheck.waitFor(() -> mScrollView.mEdgeGlowTop.getDistance() > 0);
+ PollingCheck.waitFor(() -> mScrollView.mEdgeGlowTop.getDistance() == 0f);
+ assertEquals(0, mScrollView.getScrollY());
+ }
+
+ @Test
+ public void testScrollAfterFlingBottom() {
+ int childHeight = mScrollView.getChildAt(0).getHeight();
+ int maxScroll = childHeight - mScrollView.getHeight();
+ mScrollView.scrollTo(0, maxScroll - 100);
+ mScrollView.fling(10000);
+ PollingCheck.waitFor(() -> mScrollView.mEdgeGlowBottom.getDistance() > 0);
+ PollingCheck.waitFor(() -> mScrollView.mEdgeGlowBottom.getDistance() == 0f);
+ assertEquals(maxScroll, mScrollView.getScrollY());
+ }
+}
+
diff --git a/core/tests/coretests/src/com/android/internal/os/LongArrayMultiStateCounterTest.java b/core/tests/coretests/src/com/android/internal/os/LongArrayMultiStateCounterTest.java
index 516dee7..faccf1a 100644
--- a/core/tests/coretests/src/com/android/internal/os/LongArrayMultiStateCounterTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/LongArrayMultiStateCounterTest.java
@@ -20,6 +20,7 @@
import static org.junit.Assert.assertThrows;
+import android.os.BadParcelableException;
import android.os.Parcel;
import androidx.test.filters.SmallTest;
@@ -163,6 +164,45 @@
}
@Test
+ public void createFromBadBundle() {
+ Parcel data = Parcel.obtain();
+ int bundleLenPos = data.dataPosition();
+ data.writeInt(0);
+ data.writeInt(0x4C444E42); // BaseBundle.BUNDLE_MAGIC
+
+ int bundleStart = data.dataPosition();
+
+ data.writeInt(1);
+ data.writeString("key");
+ data.writeInt(4);
+ int lazyValueLenPos = data.dataPosition();
+ data.writeInt(0);
+ int lazyValueStart = data.dataPosition();
+ data.writeString("com.android.internal.os.LongArrayMultiStateCounter");
+
+ // Invalid int16 value
+ data.writeInt(0x10000); // stateCount
+ data.writeInt(10); // arrayLength
+ for (int i = 0; i < 0x10000; ++i) {
+ data.writeLong(0);
+ }
+
+ backPatchLength(data, lazyValueLenPos, lazyValueStart);
+ backPatchLength(data, bundleLenPos, bundleStart);
+ data.setDataPosition(0);
+
+ assertThrows(BadParcelableException.class,
+ () -> data.readBundle().getParcelable("key", LongArrayMultiStateCounter.class));
+ }
+
+ private static void backPatchLength(Parcel parcel, int lengthPos, int startPos) {
+ int endPos = parcel.dataPosition();
+ parcel.setDataPosition(lengthPos);
+ parcel.writeInt(endPos - startPos);
+ parcel.setDataPosition(endPos);
+ }
+
+ @Test
public void combineValues() {
long[] values = new long[] {0, 1, 2, 3, 42};
LongArrayMultiStateCounter.LongArrayContainer container =
diff --git a/core/tests/coretests/src/com/android/internal/os/LongMultiStateCounterTest.java b/core/tests/coretests/src/com/android/internal/os/LongMultiStateCounterTest.java
index fc86ebe..3413753 100644
--- a/core/tests/coretests/src/com/android/internal/os/LongMultiStateCounterTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/LongMultiStateCounterTest.java
@@ -20,6 +20,7 @@
import static org.junit.Assert.assertThrows;
+import android.os.BadParcelableException;
import android.os.Parcel;
import androidx.test.filters.SmallTest;
@@ -210,4 +211,42 @@
assertThrows(RuntimeException.class,
() -> LongMultiStateCounter.CREATOR.createFromParcel(parcel));
}
+
+ @Test
+ public void createFromBadBundle() {
+ Parcel data = Parcel.obtain();
+ int bundleLenPos = data.dataPosition();
+ data.writeInt(0);
+ data.writeInt(0x4C444E42); // BaseBundle.BUNDLE_MAGIC
+
+ int bundleStart = data.dataPosition();
+
+ data.writeInt(1);
+ data.writeString("key");
+ data.writeInt(4);
+ int lazyValueLenPos = data.dataPosition();
+ data.writeInt(0);
+ int lazyValueStart = data.dataPosition();
+ data.writeString("com.android.internal.os.LongMultiStateCounter");
+
+ // Invalid int16 value
+ data.writeInt(0x10000); // stateCount
+ for (int i = 0; i < 0x10000; ++i) {
+ data.writeLong(0);
+ }
+
+ backPatchLength(data, lazyValueLenPos, lazyValueStart);
+ backPatchLength(data, bundleLenPos, bundleStart);
+ data.setDataPosition(0);
+
+ assertThrows(BadParcelableException.class,
+ () -> data.readBundle().getParcelable("key", LongMultiStateCounter.class));
+ }
+
+ private static void backPatchLength(Parcel parcel, int lengthPos, int startPos) {
+ int endPos = parcel.dataPosition();
+ parcel.setDataPosition(lengthPos);
+ parcel.writeInt(endPos - startPos);
+ parcel.setDataPosition(endPos);
+ }
}
diff --git a/data/etc/com.android.settings.xml b/data/etc/com.android.settings.xml
index e278c52..dcc9686 100644
--- a/data/etc/com.android.settings.xml
+++ b/data/etc/com.android.settings.xml
@@ -37,6 +37,7 @@
<permission name="android.permission.MANAGE_USER_OEM_UNLOCK_STATE" />
<permission name="android.permission.MASTER_CLEAR"/>
<permission name="android.permission.MEDIA_CONTENT_CONTROL"/>
+ <permission name="android.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED" />
<permission name="android.permission.MODIFY_PHONE_STATE"/>
<permission name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
<permission name="android.permission.MOVE_PACKAGE"/>
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 94e23e7..71e9263 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -427,12 +427,6 @@
"group": "WM_DEBUG_BACK_PREVIEW",
"at": "com\/android\/server\/wm\/BackNavigationController.java"
},
- "-1715268616": {
- "message": "Last window, removing starting window %s",
- "level": "VERBOSE",
- "group": "WM_DEBUG_STARTING_WINDOW",
- "at": "com\/android\/server\/wm\/ActivityRecord.java"
- },
"-1710206702": {
"message": "Display id=%d is frozen while keyguard locked, return %d",
"level": "VERBOSE",
@@ -691,6 +685,12 @@
"group": "WM_DEBUG_WINDOW_TRANSITIONS",
"at": "com\/android\/server\/wm\/Transition.java"
},
+ "-1449515133": {
+ "message": "Content Recording: stopping active projection for display %d",
+ "level": "ERROR",
+ "group": "WM_DEBUG_CONTENT_RECORDING",
+ "at": "com\/android\/server\/wm\/ContentRecorder.java"
+ },
"-1443029505": {
"message": "SAFE MODE ENABLED (menu=%d s=%d dpad=%d trackball=%d)",
"level": "INFO",
@@ -1279,6 +1279,12 @@
"group": "WM_DEBUG_STATES",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
+ "-921346089": {
+ "message": "Content Recording: Unable to tell MediaProjectionManagerService to stop the active projection for display %d: %s",
+ "level": "ERROR",
+ "group": "WM_DEBUG_CONTENT_RECORDING",
+ "at": "com\/android\/server\/wm\/ContentRecorder.java"
+ },
"-917215012": {
"message": "%s: caller %d is using old GET_TASKS but privileged; allowing",
"level": "WARN",
@@ -2227,12 +2233,6 @@
"group": "WM_DEBUG_CONFIGURATION",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
- "-88873335": {
- "message": "Content Recording: Unable to tell MediaProjectionManagerService to stop the active projection: %s",
- "level": "ERROR",
- "group": "WM_DEBUG_CONTENT_RECORDING",
- "at": "com\/android\/server\/wm\/ContentRecorder.java"
- },
"-87705714": {
"message": "findFocusedWindow: focusedApp=null using new focus @ %s",
"level": "VERBOSE",
@@ -4057,12 +4057,6 @@
"group": "WM_DEBUG_WINDOW_TRANSITIONS",
"at": "com\/android\/server\/wm\/Transition.java"
},
- "1671994402": {
- "message": "Nulling last startingData",
- "level": "VERBOSE",
- "group": "WM_DEBUG_STARTING_WINDOW",
- "at": "com\/android\/server\/wm\/ActivityRecord.java"
- },
"1674747211": {
"message": "%s forcing orientation to %d for display id=%d",
"level": "VERBOSE",
@@ -4243,12 +4237,6 @@
"group": "WM_DEBUG_ORIENTATION",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
- "1853793312": {
- "message": "Notify removed startingWindow %s",
- "level": "VERBOSE",
- "group": "WM_DEBUG_STARTING_WINDOW",
- "at": "com\/android\/server\/wm\/ActivityRecord.java"
- },
"1856783490": {
"message": "resumeTopActivity: Restarting %s",
"level": "DEBUG",
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 f95f3ff..bcbf728 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -242,9 +242,20 @@
return false;
}
+ // Abort if no space to split.
+ final SplitAttributes calculatedSplitAttributes = mPresenter.computeSplitAttributes(
+ task.getTaskProperties(), splitPinRule,
+ splitPinRule.getDefaultSplitAttributes(),
+ getActivitiesMinDimensionsPair(primaryContainer.getTopNonFinishingActivity(),
+ topContainer.getTopNonFinishingActivity()));
+ if (!SplitPresenter.shouldShowSplit(calculatedSplitAttributes)) {
+ Log.w(TAG, "No space to split, abort pinning top ActivityStack.");
+ return false;
+ }
+
// Registers a Split
final SplitPinContainer splitPinContainer = new SplitPinContainer(primaryContainer,
- topContainer, splitPinRule, splitPinRule.getDefaultSplitAttributes());
+ topContainer, splitPinRule, calculatedSplitAttributes);
task.addSplitContainer(splitPinContainer);
// Updates the Split
@@ -263,7 +274,33 @@
@Override
public void unpinTopActivityStack(int taskId){
- // TODO
+ synchronized (mLock) {
+ final TaskContainer task = getTaskContainer(taskId);
+ if (task == null) {
+ Log.e(TAG, "Cannot find the task to unpin, id: " + taskId);
+ return;
+ }
+
+ final SplitPinContainer splitPinContainer = task.getSplitPinContainer();
+ if (splitPinContainer == null) {
+ Log.e(TAG, "No ActivityStack is pinned.");
+ return;
+ }
+
+ // Remove the SplitPinContainer from the task.
+ final TaskFragmentContainer containerToUnpin =
+ splitPinContainer.getSecondaryContainer();
+ task.removeSplitPinContainer();
+
+ // Resets the isolated navigation and updates the container.
+ final TransactionRecord transactionRecord = mTransactionManager.startNewTransaction();
+ final WindowContainerTransaction wct = transactionRecord.getTransaction();
+ mPresenter.setTaskFragmentIsolatedNavigation(wct,
+ containerToUnpin.getTaskFragmentToken(), false /* isolated */);
+ updateContainer(wct, containerToUnpin);
+ transactionRecord.apply(false /* shouldApplyIndependently */);
+ updateCallbackIfNecessary();
+ }
}
@Override
@@ -831,7 +868,8 @@
return true;
}
- if (!isOnReparent && getContainerWithActivity(activity) == null
+ final TaskFragmentContainer container = getContainerWithActivity(activity);
+ if (!isOnReparent && container == null
&& getTaskFragmentTokenFromActivityClientRecord(activity) != null) {
// We can't find the new launched activity in any recorded container, but it is
// currently placed in an embedded TaskFragment. This can happen in two cases:
@@ -843,11 +881,21 @@
return true;
}
- final TaskFragmentContainer container = getContainerWithActivity(activity);
- if (!isOnReparent && container != null
- && container.getTaskContainer().getTopNonFinishingTaskFragmentContainer()
+ // Skip resolving if the activity is on a pinned TaskFragmentContainer.
+ // TODO(b/243518738): skip resolving for overlay container.
+ if (container != null) {
+ final TaskContainer taskContainer = container.getTaskContainer();
+ if (taskContainer.isTaskFragmentContainerPinned(container)) {
+ return true;
+ }
+ }
+
+ final TaskContainer taskContainer = container != null ? container.getTaskContainer() : null;
+ if (!isOnReparent && taskContainer != null
+ && taskContainer.getTopNonFinishingTaskFragmentContainer(false /* includePin */)
!= container) {
- // Do not resolve if the launched activity is not the top-most container in the Task.
+ // Do not resolve if the launched activity is not the top-most container (excludes
+ // the pinned container) in the Task.
return true;
}
@@ -1244,6 +1292,19 @@
@GuardedBy("mLock")
TaskFragmentContainer resolveStartActivityIntent(@NonNull WindowContainerTransaction wct,
int taskId, @NonNull Intent intent, @Nullable Activity launchingActivity) {
+ // Skip resolving if started from pinned TaskFragmentContainer.
+ // TODO(b/243518738): skip resolving for overlay container.
+ if (launchingActivity != null) {
+ final TaskFragmentContainer taskFragmentContainer = getContainerWithActivity(
+ launchingActivity);
+ final TaskContainer taskContainer =
+ taskFragmentContainer != null ? taskFragmentContainer.getTaskContainer() : null;
+ if (taskContainer != null && taskContainer.isTaskFragmentContainerPinned(
+ taskFragmentContainer)) {
+ return null;
+ }
+ }
+
/*
* We will check the following to see if there is any embedding rule matched:
* 1. Whether the new activity intent should always expand.
@@ -1584,6 +1645,13 @@
return;
}
+ // If the secondary container is pinned, it should not be removed.
+ final SplitContainer activeContainer =
+ getActiveSplitForContainer(existingSplitContainer.getSecondaryContainer());
+ if (activeContainer instanceof SplitPinContainer) {
+ return;
+ }
+
existingSplitContainer.getSecondaryContainer().finish(
false /* shouldFinishDependent */, mPresenter, wct, this);
}
@@ -1625,12 +1693,7 @@
// 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.
@@ -1643,6 +1706,7 @@
// If the info is not available yet the task fragment will be expanded when it's ready
return;
}
+ final SplitContainer splitContainer = getActiveSplitForContainer(container);
if (splitContainer == null) {
return;
}
@@ -1826,6 +1890,10 @@
// Don't launch placeholder for primary split container.
return false;
}
+ if (splitContainer instanceof SplitPinContainer) {
+ // Don't launch placeholder if pinned
+ return false;
+ }
return true;
}
@@ -2072,8 +2140,9 @@
* Returns {@code true} if an Activity with the provided component name should always be
* expanded to occupy full task bounds. Such activity must not be put in a split.
*/
+ @VisibleForTesting
@GuardedBy("mLock")
- private boolean shouldExpand(@Nullable Activity activity, @Nullable Intent intent) {
+ boolean shouldExpand(@Nullable Activity activity, @Nullable Intent intent) {
for (EmbeddingRule rule : mSplitRules) {
if (!(rule instanceof ActivityRule)) {
continue;
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 969e3ed..463c8ce 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
@@ -179,8 +179,16 @@
@Nullable
TaskFragmentContainer getTopNonFinishingTaskFragmentContainer() {
+ return getTopNonFinishingTaskFragmentContainer(true /* includePin */);
+ }
+
+ @Nullable
+ TaskFragmentContainer getTopNonFinishingTaskFragmentContainer(boolean includePin) {
for (int i = mContainers.size() - 1; i >= 0; i--) {
final TaskFragmentContainer container = mContainers.get(i);
+ if (!includePin && isTaskFragmentContainerPinned(container)) {
+ continue;
+ }
if (!container.isFinished()) {
return container;
}
@@ -257,8 +265,25 @@
}
void removeSplitPinContainer() {
+ if (mSplitPinContainer == null) {
+ return;
+ }
+
+ final TaskFragmentContainer primaryContainer = mSplitPinContainer.getPrimaryContainer();
+ final TaskFragmentContainer secondaryContainer = mSplitPinContainer.getSecondaryContainer();
mSplitContainers.remove(mSplitPinContainer);
mSplitPinContainer = null;
+
+ // Remove the other SplitContainers that contains the unpinned container (unless it
+ // is the current top-most split-pair), since the state are no longer valid.
+ final List<SplitContainer> splitsToRemove = new ArrayList<>();
+ for (SplitContainer splitContainer : mSplitContainers) {
+ if (splitContainer.getSecondaryContainer().equals(secondaryContainer)
+ && !splitContainer.getPrimaryContainer().equals(primaryContainer)) {
+ splitsToRemove.add(splitContainer);
+ }
+ }
+ removeSplitContainers(splitsToRemove);
}
@Nullable
@@ -266,6 +291,11 @@
return mSplitPinContainer;
}
+ boolean isTaskFragmentContainerPinned(@NonNull TaskFragmentContainer taskFragmentContainer) {
+ return mSplitPinContainer != null
+ && mSplitPinContainer.getSecondaryContainer() == taskFragmentContainer;
+ }
+
void addTaskFragmentContainer(@NonNull TaskFragmentContainer taskFragmentContainer) {
mContainers.add(taskFragmentContainer);
onTaskFragmentContainerUpdated();
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 9af1fe91..b2ffad7 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
@@ -595,6 +595,18 @@
}
@Test
+ public void testResolveStartActivityIntent_skipIfPinned() {
+ final TaskFragmentContainer container = createMockTaskFragmentContainer(mActivity);
+ final TaskContainer taskContainer = container.getTaskContainer();
+ spyOn(taskContainer);
+ final Intent intent = new Intent();
+ setupSplitRule(mActivity, intent);
+ doReturn(true).when(taskContainer).isTaskFragmentContainerPinned(container);
+ assertNull(mSplitController.resolveStartActivityIntent(mTransaction, TASK_ID, intent,
+ mActivity));
+ }
+
+ @Test
public void testPlaceActivityInTopContainer() {
mSplitController.placeActivityInTopContainer(mTransaction, mActivity);
@@ -1044,6 +1056,29 @@
}
@Test
+ public void testResolveActivityToContainer_skipIfNonTopOrPinned() {
+ final TaskFragmentContainer container = createMockTaskFragmentContainer(mActivity);
+ final Activity pinnedActivity = createMockActivity();
+ final TaskFragmentContainer topContainer = mSplitController.newContainer(pinnedActivity,
+ TASK_ID);
+ final TaskContainer taskContainer = container.getTaskContainer();
+ spyOn(taskContainer);
+ doReturn(container).when(taskContainer).getTopNonFinishingTaskFragmentContainer(false);
+ doReturn(true).when(taskContainer).isTaskFragmentContainerPinned(topContainer);
+
+ // No need to handle when the new launched activity is in a pinned TaskFragment.
+ assertTrue(mSplitController.resolveActivityToContainer(mTransaction, pinnedActivity,
+ false /* isOnReparent */));
+ verify(mSplitController, never()).shouldExpand(any(), any());
+
+ // Should proceed to resolve if the new launched activity is in the next top TaskFragment
+ // (e.g. the top-most TaskFragment is pinned)
+ mSplitController.resolveActivityToContainer(mTransaction, mActivity,
+ false /* isOnReparent */);
+ verify(mSplitController).shouldExpand(any(), any());
+ }
+
+ @Test
public void testGetPlaceholderOptions() {
// Setup to make sure a transaction record is started.
mTransactionManager.startNewTransaction();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java
index edefe9e..74a243d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java
@@ -371,7 +371,15 @@
@Override
public void onBackCancelled() {
- mProgressAnimator.onBackCancelled(CrossActivityAnimation.this::finishAnimation);
+ mProgressAnimator.onBackCancelled(() -> {
+ // mProgressAnimator can reach finish stage earlier than mLeavingProgressSpring,
+ // and if we release all animation leash first, the leavingProgressSpring won't
+ // able to update the animation anymore, which cause flicker.
+ // Here should force update the closing animation target to the final stage before
+ // release it.
+ setLeavingProgress(0);
+ finishAnimation();
+ });
}
@Override
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 99c8acd..a70cf00 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
@@ -25,7 +25,6 @@
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_CONTROLLER;
-import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_GESTURE;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.wm.shell.bubbles.Bubbles.DISMISS_BLOCKED;
@@ -37,6 +36,7 @@
import static com.android.wm.shell.bubbles.Bubbles.DISMISS_PACKAGE_REMOVED;
import static com.android.wm.shell.bubbles.Bubbles.DISMISS_SHORTCUT_REMOVED;
import static com.android.wm.shell.bubbles.Bubbles.DISMISS_USER_CHANGED;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES;
import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_BUBBLES;
import android.annotation.BinderThread;
@@ -85,6 +85,7 @@
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.protolog.common.ProtoLog;
import com.android.internal.statusbar.IStatusBarService;
import com.android.launcher3.icons.BubbleIconFactory;
import com.android.wm.shell.R;
@@ -1004,9 +1005,7 @@
}
private void onNotificationPanelExpandedChanged(boolean expanded) {
- if (DEBUG_BUBBLE_GESTURE) {
- Log.d(TAG, "onNotificationPanelExpandedChanged: expanded=" + expanded);
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "onNotificationPanelExpandedChanged: expanded=%b", expanded);
if (mStackView != null && mStackView.isExpanded()) {
if (expanded) {
mStackView.stopMonitoringSwipeUpGesture();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDebugConfig.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDebugConfig.java
index dce6b56..250e010 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDebugConfig.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDebugConfig.java
@@ -47,7 +47,6 @@
static final boolean DEBUG_USER_EDUCATION = false;
static final boolean DEBUG_POSITIONER = false;
public static final boolean DEBUG_COLLAPSE_ANIMATOR = false;
- static final boolean DEBUG_BUBBLE_GESTURE = false;
public static boolean DEBUG_EXPANDED_VIEW_DRAGGING = false;
private static final boolean FORCE_SHOW_USER_EDUCATION = false;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index f58b121..da5974f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -21,11 +21,11 @@
import static com.android.wm.shell.animation.Interpolators.ALPHA_IN;
import static com.android.wm.shell.animation.Interpolators.ALPHA_OUT;
-import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_GESTURE;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_STACK_VIEW;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.wm.shell.bubbles.BubblePositioner.NUM_VISIBLE_WHEN_RESTING;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -75,6 +75,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.policy.ScreenDecorationsUtils;
+import com.android.internal.protolog.common.ProtoLog;
import com.android.internal.util.FrameworkStatsLog;
import com.android.wm.shell.R;
import com.android.wm.shell.animation.Interpolators;
@@ -2024,9 +2025,7 @@
* Monitor for swipe up gesture that is used to collapse expanded view
*/
void startMonitoringSwipeUpGesture() {
- if (DEBUG_BUBBLE_GESTURE) {
- Log.d(TAG, "startMonitoringSwipeUpGesture");
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "startMonitoringSwipeUpGesture");
stopMonitoringSwipeUpGestureInternal();
if (isGestureNavEnabled()) {
@@ -2046,9 +2045,7 @@
* Stop monitoring for swipe up gesture
*/
void stopMonitoringSwipeUpGesture() {
- if (DEBUG_BUBBLE_GESTURE) {
- Log.d(TAG, "stopMonitoringSwipeUpGesture");
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "stopMonitoringSwipeUpGesture");
stopMonitoringSwipeUpGestureInternal();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesNavBarGestureTracker.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesNavBarGestureTracker.java
index 3a3a378..1375684 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesNavBarGestureTracker.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesNavBarGestureTracker.java
@@ -18,10 +18,10 @@
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES;
import android.content.Context;
import android.hardware.input.InputManager;
-import android.util.Log;
import android.view.Choreographer;
import android.view.InputChannel;
import android.view.InputEventReceiver;
@@ -29,6 +29,7 @@
import androidx.annotation.Nullable;
+import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.bubbles.BubblesNavBarMotionEventHandler.MotionEventListener;
/**
@@ -58,9 +59,7 @@
* @param listener listener that is notified of touch events
*/
void start(MotionEventListener listener) {
- if (BubbleDebugConfig.DEBUG_BUBBLE_GESTURE) {
- Log.d(TAG, "start monitoring bubbles swipe up gesture");
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "start monitoring bubbles swipe up gesture");
stopInternal();
@@ -76,9 +75,7 @@
}
void stop() {
- if (BubbleDebugConfig.DEBUG_BUBBLE_GESTURE) {
- Log.d(TAG, "stop monitoring bubbles swipe up gesture");
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "stop monitoring bubbles swipe up gesture");
stopInternal();
}
@@ -94,9 +91,7 @@
}
private void onInterceptTouch() {
- if (BubbleDebugConfig.DEBUG_BUBBLE_GESTURE) {
- Log.d(TAG, "intercept touch event");
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "intercept touch event");
if (mInputMonitor != null) {
mInputMonitor.pilferPointers();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesNavBarMotionEventHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesNavBarMotionEventHandler.java
index 844526c..b7107f0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesNavBarMotionEventHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesNavBarMotionEventHandler.java
@@ -16,19 +16,20 @@
package com.android.wm.shell.bubbles;
-import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_GESTURE;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES;
import android.content.Context;
import android.graphics.PointF;
-import android.util.Log;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.ViewConfiguration;
import androidx.annotation.Nullable;
+import com.android.internal.protolog.common.ProtoLog;
+
/**
* Handles {@link MotionEvent}s for bubbles that begin in the nav bar area
*/
@@ -112,10 +113,8 @@
private boolean isInGestureRegion(MotionEvent ev) {
// Only handles touch events beginning in navigation bar system gesture zone
if (mPositioner.getNavBarGestureZone().contains((int) ev.getX(), (int) ev.getY())) {
- if (DEBUG_BUBBLE_GESTURE) {
- Log.d(TAG, "handling touch y=" + ev.getY()
- + " navBarGestureZone=" + mPositioner.getNavBarGestureZone());
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "handling touch x=%d y=%d navBarGestureZone=%s",
+ (int) ev.getX(), (int) ev.getY(), mPositioner.getNavBarGestureZone());
return true;
}
return false;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ManageEducationView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ManageEducationView.kt
index e95e8e5..1b41f79 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ManageEducationView.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ManageEducationView.kt
@@ -41,9 +41,9 @@
private val ANIMATE_DURATION: Long = 200
private val positioner: BubblePositioner = positioner
- private val manageView by lazy { findViewById<ViewGroup>(R.id.manage_education_view) }
- private val manageButton by lazy { findViewById<Button>(R.id.manage_button) }
- private val gotItButton by lazy { findViewById<Button>(R.id.got_it) }
+ private val manageView by lazy { requireViewById<ViewGroup>(R.id.manage_education_view) }
+ private val manageButton by lazy { requireViewById<Button>(R.id.manage_button) }
+ private val gotItButton by lazy { requireViewById<Button>(R.id.got_it) }
private var isHiding = false
private var realManageButtonRect = Rect()
@@ -122,7 +122,7 @@
manageButton
.setOnClickListener {
hide()
- expandedView.findViewById<View>(R.id.manage_button).performClick()
+ expandedView.requireViewById<View>(R.id.manage_button).performClick()
}
gotItButton.setOnClickListener { hide() }
setOnClickListener { hide() }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt
index d0598cd..5e3a077 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt
@@ -48,9 +48,9 @@
private val positioner: BubblePositioner = positioner
private val controller: BubbleController = controller
- private val view by lazy { findViewById<View>(R.id.stack_education_layout) }
- private val titleTextView by lazy { findViewById<TextView>(R.id.stack_education_title) }
- private val descTextView by lazy { findViewById<TextView>(R.id.stack_education_description) }
+ private val view by lazy { requireViewById<View>(R.id.stack_education_layout) }
+ private val titleTextView by lazy { requireViewById<TextView>(R.id.stack_education_title) }
+ private val descTextView by lazy { requireViewById<TextView>(R.id.stack_education_description) }
var isHiding = false
private set
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipAppOpsListener.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipAppOpsListener.kt
new file mode 100644
index 0000000..a141ff9
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipAppOpsListener.kt
@@ -0,0 +1,79 @@
+/*
+ * 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.common.pip
+
+import android.app.AppOpsManager
+import android.content.Context
+import android.content.pm.PackageManager
+import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.pip.PipUtils
+
+class PipAppOpsListener(
+ private val mContext: Context,
+ private val mCallback: Callback,
+ private val mMainExecutor: ShellExecutor
+) {
+ private val mAppOpsManager: AppOpsManager = checkNotNull(
+ mContext.getSystemService(Context.APP_OPS_SERVICE) as AppOpsManager)
+ private val mAppOpsChangedListener = AppOpsManager.OnOpChangedListener { _, packageName ->
+ try {
+ // Dismiss the PiP once the user disables the app ops setting for that package
+ val topPipActivityInfo = PipUtils.getTopPipActivity(mContext)
+ val componentName = topPipActivityInfo.first ?: return@OnOpChangedListener
+ val userId = topPipActivityInfo.second
+ val appInfo = mContext.packageManager
+ .getApplicationInfoAsUser(packageName, 0, userId)
+ if (appInfo.packageName == componentName.packageName &&
+ mAppOpsManager.checkOpNoThrow(
+ AppOpsManager.OP_PICTURE_IN_PICTURE, appInfo.uid,
+ packageName
+ ) != AppOpsManager.MODE_ALLOWED
+ ) {
+ mMainExecutor.execute { mCallback.dismissPip() }
+ }
+ } catch (e: PackageManager.NameNotFoundException) {
+ // Unregister the listener if the package can't be found
+ unregisterAppOpsListener()
+ }
+ }
+
+ fun onActivityPinned(packageName: String) {
+ // Register for changes to the app ops setting for this package while it is in PiP
+ registerAppOpsListener(packageName)
+ }
+
+ fun onActivityUnpinned() {
+ // Unregister for changes to the previously PiP'ed package
+ unregisterAppOpsListener()
+ }
+
+ private fun registerAppOpsListener(packageName: String) {
+ mAppOpsManager.startWatchingMode(
+ AppOpsManager.OP_PICTURE_IN_PICTURE, packageName,
+ mAppOpsChangedListener
+ )
+ }
+
+ private fun unregisterAppOpsListener() {
+ mAppOpsManager.stopWatchingMode(mAppOpsChangedListener)
+ }
+
+ /** Callback for PipAppOpsListener to request changes to the PIP window. */
+ interface Callback {
+ /** Dismisses the PIP window. */
+ fun dismissPip()
+ }
+}
\ No newline at end of file
diff --git a/core/java/com/android/internal/policy/DividerSnapAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java
similarity index 98%
rename from core/java/com/android/internal/policy/DividerSnapAlgorithm.java
rename to libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java
index a065e2b..1901e0b 100644
--- a/core/java/com/android/internal/policy/DividerSnapAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2015 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,7 +14,7 @@
* limitations under the License.
*/
-package com.android.internal.policy;
+package com.android.wm.shell.common.split;
import static android.view.WindowManager.DOCKED_INVALID;
import static android.view.WindowManager.DOCKED_LEFT;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
index 2dbc444..0b0c693 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
@@ -53,7 +53,6 @@
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.policy.DividerSnapAlgorithm;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.R;
import com.android.wm.shell.animation.Interpolators;
diff --git a/core/java/com/android/internal/policy/DockedDividerUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DockedDividerUtils.java
similarity index 97%
rename from core/java/com/android/internal/policy/DockedDividerUtils.java
rename to libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DockedDividerUtils.java
index b61b9de..f25dfea 100644
--- a/core/java/com/android/internal/policy/DockedDividerUtils.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DockedDividerUtils.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2015 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,7 +14,7 @@
* limitations under the License.
*/
-package com.android.internal.policy;
+package com.android.wm.shell.common.split;
import static android.view.WindowManager.DOCKED_BOTTOM;
import static android.view.WindowManager.DOCKED_INVALID;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
index d3fada3..5d7e532 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
@@ -25,8 +25,8 @@
import static android.view.WindowManager.DOCKED_TOP;
import static com.android.internal.jank.InteractionJankMonitor.CUJ_SPLIT_SCREEN_RESIZE;
-import static com.android.internal.policy.DividerSnapAlgorithm.SnapTarget.FLAG_DISMISS_END;
-import static com.android.internal.policy.DividerSnapAlgorithm.SnapTarget.FLAG_DISMISS_START;
+import static com.android.wm.shell.common.split.DividerSnapAlgorithm.SnapTarget.FLAG_DISMISS_END;
+import static com.android.wm.shell.common.split.DividerSnapAlgorithm.SnapTarget.FLAG_DISMISS_START;
import static com.android.wm.shell.animation.Interpolators.DIM_INTERPOLATOR;
import static com.android.wm.shell.animation.Interpolators.SLOWDOWN_INTERPOLATOR;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
@@ -58,8 +58,6 @@
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.policy.DividerSnapAlgorithm;
-import com.android.internal.policy.DockedDividerUtils;
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.animation.Interpolators;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
index 62b0799..0998e71 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
@@ -342,6 +342,7 @@
if (!mActiveLetterboxEduLayout.updateCompatInfo(taskInfo, taskListener,
showOnDisplay(mActiveLetterboxEduLayout.getDisplayId()))) {
// The layout is no longer eligible to be shown, clear active layout.
+ mActiveLetterboxEduLayout.release();
mActiveLetterboxEduLayout = null;
}
return;
@@ -371,15 +372,9 @@
ShellTaskOrganizer.TaskListener taskListener) {
return new LetterboxEduWindowManager(context, taskInfo,
mSyncQueue, taskListener, mDisplayController.getDisplayLayout(taskInfo.displayId),
- mTransitionsLazy.get(), this::onLetterboxEduDismissed, mDockStateReader,
- mCompatUIConfiguration);
- }
-
- private void onLetterboxEduDismissed(
- Pair<TaskInfo, ShellTaskOrganizer.TaskListener> stateInfo) {
- mActiveLetterboxEduLayout = null;
- // We need to update the UI
- createOrUpdateReachabilityEduLayout(stateInfo.first, stateInfo.second);
+ mTransitionsLazy.get(),
+ stateInfo -> createOrUpdateReachabilityEduLayout(stateInfo.first, stateInfo.second),
+ mDockStateReader, mCompatUIConfiguration);
}
private void createOrUpdateRestartDialogLayout(TaskInfo taskInfo,
@@ -448,6 +443,7 @@
if (!mActiveReachabilityEduLayout.updateCompatInfo(taskInfo, taskListener,
showOnDisplay(mActiveReachabilityEduLayout.getDisplayId()))) {
// The layout is no longer eligible to be shown, remove from active layouts.
+ mActiveReachabilityEduLayout.release();
mActiveReachabilityEduLayout = null;
}
return;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index 422e3b0..1a84f4b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -44,6 +44,7 @@
import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.DockStateReader;
+import com.android.wm.shell.common.FloatingContentCoordinator;
import com.android.wm.shell.common.LaunchAdjacentController;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
@@ -73,7 +74,6 @@
import com.android.wm.shell.keyguard.KeyguardTransitions;
import com.android.wm.shell.onehanded.OneHanded;
import com.android.wm.shell.onehanded.OneHandedController;
-import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.recents.RecentTasks;
import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.recents.RecentsTransitionHandler;
@@ -122,6 +122,12 @@
@WMSingleton
@Provides
+ static FloatingContentCoordinator provideFloatingContentCoordinator() {
+ return new FloatingContentCoordinator();
+ }
+
+ @WMSingleton
+ @Provides
static DisplayController provideDisplayController(Context context,
IWindowManager wmService,
ShellInit shellInit,
@@ -797,7 +803,6 @@
ShellTaskOrganizer shellTaskOrganizer,
Optional<BubbleController> bubblesOptional,
Optional<SplitScreenController> splitScreenOptional,
- Optional<Pip> pipOptional,
FullscreenTaskListener fullscreenTaskListener,
Optional<UnfoldAnimationController> unfoldAnimationController,
Optional<UnfoldTransitionHandler> unfoldTransitionHandler,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index 881c8f5..065e7b0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -325,12 +325,13 @@
Optional<RecentTasksController> recentTasks,
LaunchAdjacentController launchAdjacentController,
Optional<WindowDecorViewModel> windowDecorViewModel,
+ Optional<DesktopTasksController> desktopTasksController,
@ShellMainThread ShellExecutor mainExecutor) {
return new SplitScreenController(context, shellInit, shellCommandHandler, shellController,
shellTaskOrganizer, syncQueue, rootTaskDisplayAreaOrganizer, displayController,
displayImeController, displayInsetsController, dragAndDropController, transitions,
transactionPool, iconProvider, recentTasks, launchAdjacentController,
- windowDecorViewModel, mainExecutor);
+ windowDecorViewModel, desktopTasksController, mainExecutor);
}
//
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java
index 54be901..9bf973f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java
@@ -31,13 +31,13 @@
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.common.pip.PhoneSizeSpecSource;
+import com.android.wm.shell.common.pip.PipAppOpsListener;
import com.android.wm.shell.common.pip.SizeSpecSource;
import com.android.wm.shell.dagger.WMShellBaseModule;
import com.android.wm.shell.dagger.WMSingleton;
import com.android.wm.shell.onehanded.OneHandedController;
import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.pip.PipAnimationController;
-import com.android.wm.shell.pip.PipAppOpsListener;
import com.android.wm.shell.pip.PipBoundsAlgorithm;
import com.android.wm.shell.pip.PipBoundsState;
import com.android.wm.shell.pip.PipDisplayLayoutState;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1SharedModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1SharedModule.java
index f29b3a3..e8fae24 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1SharedModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1SharedModule.java
@@ -21,7 +21,6 @@
import android.os.Handler;
import com.android.internal.logging.UiEventLogger;
-import com.android.wm.shell.common.FloatingContentCoordinator;
import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.dagger.WMSingleton;
import com.android.wm.shell.pip.PipMediaController;
@@ -37,12 +36,6 @@
*/
@Module
public abstract class Pip1SharedModule {
- @WMSingleton
- @Provides
- static FloatingContentCoordinator provideFloatingContentCoordinator() {
- return new FloatingContentCoordinator();
- }
-
// Needs handler for registering broadcast receivers
@WMSingleton
@Provides
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java
index 52c6d20..80ffbb0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java
@@ -29,12 +29,12 @@
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.common.pip.LegacySizeSpecSource;
+import com.android.wm.shell.common.pip.PipAppOpsListener;
import com.android.wm.shell.common.pip.SizeSpecSource;
import com.android.wm.shell.dagger.WMShellBaseModule;
import com.android.wm.shell.dagger.WMSingleton;
import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.pip.PipAnimationController;
-import com.android.wm.shell.pip.PipAppOpsListener;
import com.android.wm.shell.pip.PipDisplayLayoutState;
import com.android.wm.shell.pip.PipMediaController;
import com.android.wm.shell.pip.PipParamsChangedForwarder;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
index db6c258..5b24d7a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
@@ -535,6 +535,11 @@
}
@Override
+ public void onDesktopSplitSelectAnimComplete(RunningTaskInfo taskInfo) {
+
+ }
+
+ @Override
public void stashDesktopApps(int displayId) throws RemoteException {
// Stashing of desktop apps not needed. Apps always launch on desktop
}
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 b15fd91..1d46e75 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
@@ -22,6 +22,7 @@
import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
+import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW
import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED
import android.app.WindowConfiguration.WindowingMode
import android.content.Context
@@ -33,7 +34,6 @@
import android.os.SystemProperties
import android.util.DisplayMetrics.DENSITY_DEFAULT
import android.view.SurfaceControl
-import android.view.SurfaceControl.Transaction
import android.view.WindowManager.TRANSIT_CHANGE
import android.view.WindowManager.TRANSIT_NONE
import android.view.WindowManager.TRANSIT_OPEN
@@ -56,6 +56,7 @@
import com.android.wm.shell.common.annotations.ShellMainThread
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository.VisibleTasksListener
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
+import com.android.wm.shell.splitscreen.SplitScreenController
import com.android.wm.shell.sysui.ShellCommandHandler
import com.android.wm.shell.sysui.ShellController
import com.android.wm.shell.sysui.ShellInit
@@ -105,6 +106,9 @@
get() = context.resources.getDimensionPixelSize(
com.android.wm.shell.R.dimen.desktop_mode_transition_area_height)
+ // This is public to avoid cyclic dependency; it is set by SplitScreenController
+ lateinit var splitScreenController: SplitScreenController
+
init {
desktopMode = DesktopModeImpl()
if (DesktopModeStatus.isProto2Enabled()) {
@@ -262,6 +266,19 @@
}
}
+ /**
+ * Perform needed cleanup transaction once animation is complete. Bounds need to be set
+ * here instead of initial wct to both avoid flicker and to have task bounds to use for
+ * the staging animation.
+ *
+ * @param taskInfo task entering split that requires a bounds update
+ */
+ fun onDesktopSplitSelectAnimComplete(taskInfo: RunningTaskInfo) {
+ val wct = WindowContainerTransaction()
+ wct.setBounds(taskInfo.token, Rect())
+ shellTaskOrganizer.applyTransaction(wct)
+ }
+
/** Move a task with given `taskId` to fullscreen */
fun moveToFullscreen(taskId: Int) {
shellTaskOrganizer.getRunningTaskInfo(taskId)?.let { task -> moveToFullscreen(task) }
@@ -296,7 +313,7 @@
task.taskId
)
val wct = WindowContainerTransaction()
- wct.setBounds(task.token, null)
+ wct.setBounds(task.token, Rect())
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
enterDesktopTaskTransitionHandler.startCancelMoveToDesktopMode(wct,
@@ -533,6 +550,7 @@
)
// Check if we should skip handling this transition
var reason = ""
+ val triggerTask = request.triggerTask
val shouldHandleRequest =
when {
// Only handle open or to front transitions
@@ -541,19 +559,19 @@
false
}
// Only handle when it is a task transition
- request.triggerTask == null -> {
+ triggerTask == null -> {
reason = "triggerTask is null"
false
}
// Only handle standard type tasks
- request.triggerTask.activityType != ACTIVITY_TYPE_STANDARD -> {
- reason = "activityType not handled (${request.triggerTask.activityType})"
+ triggerTask.activityType != ACTIVITY_TYPE_STANDARD -> {
+ reason = "activityType not handled (${triggerTask.activityType})"
false
}
// Only handle fullscreen or freeform tasks
- request.triggerTask.windowingMode != WINDOWING_MODE_FULLSCREEN &&
- request.triggerTask.windowingMode != WINDOWING_MODE_FREEFORM -> {
- reason = "windowingMode not handled (${request.triggerTask.windowingMode})"
+ triggerTask.windowingMode != WINDOWING_MODE_FULLSCREEN &&
+ triggerTask.windowingMode != WINDOWING_MODE_FREEFORM -> {
+ reason = "windowingMode not handled (${triggerTask.windowingMode})"
false
}
// Otherwise process it
@@ -569,17 +587,17 @@
return null
}
- val task: RunningTaskInfo = request.triggerTask
-
- val result = when {
- // If display has tasks stashed, handle as stashed launch
- desktopModeTaskRepository.isStashed(task.displayId) -> handleStashedTaskLaunch(task)
- // Check if fullscreen task should be updated
- task.windowingMode == WINDOWING_MODE_FULLSCREEN -> handleFullscreenTaskLaunch(task)
- // Check if freeform task should be updated
- task.windowingMode == WINDOWING_MODE_FREEFORM -> handleFreeformTaskLaunch(task)
- else -> {
- null
+ val result = triggerTask?.let { task ->
+ when {
+ // If display has tasks stashed, handle as stashed launch
+ desktopModeTaskRepository.isStashed(task.displayId) -> handleStashedTaskLaunch(task)
+ // Check if fullscreen task should be updated
+ task.windowingMode == WINDOWING_MODE_FULLSCREEN -> handleFullscreenTaskLaunch(task)
+ // Check if freeform task should be updated
+ task.windowingMode == WINDOWING_MODE_FREEFORM -> handleFreeformTaskLaunch(task)
+ else -> {
+ null
+ }
}
}
KtProtoLog.v(
@@ -686,13 +704,43 @@
WINDOWING_MODE_FULLSCREEN
}
wct.setWindowingMode(taskInfo.token, targetWindowingMode)
- wct.setBounds(taskInfo.token, null)
+ wct.setBounds(taskInfo.token, Rect())
if (isDesktopDensityOverrideSet()) {
- wct.setDensityDpi(taskInfo.token, getFullscreenDensityDpi())
+ wct.setDensityDpi(taskInfo.token, getDefaultDensityDpi())
}
}
- private fun getFullscreenDensityDpi(): Int {
+ /**
+ * Adds split screen changes to a transaction. Note that bounds are not reset here due to
+ * animation; see {@link onDesktopSplitSelectAnimComplete}
+ */
+ private fun addMoveToSplitChanges(
+ wct: WindowContainerTransaction,
+ taskInfo: RunningTaskInfo
+ ) {
+ wct.setWindowingMode(taskInfo.token, WINDOWING_MODE_MULTI_WINDOW)
+ // The task's density may have been overridden in freeform; revert it here as we don't
+ // want it overridden in multi-window.
+ wct.setDensityDpi(taskInfo.token, getDefaultDensityDpi())
+ }
+
+ /**
+ * Requests a task be transitioned from desktop to split select. Applies needed windowing
+ * changes if this transition is enabled.
+ */
+ fun requestSplit(
+ taskInfo: RunningTaskInfo
+ ) {
+ val windowingMode = taskInfo.windowingMode
+ if (windowingMode == WINDOWING_MODE_FULLSCREEN || windowingMode == WINDOWING_MODE_FREEFORM
+ ) {
+ val wct = WindowContainerTransaction()
+ addMoveToSplitChanges(wct, taskInfo)
+ splitScreenController.requestEnterSplitSelect(taskInfo, wct)
+ }
+ }
+
+ private fun getDefaultDensityDpi(): Int {
return context.resources.displayMetrics.densityDpi
}
@@ -969,6 +1017,13 @@
return result[0]
}
+ override fun onDesktopSplitSelectAnimComplete(taskInfo: RunningTaskInfo) {
+ ExecutorUtils.executeRemoteCallWithTaskPermission(
+ controller,
+ "onDesktopSplitSelectAnimComplete"
+ ) { c -> c.onDesktopSplitSelectAnimComplete(taskInfo) }
+ }
+
override fun setTaskListener(listener: IDesktopTaskListener?) {
KtProtoLog.v(
WM_SHELL_DESKTOP_MODE,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl
index ee3a080..47edfd4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl
@@ -16,6 +16,7 @@
package com.android.wm.shell.desktopmode;
+import android.app.ActivityManager.RunningTaskInfo;
import com.android.wm.shell.desktopmode.IDesktopTaskListener;
/**
@@ -38,6 +39,9 @@
/** Get count of visible desktop tasks on the given display */
int getVisibleTaskCount(int displayId);
+ /** Perform cleanup transactions after the animation to split select is complete */
+ oneway void onDesktopSplitSelectAnimComplete(in RunningTaskInfo taskInfo);
+
/** Set listener that will receive callbacks about updates to desktop tasks */
oneway void setTaskListener(IDesktopTaskListener listener);
}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt
index b9cb5c7..9debb25 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt
@@ -69,7 +69,7 @@
): Boolean {
val change = findRelevantChange(info)
val leash = change.leash
- val taskId = change.taskInfo.taskId
+ val taskId = checkNotNull(change.taskInfo).taskId
val startBounds = change.startAbsBounds
val endBounds = change.endAbsBounds
val windowDecor =
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAppOpsListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAppOpsListener.java
deleted file mode 100644
index 48a3fc2..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAppOpsListener.java
+++ /dev/null
@@ -1,94 +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.wm.shell.pip;
-
-import static android.app.AppOpsManager.MODE_ALLOWED;
-import static android.app.AppOpsManager.OP_PICTURE_IN_PICTURE;
-
-import android.app.AppOpsManager;
-import android.app.AppOpsManager.OnOpChangedListener;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.util.Pair;
-
-import com.android.wm.shell.common.ShellExecutor;
-
-public class PipAppOpsListener {
- private static final String TAG = PipAppOpsListener.class.getSimpleName();
-
- private Context mContext;
- private ShellExecutor mMainExecutor;
- private AppOpsManager mAppOpsManager;
- private Callback mCallback;
-
- private AppOpsManager.OnOpChangedListener mAppOpsChangedListener = new OnOpChangedListener() {
- @Override
- public void onOpChanged(String op, String packageName) {
- try {
- // Dismiss the PiP once the user disables the app ops setting for that package
- final Pair<ComponentName, Integer> topPipActivityInfo =
- PipUtils.getTopPipActivity(mContext);
- if (topPipActivityInfo.first != null) {
- final ApplicationInfo appInfo = mContext.getPackageManager()
- .getApplicationInfoAsUser(packageName, 0, topPipActivityInfo.second);
- if (appInfo.packageName.equals(topPipActivityInfo.first.getPackageName()) &&
- mAppOpsManager.checkOpNoThrow(OP_PICTURE_IN_PICTURE, appInfo.uid,
- packageName) != MODE_ALLOWED) {
- mMainExecutor.execute(() -> mCallback.dismissPip());
- }
- }
- } catch (NameNotFoundException e) {
- // Unregister the listener if the package can't be found
- unregisterAppOpsListener();
- }
- }
- };
-
- public PipAppOpsListener(Context context, Callback callback, ShellExecutor mainExecutor) {
- mContext = context;
- mMainExecutor = mainExecutor;
- mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
- mCallback = callback;
- }
-
- public void onActivityPinned(String packageName) {
- // Register for changes to the app ops setting for this package while it is in PiP
- registerAppOpsListener(packageName);
- }
-
- public void onActivityUnpinned() {
- // Unregister for changes to the previously PiP'ed package
- unregisterAppOpsListener();
- }
-
- private void registerAppOpsListener(String packageName) {
- mAppOpsManager.startWatchingMode(OP_PICTURE_IN_PICTURE, packageName,
- mAppOpsChangedListener);
- }
-
- private void unregisterAppOpsListener() {
- mAppOpsManager.stopWatchingMode(mAppOpsChangedListener);
- }
-
- /** Callback for PipAppOpsListener to request changes to the PIP window. */
- public interface Callback {
- /** Dismisses the PIP window. */
- void dismissPip();
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index f396f3f..b872267 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -75,6 +75,7 @@
import com.android.wm.shell.common.TabletopModeController;
import com.android.wm.shell.common.TaskStackListenerCallback;
import com.android.wm.shell.common.TaskStackListenerImpl;
+import com.android.wm.shell.common.pip.PipAppOpsListener;
import com.android.wm.shell.onehanded.OneHandedController;
import com.android.wm.shell.onehanded.OneHandedTransitionCallback;
import com.android.wm.shell.pip.IPip;
@@ -82,7 +83,6 @@
import com.android.wm.shell.pip.PinnedStackListenerForwarder;
import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.pip.PipAnimationController;
-import com.android.wm.shell.pip.PipAppOpsListener;
import com.android.wm.shell.pip.PipBoundsAlgorithm;
import com.android.wm.shell.pip.PipBoundsState;
import com.android.wm.shell.pip.PipDisplayLayoutState;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
index 43d3f36..b251f6f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
@@ -41,7 +41,7 @@
import com.android.wm.shell.animation.PhysicsAnimator;
import com.android.wm.shell.common.FloatingContentCoordinator;
import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
-import com.android.wm.shell.pip.PipAppOpsListener;
+import com.android.wm.shell.common.pip.PipAppOpsListener;
import com.android.wm.shell.pip.PipBoundsState;
import com.android.wm.shell.pip.PipSnapAlgorithm;
import com.android.wm.shell.pip.PipTaskOrganizer;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
index 02eeb2a..5e583c2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
@@ -47,10 +47,10 @@
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TaskStackListenerCallback;
import com.android.wm.shell.common.TaskStackListenerImpl;
+import com.android.wm.shell.common.pip.PipAppOpsListener;
import com.android.wm.shell.pip.PinnedStackListenerForwarder;
import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.pip.PipAnimationController;
-import com.android.wm.shell.pip.PipAppOpsListener;
import com.android.wm.shell.pip.PipDisplayLayoutState;
import com.android.wm.shell.pip.PipMediaController;
import com.android.wm.shell.pip.PipParamsChangedForwarder;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
index 3af1b75..05e4af3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
@@ -55,6 +55,8 @@
Consts.TAG_WM_SHELL),
WM_SHELL_FOLDABLE(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
Consts.TAG_WM_SHELL),
+ WM_SHELL_BUBBLES(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
+ "Bubbles"),
TEST_GROUP(true, true, false, "WindowManagerShellProtoLogTest");
private final boolean mEnabled;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
index c414e70..14304a3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
@@ -27,6 +27,7 @@
import android.window.RemoteTransition;
import com.android.wm.shell.splitscreen.ISplitScreenListener;
+import com.android.wm.shell.splitscreen.ISplitSelectListener;
/**
* Interface that is exposed to remote callers to manipulate the splitscreen feature.
@@ -44,6 +45,16 @@
oneway void unregisterSplitScreenListener(in ISplitScreenListener listener) = 2;
/**
+ * Registers a split select listener.
+ */
+ oneway void registerSplitSelectListener(in ISplitSelectListener listener) = 20;
+
+ /**
+ * Unregisters a split select listener.
+ */
+ oneway void unregisterSplitSelectListener(in ISplitSelectListener listener) = 21;
+
+ /**
* Removes a task from the side stage.
*/
oneway void removeFromSideStage(int taskId) = 4;
@@ -148,4 +159,4 @@
*/
RemoteAnimationTarget[] onStartingSplitLegacy(in RemoteAnimationTarget[] appTargets) = 14;
}
-// Last id = 19
\ No newline at end of file
+// Last id = 21
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitSelectListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitSelectListener.aidl
new file mode 100644
index 0000000..7171da5
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitSelectListener.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2021 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.splitscreen;
+
+import android.app.ActivityManager.RunningTaskInfo;
+
+/**
+ * Listener interface that Launcher attaches to SystemUI to get split-select callbacks.
+ */
+interface ISplitSelectListener {
+ /**
+ * Called when a task requests to enter split select
+ */
+ boolean onRequestSplitSelect(in RunningTaskInfo taskInfo);
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
index 2f2bc77..f20fe0b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
@@ -18,6 +18,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.app.ActivityManager;
import android.graphics.Rect;
import com.android.wm.shell.common.annotations.ExternalThread;
@@ -63,6 +64,13 @@
default void onSplitVisibilityChanged(boolean visible) {}
}
+ /** Callback interface for listening to requests to enter split select */
+ interface SplitSelectListener {
+ default boolean onRequestEnterSplitSelect(ActivityManager.RunningTaskInfo taskInfo) {
+ return false;
+ }
+ }
+
/** Registers listener that gets split screen callback. */
void registerSplitScreenListener(@NonNull SplitScreenListener listener,
@NonNull Executor executor);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index 5fa2654..210bf68 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -87,6 +87,7 @@
import com.android.wm.shell.common.annotations.ExternalThread;
import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
import com.android.wm.shell.common.split.SplitScreenUtils;
+import com.android.wm.shell.desktopmode.DesktopTasksController;
import com.android.wm.shell.draganddrop.DragAndDropController;
import com.android.wm.shell.draganddrop.DragAndDropPolicy;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
@@ -104,6 +105,7 @@
import java.lang.annotation.RetentionPolicy;
import java.util.Optional;
import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicBoolean;
/**
* Class manages split-screen multitasking mode and implements the main interface
@@ -177,6 +179,7 @@
private final Optional<RecentTasksController> mRecentTasksOptional;
private final LaunchAdjacentController mLaunchAdjacentController;
private final Optional<WindowDecorViewModel> mWindowDecorViewModel;
+ private final Optional<DesktopTasksController> mDesktopTasksController;
private final SplitScreenShellCommandHandler mSplitScreenShellCommandHandler;
private final String[] mAppsSupportMultiInstances;
@@ -205,6 +208,7 @@
Optional<RecentTasksController> recentTasks,
LaunchAdjacentController launchAdjacentController,
Optional<WindowDecorViewModel> windowDecorViewModel,
+ Optional<DesktopTasksController> desktopTasksController,
ShellExecutor mainExecutor) {
mShellCommandHandler = shellCommandHandler;
mShellController = shellController;
@@ -223,6 +227,7 @@
mRecentTasksOptional = recentTasks;
mLaunchAdjacentController = launchAdjacentController;
mWindowDecorViewModel = windowDecorViewModel;
+ mDesktopTasksController = desktopTasksController;
mSplitScreenShellCommandHandler = new SplitScreenShellCommandHandler(this);
// TODO(b/238217847): Temporarily add this check here until we can remove the dynamic
// override for this controller from the base module
@@ -254,6 +259,7 @@
RecentTasksController recentTasks,
LaunchAdjacentController launchAdjacentController,
WindowDecorViewModel windowDecorViewModel,
+ DesktopTasksController desktopTasksController,
ShellExecutor mainExecutor,
StageCoordinator stageCoordinator) {
mShellCommandHandler = shellCommandHandler;
@@ -273,6 +279,7 @@
mRecentTasksOptional = Optional.of(recentTasks);
mLaunchAdjacentController = launchAdjacentController;
mWindowDecorViewModel = Optional.of(windowDecorViewModel);
+ mDesktopTasksController = Optional.of(desktopTasksController);
mStageCoordinator = stageCoordinator;
mSplitScreenShellCommandHandler = new SplitScreenShellCommandHandler(this);
shellInit.addInitCallback(this::onInit, this);
@@ -306,6 +313,7 @@
}
mDragAndDropController.ifPresent(controller -> controller.setSplitScreenController(this));
mWindowDecorViewModel.ifPresent(viewModel -> viewModel.setSplitScreenController(this));
+ mDesktopTasksController.ifPresent(controller -> controller.setSplitScreenController(this));
}
protected StageCoordinator createStageCoordinator() {
@@ -468,6 +476,16 @@
mStageCoordinator.unregisterSplitScreenListener(listener);
}
+ /** Register a split select listener */
+ public void registerSplitSelectListener(SplitScreen.SplitSelectListener listener) {
+ mStageCoordinator.registerSplitSelectListener(listener);
+ }
+
+ /** Unregister a split select listener */
+ public void unregisterSplitSelectListener(SplitScreen.SplitSelectListener listener) {
+ mStageCoordinator.unregisterSplitSelectListener(listener);
+ }
+
public void goToFullscreenFromSplit() {
mStageCoordinator.goToFullscreenFromSplit();
}
@@ -485,6 +503,16 @@
return mStageCoordinator.getActivateSplitPosition(taskInfo);
}
+ /**
+ * Move a task to split select
+ * @param taskInfo the task being moved to split select
+ * @param wct transaction to apply if this is a valid request
+ */
+ public void requestEnterSplitSelect(ActivityManager.RunningTaskInfo taskInfo,
+ WindowContainerTransaction wct) {
+ mStageCoordinator.requestEnterSplitSelect(taskInfo, wct);
+ }
+
public void startTask(int taskId, @SplitPosition int position, @Nullable Bundle options) {
final int[] result = new int[1];
IRemoteAnimationRunner wrapper = new IRemoteAnimationRunner.Stub() {
@@ -1088,6 +1116,8 @@
private SplitScreenController mController;
private final SingleInstanceRemoteListener<SplitScreenController,
ISplitScreenListener> mListener;
+ private final SingleInstanceRemoteListener<SplitScreenController,
+ ISplitSelectListener> mSelectListener;
private final SplitScreen.SplitScreenListener mSplitScreenListener =
new SplitScreen.SplitScreenListener() {
@Override
@@ -1101,11 +1131,25 @@
}
};
+ private final SplitScreen.SplitSelectListener mSplitSelectListener =
+ new SplitScreen.SplitSelectListener() {
+ @Override
+ public boolean onRequestEnterSplitSelect(
+ ActivityManager.RunningTaskInfo taskInfo) {
+ AtomicBoolean result = new AtomicBoolean(false);
+ mSelectListener.call(l -> result.set(l.onRequestSplitSelect(taskInfo)));
+ return result.get();
+ }
+ };
+
public ISplitScreenImpl(SplitScreenController controller) {
mController = controller;
mListener = new SingleInstanceRemoteListener<>(controller,
c -> c.registerSplitScreenListener(mSplitScreenListener),
c -> c.unregisterSplitScreenListener(mSplitScreenListener));
+ mSelectListener = new SingleInstanceRemoteListener<>(controller,
+ c -> c.registerSplitSelectListener(mSplitSelectListener),
+ c -> c.unregisterSplitSelectListener(mSplitSelectListener));
}
/**
@@ -1131,6 +1175,18 @@
}
@Override
+ public void registerSplitSelectListener(ISplitSelectListener listener) {
+ executeRemoteCallWithTaskPermission(mController, "registerSplitSelectListener",
+ (controller) -> mSelectListener.register(listener));
+ }
+
+ @Override
+ public void unregisterSplitSelectListener(ISplitSelectListener listener) {
+ executeRemoteCallWithTaskPermission(mController, "unregisterSplitSelectListener",
+ (controller) -> mSelectListener.unregister());
+ }
+
+ @Override
public void exitSplitScreen(int toTopTaskId) {
executeRemoteCallWithTaskPermission(mController, "exitSplitScreen",
(controller) -> controller.exitSplitScreen(toTopTaskId, EXIT_REASON_UNKNOWN));
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
index 99be5b8..7dec12a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
@@ -324,8 +324,10 @@
void startFullscreenTransition(WindowContainerTransaction wct,
@Nullable RemoteTransition handler) {
- mTransitions.startTransition(TRANSIT_OPEN, wct,
- new OneShotRemoteHandler(mTransitions.getMainExecutor(), handler));
+ OneShotRemoteHandler fullscreenHandler =
+ new OneShotRemoteHandler(mTransitions.getMainExecutor(), handler);
+ fullscreenHandler.setTransition(mTransitions
+ .startTransition(TRANSIT_OPEN, wct, fullscreenHandler));
}
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 3758b68..6970068 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
@@ -144,8 +144,10 @@
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.HashSet;
import java.util.List;
import java.util.Optional;
+import java.util.Set;
/**
* Coordinates the staging (visibility, sizing, ...) of the split-screen {@link MainStage} and
@@ -185,6 +187,7 @@
private final ShellTaskOrganizer mTaskOrganizer;
private final Context mContext;
private final List<SplitScreen.SplitScreenListener> mListeners = new ArrayList<>();
+ private final Set<SplitScreen.SplitSelectListener> mSelectListeners = new HashSet<>();
private final DisplayController mDisplayController;
private final DisplayImeController mDisplayImeController;
private final DisplayInsetsController mDisplayInsetsController;
@@ -462,6 +465,15 @@
return mLogger;
}
+ void requestEnterSplitSelect(ActivityManager.RunningTaskInfo taskInfo,
+ WindowContainerTransaction wct) {
+ boolean enteredSplitSelect = false;
+ for (SplitScreen.SplitSelectListener listener : mSelectListeners) {
+ enteredSplitSelect |= listener.onRequestEnterSplitSelect(taskInfo);
+ }
+ if (enteredSplitSelect) mTaskOrganizer.applyTransaction(wct);
+ }
+
void startShortcut(String packageName, String shortcutId, @SplitPosition int position,
Bundle options, UserHandle user) {
final boolean isEnteringSplit = !isSplitActive();
@@ -1657,6 +1669,14 @@
mListeners.remove(listener);
}
+ void registerSplitSelectListener(SplitScreen.SplitSelectListener listener) {
+ mSelectListeners.add(listener);
+ }
+
+ void unregisterSplitSelectListener(SplitScreen.SplitSelectListener listener) {
+ mSelectListeners.remove(listener);
+ }
+
void sendStatusToListener(SplitScreen.SplitScreenListener listener) {
listener.onStagePositionChanged(STAGE_TYPE_MAIN, getMainStagePosition());
listener.onStagePositionChanged(STAGE_TYPE_SIDE, getSideStagePosition());
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitScreenController.java
index a2301b1..c101425 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitScreenController.java
@@ -87,7 +87,7 @@
syncQueue, rootTDAOrganizer, displayController, displayImeController,
displayInsetsController, dragAndDropController, transitions, transactionPool,
iconProvider, recentTasks, launchAdjacentController, Optional.empty(),
- mainExecutor);
+ Optional.empty(), mainExecutor);
mTaskOrganizer = shellTaskOrganizer;
mSyncQueue = syncQueue;
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..29be343 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
@@ -385,8 +385,8 @@
private static int estimateWindowBGColor(Drawable themeBGDrawable) {
final DrawableColorTester themeBGTester = new DrawableColorTester(
themeBGDrawable, DrawableColorTester.TRANSLUCENT_FILTER /* filterType */);
- if (themeBGTester.passFilterRatio() != 1) {
- // the window background is translucent, unable to draw
+ if (themeBGTester.passFilterRatio() < 0.5f) {
+ // more than half pixels of the window background is translucent, unable to draw
Slog.w(TAG, "Window background is translucent, fill background with black color");
return getSystemBGColor();
} else {
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 2b19da2..2be7a49 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
@@ -365,6 +365,11 @@
mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(false));
mDesktopTasksController.ifPresent(c -> c.moveToFullscreen(mTaskId));
decoration.closeHandleMenu();
+ } else if (id == R.id.split_screen_button) {
+ decoration.closeHandleMenu();
+ mDesktopTasksController.ifPresent(c -> {
+ c.requestSplit(decoration.mTaskInfo);
+ });
} else if (id == R.id.collapse_menu_button) {
decoration.closeHandleMenu();
} else if (id == R.id.select_button) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt
index 672e57a..a9eb882 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt
@@ -23,14 +23,14 @@
appIcon: Drawable
) : DesktopModeWindowDecorationViewHolder(rootView) {
- private val captionView: View = rootView.findViewById(R.id.desktop_mode_caption)
- private val captionHandle: View = rootView.findViewById(R.id.caption_handle)
- private val openMenuButton: View = rootView.findViewById(R.id.open_menu_button)
- private val closeWindowButton: ImageButton = rootView.findViewById(R.id.close_window)
- private val expandMenuButton: ImageButton = rootView.findViewById(R.id.expand_menu_button)
- private val maximizeWindowButton: ImageButton = rootView.findViewById(R.id.maximize_window)
- private val appNameTextView: TextView = rootView.findViewById(R.id.application_name)
- private val appIconImageView: ImageView = rootView.findViewById(R.id.application_icon)
+ private val captionView: View = rootView.requireViewById(R.id.desktop_mode_caption)
+ private val captionHandle: View = rootView.requireViewById(R.id.caption_handle)
+ private val openMenuButton: View = rootView.requireViewById(R.id.open_menu_button)
+ private val closeWindowButton: ImageButton = rootView.requireViewById(R.id.close_window)
+ private val expandMenuButton: ImageButton = rootView.requireViewById(R.id.expand_menu_button)
+ private val maximizeWindowButton: ImageButton = rootView.requireViewById(R.id.maximize_window)
+ private val appNameTextView: TextView = rootView.requireViewById(R.id.application_name)
+ private val appIconImageView: ImageView = rootView.requireViewById(R.id.application_icon)
init {
captionView.setOnTouchListener(onCaptionTouchListener)
@@ -47,7 +47,9 @@
override fun bindData(taskInfo: RunningTaskInfo) {
val captionDrawable = captionView.background as GradientDrawable
- captionDrawable.setColor(taskInfo.taskDescription.statusBarColor)
+ taskInfo.taskDescription?.statusBarColor?.let {
+ captionDrawable.setColor(it)
+ }
closeWindowButton.imageTintList = ColorStateList.valueOf(
getCaptionCloseButtonColor(taskInfo))
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeFocusedWindowDecorationViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeFocusedWindowDecorationViewHolder.kt
index 47a12a0..9374ac9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeFocusedWindowDecorationViewHolder.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeFocusedWindowDecorationViewHolder.kt
@@ -17,8 +17,8 @@
onCaptionButtonClickListener: View.OnClickListener
) : DesktopModeWindowDecorationViewHolder(rootView) {
- private val captionView: View = rootView.findViewById(R.id.desktop_mode_caption)
- private val captionHandle: ImageButton = rootView.findViewById(R.id.caption_handle)
+ private val captionView: View = rootView.requireViewById(R.id.desktop_mode_caption)
+ private val captionHandle: ImageButton = rootView.requireViewById(R.id.caption_handle)
init {
captionView.setOnTouchListener(onCaptionTouchListener)
@@ -27,9 +27,10 @@
}
override fun bindData(taskInfo: RunningTaskInfo) {
- val captionColor = taskInfo.taskDescription.statusBarColor
- val captionDrawable = captionView.background as GradientDrawable
- captionDrawable.setColor(captionColor)
+ taskInfo.taskDescription?.statusBarColor?.let { captionColor ->
+ val captionDrawable = captionView.background as GradientDrawable
+ captionDrawable.setColor(captionColor)
+ }
captionHandle.imageTintList = ColorStateList.valueOf(getCaptionHandleBarColor(taskInfo))
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeWindowDecorationViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeWindowDecorationViewHolder.kt
index d293cf7..49e8d15 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeWindowDecorationViewHolder.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeWindowDecorationViewHolder.kt
@@ -25,11 +25,14 @@
* with the caption background color.
*/
protected fun shouldUseLightCaptionColors(taskInfo: RunningTaskInfo): Boolean {
- return if (Color.alpha(taskInfo.taskDescription.statusBarColor) != 0 &&
- taskInfo.windowingMode == WINDOWING_MODE_FREEFORM) {
- Color.valueOf(taskInfo.taskDescription.statusBarColor).luminance() < 0.5
- } else {
- taskInfo.taskDescription.statusBarAppearance and APPEARANCE_LIGHT_STATUS_BARS == 0
- }
+ return taskInfo.taskDescription
+ ?.let { taskDescription ->
+ if (Color.alpha(taskDescription.statusBarColor) != 0 &&
+ taskInfo.windowingMode == WINDOWING_MODE_FREEFORM) {
+ Color.valueOf(taskDescription.statusBarColor).luminance() < 0.5
+ } else {
+ taskDescription.statusBarAppearance and APPEARANCE_LIGHT_STATUS_BARS == 0
+ }
+ } ?: false
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/Android.bp b/libs/WindowManager/Shell/tests/unittest/Android.bp
index ad4d97f..c2f184a 100644
--- a/libs/WindowManager/Shell/tests/unittest/Android.bp
+++ b/libs/WindowManager/Shell/tests/unittest/Android.bp
@@ -43,6 +43,7 @@
"frameworks-base-testutils",
"kotlinx-coroutines-android",
"kotlinx-coroutines-core",
+ "mockito-kotlin2",
"mockito-target-extended-minus-junit4",
"truth-prebuilt",
"testables",
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataRepositoryTest.kt
index 0e05e01..e359957 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataRepositoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataRepositoryTest.kt
@@ -29,11 +29,11 @@
import org.junit.After
import org.junit.Before
import org.junit.Test
-import org.mockito.Mockito
-import org.mockito.Mockito.mock
-import org.mockito.Mockito.never
-import org.mockito.Mockito.spy
-import org.mockito.Mockito.verify
+import org.mockito.kotlin.any
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.verify
class BubbleDataRepositoryTest : ShellTestCase() {
@@ -124,7 +124,7 @@
private val testHandler = Handler(Looper.getMainLooper())
private val mainExecutor = HandlerExecutor(testHandler)
- private val launcherApps = mock(LauncherApps::class.java)
+ private val launcherApps = mock<LauncherApps>()
private val persistedBubbles = SparseArray<List<BubbleEntity>>()
@@ -158,8 +158,7 @@
assertThat(persistedBubbles).isEqualTo(validEntitiesByUser)
// No invalid users, so no persist to disk happened
- verify(dataRepository, never()).persistToDisk(
- any(SparseArray<List<BubbleEntity>>()::class.java))
+ verify(dataRepository, never()).persistToDisk(any())
}
@Test
@@ -199,6 +198,4 @@
// Verify that persist to disk happened with the new valid entities list.
verify(dataRepository).persistToDisk(validEntitiesByUser)
}
-
- fun <T> any(type: Class<T>): T = Mockito.any<T>(type)
}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
index 443cea2..fe2da5d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
@@ -38,7 +38,6 @@
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import com.android.internal.policy.DividerSnapAlgorithm;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestRunningTaskInfoBuilder;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
index 2cc28ac..0ae2908 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
@@ -55,9 +55,9 @@
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TabletopModeController;
import com.android.wm.shell.common.TaskStackListenerImpl;
+import com.android.wm.shell.common.pip.PipAppOpsListener;
import com.android.wm.shell.onehanded.OneHandedController;
import com.android.wm.shell.pip.PipAnimationController;
-import com.android.wm.shell.pip.PipAppOpsListener;
import com.android.wm.shell.pip.PipBoundsAlgorithm;
import com.android.wm.shell.pip.PipBoundsState;
import com.android.wm.shell.pip.PipDisplayLayoutState;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
index e8a1e91..568db91 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
@@ -65,6 +65,7 @@
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.desktopmode.DesktopTasksController;
import com.android.wm.shell.draganddrop.DragAndDropController;
import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.sysui.ShellCommandHandler;
@@ -106,6 +107,7 @@
@Mock RecentTasksController mRecentTasks;
@Mock LaunchAdjacentController mLaunchAdjacentController;
@Mock WindowDecorViewModel mWindowDecorViewModel;
+ @Mock DesktopTasksController mDesktopTasksController;
@Captor ArgumentCaptor<Intent> mIntentCaptor;
private ShellController mShellController;
@@ -122,7 +124,7 @@
mRootTDAOrganizer, mDisplayController, mDisplayImeController,
mDisplayInsetsController, mDragAndDropController, mTransitions, mTransactionPool,
mIconProvider, mRecentTasks, mLaunchAdjacentController, mWindowDecorViewModel,
- mMainExecutor, mStageCoordinator));
+ mDesktopTasksController, mMainExecutor, mStageCoordinator));
}
@Test
diff --git a/media/java/android/media/RingtoneManager.java b/media/java/android/media/RingtoneManager.java
index d2b21ae..43acdd5 100644
--- a/media/java/android/media/RingtoneManager.java
+++ b/media/java/android/media/RingtoneManager.java
@@ -46,7 +46,9 @@
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
+import android.provider.BaseColumns;
import android.provider.MediaStore;
+import android.provider.MediaStore.Audio.AudioColumns;
import android.provider.MediaStore.MediaColumns;
import android.provider.Settings;
import android.provider.Settings.System;
@@ -507,6 +509,95 @@
return getUriFromCursor(mContext, mCursor);
}
+ /**
+ * Gets the valid ringtone uri by a given uri string and ringtone type for the restore purpose.
+ *
+ * @param contentResolver ContentResolver to execute media query.
+ * @param value a canonicalized uri which refers to the ringtone.
+ * @param ringtoneType an integer representation of the kind of uri that is being restored, can
+ * be RingtoneManager.TYPE_RINGTONE, RingtoneManager.TYPE_NOTIFICATION, or
+ * RingtoneManager.TYPE_ALARM.
+ * @hide
+ */
+ public static @Nullable Uri getRingtoneUriForRestore(
+ @NonNull ContentResolver contentResolver, @Nullable String value, int ringtoneType)
+ throws FileNotFoundException, IllegalArgumentException {
+ if (value == null) {
+ // Return a valid null. It means the null value is intended instead of a failure.
+ return null;
+ }
+
+ Uri ringtoneUri;
+ final Uri canonicalUri = Uri.parse(value);
+
+ // Try to get the media uri via the regular uncanonicalize method first.
+ ringtoneUri = contentResolver.uncanonicalize(canonicalUri);
+ if (ringtoneUri != null) {
+ // Canonicalize it to make the result contain the right metadata of the media asset.
+ ringtoneUri = contentResolver.canonicalize(ringtoneUri);
+ return ringtoneUri;
+ }
+
+ // Query the media by title and ringtone type.
+ final String title = canonicalUri.getQueryParameter(AudioColumns.TITLE);
+ Uri baseUri = ContentUris.removeId(canonicalUri).buildUpon().clearQuery().build();
+ String ringtoneTypeSelection = "";
+ switch (ringtoneType) {
+ case RingtoneManager.TYPE_RINGTONE:
+ ringtoneTypeSelection = MediaStore.Audio.AudioColumns.IS_RINGTONE;
+ break;
+ case RingtoneManager.TYPE_NOTIFICATION:
+ ringtoneTypeSelection = MediaStore.Audio.AudioColumns.IS_NOTIFICATION;
+ break;
+ case RingtoneManager.TYPE_ALARM:
+ ringtoneTypeSelection = MediaStore.Audio.AudioColumns.IS_ALARM;
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown ringtone type: " + ringtoneType);
+ }
+
+ final String selection = ringtoneTypeSelection + "=1 AND " + AudioColumns.TITLE + "=?";
+ Cursor cursor = null;
+ try {
+ cursor =
+ contentResolver.query(
+ baseUri,
+ /* projection */ new String[] {BaseColumns._ID},
+ /* selection */ selection,
+ /* selectionArgs */ new String[] {title},
+ /* sortOrder */ null,
+ /* cancellationSignal */ null);
+
+ } catch (IllegalArgumentException e) {
+ throw new FileNotFoundException("Volume not found for " + baseUri);
+ }
+ if (cursor == null) {
+ throw new FileNotFoundException("Missing cursor for " + baseUri);
+ } else if (cursor.getCount() == 0) {
+ FileUtils.closeQuietly(cursor);
+ throw new FileNotFoundException("No item found for " + baseUri);
+ } else if (cursor.getCount() > 1) {
+ // Find more than 1 result.
+ // We are not sure which one is the right ringtone file so just abandon this case.
+ FileUtils.closeQuietly(cursor);
+ throw new FileNotFoundException(
+ "Find multiple ringtone candidates by title+ringtone_type query: count: "
+ + cursor.getCount());
+ }
+ if (cursor.moveToFirst()) {
+ ringtoneUri = ContentUris.withAppendedId(baseUri, cursor.getLong(0));
+ FileUtils.closeQuietly(cursor);
+ } else {
+ FileUtils.closeQuietly(cursor);
+ throw new FileNotFoundException("Failed to read row from the result.");
+ }
+
+ // Canonicalize it to make the result contain the right metadata of the media asset.
+ ringtoneUri = contentResolver.canonicalize(ringtoneUri);
+ Log.v(TAG, "Find a valid result: " + ringtoneUri);
+ return ringtoneUri;
+ }
+
private static Uri getUriFromCursor(Context context, Cursor cursor) {
final Uri uri = ContentUris.withAppendedId(Uri.parse(cursor.getString(URI_COLUMN_INDEX)),
cursor.getLong(ID_COLUMN_INDEX));
diff --git a/media/java/android/media/projection/MediaProjection.java b/media/java/android/media/projection/MediaProjection.java
index fb72c7b..223b432c 100644
--- a/media/java/android/media/projection/MediaProjection.java
+++ b/media/java/android/media/projection/MediaProjection.java
@@ -83,6 +83,7 @@
try {
mImpl.start(new MediaProjectionCallback());
} catch (RemoteException e) {
+ Log.e(TAG, "Content Recording: Failed to start media projection", e);
throw new RuntimeException("Failed to start media projection", e);
}
mDisplayManager = displayManager;
@@ -105,11 +106,18 @@
* @see #unregisterCallback
*/
public void registerCallback(@NonNull Callback callback, @Nullable Handler handler) {
- final Callback c = Objects.requireNonNull(callback);
- if (handler == null) {
- handler = new Handler();
+ try {
+ final Callback c = Objects.requireNonNull(callback);
+ if (handler == null) {
+ handler = new Handler();
+ }
+ mCallbacks.put(c, new CallbackRecord(c, handler));
+ } catch (NullPointerException e) {
+ Log.e(TAG, "Content Recording: cannot register null Callback", e);
+ throw e;
+ } catch (RuntimeException e) {
+ Log.e(TAG, "Content Recording: failed to create new Handler to register Callback", e);
}
- mCallbacks.put(c, new CallbackRecord(c, handler));
}
/**
@@ -120,8 +128,13 @@
* @see #registerCallback
*/
public void unregisterCallback(@NonNull Callback callback) {
- final Callback c = Objects.requireNonNull(callback);
- mCallbacks.remove(c);
+ try {
+ final Callback c = Objects.requireNonNull(callback);
+ mCallbacks.remove(c);
+ } catch (NullPointerException e) {
+ Log.d(TAG, "Content Recording: cannot unregister null Callback", e);
+ throw e;
+ }
}
/**
@@ -203,9 +216,11 @@
@Nullable VirtualDisplay.Callback callback, @Nullable Handler handler) {
if (shouldMediaProjectionRequireCallback()) {
if (mCallbacks.isEmpty()) {
- throw new IllegalStateException(
+ final IllegalStateException e = new IllegalStateException(
"Must register a callback before starting capture, to manage resources in"
+ " response to MediaProjection states.");
+ Log.e(TAG, "Content Recording: no callback registered for virtual display", e);
+ throw e;
}
}
final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(name, width,
@@ -272,6 +287,7 @@
*/
public void stop() {
try {
+ Log.d(TAG, "Content Recording: stopping projection");
mImpl.stop();
} catch (RemoteException e) {
Log.e(TAG, "Unable to stop projection", e);
diff --git a/media/java/android/media/projection/MediaProjectionManager.java b/media/java/android/media/projection/MediaProjectionManager.java
index 5a68c53..9790d02 100644
--- a/media/java/android/media/projection/MediaProjectionManager.java
+++ b/media/java/android/media/projection/MediaProjectionManager.java
@@ -256,6 +256,7 @@
*/
public void stopActiveProjection() {
try {
+ Log.d(TAG, "Content Recording: stopping active projection");
mService.stopActiveProjection();
} catch (RemoteException e) {
Log.e(TAG, "Unable to stop the currently active media projection", e);
@@ -269,6 +270,7 @@
*/
public void addCallback(@NonNull Callback callback, @Nullable Handler handler) {
if (callback == null) {
+ Log.w(TAG, "Content Recording: cannot add null callback");
throw new IllegalArgumentException("callback must not be null");
}
CallbackDelegate delegate = new CallbackDelegate(callback, handler);
@@ -286,6 +288,7 @@
*/
public void removeCallback(@NonNull Callback callback) {
if (callback == null) {
+ Log.w(TAG, "ContentRecording: cannot remove null callback");
throw new IllegalArgumentException("callback must not be null");
}
CallbackDelegate delegate = mCallbacks.remove(callback);
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java b/packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java
index 0c4cb8e..74acf67 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java
@@ -19,6 +19,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.Activity;
+import android.app.ActivityOptions;
import android.app.LoaderManager;
import android.content.ComponentName;
import android.content.Context;
@@ -714,8 +715,13 @@
try {
mPrinterForInfoIntent = printer;
+ Bundle options = ActivityOptions.makeBasic()
+ .setPendingIntentBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED)
+ .toBundle();
startIntentSenderForResult(printer.getInfoIntent().getIntentSender(),
- INFO_INTENT_REQUEST_CODE, fillInIntent, 0, 0, 0);
+ INFO_INTENT_REQUEST_CODE, fillInIntent, 0, 0, 0,
+ options);
} catch (SendIntentException e) {
mPrinterForInfoIntent = null;
Log.e(LOG_TAG, "Could not execute pending info intent: %s", e);
diff --git a/packages/SettingsLib/Spa/spa/res/values/colors.xml b/packages/SettingsLib/Spa/spa/res/values/colors.xml
new file mode 100644
index 0000000..ca4a0b2
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/res/values/colors.xml
@@ -0,0 +1,67 @@
+<?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.
+ -->
+
+<resources>
+ <color name="settingslib_protection_color">@android:color/white</color>
+
+ <!-- Dynamic colors-->
+ <color name="settingslib_color_blue600">#1a73e8</color>
+ <color name="settingslib_color_blue400">#669df6</color>
+ <color name="settingslib_color_blue300">#8ab4f8</color>
+ <color name="settingslib_color_blue100">#d2e3fc</color>
+ <color name="settingslib_color_blue50">#e8f0fe</color>
+ <color name="settingslib_color_green600">#1e8e3e</color>
+ <color name="settingslib_color_green500">#34A853</color>
+ <color name="settingslib_color_green400">#5bb974</color>
+ <color name="settingslib_color_green100">#ceead6</color>
+ <color name="settingslib_color_green50">#e6f4ea</color>
+ <color name="settingslib_color_red600">#d93025</color>
+ <color name="settingslib_color_red500">#B3261E</color>
+ <color name="settingslib_color_red400">#ee675c</color>
+ <color name="settingslib_color_red100">#fad2cf</color>
+ <color name="settingslib_color_red50">#fce8e6</color>
+ <color name="settingslib_color_yellow600">#f9ab00</color>
+ <color name="settingslib_color_yellow400">#fcc934</color>
+ <color name="settingslib_color_yellow100">#feefc3</color>
+ <color name="settingslib_color_yellow50">#fef7e0</color>
+ <color name="settingslib_color_grey900">#202124</color>
+ <color name="settingslib_color_grey800">#3c4043</color>
+ <color name="settingslib_color_grey700">#5f6368</color>
+ <color name="settingslib_color_grey600">#80868b</color>
+ <color name="settingslib_color_grey500">#9AA0A6</color>
+ <color name="settingslib_color_grey400">#bdc1c6</color>
+ <color name="settingslib_color_grey300">#dadce0</color>
+ <color name="settingslib_color_grey200">#e8eaed</color>
+ <color name="settingslib_color_grey100">#f1f3f4</color>
+ <color name="settingslib_color_grey50">#f8f9fa</color>
+ <color name="settingslib_color_orange600">#e8710a</color>
+ <color name="settingslib_color_orange400">#fa903e</color>
+ <color name="settingslib_color_orange300">#fcad70</color>
+ <color name="settingslib_color_orange100">#fedfc8</color>
+ <color name="settingslib_color_pink600">#e52592</color>
+ <color name="settingslib_color_pink400">#ff63b8</color>
+ <color name="settingslib_color_pink300">#ff8bcb</color>
+ <color name="settingslib_color_pink100">#fdcfe8</color>
+ <color name="settingslib_color_purple600">#9334e6</color>
+ <color name="settingslib_color_purple400">#af5cf7</color>
+ <color name="settingslib_color_purple300">#c58af9</color>
+ <color name="settingslib_color_purple100">#e9d2fd</color>
+ <color name="settingslib_color_cyan600">#12b5c8</color>
+ <color name="settingslib_color_cyan400">#4ecde6</color>
+ <color name="settingslib_color_cyan300">#78d9ec</color>
+ <color name="settingslib_color_cyan100">#cbf0f8</color>
+</resources>
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/illustration/Illustration.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/illustration/Illustration.kt
index 7cc9bf7..6a2163c 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/illustration/Illustration.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/illustration/Illustration.kt
@@ -22,11 +22,11 @@
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.sizeIn
import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.material3.MaterialTheme
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.graphics.Color
import com.android.settingslib.spa.framework.theme.SettingsDimension
import com.android.settingslib.spa.widget.ui.ImageBox
import com.android.settingslib.spa.widget.ui.Lottie
@@ -81,7 +81,7 @@
maxHeight = SettingsDimension.illustrationMaxHeight,
)
.clip(RoundedCornerShape(SettingsDimension.illustrationCornerRadius))
- .background(color = MaterialTheme.colorScheme.surface)
+ .background(color = Color.Transparent)
when (resourceType) {
ResourceType.LOTTIE -> {
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Lottie.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Lottie.kt
index 915c6e2..5f7fe85 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Lottie.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Lottie.kt
@@ -16,15 +16,24 @@
package com.android.settingslib.spa.widget.ui
+import android.graphics.PorterDuff
+import android.graphics.PorterDuffColorFilter
+import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Box
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.toArgb
+import androidx.compose.ui.res.colorResource
+import com.airbnb.lottie.LottieProperty
import com.airbnb.lottie.compose.LottieAnimation
import com.airbnb.lottie.compose.LottieCompositionSpec
import com.airbnb.lottie.compose.LottieConstants
import com.airbnb.lottie.compose.animateLottieCompositionAsState
import com.airbnb.lottie.compose.rememberLottieComposition
+import com.airbnb.lottie.compose.rememberLottieDynamicProperties
+import com.airbnb.lottie.compose.rememberLottieDynamicProperty
+import com.android.settingslib.spa.R
@Composable
fun Lottie(
@@ -38,6 +47,34 @@
}
}
+object LottieColorUtils {
+ private val DARK_TO_LIGHT_THEME_COLOR_MAP = mapOf(
+ ".grey600" to R.color.settingslib_color_grey400,
+ ".grey800" to R.color.settingslib_color_grey300,
+ ".grey900" to R.color.settingslib_color_grey50,
+ ".red400" to R.color.settingslib_color_red600,
+ ".black" to android.R.color.white,
+ ".blue400" to R.color.settingslib_color_blue600,
+ ".green400" to R.color.settingslib_color_green600,
+ ".green200" to R.color.settingslib_color_green500,
+ ".red200" to R.color.settingslib_color_red500,
+ )
+
+ @Composable
+ private fun getDefaultPropertiesList() =
+ DARK_TO_LIGHT_THEME_COLOR_MAP.map { (key, colorRes) ->
+ val color = colorResource(colorRes).toArgb()
+ rememberLottieDynamicProperty(
+ property = LottieProperty.COLOR_FILTER,
+ keyPath = arrayOf("**", key, "**")
+ ){ PorterDuffColorFilter(color, PorterDuff.Mode.SRC_ATOP) }
+ }
+
+ @Composable
+ fun getDefaultDynamicProperties() =
+ rememberLottieDynamicProperties(*getDefaultPropertiesList().toTypedArray())
+}
+
@Composable
private fun BaseLottie(resId: Int) {
val composition by rememberLottieComposition(
@@ -47,8 +84,10 @@
composition,
iterations = LottieConstants.IterateForever,
)
+ val isLightMode = !isSystemInDarkTheme()
LottieAnimation(
composition = composition,
+ dynamicProperties = LottieColorUtils.getDefaultDynamicProperties().takeIf { isLightMode },
progress = { progress },
)
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index 2118d2c..f03ff00 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -1518,10 +1518,15 @@
* list.
*/
public void switchMemberDeviceContent(CachedBluetoothDevice newMainDevice) {
- // Backup from main device
+ // Remove the sub device from mMemberDevices first to prevent hash mismatch problem due
+ // to mDevice switch
+ removeMemberDevice(newMainDevice);
+
+ // Backup from current main device
final BluetoothDevice tmpDevice = mDevice;
final short tmpRssi = mRssi;
final boolean tmpJustDiscovered = mJustDiscovered;
+
// Set main device from sub device
release();
mDevice = newMainDevice.mDevice;
@@ -1535,6 +1540,9 @@
newMainDevice.mRssi = tmpRssi;
newMainDevice.mJustDiscovered = tmpJustDiscovered;
newMainDevice.fillData();
+
+ // Add the sub device back into mMemberDevices with correct hash
+ addMemberDevice(newMainDevice);
}
/**
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java
index efba953..111a2c1 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java
@@ -186,6 +186,14 @@
if (cachedDevice.getHiSyncId() != hiSyncId) {
continue;
}
+
+ // The remote device supports CSIP, the other ear should be processed as a member
+ // device. Ignore hiSyncId grouping from ASHA here.
+ if (cachedDevice.getProfiles().stream().anyMatch(
+ profile -> profile instanceof CsipSetCoordinatorProfile)) {
+ continue;
+ }
+
if (firstMatchedIndex == -1) {
// Found the first one
firstMatchedIndex = i;
diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java
index 1251b0d..9ab84d2 100644
--- a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java
+++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java
@@ -26,6 +26,7 @@
import static android.os.BatteryManager.EXTRA_PLUGGED;
import static android.os.BatteryManager.EXTRA_PRESENT;
import static android.os.BatteryManager.EXTRA_STATUS;
+import static android.os.OsProtoEnums.BATTERY_PLUGGED_NONE;
import android.content.Context;
import android.content.Intent;
@@ -40,6 +41,8 @@
*/
public class BatteryStatus {
private static final int LOW_BATTERY_THRESHOLD = 20;
+ private static final int SEVERE_LOW_BATTERY_THRESHOLD = 10;
+ private static final int EXTREME_LOW_BATTERY_THRESHOLD = 3;
private static final int DEFAULT_CHARGING_VOLTAGE_MICRO_VOLT = 5000000;
public static final int CHARGING_UNKNOWN = -1;
@@ -90,21 +93,7 @@
present = batteryChangedIntent.getBooleanExtra(EXTRA_PRESENT, true);
this.incompatibleCharger = incompatibleCharger;
- final int maxChargingMicroAmp = batteryChangedIntent.getIntExtra(EXTRA_MAX_CHARGING_CURRENT,
- -1);
- int maxChargingMicroVolt = batteryChangedIntent.getIntExtra(EXTRA_MAX_CHARGING_VOLTAGE, -1);
-
- if (maxChargingMicroVolt <= 0) {
- maxChargingMicroVolt = DEFAULT_CHARGING_VOLTAGE_MICRO_VOLT;
- }
- if (maxChargingMicroAmp > 0) {
- // Calculating muW = muA * muV / (10^6 mu^2 / mu); splitting up the divisor
- // to maintain precision equally on both factors.
- maxChargingWattage = (maxChargingMicroAmp / 1000)
- * (maxChargingMicroVolt / 1000);
- } else {
- maxChargingWattage = -1;
- }
+ maxChargingWattage = calculateMaxChargingMicroWatt(batteryChangedIntent);
}
/** Determine whether the device is plugged. */
@@ -126,7 +115,7 @@
/** Determine whether the device is plugged in dock. */
public boolean isPluggedInDock() {
- return plugged == BatteryManager.BATTERY_PLUGGED_DOCK;
+ return isPluggedInDock(plugged);
}
/**
@@ -140,15 +129,15 @@
/** Whether battery is low and needs to be charged. */
public boolean isBatteryLow() {
- return level < LOW_BATTERY_THRESHOLD;
+ return isLowBattery(level);
}
/** Whether battery defender is enabled. */
public boolean isBatteryDefender() {
- return chargingStatus == CHARGING_POLICY_ADAPTIVE_LONGLIFE;
+ return isBatteryDefender(chargingStatus);
}
- /** Return current chargin speed is fast, slow or normal. */
+ /** Return current charging speed is fast, slow or normal. */
public final int getChargingSpeed(Context context) {
final int slowThreshold = context.getResources().getInteger(
R.integer.config_chargingSlowlyThreshold);
@@ -218,4 +207,126 @@
|| plugged == BatteryManager.BATTERY_PLUGGED_WIRELESS
|| plugged == BatteryManager.BATTERY_PLUGGED_DOCK;
}
+
+ /** Determine whether the device is plugged in dock. */
+ public static boolean isPluggedInDock(Intent batteryChangedIntent) {
+ return isPluggedInDock(
+ batteryChangedIntent.getIntExtra(EXTRA_PLUGGED, BATTERY_PLUGGED_NONE));
+ }
+
+ /** Determine whether the device is plugged in dock. */
+ public static boolean isPluggedInDock(int plugged) {
+ return plugged == BatteryManager.BATTERY_PLUGGED_DOCK;
+ }
+
+ /**
+ * Whether the battery is low or not.
+ *
+ * @param batteryChangedIntent the {@link ACTION_BATTERY_CHANGED} intent
+ * @return {@code true} if the battery level is less or equal to {@link LOW_BATTERY_THRESHOLD}
+ */
+ public static boolean isLowBattery(Intent batteryChangedIntent) {
+ int level = getBatteryLevel(batteryChangedIntent);
+ return isLowBattery(level);
+ }
+
+ /**
+ * Whether the battery is low or not.
+ *
+ * @param batteryLevel the battery level
+ * @return {@code true} if the battery level is less or equal to {@link LOW_BATTERY_THRESHOLD}
+ */
+ public static boolean isLowBattery(int batteryLevel) {
+ return batteryLevel <= LOW_BATTERY_THRESHOLD;
+ }
+
+ /**
+ * Whether the battery is severe low or not.
+ *
+ * @param batteryChangedIntent the ACTION_BATTERY_CHANGED intent
+ * @return {@code true} if the battery level is less or equal to {@link
+ * SEVERE_LOW_BATTERY_THRESHOLD}
+ */
+ public static boolean isSevereLowBattery(Intent batteryChangedIntent) {
+ int level = getBatteryLevel(batteryChangedIntent);
+ return level <= SEVERE_LOW_BATTERY_THRESHOLD;
+ }
+
+ /**
+ * Whether the battery is extreme low or not.
+ *
+ * @param batteryChangedIntent the ACTION_BATTERY_CHANGED intent
+ * @return {@code true} if the battery level is less or equal to {@link
+ * EXTREME_LOW_BATTERY_THRESHOLD}
+ */
+ public static boolean isExtremeLowBattery(Intent batteryChangedIntent) {
+ int level = getBatteryLevel(batteryChangedIntent);
+ return level <= EXTREME_LOW_BATTERY_THRESHOLD;
+ }
+
+ /**
+ * Whether the battery defender is enabled or not.
+ *
+ * @param batteryChangedIntent the ACTION_BATTERY_CHANGED intent
+ * @return {@code true} if the battery defender is enabled. It could be dock defend, dwell
+ * defend, or temp defend
+ */
+ public static boolean isBatteryDefender(Intent batteryChangedIntent) {
+ int chargingStatus =
+ batteryChangedIntent.getIntExtra(EXTRA_CHARGING_STATUS, CHARGING_POLICY_DEFAULT);
+ return isBatteryDefender(chargingStatus);
+ }
+
+ /**
+ * Whether the battery defender is enabled or not.
+ *
+ * @param chargingStatus for {@link EXTRA_CHARGING_STATUS} field in the ACTION_BATTERY_CHANGED
+ * intent
+ * @return {@code true} if the battery defender is enabled. It could be dock defend, dwell
+ * defend, or temp defend
+ */
+ public static boolean isBatteryDefender(int chargingStatus) {
+ return chargingStatus == CHARGING_POLICY_ADAPTIVE_LONGLIFE;
+ }
+
+ /**
+ * Gets the max charging current and max charging voltage form {@link
+ * Intent.ACTION_BATTERY_CHANGED} and calculates the charging speed based on the {@link
+ * R.integer.config_chargingSlowlyThreshold} and {@link R.integer.config_chargingFastThreshold}.
+ *
+ * @param context the application context
+ * @param batteryChangedIntent the intent from {@link Intent.ACTION_BATTERY_CHANGED}
+ * @return the charging speed. {@link CHARGING_REGULAR}, {@link CHARGING_FAST}, {@link
+ * CHARGING_SLOWLY} or {@link CHARGING_UNKNOWN}
+ */
+ public static int getChargingSpeed(Context context, Intent batteryChangedIntent) {
+ final int maxChargingMicroWatt = calculateMaxChargingMicroWatt(batteryChangedIntent);
+ if (maxChargingMicroWatt <= 0) {
+ return CHARGING_UNKNOWN;
+ } else if (maxChargingMicroWatt
+ < context.getResources().getInteger(R.integer.config_chargingSlowlyThreshold)) {
+ return CHARGING_SLOWLY;
+ } else if (maxChargingMicroWatt
+ > context.getResources().getInteger(R.integer.config_chargingFastThreshold)) {
+ return CHARGING_FAST;
+ } else {
+ return CHARGING_REGULAR;
+ }
+ }
+
+ private static int calculateMaxChargingMicroWatt(Intent batteryChangedIntent) {
+ final int maxChargingMicroAmp =
+ batteryChangedIntent.getIntExtra(EXTRA_MAX_CHARGING_CURRENT, -1);
+ int maxChargingMicroVolt = batteryChangedIntent.getIntExtra(EXTRA_MAX_CHARGING_VOLTAGE, -1);
+ if (maxChargingMicroVolt <= 0) {
+ maxChargingMicroVolt = DEFAULT_CHARGING_VOLTAGE_MICRO_VOLT;
+ }
+
+ if (maxChargingMicroAmp > 0) {
+ // Calculating µW = mA * mV
+ return (int) Math.round(maxChargingMicroAmp * 0.001 * maxChargingMicroVolt * 0.001);
+ } else {
+ return -1;
+ }
+ }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/graph/ThemedBatteryDrawable.kt b/packages/SettingsLib/src/com/android/settingslib/graph/ThemedBatteryDrawable.kt
index a03acc3..73f6db6 100644
--- a/packages/SettingsLib/src/com/android/settingslib/graph/ThemedBatteryDrawable.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/graph/ThemedBatteryDrawable.kt
@@ -325,7 +325,7 @@
return batteryLevel
}
- override fun onBoundsChange(bounds: Rect?) {
+ override fun onBoundsChange(bounds: Rect) {
super.onBoundsChange(bounds)
updateSize()
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
index a9d15f3..ac93019 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
@@ -22,6 +22,7 @@
import static android.media.MediaRoute2Info.TYPE_GROUP;
import static android.media.MediaRoute2Info.TYPE_HDMI;
import static android.media.MediaRoute2Info.TYPE_HEARING_AID;
+import static android.media.MediaRoute2Info.TYPE_REMOTE_AUDIO_VIDEO_RECEIVER;
import static android.media.MediaRoute2Info.TYPE_REMOTE_SPEAKER;
import static android.media.MediaRoute2Info.TYPE_REMOTE_TV;
import static android.media.MediaRoute2Info.TYPE_UNKNOWN;
@@ -83,7 +84,8 @@
MediaDeviceType.TYPE_FAST_PAIR_BLUETOOTH_DEVICE,
MediaDeviceType.TYPE_BLUETOOTH_DEVICE,
MediaDeviceType.TYPE_CAST_DEVICE,
- MediaDeviceType.TYPE_CAST_GROUP_DEVICE})
+ MediaDeviceType.TYPE_CAST_GROUP_DEVICE,
+ MediaDeviceType.TYPE_REMOTE_AUDIO_VIDEO_RECEIVER})
public @interface MediaDeviceType {
int TYPE_UNKNOWN = 0;
int TYPE_PHONE_DEVICE = 1;
@@ -93,6 +95,7 @@
int TYPE_BLUETOOTH_DEVICE = 5;
int TYPE_CAST_DEVICE = 6;
int TYPE_CAST_GROUP_DEVICE = 7;
+ int TYPE_REMOTE_AUDIO_VIDEO_RECEIVER = 8;
}
@Retention(RetentionPolicy.SOURCE)
@@ -161,6 +164,9 @@
case TYPE_BLE_HEADSET:
mType = MediaDeviceType.TYPE_BLUETOOTH_DEVICE;
break;
+ case TYPE_REMOTE_AUDIO_VIDEO_RECEIVER:
+ mType = MediaDeviceType.TYPE_REMOTE_AUDIO_VIDEO_RECEIVER;
+ break;
case TYPE_UNKNOWN:
case TYPE_REMOTE_TV:
case TYPE_REMOTE_SPEAKER:
diff --git a/packages/SettingsLib/tests/unit/src/com/android/settingslib/fuelgague/BatteryStatusTest.kt b/packages/SettingsLib/tests/unit/src/com/android/settingslib/fuelgague/BatteryStatusTest.kt
new file mode 100644
index 0000000..6c0c1a7
--- /dev/null
+++ b/packages/SettingsLib/tests/unit/src/com/android/settingslib/fuelgague/BatteryStatusTest.kt
@@ -0,0 +1,330 @@
+/*
+ * 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.fuelgague
+
+import android.content.Context
+import android.content.Intent
+import android.os.BatteryManager
+import android.os.BatteryManager.BATTERY_PLUGGED_AC
+import android.os.BatteryManager.BATTERY_PLUGGED_DOCK
+import android.os.BatteryManager.BATTERY_PLUGGED_USB
+import android.os.BatteryManager.BATTERY_PLUGGED_WIRELESS
+import android.os.BatteryManager.BATTERY_STATUS_FULL
+import android.os.BatteryManager.BATTERY_STATUS_UNKNOWN
+import android.os.BatteryManager.CHARGING_POLICY_ADAPTIVE_LONGLIFE
+import android.os.BatteryManager.CHARGING_POLICY_DEFAULT
+import android.os.BatteryManager.EXTRA_MAX_CHARGING_CURRENT
+import android.os.BatteryManager.EXTRA_MAX_CHARGING_VOLTAGE
+import android.os.OsProtoEnums.BATTERY_PLUGGED_NONE
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.fuelgauge.BatteryStatus
+import com.android.settingslib.fuelgauge.BatteryStatus.CHARGING_FAST
+import com.android.settingslib.fuelgauge.BatteryStatus.CHARGING_REGULAR
+import com.android.settingslib.fuelgauge.BatteryStatus.CHARGING_SLOWLY
+import com.android.settingslib.fuelgauge.BatteryStatus.CHARGING_UNKNOWN
+import com.android.settingslib.fuelgauge.BatteryStatus.isBatteryDefender
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import java.util.Optional
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import org.junit.runners.Suite
+import org.junit.runners.Suite.SuiteClasses
+
+@RunWith(Suite::class)
+@SuiteClasses(
+ BatteryStatusTest.NonParameterizedTest::class,
+ BatteryStatusTest.IsPluggedInTest::class,
+ BatteryStatusTest.IsChargedTest::class,
+ BatteryStatusTest.GetChargingSpeedTest::class,
+ BatteryStatusTest.IsPluggedInDockTest::class,
+)
+open class BatteryStatusTest {
+
+ @RunWith(AndroidJUnit4::class)
+ class NonParameterizedTest : BatteryStatusTest() {
+ @Test
+ fun isLowBattery_20Percent_returnsTrue() {
+ val level = 20
+ val intent = createIntent(batteryLevel = level)
+
+ assertWithMessage("failed by isLowBattery(Intent), level=$level")
+ .that(BatteryStatus.isLowBattery(intent))
+ .isTrue()
+ assertWithMessage("failed by isLowBattery($level)")
+ .that(BatteryStatus.isLowBattery(level))
+ .isTrue()
+ }
+
+ @Test
+ fun isLowBattery_21Percent_returnsFalse() {
+ val level = 21
+ val intent = createIntent(batteryLevel = level)
+
+ assertWithMessage("failed by isLowBattery(intent), level=$level")
+ .that(BatteryStatus.isLowBattery(intent))
+ .isFalse()
+ assertWithMessage("failed by isLowBattery($level)")
+ .that(BatteryStatus.isLowBattery(intent))
+ .isFalse()
+ }
+
+ @Test
+ fun isSevereLowBattery_10Percent_returnsTrue() {
+ val batteryChangedIntent = createIntent(batteryLevel = 10)
+
+ assertThat(BatteryStatus.isSevereLowBattery(batteryChangedIntent)).isTrue()
+ }
+
+ @Test
+ fun isSevereLowBattery_11Percent_returnFalse() {
+ val batteryChangedIntent = createIntent(batteryLevel = 11)
+
+ assertThat(BatteryStatus.isSevereLowBattery(batteryChangedIntent)).isFalse()
+ }
+
+ @Test
+ fun isExtremeLowBattery_3Percent_returnsTrue() {
+ val batteryChangedIntent = createIntent(batteryLevel = 3)
+
+ assertThat(BatteryStatus.isExtremeLowBattery(batteryChangedIntent)).isTrue()
+ }
+
+ @Test
+ fun isExtremeLowBattery_4Percent_returnsFalse() {
+ val batteryChangedIntent = createIntent(batteryLevel = 4)
+
+ assertThat(BatteryStatus.isExtremeLowBattery(batteryChangedIntent)).isFalse()
+ }
+
+ @Test
+ fun isBatteryDefender_chargingLongLife_returnsTrue() {
+ val chargingStatus = CHARGING_POLICY_ADAPTIVE_LONGLIFE
+ val batteryChangedIntent = createIntent(chargingStatus = chargingStatus)
+
+ assertIsBatteryDefender(chargingStatus, batteryChangedIntent).isTrue()
+ }
+
+ @Test
+ fun isBatteryDefender_nonChargingLongLife_returnsFalse() {
+ val chargingStatus = CHARGING_POLICY_DEFAULT
+ val batteryChangedIntent = createIntent(chargingStatus = chargingStatus)
+
+ assertIsBatteryDefender(chargingStatus, batteryChangedIntent).isFalse()
+ }
+
+ private fun assertIsBatteryDefender(chargingStatus: Int, batteryChangedIntent: Intent) =
+ object {
+ val assertions =
+ listOf(
+ "failed by isBatteryDefender(Intent), chargingStatus=$chargingStatus".let {
+ assertWithMessage(it).that(isBatteryDefender(batteryChangedIntent))
+ },
+ "failed by isBatteryDefender($chargingStatus)".let {
+ assertWithMessage(it).that(isBatteryDefender(chargingStatus))
+ },
+ )
+
+ fun isTrue() = assertions.forEach { it.isTrue() }
+
+ fun isFalse() = assertions.forEach { it.isFalse() }
+ }
+ }
+
+ @RunWith(Parameterized::class)
+ class IsPluggedInTest(
+ private val name: String,
+ private val plugged: Int,
+ val expected: Boolean
+ ) : BatteryStatusTest() {
+
+ @Test
+ fun isPluggedIn_() {
+ val batteryChangedIntent = createIntent(plugged = plugged)
+
+ assertWithMessage("failed by isPluggedIn(plugged=$plugged)")
+ .that(BatteryStatus.isPluggedIn(plugged))
+ .isEqualTo(expected)
+ assertWithMessage("failed by isPlugged(Intent), which plugged=$plugged")
+ .that(BatteryStatus.isPluggedIn(batteryChangedIntent))
+ .isEqualTo(expected)
+ }
+
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun parameters() =
+ arrayListOf(
+ arrayOf("withAC_returnsTrue", BATTERY_PLUGGED_AC, true),
+ arrayOf("withDock_returnsTrue", BATTERY_PLUGGED_DOCK, true),
+ arrayOf("withUSB_returnsTrue", BATTERY_PLUGGED_USB, true),
+ arrayOf("withWireless_returnsTrue", BATTERY_PLUGGED_WIRELESS, true),
+ arrayOf("pluggedNone_returnsTrue", BATTERY_PLUGGED_NONE, false),
+ )
+ }
+ }
+
+ @RunWith(Parameterized::class)
+ class IsPluggedInDockTest(
+ private val name: String,
+ private val plugged: Int,
+ val expected: Boolean
+ ) : BatteryStatusTest() {
+
+ @Test
+ fun isPluggedDockIn_() {
+ val batteryChangedIntent = createIntent(plugged = plugged)
+
+ assertWithMessage("failed by isPluggedInDock(plugged=$plugged)")
+ .that(BatteryStatus.isPluggedInDock(plugged))
+ .isEqualTo(expected)
+ assertWithMessage("failed by isPluggedInDock(Intent), which plugged=$plugged")
+ .that(BatteryStatus.isPluggedInDock(batteryChangedIntent))
+ .isEqualTo(expected)
+ }
+
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun parameters() =
+ arrayListOf(
+ arrayOf("withAC_returnsTrue", BATTERY_PLUGGED_AC, false),
+ arrayOf("withDock_returnsTrue", BATTERY_PLUGGED_DOCK, true),
+ arrayOf("withUSB_returnsTrue", BATTERY_PLUGGED_USB, false),
+ arrayOf("withWireless_returnsTrue", BATTERY_PLUGGED_WIRELESS, false),
+ arrayOf("pluggedNone_returnsTrue", BATTERY_PLUGGED_NONE, false),
+ )
+ }
+ }
+
+ @RunWith(Parameterized::class)
+ class IsChargedTest(
+ private val status: Int,
+ private val batteryLevel: Int,
+ private val expected: Boolean
+ ) : BatteryStatusTest() {
+
+ @Test
+ fun isCharged_() {
+ val batteryChangedIntent = createIntent(batteryLevel = batteryLevel, status = status)
+
+ assertWithMessage(
+ "failed by isCharged(Intent), status=$status, batteryLevel=$batteryLevel"
+ )
+ .that(BatteryStatus.isCharged(batteryChangedIntent))
+ .isEqualTo(expected)
+ assertWithMessage("failed by isCharged($status, $batteryLevel)")
+ .that(BatteryStatus.isCharged(status, batteryLevel))
+ .isEqualTo(expected)
+ }
+
+ companion object {
+ @Parameterized.Parameters(name = "status{0}_level{1}_returns-{2}")
+ @JvmStatic
+ fun parameters() =
+ arrayListOf(
+ arrayOf(BATTERY_STATUS_FULL, 99, true),
+ arrayOf(BATTERY_STATUS_UNKNOWN, 100, true),
+ arrayOf(BATTERY_STATUS_FULL, 100, true),
+ arrayOf(BATTERY_STATUS_UNKNOWN, 99, false),
+ )
+ }
+ }
+
+ @RunWith(Parameterized::class)
+ class GetChargingSpeedTest(
+ private val name: String,
+ private val maxChargingCurrent: Optional<Int>,
+ private val maxChargingVoltage: Optional<Int>,
+ private val expectedChargingSpeed: Int,
+ ) {
+
+ val context: Context = ApplicationProvider.getApplicationContext()
+
+ @Test
+ fun getChargingSpeed_() {
+ val batteryChangedIntent =
+ Intent(Intent.ACTION_BATTERY_CHANGED).apply {
+ maxChargingCurrent.ifPresent { putExtra(EXTRA_MAX_CHARGING_CURRENT, it) }
+ maxChargingVoltage.ifPresent { putExtra(EXTRA_MAX_CHARGING_VOLTAGE, it) }
+ }
+
+ assertThat(BatteryStatus.getChargingSpeed(context, batteryChangedIntent))
+ .isEqualTo(expectedChargingSpeed)
+ }
+
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun parameters() =
+ arrayListOf(
+ arrayOf(
+ "maxCurrent=n/a, maxVoltage=n/a -> UNKNOWN",
+ Optional.empty<Int>(),
+ Optional.empty<Int>(),
+ CHARGING_UNKNOWN
+ ),
+ arrayOf(
+ "maxCurrent=0, maxVoltage=9000000 -> UNKNOWN",
+ Optional.of(0),
+ Optional.of(0),
+ CHARGING_UNKNOWN
+ ),
+ arrayOf(
+ "maxCurrent=1500000, maxVoltage=5000000 -> CHARGING_REGULAR",
+ Optional.of(1500000),
+ Optional.of(5000000),
+ CHARGING_REGULAR
+ ),
+ arrayOf(
+ "maxCurrent=1000000, maxVoltage=5000000 -> CHARGING_REGULAR",
+ Optional.of(1000000),
+ Optional.of(5000000),
+ CHARGING_REGULAR
+ ),
+ arrayOf(
+ "maxCurrent=1500001, maxVoltage=5000000 -> CHARGING_FAST",
+ Optional.of(1501000),
+ Optional.of(5000000),
+ CHARGING_FAST
+ ),
+ arrayOf(
+ "maxCurrent=999999, maxVoltage=5000000 -> CHARGING_SLOWLY",
+ Optional.of(999999),
+ Optional.of(5000000),
+ CHARGING_SLOWLY
+ ),
+ )
+ }
+ }
+
+ protected fun createIntent(
+ batteryLevel: Int = 50,
+ chargingStatus: Int = CHARGING_POLICY_DEFAULT,
+ plugged: Int = BATTERY_PLUGGED_NONE,
+ status: Int = BatteryManager.BATTERY_STATUS_CHARGING,
+ ): Intent =
+ Intent(Intent.ACTION_BATTERY_CHANGED).apply {
+ putExtra(BatteryManager.EXTRA_STATUS, status)
+ putExtra(BatteryManager.EXTRA_LEVEL, batteryLevel)
+ putExtra(BatteryManager.EXTRA_SCALE, 100)
+ putExtra(BatteryManager.EXTRA_CHARGING_STATUS, chargingStatus)
+ putExtra(BatteryManager.EXTRA_PLUGGED, plugged)
+ }
+}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
index 9da1ab8..27a45df 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
@@ -44,6 +44,7 @@
import com.android.internal.app.LocalePicker;
import com.android.settingslib.devicestate.DeviceStateRotationLockSettingsManager;
+import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Locale;
@@ -332,21 +333,30 @@
* @param value can be a canonicalized uri or "_silent" to indicate a silent (null) ringtone.
*/
private void setRingtone(String name, String value) {
- // If it's null, don't change the default
- if (value == null) return;
- final Uri ringtoneUri;
- if (SILENT_RINGTONE.equals(value)) {
- ringtoneUri = null;
- } else {
- Uri canonicalUri = Uri.parse(value);
- ringtoneUri = mContext.getContentResolver().uncanonicalize(canonicalUri);
- if (ringtoneUri == null) {
- // Unrecognized or invalid Uri, don't restore
- return;
- }
- }
- final int ringtoneType = getRingtoneType(name);
+ Log.v(TAG, "Set ringtone for name: " + name + " value: " + value);
+ // If it's null, don't change the default.
+ if (value == null) return;
+ final int ringtoneType = getRingtoneType(name);
+ if (SILENT_RINGTONE.equals(value)) {
+ // SILENT_RINGTONE is a special constant generated by onBackupValue in the source
+ // device.
+ RingtoneManager.setActualDefaultRingtoneUri(mContext, ringtoneType, null);
+ return;
+ }
+
+ Uri ringtoneUri = null;
+ try {
+ ringtoneUri =
+ RingtoneManager.getRingtoneUriForRestore(
+ mContext.getContentResolver(), value, ringtoneType);
+ } catch (FileNotFoundException | IllegalArgumentException e) {
+ Log.w(TAG, "Failed to resolve " + value + ": " + e);
+ // Unrecognized or invalid Uri, don't restore
+ return;
+ }
+
+ Log.v(TAG, "setActualDefaultRingtoneUri type: " + ringtoneType + ", uri: " + ringtoneUri);
RingtoneManager.setActualDefaultRingtoneUri(mContext, ringtoneType, ringtoneUri);
}
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsHelperTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsHelperTest.java
index bc81c44..ef062df 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsHelperTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsHelperTest.java
@@ -26,15 +26,24 @@
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
+import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
+import android.content.pm.ApplicationInfo;
import android.content.res.Resources;
+import android.database.Cursor;
+import android.database.MatrixCursor;
import android.media.AudioManager;
import android.net.Uri;
+import android.os.Bundle;
import android.os.LocaleList;
+import android.provider.BaseColumns;
+import android.provider.MediaStore;
import android.provider.Settings;
import android.telephony.TelephonyManager;
+import android.test.mock.MockContentProvider;
+import android.test.mock.MockContentResolver;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
@@ -57,6 +66,13 @@
private static final String SETTING_VALUE = "setting_value";
private static final String SETTING_REAL_VALUE = "setting_real_value";
+ private static final String DEFAULT_RINGTONE_VALUE =
+ "content://media/internal/audio/media/10?title=DefaultRingtone&canonical=1";
+ private static final String DEFAULT_NOTIFICATION_VALUE =
+ "content://media/internal/audio/media/20?title=DefaultNotification&canonical=1";
+ private static final String DEFAULT_ALARM_VALUE =
+ "content://media/internal/audio/media/30?title=DefaultAlarm&canonical=1";
+
private SettingsHelper mSettingsHelper;
@Mock private Context mContext;
@@ -74,6 +90,7 @@
mTelephonyManager);
when(mContext.getResources()).thenReturn(mResources);
when(mContext.getApplicationContext()).thenReturn(mContext);
+ when(mContext.getApplicationInfo()).thenReturn(new ApplicationInfo());
when(mContext.getContentResolver()).thenReturn(getContentResolver());
mSettingsHelper = spy(new SettingsHelper(mContext));
@@ -338,6 +355,377 @@
}
@Test
+ public void testRestoreValue_customRingtone_regularUncanonicalize_Success() {
+ final String sourceRingtoneValue =
+ "content://media/internal/audio/media/1?title=Song&canonical=1";
+ final String newRingtoneValueUncanonicalized =
+ "content://media/internal/audio/media/100";
+ final String newRingtoneValueCanonicalized =
+ "content://media/internal/audio/media/100?title=Song&canonical=1";
+
+ MockContentResolver mMockContentResolver = new MockContentResolver();
+ when(mContext.getContentResolver()).thenReturn(mMockContentResolver);
+
+ ContentProvider mockMediaContentProvider =
+ new MockContentProvider(mContext) {
+ @Override
+ public Uri uncanonicalize(Uri url) {
+ assertThat(url).isEqualTo(Uri.parse(sourceRingtoneValue));
+ return Uri.parse(newRingtoneValueUncanonicalized);
+ }
+
+ @Override
+ public Uri canonicalize(Uri url) {
+ assertThat(url).isEqualTo(Uri.parse(newRingtoneValueUncanonicalized));
+ return Uri.parse(newRingtoneValueCanonicalized);
+ }
+
+ @Override
+ public String getType(Uri url) {
+ return "audio/ogg";
+ }
+ };
+
+ ContentProvider mockSettingsContentProvider =
+ new MockSettingsProvider(mContext, getContentResolver());
+ mMockContentResolver.addProvider(MediaStore.AUTHORITY, mockMediaContentProvider);
+ mMockContentResolver.addProvider(Settings.AUTHORITY, mockSettingsContentProvider);
+
+ resetRingtoneSettingsToDefault(mMockContentResolver);
+ assertThat(Settings.System.getString(mMockContentResolver, Settings.System.RINGTONE))
+ .isEqualTo(DEFAULT_RINGTONE_VALUE);
+
+ mSettingsHelper.restoreValue(
+ mContext,
+ mMockContentResolver,
+ new ContentValues(),
+ Uri.EMPTY,
+ Settings.System.RINGTONE,
+ sourceRingtoneValue,
+ 0);
+
+ assertThat(Settings.System.getString(mMockContentResolver, Settings.System.RINGTONE))
+ .isEqualTo(newRingtoneValueCanonicalized);
+ }
+
+ @Test
+ public void testRestoreValue_customRingtone_useCustomLookup_success() {
+ final String sourceRingtoneValue =
+ "content://0@media/external/audio/media/1?title=Song&canonical=1";
+ final String newRingtoneValueUncanonicalized =
+ "content://0@media/external/audio/media/100";
+ final String newRingtoneValueCanonicalized =
+ "content://0@media/external/audio/media/100?title=Song&canonical=1";
+
+ MockContentResolver mMockContentResolver = new MockContentResolver();
+ when(mContext.getContentResolver()).thenReturn(mMockContentResolver);
+
+ MatrixCursor cursor = new MatrixCursor(new String[] {BaseColumns._ID});
+ cursor.addRow(new Object[] {100L});
+
+ ContentProvider mockMediaContentProvider =
+ new MockContentProvider(mContext) {
+ @Override
+ public Uri uncanonicalize(Uri url) {
+ // mock the lookup failure in regular MediaProvider.uncanonicalize.
+ return null;
+ }
+
+ @Override
+ public Uri canonicalize(Uri url) {
+ assertThat(url).isEqualTo(Uri.parse(newRingtoneValueUncanonicalized));
+ return Uri.parse(newRingtoneValueCanonicalized);
+ }
+
+ @Override
+ public String getType(Uri url) {
+ return "audio/ogg";
+ }
+
+ @Override
+ public Cursor query(
+ Uri uri,
+ String[] projection,
+ String selection,
+ String[] selectionArgs,
+ String sortOrder) {
+ assertThat(uri)
+ .isEqualTo(Uri.parse("content://0@media/external/audio/media"));
+ assertThat(projection).isEqualTo(new String[] {"_id"});
+ assertThat(selection).isEqualTo("is_ringtone=1 AND title=?");
+ assertThat(selectionArgs).isEqualTo(new String[] {"Song"});
+ return cursor;
+ }
+ };
+
+ ContentProvider mockSettingsContentProvider =
+ new MockSettingsProvider(mContext, getContentResolver());
+ mMockContentResolver.addProvider(MediaStore.AUTHORITY, mockMediaContentProvider);
+ mMockContentResolver.addProvider("0@" + MediaStore.AUTHORITY, mockMediaContentProvider);
+ mMockContentResolver.addProvider(Settings.AUTHORITY, mockSettingsContentProvider);
+
+ resetRingtoneSettingsToDefault(mMockContentResolver);
+
+ mSettingsHelper.restoreValue(
+ mContext,
+ mMockContentResolver,
+ new ContentValues(),
+ Uri.EMPTY,
+ Settings.System.RINGTONE,
+ sourceRingtoneValue,
+ 0);
+
+ assertThat(Settings.System.getString(mMockContentResolver, Settings.System.RINGTONE))
+ .isEqualTo(newRingtoneValueCanonicalized);
+ }
+
+ @Test
+ public void testRestoreValue_customRingtone_notificationSound_useCustomLookup_success() {
+ final String sourceRingtoneValue =
+ "content://0@media/external/audio/media/2?title=notificationPing&canonical=1";
+ final String newRingtoneValueUncanonicalized =
+ "content://0@media/external/audio/media/200";
+ final String newRingtoneValueCanonicalized =
+ "content://0@media/external/audio/media/200?title=notificationPing&canonicalize=1";
+
+ MockContentResolver mMockContentResolver = new MockContentResolver();
+ when(mContext.getContentResolver()).thenReturn(mMockContentResolver);
+
+ MatrixCursor cursor = new MatrixCursor(new String[] {BaseColumns._ID});
+ cursor.addRow(new Object[] {200L});
+
+ ContentProvider mockMediaContentProvider =
+ new MockContentProvider(mContext) {
+ @Override
+ public Uri uncanonicalize(Uri url) {
+ // mock the lookup failure in regular MediaProvider.uncanonicalize.
+ return null;
+ }
+
+ @Override
+ public Uri canonicalize(Uri url) {
+ assertThat(url).isEqualTo(Uri.parse(newRingtoneValueUncanonicalized));
+ return Uri.parse(newRingtoneValueCanonicalized);
+ }
+
+ @Override
+ public String getType(Uri url) {
+ return "audio/ogg";
+ }
+
+ @Override
+ public Cursor query(
+ Uri uri,
+ String[] projection,
+ String selection,
+ String[] selectionArgs,
+ String sortOrder) {
+ assertThat(uri)
+ .isEqualTo(Uri.parse("content://0@media/external/audio/media"));
+ assertThat(projection).isEqualTo(new String[] {"_id"});
+ assertThat(selection).isEqualTo("is_notification=1 AND title=?");
+ assertThat(selectionArgs).isEqualTo(new String[] {"notificationPing"});
+ return cursor;
+ }
+ };
+
+ ContentProvider mockSettingsContentProvider =
+ new MockSettingsProvider(mContext, getContentResolver());
+ mMockContentResolver.addProvider(MediaStore.AUTHORITY, mockMediaContentProvider);
+ mMockContentResolver.addProvider("0@" + MediaStore.AUTHORITY, mockMediaContentProvider);
+ mMockContentResolver.addProvider(Settings.AUTHORITY, mockSettingsContentProvider);
+
+ resetRingtoneSettingsToDefault(mMockContentResolver);
+
+ mSettingsHelper.restoreValue(
+ mContext,
+ mMockContentResolver,
+ new ContentValues(),
+ Uri.EMPTY,
+ Settings.System.NOTIFICATION_SOUND,
+ sourceRingtoneValue,
+ 0);
+
+ assertThat(
+ Settings.System.getString(
+ mMockContentResolver, Settings.System.NOTIFICATION_SOUND))
+ .isEqualTo(newRingtoneValueCanonicalized);
+ }
+
+ @Test
+ public void testRestoreValue_customRingtone_alarmSound_useCustomLookup_success() {
+ final String sourceRingtoneValue =
+ "content://0@media/external/audio/media/3?title=alarmSound&canonical=1";
+ final String newRingtoneValueUncanonicalized =
+ "content://0@media/external/audio/media/300";
+ final String newRingtoneValueCanonicalized =
+ "content://0@media/external/audio/media/300?title=alarmSound&canonical=1";
+
+ MockContentResolver mMockContentResolver = new MockContentResolver();
+ when(mContext.getContentResolver()).thenReturn(mMockContentResolver);
+
+ MatrixCursor cursor = new MatrixCursor(new String[] {BaseColumns._ID});
+ cursor.addRow(new Object[] {300L});
+
+ ContentProvider mockMediaContentProvider =
+ new MockContentProvider(mContext) {
+ @Override
+ public Uri uncanonicalize(Uri url) {
+ // mock the lookup failure in regular MediaProvider.uncanonicalize.
+ return null;
+ }
+
+ @Override
+ public Uri canonicalize(Uri url) {
+ assertThat(url).isEqualTo(Uri.parse(newRingtoneValueUncanonicalized));
+ return Uri.parse(newRingtoneValueCanonicalized);
+ }
+
+ @Override
+ public String getType(Uri url) {
+ return "audio/ogg";
+ }
+
+ @Override
+ public Cursor query(
+ Uri uri,
+ String[] projection,
+ String selection,
+ String[] selectionArgs,
+ String sortOrder) {
+ assertThat(uri)
+ .isEqualTo(Uri.parse("content://0@media/external/audio/media"));
+ assertThat(projection).isEqualTo(new String[] {"_id"});
+ assertThat(selection).isEqualTo("is_alarm=1 AND title=?");
+ assertThat(selectionArgs).isEqualTo(new String[] {"alarmSound"});
+ return cursor;
+ }
+ };
+
+ ContentProvider mockSettingsContentProvider =
+ new MockSettingsProvider(mContext, getContentResolver());
+ mMockContentResolver.addProvider(MediaStore.AUTHORITY, mockMediaContentProvider);
+ mMockContentResolver.addProvider("0@" + MediaStore.AUTHORITY, mockMediaContentProvider);
+ mMockContentResolver.addProvider(Settings.AUTHORITY, mockSettingsContentProvider);
+
+ resetRingtoneSettingsToDefault(mMockContentResolver);
+
+ mSettingsHelper.restoreValue(
+ mContext,
+ mMockContentResolver,
+ new ContentValues(),
+ Uri.EMPTY,
+ Settings.System.ALARM_ALERT,
+ sourceRingtoneValue,
+ 0);
+
+ assertThat(Settings.System.getString(mMockContentResolver, Settings.System.ALARM_ALERT))
+ .isEqualTo(newRingtoneValueCanonicalized);
+ }
+
+ @Test
+ public void testRestoreValue_customRingtone_useCustomLookup_multipleResults_notRestore() {
+ final String sourceRingtoneValue =
+ "content://0@media/external/audio/media/1?title=Song&canonical=1";
+
+ MockContentResolver mMockContentResolver = new MockContentResolver();
+ when(mContext.getContentResolver()).thenReturn(mMockContentResolver);
+
+ // This is to mock the case that there are multiple results by querying title +
+ // ringtone_type.
+ MatrixCursor cursor = new MatrixCursor(new String[] {BaseColumns._ID});
+ cursor.addRow(new Object[] {100L});
+ cursor.addRow(new Object[] {110L});
+
+ ContentProvider mockMediaContentProvider =
+ new MockContentProvider(mContext) {
+ @Override
+ public Uri uncanonicalize(Uri url) {
+ // mock the lookup failure in regular MediaProvider.uncanonicalize.
+ return null;
+ }
+
+ @Override
+ public String getType(Uri url) {
+ return "audio/ogg";
+ }
+ };
+
+ ContentProvider mockSettingsContentProvider =
+ new MockSettingsProvider(mContext, getContentResolver());
+ mMockContentResolver.addProvider(MediaStore.AUTHORITY, mockMediaContentProvider);
+ mMockContentResolver.addProvider("0@" + MediaStore.AUTHORITY, mockMediaContentProvider);
+ mMockContentResolver.addProvider(Settings.AUTHORITY, mockSettingsContentProvider);
+
+ resetRingtoneSettingsToDefault(mMockContentResolver);
+
+ mSettingsHelper.restoreValue(
+ mContext,
+ mMockContentResolver,
+ new ContentValues(),
+ Uri.EMPTY,
+ Settings.System.RINGTONE,
+ sourceRingtoneValue,
+ 0);
+
+ assertThat(Settings.System.getString(mMockContentResolver, Settings.System.RINGTONE))
+ .isEqualTo(DEFAULT_RINGTONE_VALUE);
+ }
+
+ @Test
+ public void testRestoreValue_customRingtone_restoreSilentValue() {
+ MockContentResolver mMockContentResolver = new MockContentResolver();
+ when(mContext.getContentResolver()).thenReturn(mMockContentResolver);
+
+ ContentProvider mockMediaContentProvider =
+ new MockContentProvider(mContext) {
+ @Override
+ public Uri uncanonicalize(Uri url) {
+ // mock the lookup failure in regular MediaProvider.uncanonicalize.
+ return null;
+ }
+
+ @Override
+ public String getType(Uri url) {
+ return "audio/ogg";
+ }
+ };
+
+ ContentProvider mockSettingsContentProvider =
+ new MockSettingsProvider(mContext, getContentResolver());
+ mMockContentResolver.addProvider(MediaStore.AUTHORITY, mockMediaContentProvider);
+ mMockContentResolver.addProvider(Settings.AUTHORITY, mockSettingsContentProvider);
+
+ resetRingtoneSettingsToDefault(mMockContentResolver);
+
+ mSettingsHelper.restoreValue(
+ mContext,
+ mMockContentResolver,
+ new ContentValues(),
+ Uri.EMPTY,
+ Settings.System.RINGTONE,
+ "_silent",
+ 0);
+
+ assertThat(Settings.System.getString(mMockContentResolver, Settings.System.RINGTONE))
+ .isEqualTo(null);
+ }
+
+ public static class MockSettingsProvider extends MockContentProvider {
+ ContentResolver mBaseContentResolver;
+
+ public MockSettingsProvider(Context context, ContentResolver baseContentResolver) {
+ super(context);
+ this.mBaseContentResolver = baseContentResolver;
+ }
+
+ @Override
+ public Bundle call(String method, String request, Bundle args) {
+ return mBaseContentResolver.call(Settings.AUTHORITY, method, request, args);
+ }
+ }
+
+ @Test
public void restoreValue_autoRotation_deviceStateAutoRotationDisabled_restoresValue() {
when(mResources.getStringArray(R.array.config_perDeviceStateRotationLockDefaults))
.thenReturn(new String[]{});
@@ -400,4 +788,20 @@
Settings.Global.putString(cr, Settings.Global.POWER_BUTTON_LONG_PRESS, null);
Settings.Global.putString(cr, Settings.Global.KEY_CHORD_POWER_VOLUME_UP, null);
}
+
+ private void resetRingtoneSettingsToDefault(ContentResolver contentResolver) {
+ Settings.System.putString(
+ contentResolver, Settings.System.RINGTONE, DEFAULT_RINGTONE_VALUE);
+ Settings.System.putString(
+ contentResolver, Settings.System.NOTIFICATION_SOUND, DEFAULT_NOTIFICATION_VALUE);
+ Settings.System.putString(
+ contentResolver, Settings.System.ALARM_ALERT, DEFAULT_ALARM_VALUE);
+
+ assertThat(Settings.System.getString(contentResolver, Settings.System.RINGTONE))
+ .isEqualTo(DEFAULT_RINGTONE_VALUE);
+ assertThat(Settings.System.getString(contentResolver, Settings.System.NOTIFICATION_SOUND))
+ .isEqualTo(DEFAULT_NOTIFICATION_VALUE);
+ assertThat(Settings.System.getString(contentResolver, Settings.System.ALARM_ALERT))
+ .isEqualTo(DEFAULT_ALARM_VALUE);
+ }
}
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 851f976..07f7ecd 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -409,6 +409,7 @@
"mockito-target-extended-minus-junit4",
"androidx.test.ext.junit",
"androidx.test.ext.truth",
+ "kotlin-test",
],
libs: [
"android.test.runner",
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
index 37b1ee5..187d073 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
@@ -249,7 +249,7 @@
// intent is to launch a dialog from another dialog.
val animatedParent =
openedDialogs.firstOrNull {
- it.dialog.window.decorView.viewRootImpl == controller.viewRoot
+ it.dialog.window?.decorView?.viewRootImpl == controller.viewRoot
}
val controller =
animatedParent?.dialogContentWithBackground?.let {
@@ -336,7 +336,7 @@
): ActivityLaunchAnimator.Controller? {
val animatedDialog =
openedDialogs.firstOrNull {
- it.dialog.window.decorView.viewRootImpl == view.viewRootImpl
+ it.dialog.window?.decorView?.viewRootImpl == view.viewRootImpl
}
?: return null
return createActivityLaunchController(animatedDialog, cujType)
@@ -417,7 +417,7 @@
animatedDialog.prepareForStackDismiss()
// Remove the dim.
- dialog.window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
+ dialog.window?.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
}
override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
@@ -783,7 +783,7 @@
}
// Show the background dim.
- dialog.window.addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
+ dialog.window?.addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
startAnimation(
isLaunching = true,
@@ -863,7 +863,7 @@
isLaunching = false,
onLaunchAnimationStart = {
// Remove the dim background as soon as we start the animation.
- dialog.window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
+ dialog.window?.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
},
onLaunchAnimationEnd = {
val dialogContentWithBackground = this.dialogContentWithBackground!!
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
index 1a03ede..6c4b695 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
@@ -206,8 +206,9 @@
return
}
- backgroundView = FrameLayout(launchContainer.context)
- launchContainerOverlay.add(backgroundView)
+ backgroundView = FrameLayout(launchContainer.context).also {
+ launchContainerOverlay.add(it)
+ }
// We wrap the ghosted view background and use it to draw the expandable background. Its
// alpha will be set to 0 as soon as we start drawing the expanding background.
@@ -319,7 +320,7 @@
backgroundDrawable?.wrapped?.alpha = startBackgroundAlpha
GhostView.removeGhost(ghostedView)
- launchContainerOverlay.remove(backgroundView)
+ backgroundView?.let { launchContainerOverlay.remove(it) }
if (ghostedView is LaunchableView) {
// Restore the ghosted view visibility.
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchAnimator.kt
index 142fd21..d6eba2e 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchAnimator.kt
@@ -283,7 +283,7 @@
animator.addListener(
object : AnimatorListenerAdapter() {
- override fun onAnimationStart(animation: Animator?, isReverse: Boolean) {
+ override fun onAnimationStart(animation: Animator, isReverse: Boolean) {
if (DEBUG) {
Log.d(TAG, "Animation started")
}
@@ -295,7 +295,7 @@
launchContainerOverlay.add(windowBackgroundLayer)
}
- override fun onAnimationEnd(animation: Animator?) {
+ override fun onAnimationEnd(animation: Animator) {
if (DEBUG) {
Log.d(TAG, "Animation ended")
}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
index b555fa5..8dc7495 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
@@ -42,7 +42,9 @@
return baseTypeface
}
- val axes = FontVariationAxis.fromFontVariationSettings(fVar).toMutableList()
+ val axes = FontVariationAxis.fromFontVariationSettings(fVar)
+ ?.toMutableList()
+ ?: mutableListOf()
axes.removeIf { !baseTypeface.isSupportedAxes(it.getOpenTypeTagValue()) }
if (axes.isEmpty()) {
return baseTypeface
@@ -120,8 +122,8 @@
}
addListener(
object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator?) = textInterpolator.rebase()
- override fun onAnimationCancel(animation: Animator?) = textInterpolator.rebase()
+ override fun onAnimationEnd(animation: Animator) = textInterpolator.rebase()
+ override fun onAnimationCancel(animation: Animator) = textInterpolator.rebase()
}
)
}
@@ -302,11 +304,11 @@
if (onAnimationEnd != null) {
val listener =
object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator?) {
+ override fun onAnimationEnd(animation: Animator) {
onAnimationEnd.run()
animator.removeListener(this)
}
- override fun onAnimationCancel(animation: Animator?) {
+ override fun onAnimationCancel(animation: Animator) {
animator.removeListener(this)
}
}
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 38b99cc..bd3706e 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
@@ -1046,7 +1046,7 @@
}
}
- override fun onAnimationCancel(animation: Animator?) {
+ override fun onAnimationCancel(animation: Animator) {
cancelled = true
}
}
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Key.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Key.kt
index c3f44f8..f7ebe2f 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Key.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Key.kt
@@ -37,7 +37,14 @@
}
/** Key for a scene. */
-class SceneKey(name: String, identity: Any = Object()) : Key(name, identity) {
+class SceneKey(
+ name: String,
+ identity: Any = Object(),
+) : Key(name, identity) {
+
+ /** The unique [ElementKey] identifying this scene's root element. */
+ val rootElementKey = ElementKey(name, identity)
+
override fun toString(): String {
return "SceneKey(name=$name)"
}
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitions.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitions.kt
index 9752f53..f4e3902 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitions.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitions.kt
@@ -35,7 +35,7 @@
/** The transitions configuration of a [SceneTransitionLayout]. */
class SceneTransitions(
- val transitionSpecs: List<TransitionSpec>,
+ private val transitionSpecs: List<TransitionSpec>,
) {
private val cache = mutableMapOf<SceneKey, MutableMap<SceneKey, TransitionSpec>>()
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/TransitionDslImpl.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/TransitionDslImpl.kt
index afd49b4..48d5638e8b 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/TransitionDslImpl.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/TransitionDslImpl.kt
@@ -75,7 +75,7 @@
}
}
-private class TransitionBuilderImpl : TransitionBuilder {
+internal class TransitionBuilderImpl : TransitionBuilder {
val transformations = mutableListOf<Transformation>()
override var spec: AnimationSpec<Float> = spring(stiffness = Spring.StiffnessLow)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
index b9baa793..81b9eb0 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
@@ -24,7 +24,7 @@
import androidx.compose.animation.Crossfade
import androidx.compose.animation.core.snap
import androidx.compose.animation.core.tween
-import androidx.compose.foundation.background
+import androidx.compose.foundation.Canvas
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
@@ -46,6 +46,7 @@
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
+import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.SceneScope
import com.android.systemui.R
import com.android.systemui.bouncer.ui.viewmodel.AuthMethodBouncerViewModel
@@ -63,6 +64,13 @@
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
+object Bouncer {
+ object Elements {
+ val Background = ElementKey("BouncerBackground")
+ val Content = ElementKey("BouncerContent")
+ }
+}
+
/** The bouncer scene displays authentication challenges like PIN, password, or pattern. */
@SysUISingleton
class BouncerScene
@@ -88,7 +96,7 @@
}
@Composable
-private fun BouncerScene(
+private fun SceneScope.BouncerScene(
viewModel: BouncerViewModel,
dialogFactory: BouncerSceneDialogFactory,
modifier: Modifier = Modifier,
@@ -97,84 +105,90 @@
val authMethodViewModel: AuthMethodBouncerViewModel? by viewModel.authMethod.collectAsState()
val dialogMessage: String? by viewModel.throttlingDialogMessage.collectAsState()
var dialog: Dialog? by remember { mutableStateOf(null) }
+ val backgroundColor = MaterialTheme.colorScheme.surface
- Column(
- horizontalAlignment = Alignment.CenterHorizontally,
- verticalArrangement = Arrangement.spacedBy(60.dp),
- modifier =
- modifier
- .fillMaxSize()
- .background(MaterialTheme.colorScheme.surface)
- .padding(start = 32.dp, top = 92.dp, end = 32.dp, bottom = 32.dp)
- ) {
- Crossfade(
- targetState = message,
- label = "Bouncer message",
- animationSpec = if (message.isUpdateAnimated) tween() else snap(),
- ) { message ->
- Text(
- text = message.text,
- color = MaterialTheme.colorScheme.onSurface,
- style = MaterialTheme.typography.bodyLarge,
- )
+ Box(modifier) {
+ Canvas(Modifier.element(Bouncer.Elements.Background).fillMaxSize()) {
+ drawRect(color = backgroundColor)
}
- Box(Modifier.weight(1f)) {
- when (val nonNullViewModel = authMethodViewModel) {
- is PinBouncerViewModel ->
- PinBouncer(
- viewModel = nonNullViewModel,
- modifier = Modifier.align(Alignment.Center),
- )
- is PasswordBouncerViewModel ->
- PasswordBouncer(
- viewModel = nonNullViewModel,
- modifier = Modifier.align(Alignment.Center),
- )
- is PatternBouncerViewModel ->
- PatternBouncer(
- viewModel = nonNullViewModel,
- modifier =
- Modifier.aspectRatio(1f, matchHeightConstraintsFirst = false)
- .align(Alignment.BottomCenter),
- )
- else -> Unit
- }
- }
-
- Button(
- onClick = viewModel::onEmergencyServicesButtonClicked,
- colors =
- ButtonDefaults.buttonColors(
- containerColor = MaterialTheme.colorScheme.tertiaryContainer,
- contentColor = MaterialTheme.colorScheme.onTertiaryContainer,
- ),
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = Arrangement.spacedBy(60.dp),
+ modifier =
+ Modifier.element(Bouncer.Elements.Content)
+ .fillMaxSize()
+ .padding(start = 32.dp, top = 92.dp, end = 32.dp, bottom = 32.dp)
) {
- Text(
- text = stringResource(com.android.internal.R.string.lockscreen_emergency_call),
- style = MaterialTheme.typography.bodyMedium,
- )
- }
-
- if (dialogMessage != null) {
- if (dialog == null) {
- dialog =
- dialogFactory().apply {
- setMessage(dialogMessage)
- setButton(
- DialogInterface.BUTTON_NEUTRAL,
- context.getString(R.string.ok),
- ) { _, _ ->
- viewModel.onThrottlingDialogDismissed()
- }
- setCancelable(false)
- setCanceledOnTouchOutside(false)
- show()
- }
+ Crossfade(
+ targetState = message,
+ label = "Bouncer message",
+ animationSpec = if (message.isUpdateAnimated) tween() else snap(),
+ ) { message ->
+ Text(
+ text = message.text,
+ color = MaterialTheme.colorScheme.onSurface,
+ style = MaterialTheme.typography.bodyLarge,
+ )
}
- } else {
- dialog?.dismiss()
- dialog = null
+
+ Box(Modifier.weight(1f)) {
+ when (val nonNullViewModel = authMethodViewModel) {
+ is PinBouncerViewModel ->
+ PinBouncer(
+ viewModel = nonNullViewModel,
+ modifier = Modifier.align(Alignment.Center),
+ )
+ is PasswordBouncerViewModel ->
+ PasswordBouncer(
+ viewModel = nonNullViewModel,
+ modifier = Modifier.align(Alignment.Center),
+ )
+ is PatternBouncerViewModel ->
+ PatternBouncer(
+ viewModel = nonNullViewModel,
+ modifier =
+ Modifier.aspectRatio(1f, matchHeightConstraintsFirst = false)
+ .align(Alignment.BottomCenter),
+ )
+ else -> Unit
+ }
+ }
+
+ Button(
+ onClick = viewModel::onEmergencyServicesButtonClicked,
+ colors =
+ ButtonDefaults.buttonColors(
+ containerColor = MaterialTheme.colorScheme.tertiaryContainer,
+ contentColor = MaterialTheme.colorScheme.onTertiaryContainer,
+ ),
+ ) {
+ Text(
+ text = stringResource(com.android.internal.R.string.lockscreen_emergency_call),
+ style = MaterialTheme.typography.bodyMedium,
+ )
+ }
+
+ if (dialogMessage != null) {
+ if (dialog == null) {
+ dialog =
+ dialogFactory().apply {
+ setMessage(dialogMessage)
+ setButton(
+ DialogInterface.BUTTON_NEUTRAL,
+ context.getString(R.string.ok),
+ ) { _, _ ->
+ viewModel.onThrottlingDialogDismissed()
+ }
+ setCancelable(false)
+ setCanceledOnTouchOutside(false)
+ show()
+ }
+ }
+ } else {
+ dialog?.dismiss()
+ dialog = null
+ }
}
}
}
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 38b751c..889c026 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
@@ -31,9 +31,17 @@
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.unit.dp
+import com.android.compose.animation.scene.ElementKey
+import com.android.compose.animation.scene.SceneScope
+
+object Notifications {
+ object Elements {
+ val Notifications = ElementKey("Notifications")
+ }
+}
@Composable
-fun Notifications(
+fun SceneScope.Notifications(
modifier: Modifier = Modifier,
) {
// TODO(b/272779828): implement.
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 1bb341c..c84a5e9 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
@@ -31,15 +31,27 @@
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.unit.dp
+import com.android.compose.animation.scene.ElementKey
+import com.android.compose.animation.scene.SceneScope
+
+object QuickSettings {
+ object Elements {
+ // TODO RENAME
+ val Content = ElementKey("QuickSettingsContent")
+ val CollapsedGrid = ElementKey("QuickSettingsCollapsedGrid")
+ val FooterActions = ElementKey("QuickSettingsFooterActions")
+ }
+}
@Composable
-fun QuickSettings(
+fun SceneScope.QuickSettings(
modifier: Modifier = Modifier,
) {
// TODO(b/272780058): implement.
Column(
modifier =
modifier
+ .element(QuickSettings.Elements.Content)
.fillMaxWidth()
.defaultMinSize(minHeight = 300.dp)
.clip(RoundedCornerShape(32.dp))
@@ -47,15 +59,19 @@
.padding(16.dp),
) {
Text(
- text = "Quick settings",
- modifier = Modifier.align(Alignment.CenterHorizontally),
+ text = "Quick settings grid",
+ modifier =
+ Modifier.element(QuickSettings.Elements.CollapsedGrid)
+ .align(Alignment.CenterHorizontally),
style = MaterialTheme.typography.titleLarge,
color = MaterialTheme.colorScheme.onPrimary,
)
Spacer(modifier = Modifier.weight(1f))
Text(
text = "QS footer actions",
- modifier = Modifier.align(Alignment.CenterHorizontally),
+ modifier =
+ Modifier.element(QuickSettings.Elements.FooterActions)
+ .align(Alignment.CenterHorizontally),
style = MaterialTheme.typography.titleSmall,
color = MaterialTheme.colorScheme.onPrimary,
)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
index 29763c2..e5cd439 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
@@ -16,19 +16,17 @@
package com.android.systemui.qs.ui.composable
-import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.clickable
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.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxSize
+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.compose.animation.scene.SceneScope
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.footer.ui.compose.QuickSettings
import com.android.systemui.qs.ui.viewmodel.QuickSettingsSceneViewModel
import com.android.systemui.scene.shared.model.Direction
import com.android.systemui.scene.shared.model.SceneKey
@@ -69,23 +67,18 @@
}
@Composable
-private fun QuickSettingsScene(
+private fun SceneScope.QuickSettingsScene(
viewModel: QuickSettingsSceneViewModel,
modifier: Modifier = Modifier,
) {
// TODO(b/280887232): implement the real UI.
- Box(modifier = modifier) {
- Column(
- horizontalAlignment = Alignment.CenterHorizontally,
- modifier = Modifier.align(Alignment.Center)
- ) {
- Text("Quick settings", style = MaterialTheme.typography.headlineMedium)
- Row(
- horizontalArrangement = Arrangement.spacedBy(8.dp),
- ) {
- Button(onClick = { viewModel.onContentClicked() }) { Text("Open some content") }
- }
- }
+ Box(
+ modifier
+ .fillMaxSize()
+ .clickable(onClick = { viewModel.onContentClicked() })
+ .padding(horizontal = 16.dp, vertical = 48.dp)
+ ) {
+ QuickSettings(modifier = Modifier.fillMaxHeight())
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
index f91baf2..c865070 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
@@ -31,7 +31,6 @@
import com.android.compose.animation.scene.Swipe
import com.android.compose.animation.scene.UserAction as SceneTransitionUserAction
import com.android.compose.animation.scene.observableTransitionState
-import com.android.compose.animation.scene.transitions
import com.android.systemui.scene.shared.model.Direction
import com.android.systemui.scene.shared.model.ObservableTransitionState
import com.android.systemui.scene.shared.model.SceneKey
@@ -78,7 +77,7 @@
SceneTransitionLayout(
currentScene = currentSceneKey.toTransitionSceneKey(),
onChangeScene = viewModel::onSceneChanged,
- transitions = transitions {},
+ transitions = SceneContainerTransitions,
state = state,
modifier = modifier.fillMaxSize(),
) {
@@ -98,7 +97,9 @@
) {
with(composableScene) {
this@scene.Content(
- modifier = Modifier.fillMaxSize(),
+ modifier =
+ Modifier.element(sceneKey.toTransitionSceneKey().rootElementKey)
+ .fillMaxSize(),
)
}
}
@@ -129,14 +130,6 @@
}
// TODO(b/293899074): remove this once we can use the one from SceneTransitionLayout.
-private fun SceneKey.toTransitionSceneKey(): SceneTransitionSceneKey {
- return SceneTransitionSceneKey(
- name = toString(),
- identity = this,
- )
-}
-
-// TODO(b/293899074): remove this once we can use the one from SceneTransitionLayout.
private fun SceneTransitionSceneKey.toModel(): SceneModel {
return SceneModel(key = identity as SceneKey)
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt
new file mode 100644
index 0000000..404bf81
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt
@@ -0,0 +1,34 @@
+package com.android.systemui.scene.ui.composable
+
+import com.android.compose.animation.scene.transitions
+import com.android.systemui.scene.ui.composable.transitions.bouncerToGoneTransition
+import com.android.systemui.scene.ui.composable.transitions.goneToQuickSettingsTransition
+import com.android.systemui.scene.ui.composable.transitions.goneToShadeTransition
+import com.android.systemui.scene.ui.composable.transitions.lockscreenToBouncerTransition
+import com.android.systemui.scene.ui.composable.transitions.lockscreenToGoneTransition
+import com.android.systemui.scene.ui.composable.transitions.lockscreenToQuickSettingsTransition
+import com.android.systemui.scene.ui.composable.transitions.lockscreenToShadeTransition
+import com.android.systemui.scene.ui.composable.transitions.shadeToQuickSettingsTransition
+
+/**
+ * Comprehensive definition of all transitions between scenes in [SceneContainer].
+ *
+ * Transitions are automatically reversible, so define only one transition per scene pair. By
+ * convention, use the more common transition direction when defining the pair order, e.g.
+ * Lockscreen to Bouncer rather than Bouncer to Lockscreen.
+ *
+ * The actual transition DSL must be placed in a separate file under the package
+ * [com.android.systemui.scene.ui.composable.transitions].
+ *
+ * Please keep the list sorted alphabetically.
+ */
+val SceneContainerTransitions = transitions {
+ from(Bouncer, to = Gone) { bouncerToGoneTransition() }
+ from(Gone, to = Shade) { goneToShadeTransition() }
+ from(Gone, to = QuickSettings) { goneToQuickSettingsTransition() }
+ from(Lockscreen, to = Bouncer) { lockscreenToBouncerTransition() }
+ from(Lockscreen, to = Shade) { lockscreenToShadeTransition() }
+ from(Lockscreen, to = QuickSettings) { lockscreenToQuickSettingsTransition() }
+ from(Lockscreen, to = Gone) { lockscreenToGoneTransition() }
+ from(Shade, to = QuickSettings) { shadeToQuickSettingsTransition() }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/TransitionSceneKeys.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/TransitionSceneKeys.kt
new file mode 100644
index 0000000..8d0d705
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/TransitionSceneKeys.kt
@@ -0,0 +1,15 @@
+package com.android.systemui.scene.ui.composable
+
+import com.android.compose.animation.scene.SceneKey as SceneTransitionSceneKey
+import com.android.systemui.scene.shared.model.SceneKey
+
+val Lockscreen = SceneKey.Lockscreen.toTransitionSceneKey()
+val Bouncer = SceneKey.Bouncer.toTransitionSceneKey()
+val Shade = SceneKey.Shade.toTransitionSceneKey()
+val QuickSettings = SceneKey.QuickSettings.toTransitionSceneKey()
+val Gone = SceneKey.Gone.toTransitionSceneKey()
+
+// TODO(b/293899074): Remove this file once we can use the scene keys from SceneTransitionLayout.
+fun SceneKey.toTransitionSceneKey(): SceneTransitionSceneKey {
+ return SceneTransitionSceneKey(name = toString(), identity = this)
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromBouncerToGoneTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromBouncerToGoneTransition.kt
new file mode 100644
index 0000000..1a9face
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromBouncerToGoneTransition.kt
@@ -0,0 +1,11 @@
+package com.android.systemui.scene.ui.composable.transitions
+
+import androidx.compose.animation.core.tween
+import com.android.compose.animation.scene.TransitionBuilder
+import com.android.systemui.scene.ui.composable.Bouncer
+
+fun TransitionBuilder.bouncerToGoneTransition() {
+ spec = tween(durationMillis = 500)
+
+ fade(Bouncer.rootElementKey)
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToQuickSettingsTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToQuickSettingsTransition.kt
new file mode 100644
index 0000000..38712b0
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToQuickSettingsTransition.kt
@@ -0,0 +1,11 @@
+package com.android.systemui.scene.ui.composable.transitions
+
+import androidx.compose.animation.core.tween
+import com.android.compose.animation.scene.TransitionBuilder
+import com.android.systemui.scene.ui.composable.QuickSettings
+
+fun TransitionBuilder.goneToQuickSettingsTransition() {
+ spec = tween(durationMillis = 500)
+
+ fade(QuickSettings.rootElementKey)
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToShadeTransition.kt
new file mode 100644
index 0000000..1d57c1a
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToShadeTransition.kt
@@ -0,0 +1,11 @@
+package com.android.systemui.scene.ui.composable.transitions
+
+import androidx.compose.animation.core.tween
+import com.android.compose.animation.scene.TransitionBuilder
+import com.android.systemui.scene.ui.composable.Shade
+
+fun TransitionBuilder.goneToShadeTransition() {
+ spec = tween(durationMillis = 500)
+
+ fade(Shade.rootElementKey)
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToBouncerTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToBouncerTransition.kt
new file mode 100644
index 0000000..1fee874
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToBouncerTransition.kt
@@ -0,0 +1,14 @@
+package com.android.systemui.scene.ui.composable.transitions
+
+import androidx.compose.animation.core.tween
+import androidx.compose.ui.unit.dp
+import com.android.compose.animation.scene.TransitionBuilder
+import com.android.systemui.bouncer.ui.composable.Bouncer
+
+fun TransitionBuilder.lockscreenToBouncerTransition() {
+ spec = tween(durationMillis = 500)
+
+ translate(Bouncer.Elements.Content, y = 300.dp)
+ fractionRange(end = 0.5f) { fade(Bouncer.Elements.Background) }
+ fractionRange(start = 0.5f) { fade(Bouncer.Elements.Content) }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToGoneTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToGoneTransition.kt
new file mode 100644
index 0000000..da6306d
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToGoneTransition.kt
@@ -0,0 +1,11 @@
+package com.android.systemui.scene.ui.composable.transitions
+
+import androidx.compose.animation.core.tween
+import com.android.compose.animation.scene.TransitionBuilder
+import com.android.systemui.scene.ui.composable.Lockscreen
+
+fun TransitionBuilder.lockscreenToGoneTransition() {
+ spec = tween(durationMillis = 500)
+
+ fade(Lockscreen.rootElementKey)
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToQuickSettingsTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToQuickSettingsTransition.kt
new file mode 100644
index 0000000..9a8a3e2
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToQuickSettingsTransition.kt
@@ -0,0 +1,11 @@
+package com.android.systemui.scene.ui.composable.transitions
+
+import androidx.compose.animation.core.tween
+import com.android.compose.animation.scene.TransitionBuilder
+import com.android.systemui.scene.ui.composable.QuickSettings
+
+fun TransitionBuilder.lockscreenToQuickSettingsTransition() {
+ spec = tween(durationMillis = 500)
+
+ fade(QuickSettings.rootElementKey)
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToShadeTransition.kt
new file mode 100644
index 0000000..7ecfb62
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToShadeTransition.kt
@@ -0,0 +1,24 @@
+package com.android.systemui.scene.ui.composable.transitions
+
+import androidx.compose.animation.core.tween
+import com.android.compose.animation.scene.Edge
+import com.android.compose.animation.scene.TransitionBuilder
+import com.android.systemui.notifications.ui.composable.Notifications
+import com.android.systemui.qs.footer.ui.compose.QuickSettings
+import com.android.systemui.shade.ui.composable.Shade
+
+fun TransitionBuilder.lockscreenToShadeTransition() {
+ spec = tween(durationMillis = 500)
+
+ punchHole(Shade.Elements.QuickSettings, bounds = Shade.Elements.Scrim, Shade.Shapes.Scrim)
+ translate(Shade.Elements.Scrim, Edge.Top, startsOutsideLayoutBounds = false)
+ fractionRange(end = 0.5f) {
+ fade(Shade.Elements.ScrimBackground)
+ translate(
+ QuickSettings.Elements.CollapsedGrid,
+ Edge.Top,
+ startsOutsideLayoutBounds = false,
+ )
+ }
+ fractionRange(start = 0.5f) { fade(Notifications.Elements.Notifications) }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromShadeToQuickSettingsTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromShadeToQuickSettingsTransition.kt
new file mode 100644
index 0000000..6c7964b
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromShadeToQuickSettingsTransition.kt
@@ -0,0 +1,15 @@
+package com.android.systemui.scene.ui.composable.transitions
+
+import androidx.compose.animation.core.tween
+import com.android.compose.animation.scene.Edge
+import com.android.compose.animation.scene.TransitionBuilder
+import com.android.systemui.notifications.ui.composable.Notifications
+import com.android.systemui.qs.footer.ui.compose.QuickSettings
+
+fun TransitionBuilder.shadeToQuickSettingsTransition() {
+ spec = tween(durationMillis = 500)
+
+ translate(Notifications.Elements.Notifications, Edge.Bottom)
+ fade(Notifications.Elements.Notifications)
+ timestampRange(endMillis = 83) { fade(QuickSettings.Elements.FooterActions) }
+}
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 ff1cb5f..f985aa2 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,16 +16,22 @@
package com.android.systemui.shade.ui.composable
+import androidx.compose.foundation.background
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.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
+import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.SceneScope
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
@@ -44,6 +50,26 @@
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
+object Shade {
+ object Elements {
+ val QuickSettings = ElementKey("ShadeQuickSettings")
+ val Scrim = ElementKey("ShadeScrim")
+ val ScrimBackground = ElementKey("ShadeScrimBackground")
+ }
+
+ object Dimensions {
+ val ScrimCornerSize = 32.dp
+ }
+
+ object Shapes {
+ val Scrim =
+ RoundedCornerShape(
+ topStart = Dimensions.ScrimCornerSize,
+ topEnd = Dimensions.ScrimCornerSize,
+ )
+ }
+}
+
/** The shade scene shows scrolling list of notifications and some of the quick setting tiles. */
@SysUISingleton
class ShadeScene
@@ -79,20 +105,28 @@
}
@Composable
-private fun ShadeScene(
+private fun SceneScope.ShadeScene(
viewModel: ShadeSceneViewModel,
modifier: Modifier = Modifier,
) {
- 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))
+ Box(modifier.element(Shade.Elements.Scrim)) {
+ Spacer(
+ modifier =
+ Modifier.element(Shade.Elements.ScrimBackground)
+ .fillMaxSize()
+ .background(MaterialTheme.colorScheme.scrim, shape = Shade.Shapes.Scrim)
+ )
+
+ 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/plugin/src/com/android/systemui/plugins/WeatherData.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/WeatherData.kt
index f83fa33..affb76b 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/WeatherData.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/WeatherData.kt
@@ -57,7 +57,7 @@
private fun readIntFromBundle(extras: Bundle, key: String): Int? =
try {
- extras.getString(key).toInt()
+ extras.getString(key)?.toInt()
} catch (e: Exception) {
null
}
diff --git a/packages/SystemUI/res-keyguard/layout-land/keyguard_pin_view.xml b/packages/SystemUI/res-keyguard/layout-land/keyguard_pin_view.xml
new file mode 100644
index 0000000..cd7ab98
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/layout-land/keyguard_pin_view.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+**
+** 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.
+*/
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <include layout="@layout/keyguard_pin_view_landscape" />
+
+</FrameLayout>
diff --git a/packages/SystemUI/res-keyguard/layout-sw600dp-land/keyguard_pin_view.xml b/packages/SystemUI/res-keyguard/layout-sw600dp-land/keyguard_pin_view.xml
new file mode 100644
index 0000000..80cc8c0
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/layout-sw600dp-land/keyguard_pin_view.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+**
+** 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.
+*/
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <include layout="@layout/keyguard_pin_view_portrait" />
+
+</FrameLayout>
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_pin_view_landscape.xml b/packages/SystemUI/res-keyguard/layout/keyguard_pin_view_landscape.xml
new file mode 100644
index 0000000..e00742d
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_pin_view_landscape.xml
@@ -0,0 +1,231 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+**
+** 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.
+*/
+-->
+
+<com.android.keyguard.KeyguardPINView xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:id="@+id/keyguard_pin_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_gravity="center_horizontal|bottom"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:orientation="horizontal">
+
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="2"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:layoutDirection="ltr"
+ android:orientation="vertical">
+
+ <include layout="@layout/keyguard_bouncer_message_area" />
+
+ <com.android.systemui.bouncer.ui.BouncerMessageView
+ android:id="@+id/bouncer_message_view"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ androidprv:layout_constraintBottom_toTopOf="@+id/row0"
+ androidprv:layout_constraintTop_toTopOf="parent"
+ androidprv:layout_constraintVertical_chainStyle="packed" />
+
+ <!-- Set this to be just above key1. It would be better to introduce a barrier above
+ key1/key2/key3, then place this View above that. Sadly, that doesn't work (the Barrier
+ drops to the bottom of the page, and key1/2/3 all shoot up to the top-left). In any
+ case, the Flow should ensure that key1/2/3 all have the same top, so this should be
+ fine. -->
+ <com.android.keyguard.AlphaOptimizedRelativeLayout
+ android:id="@+id/row0"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingBottom="@dimen/num_pad_entry_row_margin_bottom"
+ androidprv:layout_constraintBottom_toTopOf="@+id/keyguard_selector_fade_container"
+ androidprv:layout_constraintTop_toBottomOf="@+id/bouncer_message_view"
+ tools:layout_editor_absoluteX="-16dp">
+
+ <com.android.keyguard.PasswordTextView
+ android:id="@+id/pinEntry"
+ style="@style/Widget.TextView.Password"
+ android:layout_width="@dimen/keyguard_security_width"
+ android:layout_height="@dimen/keyguard_password_height"
+ android:layout_centerHorizontal="true"
+ android:layout_marginRight="72dp"
+ android:contentDescription="@string/keyguard_accessibility_pin_area"
+ androidprv:scaledTextSize="@integer/scaled_password_text_size" />
+ </com.android.keyguard.AlphaOptimizedRelativeLayout>
+
+ <include
+ android:id="@+id/keyguard_selector_fade_container"
+ layout="@layout/keyguard_eca"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="bottom|center_horizontal"
+ android:layout_marginBottom="@dimen/keyguard_eca_bottom_margin"
+ android:layout_marginTop="@dimen/keyguard_eca_top_margin"
+ android:gravity="center_horizontal"
+ android:orientation="vertical"
+ androidprv:layout_constraintBottom_toBottomOf="parent" />
+
+ </androidx.constraintlayout.widget.ConstraintLayout>
+
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:id="@+id/pin_container"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="3"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:layoutDirection="ltr"
+ android:orientation="vertical">
+
+ <!-- Guideline used to place the top row of keys relative to the screen height. This will be
+ updated in KeyguardPINView to reduce the height of the PIN pad. -->
+ <androidx.constraintlayout.widget.Guideline
+ android:id="@+id/pin_pad_top_guideline"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ androidprv:layout_constraintGuide_percent="0" />
+
+ <com.android.keyguard.KeyguardPinFlowView
+ android:id="@+id/flow1"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:orientation="horizontal"
+
+ androidprv:constraint_referenced_ids="key1,key2,key3,key4,key5,key6,key7,key8,key9,delete_button,key0,key_enter"
+
+ androidprv:flow_horizontalGap="@dimen/num_pad_key_margin_end"
+
+ androidprv:flow_horizontalStyle="packed"
+ androidprv:flow_maxElementsWrap="3"
+
+ androidprv:flow_verticalBias="0.5"
+ androidprv:flow_verticalGap="@dimen/num_pad_entry_row_margin_bottom"
+ androidprv:flow_verticalStyle="packed"
+
+ androidprv:flow_wrapMode="aligned"
+ androidprv:layout_constraintBottom_toBottomOf="parent"
+ androidprv:layout_constraintEnd_toEndOf="parent"
+ androidprv:layout_constraintStart_toStartOf="parent"
+ androidprv:layout_constraintTop_toBottomOf="@id/pin_pad_top_guideline" />
+
+ <com.android.keyguard.NumPadButton
+ android:id="@+id/delete_button"
+ style="@style/NumPadKey.Delete"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/key0"
+ android:contentDescription="@string/keyboardview_keycode_delete" />
+
+ <com.android.keyguard.NumPadButton
+ android:id="@+id/key_enter"
+ style="@style/NumPadKey.Enter"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:contentDescription="@string/keyboardview_keycode_enter" />
+
+ <com.android.keyguard.NumPadKey
+ android:id="@+id/key1"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/key2"
+ androidprv:digit="1"
+ androidprv:textView="@+id/pinEntry" />
+
+ <com.android.keyguard.NumPadKey
+ android:id="@+id/key2"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/key3"
+ androidprv:digit="2"
+ androidprv:textView="@+id/pinEntry" />
+
+ <com.android.keyguard.NumPadKey
+ android:id="@+id/key3"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/key4"
+ androidprv:digit="3"
+ androidprv:textView="@+id/pinEntry" />
+
+ <com.android.keyguard.NumPadKey
+ android:id="@+id/key4"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/key5"
+ androidprv:digit="4"
+ androidprv:textView="@+id/pinEntry" />
+
+ <com.android.keyguard.NumPadKey
+ android:id="@+id/key5"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/key6"
+ androidprv:digit="5"
+ androidprv:textView="@+id/pinEntry" />
+
+
+ <com.android.keyguard.NumPadKey
+ android:id="@+id/key6"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/key7"
+ androidprv:digit="6"
+ androidprv:textView="@+id/pinEntry" />
+
+ <com.android.keyguard.NumPadKey
+ android:id="@+id/key7"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/key8"
+ androidprv:digit="7"
+ androidprv:textView="@+id/pinEntry" />
+
+ <com.android.keyguard.NumPadKey
+ android:id="@+id/key8"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/key9"
+ androidprv:digit="8"
+ androidprv:textView="@+id/pinEntry" />
+
+ <com.android.keyguard.NumPadKey
+ android:id="@+id/key9"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/delete_button"
+ androidprv:digit="9"
+ androidprv:textView="@+id/pinEntry" />
+
+ <com.android.keyguard.NumPadKey
+ android:id="@+id/key0"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/key_enter"
+ androidprv:digit="0"
+ androidprv:textView="@+id/pinEntry" />
+
+ </androidx.constraintlayout.widget.ConstraintLayout>
+
+</com.android.keyguard.KeyguardPINView>
diff --git a/packages/SystemUI/res/layout/keyguard_bottom_area.xml b/packages/SystemUI/res/layout/keyguard_bottom_area.xml
index 36f7b96..66c57fc 100644
--- a/packages/SystemUI/res/layout/keyguard_bottom_area.xml
+++ b/packages/SystemUI/res/layout/keyguard_bottom_area.xml
@@ -22,6 +22,42 @@
android:layout_width="match_parent"
android:outlineProvider="none" >
+ <LinearLayout
+ android:id="@id/keyguard_indication_area"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="@dimen/keyguard_indication_margin_bottom"
+ android:layout_gravity="bottom|center_horizontal"
+ android:orientation="vertical">
+
+ <com.android.systemui.statusbar.phone.KeyguardIndicationTextView
+ android:id="@id/keyguard_indication_text"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:paddingStart="@dimen/keyguard_indication_text_padding"
+ android:paddingEnd="@dimen/keyguard_indication_text_padding"
+ android:textAppearance="@style/TextAppearance.Keyguard.BottomArea"
+ android:accessibilityLiveRegion="polite"/>
+
+ <com.android.systemui.statusbar.phone.KeyguardIndicationTextView
+ android:id="@id/keyguard_indication_text_bottom"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:minHeight="@dimen/keyguard_indication_text_min_height"
+ android:layout_gravity="center_horizontal"
+ android:paddingStart="@dimen/keyguard_indication_text_padding"
+ android:paddingEnd="@dimen/keyguard_indication_text_padding"
+ android:textAppearance="@style/TextAppearance.Keyguard.BottomArea"
+ android:maxLines="2"
+ android:ellipsize="end"
+ android:alpha=".8"
+ android:accessibilityLiveRegion="polite"
+ android:visibility="gone"/>
+
+ </LinearLayout>
+
<com.android.systemui.animation.view.LaunchableImageView
android:id="@+id/start_button"
android:layout_height="@dimen/keyguard_affordance_fixed_height"
diff --git a/packages/SystemUI/res/values-sw600dp/dimens.xml b/packages/SystemUI/res/values-sw600dp/dimens.xml
index 7e892f7..d85e012 100644
--- a/packages/SystemUI/res/values-sw600dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp/dimens.xml
@@ -21,9 +21,11 @@
<dimen name="status_bar_header_height_keyguard">@dimen/status_bar_height</dimen>
<!-- padding for container with status icons and battery -->
- <dimen name="status_bar_icons_padding_end">12dp</dimen>
+ <dimen name="status_bar_icons_padding_end">4dp</dimen>
<!-- start padding is smaller to account for status icon margins coming from drawable itself -->
- <dimen name="status_bar_icons_padding_start">11dp</dimen>
+ <dimen name="status_bar_icons_padding_start">3dp</dimen>
+ <dimen name="status_bar_icons_padding_bottom">2dp</dimen>
+ <dimen name="status_bar_icons_padding_top">2dp</dimen>
<dimen name="status_bar_padding_end">0dp</dimen>
@@ -78,8 +80,8 @@
<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>
+ <dimen name="shade_header_system_icons_padding_start">3dp</dimen>
+ <dimen name="shade_header_system_icons_padding_end">4dp</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 d74eca6..dc1f0e4 100644
--- a/packages/SystemUI/res/values-sw720dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw720dp/dimens.xml
@@ -16,9 +16,6 @@
*/
-->
<resources>
- <!-- it's a bit smaller on 720dp to account for status_bar_icon_horizontal_margin -->
- <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">1sp</dimen>
@@ -30,9 +27,6 @@
<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/config.xml b/packages/SystemUI/res/values/config.xml
index b6ef594..6164f29f 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -774,6 +774,9 @@
<!-- Flag to enable privacy dot views, it shall be true for normal case -->
<bool name="config_enablePrivacyDot">true</bool>
+ <!-- Flag to enable privacy chip animation, it shall be true for normal case -->
+ <bool name="config_enablePrivacyChipAnimation">true</bool>
+
<!-- Class for the communal source connector to be used -->
<string name="config_communalSourceConnector" translatable="false"></string>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index a056445..55978e6 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -351,9 +351,9 @@
<!-- paddings for container with status icons and battery -->
<!-- padding start is a bit smaller than end to account for status icon margin-->
- <dimen name="status_bar_icons_padding_start">11dp</dimen>
+ <dimen name="status_bar_icons_padding_start">3dp</dimen>
- <dimen name="status_bar_icons_padding_end">0dp</dimen>
+ <dimen name="status_bar_icons_padding_end">4dp</dimen>
<dimen name="status_bar_icons_padding_bottom">0dp</dimen>
<dimen name="status_bar_icons_padding_top">0dp</dimen>
@@ -364,7 +364,7 @@
<dimen name="status_bar_padding_start">8dp</dimen>
<!-- the padding on the end of the statusbar -->
- <dimen name="status_bar_padding_end">8dp</dimen>
+ <dimen name="status_bar_padding_end">4dp</dimen>
<!-- the padding on the top of the statusbar (usually 0) -->
<dimen name="status_bar_padding_top">0dp</dimen>
@@ -1607,7 +1607,7 @@
<!-- Status bar user chip -->
<dimen name="status_bar_user_chip_avatar_size">16dp</dimen>
<!-- below also works as break between user chip and hover state of status icons -->
- <dimen name="status_bar_user_chip_end_margin">4dp</dimen>
+ <dimen name="status_bar_user_chip_end_margin">8dp</dimen>
<dimen name="status_bar_user_chip_text_size">12sp</dimen>
<!-- System UI Dialog -->
diff --git a/packages/SystemUI/res/values/flags.xml b/packages/SystemUI/res/values/flags.xml
index c2dba6c..261b08d 100644
--- a/packages/SystemUI/res/values/flags.xml
+++ b/packages/SystemUI/res/values/flags.xml
@@ -40,4 +40,7 @@
<!-- Whether face auth will immediately stop when the display state is OFF -->
<bool name="flag_stop_face_auth_on_display_off">false</bool>
+
+ <!-- Whether we want to stop pulsing while running the face scanning animation -->
+ <bool name="flag_stop_pulsing_face_scanning_animation">true</bool>
</resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 6ea8df8..cddfda2 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2406,6 +2406,8 @@
<string name="magnification_open_settings_click_label">Open magnification settings</string>
<!-- Click action label for magnification settings panel. [CHAR LIMIT=NONE] -->
<string name="magnification_close_settings_click_label">Close magnification settings</string>
+ <!-- Click action label for exiting magnifier edit mode. [CHAR LIMIT=NONE] -->
+ <string name="magnification_exit_edit_mode_click_label">Exit edit mode</string>
<!-- Label of the corner of a rectangle that you can tap and drag to resize the magnification area. [CHAR LIMIT=NONE] -->
<string name="magnification_drag_corner_to_resize">Drag corner to resize</string>
diff --git a/packages/SystemUI/res/xml/large_screen_shade_header.xml b/packages/SystemUI/res/xml/large_screen_shade_header.xml
index cb2c3a1..2ec6180 100644
--- a/packages/SystemUI/res/xml/large_screen_shade_header.xml
+++ b/packages/SystemUI/res/xml/large_screen_shade_header.xml
@@ -60,7 +60,7 @@
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/privacy_container"
app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintEnd_toEndOf="@id/carrier_group"/>
+ app:layout_constraintStart_toEndOf="@id/carrier_group"/>
<PropertySet android:alpha="1" />
</Constraint>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerKt.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerKt.kt
index c142933..5edd283 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerKt.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerKt.kt
@@ -27,6 +27,6 @@
*/
fun ActivityManager.isInForeground(packageName: String): Boolean {
val tasks: List<ActivityManager.RunningTaskInfo> = getRunningTasks(1)
- return tasks.isNotEmpty() && packageName == tasks[0].topActivity.packageName
+ return tasks.isNotEmpty() && packageName == tasks[0].topActivity?.packageName
}
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/SmartspaceState.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/SmartspaceState.kt
index d7e61d6..ebc57d2 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/SmartspaceState.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/SmartspaceState.kt
@@ -31,15 +31,15 @@
var visibleOnScreen = false
constructor(parcel: Parcel) : this() {
- this.boundsOnScreen = parcel.readParcelable(Rect::javaClass.javaClass.classLoader)
+ this.boundsOnScreen = parcel.readParcelable(Rect::javaClass.javaClass.classLoader) ?: Rect()
this.selectedPage = parcel.readInt()
this.visibleOnScreen = parcel.readBoolean()
}
- override fun writeToParcel(dest: Parcel?, flags: Int) {
- dest?.writeParcelable(boundsOnScreen, 0)
- dest?.writeInt(selectedPage)
- dest?.writeBoolean(visibleOnScreen)
+ override fun writeToParcel(dest: Parcel, flags: Int) {
+ dest.writeParcelable(boundsOnScreen, 0)
+ dest.writeInt(selectedPage)
+ dest.writeBoolean(visibleOnScreen)
}
override fun describeContents(): Int {
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProvider.kt
index aca9907..dac130d 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProvider.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProvider.kt
@@ -39,7 +39,7 @@
fun init() {
rotationChangeProvider.addCallback(rotationListener)
- rotationListener.onRotationChanged(context.display.rotation)
+ context.display?.rotation?.let { rotationListener.onRotationChanged(it) }
}
private val rotationListener = RotationListener { rotation ->
diff --git a/packages/SystemUI/src/com/android/keyguard/BouncerKeyguardMessageArea.kt b/packages/SystemUI/src/com/android/keyguard/BouncerKeyguardMessageArea.kt
index 78a5c98..495367b 100644
--- a/packages/SystemUI/src/com/android/keyguard/BouncerKeyguardMessageArea.kt
+++ b/packages/SystemUI/src/com/android/keyguard/BouncerKeyguardMessageArea.kt
@@ -105,7 +105,7 @@
hideAnimator.addListener(
object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator?) {
+ override fun onAnimationEnd(animation: Animator) {
super@BouncerKeyguardMessageArea.setMessage(msg, animate)
}
}
@@ -118,7 +118,7 @@
showAnimator.addListener(
object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator?) {
+ override fun onAnimationEnd(animation: Animator) {
textAboutToShow = null
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index 4a6e53d..4f4eec6 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -144,7 +144,7 @@
smallClockOnAttachStateChangeListener =
object : OnAttachStateChangeListener {
var pastVisibility: Int? = null
- override fun onViewAttachedToWindow(view: View?) {
+ override fun onViewAttachedToWindow(view: View) {
value.events.onTimeFormatChanged(DateFormat.is24HourFormat(context))
if (view != null) {
smallClockFrame = view.parent as FrameLayout
@@ -168,7 +168,7 @@
}
}
- override fun onViewDetachedFromWindow(p0: View?) {
+ override fun onViewDetachedFromWindow(p0: View) {
smallClockFrame?.viewTreeObserver
?.removeOnGlobalLayoutListener(onGlobalLayoutListener)
}
@@ -178,10 +178,10 @@
largeClockOnAttachStateChangeListener =
object : OnAttachStateChangeListener {
- override fun onViewAttachedToWindow(p0: View?) {
+ override fun onViewAttachedToWindow(p0: View) {
value.events.onTimeFormatChanged(DateFormat.is24HourFormat(context))
}
- override fun onViewDetachedFromWindow(p0: View?) {
+ override fun onViewDetachedFromWindow(p0: View) {
}
}
value.largeClock.view
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index 42a4e72..42dceb8 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -100,6 +100,7 @@
import com.android.systemui.R;
import com.android.systemui.classifier.FalsingA11yDelegate;
import com.android.systemui.plugins.FalsingManager;
+import com.android.systemui.shade.TouchLogger;
import com.android.systemui.shared.system.SysUiStatsLog;
import com.android.systemui.statusbar.policy.BaseUserSwitcherAdapter;
import com.android.systemui.statusbar.policy.UserSwitcherController;
@@ -666,6 +667,11 @@
}
@Override
+ public boolean dispatchTouchEvent(MotionEvent ev) {
+ return TouchLogger.logDispatchTouch(TAG, ev, super.dispatchTouchEvent(ev));
+ }
+
+ @Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
if (mViewMediatorCallback != null) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 4e1cbc7..d9a1dc6 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -33,7 +33,6 @@
import android.app.admin.DevicePolicyManager;
import android.content.Intent;
import android.content.res.ColorStateList;
-import android.content.res.Configuration;
import android.content.res.Resources;
import android.hardware.biometrics.BiometricOverlayConstants;
import android.media.AudioManager;
@@ -69,6 +68,7 @@
import com.android.settingslib.utils.ThreadUtils;
import com.android.systemui.Gefingerpoken;
import com.android.systemui.R;
+import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor;
import com.android.systemui.biometrics.FaceAuthAccessibilityDelegate;
import com.android.systemui.biometrics.SideFpsController;
import com.android.systemui.biometrics.SideFpsUiRequestSource;
@@ -78,11 +78,10 @@
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
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.SceneKey;
import com.android.systemui.shared.system.SysUiStatsLog;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -128,6 +127,7 @@
private final KeyguardFaceAuthInteractor mKeyguardFaceAuthInteractor;
private final BouncerMessageInteractor mBouncerMessageInteractor;
private int mTranslationY;
+ private final KeyguardTransitionInteractor mKeyguardTransitionInteractor;
// Whether the volume keys should be handled by keyguard. If true, then
// they will be handled here for specific media types such as music, otherwise
// the audio service will bring up the volume dialog.
@@ -143,7 +143,7 @@
private Runnable mCancelAction;
private boolean mWillRunDismissFromKeyguard;
- private int mLastOrientation = Configuration.ORIENTATION_UNDEFINED;
+ private int mLastOrientation;
private SecurityMode mCurrentSecurityMode = SecurityMode.Invalid;
private UserSwitcherController.UserSwitchCallback mUserSwitchCallback =
@@ -301,6 +301,10 @@
mViewMediatorCallback.keyguardDone(fromPrimaryAuth, targetUserId);
}
}
+
+ if (mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ mKeyguardTransitionInteractor.startDismissKeyguardTransition();
+ }
}
@Override
@@ -349,7 +353,14 @@
@Override
public void onDensityOrFontScaleChanged() {
- KeyguardSecurityContainerController.this.onDensityOrFontScaleChanged();
+ KeyguardSecurityContainerController.this
+ .onDensityOrFontScaleOrOrientationChanged();
+ }
+
+ @Override
+ public void onOrientationChanged(int orientation) {
+ KeyguardSecurityContainerController.this
+ .onDensityOrFontScaleOrOrientationChanged();
}
};
private final KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback =
@@ -388,7 +399,7 @@
}
};
private final UserInteractor mUserInteractor;
- private final Provider<SceneInteractor> mSceneInteractor;
+ private final Provider<AuthenticationInteractor> mAuthenticationInteractor;
private final Provider<JavaAdapter> mJavaAdapter;
@Nullable private Job mSceneTransitionCollectionJob;
@@ -419,7 +430,8 @@
Provider<JavaAdapter> javaAdapter,
UserInteractor userInteractor,
FaceAuthAccessibilityDelegate faceAuthAccessibilityDelegate,
- Provider<SceneInteractor> sceneInteractor
+ KeyguardTransitionInteractor keyguardTransitionInteractor,
+ Provider<AuthenticationInteractor> authenticationInteractor
) {
super(view);
view.setAccessibilityDelegate(faceAuthAccessibilityDelegate);
@@ -448,8 +460,9 @@
mKeyguardFaceAuthInteractor = keyguardFaceAuthInteractor;
mBouncerMessageInteractor = bouncerMessageInteractor;
mUserInteractor = userInteractor;
- mSceneInteractor = sceneInteractor;
+ mAuthenticationInteractor = authenticationInteractor;
mJavaAdapter = javaAdapter;
+ mKeyguardTransitionInteractor = keyguardTransitionInteractor;
}
@Override
@@ -474,19 +487,21 @@
showPrimarySecurityScreen(false);
if (mFeatureFlags.isEnabled(Flags.SCENE_CONTAINER)) {
- // When the scene framework transitions from bouncer to gone, we dismiss the keyguard.
+ // When the scene framework says that the lockscreen has been dismissed, dismiss the
+ // keyguard here, revealing the underlying app or launcher:
mSceneTransitionCollectionJob = mJavaAdapter.get().alwaysCollectFlow(
- mSceneInteractor.get().finishedSceneTransitions(
- /* from= */ SceneKey.Bouncer.INSTANCE,
- /* to= */ SceneKey.Gone.INSTANCE),
- unused -> {
- final int selectedUserId = mUserInteractor.getSelectedUserId();
- showNextSecurityScreenOrFinish(
+ mAuthenticationInteractor.get().isLockscreenDismissed(),
+ isLockscreenDismissed -> {
+ if (isLockscreenDismissed) {
+ final int selectedUserId = mUserInteractor.getSelectedUserId();
+ showNextSecurityScreenOrFinish(
/* authenticated= */ true,
selectedUserId,
/* bypassSecondaryLockScreen= */ true,
mSecurityModel.getSecurityMode(selectedUserId));
- });
+ }
+ }
+ );
}
}
@@ -833,8 +848,7 @@
SecurityMode securityMode = mSecurityModel.getSecurityMode(targetUserId);
boolean isLockscreenDisabled = mLockPatternUtils.isLockScreenDisabled(
KeyguardUpdateMonitor.getCurrentUser());
-
- if (securityMode == SecurityMode.None) {
+ if (securityMode == SecurityMode.None || isLockscreenDisabled) {
finish = isLockscreenDisabled;
eventSubtype = BOUNCER_DISMISS_SIM;
uiEvent = BouncerUiEvent.BOUNCER_DISMISS_SIM;
@@ -1154,7 +1168,7 @@
}
/** Handles density or font scale changes. */
- private void onDensityOrFontScaleChanged() {
+ private void onDensityOrFontScaleOrOrientationChanged() {
reinflateViewFlipper(controller -> mView.onDensityOrFontScaleChanged());
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewTransition.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewTransition.kt
index 96ac8ad..e1c060f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewTransition.kt
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewTransition.kt
@@ -66,11 +66,11 @@
}
override fun createAnimator(
- sceneRoot: ViewGroup?,
+ sceneRoot: ViewGroup,
startValues: TransitionValues?,
endValues: TransitionValues?
): Animator? {
- if (sceneRoot == null || startValues == null || endValues == null) {
+ if (startValues == null || endValues == null) {
return null
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
index 7585279..5774e42 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
@@ -23,12 +23,14 @@
import android.os.Build;
import android.os.Trace;
import android.util.AttributeSet;
+import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewPropertyAnimator;
import android.widget.GridLayout;
import com.android.systemui.R;
+import com.android.systemui.shade.TouchLogger;
import com.android.systemui.statusbar.CrossFadeHelper;
import java.io.PrintWriter;
@@ -110,6 +112,11 @@
}
}
+ @Override
+ public boolean dispatchTouchEvent(MotionEvent ev) {
+ return TouchLogger.logDispatchTouch(TAG, ev, super.dispatchTouchEvent(ev));
+ }
+
public void dump(PrintWriter pw, String[] args) {
pw.println("KeyguardStatusView:");
pw.println(" mDarkAmount: " + mDarkAmount);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index 73b4c5f..757022d 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -543,7 +543,8 @@
@Nullable
@Override
- public Animator createAnimator(ViewGroup sceneRoot, @Nullable TransitionValues startValues,
+ public Animator createAnimator(@NonNull ViewGroup sceneRoot,
+ @Nullable TransitionValues startValues,
@Nullable TransitionValues endValues) {
if (startValues == null || endValues == null) {
return null;
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconView.java b/packages/SystemUI/src/com/android/keyguard/LockIconView.java
index 1a0c7f9..8611dbbb 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconView.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconView.java
@@ -22,6 +22,7 @@
import android.content.res.ColorStateList;
import android.graphics.Color;
import android.graphics.Point;
+import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
@@ -154,11 +155,16 @@
}
float getLocationTop() {
- return mLockIconCenter.y - mRadius;
+ Rect r = new Rect();
+ mLockIcon.getGlobalVisibleRect(r);
+ return r.top;
}
float getLocationBottom() {
- return mLockIconCenter.y + mRadius;
+ Rect r = new Rect();
+ mLockIcon.getGlobalVisibleRect(r);
+ return r.bottom;
+
}
/**
diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java b/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java
index a04a48d..e773416 100644
--- a/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java
+++ b/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java
@@ -58,6 +58,7 @@
private float mStartRadius;
private float mEndRadius;
private int mHeight;
+ private int mWidth;
private static final int EXPAND_ANIMATION_MS = 100;
private static final int EXPAND_COLOR_ANIMATION_MS = 50;
@@ -95,11 +96,17 @@
mBackground.setCornerRadius(mEndRadius + (mStartRadius - mEndRadius) * progress);
int height = (int) (mHeight * 0.7f + mHeight * 0.3 * progress);
int difference = mHeight - height;
- mBackground.setBounds(0, difference / 2, mHeight, mHeight - difference / 2);
+
+ int left = 0;
+ int top = difference / 2;
+ int right = mWidth;
+ int bottom = mHeight - difference / 2;
+ mBackground.setBounds(left, top, right, bottom);
}
- void onLayout(int height) {
+ void onLayout(int width, int height) {
boolean shouldUpdateHeight = height != mHeight;
+ mWidth = width;
mHeight = height;
mStartRadius = height / 2f;
mEndRadius = height / 4f;
@@ -121,7 +128,7 @@
ContextThemeWrapper ctw = new ContextThemeWrapper(context, mStyle);
@SuppressLint("ResourceType") TypedArray a = ctw.obtainStyledAttributes(customAttrs);
mNormalBackgroundColor = getPrivateAttrColorIfUnset(ctw, a, 0, 0,
- NUM_PAD_BACKGROUND);
+ NUM_PAD_BACKGROUND);
a.recycle();
mPressedBackgroundColor = getColorAttrDefaultColor(context, NUM_PAD_BACKGROUND_PRESSED);
diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadButton.java b/packages/SystemUI/src/com/android/keyguard/NumPadButton.java
index 3f1741a6..5c2f3b3 100644
--- a/packages/SystemUI/src/com/android/keyguard/NumPadButton.java
+++ b/packages/SystemUI/src/com/android/keyguard/NumPadButton.java
@@ -74,8 +74,9 @@
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
-
- if (mAnimator != null) mAnimator.onLayout(b - t);
+ int width = r - l;
+ int height = b - t;
+ if (mAnimator != null) mAnimator.onLayout(width, height);
}
@Override
diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
index edc298c..466d154 100644
--- a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
+++ b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
@@ -211,7 +211,9 @@
left = centerX - mKlondikeText.getMeasuredWidth() / 2;
mKlondikeText.layout(left, top, left + mKlondikeText.getMeasuredWidth(), bottom);
- if (mAnimator != null) mAnimator.onLayout(b - t);
+ int width = r - l;
+ int height = b - t;
+ if (mAnimator != null) mAnimator.onLayout(width, height);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index 04acd0b..27b9056 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -131,14 +131,14 @@
import com.android.systemui.util.leak.LeakReporter;
import com.android.systemui.util.sensors.AsyncSensorManager;
-import dagger.Lazy;
-
import java.util.concurrent.Executor;
import java.util.function.Consumer;
import javax.inject.Inject;
import javax.inject.Named;
+import dagger.Lazy;
+
/**
* Class to handle ugly dependencies throughout sysui until we determine the
* long-term dependency injection solution.
@@ -280,7 +280,6 @@
@Inject Lazy<AccessibilityManagerWrapper> mAccessibilityManagerWrapper;
@Inject Lazy<SysuiColorExtractor> mSysuiColorExtractor;
@Inject Lazy<TunablePaddingService> mTunablePaddingService;
- @Inject Lazy<ForegroundServiceController> mForegroundServiceController;
@Inject Lazy<UiOffloadThread> mUiOffloadThread;
@Inject Lazy<PowerUI.WarningsUI> mWarningsUI;
@Inject Lazy<LightBarController> mLightBarController;
@@ -458,8 +457,6 @@
mProviders.put(TunablePaddingService.class, mTunablePaddingService::get);
- mProviders.put(ForegroundServiceController.class, mForegroundServiceController::get);
-
mProviders.put(UiOffloadThread.class, mUiOffloadThread::get);
mProviders.put(PowerUI.WarningsUI.class, mWarningsUI::get);
diff --git a/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt b/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt
index 403c809..95e2dba 100644
--- a/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt
@@ -36,6 +36,8 @@
import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.settingslib.Utils
import com.android.systemui.biometrics.AuthController
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.log.ScreenDecorationsLogger
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.util.asIndenting
@@ -54,6 +56,7 @@
val mainExecutor: Executor,
val logger: ScreenDecorationsLogger,
val authController: AuthController,
+ val featureFlags: FeatureFlags,
) : ScreenDecorations.DisplayCutoutView(context, pos) {
private var showScanningAnim = false
private val rimPaint = Paint()
@@ -294,6 +297,15 @@
}
private fun createFaceScanningRimAnimator(): AnimatorSet {
+ val dontPulse = featureFlags.isEnabled(Flags.STOP_PULSING_FACE_SCANNING_ANIMATION)
+ if (dontPulse) {
+ return AnimatorSet().apply {
+ playSequentially(
+ cameraProtectionAnimator,
+ createRimAppearAnimator(),
+ )
+ }
+ }
return AnimatorSet().apply {
playSequentially(
cameraProtectionAnimator,
diff --git a/packages/SystemUI/src/com/android/systemui/ForegroundServiceController.java b/packages/SystemUI/src/com/android/systemui/ForegroundServiceController.java
deleted file mode 100644
index 15e8c4e..0000000
--- a/packages/SystemUI/src/com/android/systemui/ForegroundServiceController.java
+++ /dev/null
@@ -1,183 +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;
-
-import android.annotation.Nullable;
-import android.app.AppOpsManager;
-import android.os.Handler;
-import android.os.UserHandle;
-import android.service.notification.StatusBarNotification;
-import android.util.ArraySet;
-import android.util.SparseArray;
-
-import com.android.internal.messages.nano.SystemMessageProto;
-import com.android.systemui.appops.AppOpsController;
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.util.Assert;
-
-import javax.inject.Inject;
-
-/**
- * Tracks state of foreground services and notifications related to foreground services per user.
- */
-@SysUISingleton
-public class ForegroundServiceController {
- public static final int[] APP_OPS = new int[] {AppOpsManager.OP_SYSTEM_ALERT_WINDOW};
-
- private final SparseArray<ForegroundServicesUserState> mUserServices = new SparseArray<>();
- private final Object mMutex = new Object();
- private final Handler mMainHandler;
-
- @Inject
- public ForegroundServiceController(
- AppOpsController appOpsController,
- @Main Handler mainHandler) {
- mMainHandler = mainHandler;
- appOpsController.addCallback(APP_OPS, (code, uid, packageName, active) -> {
- mMainHandler.post(() -> {
- onAppOpChanged(code, uid, packageName, active);
- });
- });
- }
-
- /**
- * @return true if this user has services missing notifications and therefore needs a
- * disclosure notification for running a foreground service.
- */
- public boolean isDisclosureNeededForUser(int userId) {
- synchronized (mMutex) {
- final ForegroundServicesUserState services = mUserServices.get(userId);
- if (services == null) return false;
- return services.isDisclosureNeeded();
- }
- }
-
- /**
- * @return true if this user/pkg has a missing or custom layout notification and therefore needs
- * a disclosure notification showing the user which appsOps the app is using.
- */
- public boolean isSystemAlertWarningNeeded(int userId, String pkg) {
- synchronized (mMutex) {
- final ForegroundServicesUserState services = mUserServices.get(userId);
- if (services == null) return false;
- return services.getStandardLayoutKeys(pkg) == null;
- }
- }
-
- /**
- * Gets active app ops for this user and package
- */
- @Nullable
- public ArraySet<Integer> getAppOps(int userId, String pkg) {
- synchronized (mMutex) {
- final ForegroundServicesUserState services = mUserServices.get(userId);
- if (services == null) {
- return null;
- }
- return services.getFeatures(pkg);
- }
- }
-
- /**
- * Records active app ops and updates the app op for the pending or visible notifications
- * with the given parameters.
- * App Ops are stored in FSC in addition to NotificationEntry in case they change before we
- * have a notification to tag.
- * @param appOpCode code for appOp to add/remove
- * @param uid of user the notification is sent to
- * @param packageName package that created the notification
- * @param active whether the appOpCode is active or not
- */
- void onAppOpChanged(int appOpCode, int uid, String packageName, boolean active) {
- Assert.isMainThread();
-
- int userId = UserHandle.getUserId(uid);
- // Record active app ops
- synchronized (mMutex) {
- ForegroundServicesUserState userServices = mUserServices.get(userId);
- if (userServices == null) {
- userServices = new ForegroundServicesUserState();
- mUserServices.put(userId, userServices);
- }
- if (active) {
- userServices.addOp(packageName, appOpCode);
- } else {
- userServices.removeOp(packageName, appOpCode);
- }
- }
- }
-
- /**
- * Looks up the {@link ForegroundServicesUserState} for the given {@code userId}, then performs
- * the given {@link UserStateUpdateCallback} on it. If no state exists for the user ID, creates
- * a new one if {@code createIfNotFound} is true, then performs the update on the new state.
- * If {@code createIfNotFound} is false, no update is performed.
- *
- * @return false if no user state was found and none was created; true otherwise.
- */
- boolean updateUserState(int userId,
- UserStateUpdateCallback updateCallback,
- boolean createIfNotFound) {
- synchronized (mMutex) {
- ForegroundServicesUserState userState = mUserServices.get(userId);
- if (userState == null) {
- if (createIfNotFound) {
- userState = new ForegroundServicesUserState();
- mUserServices.put(userId, userState);
- } else {
- return false;
- }
- }
- return updateCallback.updateUserState(userState);
- }
- }
-
- /**
- * @return true if {@code sbn} is the system-provided disclosure notification containing the
- * list of running foreground services.
- */
- public boolean isDisclosureNotification(StatusBarNotification sbn) {
- return sbn.getId() == SystemMessageProto.SystemMessage.NOTE_FOREGROUND_SERVICES
- && sbn.getTag() == null
- && sbn.getPackageName().equals("android");
- }
-
- /**
- * @return true if sbn is one of the window manager "drawing over other apps" notifications
- */
- public boolean isSystemAlertNotification(StatusBarNotification sbn) {
- return sbn.getPackageName().equals("android")
- && sbn.getTag() != null
- && sbn.getTag().contains("AlertWindowNotification");
- }
-
- /**
- * Callback provided to {@link #updateUserState(int, UserStateUpdateCallback, boolean)}
- * to perform the update.
- */
- interface UserStateUpdateCallback {
- /**
- * Perform update operations on the provided {@code userState}.
- *
- * @return true if the update succeeded.
- */
- boolean updateUserState(ForegroundServicesUserState userState);
-
- /** Called if the state was not found and was not created. */
- default void userStateNotFound(int userId) {
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java b/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java
deleted file mode 100644
index a1a3b72..0000000
--- a/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java
+++ /dev/null
@@ -1,150 +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;
-
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.content.Context;
-import android.os.Bundle;
-import android.service.notification.StatusBarNotification;
-import android.util.Log;
-
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.statusbar.notification.collection.NotifPipeline;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
-
-import javax.inject.Inject;
-
-/** Updates foreground service notification state in response to notification data events. */
-@SysUISingleton
-public class ForegroundServiceNotificationListener {
-
- private static final String TAG = "FgServiceController";
- private static final boolean DBG = false;
-
- private final Context mContext;
- private final ForegroundServiceController mForegroundServiceController;
- private final NotifPipeline mNotifPipeline;
-
- @Inject
- public ForegroundServiceNotificationListener(Context context,
- ForegroundServiceController foregroundServiceController,
- NotifPipeline notifPipeline) {
- mContext = context;
- mForegroundServiceController = foregroundServiceController;
- mNotifPipeline = notifPipeline;
- }
-
- /** Initializes this listener by connecting it to the notification pipeline. */
- public void init() {
- mNotifPipeline.addCollectionListener(new NotifCollectionListener() {
- @Override
- public void onEntryAdded(NotificationEntry entry) {
- addNotification(entry, entry.getImportance());
- }
-
- @Override
- public void onEntryUpdated(NotificationEntry entry) {
- updateNotification(entry, entry.getImportance());
- }
-
- @Override
- public void onEntryRemoved(NotificationEntry entry, int reason) {
- removeNotification(entry.getSbn());
- }
- });
- }
-
- /**
- * @param entry notification that was just posted
- */
- private void addNotification(NotificationEntry entry, int importance) {
- updateNotification(entry, importance);
- }
-
- /**
- * @param sbn notification that was just removed
- */
- private void removeNotification(StatusBarNotification sbn) {
- mForegroundServiceController.updateUserState(
- sbn.getUserId(),
- new ForegroundServiceController.UserStateUpdateCallback() {
- @Override
- public boolean updateUserState(ForegroundServicesUserState userState) {
- if (mForegroundServiceController.isDisclosureNotification(sbn)) {
- // if you remove the dungeon entirely, we take that to mean there are
- // no running services
- userState.setRunningServices(null, 0);
- return true;
- } else {
- // this is safe to call on any notification, not just
- // FLAG_FOREGROUND_SERVICE
- return userState.removeNotification(sbn.getPackageName(), sbn.getKey());
- }
- }
-
- @Override
- public void userStateNotFound(int userId) {
- if (DBG) {
- Log.w(TAG, String.format(
- "user %d with no known notifications got removeNotification "
- + "for %s",
- sbn.getUserId(), sbn));
- }
- }
- },
- false /* don't create */);
- }
-
- /**
- * @param entry notification that was just changed in some way
- */
- private void updateNotification(NotificationEntry entry, int newImportance) {
- final StatusBarNotification sbn = entry.getSbn();
- mForegroundServiceController.updateUserState(
- sbn.getUserId(),
- userState -> {
- if (mForegroundServiceController.isDisclosureNotification(sbn)) {
- final Bundle extras = sbn.getNotification().extras;
- if (extras != null) {
- final String[] svcs = extras.getStringArray(
- Notification.EXTRA_FOREGROUND_APPS);
- userState.setRunningServices(svcs, sbn.getNotification().when);
- }
- } else {
- userState.removeNotification(sbn.getPackageName(), sbn.getKey());
- if (0 != (sbn.getNotification().flags
- & Notification.FLAG_FOREGROUND_SERVICE)) {
- if (newImportance > NotificationManager.IMPORTANCE_MIN) {
- userState.addImportantNotification(sbn.getPackageName(),
- sbn.getKey());
- }
- }
- final Notification.Builder builder =
- Notification.Builder.recoverBuilder(
- mContext, sbn.getNotification());
- if (builder.usesStandardHeader()) {
- userState.addStandardLayoutNotification(
- sbn.getPackageName(), sbn.getKey());
- }
- }
- return true;
- },
- true /* create if not found */);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
index 602f817..28d59c2 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
@@ -1446,6 +1446,12 @@
private class MirrorWindowA11yDelegate extends View.AccessibilityDelegate {
private CharSequence getClickAccessibilityActionLabel() {
+ if (mEditSizeEnable) {
+ // Perform click action to exit edit mode
+ return mContext.getResources().getString(
+ R.string.magnification_exit_edit_mode_click_label);
+ }
+
return mSettingsPanelVisibility
? mContext.getResources().getString(
R.string.magnification_close_settings_click_label)
@@ -1488,8 +1494,14 @@
private boolean performA11yAction(int action) {
if (action == AccessibilityAction.ACTION_CLICK.getId()) {
- // Simulate tapping the drag view so it opens the Settings.
- handleSingleTap(mDragView);
+ if (mEditSizeEnable) {
+ // When edit mode is enabled, click the magnifier to exit edit mode.
+ setEditMagnifierSizeMode(false);
+ } else {
+ // Simulate tapping the drag view so it opens the Settings.
+ handleSingleTap(mDragView);
+ }
+
} else if (action == R.id.accessibility_action_zoom_in) {
performScale(mScale + A11Y_CHANGE_SCALE_DIFFERENCE);
} else if (action == R.id.accessibility_action_zoom_out) {
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 e121790..ecd7bae 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
@@ -102,7 +102,7 @@
.stateIn(
scope = applicationScope,
started = SharingStarted.Eagerly,
- initialValue = true,
+ initialValue = false,
)
/**
@@ -114,14 +114,18 @@
* - `true` doesn't mean the lockscreen is invisible (since this state changes before the
* transition occurs).
*/
- private val isLockscreenDismissed =
+ val isLockscreenDismissed: StateFlow<Boolean> =
sceneInteractor.desiredScene
.map { it.key }
.filter { currentScene ->
currentScene == SceneKey.Gone || currentScene == SceneKey.Lockscreen
}
.map { it == SceneKey.Gone }
- .distinctUntilChanged()
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = false,
+ )
/**
* Whether it's currently possible to swipe up to dismiss the lockscreen without requiring
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index 58adfa1..58c8000 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -82,6 +82,7 @@
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.util.concurrency.DelayableExecutor;
import java.io.PrintWriter;
@@ -288,12 +289,13 @@
@NonNull Provider<PromptSelectorInteractor> promptSelectorInteractor,
@NonNull PromptViewModel promptViewModel,
@NonNull Provider<CredentialViewModel> credentialViewModelProvider,
- @NonNull @Background DelayableExecutor bgExecutor) {
+ @NonNull @Background DelayableExecutor bgExecutor,
+ @NonNull VibratorHelper vibratorHelper) {
this(config, featureFlags, applicationCoroutineScope, fpProps, faceProps,
wakefulnessLifecycle, panelInteractionDetector, userManager, lockPatternUtils,
jankMonitor, authBiometricFingerprintViewModelProvider, promptSelectorInteractor,
promptCredentialInteractor, promptViewModel, credentialViewModelProvider,
- new Handler(Looper.getMainLooper()), bgExecutor);
+ new Handler(Looper.getMainLooper()), bgExecutor, vibratorHelper);
}
@VisibleForTesting
@@ -314,7 +316,8 @@
@NonNull PromptViewModel promptViewModel,
@NonNull Provider<CredentialViewModel> credentialViewModelProvider,
@NonNull Handler mainHandler,
- @NonNull @Background DelayableExecutor bgExecutor) {
+ @NonNull @Background DelayableExecutor bgExecutor,
+ @NonNull VibratorHelper vibratorHelper) {
super(config.mContext);
mConfig = config;
@@ -364,7 +367,8 @@
if (featureFlags.isEnabled(Flags.BIOMETRIC_BP_STRONG)) {
showPrompt(config, layoutInflater, promptViewModel,
Utils.findFirstSensorProperties(fpProps, mConfig.mSensorIds),
- Utils.findFirstSensorProperties(faceProps, mConfig.mSensorIds));
+ Utils.findFirstSensorProperties(faceProps, mConfig.mSensorIds),
+ vibratorHelper, featureFlags);
} else {
showLegacyPrompt(config, layoutInflater, fpProps, faceProps);
}
@@ -388,7 +392,10 @@
private void showPrompt(@NonNull Config config, @NonNull LayoutInflater layoutInflater,
@NonNull PromptViewModel viewModel,
@Nullable FingerprintSensorPropertiesInternal fpProps,
- @Nullable FaceSensorPropertiesInternal faceProps) {
+ @Nullable FaceSensorPropertiesInternal faceProps,
+ @NonNull VibratorHelper vibratorHelper,
+ @NonNull FeatureFlags featureFlags
+ ) {
if (Utils.isBiometricAllowed(config.mPromptInfo)) {
mPromptSelectorInteractorProvider.get().useBiometricsForAuthentication(
config.mPromptInfo,
@@ -401,7 +408,8 @@
mBiometricView = BiometricViewBinder.bind(view, viewModel, mPanelController,
// TODO(b/201510778): This uses the wrong timeout in some cases
getJankListener(view, TRANSIT, AuthDialog.ANIMATE_MEDIUM_TO_LARGE_DURATION_MS),
- mBackgroundView, mBiometricCallback, mApplicationCoroutineScope);
+ mBackgroundView, mBiometricCallback, mApplicationCoroutineScope,
+ vibratorHelper, featureFlags);
// TODO(b/251476085): migrate these dependencies
if (fpProps != null && fpProps.isAnyUdfpsType()) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index 3df7ca5..7b288a8 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -85,9 +85,12 @@
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.keyguard.data.repository.BiometricType;
import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.concurrency.Execution;
+import kotlin.Unit;
+
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
@@ -101,7 +104,6 @@
import javax.inject.Inject;
import javax.inject.Provider;
-import kotlin.Unit;
import kotlinx.coroutines.CoroutineScope;
/**
@@ -183,6 +185,7 @@
@NonNull private final UdfpsUtils mUdfpsUtils;
private final @Background DelayableExecutor mBackgroundExecutor;
private final DisplayInfo mCachedDisplayInfo = new DisplayInfo();
+ @NonNull private final VibratorHelper mVibratorHelper;
@VisibleForTesting
final TaskStackListener mTaskStackListener = new TaskStackListener() {
@@ -771,7 +774,8 @@
@NonNull InteractionJankMonitor jankMonitor,
@Main Handler handler,
@Background DelayableExecutor bgExecutor,
- @NonNull UdfpsUtils udfpsUtils) {
+ @NonNull UdfpsUtils udfpsUtils,
+ @NonNull VibratorHelper vibratorHelper) {
mContext = context;
mFeatureFlags = featureFlags;
mExecution = execution;
@@ -794,6 +798,7 @@
mFaceEnrolledForUser = new SparseBooleanArray();
mUdfpsUtils = udfpsUtils;
mApplicationCoroutineScope = applicationCoroutineScope;
+ mVibratorHelper = vibratorHelper;
mLogContextInteractor = logContextInteractor;
mAuthBiometricFingerprintViewModelProvider = authBiometricFingerprintViewModelProvider;
@@ -1044,7 +1049,7 @@
final int userId = mCurrentDialogArgs.argi1;
if (isFaceAuthEnrolled(userId) && isFingerprintEnrolled(userId)) {
messageRes = modality == TYPE_FACE
- ? R.string.biometric_face_not_recognized
+ ? R.string.fingerprint_dialog_use_fingerprint_instead
: R.string.fingerprint_error_not_match;
} else {
messageRes = R.string.biometric_not_recognized;
@@ -1341,7 +1346,7 @@
wakefulnessLifecycle, panelInteractionDetector, userManager, lockPatternUtils,
mInteractionJankMonitor, mAuthBiometricFingerprintViewModelProvider,
mPromptCredentialInteractor, mPromptSelectorInteractor, viewModel,
- mCredentialViewModelProvider, bgExecutor);
+ mCredentialViewModelProvider, bgExecutor, mVibratorHelper);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
index 946ddba..ea9fe5f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
@@ -230,7 +230,7 @@
lightRevealScrim.revealAmount = animator.animatedValue as Float
}
addListener(object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator?) {
+ override fun onAnimationEnd(animation: Animator) {
// Reset light reveal scrim to the default, so the CentralSurfaces
// can handle any subsequent light reveal changes
// (ie: from dozing changes)
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
index 5ede16d..4c2dc41 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
@@ -147,12 +147,12 @@
retractDwellAnimator = AnimatorSet().apply {
playTogether(retractDwellRippleAnimator, retractAlphaAnimator)
addListener(object : AnimatorListenerAdapter() {
- override fun onAnimationStart(animation: Animator?) {
+ override fun onAnimationStart(animation: Animator) {
dwellPulseOutAnimator?.cancel()
drawDwell = true
}
- override fun onAnimationEnd(animation: Animator?) {
+ override fun onAnimationEnd(animation: Animator) {
drawDwell = false
resetDwellAlpha()
}
@@ -182,13 +182,13 @@
invalidate()
}
addListener(object : AnimatorListenerAdapter() {
- override fun onAnimationStart(animation: Animator?) {
+ override fun onAnimationStart(animation: Animator) {
retractDwellAnimator?.cancel()
dwellPulseOutAnimator?.cancel()
drawDwell = true
}
- override fun onAnimationEnd(animation: Animator?) {
+ override fun onAnimationEnd(animation: Animator) {
drawDwell = false
resetDwellAlpha()
}
@@ -239,14 +239,14 @@
expandDwellRippleAnimator
)
addListener(object : AnimatorListenerAdapter() {
- override fun onAnimationStart(animation: Animator?) {
+ override fun onAnimationStart(animation: Animator) {
retractDwellAnimator?.cancel()
fadeDwellAnimator?.cancel()
visibility = VISIBLE
drawDwell = true
}
- override fun onAnimationEnd(animation: Animator?) {
+ override fun onAnimationEnd(animation: Animator) {
drawDwell = false
}
})
@@ -273,12 +273,12 @@
unlockedRippleAnimator = rippleAnimator.apply {
addListener(object : AnimatorListenerAdapter() {
- override fun onAnimationStart(animation: Animator?) {
+ override fun onAnimationStart(animation: Animator) {
drawRipple = true
visibility = VISIBLE
}
- override fun onAnimationEnd(animation: Animator?) {
+ override fun onAnimationEnd(animation: Animator) {
onAnimationEnd?.run()
drawRipple = false
visibility = GONE
@@ -327,7 +327,7 @@
}
}
- override fun onDraw(canvas: Canvas?) {
+ override fun onDraw(canvas: Canvas) {
// To reduce overdraw, we mask the effect to a circle whose radius is big enough to cover
// the active effect area. Values here should be kept in sync with the
// animation implementation in the ripple shader. (Twice bigger)
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/FaceAuthAccessibilityDelegate.kt b/packages/SystemUI/src/com/android/systemui/biometrics/FaceAuthAccessibilityDelegate.kt
index b9fa240..a24a47b 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/FaceAuthAccessibilityDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/FaceAuthAccessibilityDelegate.kt
@@ -40,7 +40,7 @@
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
private val faceAuthInteractor: KeyguardFaceAuthInteractor,
) : View.AccessibilityDelegate() {
- override fun onInitializeAccessibilityNodeInfo(host: View?, info: AccessibilityNodeInfo) {
+ override fun onInitializeAccessibilityNodeInfo(host: View, info: AccessibilityNodeInfo) {
super.onInitializeAccessibilityNodeInfo(host, info)
if (keyguardUpdateMonitor.shouldListenForFace()) {
val clickActionToRetryFace =
@@ -52,7 +52,7 @@
}
}
- override fun performAccessibilityAction(host: View?, action: Int, args: Bundle?): Boolean {
+ override fun performAccessibilityAction(host: View, action: Int, args: Bundle?): Boolean {
return if (action == AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK.id) {
keyguardUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.ACCESSIBILITY_ACTION)
faceAuthInteractor.onAccessibilityAction()
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
index 7f696fd..eb6864d 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
@@ -119,7 +119,7 @@
private var overlayView: View? = null
set(value) {
field?.let { oldView ->
- val lottie = oldView.findViewById(R.id.sidefps_animation) as LottieAnimationView
+ val lottie = oldView.requireViewById(R.id.sidefps_animation) as LottieAnimationView
lottie.pauseAnimation()
windowManager.removeView(oldView)
orientationListener.disable()
@@ -274,7 +274,7 @@
}
overlayOffsets = offsets
- val lottie = view.findViewById(R.id.sidefps_animation) as LottieAnimationView
+ val lottie = view.requireViewById(R.id.sidefps_animation) as LottieAnimationView
view.rotation =
display.asSideFpsAnimationRotation(
offsets.isYAligned(),
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 39a45f7..b23e085 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -646,8 +646,9 @@
shouldPilfer = true;
}
- // Pilfer only once per gesture
- if (shouldPilfer && !mPointerPilfered) {
+ // Pilfer only once per gesture, don't pilfer for BP
+ if (shouldPilfer && !mPointerPilfered
+ && getBiometricSessionType() != SESSION_BIOMETRIC_PROMPT) {
mInputManager.pilferPointers(
mOverlay.getOverlayView().getViewRootImpl().getInputToken());
mPointerPilfered = true;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmEmptyView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmEmptyView.kt
index 8352d0a..5dafa61 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmEmptyView.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmEmptyView.kt
@@ -38,7 +38,8 @@
override fun getDrawable(): UdfpsDrawable = fingerprintDrawable
fun updateAccessibilityViewLocation(sensorBounds: Rect) {
- val fingerprintAccessibilityView: View = findViewById(R.id.udfps_enroll_accessibility_view)
+ val fingerprintAccessibilityView: View =
+ requireViewById(R.id.udfps_enroll_accessibility_view)
val params: ViewGroup.LayoutParams = fingerprintAccessibilityView.layoutParams
params.width = sensorBounds.width()
params.height = sensorBounds.height()
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardAccessibilityDelegate.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardAccessibilityDelegate.kt
index fb7b56e..8497879 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardAccessibilityDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardAccessibilityDelegate.kt
@@ -33,7 +33,7 @@
@Main private val resources: Resources,
private val keyguardViewManager: StatusBarKeyguardViewManager,
) : View.AccessibilityDelegate() {
- override fun onInitializeAccessibilityNodeInfo(host: View?, info: AccessibilityNodeInfo) {
+ override fun onInitializeAccessibilityNodeInfo(host: View, info: AccessibilityNodeInfo) {
super.onInitializeAccessibilityNodeInfo(host, info)
val clickAction =
AccessibilityNodeInfo.AccessibilityAction(
@@ -43,7 +43,7 @@
info.addAction(clickAction)
}
- override fun performAccessibilityAction(host: View?, action: Int, args: Bundle?): Boolean {
+ override fun performAccessibilityAction(host: View, action: Int, args: Bundle?): Boolean {
// when an a11y service is enabled, double tapping on the fingerprint sensor should
// show the primary bouncer
return if (action == AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK.id) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
index db30a55..e3fd3ce1 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
@@ -306,8 +306,9 @@
activityLaunchAnimator.addListener(activityLaunchAnimatorListener)
view.mUseExpandedOverlay = useExpandedOverlay
view.startIconAsyncInflate {
- (view.findViewById(R.id.udfps_animation_view_internal) as View).accessibilityDelegate =
- udfpsKeyguardAccessibilityDelegate
+ val animationViewInternal: View =
+ view.requireViewById(R.id.udfps_animation_view_internal)
+ animationViewInternal.accessibilityDelegate = udfpsKeyguardAccessibilityDelegate
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/Utils.kt b/packages/SystemUI/src/com/android/systemui/biometrics/Utils.kt
index b538085..1ca57e7 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/Utils.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/Utils.kt
@@ -60,6 +60,13 @@
return dp * (density / DisplayMetrics.DENSITY_DEFAULT)
}
+ /**
+ * Note: Talkback 14.0 has new rate-limitation design to reduce frequency
+ * of TYPE_WINDOW_CONTENT_CHANGED events to once every 30 seconds.
+ * (context: b/281765653#comment18)
+ * Using {@link View#announceForAccessibility} instead as workaround when sending events
+ * exceeding this frequency is required.
+ */
@JvmStatic
fun notifyAccessibilityContentChanged(am: AccessibilityManager, view: ViewGroup) {
if (!am.isEnabled) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractor.kt
index 1f1a1b5..2a02667 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractor.kt
@@ -89,7 +89,7 @@
)
val hat = gkResponse.gatekeeperHAT
lockPatternUtils.removeGatekeeperPasswordHandle(pwHandle)
- emit(CredentialStatus.Success.Verified(hat))
+ emit(CredentialStatus.Success.Verified(checkNotNull(hat)))
} else if (response.timeout > 0) {
// if requests are being throttled, update the error message every
// second until the temporary lock has expired
@@ -226,8 +226,7 @@
is BiometricPromptRequest.Credential.Password ->
DevicePolicyResources.Strings.SystemUi.BIOMETRIC_DIALOG_WORK_PASSWORD_LAST_ATTEMPT
}
- return devicePolicyManager.resources.getString(id) {
- // use fallback a string if not found
+ val getFallbackString = {
val defaultId =
when (request) {
is BiometricPromptRequest.Credential.Pin ->
@@ -239,6 +238,8 @@
}
getString(defaultId)
}
+
+ return devicePolicyManager.resources?.getString(id, getFallbackString) ?: getFallbackString()
}
private fun Context.getLastAttemptBeforeWipeUserMessage(
@@ -266,8 +267,8 @@
DevicePolicyResources.Strings.SystemUi.BIOMETRIC_DIALOG_WORK_LOCK_FAILED_ATTEMPTS
else -> DevicePolicyResources.UNDEFINED
}
- return devicePolicyManager.resources.getString(id) {
- // use fallback a string if not found
+
+ val getFallbackString = {
val defaultId =
when (userType) {
UserType.PRIMARY ->
@@ -279,4 +280,6 @@
}
getString(defaultId)
}
+
+ return devicePolicyManager.resources?.getString(id, getFallbackString) ?: getFallbackString()
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialPasswordView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialPasswordView.kt
index a3f34ce..b940ec8 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialPasswordView.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialPasswordView.kt
@@ -122,7 +122,7 @@
titleView.ellipsize = TextUtils.TruncateAt.MARQUEE
titleView.marqueeRepeatLimit = -1
// select to enable marquee unless a screen reader is enabled
- titleView.isSelected = accessibilityManager.shouldMarquee()
+ titleView.isSelected = accessibilityManager?.shouldMarquee() ?: false
} else {
titleView.isSingleLine = false
titleView.ellipsize = null
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 7b78761..d054751 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
@@ -17,6 +17,7 @@
package com.android.systemui.biometrics.ui.binder
import android.animation.Animator
+import android.annotation.SuppressLint
import android.content.Context
import android.hardware.biometrics.BiometricAuthenticator
import android.hardware.biometrics.BiometricConstants
@@ -25,6 +26,8 @@
import android.os.Bundle
import android.text.method.ScrollingMovementMethod
import android.util.Log
+import android.view.HapticFeedbackConstants
+import android.view.MotionEvent
import android.view.View
import android.view.View.IMPORTANT_FOR_ACCESSIBILITY_NO
import android.view.accessibility.AccessibilityManager
@@ -45,7 +48,6 @@
import com.android.systemui.biometrics.AuthBiometricViewAdapter
import com.android.systemui.biometrics.AuthIconController
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.shared.model.BiometricModality
import com.android.systemui.biometrics.shared.model.PromptKind
@@ -55,9 +57,13 @@
import com.android.systemui.biometrics.ui.viewmodel.PromptMessage
import com.android.systemui.biometrics.ui.viewmodel.PromptSize
import com.android.systemui.biometrics.ui.viewmodel.PromptViewModel
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION
import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.statusbar.VibratorHelper
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
@@ -69,6 +75,7 @@
object BiometricViewBinder {
/** Binds a [BiometricPromptLayout] to a [PromptViewModel]. */
+ @SuppressLint("ClickableViewAccessibility")
@JvmStatic
fun bind(
view: BiometricPromptLayout,
@@ -78,20 +85,19 @@
backgroundView: View,
legacyCallback: Callback,
applicationScope: CoroutineScope,
+ vibratorHelper: VibratorHelper,
+ featureFlags: FeatureFlags,
): AuthBiometricViewAdapter {
val accessibilityManager = view.context.getSystemService(AccessibilityManager::class.java)!!
- fun notifyAccessibilityChanged() {
- Utils.notifyAccessibilityContentChanged(accessibilityManager, view)
- }
val textColorError =
view.resources.getColor(R.color.biometric_dialog_error, view.context.theme)
val textColorHint =
view.resources.getColor(R.color.biometric_dialog_gray, view.context.theme)
- val titleView = view.findViewById<TextView>(R.id.title)
- val subtitleView = view.findViewById<TextView>(R.id.subtitle)
- val descriptionView = view.findViewById<TextView>(R.id.description)
+ val titleView = view.requireViewById<TextView>(R.id.title)
+ val subtitleView = view.requireViewById<TextView>(R.id.subtitle)
+ val descriptionView = view.requireViewById<TextView>(R.id.description)
// set selected to enable marquee unless a screen reader is enabled
titleView.isSelected =
@@ -100,18 +106,18 @@
!accessibilityManager.isEnabled || !accessibilityManager.isTouchExplorationEnabled
descriptionView.movementMethod = ScrollingMovementMethod()
- val iconViewOverlay = view.findViewById<LottieAnimationView>(R.id.biometric_icon_overlay)
- val iconView = view.findViewById<LottieAnimationView>(R.id.biometric_icon)
- val indicatorMessageView = view.findViewById<TextView>(R.id.indicator)
+ val iconViewOverlay = view.requireViewById<LottieAnimationView>(R.id.biometric_icon_overlay)
+ val iconView = view.requireViewById<LottieAnimationView>(R.id.biometric_icon)
+ val indicatorMessageView = view.requireViewById<TextView>(R.id.indicator)
// Negative-side (left) buttons
- val negativeButton = view.findViewById<Button>(R.id.button_negative)
- val cancelButton = view.findViewById<Button>(R.id.button_cancel)
- val credentialFallbackButton = view.findViewById<Button>(R.id.button_use_credential)
+ val negativeButton = view.requireViewById<Button>(R.id.button_negative)
+ val cancelButton = view.requireViewById<Button>(R.id.button_cancel)
+ val credentialFallbackButton = view.requireViewById<Button>(R.id.button_use_credential)
// Positive-side (right) buttons
- val confirmationButton = view.findViewById<Button>(R.id.button_confirm)
- val retryButton = view.findViewById<Button>(R.id.button_try_again)
+ val confirmationButton = view.requireViewById<Button>(R.id.button_confirm)
+ val retryButton = view.requireViewById<Button>(R.id.button_try_again)
// TODO(b/251476085): temporary workaround for the unsafe callbacks & legacy controllers
val adapter =
@@ -297,21 +303,19 @@
// reuse the icon as a confirm button
launch {
- viewModel.isConfirmButtonVisible
+ viewModel.isIconConfirmButton
.map { isPending ->
when {
isPending && iconController.actsAsConfirmButton ->
- View.OnClickListener { viewModel.confirmAuthenticated() }
+ View.OnTouchListener { _: View, event: MotionEvent ->
+ viewModel.onOverlayTouch(event)
+ }
else -> null
}
}
- .collect { onClick ->
- iconViewOverlay.setOnClickListener(onClick)
- iconView.setOnClickListener(onClick)
- if (onClick == null) {
- iconViewOverlay.isClickable = false
- iconView.isClickable = false
- }
+ .collect { onTouch ->
+ iconViewOverlay.setOnTouchListener(onTouch)
+ iconView.setOnTouchListener(onTouch)
}
}
@@ -326,30 +330,30 @@
}
}
- // not sure why this is here, but the legacy code did it probably needed?
- launch {
- viewModel.isAuthenticating.collect { isAuthenticating ->
- if (isAuthenticating) {
- notifyAccessibilityChanged()
- }
- }
- }
-
// dismiss prompt when authenticated and confirmed
launch {
viewModel.isAuthenticated.collect { authState ->
// Disable background view for cancelling authentication once authenticated,
// and remove from talkback
if (authState.isAuthenticated) {
+ // Prevents Talkback from speaking subtitle after already authenticated
+ subtitleView.importantForAccessibility = IMPORTANT_FOR_ACCESSIBILITY_NO
backgroundView.setOnClickListener(null)
backgroundView.importantForAccessibility =
IMPORTANT_FOR_ACCESSIBILITY_NO
+
+ // Allow icon to be used as confirmation button with a11y enabled
+ if (accessibilityManager.isTouchExplorationEnabled) {
+ iconViewOverlay.setOnClickListener {
+ viewModel.confirmAuthenticated()
+ }
+ iconView.setOnClickListener { viewModel.confirmAuthenticated() }
+ }
}
if (authState.isAuthenticatedAndConfirmed) {
view.announceForAccessibility(
view.resources.getString(R.string.biometric_dialog_authenticated)
)
- notifyAccessibilityChanged()
launch {
delay(authState.delay)
@@ -381,7 +385,30 @@
!accessibilityManager.isEnabled ||
!accessibilityManager.isTouchExplorationEnabled
- notifyAccessibilityChanged()
+ /**
+ * Note: Talkback 14.0 has new rate-limitation design to reduce frequency of
+ * TYPE_WINDOW_CONTENT_CHANGED events to once every 30 seconds. (context:
+ * b/281765653#comment18) Using {@link View#announceForAccessibility}
+ * instead as workaround since sending events exceeding this frequency is
+ * required.
+ */
+ indicatorMessageView?.text?.let {
+ if (it.isNotBlank()) {
+ view.announceForAccessibility(it)
+ }
+ }
+ }
+ }
+
+ // Play haptics
+ if (featureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) {
+ launch {
+ viewModel.hapticsToPlay.collect { hapticFeedbackConstant ->
+ if (hapticFeedbackConstant != HapticFeedbackConstants.NO_HAPTICS) {
+ vibratorHelper.performHapticFeedback(view, hapticFeedbackConstant)
+ viewModel.clearHaptics()
+ }
+ }
}
}
}
@@ -477,7 +504,7 @@
modalities.hasFaceAndFingerprint &&
(viewModel.fingerprintStartMode.first() != FingerprintStartMode.Pending) &&
(authenticatedModality == BiometricModality.Face) ->
- R.string.biometric_dialog_tap_confirm_with_face
+ R.string.biometric_dialog_tap_confirm_with_face_alt_1
else -> null
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
index 1a286cf..370b36b 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
@@ -72,7 +72,7 @@
}
}
- val iconHolderView = view.findViewById<View>(R.id.biometric_icon_frame)
+ val iconHolderView = view.requireViewById<View>(R.id.biometric_icon_frame)
val iconPadding = view.resources.getDimension(R.dimen.biometric_dialog_icon_padding)
val fullSizeYOffset =
view.resources.getDimension(R.dimen.biometric_dialog_medium_to_large_translation_offset)
@@ -205,7 +205,7 @@
}
private fun View.isLandscape(): Boolean {
- val r = context.display.rotation
+ val r = context.display?.rotation
return r == Surface.ROTATION_90 || r == Surface.ROTATION_270
}
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 dca19c5..89561a5 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
@@ -17,11 +17,15 @@
import android.hardware.biometrics.BiometricPrompt
import android.util.Log
+import android.view.HapticFeedbackConstants
+import android.view.MotionEvent
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.shared.model.BiometricModality
import com.android.systemui.biometrics.shared.model.PromptKind
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION
import com.android.systemui.statusbar.VibratorHelper
import javax.inject.Inject
import kotlinx.coroutines.Job
@@ -43,6 +47,7 @@
constructor(
private val interactor: PromptSelectorInteractor,
private val vibrator: VibratorHelper,
+ private val featureFlags: FeatureFlags,
) {
/** The set of modalities available for this prompt */
val modalities: Flow<BiometricModalities> =
@@ -63,11 +68,18 @@
/** If the user has successfully authenticated and confirmed (when explicitly required). */
val isAuthenticated: Flow<PromptAuthState> = _isAuthenticated.asStateFlow()
+ private val _isOverlayTouched: MutableStateFlow<Boolean> = MutableStateFlow(false)
+
/**
* If the API caller or the user's personal preferences require explicit confirmation after
* successful authentication.
*/
- val isConfirmationRequired: Flow<Boolean> = interactor.isConfirmationRequired
+ val isConfirmationRequired: Flow<Boolean> =
+ combine(_isOverlayTouched, interactor.isConfirmationRequired) {
+ isOverlayTouched,
+ isConfirmationRequired ->
+ !isOverlayTouched && isConfirmationRequired
+ }
/** The kind of credential the user has. */
val credentialKind: Flow<PromptKind> = interactor.credentialKind
@@ -90,6 +102,11 @@
private val _forceLargeSize = MutableStateFlow(false)
private val _forceMediumSize = MutableStateFlow(false)
+ private val _hapticsToPlay = MutableStateFlow(HapticFeedbackConstants.NO_HAPTICS)
+
+ /** Event fired to the view indicating a [HapticFeedbackConstants] to be played */
+ val hapticsToPlay = _hapticsToPlay.asStateFlow()
+
/** The size of the prompt. */
val size: Flow<PromptSize> =
combine(
@@ -141,6 +158,12 @@
}
.distinctUntilChanged()
+ /** If the icon can be used as a confirmation button. */
+ val isIconConfirmButton: Flow<Boolean> =
+ combine(size, interactor.isConfirmationRequired) { size, isConfirmationRequired ->
+ size.isNotSmall && isConfirmationRequired
+ }
+
/** If the negative button should be shown. */
val isNegativeButtonVisible: Flow<Boolean> =
combine(
@@ -289,8 +312,10 @@
if (message.isNotBlank()) PromptMessage.Help(message) else PromptMessage.Empty
_forceMediumSize.value = true
_legacyState.value =
- if (alreadyAuthenticated) {
+ if (alreadyAuthenticated && isConfirmationRequired.first()) {
AuthBiometricView.STATE_PENDING_CONFIRMATION
+ } else if (alreadyAuthenticated && !isConfirmationRequired.first()) {
+ AuthBiometricView.STATE_AUTHENTICATED
} else {
AuthBiometricView.STATE_HELP
}
@@ -388,18 +413,10 @@
}
private suspend fun needsExplicitConfirmation(modality: BiometricModality): Boolean {
- val availableModalities = modalities.first()
val confirmationRequired = isConfirmationRequired.first()
- if (availableModalities.hasFaceAndFingerprint) {
- // coex only needs confirmation when face is successful, unless it happens on the
- // first attempt (i.e. without failure) before fingerprint scanning starts
- val fingerprintStarted = fingerprintStartMode.first() != FingerprintStartMode.Pending
- if (modality == BiometricModality.Face) {
- return fingerprintStarted || confirmationRequired
- }
- }
- if (availableModalities.hasFaceOnly) {
+ // Only worry about confirmationRequired if face was used to unlock
+ if (modality == BiometricModality.Face) {
return confirmationRequired
}
// fingerprint only never requires confirmation
@@ -430,6 +447,26 @@
}
/**
+ * Touch event occurred on the overlay
+ *
+ * Tracks whether a finger is currently down to set [_isOverlayTouched] to be used as user
+ * confirmation
+ */
+ fun onOverlayTouch(event: MotionEvent): Boolean {
+ if (event.actionMasked == MotionEvent.ACTION_DOWN) {
+ _isOverlayTouched.value = true
+
+ if (_isAuthenticated.value.needsUserConfirmation) {
+ confirmAuthenticated()
+ }
+ return true
+ } else if (event.actionMasked == MotionEvent.ACTION_UP) {
+ _isOverlayTouched.value = false
+ }
+ return false
+ }
+
+ /**
* Switch to the credential view.
*
* TODO(b/251476085): this should be decoupled from the shared panel controller
@@ -438,11 +475,26 @@
_forceLargeSize.value = true
}
- private fun VibratorHelper.success(modality: BiometricModality) =
- vibrateAuthSuccess("$TAG, modality = $modality BP::success")
+ private fun VibratorHelper.success(modality: BiometricModality) {
+ if (featureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) {
+ _hapticsToPlay.value = HapticFeedbackConstants.CONFIRM
+ } else {
+ vibrateAuthSuccess("$TAG, modality = $modality BP::success")
+ }
+ }
- private fun VibratorHelper.error(modality: BiometricModality = BiometricModality.None) =
- vibrateAuthError("$TAG, modality = $modality BP::error")
+ private fun VibratorHelper.error(modality: BiometricModality = BiometricModality.None) {
+ if (featureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) {
+ _hapticsToPlay.value = HapticFeedbackConstants.REJECT
+ } else {
+ vibrateAuthError("$TAG, modality = $modality BP::error")
+ }
+ }
+
+ /** Clears the [hapticsToPlay] variable by setting it to the NO_HAPTICS default. */
+ fun clearHaptics() {
+ _hapticsToPlay.value = HapticFeedbackConstants.NO_HAPTICS
+ }
companion object {
private const val TAG = "PromptViewModel"
diff --git a/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt b/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt
index 5ca36ab..ddb09749 100644
--- a/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt
@@ -156,9 +156,9 @@
}
windowLayoutParams.packageName = context.opPackageName
rippleView.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener {
- override fun onViewDetachedFromWindow(view: View?) {}
+ override fun onViewDetachedFromWindow(view: View) {}
- override fun onViewAttachedToWindow(view: View?) {
+ override fun onViewAttachedToWindow(view: View) {
layoutRipple()
rippleView.startRipple(Runnable {
windowManager.removeView(rippleView)
@@ -176,7 +176,7 @@
val height = bounds.height()
val maxDiameter = Integer.max(width, height) * 2f
rippleView.setMaxSize(maxDiameter, maxDiameter)
- when (context.display.rotation) {
+ when (context.display?.rotation) {
Surface.ROTATION_0 -> {
rippleView.setCenter(
width * normalizedPortPosX, height * normalizedPortPosY)
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingA11yDelegate.kt b/packages/SystemUI/src/com/android/systemui/classifier/FalsingA11yDelegate.kt
index 63d57cc..a9f3b77 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingA11yDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingA11yDelegate.kt
@@ -29,7 +29,7 @@
*/
class FalsingA11yDelegate @Inject constructor(private val falsingCollector: FalsingCollector) :
View.AccessibilityDelegate() {
- override fun performAccessibilityAction(host: View?, action: Int, args: Bundle?): Boolean {
+ override fun performAccessibilityAction(host: View, action: Int, args: Bundle?): Boolean {
if (action == ACTION_CLICK) {
falsingCollector.onA11yAction()
}
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardTransitionExecutor.kt b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardTransitionExecutor.kt
index 0b8e83e..1b45ecd 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardTransitionExecutor.kt
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardTransitionExecutor.kt
@@ -59,7 +59,7 @@
context.startActivity(intent, transition.first.toBundle())
val runner = RemoteAnimationAdapter(NULL_ACTIVITY_TRANSITION, 0, 0)
try {
- WindowManagerGlobal.getWindowManagerService()
+ checkNotNull(WindowManagerGlobal.getWindowManagerService())
.overridePendingAppTransitionRemote(runner, displayTracker.defaultDisplayId)
} catch (e: Exception) {
Log.e(TAG, "Error overriding clipboard app transition", e)
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt b/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt
index 3e6ac86..c0d1951 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt
@@ -100,7 +100,7 @@
.stateIn(scope, SharingStarted.WhileSubscribed(), getResolutionScale())
override fun getResolutionScale(): Float {
- context.display.getDisplayInfo(displayInfo.value)
+ context.display?.getDisplayInfo(displayInfo.value)
val maxDisplayMode =
displayUtils.getMaximumResolutionDisplayMode(displayInfo.value.supportedModes)
maxDisplayMode?.let {
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingView.kt b/packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingView.kt
index 2dd98dc..323070a 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingView.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingView.kt
@@ -22,6 +22,7 @@
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View
+import com.android.systemui.shade.TouchLogger
import kotlin.math.pow
import kotlin.math.sqrt
import kotlinx.coroutines.DisposableHandle
@@ -83,6 +84,10 @@
interactionHandler.isLongPressHandlingEnabled = isEnabled
}
+ override fun dispatchTouchEvent(event: MotionEvent): Boolean {
+ return TouchLogger.logDispatchTouch("long_press", event, super.dispatchTouchEvent(event))
+ }
+
@SuppressLint("ClickableViewAccessibility")
override fun onTouchEvent(event: MotionEvent?): Boolean {
return interactionHandler.onTouchEvent(event?.toModel())
diff --git a/packages/SystemUI/src/com/android/systemui/contrast/ContrastDialog.kt b/packages/SystemUI/src/com/android/systemui/contrast/ContrastDialog.kt
index f17d0f3..0b327a1 100644
--- a/packages/SystemUI/src/com/android/systemui/contrast/ContrastDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/contrast/ContrastDialog.kt
@@ -66,9 +66,9 @@
contrastButtons =
mapOf(
- CONTRAST_LEVEL_STANDARD to findViewById(R.id.contrast_button_standard),
- CONTRAST_LEVEL_MEDIUM to findViewById(R.id.contrast_button_medium),
- CONTRAST_LEVEL_HIGH to findViewById(R.id.contrast_button_high)
+ CONTRAST_LEVEL_STANDARD to requireViewById(R.id.contrast_button_standard),
+ CONTRAST_LEVEL_MEDIUM to requireViewById(R.id.contrast_button_medium),
+ CONTRAST_LEVEL_HIGH to requireViewById(R.id.contrast_button_high)
)
contrastButtons.forEach { (contrastLevel, contrastButton) ->
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
index e8c97bf..4a534e9c 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
@@ -190,7 +190,7 @@
PREFS_CONTROLS_SEEDING_COMPLETED, mutableSetOf<String>())
val servicePackageSet = serviceInfoSet.map { it.packageName }
prefs.edit().putStringSet(PREFS_CONTROLS_SEEDING_COMPLETED,
- completedSeedingPackageSet.intersect(servicePackageSet)).apply()
+ completedSeedingPackageSet?.intersect(servicePackageSet) ?: emptySet()).apply()
var changed = false
favoriteComponentSet.subtract(serviceInfoSet).forEach {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
index 23721c9..8bae667 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
@@ -193,7 +193,7 @@
ControlsAnimations.enterAnimation(pageIndicator).apply {
addListener(object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator?) {
+ override fun onAnimationEnd(animation: Animator) {
// Position the tooltip if necessary after animations are complete
// so we can get the position on screen. The tooltip is not
// rooted in the layout root.
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ChallengeDialogs.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ChallengeDialogs.kt
index ff55b76d..a13f717 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ChallengeDialogs.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ChallengeDialogs.kt
@@ -106,10 +106,8 @@
}
)
- getWindow().apply {
- setType(WINDOW_TYPE)
- setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE)
- }
+ window?.setType(WINDOW_TYPE)
+ window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE)
setOnShowListener(DialogInterface.OnShowListener { _ ->
val editText = requireViewById<EditText>(R.id.controls_pin_input)
editText.setHint(instructions)
@@ -153,9 +151,7 @@
)
}
return builder.create().apply {
- getWindow().apply {
- setType(WINDOW_TYPE)
- }
+ window?.setType(WINDOW_TYPE)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt
index c04bc87..abe3423 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt
@@ -384,7 +384,7 @@
)
}
addListener(object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator?) {
+ override fun onAnimationEnd(animation: Animator) {
stateAnimator = null
}
})
@@ -438,7 +438,7 @@
duration = 200L
interpolator = Interpolators.LINEAR
addListener(object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator?) {
+ override fun onAnimationEnd(animation: Animator) {
statusRowUpdater.invoke()
}
})
@@ -450,7 +450,7 @@
statusAnimator = AnimatorSet().apply {
playSequentially(fadeOut, fadeIn)
addListener(object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator?) {
+ override fun onAnimationEnd(animation: Animator) {
status.alpha = STATUS_ALPHA_ENABLED
statusAnimator = null
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt
index be50a14..98f17f4 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt
@@ -132,8 +132,8 @@
init {
// To pass touches to the task inside TaskView.
- window.addFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL)
- window.addPrivateFlags(WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY)
+ window?.addFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL)
+ window?.addPrivateFlags(WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY)
setContentView(R.layout.controls_detail_dialog)
@@ -182,7 +182,7 @@
}
// consume all insets to achieve slide under effect
- window.getDecorView().setOnApplyWindowInsetsListener {
+ checkNotNull(window).decorView.setOnApplyWindowInsetsListener {
v: View, insets: WindowInsets ->
val l = v.getPaddingLeft()
val r = v.getPaddingRight()
@@ -202,7 +202,7 @@
}
fun getTaskViewBounds(): Rect {
- val wm = context.getSystemService(WindowManager::class.java)
+ val wm = checkNotNull(context.getSystemService(WindowManager::class.java))
val windowMetrics = wm.getCurrentWindowMetrics()
val rect = windowMetrics.bounds
val metricInsets = windowMetrics.windowInsets
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/RenderInfo.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/RenderInfo.kt
index ad2b785..dbbda9a 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/RenderInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/RenderInfo.kt
@@ -67,7 +67,8 @@
iconMap.put(resourceId, icon)
}
}
- return RenderInfo(icon!!.constantState.newDrawable(context.resources), fg, bg)
+ return RenderInfo(
+ checkNotNull(icon?.constantState).newDrawable(context.resources), fg, bg)
}
fun registerComponentIcon(componentName: ComponentName, icon: Drawable) {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/StatusBehavior.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/StatusBehavior.kt
index 84cda5a..3c2bfa0 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/StatusBehavior.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/StatusBehavior.kt
@@ -94,10 +94,8 @@
)
}
cvh.visibleDialog = builder.create().apply {
- getWindow().apply {
- setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY)
- show()
- }
+ window?.setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY)
+ show()
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleRangeBehavior.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleRangeBehavior.kt
index 1461135..b2c95a6 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleRangeBehavior.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleRangeBehavior.kt
@@ -244,7 +244,7 @@
cvh.clipLayer.level = it.animatedValue as Int
}
addListener(object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator?) {
+ override fun onAnimationEnd(animation: Animator) {
rangeAnimator = null
}
})
@@ -335,7 +335,7 @@
}
override fun onScroll(
- e1: MotionEvent,
+ e1: MotionEvent?,
e2: MotionEvent,
xDiff: Float,
yDiff: Float
diff --git a/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt b/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt
index 4e62104..ac0d3c8 100644
--- a/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt
@@ -34,6 +34,7 @@
import com.android.systemui.biometrics.AuthController
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.flags.FeatureFlags
import com.android.systemui.log.ScreenDecorationsLogger
import com.android.systemui.plugins.statusbar.StatusBarStateController
import java.util.concurrent.Executor
@@ -47,6 +48,7 @@
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
@Main private val mainExecutor: Executor,
private val logger: ScreenDecorationsLogger,
+ private val featureFlags: FeatureFlags,
) : DecorProviderFactory() {
private val display = context.display
private val displayInfo = DisplayInfo()
@@ -86,6 +88,7 @@
keyguardUpdateMonitor,
mainExecutor,
logger,
+ featureFlags,
)
)
}
@@ -110,6 +113,7 @@
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
private val mainExecutor: Executor,
private val logger: ScreenDecorationsLogger,
+ private val featureFlags: FeatureFlags,
) : BoundDecorProvider() {
override val viewId: Int = com.android.systemui.R.id.face_scanning_anim
@@ -144,6 +148,7 @@
mainExecutor,
logger,
authController,
+ featureFlags
)
view.id = viewId
view.setColor(tintColor)
diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayMetricsRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayMetricsRepository.kt
index bcfeeb9e..cef45dc 100644
--- a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayMetricsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayMetricsRepository.kt
@@ -51,7 +51,7 @@
val callback =
object : ConfigurationController.ConfigurationListener {
override fun onConfigChanged(newConfig: Configuration?) {
- context.display.getMetrics(displayMetricsHolder)
+ context.display?.getMetrics(displayMetricsHolder)
trySend(displayMetricsHolder)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
index 7c816ce..34a80e8 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
@@ -26,9 +26,9 @@
import android.text.format.Formatter;
import android.util.Log;
+import com.android.systemui.DejankUtils;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.doze.dagger.DozeScope;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.util.AlarmTimeout;
import com.android.systemui.util.wakelock.WakeLock;
@@ -52,15 +52,21 @@
private final boolean mCanAnimateTransition;
private final DozeParameters mDozeParameters;
private final DozeLog mDozeLog;
- private final StatusBarStateController mStatusBarStateController;
private long mLastTimeTickElapsed = 0;
+ // If time tick is scheduled and there's not a pending runnable to cancel:
+ private boolean mTimeTickScheduled;
+ private final Runnable mCancelTimeTickerRunnable = new Runnable() {
+ @Override
+ public void run() {
+ mTimeTicker.cancel();
+ }
+ };
@Inject
public DozeUi(Context context, AlarmManager alarmManager,
WakeLock wakeLock, DozeHost host, @Main Handler handler,
DozeParameters params,
- StatusBarStateController statusBarStateController,
DozeLog dozeLog) {
mContext = context;
mWakeLock = wakeLock;
@@ -70,7 +76,6 @@
mDozeParameters = params;
mTimeTicker = new AlarmTimeout(alarmManager, this::onTimeTick, "doze_time_tick", handler);
mDozeLog = dozeLog;
- mStatusBarStateController = statusBarStateController;
}
@Override
@@ -157,13 +162,15 @@
}
private void scheduleTimeTick() {
- if (mTimeTicker.isScheduled()) {
+ if (mTimeTickScheduled) {
return;
}
+ mTimeTickScheduled = true;
+ DejankUtils.removeCallbacks(mCancelTimeTickerRunnable);
long time = System.currentTimeMillis();
long delta = roundToNextMinute(time) - System.currentTimeMillis();
- boolean scheduled = mTimeTicker.schedule(delta, AlarmTimeout.MODE_IGNORE_IF_SCHEDULED);
+ boolean scheduled = mTimeTicker.schedule(delta, AlarmTimeout.MODE_RESCHEDULE_IF_SCHEDULED);
if (scheduled) {
mDozeLog.traceTimeTickScheduled(time, time + delta);
}
@@ -171,11 +178,11 @@
}
private void unscheduleTimeTick() {
- if (!mTimeTicker.isScheduled()) {
+ if (!mTimeTickScheduled) {
return;
}
- verifyLastTimeTick();
- mTimeTicker.cancel();
+ mTimeTickScheduled = false;
+ DejankUtils.postAfterTraversal(mCancelTimeTickerRunnable);
}
private void verifyLastTimeTick() {
@@ -205,6 +212,7 @@
// Keep wakelock until a frame has been pushed.
mHandler.post(mWakeLock.wrap(() -> {}));
+ mTimeTickScheduled = false;
scheduleTimeTick();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt b/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt
index ae40f7e8..7150d69e 100644
--- a/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt
@@ -432,9 +432,11 @@
}
private inline fun PrintWriter.wrapSection(entry: DumpsysEntry, block: () -> Unit) {
+ Trace.beginSection(entry.name)
preamble(entry)
val dumpTime = measureTimeMillis(block)
footer(entry, dumpTime)
+ Trace.endSection()
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/dump/DumpsysTableLogger.kt b/packages/SystemUI/src/com/android/systemui/dump/DumpsysTableLogger.kt
index f7e6b98..2e9d04b 100644
--- a/packages/SystemUI/src/com/android/systemui/dump/DumpsysTableLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/dump/DumpsysTableLogger.kt
@@ -16,6 +16,7 @@
package com.android.systemui.dump
+import android.os.Trace
import java.io.PrintWriter
/**
@@ -83,31 +84,33 @@
) {
fun printTableData(pw: PrintWriter) {
+ Trace.beginSection("DumpsysTableLogger#printTableData")
printSectionStart(pw)
printSchema(pw)
printData(pw)
printSectionEnd(pw)
+ Trace.endSection()
}
private fun printSectionStart(pw: PrintWriter) {
- pw.println(HEADER_PREFIX + sectionName)
- pw.println("version $VERSION")
+ pw.append(HEADER_PREFIX).println(sectionName)
+ pw.append("version ").println(VERSION)
}
private fun printSectionEnd(pw: PrintWriter) {
- pw.println(FOOTER_PREFIX + sectionName)
+ pw.append(FOOTER_PREFIX).println(sectionName)
}
private fun printSchema(pw: PrintWriter) {
- pw.println(columns.joinToString(separator = SEPARATOR))
+ columns.joinTo(pw, separator = SEPARATOR).println()
}
private fun printData(pw: PrintWriter) {
val count = columns.size
- rows
- .filter { it.size == count }
- .forEach { dataLine ->
- pw.println(dataLine.joinToString(separator = SEPARATOR))
+ rows.forEach { dataLine ->
+ if (dataLine.size == count) {
+ dataLine.joinTo(pw, separator = SEPARATOR).println()
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index d1a9d6bc..5b56223 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -230,7 +230,7 @@
// TODO(b/294866904): Tracking bug.
@JvmField
val WALLPAPER_PICKER_GRID_APPLY_BUTTON =
- unreleasedFlag("wallpaper_picker_grid_apply_button")
+ unreleasedFlag("wallpaper_picker_grid_apply_button")
/** Whether to run the new udfps keyguard refactor code. */
// TODO(b/279440316): Tracking bug.
@@ -258,8 +258,7 @@
/** Whether to listen for fingerprint authentication over keyguard occluding activities. */
// TODO(b/283260512): Tracking bug.
- @JvmField val FP_LISTEN_OCCLUDING_APPS = unreleasedFlag("fp_listen_occluding_apps",
- teamfood = true)
+ @JvmField val FP_LISTEN_OCCLUDING_APPS = releasedFlag("fp_listen_occluding_apps")
/** Flag meant to guard the talkback fix for the KeyguardIndicationTextView */
// TODO(b/286563884): Tracking bug
@@ -297,6 +296,25 @@
@JvmField val STOP_FACE_AUTH_ON_DISPLAY_OFF = resourceBooleanFlag(
R.bool.flag_stop_face_auth_on_display_off, "stop_face_auth_on_display_off")
+ /** Flag to disable the face scanning animation pulsing. */
+ // TODO(b/295245791): Tracking bug.
+ @JvmField val STOP_PULSING_FACE_SCANNING_ANIMATION = resourceBooleanFlag(
+ R.bool.flag_stop_pulsing_face_scanning_animation,
+ "stop_pulsing_face_scanning_animation")
+
+ /**
+ * TODO(b/278086361): Tracking bug
+ * Complete rewrite of the interactions between System UI and Window Manager involving keyguard
+ * state. When enabled, calls to ActivityTaskManagerService from System UI will exclusively
+ * occur from [WmLockscreenVisibilityManager] rather than the legacy KeyguardViewMediator.
+ *
+ * This flag is under development; some types of unlock may not animate properly if you enable
+ * it.
+ */
+ @JvmField
+ val KEYGUARD_WM_STATE_REFACTOR: UnreleasedFlag =
+ unreleasedFlag("keyguard_wm_state_refactor")
+
// 300 - power menu
// TODO(b/254512600): Tracking Bug
@JvmField val POWER_MENU_LITE = releasedFlag("power_menu_lite")
@@ -373,13 +391,13 @@
// TODO(b/293863612): Tracking Bug
@JvmField val INCOMPATIBLE_CHARGING_BATTERY_ICON =
- unreleasedFlag("incompatible_charging_battery_icon")
+ releasedFlag("incompatible_charging_battery_icon")
// TODO(b/293585143): Tracking Bug
val INSTANT_TETHER = unreleasedFlag("instant_tether")
// TODO(b/294588085): Tracking Bug
- val WIFI_SECONDARY_NETWORKS = unreleasedFlag("wifi_secondary_networks")
+ val WIFI_SECONDARY_NETWORKS = releasedFlag("wifi_secondary_networks")
// 700 - dialer/calls
// TODO(b/254512734): Tracking Bug
@@ -533,6 +551,12 @@
val ENABLE_PIP_APP_ICON_OVERLAY =
sysPropBooleanFlag("persist.wm.debug.enable_pip_app_icon_overlay", default = true)
+
+ // TODO(b/293252410) : Tracking Bug
+ @JvmField
+ val LOCKSCREEN_ENABLE_LANDSCAPE =
+ unreleasedFlag("lockscreen.enable_landscape")
+
// TODO(b/273443374): Tracking Bug
@Keep
@JvmField
@@ -759,4 +783,8 @@
/** Enable the Compose implementation of the Quick Settings footer actions. */
@JvmField
val COMPOSE_QS_FOOTER_ACTIONS = unreleasedFlag("compose_qs_footer_actions")
+
+ /** Enable the share wifi button in Quick Settings internet dialog. */
+ @JvmField
+ val SHARE_WIFI_QS_BUTTON = unreleasedFlag("share_wifi_qs_button")
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/view/KeyboardBacklightDialog.kt b/packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/view/KeyboardBacklightDialog.kt
index 7078341..b5b56b2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/view/KeyboardBacklightDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/view/KeyboardBacklightDialog.kt
@@ -161,7 +161,7 @@
}
private fun updateIconTile() {
- val iconTile = rootView.findViewById(BACKLIGHT_ICON_ID) as ImageView
+ val iconTile = rootView.requireViewById(BACKLIGHT_ICON_ID) as ImageView
val backgroundDrawable = iconTile.background as ShapeDrawable
if (currentLevel == 0) {
iconTile.setColorFilter(dimmedIconColor)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index e6053fb..9d2771e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -73,6 +73,14 @@
import com.android.internal.policy.IKeyguardStateCallback;
import com.android.keyguard.mediator.ScreenOnCoordinator;
import com.android.systemui.SystemUIApplication;
+import com.android.systemui.dagger.qualifiers.Application;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
+import com.android.systemui.keyguard.ui.binder.KeyguardSurfaceBehindParamsApplier;
+import com.android.systemui.keyguard.ui.binder.KeyguardSurfaceBehindViewBinder;
+import com.android.systemui.keyguard.ui.binder.WindowManagerLockscreenVisibilityViewBinder;
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardSurfaceBehindViewModel;
+import com.android.systemui.keyguard.ui.viewmodel.WindowManagerLockscreenVisibilityViewModel;
import com.android.systemui.settings.DisplayTracker;
import com.android.wm.shell.transition.ShellTransitions;
import com.android.wm.shell.transition.Transitions;
@@ -85,10 +93,13 @@
import javax.inject.Inject;
+import kotlinx.coroutines.CoroutineScope;
+
public class KeyguardService extends Service {
static final String TAG = "KeyguardService";
static final String PERMISSION = android.Manifest.permission.CONTROL_KEYGUARD;
+ private final FeatureFlags mFlags;
private final KeyguardViewMediator mKeyguardViewMediator;
private final KeyguardLifecyclesDispatcher mKeyguardLifecyclesDispatcher;
private final ScreenOnCoordinator mScreenOnCoordinator;
@@ -291,13 +302,33 @@
KeyguardLifecyclesDispatcher keyguardLifecyclesDispatcher,
ScreenOnCoordinator screenOnCoordinator,
ShellTransitions shellTransitions,
- DisplayTracker displayTracker) {
+ DisplayTracker displayTracker,
+ WindowManagerLockscreenVisibilityViewModel
+ wmLockscreenVisibilityViewModel,
+ WindowManagerLockscreenVisibilityManager wmLockscreenVisibilityManager,
+ KeyguardSurfaceBehindViewModel keyguardSurfaceBehindViewModel,
+ KeyguardSurfaceBehindParamsApplier keyguardSurfaceBehindAnimator,
+ @Application CoroutineScope scope,
+ FeatureFlags featureFlags) {
super();
mKeyguardViewMediator = keyguardViewMediator;
mKeyguardLifecyclesDispatcher = keyguardLifecyclesDispatcher;
mScreenOnCoordinator = screenOnCoordinator;
mShellTransitions = shellTransitions;
mDisplayTracker = displayTracker;
+ mFlags = featureFlags;
+
+ if (mFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ WindowManagerLockscreenVisibilityViewBinder.bind(
+ wmLockscreenVisibilityViewModel,
+ wmLockscreenVisibilityManager,
+ scope);
+
+ KeyguardSurfaceBehindViewBinder.bind(
+ keyguardSurfaceBehindViewModel,
+ keyguardSurfaceBehindAnimator,
+ scope);
+ }
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
index 3646144..32a9559 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
@@ -405,7 +405,9 @@
* the device.
*/
fun canPerformInWindowLauncherAnimations(): Boolean {
- return isNexusLauncherUnderneath() &&
+ // TODO(b/278086361): Refactor in-window animations.
+ return !featureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR) &&
+ isNexusLauncherUnderneath() &&
// If the launcher is underneath, but we're about to launch an activity, don't do
// the animations since they won't be visible.
!notificationShadeWindowController.isLaunchingActivity &&
@@ -849,54 +851,57 @@
}
surfaceBehindRemoteAnimationTargets?.forEach { surfaceBehindRemoteAnimationTarget ->
- val surfaceHeight: Int = surfaceBehindRemoteAnimationTarget.screenSpaceBounds.height()
+ if (!featureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ val surfaceHeight: Int =
+ surfaceBehindRemoteAnimationTarget.screenSpaceBounds.height()
- var scaleFactor = (SURFACE_BEHIND_START_SCALE_FACTOR +
- (1f - SURFACE_BEHIND_START_SCALE_FACTOR) *
- MathUtils.clamp(amount, 0f, 1f))
+ var scaleFactor = (SURFACE_BEHIND_START_SCALE_FACTOR +
+ (1f - SURFACE_BEHIND_START_SCALE_FACTOR) *
+ MathUtils.clamp(amount, 0f, 1f))
- // If we're dismissing via swipe to the Launcher, we'll play in-window scale animations,
- // so don't also scale the window.
- if (keyguardStateController.isDismissingFromSwipe &&
- willUnlockWithInWindowLauncherAnimations) {
- scaleFactor = 1f
- }
-
- // Translate up from the bottom.
- surfaceBehindMatrix.setTranslate(
- surfaceBehindRemoteAnimationTarget.screenSpaceBounds.left.toFloat(),
- surfaceBehindRemoteAnimationTarget.screenSpaceBounds.top.toFloat() +
- surfaceHeight * SURFACE_BEHIND_START_TRANSLATION_Y * (1f - amount)
- )
-
- // Scale up from a point at the center-bottom of the surface.
- surfaceBehindMatrix.postScale(
- scaleFactor,
- scaleFactor,
- keyguardViewController.viewRootImpl.width / 2f,
- surfaceHeight * SURFACE_BEHIND_SCALE_PIVOT_Y
- )
-
- // SyncRtSurfaceTransactionApplier cannot apply transaction when the target view is
- // unable to draw
- val sc: SurfaceControl? = surfaceBehindRemoteAnimationTarget.leash
- if (keyguardViewController.viewRootImpl.view?.visibility != View.VISIBLE &&
- sc?.isValid == true) {
- with(SurfaceControl.Transaction()) {
- setMatrix(sc, surfaceBehindMatrix, tmpFloat)
- setCornerRadius(sc, roundedCornerRadius)
- setAlpha(sc, animationAlpha)
- apply()
+ // If we're dismissing via swipe to the Launcher, we'll play in-window scale
+ // animations, so don't also scale the window.
+ if (keyguardStateController.isDismissingFromSwipe &&
+ willUnlockWithInWindowLauncherAnimations) {
+ scaleFactor = 1f
}
- } else {
- applyParamsToSurface(
- SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(
- surfaceBehindRemoteAnimationTarget.leash)
- .withMatrix(surfaceBehindMatrix)
- .withCornerRadius(roundedCornerRadius)
- .withAlpha(animationAlpha)
- .build()
+
+ // Translate up from the bottom.
+ surfaceBehindMatrix.setTranslate(
+ surfaceBehindRemoteAnimationTarget.screenSpaceBounds.left.toFloat(),
+ surfaceBehindRemoteAnimationTarget.screenSpaceBounds.top.toFloat() +
+ surfaceHeight * SURFACE_BEHIND_START_TRANSLATION_Y * (1f - amount)
)
+
+ // Scale up from a point at the center-bottom of the surface.
+ surfaceBehindMatrix.postScale(
+ scaleFactor,
+ scaleFactor,
+ keyguardViewController.viewRootImpl.width / 2f,
+ surfaceHeight * SURFACE_BEHIND_SCALE_PIVOT_Y
+ )
+
+ // SyncRtSurfaceTransactionApplier cannot apply transaction when the target view is
+ // unable to draw
+ val sc: SurfaceControl? = surfaceBehindRemoteAnimationTarget.leash
+ if (keyguardViewController.viewRootImpl.view?.visibility != View.VISIBLE &&
+ sc?.isValid == true) {
+ with(SurfaceControl.Transaction()) {
+ setMatrix(sc, surfaceBehindMatrix, tmpFloat)
+ setCornerRadius(sc, roundedCornerRadius)
+ setAlpha(sc, animationAlpha)
+ apply()
+ }
+ } else {
+ applyParamsToSurface(
+ SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(
+ surfaceBehindRemoteAnimationTarget.leash)
+ .withMatrix(surfaceBehindMatrix)
+ .withCornerRadius(roundedCornerRadius)
+ .withAlpha(animationAlpha)
+ .build()
+ )
+ }
}
}
@@ -931,28 +936,41 @@
/**
* Called by [KeyguardViewMediator] to let us know that the remote animation has finished, and
- * we should clean up all of our state.
+ * we should clean up all of our state. [showKeyguard] will tell us which surface should be
+ * visible after the animation has been completed or canceled.
*
* This is generally triggered by us, calling
* [KeyguardViewMediator.finishSurfaceBehindRemoteAnimation].
*/
- fun notifyFinishedKeyguardExitAnimation(cancelled: Boolean) {
+ fun notifyFinishedKeyguardExitAnimation(showKeyguard: Boolean) {
// Cancel any pending actions.
handler.removeCallbacksAndMessages(null)
- // Make sure we made the surface behind fully visible, just in case. It should already be
- // fully visible. The exit animation is finished, and we should not hold the leash anymore,
- // so forcing it to 1f.
- surfaceBehindAlpha = 1f
- setSurfaceBehindAppearAmount(1f)
+ // The lockscreen surface is gone, so it is now safe to re-show the smartspace.
+ if (lockscreenSmartspace?.visibility == View.INVISIBLE) {
+ lockscreenSmartspace?.visibility = View.VISIBLE
+ }
+
+ if (!showKeyguard) {
+ // Make sure we made the surface behind fully visible, just in case. It should already be
+ // fully visible. The exit animation is finished, and we should not hold the leash anymore,
+ // so forcing it to 1f.
+ surfaceBehindAlpha = 1f
+ setSurfaceBehindAppearAmount(1f)
+
+ try {
+ launcherUnlockController?.setUnlockAmount(1f, false /* forceIfAnimating */)
+ } catch (e: RemoteException) {
+ Log.e(TAG, "Remote exception in notifyFinishedKeyguardExitAnimation", e)
+ }
+ }
+
+ listeners.forEach { it.onUnlockAnimationFinished() }
+
+ // Reset all state
surfaceBehindAlphaAnimator.cancel()
surfaceBehindEntryAnimator.cancel()
wallpaperCannedUnlockAnimator.cancel()
- try {
- launcherUnlockController?.setUnlockAmount(1f, false /* forceIfAnimating */)
- } catch (e: RemoteException) {
- Log.e(TAG, "Remote exception in notifyFinishedKeyguardExitAnimation", e)
- }
// That target is no longer valid since the animation finished, null it out.
surfaceBehindRemoteAnimationTargets = null
@@ -962,13 +980,6 @@
dismissAmountThresholdsReached = false
willUnlockWithInWindowLauncherAnimations = false
willUnlockWithSmartspaceTransition = false
-
- // The lockscreen surface is gone, so it is now safe to re-show the smartspace.
- if (lockscreenSmartspace?.visibility == View.INVISIBLE) {
- lockscreenSmartspace?.visibility = View.VISIBLE
- }
-
- listeners.forEach { it.onUnlockAnimationFinished() }
}
/**
@@ -979,10 +990,12 @@
if (keyguardStateController.isShowing) {
// Hide the keyguard, with no fade out since we animated it away during the unlock.
- keyguardViewController.hide(
- surfaceBehindRemoteAnimationStartTime,
- 0 /* fadeOutDuration */
- )
+ if (!featureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ keyguardViewController.hide(
+ surfaceBehindRemoteAnimationStartTime,
+ 0 /* fadeOutDuration */
+ )
+ }
} else {
Log.i(TAG, "#hideKeyguardViewAfterRemoteAnimation called when keyguard view is not " +
"showing. Ignoring...")
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
index 2b6f77d..8e323d8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
@@ -137,6 +137,12 @@
fun bindIndicationArea() {
indicationAreaHandle?.dispose()
+ if (!featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
+ keyguardRootView.findViewById<View?>(R.id.keyguard_indication_area)?.let {
+ keyguardRootView.removeView(it)
+ }
+ }
+
indicationAreaHandle =
KeyguardIndicationAreaBinder.bind(
notificationShadeWindowView,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index e2929ae..a55cfa7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -171,8 +171,6 @@
import com.android.systemui.wallpapers.data.repository.WallpaperRepository;
import com.android.wm.shell.keyguard.KeyguardTransitions;
-import dagger.Lazy;
-
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -182,6 +180,7 @@
import java.util.concurrent.Executor;
import java.util.function.Consumer;
+import dagger.Lazy;
import kotlinx.coroutines.CoroutineDispatcher;
/**
@@ -1035,12 +1034,19 @@
IRemoteAnimationFinishedCallback finishedCallback) {
Trace.beginSection("mExitAnimationRunner.onAnimationStart#startKeyguardExitAnimation");
startKeyguardExitAnimation(transit, apps, wallpapers, nonApps, finishedCallback);
+ if (mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ mWmLockscreenVisibilityManager.get().onKeyguardGoingAwayRemoteAnimationStart(
+ transit, apps, wallpapers, nonApps, finishedCallback);
+ }
Trace.endSection();
}
@Override // Binder interface
public void onAnimationCancelled() {
cancelKeyguardExitAnimation();
+ if (mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ mWmLockscreenVisibilityManager.get().onKeyguardGoingAwayRemoteAnimationCancelled();
+ }
}
};
@@ -1106,7 +1112,7 @@
mOccludeByDreamAnimator = ValueAnimator.ofFloat(0f, 1f);
mOccludeByDreamAnimator.setDuration(mDreamOpenAnimationDuration);
- mOccludeByDreamAnimator.setInterpolator(Interpolators.LINEAR);
+ //mOccludeByDreamAnimator.setInterpolator(Interpolators.LINEAR);
mOccludeByDreamAnimator.addUpdateListener(
animation -> {
SyncRtSurfaceTransactionApplier.SurfaceParams.Builder
@@ -1335,6 +1341,8 @@
mDreamingToLockscreenTransitionViewModel;
private RemoteAnimationTarget mRemoteAnimationTarget;
+ private Lazy<WindowManagerLockscreenVisibilityManager> mWmLockscreenVisibilityManager;
+
/**
* Injected constructor. See {@link KeyguardModule}.
*/
@@ -1377,7 +1385,8 @@
SystemClock systemClock,
@Main CoroutineDispatcher mainDispatcher,
Lazy<DreamingToLockscreenTransitionViewModel> dreamingToLockscreenTransitionViewModel,
- SystemPropertiesHelper systemPropertiesHelper) {
+ SystemPropertiesHelper systemPropertiesHelper,
+ Lazy<WindowManagerLockscreenVisibilityManager> wmLockscreenVisibilityManager) {
mContext = context;
mUserTracker = userTracker;
mFalsingCollector = falsingCollector;
@@ -1443,8 +1452,9 @@
mUiEventLogger = uiEventLogger;
mSessionTracker = sessionTracker;
- mMainDispatcher = mainDispatcher;
mDreamingToLockscreenTransitionViewModel = dreamingToLockscreenTransitionViewModel;
+ mWmLockscreenVisibilityManager = wmLockscreenVisibilityManager;
+ mMainDispatcher = mainDispatcher;
}
public void userActivity() {
@@ -2553,7 +2563,7 @@
} else if (mSurfaceBehindRemoteAnimationRunning) {
// We're already running the keyguard exit animation, likely due to an in-progress swipe
// to unlock.
- exitKeyguardAndFinishSurfaceBehindRemoteAnimation(false /* cancelled */);
+ exitKeyguardAndFinishSurfaceBehindRemoteAnimation(false /* showKeyguard */);
} else if (!mHideAnimationRun) {
if (DEBUG) Log.d(TAG, "tryKeyguardDone: starting pre-hide animation");
mHideAnimationRun = true;
@@ -2677,6 +2687,12 @@
if (DEBUG) {
Log.d(TAG, "updateActivityLockScreenState(" + showing + ", " + aodShowing + ")");
}
+
+ if (mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ // Handled in WmLockscreenVisibilityManager if flag is enabled.
+ return;
+ }
+
try {
ActivityTaskManager.getService().setLockScreenShown(showing, aodShowing);
} catch (RemoteException e) {
@@ -2716,7 +2732,11 @@
}
mHiding = false;
- mKeyguardViewControllerLazy.get().show(options);
+ if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ // Handled directly in StatusBarKeyguardViewManager if enabled.
+ mKeyguardViewControllerLazy.get().show(options);
+ }
+
resetKeyguardDonePendingLocked();
mHideAnimationRun = false;
adjustStatusBarLocked();
@@ -2787,19 +2807,22 @@
mUpdateMonitor.setKeyguardGoingAway(true);
mKeyguardViewControllerLazy.get().setKeyguardGoingAwayState(true);
- // Don't actually hide the Keyguard at the moment, wait for window
- // manager until it tells us it's safe to do so with
- // startKeyguardExitAnimation.
- // Posting to mUiOffloadThread to ensure that calls to ActivityTaskManager will be in
- // order.
- final int keyguardFlag = flags;
- mUiBgExecutor.execute(() -> {
- try {
- ActivityTaskManager.getService().keyguardGoingAway(keyguardFlag);
- } catch (RemoteException e) {
- Log.e(TAG, "Error while calling WindowManager", e);
- }
- });
+ // Handled in WmLockscreenVisibilityManager if flag is enabled.
+ if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ // Don't actually hide the Keyguard at the moment, wait for window manager until it
+ // tells us it's safe to do so with startKeyguardExitAnimation.
+ // Posting to mUiOffloadThread to ensure that calls to ActivityTaskManager will be
+ // in order.
+ final int keyguardFlag = flags;
+ mUiBgExecutor.execute(() -> {
+ try {
+ ActivityTaskManager.getService().keyguardGoingAway(keyguardFlag);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error while calling WindowManager", e);
+ }
+ });
+ }
+
Trace.endSection();
}
};
@@ -2913,7 +2936,10 @@
if (!mHiding
&& !mSurfaceBehindRemoteAnimationRequested
&& !mKeyguardStateController.isFlingingToDismissKeyguardDuringSwipeGesture()) {
- if (finishedCallback != null) {
+ // If the flag is enabled, remote animation state is handled in
+ // WmLockscreenVisibilityManager.
+ if (finishedCallback != null
+ && !mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
// There will not execute animation, send a finish callback to ensure the remote
// animation won't hang there.
try {
@@ -2939,10 +2965,12 @@
new IRemoteAnimationFinishedCallback() {
@Override
public void onAnimationFinished() throws RemoteException {
- try {
- finishedCallback.onAnimationFinished();
- } catch (RemoteException e) {
- Slog.w(TAG, "Failed to call onAnimationFinished", e);
+ if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ try {
+ finishedCallback.onAnimationFinished();
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to call onAnimationFinished", e);
+ }
}
onKeyguardExitFinished();
mKeyguardViewControllerLazy.get().hide(0 /* startTime */,
@@ -2969,7 +2997,11 @@
// it will dismiss the panel in that case.
} else if (!mStatusBarStateController.leaveOpenOnKeyguardHide()
&& apps != null && apps.length > 0) {
- mSurfaceBehindRemoteAnimationFinishedCallback = finishedCallback;
+ if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ // Handled in WmLockscreenVisibilityManager. Other logic in this class will
+ // short circuit when this is null.
+ mSurfaceBehindRemoteAnimationFinishedCallback = finishedCallback;
+ }
mSurfaceBehindRemoteAnimationRunning = true;
mInteractionJankMonitor.begin(
@@ -2989,7 +3021,10 @@
createInteractionJankMonitorConf(
CUJ_LOCKSCREEN_UNLOCK_ANIMATION, "RemoteAnimationDisabled"));
- mKeyguardViewControllerLazy.get().hide(startTime, fadeoutDuration);
+ if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ // Handled directly in StatusBarKeyguardViewManager if enabled.
+ mKeyguardViewControllerLazy.get().hide(startTime, fadeoutDuration);
+ }
// TODO(bc-animation): When remote animation is enabled for keyguard exit animation,
// apps, wallpapers and finishedCallback are set to non-null. nonApps is not yet
@@ -2997,19 +3032,23 @@
mContext.getMainExecutor().execute(() -> {
if (finishedCallback == null) {
mKeyguardUnlockAnimationControllerLazy.get()
- .notifyFinishedKeyguardExitAnimation(false /* cancelled */);
+ .notifyFinishedKeyguardExitAnimation(false /* showKeyguard */);
mInteractionJankMonitor.end(CUJ_LOCKSCREEN_UNLOCK_ANIMATION);
return;
}
if (apps == null || apps.length == 0) {
Slog.e(TAG, "Keyguard exit without a corresponding app to show.");
+
try {
- finishedCallback.onAnimationFinished();
+ if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ finishedCallback.onAnimationFinished();
+ }
} catch (RemoteException e) {
Slog.e(TAG, "RemoteException");
} finally {
mInteractionJankMonitor.end(CUJ_LOCKSCREEN_UNLOCK_ANIMATION);
}
+
return;
}
@@ -3033,7 +3072,9 @@
@Override
public void onAnimationEnd(Animator animation) {
try {
- finishedCallback.onAnimationFinished();
+ if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ finishedCallback.onAnimationFinished();
+ }
} catch (RemoteException e) {
Slog.e(TAG, "RemoteException");
} finally {
@@ -3044,7 +3085,9 @@
@Override
public void onAnimationCancel(Animator animation) {
try {
- finishedCallback.onAnimationFinished();
+ if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ finishedCallback.onAnimationFinished();
+ }
} catch (RemoteException e) {
Slog.e(TAG, "RemoteException");
} finally {
@@ -3114,7 +3157,7 @@
// A lock is pending, meaning the keyguard exit animation was cancelled because we're
// re-locking. We should just end the surface-behind animation without exiting the
// keyguard. The pending lock will be handled by onFinishedGoingToSleep().
- finishSurfaceBehindRemoteAnimation(true);
+ finishSurfaceBehindRemoteAnimation(true /* showKeyguard */);
maybeHandlePendingLock();
} else {
Log.d(TAG, "#handleCancelKeyguardExitAnimation: keyguard exit animation cancelled. "
@@ -3123,7 +3166,7 @@
// No lock is pending, so the animation was cancelled during the unlock sequence, but
// we should end up unlocked. Show the surface and exit the keyguard.
showSurfaceBehindKeyguard();
- exitKeyguardAndFinishSurfaceBehindRemoteAnimation(true /* cancelled */);
+ exitKeyguardAndFinishSurfaceBehindRemoteAnimation(false /* showKeyguard */);
}
}
@@ -3134,12 +3177,13 @@
* with the RemoteAnimation, actually hide the keyguard, and clean up state related to the
* keyguard exit animation.
*
- * @param cancelled {@code true} if the animation was cancelled before it finishes.
+ * @param showKeyguard {@code true} if the animation was cancelled and keyguard should remain
+ * visible
*/
- public void exitKeyguardAndFinishSurfaceBehindRemoteAnimation(boolean cancelled) {
+ public void exitKeyguardAndFinishSurfaceBehindRemoteAnimation(boolean showKeyguard) {
Log.d(TAG, "onKeyguardExitRemoteAnimationFinished");
if (!mSurfaceBehindRemoteAnimationRunning && !mSurfaceBehindRemoteAnimationRequested) {
- Log.d(TAG, "skip onKeyguardExitRemoteAnimationFinished cancelled=" + cancelled
+ Log.d(TAG, "skip onKeyguardExitRemoteAnimationFinished showKeyguard=" + showKeyguard
+ " surfaceAnimationRunning=" + mSurfaceBehindRemoteAnimationRunning
+ " surfaceAnimationRequested=" + mSurfaceBehindRemoteAnimationRequested);
return;
@@ -3164,9 +3208,7 @@
+ " wasShowing=" + wasShowing);
}
- mKeyguardUnlockAnimationControllerLazy.get()
- .notifyFinishedKeyguardExitAnimation(cancelled);
- finishSurfaceBehindRemoteAnimation(cancelled);
+ finishSurfaceBehindRemoteAnimation(showKeyguard);
// Dispatch the callback on animation finishes.
mUpdateMonitor.dispatchKeyguardDismissAnimationFinished();
@@ -3194,7 +3236,10 @@
flags |= KEYGUARD_GOING_AWAY_FLAG_TO_LAUNCHER_CLEAR_SNAPSHOT;
}
- ActivityTaskManager.getService().keyguardGoingAway(flags);
+ if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ // Handled in WmLockscreenVisibilityManager.
+ ActivityTaskManager.getService().keyguardGoingAway(flags);
+ }
mKeyguardStateController.notifyKeyguardGoingAway(true);
} catch (RemoteException e) {
mSurfaceBehindRemoteAnimationRequested = false;
@@ -3230,7 +3275,10 @@
* This does not set keyguard state to either locked or unlocked, it simply ends the remote
* animation on the surface behind the keyguard. This can be called by
*/
- void finishSurfaceBehindRemoteAnimation(boolean cancelled) {
+ void finishSurfaceBehindRemoteAnimation(boolean showKeyguard) {
+ mKeyguardUnlockAnimationControllerLazy.get()
+ .notifyFinishedKeyguardExitAnimation(showKeyguard);
+
mSurfaceBehindRemoteAnimationRequested = false;
mSurfaceBehindRemoteAnimationRunning = false;
mKeyguardStateController.notifyKeyguardGoingAway(false);
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt
new file mode 100644
index 0000000..75677f2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt
@@ -0,0 +1,206 @@
+/*
+ * 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
+
+import android.app.IActivityTaskManager
+import android.util.Log
+import android.view.IRemoteAnimationFinishedCallback
+import android.view.RemoteAnimationTarget
+import android.view.WindowManager
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.keyguard.ui.binder.KeyguardSurfaceBehindParamsApplier
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import java.util.concurrent.Executor
+import javax.inject.Inject
+
+/**
+ * Manages lockscreen and AOD visibility state via the [IActivityTaskManager], and keeps track of
+ * remote animations related to changes in lockscreen visibility.
+ */
+@SysUISingleton
+class WindowManagerLockscreenVisibilityManager
+@Inject
+constructor(
+ @Main private val executor: Executor,
+ private val activityTaskManagerService: IActivityTaskManager,
+ private val keyguardStateController: KeyguardStateController,
+ private val keyguardSurfaceBehindAnimator: KeyguardSurfaceBehindParamsApplier,
+) {
+
+ /**
+ * Whether the lockscreen is showing, which we pass to [IActivityTaskManager.setLockScreenShown]
+ * in order to show the lockscreen and hide the surface behind the keyguard (or the inverse).
+ */
+ private var isLockscreenShowing = true
+
+ /**
+ * Whether AOD is showing, which we pass to [IActivityTaskManager.setLockScreenShown] in order
+ * to show AOD when the lockscreen is visible.
+ */
+ private var isAodVisible = false
+
+ /**
+ * Whether the keyguard is currently "going away", which we triggered via a call to
+ * [IActivityTaskManager.keyguardGoingAway]. When we tell WM that the keyguard is going away,
+ * the app/launcher surface behind the keyguard is made visible, and WM calls
+ * [onKeyguardGoingAwayRemoteAnimationStart] with a RemoteAnimationTarget so that we can animate
+ * it.
+ *
+ * Going away does not inherently result in [isLockscreenShowing] being set to false; we need to
+ * do that ourselves once we are done animating the surface.
+ *
+ * THIS IS THE ONLY PLACE 'GOING AWAY' TERMINOLOGY SHOULD BE USED. 'Going away' is a WM concept
+ * and we have gotten into trouble using it to mean various different things in the past. Unlock
+ * animations may still be visible when the keyguard is NOT 'going away', for example, when we
+ * play in-window animations, we set the surface to alpha=1f and end the animation immediately.
+ * The remainder of the animation occurs in-window, so while you might expect that the keyguard
+ * is still 'going away' because unlock animations are playing, it's actually not.
+ *
+ * If you want to know if the keyguard is 'going away', you probably want to check if we have
+ * STARTED but not FINISHED a transition to GONE.
+ *
+ * The going away animation will run until:
+ * - We manually call [endKeyguardGoingAwayAnimation] after we're done animating.
+ * - We call [setLockscreenShown] = true, which cancels the going away animation.
+ * - WM calls [onKeyguardGoingAwayRemoteAnimationCancelled] for another reason (such as the 10
+ * second timeout).
+ */
+ private var isKeyguardGoingAway = false
+ private set(value) {
+ // TODO(b/278086361): Extricate the keyguard state controller.
+ keyguardStateController.notifyKeyguardGoingAway(value)
+ field = value
+ }
+
+ /** Callback provided by WM to call once we're done with the going away animation. */
+ private var goingAwayRemoteAnimationFinishedCallback: IRemoteAnimationFinishedCallback? = null
+
+ /**
+ * Set the visibility of the surface behind the keyguard, making the appropriate calls to Window
+ * Manager to effect the change.
+ */
+ fun setSurfaceBehindVisibility(visible: Boolean) {
+ if (isKeyguardGoingAway == visible) {
+ Log.d(TAG, "WmLockscreenVisibilityManager#setVisibility -> already visible=$visible")
+ return
+ }
+
+ // The surface behind is always visible if the lockscreen is not showing, so we're already
+ // visible.
+ if (visible && !isLockscreenShowing) {
+ Log.d(TAG, "#setVisibility -> already visible since the lockscreen isn't showing")
+ return
+ }
+
+ if (visible) {
+ // Make the surface visible behind the keyguard by calling keyguardGoingAway. The
+ // lockscreen is still showing as well, allowing us to animate unlocked.
+ Log.d(TAG, "ActivityTaskManagerService#keyguardGoingAway()")
+ activityTaskManagerService.keyguardGoingAway(0)
+ isKeyguardGoingAway = true
+ } else {
+ // Hide the surface by setting the lockscreen showing.
+ setLockscreenShown(true)
+ }
+ }
+
+ fun setAodVisible(aodVisible: Boolean) {
+ setWmLockscreenState(aodVisible = aodVisible)
+ }
+
+ /** Sets the visibility of the lockscreen. */
+ fun setLockscreenShown(lockscreenShown: Boolean) {
+ setWmLockscreenState(lockscreenShowing = lockscreenShown)
+ }
+
+ fun onKeyguardGoingAwayRemoteAnimationStart(
+ @WindowManager.TransitionOldType transit: Int,
+ apps: Array<RemoteAnimationTarget>,
+ wallpapers: Array<RemoteAnimationTarget>,
+ nonApps: Array<RemoteAnimationTarget>,
+ finishedCallback: IRemoteAnimationFinishedCallback
+ ) {
+ goingAwayRemoteAnimationFinishedCallback = finishedCallback
+ keyguardSurfaceBehindAnimator.applyParamsToSurface(apps[0])
+ }
+
+ fun onKeyguardGoingAwayRemoteAnimationCancelled() {
+ // If WM cancelled the animation, we need to end immediately even if we're still using the
+ // animation.
+ endKeyguardGoingAwayAnimation()
+ }
+
+ /**
+ * Whether the going away remote animation target is in-use, which means we're animating it or
+ * intend to animate it.
+ *
+ * Some unlock animations (such as the translation spring animation) are non-deterministic and
+ * might end after the transition to GONE ends. In that case, we want to keep the remote
+ * animation running until the spring ends.
+ */
+ fun setUsingGoingAwayRemoteAnimation(usingTarget: Boolean) {
+ if (!usingTarget) {
+ endKeyguardGoingAwayAnimation()
+ }
+ }
+
+ private fun setWmLockscreenState(
+ lockscreenShowing: Boolean = this.isLockscreenShowing,
+ aodVisible: Boolean = this.isAodVisible
+ ) {
+ Log.d(
+ TAG,
+ "#setWmLockscreenState(" +
+ "isLockscreenShowing=$lockscreenShowing, " +
+ "aodVisible=$aodVisible)."
+ )
+
+ if (this.isLockscreenShowing == lockscreenShowing && this.isAodVisible == aodVisible) {
+ return
+ }
+
+ activityTaskManagerService.setLockScreenShown(lockscreenShowing, aodVisible)
+ this.isLockscreenShowing = lockscreenShowing
+ this.isAodVisible = aodVisible
+ }
+
+ private fun endKeyguardGoingAwayAnimation() {
+ if (!isKeyguardGoingAway) {
+ Log.d(
+ TAG,
+ "#endKeyguardGoingAwayAnimation() called when isKeyguardGoingAway=false. " +
+ "Short-circuiting."
+ )
+ return
+ }
+
+ executor.execute {
+ Log.d(TAG, "Finishing remote animation.")
+ goingAwayRemoteAnimationFinishedCallback?.onAnimationFinished()
+ goingAwayRemoteAnimationFinishedCallback = null
+
+ isKeyguardGoingAway = false
+
+ keyguardSurfaceBehindAnimator.notifySurfaceReleased()
+ }
+ }
+
+ companion object {
+ private val TAG = this::class.java.simpleName
+ }
+}
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 4205ed2..dc19ea2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -46,6 +46,7 @@
import com.android.systemui.keyguard.DismissCallbackRegistry;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.KeyguardViewMediator;
+import com.android.systemui.keyguard.WindowManagerLockscreenVisibilityManager;
import com.android.systemui.keyguard.data.quickaffordance.KeyguardDataQuickAffordanceModule;
import com.android.systemui.keyguard.data.repository.KeyguardFaceAuthModule;
import com.android.systemui.keyguard.data.repository.KeyguardRepositoryModule;
@@ -73,12 +74,11 @@
import com.android.systemui.wallpapers.data.repository.WallpaperRepository;
import com.android.wm.shell.keyguard.KeyguardTransitions;
+import java.util.concurrent.Executor;
+
import dagger.Lazy;
import dagger.Module;
import dagger.Provides;
-
-import java.util.concurrent.Executor;
-
import kotlinx.coroutines.CoroutineDispatcher;
/**
@@ -144,7 +144,8 @@
SystemClock systemClock,
@Main CoroutineDispatcher mainDispatcher,
Lazy<DreamingToLockscreenTransitionViewModel> dreamingToLockscreenTransitionViewModel,
- SystemPropertiesHelper systemPropertiesHelper) {
+ SystemPropertiesHelper systemPropertiesHelper,
+ Lazy<WindowManagerLockscreenVisibilityManager> wmLockscreenVisibilityManager) {
return new KeyguardViewMediator(
context,
uiEventLogger,
@@ -186,7 +187,8 @@
systemClock,
mainDispatcher,
dreamingToLockscreenTransitionViewModel,
- systemPropertiesHelper);
+ systemPropertiesHelper,
+ wmLockscreenVisibilityManager);
}
/** */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
index c019d21..5d7a3d4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
@@ -59,7 +59,7 @@
conflatedCallbackFlow {
val callback =
object : QuickAccessWalletClient.OnWalletCardsRetrievedCallback {
- override fun onWalletCardsRetrieved(response: GetWalletCardsResponse?) {
+ override fun onWalletCardsRetrieved(response: GetWalletCardsResponse) {
val hasCards = response?.walletCards?.isNotEmpty() == true
trySendWithFailureLogging(
state(
@@ -71,7 +71,7 @@
)
}
- override fun onWalletCardRetrievalError(error: GetWalletCardsError?) {
+ override fun onWalletCardRetrievalError(error: GetWalletCardsError) {
Log.e(TAG, "Wallet card retrieval error, message: \"${error?.message}\"")
trySendWithFailureLogging(
KeyguardQuickAffordanceConfig.LockScreenState.Hidden,
@@ -133,13 +133,13 @@
return suspendCancellableCoroutine { continuation ->
val callback =
object : QuickAccessWalletClient.OnWalletCardsRetrievedCallback {
- override fun onWalletCardsRetrieved(response: GetWalletCardsResponse?) {
+ override fun onWalletCardsRetrieved(response: GetWalletCardsResponse) {
continuation.resumeWith(
Result.success(response?.walletCards ?: emptyList())
)
}
- override fun onWalletCardRetrievalError(error: GetWalletCardsError?) {
+ override fun onWalletCardRetrievalError(error: GetWalletCardsError) {
continuation.resumeWith(Result.success(emptyList()))
}
}
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 30f8f3e..6894147 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
@@ -46,6 +46,7 @@
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.KeyguardState
import com.android.systemui.keyguard.shared.model.SuccessFaceAuthenticationStatus
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.log.FaceAuthenticationLogger
@@ -160,7 +161,7 @@
@FaceDetectTableLog private val faceDetectLog: TableLogBuffer,
@FaceAuthTableLog private val faceAuthLog: TableLogBuffer,
private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
- featureFlags: FeatureFlags,
+ private val featureFlags: FeatureFlags,
facePropertyRepository: FacePropertyRepository,
dumpManager: DumpManager,
) : DeviceEntryFaceAuthRepository, Dumpable {
@@ -286,8 +287,12 @@
// starts going to sleep.
merge(
keyguardRepository.wakefulness.map { it.isStartingToSleepOrAsleep() },
- keyguardRepository.isKeyguardGoingAway,
- userRepository.userSwitchingInProgress
+ if (featureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ keyguardTransitionInteractor.isInTransitionToState(KeyguardState.GONE)
+ } else {
+ keyguardRepository.isKeyguardGoingAway
+ },
+ userRepository.userSwitchingInProgress,
)
.onEach { anyOfThemIsTrue ->
if (anyOfThemIsTrue) {
@@ -581,7 +586,7 @@
// We always want to invoke face detect in the main thread.
faceAuthLogger.faceDetectionStarted()
faceManager?.detectFace(
- detectCancellationSignal,
+ checkNotNull(detectCancellationSignal),
detectionCallback,
FaceAuthenticateOptions.Builder().setUserId(currentUserId).build()
)
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 e35c369..42cd3a5 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
@@ -99,7 +99,16 @@
/** Is an activity showing over the keyguard? */
val isKeyguardOccluded: Flow<Boolean>
- /** Observable for the signal that keyguard is about to go away. */
+ /**
+ * Observable for the signal that keyguard is about to go away.
+ *
+ * TODO(b/278086361): Remove once KEYGUARD_WM_STATE_REFACTOR flag is removed.
+ */
+ @Deprecated(
+ "Use KeyguardTransitionInteractor flows instead. The closest match for 'going " +
+ "away' is isInTransitionToState(GONE), but consider using more specific flows " +
+ "whenever possible."
+ )
val isKeyguardGoingAway: Flow<Boolean>
/** Is the always-on display available to be used? */
@@ -365,10 +374,11 @@
awaitClose { keyguardStateController.removeCallback(callback) }
}
+ .distinctUntilChanged()
.stateIn(
- scope = scope,
- started = SharingStarted.WhileSubscribed(),
- initialValue = keyguardStateController.isUnlocked,
+ scope,
+ SharingStarted.Eagerly,
+ initialValue = false,
)
override val isKeyguardGoingAway: Flow<Boolean> = conflatedCallbackFlow {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
index 246ee33..2f80106 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
@@ -32,6 +32,11 @@
@Binds fun keyguardRepository(impl: KeyguardRepositoryImpl): KeyguardRepository
@Binds
+ fun keyguardSurfaceBehindRepository(
+ impl: KeyguardSurfaceBehindRepositoryImpl
+ ): KeyguardSurfaceBehindRepository
+
+ @Binds
fun keyguardTransitionRepository(
impl: KeyguardTransitionRepositoryImpl
): KeyguardTransitionRepository
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardSurfaceBehindRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardSurfaceBehindRepository.kt
new file mode 100644
index 0000000..014b7fa
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardSurfaceBehindRepository.kt
@@ -0,0 +1,46 @@
+/*
+ * 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.data.repository
+
+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
+
+/**
+ * State related to SysUI's handling of the surface behind the keyguard (typically an app or the
+ * launcher). We manipulate this surface during unlock animations.
+ */
+interface KeyguardSurfaceBehindRepository {
+
+ /** Whether we're running animations on the surface. */
+ val isAnimatingSurface: Flow<Boolean>
+
+ /** Set whether we're running animations on the surface. */
+ fun setAnimatingSurface(animating: Boolean)
+}
+
+@SysUISingleton
+class KeyguardSurfaceBehindRepositoryImpl @Inject constructor() : KeyguardSurfaceBehindRepository {
+ private val _isAnimatingSurface = MutableStateFlow(false)
+ override val isAnimatingSurface = _isAnimatingSurface.asStateFlow()
+
+ override fun setAnimatingSurface(animating: Boolean) {
+ _isAnimatingSurface.value = animating
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepository.kt
index 6a2511f..1c0b73f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepository.kt
@@ -163,12 +163,13 @@
private fun constructCircleRevealFromPoint(point: Point): LightRevealEffect {
return with(point) {
+ val display = checkNotNull(context.display)
CircleReveal(
x,
y,
startRadius = 0,
endRadius =
- max(max(x, context.display.width - x), max(y, context.display.height - y)),
+ max(max(x, display.width - x), max(y, display.height - y)),
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
index 8f0b91b..271bc38 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
@@ -17,7 +17,6 @@
package com.android.systemui.keyguard.domain.interactor
import android.animation.ValueAnimator
-import com.android.app.animation.Interpolators
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
@@ -26,6 +25,7 @@
import com.android.systemui.util.kotlin.Utils.Companion.toQuad
import com.android.systemui.util.kotlin.Utils.Companion.toQuint
import com.android.systemui.util.kotlin.sample
+import com.android.wm.shell.animation.Interpolators
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
index 6b28b27..aa771fd 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
@@ -20,8 +20,11 @@
import com.android.app.animation.Interpolators
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.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.KeyguardSurfaceBehindModel
import com.android.systemui.keyguard.shared.model.StatusBarState.KEYGUARD
import com.android.systemui.keyguard.shared.model.TransitionInfo
import com.android.systemui.keyguard.shared.model.TransitionState
@@ -34,7 +37,11 @@
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.launch
@SysUISingleton
@@ -45,6 +52,7 @@
override val transitionInteractor: KeyguardTransitionInteractor,
@Application private val scope: CoroutineScope,
private val keyguardInteractor: KeyguardInteractor,
+ private val flags: FeatureFlags,
private val shadeRepository: ShadeRepository,
) :
TransitionInteractor(
@@ -53,6 +61,7 @@
override fun start() {
listenForLockscreenToGone()
+ listenForLockscreenToGoneDragging()
listenForLockscreenToOccluded()
listenForLockscreenToCamera()
listenForLockscreenToAodOrDozing()
@@ -62,6 +71,63 @@
listenForLockscreenToAlternateBouncer()
}
+ /**
+ * Whether we want the surface behind the keyguard visible for the transition from LOCKSCREEN,
+ * or null if we don't care and should just use a reasonable default.
+ *
+ * [KeyguardSurfaceBehindInteractor] will switch to this flow whenever a transition from
+ * LOCKSCREEN is running.
+ */
+ val surfaceBehindVisibility: Flow<Boolean?> =
+ transitionInteractor.startedKeyguardTransitionStep
+ .map { startedStep ->
+ if (startedStep.to != KeyguardState.GONE) {
+ // LOCKSCREEN to anything but GONE does not require any special surface
+ // visibility handling.
+ return@map null
+ }
+
+ true // TODO(b/278086361): Implement continuous swipe to unlock.
+ }
+ .onStart {
+ // Default to null ("don't care, use a reasonable default").
+ emit(null)
+ }
+ .distinctUntilChanged()
+
+ /**
+ * The surface behind view params to use for the transition from LOCKSCREEN, or null if we don't
+ * care and should use a reasonable default.
+ */
+ val surfaceBehindModel: Flow<KeyguardSurfaceBehindModel?> =
+ combine(
+ transitionInteractor.startedKeyguardTransitionStep,
+ transitionInteractor.transitionStepsFromState(KeyguardState.LOCKSCREEN)
+ ) { startedStep, fromLockscreenStep ->
+ if (startedStep.to != KeyguardState.GONE) {
+ // Only LOCKSCREEN -> GONE has specific surface params (for the unlock
+ // animation).
+ return@combine null
+ } else if (fromLockscreenStep.value > 0.5f) {
+ // Start the animation once we're 50% transitioned to GONE.
+ KeyguardSurfaceBehindModel(
+ animateFromAlpha = 0f,
+ alpha = 1f,
+ animateFromTranslationY = 500f,
+ translationY = 0f
+ )
+ } else {
+ KeyguardSurfaceBehindModel(
+ alpha = 0f,
+ )
+ }
+ }
+ .onStart {
+ // Default to null ("don't care, use a reasonable default").
+ emit(null)
+ }
+ .distinctUntilChanged()
+
private fun listenForLockscreenToDreaming() {
val invalidFromStates = setOf(KeyguardState.AOD, KeyguardState.DOZING)
scope.launch {
@@ -169,7 +235,8 @@
}
// If canceled, just put the state back
- // TODO: This logic should happen in FromPrimaryBouncerInteractor.
+ // TODO(b/278086361): This logic should happen in
+ // FromPrimaryBouncerInteractor.
if (nextState == TransitionState.CANCELED) {
transitionRepository.startTransition(
TransitionInfo(
@@ -201,7 +268,32 @@
}
}
+ fun dismissKeyguard() {
+ startTransitionTo(KeyguardState.GONE)
+ }
+
private fun listenForLockscreenToGone() {
+ if (flags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ return
+ }
+
+ scope.launch {
+ keyguardInteractor.isKeyguardGoingAway
+ .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
+ .collect { pair ->
+ val (isKeyguardGoingAway, lastStartedStep) = pair
+ if (isKeyguardGoingAway && lastStartedStep.to == KeyguardState.LOCKSCREEN) {
+ startTransitionTo(KeyguardState.GONE)
+ }
+ }
+ }
+ }
+
+ private fun listenForLockscreenToGoneDragging() {
+ if (flags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ return
+ }
+
scope.launch {
keyguardInteractor.isKeyguardGoingAway
.sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
@@ -291,7 +383,7 @@
}
companion object {
- private val DEFAULT_DURATION = 500.milliseconds
+ private val DEFAULT_DURATION = 400.milliseconds
val TO_DREAMING_DURATION = 933.milliseconds
val TO_OCCLUDED_DURATION = 450.milliseconds
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
index 9142d1f..c9f32da 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
@@ -17,23 +17,28 @@
package com.android.systemui.keyguard.domain.interactor
import android.animation.ValueAnimator
-import com.android.app.animation.Interpolators
import com.android.keyguard.KeyguardSecurityModel
-import com.android.keyguard.KeyguardSecurityModel.SecurityMode.Password
import com.android.keyguard.KeyguardUpdateMonitor
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.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.KeyguardSurfaceBehindModel
import com.android.systemui.keyguard.shared.model.WakefulnessState
import com.android.systemui.util.kotlin.Utils.Companion.toQuad
import com.android.systemui.util.kotlin.Utils.Companion.toQuint
import com.android.systemui.util.kotlin.Utils.Companion.toTriple
import com.android.systemui.util.kotlin.sample
+import com.android.wm.shell.animation.Interpolators
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.launch
@SysUISingleton
@@ -44,6 +49,7 @@
override val transitionInteractor: KeyguardTransitionInteractor,
@Application private val scope: CoroutineScope,
private val keyguardInteractor: KeyguardInteractor,
+ private val flags: FeatureFlags,
private val keyguardSecurityModel: KeyguardSecurityModel,
) :
TransitionInteractor(
@@ -57,6 +63,57 @@
listenForPrimaryBouncerToDreamingLockscreenHosted()
}
+ val surfaceBehindVisibility: Flow<Boolean?> =
+ combine(
+ transitionInteractor.startedKeyguardTransitionStep,
+ transitionInteractor.transitionStepsFromState(KeyguardState.PRIMARY_BOUNCER)
+ ) { startedStep, fromBouncerStep ->
+ if (startedStep.to != KeyguardState.GONE) {
+ return@combine null
+ }
+
+ fromBouncerStep.value > 0.5f
+ }
+ .onStart {
+ // Default to null ("don't care, use a reasonable default").
+ emit(null)
+ }
+ .distinctUntilChanged()
+
+ val surfaceBehindModel: Flow<KeyguardSurfaceBehindModel?> =
+ combine(
+ transitionInteractor.startedKeyguardTransitionStep,
+ transitionInteractor.transitionStepsFromState(KeyguardState.PRIMARY_BOUNCER)
+ ) { startedStep, fromBouncerStep ->
+ if (startedStep.to != KeyguardState.GONE) {
+ // BOUNCER to anything but GONE does not require any special surface
+ // visibility handling.
+ return@combine null
+ }
+
+ if (fromBouncerStep.value > 0.5f) {
+ KeyguardSurfaceBehindModel(
+ animateFromAlpha = 0f,
+ alpha = 1f,
+ animateFromTranslationY = 500f,
+ translationY = 0f,
+ )
+ } else {
+ KeyguardSurfaceBehindModel(
+ alpha = 0f,
+ )
+ }
+ }
+ .onStart {
+ // Default to null ("don't care, use a reasonable default").
+ emit(null)
+ }
+ .distinctUntilChanged()
+
+ fun dismissPrimaryBouncer() {
+ startTransitionTo(KeyguardState.GONE)
+ }
+
private fun listenForPrimaryBouncerToLockscreenOrOccluded() {
scope.launch {
keyguardInteractor.primaryBouncerShowing
@@ -124,28 +181,34 @@
private fun listenForPrimaryBouncerToDreamingLockscreenHosted() {
scope.launch {
keyguardInteractor.primaryBouncerShowing
- .sample(
- combine(
- keyguardInteractor.isActiveDreamLockscreenHosted,
- transitionInteractor.startedKeyguardTransitionStep,
- ::Pair
- ),
- ::toTriple
- )
- .collect {
- (isBouncerShowing, isActiveDreamLockscreenHosted, lastStartedTransitionStep) ->
- if (
- !isBouncerShowing &&
- isActiveDreamLockscreenHosted &&
- lastStartedTransitionStep.to == KeyguardState.PRIMARY_BOUNCER
- ) {
- startTransitionTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
+ .sample(
+ combine(
+ keyguardInteractor.isActiveDreamLockscreenHosted,
+ transitionInteractor.startedKeyguardTransitionStep,
+ ::Pair
+ ),
+ ::toTriple
+ )
+ .collect { (isBouncerShowing, isActiveDreamLockscreenHosted, lastStartedTransitionStep) ->
+ if (
+ !isBouncerShowing &&
+ isActiveDreamLockscreenHosted &&
+ lastStartedTransitionStep.to == KeyguardState.PRIMARY_BOUNCER
+ ) {
+ startTransitionTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
+ }
}
- }
}
}
private fun listenForPrimaryBouncerToGone() {
+ if (flags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ // This is handled in KeyguardSecurityContainerController and
+ // StatusBarKeyguardViewManager, which calls the transition interactor to kick off a
+ // transition vs. listening to legacy state flags.
+ return
+ }
+
scope.launch {
keyguardInteractor.isKeyguardGoingAway
.sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
@@ -160,7 +223,7 @@
)
// IME for password requires a slightly faster animation
val duration =
- if (securityMode == Password) {
+ if (securityMode == KeyguardSecurityModel.SecurityMode.Password) {
TO_GONE_SHORT_DURATION
} else {
TO_GONE_DURATION
@@ -188,7 +251,7 @@
companion object {
private val DEFAULT_DURATION = 300.milliseconds
- val TO_GONE_DURATION = 250.milliseconds
+ val TO_GONE_DURATION = 500.milliseconds
val TO_GONE_SHORT_DURATION = 200.milliseconds
}
}
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 53d3c07..562c4db 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
@@ -34,12 +34,12 @@
import com.android.systemui.keyguard.shared.model.DozeStateModel.Companion.isDozeOff
import com.android.systemui.keyguard.shared.model.DozeTransitionModel
import com.android.systemui.keyguard.shared.model.KeyguardRootViewVisibilityState
+import com.android.systemui.keyguard.shared.model.KeyguardState
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.statusbar.CommandQueue
import com.android.systemui.util.kotlin.sample
-import javax.inject.Inject
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
@@ -53,6 +53,7 @@
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onStart
+import javax.inject.Inject
/**
* Encapsulates business-logic related to the keyguard but not to a more specific part within it.
@@ -264,7 +265,26 @@
repository.setAnimateDozingTransitions(animate)
}
+ fun isKeyguardDismissable(): Boolean {
+ return repository.isKeyguardUnlocked.value
+ }
+
companion object {
private const val TAG = "KeyguardInteractor"
+
+ fun isKeyguardVisibleInState(state: KeyguardState): Boolean {
+ return when (state) {
+ KeyguardState.OFF -> true
+ KeyguardState.DOZING -> true
+ KeyguardState.DREAMING -> true
+ KeyguardState.AOD -> true
+ KeyguardState.ALTERNATE_BOUNCER -> true
+ KeyguardState.PRIMARY_BOUNCER -> true
+ KeyguardState.LOCKSCREEN -> true
+ KeyguardState.GONE -> false
+ KeyguardState.OCCLUDED -> true
+ KeyguardState.DREAMING_LOCKSCREEN_HOSTED -> false
+ }
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractor.kt
new file mode 100644
index 0000000..bf04f8f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractor.kt
@@ -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 com.android.systemui.keyguard.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.data.repository.KeyguardSurfaceBehindRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.KeyguardSurfaceBehindModel
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+import javax.inject.Inject
+
+@SysUISingleton
+class KeyguardSurfaceBehindInteractor
+@Inject
+constructor(
+ private val repository: KeyguardSurfaceBehindRepository,
+ private val fromLockscreenInteractor: FromLockscreenTransitionInteractor,
+ private val fromPrimaryBouncerInteractor: FromPrimaryBouncerTransitionInteractor,
+ transitionInteractor: KeyguardTransitionInteractor,
+) {
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ val viewParams: Flow<KeyguardSurfaceBehindModel> =
+ transitionInteractor.isInTransitionToAnyState
+ .flatMapLatest { isInTransition ->
+ if (!isInTransition) {
+ defaultParams
+ } else {
+ combine(
+ transitionSpecificViewParams,
+ defaultParams,
+ ) { transitionParams, defaultParams ->
+ transitionParams ?: defaultParams
+ }
+ }
+ }
+
+ val isAnimatingSurface = repository.isAnimatingSurface
+
+ private val defaultParams =
+ transitionInteractor.finishedKeyguardState.map { state ->
+ KeyguardSurfaceBehindModel(
+ alpha =
+ if (WindowManagerLockscreenVisibilityInteractor.isSurfaceVisible(state)) 1f
+ else 0f
+ )
+ }
+
+ /**
+ * View params provided by the transition interactor for the most recently STARTED transition.
+ * This is used to run transition-specific animations on the surface.
+ *
+ * If null, there are no transition-specific view params needed for this transition and we will
+ * use a reasonable default.
+ */
+ @OptIn(ExperimentalCoroutinesApi::class)
+ private val transitionSpecificViewParams: Flow<KeyguardSurfaceBehindModel?> =
+ transitionInteractor.startedKeyguardTransitionStep.flatMapLatest { startedStep ->
+ when (startedStep.from) {
+ KeyguardState.LOCKSCREEN -> fromLockscreenInteractor.surfaceBehindModel
+ KeyguardState.PRIMARY_BOUNCER -> fromPrimaryBouncerInteractor.surfaceBehindModel
+ // Return null for other states, where no transition specific params are needed.
+ else -> flowOf(null)
+ }
+ }
+
+ fun setAnimatingSurface(animating: Boolean) {
+ repository.setAnimatingSurface(animating)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
index 8c4c7ae..9382618 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
@@ -17,17 +17,20 @@
package com.android.systemui.keyguard.domain.interactor
+import android.util.Log
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER
import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
+import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING_LOCKSCREEN_HOSTED
import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED
+import com.android.systemui.keyguard.shared.model.KeyguardState.OFF
import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
@@ -36,6 +39,7 @@
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
@@ -46,8 +50,12 @@
class KeyguardTransitionInteractor
@Inject
constructor(
- private val repository: KeyguardTransitionRepository,
@Application val scope: CoroutineScope,
+ private val repository: KeyguardTransitionRepository,
+ private val keyguardInteractor: dagger.Lazy<KeyguardInteractor>,
+ private val fromLockscreenTransitionInteractor: dagger.Lazy<FromLockscreenTransitionInteractor>,
+ private val fromPrimaryBouncerTransitionInteractor:
+ dagger.Lazy<FromPrimaryBouncerTransitionInteractor>,
) {
private val TAG = this::class.simpleName
@@ -128,12 +136,11 @@
repository.transition(PRIMARY_BOUNCER, GONE)
/** OFF->LOCKSCREEN transition information. */
- val offToLockscreenTransition: Flow<TransitionStep> =
- repository.transition(KeyguardState.OFF, LOCKSCREEN)
+ val offToLockscreenTransition: Flow<TransitionStep> = repository.transition(OFF, LOCKSCREEN)
/** DOZING->LOCKSCREEN transition information. */
val dozingToLockscreenTransition: Flow<TransitionStep> =
- repository.transition(KeyguardState.DOZING, LOCKSCREEN)
+ repository.transition(DOZING, LOCKSCREEN)
/**
* AOD<->LOCKSCREEN transition information, mapped to dozeAmount range of AOD (1f) <->
@@ -157,17 +164,30 @@
val finishedKeyguardTransitionStep: Flow<TransitionStep> =
repository.transitions.filter { step -> step.transitionState == TransitionState.FINISHED }
- /** The destination state of the last started transition */
+ /** The destination state of the last started transition. */
val startedKeyguardState: StateFlow<KeyguardState> =
startedKeyguardTransitionStep
.map { step -> step.to }
- .stateIn(scope, SharingStarted.Eagerly, KeyguardState.OFF)
+ .stateIn(scope, SharingStarted.Eagerly, OFF)
/** The last completed [KeyguardState] transition */
val finishedKeyguardState: StateFlow<KeyguardState> =
finishedKeyguardTransitionStep
.map { step -> step.to }
.stateIn(scope, SharingStarted.Eagerly, LOCKSCREEN)
+
+ /**
+ * Whether we're currently in a transition to a new [KeyguardState] and haven't yet completed
+ * it.
+ */
+ val isInTransitionToAnyState =
+ combine(
+ startedKeyguardTransitionStep,
+ finishedKeyguardState,
+ ) { startedStep, finishedState ->
+ startedStep.to != finishedState
+ }
+
/**
* The amount of transition into or out of the given [KeyguardState].
*
@@ -187,4 +207,41 @@
}
}
}
+
+ fun transitionStepsFromState(fromState: KeyguardState): Flow<TransitionStep> {
+ return repository.transitions.filter { step -> step.from == fromState }
+ }
+
+ fun transitionStepsToState(toState: KeyguardState): Flow<TransitionStep> {
+ return repository.transitions.filter { step -> step.to == toState }
+ }
+
+ /**
+ * Called to start a transition that will ultimately dismiss the keyguard from the current
+ * state.
+ */
+ fun startDismissKeyguardTransition() {
+ when (startedKeyguardState.value) {
+ LOCKSCREEN -> fromLockscreenTransitionInteractor.get().dismissKeyguard()
+ PRIMARY_BOUNCER -> fromPrimaryBouncerTransitionInteractor.get().dismissPrimaryBouncer()
+ else ->
+ Log.e(
+ "KeyguardTransitionInteractor",
+ "We don't know how to dismiss keyguard from state " +
+ "${startedKeyguardState.value}"
+ )
+ }
+ }
+
+ /** Whether we're in a transition to the given [KeyguardState], but haven't yet completed it. */
+ fun isInTransitionToState(
+ state: KeyguardState,
+ ): Flow<Boolean> {
+ return combine(
+ startedKeyguardTransitionStep,
+ finishedKeyguardState,
+ ) { startedStep, finishedState ->
+ startedStep.to == state && finishedState != state
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractor.kt
index ff8d5c9..3ec660a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractor.kt
@@ -22,10 +22,14 @@
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
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.data.repository.DeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.shared.model.ErrorFingerprintAuthenticationStatus
import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -55,6 +59,8 @@
@Application scope: CoroutineScope,
private val context: Context,
activityStarter: ActivityStarter,
+ powerInteractor: PowerInteractor,
+ featureFlags: FeatureFlags,
) {
private val keyguardOccludedByApp: Flow<Boolean> =
combine(
@@ -87,29 +93,37 @@
.ifKeyguardOccludedByApp(/* elseFlow */ flowOf(null))
init {
- scope.launch {
- // On fingerprint success, go to the home screen
- fingerprintUnlockSuccessEvents.collect { goToHomeScreen() }
- }
+ if (featureFlags.isEnabled(Flags.FP_LISTEN_OCCLUDING_APPS)) {
+ scope.launch {
+ // On fingerprint success when the screen is on, go to the home screen
+ fingerprintUnlockSuccessEvents.sample(powerInteractor.isInteractive).collect {
+ if (it) {
+ goToHomeScreen()
+ }
+ // don't go to the home screen if the authentication is from AOD/dozing/off
+ }
+ }
- scope.launch {
- // On device fingerprint lockout, request the bouncer with a runnable to
- // go to the home screen. Without this, the bouncer won't proceed to the home screen.
- fingerprintLockoutEvents.collect {
- activityStarter.dismissKeyguardThenExecute(
- object : ActivityStarter.OnDismissAction {
- override fun onDismiss(): Boolean {
- goToHomeScreen()
- return false
- }
+ scope.launch {
+ // On device fingerprint lockout, request the bouncer with a runnable to
+ // go to the home screen. Without this, the bouncer won't proceed to the home
+ // screen.
+ fingerprintLockoutEvents.collect {
+ activityStarter.dismissKeyguardThenExecute(
+ object : ActivityStarter.OnDismissAction {
+ override fun onDismiss(): Boolean {
+ goToHomeScreen()
+ return false
+ }
- override fun willRunAnimationOnKeyguard(): Boolean {
- return false
- }
- },
- /* cancel= */ null,
- /* afterKeyguardGone */ false
- )
+ override fun willRunAnimationOnKeyguard(): Boolean {
+ return false
+ }
+ },
+ /* cancel= */ null,
+ /* afterKeyguardGone */ false
+ )
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt
new file mode 100644
index 0000000..96bfdc6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt
@@ -0,0 +1,162 @@
+/*
+ * 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.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+import javax.inject.Inject
+
+@SysUISingleton
+class WindowManagerLockscreenVisibilityInteractor
+@Inject
+constructor(
+ keyguardInteractor: KeyguardInteractor,
+ transitionInteractor: KeyguardTransitionInteractor,
+ surfaceBehindInteractor: KeyguardSurfaceBehindInteractor,
+ fromLockscreenInteractor: FromLockscreenTransitionInteractor,
+ fromBouncerInteractor: FromPrimaryBouncerTransitionInteractor,
+) {
+ private val defaultSurfaceBehindVisibility =
+ transitionInteractor.finishedKeyguardState.map(::isSurfaceVisible)
+
+ /**
+ * Surface visibility provided by the From*TransitionInteractor responsible for the currently
+ * RUNNING transition, or null if the current transition does not require special surface
+ * visibility handling.
+ *
+ * An example of transition-specific visibility is swipe to unlock, where the surface should
+ * only be visible after swiping 20% of the way up the screen, and should become invisible again
+ * if the user swipes back down.
+ */
+ @OptIn(ExperimentalCoroutinesApi::class)
+ private val transitionSpecificSurfaceBehindVisibility: Flow<Boolean?> =
+ transitionInteractor.startedKeyguardTransitionStep
+ .flatMapLatest { startedStep ->
+ when (startedStep.from) {
+ KeyguardState.LOCKSCREEN -> {
+ fromLockscreenInteractor.surfaceBehindVisibility
+ }
+ KeyguardState.PRIMARY_BOUNCER -> {
+ fromBouncerInteractor.surfaceBehindVisibility
+ }
+ else -> flowOf(null)
+ }
+ }
+ .distinctUntilChanged()
+
+ /**
+ * Surface visibility, which is either determined by the default visibility in the FINISHED
+ * KeyguardState, or the transition-specific visibility used during certain RUNNING transitions.
+ */
+ @OptIn(ExperimentalCoroutinesApi::class)
+ val surfaceBehindVisibility: Flow<Boolean> =
+ transitionInteractor
+ .isInTransitionToAnyState
+ .flatMapLatest { isInTransition ->
+ if (!isInTransition) {
+ defaultSurfaceBehindVisibility
+ } else {
+ combine(
+ transitionSpecificSurfaceBehindVisibility,
+ defaultSurfaceBehindVisibility,
+ ) { transitionVisibility, defaultVisibility ->
+ // Defer to the transition-specific visibility since we're RUNNING a
+ // transition, but fall back to the default visibility if the current
+ // transition's interactor did not specify a visibility.
+ transitionVisibility ?: defaultVisibility
+ }
+ }
+ }
+ .distinctUntilChanged()
+
+ /**
+ * Whether we're animating, or intend to animate, the surface behind the keyguard via remote
+ * animation. This is used to keep the RemoteAnimationTarget alive until we're done using it.
+ */
+ val usingKeyguardGoingAwayAnimation: Flow<Boolean> =
+ combine(
+ transitionInteractor.isInTransitionToState(KeyguardState.GONE),
+ transitionInteractor.finishedKeyguardState,
+ surfaceBehindInteractor.isAnimatingSurface
+ ) { isInTransitionToGone, finishedState, isAnimatingSurface ->
+ // We may still be animating the surface after the keyguard is fully GONE, since
+ // some animations (like the translation spring) are not tied directly to the
+ // transition step amount.
+ isInTransitionToGone || (finishedState == KeyguardState.GONE && isAnimatingSurface)
+ }
+ .distinctUntilChanged()
+
+ /**
+ * Whether the lockscreen is visible, from the Window Manager (WM) perspective.
+ *
+ * Note: This may briefly be true even if the lockscreen UI has animated out (alpha = 0f), as we
+ * only inform WM once we're done with the keyguard and we're fully GONE. Don't use this if you
+ * want to know if the AOD/clock/notifs/etc. are visible.
+ */
+ val lockscreenVisibility: Flow<Boolean> =
+ combine(
+ transitionInteractor.startedKeyguardTransitionStep,
+ transitionInteractor.finishedKeyguardState,
+ ) { startedStep, finishedState ->
+ // If we finished the transition, use the finished state. If we're running a
+ // transition, use the state we're transitioning FROM. This can be different from
+ // the last finished state if a transition is interrupted. For example, if we were
+ // transitioning from GONE to AOD and then started AOD -> LOCKSCREEN mid-transition,
+ // we want to immediately use the visibility for AOD (lockscreenVisibility=true)
+ // even though the lastFinishedState is still GONE (lockscreenVisibility=false).
+ if (finishedState == startedStep.to) finishedState else startedStep.from
+ }
+ .map(::isLockscreenVisible)
+ .distinctUntilChanged()
+
+ /**
+ * Whether always-on-display (AOD) is visible when the lockscreen is visible, from window
+ * manager's perspective.
+ *
+ * Note: This may be true even if AOD is not user-visible, such as when the light sensor
+ * indicates the device is in the user's pocket. Don't use this if you want to know if the AOD
+ * clock/smartspace/notif icons are visible.
+ */
+ val aodVisibility: Flow<Boolean> =
+ combine(
+ keyguardInteractor.isDozing,
+ keyguardInteractor.biometricUnlockState,
+ ) { isDozing, biometricUnlockState ->
+ // AOD is visible if we're dozing, unless we are wake and unlocking (where we go
+ // directly from AOD to unlocked while dozing).
+ isDozing && !BiometricUnlockModel.isWakeAndUnlock(biometricUnlockState)
+ }
+ .distinctUntilChanged()
+
+ companion object {
+ fun isSurfaceVisible(state: KeyguardState): Boolean {
+ return !isLockscreenVisible(state)
+ }
+
+ fun isLockscreenVisible(state: KeyguardState): Boolean {
+ return state != KeyguardState.GONE
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardSurfaceBehindModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardSurfaceBehindModel.kt
new file mode 100644
index 0000000..7fb5cfd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardSurfaceBehindModel.kt
@@ -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.systemui.keyguard.shared.model
+
+/**
+ * Models the appearance of the surface behind the keyguard, and (optionally) how it should be
+ * animating.
+ *
+ * This is intended to be an atomic, high-level description of the surface's appearance and related
+ * animations, which we can derive from the STARTED/FINISHED transition states rather than the
+ * individual TransitionSteps.
+ *
+ * For example, if we're transitioning from LOCKSCREEN to GONE, that means we should be
+ * animatingFromAlpha 0f -> 1f and animatingFromTranslationY 500f -> 0f.
+ * KeyguardSurfaceBehindAnimator can decide how best to implement this, depending on previously
+ * running animations, spring momentum, and other state.
+ */
+data class KeyguardSurfaceBehindModel(
+ val alpha: Float = 1f,
+
+ /**
+ * If provided, animate from this value to [alpha] unless an animation is already running, in
+ * which case we'll animate from the current value to [alpha].
+ */
+ val animateFromAlpha: Float = alpha,
+ val translationY: Float = 0f,
+
+ /**
+ * If provided, animate from this value to [translationY] unless an animation is already
+ * running, in which case we'll animate from the current value to [translationY].
+ */
+ val animateFromTranslationY: Float = translationY,
+) {
+ fun willAnimateAlpha(): Boolean {
+ return animateFromAlpha != alpha
+ }
+
+ fun willAnimateTranslationY(): Boolean {
+ return animateFromTranslationY != translationY
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardAmbientIndicationAreaViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardAmbientIndicationAreaViewBinder.kt
index d6883dd..5c072fb 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardAmbientIndicationAreaViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardAmbientIndicationAreaViewBinder.kt
@@ -67,14 +67,15 @@
repeatOnLifecycle(Lifecycle.State.STARTED) {
launch {
keyguardRootViewModel.alpha.collect { alpha ->
- view.importantForAccessibility =
- if (alpha == 0f) {
- View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
- } else {
- View.IMPORTANT_FOR_ACCESSIBILITY_AUTO
- }
-
- ambientIndicationArea?.alpha = alpha
+ ambientIndicationArea?.apply {
+ this.importantForAccessibility =
+ if (alpha == 0f) {
+ View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
+ } else {
+ View.IMPORTANT_FOR_ACCESSIBILITY_AUTO
+ }
+ this.alpha = alpha
+ }
}
}
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 a0a2abe..44acf4f 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
@@ -176,14 +176,15 @@
launch {
viewModel.alpha.collect { alpha ->
- view.importantForAccessibility =
- if (alpha == 0f) {
- View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
- } else {
- View.IMPORTANT_FOR_ACCESSIBILITY_AUTO
- }
-
- ambientIndicationArea?.alpha = alpha
+ ambientIndicationArea?.apply {
+ this.importantForAccessibility =
+ if (alpha == 0f) {
+ View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
+ } else {
+ View.IMPORTANT_FOR_ACCESSIBILITY_AUTO
+ }
+ this.alpha = alpha
+ }
}
}
@@ -471,7 +472,7 @@
return true
}
- override fun onLongClickUseDefaultHapticFeedback(view: View?) = false
+ override fun onLongClickUseDefaultHapticFeedback(view: View) = false
}
@Deprecated("Deprecated as part of b/278057014")
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt
index a385a0e..dc51944 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt
@@ -73,25 +73,27 @@
launch {
if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
keyguardRootViewModel.alpha.collect { alpha ->
- view.importantForAccessibility =
- if (alpha == 0f) {
- View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
- } else {
- View.IMPORTANT_FOR_ACCESSIBILITY_AUTO
- }
-
- indicationArea.alpha = alpha
+ indicationArea.apply {
+ this.importantForAccessibility =
+ if (alpha == 0f) {
+ View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
+ } else {
+ View.IMPORTANT_FOR_ACCESSIBILITY_AUTO
+ }
+ this.alpha = alpha
+ }
}
} else {
viewModel.alpha.collect { alpha ->
- view.importantForAccessibility =
- if (alpha == 0f) {
- View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
- } else {
- View.IMPORTANT_FOR_ACCESSIBILITY_AUTO
- }
-
- indicationArea.alpha = alpha
+ indicationArea.apply {
+ this.importantForAccessibility =
+ if (alpha == 0f) {
+ View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
+ } else {
+ View.IMPORTANT_FOR_ACCESSIBILITY_AUTO
+ }
+ this.alpha = alpha
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt
index 63a6791..83b5463 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt
@@ -304,7 +304,7 @@
return true
}
- override fun onLongClickUseDefaultHapticFeedback(view: View?) = false
+ override fun onLongClickUseDefaultHapticFeedback(view: View) = false
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsViewBinder.kt
index 162c109..82610e6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsViewBinder.kt
@@ -43,7 +43,7 @@
vibratorHelper: VibratorHelper,
activityStarter: ActivityStarter
): DisposableHandle {
- val view = parentView.findViewById<LaunchableLinearLayout>(R.id.keyguard_settings_button)
+ val view = parentView.requireViewById<LaunchableLinearLayout>(R.id.keyguard_settings_button)
val disposableHandle =
view.repeatWhenAttached {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindParamsApplier.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindParamsApplier.kt
new file mode 100644
index 0000000..fe2df21
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindParamsApplier.kt
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.binder
+
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.ValueAnimator
+import android.graphics.Matrix
+import android.util.Log
+import android.view.RemoteAnimationTarget
+import android.view.SurfaceControl
+import android.view.SyncRtSurfaceTransactionApplier
+import android.view.View
+import androidx.dynamicanimation.animation.FloatValueHolder
+import androidx.dynamicanimation.animation.SpringAnimation
+import androidx.dynamicanimation.animation.SpringForce
+import com.android.keyguard.KeyguardViewController
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.keyguard.TAG
+import com.android.systemui.keyguard.domain.interactor.KeyguardSurfaceBehindInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardSurfaceBehindModel
+import com.android.wm.shell.animation.Interpolators
+import java.util.concurrent.Executor
+import javax.inject.Inject
+
+/**
+ * Applies [KeyguardSurfaceBehindViewParams] to a RemoteAnimationTarget, starting and managing
+ * animations as needed.
+ */
+@SysUISingleton
+class KeyguardSurfaceBehindParamsApplier
+@Inject
+constructor(
+ @Main private val executor: Executor,
+ private val keyguardViewController: KeyguardViewController,
+ private val interactor: KeyguardSurfaceBehindInteractor,
+) {
+ private var surfaceBehind: RemoteAnimationTarget? = null
+ private val surfaceTransactionApplier: SyncRtSurfaceTransactionApplier
+ get() = SyncRtSurfaceTransactionApplier(keyguardViewController.viewRootImpl.view)
+
+ private val matrix = Matrix()
+ private val tmpFloat = FloatArray(9)
+
+ private var animatedTranslationY = FloatValueHolder()
+ private val translateYSpring =
+ SpringAnimation(animatedTranslationY).apply {
+ spring =
+ SpringForce().apply {
+ stiffness = 200f
+ dampingRatio = 1f
+ }
+ addUpdateListener { _, _, _ -> applyToSurfaceBehind() }
+ addEndListener { _, _, _, _ ->
+ try {
+ updateIsAnimatingSurface()
+ } catch (e: NullPointerException) {
+ // TODO(b/291645410): Remove when we can isolate DynamicAnimations.
+ e.printStackTrace()
+ }
+ }
+ }
+
+ private var animatedAlpha = 0f
+ private var alphaAnimator =
+ ValueAnimator.ofFloat(0f, 1f).apply {
+ duration = 500
+ interpolator = Interpolators.ALPHA_IN
+ addUpdateListener {
+ animatedAlpha = it.animatedValue as Float
+ applyToSurfaceBehind()
+ }
+ addListener(
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator?) {
+ updateIsAnimatingSurface()
+ }
+ }
+ )
+ }
+
+ /**
+ * ViewParams to apply to the surface provided to [applyParamsToSurface]. If the surface is null
+ * these will be applied once someone gives us a surface via [applyParamsToSurface].
+ */
+ var viewParams: KeyguardSurfaceBehindModel = KeyguardSurfaceBehindModel()
+ set(newParams) {
+ field = newParams
+ startOrUpdateAnimators()
+ applyToSurfaceBehind()
+ }
+
+ /**
+ * Provides us with a surface to animate. We'll apply the [viewParams] to this surface and start
+ * any necessary animations.
+ */
+ fun applyParamsToSurface(surface: RemoteAnimationTarget) {
+ this.surfaceBehind = surface
+ startOrUpdateAnimators()
+ }
+
+ /**
+ * Notifies us that the [RemoteAnimationTarget] has been released, one way or another.
+ * Attempting to animate a released target will cause a crash.
+ *
+ * This can be called either because we finished animating the surface naturally, or by WM
+ * because external factors cancelled the remote animation (timeout, re-lock, etc). If it's the
+ * latter, cancel any outstanding animations we have.
+ */
+ fun notifySurfaceReleased() {
+ surfaceBehind = null
+
+ if (alphaAnimator.isRunning) {
+ alphaAnimator.cancel()
+ }
+
+ if (translateYSpring.isRunning) {
+ translateYSpring.cancel()
+ }
+ }
+
+ private fun startOrUpdateAnimators() {
+ if (surfaceBehind == null) {
+ return
+ }
+
+ if (viewParams.willAnimateAlpha()) {
+ var fromAlpha = viewParams.animateFromAlpha
+
+ if (alphaAnimator.isRunning) {
+ alphaAnimator.cancel()
+ fromAlpha = animatedAlpha
+ }
+
+ alphaAnimator.setFloatValues(fromAlpha, viewParams.alpha)
+ alphaAnimator.start()
+ }
+
+ if (viewParams.willAnimateTranslationY()) {
+ if (!translateYSpring.isRunning) {
+ // If the spring isn't running yet, set the start value. Otherwise, respect the
+ // current position.
+ animatedTranslationY.value = viewParams.animateFromTranslationY
+ }
+
+ translateYSpring.animateToFinalPosition(viewParams.translationY)
+ }
+
+ updateIsAnimatingSurface()
+ }
+
+ private fun updateIsAnimatingSurface() {
+ interactor.setAnimatingSurface(translateYSpring.isRunning || alphaAnimator.isRunning)
+ }
+
+ private fun applyToSurfaceBehind() {
+ surfaceBehind?.leash?.let { sc ->
+ executor.execute {
+ if (surfaceBehind == null) {
+ Log.d(
+ TAG,
+ "Attempting to modify params of surface that isn't " +
+ "animating. Ignoring."
+ )
+ matrix.set(Matrix.IDENTITY_MATRIX)
+ return@execute
+ }
+
+ val translationY =
+ if (translateYSpring.isRunning) animatedTranslationY.value
+ else viewParams.translationY
+
+ val alpha =
+ if (alphaAnimator.isRunning) {
+ animatedAlpha
+ } else {
+ viewParams.alpha
+ }
+
+ if (
+ keyguardViewController.viewRootImpl.view?.visibility != View.VISIBLE &&
+ sc.isValid
+ ) {
+ with(SurfaceControl.Transaction()) {
+ setMatrix(
+ sc,
+ matrix.apply { setTranslate(/* dx= */ 0f, translationY) },
+ tmpFloat
+ )
+ setAlpha(sc, alpha)
+ apply()
+ }
+ } else {
+ surfaceTransactionApplier.scheduleApply(
+ SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(sc)
+ .withMatrix(matrix.apply { setTranslate(/* dx= */ 0f, translationY) })
+ .withAlpha(alpha)
+ .build()
+ )
+ }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindViewBinder.kt
new file mode 100644
index 0000000..599f69f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindViewBinder.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.binder
+
+import com.android.systemui.keyguard.WindowManagerLockscreenVisibilityManager
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardSurfaceBehindViewModel
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+
+/**
+ * Binds the [WindowManagerLockscreenVisibilityManager] "view", which manages the visibility of the
+ * surface behind the keyguard.
+ */
+object KeyguardSurfaceBehindViewBinder {
+ @JvmStatic
+ fun bind(
+ viewModel: KeyguardSurfaceBehindViewModel,
+ applier: KeyguardSurfaceBehindParamsApplier,
+ scope: CoroutineScope
+ ) {
+ scope.launch { viewModel.surfaceBehindViewParams.collect { applier.viewParams = it } }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardInternalViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardInternalViewBinder.kt
index b568a9a..3bb01f2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardInternalViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardInternalViewBinder.kt
@@ -42,13 +42,13 @@
view.accessibilityDelegate = viewModel.accessibilityDelegate
// bind child views
- UdfpsAodFingerprintViewBinder.bind(view.findViewById(R.id.udfps_aod_fp), aodViewModel)
+ UdfpsAodFingerprintViewBinder.bind(view.requireViewById(R.id.udfps_aod_fp), aodViewModel)
UdfpsFingerprintViewBinder.bind(
- view.findViewById(R.id.udfps_lockscreen_fp),
+ view.requireViewById(R.id.udfps_lockscreen_fp),
fingerprintViewModel
)
UdfpsBackgroundViewBinder.bind(
- view.findViewById(R.id.udfps_keyguard_fp_bg),
+ view.requireViewById(R.id.udfps_keyguard_fp_bg),
backgroundViewModel
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityViewBinder.kt
new file mode 100644
index 0000000..fc0c78a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityViewBinder.kt
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.binder
+
+import com.android.systemui.keyguard.WindowManagerLockscreenVisibilityManager
+import com.android.systemui.keyguard.ui.viewmodel.WindowManagerLockscreenVisibilityViewModel
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+
+/**
+ * Binds the [WindowManagerLockscreenVisibilityManager] "view", which manages the visibility of the
+ * surface behind the keyguard.
+ */
+object WindowManagerLockscreenVisibilityViewBinder {
+ @JvmStatic
+ fun bind(
+ viewModel: WindowManagerLockscreenVisibilityViewModel,
+ lockscreenVisibilityManager: WindowManagerLockscreenVisibilityManager,
+ scope: CoroutineScope
+ ) {
+ scope.launch {
+ viewModel.surfaceBehindVisibility.collect {
+ lockscreenVisibilityManager.setSurfaceBehindVisibility(it)
+ }
+ }
+
+ scope.launch {
+ viewModel.lockscreenVisibility.collect {
+ lockscreenVisibilityManager.setLockscreenShown(it)
+ }
+ }
+
+ scope.launch {
+ viewModel.aodVisibility.collect { lockscreenVisibilityManager.setAodVisible(it) }
+ }
+
+ scope.launch {
+ viewModel.surfaceBehindAnimating.collect {
+ lockscreenVisibilityManager.setUsingGoingAwayRemoteAnimation(it)
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
index 58bc552..580db35 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
@@ -117,7 +117,7 @@
private var host: SurfaceControlViewHost
val surfacePackage: SurfaceControlViewHost.SurfacePackage
- get() = host.surfacePackage
+ get() = checkNotNull(host.surfacePackage)
private lateinit var largeClockHostView: FrameLayout
private lateinit var smallClockHostView: FrameLayout
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSurfaceBehindViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSurfaceBehindViewModel.kt
new file mode 100644
index 0000000..4f52962
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSurfaceBehindViewModel.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.KeyguardSurfaceBehindInteractor
+import javax.inject.Inject
+
+@SysUISingleton
+class KeyguardSurfaceBehindViewModel
+@Inject
+constructor(interactor: KeyguardSurfaceBehindInteractor) {
+ val surfaceBehindViewParams = interactor.viewParams
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
index 11e85d0..6d3b7f1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
@@ -26,6 +26,7 @@
import com.android.systemui.scene.shared.model.SceneKey
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
@@ -51,15 +52,14 @@
)
/** The key of the scene we should switch to when swiping up. */
- val upDestinationSceneKey =
- authenticationInteractor.canSwipeToDismiss
- .map { canSwipeToDismiss -> upDestinationSceneKey(canSwipeToDismiss) }
- .stateIn(
- scope = applicationScope,
- started = SharingStarted.WhileSubscribed(),
- initialValue =
- upDestinationSceneKey(authenticationInteractor.canSwipeToDismiss.value),
- )
+ val upDestinationSceneKey: Flow<SceneKey> =
+ authenticationInteractor.isUnlocked.map { isUnlocked ->
+ if (isUnlocked) {
+ SceneKey.Gone
+ } else {
+ SceneKey.Bouncer
+ }
+ }
/** Notifies that the lock button on the lock screen was clicked. */
fun onLockButtonClicked() {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/WindowManagerLockscreenVisibilityViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/WindowManagerLockscreenVisibilityViewModel.kt
new file mode 100644
index 0000000..f797640
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/WindowManagerLockscreenVisibilityViewModel.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.WindowManagerLockscreenVisibilityInteractor
+import javax.inject.Inject
+
+@SysUISingleton
+class WindowManagerLockscreenVisibilityViewModel
+@Inject
+constructor(interactor: WindowManagerLockscreenVisibilityInteractor) {
+ val surfaceBehindVisibility = interactor.surfaceBehindVisibility
+ val surfaceBehindAnimating = interactor.usingKeyguardGoingAwayAnimation
+ val lockscreenVisibility = interactor.lockscreenVisibility
+ val aodVisibility = interactor.aodVisibility
+}
diff --git a/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt b/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt
index e064839..5f7991e 100644
--- a/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt
+++ b/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt
@@ -70,7 +70,7 @@
var lifecycleOwner: ViewLifecycleOwner? = null
val onAttachListener =
object : View.OnAttachStateChangeListener {
- override fun onViewAttachedToWindow(v: View?) {
+ override fun onViewAttachedToWindow(v: View) {
Assert.isMainThread()
lifecycleOwner?.onDestroy()
lifecycleOwner =
@@ -81,7 +81,7 @@
)
}
- override fun onViewDetachedFromWindow(v: View?) {
+ override fun onViewDetachedFromWindow(v: View) {
lifecycleOwner?.onDestroy()
lifecycleOwner = null
}
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index cc1504a..b6577f7 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -120,6 +120,14 @@
return factory.create("ShadeLog", 500, false);
}
+ /** Provides a logging buffer for Shade messages. */
+ @Provides
+ @SysUISingleton
+ @ShadeTouchLog
+ public static LogBuffer provideShadeTouchLogBuffer(LogBufferFactory factory) {
+ return factory.create("ShadeTouchLog", 500, false);
+ }
+
/** Provides a logging buffer for all logs related to managing notification sections. */
@Provides
@SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/ShadeTouchLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/ShadeTouchLog.java
new file mode 100644
index 0000000..b13667e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/ShadeTouchLog.java
@@ -0,0 +1,33 @@
+/*
+ * 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.log.dagger;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import com.android.systemui.log.LogBuffer;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Qualifier;
+
+/** A {@link LogBuffer} for tracking touches in various shade child views. */
+@Qualifier
+@Documented
+@Retention(RUNTIME)
+public @interface ShadeTouchLog {
+}
diff --git a/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt b/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt
index 67a985e..a7ffc5f 100644
--- a/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt
@@ -302,14 +302,14 @@
@Synchronized
override fun dump(pw: PrintWriter, args: Array<out String>) {
- pw.println(HEADER_PREFIX + name)
- pw.println("version $VERSION")
+ pw.append(HEADER_PREFIX).println(name)
+ pw.append("version ").println(VERSION)
lastEvictedValues.values.sortedBy { it.timestamp }.forEach { it.dump(pw) }
for (i in 0 until buffer.size) {
buffer[i].dump(pw)
}
- pw.println(FOOTER_PREFIX + name)
+ pw.append(FOOTER_PREFIX).println(name)
}
/** Dumps an individual [TableChange]. */
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarViewModel.kt
index 35f5a8c..a91917a 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarViewModel.kt
@@ -514,7 +514,7 @@
* Returns true when the down event of the scroll hits within the target box of the thumb.
*/
override fun onScroll(
- eventStart: MotionEvent,
+ eventStart: MotionEvent?,
event: MotionEvent,
distanceX: Float,
distanceY: Float
@@ -528,7 +528,7 @@
* Gestures that include a fling are considered a false gesture on the seek bar.
*/
override fun onFling(
- eventStart: MotionEvent,
+ eventStart: MotionEvent?,
event: MotionEvent,
velocityX: Float,
velocityY: Float
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt
index 207df6b..a1291a4 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt
@@ -149,11 +149,7 @@
// Check if smartspace has explicitly specified whether to re-activate resumable media.
// The default behavior is to trigger if the smartspace data is active.
val shouldTriggerResume =
- if (data.cardAction?.extras?.containsKey(EXTRA_KEY_TRIGGER_RESUME) == true) {
- data.cardAction.extras.getBoolean(EXTRA_KEY_TRIGGER_RESUME, true)
- } else {
- true
- }
+ data.cardAction?.extras?.getBoolean(EXTRA_KEY_TRIGGER_RESUME, true) ?: true
val shouldReactivate =
shouldTriggerResume && !hasActiveMedia() && hasAnyMedia() && data.isActive
@@ -269,9 +265,7 @@
"Cannot create dismiss action click action: extras missing dismiss_intent."
)
} else if (
- dismissIntent.getComponent() != null &&
- dismissIntent.getComponent().getClassName() ==
- EXPORTED_SMARTSPACE_TRAMPOLINE_ACTIVITY_NAME
+ dismissIntent.component?.className == EXPORTED_SMARTSPACE_TRAMPOLINE_ACTIVITY_NAME
) {
// Dismiss the card Smartspace data through Smartspace trampoline activity.
context.startActivity(dismissIntent)
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
index 576eb9e..282a65a 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
@@ -22,6 +22,7 @@
import android.app.Notification.EXTRA_SUBSTITUTE_APP_NAME
import android.app.PendingIntent
import android.app.StatusBarManager
+import android.app.smartspace.SmartspaceAction
import android.app.smartspace.SmartspaceConfig
import android.app.smartspace.SmartspaceManager
import android.app.smartspace.SmartspaceSession
@@ -1623,20 +1624,18 @@
* SmartspaceTarget's data is invalid.
*/
private fun toSmartspaceMediaData(target: SmartspaceTarget): SmartspaceMediaData {
- var dismissIntent: Intent? = null
- if (target.baseAction != null && target.baseAction.extras != null) {
- dismissIntent =
- target.baseAction.extras.getParcelable(EXTRAS_SMARTSPACE_DISMISS_INTENT_KEY)
- as Intent?
- }
+ val baseAction: SmartspaceAction? = target.baseAction
+ val dismissIntent =
+ baseAction?.extras?.getParcelable(EXTRAS_SMARTSPACE_DISMISS_INTENT_KEY) as Intent?
val isActive =
when {
!mediaFlags.isPersistentSsCardEnabled() -> true
- target.baseAction == null -> true
- else ->
- target.baseAction.extras.getString(EXTRA_KEY_TRIGGER_SOURCE) !=
- EXTRA_VALUE_TRIGGER_PERIODIC
+ baseAction == null -> true
+ else -> {
+ val triggerSource = baseAction.extras?.getString(EXTRA_KEY_TRIGGER_SOURCE)
+ triggerSource != EXTRA_VALUE_TRIGGER_PERIODIC
+ }
}
packageName(target)?.let {
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaSessionBasedFilter.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaSessionBasedFilter.kt
index d6f941d..6a8ffb7 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaSessionBasedFilter.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaSessionBasedFilter.kt
@@ -65,7 +65,7 @@
private val sessionListener =
object : MediaSessionManager.OnActiveSessionsChangedListener {
- override fun onActiveSessionsChanged(controllers: List<MediaController>) {
+ override fun onActiveSessionsChanged(controllers: List<MediaController>?) {
handleControllersChanged(controllers)
}
}
@@ -190,16 +190,18 @@
}
}
- private fun handleControllersChanged(controllers: List<MediaController>) {
+ private fun handleControllersChanged(controllers: List<MediaController>?) {
packageControllers.clear()
- controllers.forEach { controller ->
+ controllers?.forEach { controller ->
packageControllers.get(controller.packageName)?.let { tokens -> tokens.add(controller) }
?: run {
val tokens = mutableListOf(controller)
packageControllers.put(controller.packageName, tokens)
}
}
- tokensWithNotifications.retainAll(controllers.map { TokenId(it.sessionToken) })
+ controllers?.map { TokenId(it.sessionToken) }?.let {
+ tokensWithNotifications.retainAll(it)
+ }
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/IlluminationDrawable.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/IlluminationDrawable.kt
index b46ebb2..b9cc772 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/IlluminationDrawable.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/IlluminationDrawable.kt
@@ -195,7 +195,7 @@
}
addListener(
object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator?) {
+ override fun onAnimationEnd(animation: Animator) {
backgroundAnimation = null
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/LightSourceDrawable.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/LightSourceDrawable.kt
index 937a618..646d1d0 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/LightSourceDrawable.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/LightSourceDrawable.kt
@@ -98,11 +98,11 @@
addListener(
object : AnimatorListenerAdapter() {
var cancelled = false
- override fun onAnimationCancel(animation: Animator?) {
+ override fun onAnimationCancel(animation: Animator) {
cancelled = true
}
- override fun onAnimationEnd(animation: Animator?) {
+ override fun onAnimationEnd(animation: Animator) {
if (cancelled) {
return
}
@@ -226,7 +226,7 @@
)
addListener(
object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator?) {
+ override fun onAnimationEnd(animation: Animator) {
rippleData.progress = 0f
rippleAnimation = null
invalidateSelf()
@@ -270,11 +270,8 @@
return bounds
}
- override fun onStateChange(stateSet: IntArray?): Boolean {
+ override fun onStateChange(stateSet: IntArray): Boolean {
val changed = super.onStateChange(stateSet)
- if (stateSet == null) {
- return changed
- }
val wasPressed = pressed
var enabled = false
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselScrollHandler.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselScrollHandler.kt
index 1ace316..ce50a11 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselScrollHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselScrollHandler.kt
@@ -127,19 +127,19 @@
object : GestureDetector.SimpleOnGestureListener() {
override fun onFling(
eStart: MotionEvent?,
- eCurrent: MotionEvent?,
+ eCurrent: MotionEvent,
vX: Float,
vY: Float
) = onFling(vX, vY)
override fun onScroll(
down: MotionEvent?,
- lastMotion: MotionEvent?,
+ lastMotion: MotionEvent,
distanceX: Float,
distanceY: Float
- ) = onScroll(down!!, lastMotion!!, distanceX)
+ ) = onScroll(down!!, lastMotion, distanceX)
- override fun onDown(e: MotionEvent?): Boolean {
+ override fun onDown(e: MotionEvent): Boolean {
if (falsingProtectionNeeded) {
falsingCollector.onNotificationStartDismissing()
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
index fe8ebaf..c1c757e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
@@ -180,20 +180,20 @@
object : AnimatorListenerAdapter() {
private var cancelled: Boolean = false
- override fun onAnimationCancel(animation: Animator?) {
+ override fun onAnimationCancel(animation: Animator) {
cancelled = true
animationPending = false
rootView?.removeCallbacks(startAnimation)
}
- override fun onAnimationEnd(animation: Animator?) {
+ override fun onAnimationEnd(animation: Animator) {
isCrossFadeAnimatorRunning = false
if (!cancelled) {
applyTargetStateIfNotAnimating()
}
}
- override fun onAnimationStart(animation: Animator?) {
+ override fun onAnimationStart(animation: Animator) {
cancelled = false
animationPending = false
}
@@ -606,7 +606,7 @@
val viewHost = UniqueObjectHostView(context)
viewHost.addOnAttachStateChangeListener(
object : View.OnAttachStateChangeListener {
- override fun onViewAttachedToWindow(p0: View?) {
+ override fun onViewAttachedToWindow(p0: View) {
if (rootOverlay == null) {
rootView = viewHost.viewRootImpl.view
rootOverlay = (rootView!!.overlay as ViewGroupOverlay)
@@ -614,7 +614,7 @@
viewHost.removeOnAttachStateChangeListener(this)
}
- override fun onViewDetachedFromWindow(p0: View?) {}
+ override fun onViewDetachedFromWindow(p0: View) {}
}
)
return viewHost
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHost.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHost.kt
index be570b4..631a0b8 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHost.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHost.kt
@@ -144,12 +144,12 @@
setListeningToMediaData(true)
hostView.addOnAttachStateChangeListener(
object : OnAttachStateChangeListener {
- override fun onViewAttachedToWindow(v: View?) {
+ override fun onViewAttachedToWindow(v: View) {
setListeningToMediaData(true)
updateViewVisibility()
}
- override fun onViewDetachedFromWindow(v: View?) {
+ override fun onViewDetachedFromWindow(v: View) {
setListeningToMediaData(false)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/SquigglyProgress.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/SquigglyProgress.kt
index 583c626..16dfc21 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/SquigglyProgress.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/SquigglyProgress.kt
@@ -117,7 +117,7 @@
}
addListener(
object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator?) {
+ override fun onAnimationEnd(animation: Animator) {
heightAnimator = null
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
index 7712690..3a1d8b0 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
@@ -296,7 +296,10 @@
@Override
public void stop() {
- if (isBroadcastSupported() && mIsLeBroadcastCallbackRegistered) {
+ // unregister broadcast callback should only depend on profile and registered flag
+ // rather than remote device or broadcast state
+ // otherwise it might have risks of leaking registered callback handle
+ if (mMediaOutputController.isBroadcastSupported() && mIsLeBroadcastCallbackRegistered) {
mMediaOutputController.unregisterLeBroadcastServiceCallback(mBroadcastCallback);
mIsLeBroadcastCallbackRegistered = false;
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java
index f3865f5..e5a6bb5 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java
@@ -104,11 +104,16 @@
@Override
public boolean isBroadcastSupported() {
boolean isBluetoothLeDevice = false;
+ boolean isBroadcastEnabled = false;
if (FeatureFlagUtils.isEnabled(mContext,
FeatureFlagUtils.SETTINGS_NEED_CONNECTED_BLE_DEVICE_FOR_BROADCAST)) {
if (mMediaOutputController.getCurrentConnectedMediaDevice() != null) {
isBluetoothLeDevice = mMediaOutputController.isBluetoothLeDevice(
mMediaOutputController.getCurrentConnectedMediaDevice());
+ // if broadcast is active, broadcast should be considered as supported
+ // there could be a valid case that broadcast is ongoing
+ // without active LEA device connected
+ isBroadcastEnabled = mMediaOutputController.isBluetoothLeBroadcastEnabled();
}
} else {
// To decouple LE Audio Broadcast and Unicast, it always displays the button when there
@@ -116,7 +121,8 @@
isBluetoothLeDevice = true;
}
- return mMediaOutputController.isBroadcastSupported() && isBluetoothLeDevice;
+ return mMediaOutputController.isBroadcastSupported()
+ && (isBluetoothLeDevice || isBroadcastEnabled);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputMetricLogger.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputMetricLogger.java
index 39d4e6e..412d1a3 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputMetricLogger.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputMetricLogger.java
@@ -56,6 +56,7 @@
* Update the endpoints of a content switching operation.
* This method should be called before a switching operation, so the metric logger can track
* source and target devices.
+ *
* @param source the current connected media device
* @param target the target media device for content switching to
*/
@@ -72,37 +73,9 @@
/**
* Do the metric logging of content switching success.
+ *
* @param selectedDeviceType string representation of the target media device
- * @param deviceList media device list for device count updating
- */
- public void logOutputSuccess(String selectedDeviceType, List<MediaDevice> deviceList) {
- if (DEBUG) {
- Log.d(TAG, "logOutputSuccess - selected device: " + selectedDeviceType);
- }
-
- if (mSourceDevice == null && mTargetDevice == null) {
- return;
- }
-
- updateLoggingDeviceCount(deviceList);
-
- SysUiStatsLog.write(
- SysUiStatsLog.MEDIAOUTPUT_OP_SWITCH_REPORTED,
- getLoggingDeviceType(mSourceDevice, true),
- getLoggingDeviceType(mTargetDevice, false),
- SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__RESULT__OK,
- SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__SUBRESULT__NO_ERROR,
- getLoggingPackageName(),
- mWiredDeviceCount,
- mConnectedBluetoothDeviceCount,
- mRemoteDeviceCount,
- mAppliedDeviceCountWithinRemoteGroup);
- }
-
- /**
- * Do the metric logging of content switching success.
- * @param selectedDeviceType string representation of the target media device
- * @param deviceItemList media item list for device count updating
+ * @param deviceItemList media item list for device count updating
*/
public void logOutputItemSuccess(String selectedDeviceType, List<MediaItem> deviceItemList) {
if (DEBUG) {
@@ -125,11 +98,14 @@
mWiredDeviceCount,
mConnectedBluetoothDeviceCount,
mRemoteDeviceCount,
- mAppliedDeviceCountWithinRemoteGroup);
+ mAppliedDeviceCountWithinRemoteGroup,
+ mTargetDevice.isSuggestedDevice(),
+ mTargetDevice.hasOngoingSession());
}
/**
* Do the metric logging of volume adjustment.
+ *
* @param source the device been adjusted
*/
public void logInteractionAdjustVolume(MediaDevice source) {
@@ -141,7 +117,8 @@
SysUiStatsLog.MEDIAOUTPUT_OP_INTERACTION_REPORT,
SysUiStatsLog.MEDIA_OUTPUT_OP_INTERACTION_REPORTED__INTERACTION_TYPE__ADJUST_VOLUME,
getInteractionDeviceType(source),
- getLoggingPackageName());
+ getLoggingPackageName(),
+ source.isSuggestedDevice());
}
/**
@@ -156,7 +133,8 @@
SysUiStatsLog.MEDIAOUTPUT_OP_INTERACTION_REPORT,
SysUiStatsLog.MEDIA_OUTPUT_OP_INTERACTION_REPORTED__INTERACTION_TYPE__STOP_CASTING,
SysUiStatsLog.MEDIA_OUTPUT_OP_INTERACTION_REPORTED__TARGET__UNKNOWN_TYPE,
- getLoggingPackageName());
+ getLoggingPackageName(),
+ /*isSuggestedDevice = */false);
}
/**
@@ -171,42 +149,15 @@
SysUiStatsLog.MEDIAOUTPUT_OP_INTERACTION_REPORT,
SysUiStatsLog.MEDIA_OUTPUT_OP_INTERACTION_REPORTED__INTERACTION_TYPE__EXPANSION,
getInteractionDeviceType(source),
- getLoggingPackageName());
- }
-
- /**
- * Do the metric logging of content switching failure.
- * @param deviceList media device list for device count updating
- * @param reason the reason of content switching failure
- */
- public void logOutputFailure(List<MediaDevice> deviceList, int reason) {
- if (DEBUG) {
- Log.e(TAG, "logRequestFailed - " + reason);
- }
-
- if (mSourceDevice == null && mTargetDevice == null) {
- return;
- }
-
- updateLoggingDeviceCount(deviceList);
-
- SysUiStatsLog.write(
- SysUiStatsLog.MEDIAOUTPUT_OP_SWITCH_REPORTED,
- getLoggingDeviceType(mSourceDevice, true),
- getLoggingDeviceType(mTargetDevice, false),
- SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__RESULT__ERROR,
- getLoggingSwitchOpSubResult(reason),
getLoggingPackageName(),
- mWiredDeviceCount,
- mConnectedBluetoothDeviceCount,
- mRemoteDeviceCount,
- mAppliedDeviceCountWithinRemoteGroup);
+ source.isSuggestedDevice());
}
/**
* Do the metric logging of content switching failure.
+ *
* @param deviceItemList media item list for device count updating
- * @param reason the reason of content switching failure
+ * @param reason the reason of content switching failure
*/
public void logOutputItemFailure(List<MediaItem> deviceItemList, int reason) {
if (DEBUG) {
@@ -229,7 +180,9 @@
mWiredDeviceCount,
mConnectedBluetoothDeviceCount,
mRemoteDeviceCount,
- mAppliedDeviceCountWithinRemoteGroup);
+ mAppliedDeviceCountWithinRemoteGroup,
+ mTargetDevice.isSuggestedDevice(),
+ mTargetDevice.hasOngoingSession());
}
private void updateLoggingDeviceCount(List<MediaDevice> deviceList) {
@@ -266,7 +219,7 @@
mWiredDeviceCount = mConnectedBluetoothDeviceCount = mRemoteDeviceCount = 0;
mAppliedDeviceCountWithinRemoteGroup = 0;
- for (MediaItem mediaItem: deviceItemList) {
+ for (MediaItem mediaItem : deviceItemList) {
if (mediaItem.getMediaDevice().isPresent()
&& mediaItem.getMediaDevice().get().isConnected()) {
switch (mediaItem.getMediaDevice().get().getDeviceType()) {
@@ -326,6 +279,10 @@
return isSourceDevice
? SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__SOURCE__REMOTE_GROUP
: SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__TARGET__REMOTE_GROUP;
+ case MediaDevice.MediaDeviceType.TYPE_REMOTE_AUDIO_VIDEO_RECEIVER:
+ return isSourceDevice
+ ? SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__SOURCE__AVR
+ : SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__TARGET__AVR;
default:
return isSourceDevice
? SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__SOURCE__UNKNOWN_TYPE
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
index bbd3d33..da8e106 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
@@ -201,13 +201,13 @@
}
override fun updateView(newInfo: ChipReceiverInfo, currentView: ViewGroup) {
- val packageName = newInfo.routeInfo.clientPackageName
+ val packageName: String? = newInfo.routeInfo.clientPackageName
var iconInfo = MediaTttUtils.getIconInfoFromPackageName(
context,
packageName,
isReceiver = true,
) {
- logger.logPackageNotFound(packageName)
+ packageName?.let { logger.logPackageNotFound(it) }
}
if (newInfo.appNameOverride != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverRippleController.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverRippleController.kt
index 5013802..fbf7e25 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverRippleController.kt
@@ -68,9 +68,9 @@
)
rippleView.addOnAttachStateChangeListener(
object : View.OnAttachStateChangeListener {
- override fun onViewDetachedFromWindow(view: View?) {}
+ override fun onViewDetachedFromWindow(view: View) {}
- override fun onViewAttachedToWindow(view: View?) {
+ override fun onViewAttachedToWindow(view: View) {
if (view == null) {
return
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt
index 0b0535d..35018f1 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt
@@ -54,7 +54,7 @@
// Reset all listeners to animator.
animator.removeAllListeners()
animator.addListener(object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator?) {
+ override fun onAnimationEnd(animation: Animator) {
onAnimationEnd?.run()
isStarted = false
}
@@ -86,7 +86,7 @@
invalidate()
}
animator.addListener(object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator?) {
+ override fun onAnimationEnd(animation: Animator) {
animation?.let { visibility = GONE }
onAnimationEnd?.run()
isStarted = false
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt
index f75f8b9..87d0098 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt
@@ -162,7 +162,7 @@
logger: MediaTttSenderLogger,
instanceId: InstanceId,
): ChipbarInfo {
- val packageName = routeInfo.clientPackageName
+ val packageName = checkNotNull(routeInfo.clientPackageName)
val otherDeviceName =
if (routeInfo.name.isBlank()) {
context.getString(R.string.media_ttt_default_device_type)
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt
index c816446..64de9bd 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt
@@ -88,7 +88,7 @@
.inflate(R.layout.media_projection_recent_tasks, parent, /* attachToRoot= */ false)
as ViewGroup
- val container = recentsRoot.findViewById<View>(R.id.media_projection_recent_tasks_container)
+ val container = recentsRoot.requireViewById<View>(R.id.media_projection_recent_tasks_container)
container.setTaskHeightSize()
val progress = recentsRoot.requireViewById<View>(R.id.media_projection_recent_tasks_loader)
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepository.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepository.kt
index 38d4e69..6480a47 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepository.kt
@@ -81,8 +81,8 @@
return MediaProjectionState.EntireScreen
}
val matchingTask =
- tasksRepository.findRunningTaskFromWindowContainerToken(session.tokenToRecord)
- ?: return MediaProjectionState.EntireScreen
+ tasksRepository.findRunningTaskFromWindowContainerToken(
+ checkNotNull(session.tokenToRecord)) ?: return MediaProjectionState.EntireScreen
return MediaProjectionState.SingleTask(matchingTask)
}
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskRoleManagerExt.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskRoleManagerExt.kt
index 4d30634..63d4634 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskRoleManagerExt.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskRoleManagerExt.kt
@@ -77,8 +77,13 @@
.build()
}
- private fun PackageManager.getApplicationLabel(packageName: String?): String? =
- runCatching { getApplicationInfo(packageName, /* flags= */ 0)!! }
+ private fun PackageManager.getApplicationLabel(packageName: String?): String? {
+ if (packageName == null) {
+ return null
+ }
+
+ return runCatching { getApplicationInfo(packageName, /* flags= */ 0)!! }
.getOrNull()
?.let { info -> getApplicationLabel(info).toString() }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/people/ui/view/PeopleViewBinder.kt b/packages/SystemUI/src/com/android/systemui/people/ui/view/PeopleViewBinder.kt
index 5f338c3..46c8d35 100644
--- a/packages/SystemUI/src/com/android/systemui/people/ui/view/PeopleViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/people/ui/view/PeopleViewBinder.kt
@@ -132,13 +132,13 @@
LayoutInflater.from(context)
.inflate(R.layout.people_space_activity_no_conversations, /* root= */ view)
- noConversationsView.findViewById<View>(R.id.got_it_button).setOnClickListener {
+ noConversationsView.requireViewById<View>(R.id.got_it_button).setOnClickListener {
onGotItClicked()
}
// The Tile preview has colorBackground as its background. Change it so it's different than
// the activity's background.
- val item = noConversationsView.findViewById<LinearLayout>(android.R.id.background)
+ val item = noConversationsView.requireViewById<LinearLayout>(android.R.id.background)
val shape = item.background as GradientDrawable
val ta =
context.theme.obtainStyledAttributes(
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogV2.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogV2.kt
index f4aa27d..c202f14 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogV2.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogV2.kt
@@ -192,7 +192,7 @@
return null
}
val closeAppButton =
- window.layoutInflater.inflate(
+ checkNotNull(window).layoutInflater.inflate(
R.layout.privacy_dialog_card_button,
expandedLayout,
false
@@ -248,7 +248,7 @@
private fun configureManageButton(element: PrivacyElement, expandedLayout: ViewGroup): View {
val manageButton =
- window.layoutInflater.inflate(
+ checkNotNull(window).layoutInflater.inflate(
R.layout.privacy_dialog_card_button,
expandedLayout,
false
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
index 1afc885..d2eac45 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
@@ -23,12 +23,14 @@
import android.graphics.Path;
import android.graphics.PointF;
import android.util.AttributeSet;
+import android.view.MotionEvent;
import android.view.View;
import android.widget.FrameLayout;
import com.android.systemui.Dumpable;
import com.android.systemui.R;
import com.android.systemui.qs.customize.QSCustomizer;
+import com.android.systemui.shade.TouchLogger;
import com.android.systemui.util.LargeScreenUtils;
import java.io.PrintWriter;
@@ -129,6 +131,11 @@
}
@Override
+ public boolean dispatchTouchEvent(MotionEvent ev) {
+ return TouchLogger.logDispatchTouch("QS", ev, super.dispatchTouchEvent(ev));
+ }
+
+ @Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
updateExpansion();
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 cf7abdd..76d9b03 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
@@ -17,21 +17,22 @@
package com.android.systemui.scene.domain.interactor
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.scene.data.repository.SceneContainerRepository
import com.android.systemui.scene.shared.logger.SceneLogger
import com.android.systemui.scene.shared.model.ObservableTransitionState
import com.android.systemui.scene.shared.model.RemoteUserInput
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
-import com.android.systemui.util.kotlin.pairwise
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.distinctUntilChanged
import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.mapNotNull
+import kotlinx.coroutines.flow.stateIn
/**
* Generic business logic and app state accessors for the scene framework.
@@ -44,6 +45,7 @@
class SceneInteractor
@Inject
constructor(
+ @Application applicationScope: CoroutineScope,
private val repository: SceneContainerRepository,
private val logger: SceneLogger,
) {
@@ -88,6 +90,22 @@
*/
val transitionState: StateFlow<ObservableTransitionState> = repository.transitionState
+ /**
+ * The key of the scene that the UI is currently transitioning to or `null` if there is no
+ * active transition at the moment.
+ *
+ * This is a convenience wrapper around [transitionState], meant for flow-challenged consumers
+ * like Java code.
+ */
+ val transitioningTo: StateFlow<SceneKey?> =
+ transitionState
+ .map { state -> (state as? ObservableTransitionState.Transition)?.toScene }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = null,
+ )
+
/** Whether the scene container is visible. */
val isVisible: StateFlow<Boolean> = repository.isVisible
@@ -142,21 +160,6 @@
repository.setTransitionState(transitionState)
}
- /**
- * Returns a stream of events that emits one [Unit] every time the framework transitions from
- * [from] to [to].
- */
- fun finishedSceneTransitions(from: SceneKey, to: SceneKey): Flow<Unit> {
- return transitionState
- .mapNotNull { it as? ObservableTransitionState.Idle }
- .map { idleState -> idleState.scene }
- .distinctUntilChanged()
- .pairwise()
- .mapNotNull { (previousSceneKey, currentSceneKey) ->
- Unit.takeIf { previousSceneKey == from && currentSceneKey == to }
- }
- }
-
/** Handles a remote user input. */
fun onRemoteUserInput(input: RemoteUserInput) {
_remoteUserInput.value = input
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
index afefccb..1747099 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
@@ -178,12 +178,24 @@
}
WakefulnessState.STARTING_TO_WAKE -> {
val authMethod = authenticationInteractor.getAuthenticationMethod()
- if (authMethod == AuthenticationMethodModel.None) {
- switchToScene(
- targetSceneKey = SceneKey.Gone,
- loggingReason =
- "device is starting to wake up while auth method is None",
- )
+ val isUnlocked = authenticationInteractor.isUnlocked.value
+ when {
+ authMethod == AuthenticationMethodModel.None -> {
+ switchToScene(
+ targetSceneKey = SceneKey.Gone,
+ loggingReason =
+ "device is starting to wake up while auth method is" +
+ " none",
+ )
+ }
+ authMethod.isSecure && isUnlocked -> {
+ switchToScene(
+ targetSceneKey = SceneKey.Gone,
+ loggingReason =
+ "device is starting to wake up while unlocked with a" +
+ " secure auth method",
+ )
+ }
}
}
else -> Unit
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/BaseScreenSharePermissionDialog.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/BaseScreenSharePermissionDialog.kt
index b340043..23894a3 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/BaseScreenSharePermissionDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/BaseScreenSharePermissionDialog.kt
@@ -50,22 +50,20 @@
public override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- window.apply {
- addPrivateFlags(WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS)
- setGravity(Gravity.CENTER)
- }
+ window?.addPrivateFlags(WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS)
+ window?.setGravity(Gravity.CENTER)
setContentView(R.layout.screen_share_dialog)
- dialogTitle = findViewById(R.id.screen_share_dialog_title)
- warning = findViewById(R.id.text_warning)
- startButton = findViewById(android.R.id.button1)
- cancelButton = findViewById(android.R.id.button2)
+ dialogTitle = requireViewById(R.id.screen_share_dialog_title)
+ warning = requireViewById(R.id.text_warning)
+ startButton = requireViewById(android.R.id.button1)
+ cancelButton = requireViewById(android.R.id.button2)
updateIcon()
initScreenShareOptions()
createOptionsView(getOptionsViewLayoutId())
}
private fun updateIcon() {
- val icon = findViewById<ImageView>(R.id.screen_share_dialog_icon)
+ val icon = requireViewById<ImageView>(R.id.screen_share_dialog_icon)
if (dialogIconTint != null) {
icon.setColorFilter(context.getColor(dialogIconTint))
}
@@ -92,7 +90,7 @@
options
)
adapter.setDropDownViewResource(R.layout.screen_share_dialog_spinner_item_text)
- screenShareModeSpinner = findViewById(R.id.screen_share_mode_spinner)
+ screenShareModeSpinner = requireViewById(R.id.screen_share_mode_spinner)
screenShareModeSpinner.adapter = adapter
screenShareModeSpinner.onItemSelectedListener = this
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt
index 604d449..e8683fb 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt
@@ -100,11 +100,11 @@
@LayoutRes override fun getOptionsViewLayoutId(): Int = R.layout.screen_record_options
private fun initRecordOptionsView() {
- audioSwitch = findViewById(R.id.screenrecord_audio_switch)
- tapsSwitch = findViewById(R.id.screenrecord_taps_switch)
- tapsView = findViewById(R.id.show_taps)
+ audioSwitch = requireViewById(R.id.screenrecord_audio_switch)
+ tapsSwitch = requireViewById(R.id.screenrecord_taps_switch)
+ tapsView = requireViewById(R.id.show_taps)
updateTapsViewVisibility()
- options = findViewById(R.id.screen_recording_options)
+ options = requireViewById(R.id.screen_recording_options)
val a: ArrayAdapter<*> =
ScreenRecordingAdapter(context, android.R.layout.simple_spinner_dropdown_item, MODES)
a.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt
index ecd4568..aa6bfc3 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt
@@ -83,7 +83,7 @@
if (overrideTransition) {
val runner = RemoteAnimationAdapter(SCREENSHOT_REMOTE_RUNNER, 0, 0)
try {
- WindowManagerGlobal.getWindowManagerService()
+ checkNotNull(WindowManagerGlobal.getWindowManagerService())
.overridePendingAppTransitionRemote(runner, displayTracker.defaultDisplayId)
} catch (e: Exception) {
Log.e(TAG, "Error overriding screenshot app transition", e)
diff --git a/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java b/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java
index fc89a9e..f4d19dc 100644
--- a/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java
+++ b/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java
@@ -30,6 +30,7 @@
import android.graphics.drawable.Drawable;
import android.os.Looper;
import android.util.AttributeSet;
+import android.view.MotionEvent;
import android.view.View;
import androidx.annotation.Nullable;
@@ -38,6 +39,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.colorextraction.ColorExtractor;
+import com.android.systemui.shade.TouchLogger;
import com.android.systemui.util.LargeScreenUtils;
import java.util.concurrent.Executor;
@@ -59,6 +61,7 @@
private float mViewAlpha = 1.0f;
private Drawable mDrawable;
private PorterDuffColorFilter mColorFilter;
+ private String mScrimName;
private int mTintColor;
private boolean mBlendWithMainColor = true;
private Runnable mChangeRunnable;
@@ -336,6 +339,15 @@
}
}
+ public void setScrimName(String scrimName) {
+ mScrimName = scrimName;
+ }
+
+ @Override
+ public boolean dispatchTouchEvent(MotionEvent ev) {
+ return TouchLogger.logDispatchTouch(mScrimName, ev, super.dispatchTouchEvent(ev));
+ }
+
/**
* The position of the bottom of the scrim, used for clipping.
* @see #enableBottomEdgeConcave(boolean)
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NPVCDownEventState.kt b/packages/SystemUI/src/com/android/systemui/shade/NPVCDownEventState.kt
index 6143308..4644d41 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NPVCDownEventState.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/NPVCDownEventState.kt
@@ -108,7 +108,7 @@
* @see NPVCDownEventState.asStringList
*/
fun toList(): List<Row> {
- return buffer.asSequence().map { it.asStringList }.toList()
+ return buffer.map { it.asStringList }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelView.java
index af3cc86..c501d88 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelView.java
@@ -106,6 +106,11 @@
}
@Override
+ public boolean dispatchTouchEvent(MotionEvent ev) {
+ return TouchLogger.logDispatchTouch("NPV", ev, super.dispatchTouchEvent(ev));
+ }
+
+ @Override
public void dispatchConfigurationChanged(Configuration newConfig) {
super.dispatchConfigurationChanged(newConfig);
mOnConfigurationChangedListener.onConfigurationChanged(newConfig);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 35fd98c..132cd61 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -1117,7 +1117,8 @@
collectFlow(mView, mKeyguardTransitionInteractor.getDreamingToLockscreenTransition(),
mDreamingToLockscreenTransition, mMainDispatcher);
collectFlow(mView, mDreamingToLockscreenTransitionViewModel.getLockscreenAlpha(),
- setTransitionAlpha(mNotificationStackScrollLayoutController), mMainDispatcher);
+ setDreamLockscreenTransitionAlpha(mNotificationStackScrollLayoutController),
+ mMainDispatcher);
collectFlow(mView, mDreamingToLockscreenTransitionViewModel.lockscreenTranslationY(
mDreamingToLockscreenTransitionTranslationY),
setTransitionY(mNotificationStackScrollLayoutController), mMainDispatcher);
@@ -1153,7 +1154,8 @@
collectFlow(mView, mKeyguardTransitionInteractor.getLockscreenToDreamingTransition(),
mLockscreenToDreamingTransition, mMainDispatcher);
collectFlow(mView, mLockscreenToDreamingTransitionViewModel.getLockscreenAlpha(),
- setTransitionAlpha(mNotificationStackScrollLayoutController), mMainDispatcher);
+ setDreamLockscreenTransitionAlpha(mNotificationStackScrollLayoutController),
+ mMainDispatcher);
collectFlow(mView, mLockscreenToDreamingTransitionViewModel.lockscreenTranslationY(
mLockscreenToDreamingTransitionTranslationY),
setTransitionY(mNotificationStackScrollLayoutController), mMainDispatcher);
@@ -2129,6 +2131,7 @@
}
updateExpansionAndVisibility();
mNotificationStackScrollLayoutController.setPanelFlinging(false);
+ mShadeLog.d("onFlingEnd called"); // TODO(b/277909752): remove log when bug is fixed
// expandImmediate should be always reset at the end of animation
mQsController.setExpandImmediate(false);
}
@@ -2664,6 +2667,11 @@
setListening(true);
}
if (mBarState != SHADE) {
+ // TODO(b/277909752): remove below logs when bug is fixed
+ mShadeLog.d("onExpandingFinished called");
+ if (mSplitShadeEnabled && !mQsController.getExpanded()) {
+ mShadeLog.d("onExpandingFinished called before QS got expanded");
+ }
// updating qsExpandImmediate is done in onPanelStateChanged for unlocked shade but
// on keyguard panel state is always OPEN so we need to have that extra update
mQsController.setExpandImmediate(false);
@@ -3447,11 +3455,13 @@
ipw.print("mIgnoreXTouchSlop="); ipw.println(mIgnoreXTouchSlop);
ipw.print("mExpandLatencyTracking="); ipw.println(mExpandLatencyTracking);
ipw.println("gestureExclusionRect:" + calculateGestureExclusionRect());
+ Trace.beginSection("Table<DownEvents>");
new DumpsysTableLogger(
TAG,
NPVCDownEventState.TABLE_HEADERS,
mLastDownEvents.toList()
).printTableData(ipw);
+ Trace.endSection();
}
@Override
@@ -4714,6 +4724,7 @@
}
private void onPanelStateChanged(@PanelState int state) {
+ mShadeLog.logPanelStateChanged(state);
mQsController.updateExpansionEnabledAmbient();
if (state == STATE_OPEN && mCurrentPanelState != state) {
@@ -4740,6 +4751,16 @@
mCurrentPanelState = state;
}
+ private Consumer<Float> setDreamLockscreenTransitionAlpha(
+ NotificationStackScrollLayoutController stackScroller) {
+ return (Float alpha) -> {
+ // Also animate the status bar's alpha during transitions between the lockscreen and
+ // dreams.
+ mKeyguardStatusBarViewController.setAlpha(alpha);
+ setTransitionAlpha(stackScroller).accept(alpha);
+ };
+ }
+
private Consumer<Float> setTransitionAlpha(
NotificationStackScrollLayoutController stackScroller) {
return (Float alpha) -> {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
index 1f401fb..d2b62eb 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
@@ -840,13 +840,17 @@
pw.println(" mDeferWindowLayoutParams=" + mDeferWindowLayoutParams);
pw.println(mCurrentState);
if (mWindowRootView != null && mWindowRootView.getViewRootImpl() != null) {
+ Trace.beginSection("mWindowRootView.dump()");
mWindowRootView.getViewRootImpl().dump(" ", pw);
+ Trace.endSection();
}
+ Trace.beginSection("Table<State>");
new DumpsysTableLogger(
TAG,
NotificationShadeWindowState.TABLE_HEADERS,
mStateBuffer.toList()
).printTableData(pw);
+ Trace.endSection();
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt
index d252943..e3010ca 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt
@@ -170,7 +170,7 @@
* @see [NotificationShadeWindowState.asStringList]
*/
fun toList(): List<Row> {
- return buffer.asSequence().map { it.asStringList }.toList()
+ return buffer.map { it.asStringList }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java
index a9c4aeb..f9b4e67 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java
@@ -107,6 +107,8 @@
result = result != null ? result : super.dispatchTouchEvent(ev);
+ TouchLogger.logDispatchTouch(TAG, ev, result);
+
mInteractionEventHandler.dispatchTouchEventComplete();
return result;
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index 18e9644..832a25b 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -91,6 +91,7 @@
private final NotificationStackScrollLayoutController mNotificationStackScrollLayoutController;
private final LockscreenShadeTransitionController mLockscreenShadeTransitionController;
private final LockIconViewController mLockIconViewController;
+ private final ShadeLogger mShadeLogger;
private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
private final StatusBarWindowStateController mStatusBarWindowStateController;
private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
@@ -151,6 +152,7 @@
KeyguardUnlockAnimationController keyguardUnlockAnimationController,
NotificationInsetsController notificationInsetsController,
AmbientState ambientState,
+ ShadeLogger shadeLogger,
PulsingGestureListener pulsingGestureListener,
LockscreenHostedDreamGestureListener lockscreenHostedDreamGestureListener,
KeyguardBouncerViewModel keyguardBouncerViewModel,
@@ -176,6 +178,7 @@
mStatusBarWindowStateController = statusBarWindowStateController;
mLockIconViewController = lockIconViewController;
mBackActionInteractor = backActionInteractor;
+ mShadeLogger = shadeLogger;
mLockIconViewController.init();
mService = centralSurfaces;
mPowerInteractor = powerInteractor;
@@ -223,6 +226,13 @@
return mView.findViewById(R.id.keyguard_message_area);
}
+ private Boolean logDownDispatch(MotionEvent ev, String msg, Boolean result) {
+ if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+ mShadeLogger.logShadeWindowDispatch(ev, msg, result);
+ }
+ return result;
+ }
+
/** Inflates the {@link R.layout#status_bar_expanded} layout and sets it up. */
public void setupExpandedStatusBar() {
mStackScrollLayout = mView.findViewById(R.id.notification_stack_scroller);
@@ -237,8 +247,8 @@
@Override
public Boolean handleDispatchTouchEvent(MotionEvent ev) {
if (mStatusBarViewController == null) { // Fix for b/192490822
- Log.w(TAG, "Ignoring touch while statusBarView not yet set.");
- return false;
+ return logDownDispatch(ev,
+ "Ignoring touch while statusBarView not yet set", false);
}
boolean isDown = ev.getActionMasked() == MotionEvent.ACTION_DOWN;
boolean isUp = ev.getActionMasked() == MotionEvent.ACTION_UP;
@@ -250,10 +260,9 @@
}
// Reset manual touch dispatch state here but make sure the UP/CANCEL event still
- // gets
- // delivered.
+ // gets delivered.
if (!isCancel && mService.shouldIgnoreTouch()) {
- return false;
+ return logDownDispatch(ev, "touch ignored by CS", false);
}
if (isDown) {
@@ -265,8 +274,11 @@
mTouchActive = false;
mDownEvent = null;
}
- if (mTouchCancelled || mExpandAnimationRunning) {
- return false;
+ if (mTouchCancelled) {
+ return logDownDispatch(ev, "touch cancelled", false);
+ }
+ if (mExpandAnimationRunning) {
+ return logDownDispatch(ev, "expand animation running", false);
}
if (mKeyguardUnlockAnimationController.isPlayingCannedUnlockAnimation()) {
@@ -280,17 +292,17 @@
}
if (mIsOcclusionTransitionRunning) {
- return false;
+ return logDownDispatch(ev, "occlusion transition running", false);
}
mFalsingCollector.onTouchEvent(ev);
mPulsingWakeupGestureHandler.onTouchEvent(ev);
if (mDreamingWakeupGestureHandler != null
&& mDreamingWakeupGestureHandler.onTouchEvent(ev)) {
- return true;
+ return logDownDispatch(ev, "dream wakeup gesture handled", true);
}
if (mStatusBarKeyguardViewManager.dispatchTouchEvent(ev)) {
- return true;
+ return logDownDispatch(ev, "dispatched to Keyguard", true);
}
if (mBrightnessMirror != null
&& mBrightnessMirror.getVisibility() == View.VISIBLE) {
@@ -298,7 +310,7 @@
// you can't touch anything other than the brightness slider while the mirror is
// showing and the rest of the panel is transparent.
if (ev.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN) {
- return false;
+ return logDownDispatch(ev, "disallowed new pointer", false);
}
}
if (isDown) {
@@ -329,7 +341,9 @@
expandingBelowNotch = true;
}
if (expandingBelowNotch) {
- return mStatusBarViewController.sendTouchToView(ev);
+ return logDownDispatch(ev,
+ "expand below notch. sending touch to status bar",
+ mStatusBarViewController.sendTouchToView(ev));
}
if (!mIsTrackingBarGesture && isDown
@@ -339,9 +353,10 @@
if (mStatusBarViewController.touchIsWithinView(x, y)) {
if (mStatusBarWindowStateController.windowIsShowing()) {
mIsTrackingBarGesture = true;
- return mStatusBarViewController.sendTouchToView(ev);
- } else { // it's hidden or hiding, don't send to notification shade.
- return true;
+ return logDownDispatch(ev, "sending touch to status bar",
+ mStatusBarViewController.sendTouchToView(ev));
+ } else {
+ return logDownDispatch(ev, "hidden or hiding", true);
}
}
} else if (mIsTrackingBarGesture) {
@@ -349,10 +364,10 @@
if (isUp || isCancel) {
mIsTrackingBarGesture = false;
}
- return sendToStatusBar;
+ return logDownDispatch(ev, "sending bar gesture to status bar",
+ sendToStatusBar);
}
-
- return null;
+ return logDownDispatch(ev, "no custom touch dispatch of down event", null);
}
@Override
@@ -364,18 +379,26 @@
public boolean shouldInterceptTouchEvent(MotionEvent ev) {
if (mStatusBarStateController.isDozing() && !mService.isPulsing()
&& !mDockManager.isDocked()) {
- // Capture all touch events in always-on.
+ if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+ mShadeLogger.d("NSWVC: capture all touch events in always-on");
+ }
return true;
}
if (mStatusBarKeyguardViewManager.shouldInterceptTouchEvent(ev)) {
// Don't allow touches to proceed to underlying views if alternate
// bouncer is showing
+ if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+ mShadeLogger.d("NSWVC: alt bouncer showing");
+ }
return true;
}
if (mLockIconViewController.onInterceptTouchEvent(ev)) {
// immediately return true; don't send the touch to the drag down helper
+ if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+ mShadeLogger.d("NSWVC: don't send touch to drag down helper");
+ }
return true;
}
@@ -383,7 +406,13 @@
&& mDragDownHelper.isDragDownEnabled()
&& !mService.isBouncerShowing()
&& !mStatusBarStateController.isDozing()) {
- return mDragDownHelper.onInterceptTouchEvent(ev);
+ boolean result = mDragDownHelper.onInterceptTouchEvent(ev);
+ if (result) {
+ if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+ mShadeLogger.d("NSWVC: drag down helper intercepted");
+ }
+ }
+ return result;
} else {
return false;
}
@@ -495,6 +524,7 @@
}
public void cancelCurrentTouch() {
+ mShadeLogger.d("NSWVC: cancelling current touch");
if (mTouchActive) {
final long now = mClock.uptimeMillis();
final MotionEvent event;
@@ -508,6 +538,7 @@
MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
}
+ Log.w(TAG, "Canceling current touch event (should be very rare)");
mView.dispatchTouchEvent(event);
event.recycle();
mTouchCancelled = true;
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java
index 3b3df50..a4e439b 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java
@@ -22,6 +22,7 @@
import android.graphics.Canvas;
import android.graphics.Rect;
import android.util.AttributeSet;
+import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup.MarginLayoutParams;
import android.view.WindowInsets;
@@ -183,6 +184,12 @@
}
@Override
+ public boolean dispatchTouchEvent(MotionEvent ev) {
+ return TouchLogger.logDispatchTouch("NotificationsQuickSettingsContainer", ev,
+ super.dispatchTouchEvent(ev));
+ }
+
+ @Override
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
if (mIsMigratingNSSL) {
return super.drawChild(canvas, child, drawingTime);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
index baac57c..ac7aeae 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
@@ -787,6 +787,12 @@
/** update Qs height state */
public void setExpansionHeight(float height) {
+ // TODO(b/277909752): remove below log when bug is fixed
+ if (mSplitShadeEnabled && mShadeExpandedFraction == 1.0f && height == 0) {
+ Log.wtf(TAG,
+ "setting QS height to 0 in split shade while shade is open(ing). "
+ + "Value of mExpandImmediate = " + mExpandImmediate);
+ }
int maxHeight = getMaxExpansionHeight();
height = Math.min(Math.max(
height, getMinExpansionHeight()), maxHeight);
@@ -933,7 +939,6 @@
return mShadeExpandedHeight;
}
- @VisibleForTesting
void setExpandImmediate(boolean expandImmediate) {
if (expandImmediate != mExpandImmediate) {
mShadeLog.logQsExpandImmediateChanged(expandImmediate);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
index 22c63817..d7a3392 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
@@ -27,6 +27,8 @@
import com.android.systemui.assist.AssistManager;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.log.LogBuffer;
+import com.android.systemui.log.dagger.ShadeTouchLog;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationPresenter;
@@ -56,6 +58,7 @@
private final CommandQueue mCommandQueue;
private final Executor mMainExecutor;
+ private final LogBuffer mTouchLog;
private final KeyguardStateController mKeyguardStateController;
private final NotificationShadeWindowController mNotificationShadeWindowController;
private final StatusBarStateController mStatusBarStateController;
@@ -79,6 +82,7 @@
public ShadeControllerImpl(
CommandQueue commandQueue,
@Main Executor mainExecutor,
+ @ShadeTouchLog LogBuffer touchLog,
KeyguardStateController keyguardStateController,
StatusBarStateController statusBarStateController,
StatusBarKeyguardViewManager statusBarKeyguardViewManager,
@@ -92,6 +96,7 @@
) {
mCommandQueue = commandQueue;
mMainExecutor = mainExecutor;
+ mTouchLog = touchLog;
mShadeViewControllerLazy = shadeViewControllerLazy;
mStatusBarStateController = statusBarStateController;
mStatusBarWindowController = statusBarWindowController;
@@ -413,6 +418,7 @@
@Override
public void start() {
+ TouchLogger.logTouchesTo(mTouchLog);
getShadeViewController().setTrackingStartedListener(this::runPostCollapseRunnables);
getShadeViewController().setOpenCloseListener(
new OpenCloseListener() {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
index c6cb9c4..bea12de 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
@@ -130,12 +130,12 @@
private lateinit var carrierIconSlots: List<String>
private lateinit var mShadeCarrierGroupController: ShadeCarrierGroupController
- private val batteryIcon: BatteryMeterView = header.findViewById(R.id.batteryRemainingIcon)
- private val clock: Clock = header.findViewById(R.id.clock)
- 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 val batteryIcon: BatteryMeterView = header.requireViewById(R.id.batteryRemainingIcon)
+ private val clock: Clock = header.requireViewById(R.id.clock)
+ private val date: TextView = header.requireViewById(R.id.date)
+ private val iconContainer: StatusIconContainer = header.requireViewById(R.id.statusIcons)
+ private val mShadeCarrierGroup: ShadeCarrierGroup = header.requireViewById(R.id.carrier_group)
+ private val systemIcons: View = header.requireViewById(R.id.shade_header_system_icons)
private var roundedCorners = 0
private var cutout: DisplayCutout? = null
@@ -582,7 +582,7 @@
inner class CustomizerAnimationListener(
private val enteringCustomizing: Boolean,
) : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator?) {
+ override fun onAnimationEnd(animation: Animator) {
super.onAnimationEnd(animation)
header.animate().setListener(null)
if (enteringCustomizing) {
@@ -590,7 +590,7 @@
}
}
- override fun onAnimationStart(animation: Animator?) {
+ override fun onAnimationStart(animation: Animator) {
super.onAnimationStart(animation)
if (!enteringCustomizing) {
customizing = false
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
index 1c30bdd..8d23f5d 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
@@ -79,19 +79,39 @@
fun logMotionEvent(event: MotionEvent, message: String) {
buffer.log(
- TAG,
- LogLevel.VERBOSE,
- {
- str1 = message
- long1 = event.eventTime
- long2 = event.downTime
- int1 = event.action
- int2 = event.classification
- double1 = event.y.toDouble()
- },
- {
- "$str1: eventTime=$long1,downTime=$long2,y=$double1,action=$int1,class=$int2"
- }
+ TAG,
+ LogLevel.VERBOSE,
+ {
+ str1 = message
+ long1 = event.eventTime
+ long2 = event.downTime
+ int1 = event.action
+ int2 = event.classification
+ },
+ {
+ "$str1: eventTime=$long1,downTime=$long2,action=$int1,class=$int2"
+ }
+ )
+ }
+
+ /** Logs motion event dispatch results from NotificationShadeWindowViewController. */
+ fun logShadeWindowDispatch(event: MotionEvent, message: String, result: Boolean?) {
+ buffer.log(
+ TAG,
+ LogLevel.VERBOSE,
+ {
+ str1 = message
+ long1 = event.eventTime
+ long2 = event.downTime
+ },
+ {
+ val prefix = when (result) {
+ true -> "SHADE TOUCH REROUTED"
+ false -> "SHADE TOUCH BLOCKED"
+ null -> "SHADE TOUCH DISPATCHED"
+ }
+ "$prefix: eventTime=$long1,downTime=$long2, reason=$str1"
+ }
)
}
@@ -316,6 +336,17 @@
)
}
+ fun logPanelStateChanged(@PanelState panelState: Int) {
+ buffer.log(
+ TAG,
+ LogLevel.VERBOSE,
+ {
+ str1 = panelState.panelStateToString()
+ },
+ { "New panel State: $str1" }
+ )
+ }
+
fun flingQs(flingType: Int, isClick: Boolean) {
buffer.log(
TAG,
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
index 6e76784..05b1ac6 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
@@ -106,7 +106,7 @@
featureFlags: FeatureFlags,
): NotificationShadeWindowView {
if (featureFlags.isEnabled(Flags.SCENE_CONTAINER)) {
- return root.findViewById(R.id.legacy_window_root)
+ return root.requireViewById(R.id.legacy_window_root)
}
return root as NotificationShadeWindowView?
?: throw IllegalStateException("root view not a NotificationShadeWindowView")
@@ -118,7 +118,7 @@
fun providesNotificationStackScrollLayout(
notificationShadeWindowView: NotificationShadeWindowView,
): NotificationStackScrollLayout {
- return notificationShadeWindowView.findViewById(R.id.notification_stack_scroller)
+ return notificationShadeWindowView.requireViewById(R.id.notification_stack_scroller)
}
@Provides
@@ -153,7 +153,7 @@
fun providesNotificationPanelView(
notificationShadeWindowView: NotificationShadeWindowView,
): NotificationPanelView {
- return notificationShadeWindowView.findViewById(R.id.notification_panel)
+ return notificationShadeWindowView.requireViewById(R.id.notification_panel)
}
/**
@@ -175,7 +175,7 @@
fun providesLightRevealScrim(
notificationShadeWindowView: NotificationShadeWindowView,
): LightRevealScrim {
- return notificationShadeWindowView.findViewById(R.id.light_reveal_scrim)
+ return notificationShadeWindowView.requireViewById(R.id.light_reveal_scrim)
}
@Provides
@@ -183,7 +183,7 @@
fun providesKeyguardRootView(
notificationShadeWindowView: NotificationShadeWindowView,
): KeyguardRootView {
- return notificationShadeWindowView.findViewById(R.id.keyguard_root_view)
+ return notificationShadeWindowView.requireViewById(R.id.keyguard_root_view)
}
@Provides
@@ -191,7 +191,7 @@
fun providesSharedNotificationContainer(
notificationShadeWindowView: NotificationShadeWindowView,
): SharedNotificationContainer {
- return notificationShadeWindowView.findViewById(R.id.shared_notification_container)
+ return notificationShadeWindowView.requireViewById(R.id.shared_notification_container)
}
// TODO(b/277762009): Only allow this view's controller to inject the view. See above.
@@ -200,7 +200,7 @@
fun providesAuthRippleView(
notificationShadeWindowView: NotificationShadeWindowView,
): AuthRippleView? {
- return notificationShadeWindowView.findViewById(R.id.auth_ripple)
+ return notificationShadeWindowView.requireViewById(R.id.auth_ripple)
}
// TODO(b/277762009): Only allow this view's controller to inject the view. See above.
@@ -212,9 +212,9 @@
featureFlags: FeatureFlags
): LockIconView {
if (featureFlags.isEnabled(Flags.MIGRATE_LOCK_ICON)) {
- return keyguardRootView.findViewById(R.id.lock_icon_view)
+ return keyguardRootView.requireViewById(R.id.lock_icon_view)
} else {
- return notificationPanelView.findViewById(R.id.lock_icon_view)
+ return notificationPanelView.requireViewById(R.id.lock_icon_view)
}
}
@@ -224,7 +224,7 @@
fun providesTapAgainView(
notificationPanelView: NotificationPanelView,
): TapAgainView {
- return notificationPanelView.findViewById(R.id.shade_falsing_tap_again)
+ return notificationPanelView.requireViewById(R.id.shade_falsing_tap_again)
}
// TODO(b/277762009): Only allow this view's controller to inject the view. See above.
@@ -233,7 +233,7 @@
fun providesNotificationsQuickSettingsContainer(
notificationShadeWindowView: NotificationShadeWindowView,
): NotificationsQuickSettingsContainer {
- return notificationShadeWindowView.findViewById(R.id.notification_container_parent)
+ return notificationShadeWindowView.requireViewById(R.id.notification_container_parent)
}
// TODO(b/277762009): Only allow this view's controller to inject the view. See above.
@@ -243,7 +243,7 @@
fun providesShadeHeaderView(
notificationShadeWindowView: NotificationShadeWindowView,
): MotionLayout {
- val stub = notificationShadeWindowView.findViewById<ViewStub>(R.id.qs_header_stub)
+ val stub = notificationShadeWindowView.requireViewById<ViewStub>(R.id.qs_header_stub)
val layoutId = R.layout.combined_qs_header
stub.layoutResource = layoutId
return stub.inflate() as MotionLayout
@@ -260,7 +260,7 @@
@SysUISingleton
@Named(SHADE_HEADER)
fun providesBatteryMeterView(@Named(SHADE_HEADER) view: MotionLayout): BatteryMeterView {
- return view.findViewById(R.id.batteryRemainingIcon)
+ return view.requireViewById(R.id.batteryRemainingIcon)
}
@Provides
@@ -295,7 +295,7 @@
fun providesOngoingPrivacyChip(
@Named(SHADE_HEADER) header: MotionLayout,
): OngoingPrivacyChip {
- return header.findViewById(R.id.privacy_chip)
+ return header.requireViewById(R.id.privacy_chip)
}
@Provides
@@ -304,7 +304,7 @@
fun providesStatusIconContainer(
@Named(SHADE_HEADER) header: MotionLayout,
): StatusIconContainer {
- return header.findViewById(R.id.statusIcons)
+ return header.requireViewById(R.id.statusIcons)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/TouchLogger.kt b/packages/SystemUI/src/com/android/systemui/shade/TouchLogger.kt
new file mode 100644
index 0000000..58704bf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/TouchLogger.kt
@@ -0,0 +1,78 @@
+/*
+ * 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 com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
+
+private const val TAG = "systemui.shade.touch"
+
+/**
+ * A logger for tracking touch dispatching in the shade view hierarchy. The purpose of this logger
+ * is to passively observe dispatchTouchEvent calls in order to see which subtrees of the shade are
+ * handling touches. Additionally, some touches may be passively observed for views near the top of
+ * the shade hierarchy that cannot intercept touches, i.e. scrims. The usage of static methods for
+ * logging is sub-optimal in many ways, but it was selected in this case to make usage of this
+ * non-function diagnostic code as low friction as possible.
+ */
+class TouchLogger {
+ companion object {
+ private var touchLogger: DispatchTouchLogger? = null
+
+ @JvmStatic
+ fun logTouchesTo(buffer: LogBuffer) {
+ touchLogger = DispatchTouchLogger(buffer)
+ }
+
+ @JvmStatic
+ fun logDispatchTouch(viewTag: String, ev: MotionEvent, result: Boolean): Boolean {
+ touchLogger?.logDispatchTouch(viewTag, ev, result)
+ return result
+ }
+ }
+}
+
+/** Logs touches. */
+private class DispatchTouchLogger(private val buffer: LogBuffer) {
+ fun logDispatchTouch(viewTag: String, ev: MotionEvent, result: Boolean) {
+ // NOTE: never log position of touches for security purposes
+ buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ {
+ str1 = viewTag
+ int1 = ev.action
+ long1 = ev.downTime
+ bool1 = result
+ },
+ { "Touch: view=$str1, type=${typeToString(int1)}, downtime=$long1, result=$bool1" }
+ )
+ }
+
+ private fun typeToString(type: Int): String {
+ return when (type) {
+ MotionEvent.ACTION_DOWN -> "DOWN"
+ MotionEvent.ACTION_UP -> "UP"
+ MotionEvent.ACTION_MOVE -> "MOVE"
+ MotionEvent.ACTION_CANCEL -> "CANCEL"
+ MotionEvent.ACTION_POINTER_DOWN -> "POINTER_DOWN"
+ MotionEvent.ACTION_POINTER_UP -> "POINTER_UP"
+ else -> "OTHER"
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
index 87abc92..8edc26d 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
@@ -25,7 +25,7 @@
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.stateIn
/** Models UI state and handles user input for the shade scene. */
@@ -39,14 +39,22 @@
) {
/** The key of the scene we should switch to when swiping up. */
val upDestinationSceneKey: StateFlow<SceneKey> =
- authenticationInteractor.isUnlocked
- .map { isUnlocked -> upDestinationSceneKey(isUnlocked = isUnlocked) }
+ combine(
+ authenticationInteractor.isUnlocked,
+ authenticationInteractor.canSwipeToDismiss,
+ ) { isUnlocked, canSwipeToDismiss ->
+ upDestinationSceneKey(
+ isUnlocked = isUnlocked,
+ canSwipeToDismiss = canSwipeToDismiss,
+ )
+ }
.stateIn(
scope = applicationScope,
started = SharingStarted.WhileSubscribed(),
initialValue =
upDestinationSceneKey(
isUnlocked = authenticationInteractor.isUnlocked.value,
+ canSwipeToDismiss = authenticationInteractor.canSwipeToDismiss.value,
),
)
@@ -57,7 +65,12 @@
private fun upDestinationSceneKey(
isUnlocked: Boolean,
+ canSwipeToDismiss: Boolean,
): SceneKey {
- return if (isUnlocked) SceneKey.Gone else SceneKey.Lockscreen
+ return when {
+ canSwipeToDismiss -> SceneKey.Lockscreen
+ isUnlocked -> SceneKey.Gone
+ else -> SceneKey.Lockscreen
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BatteryStatusChip.kt b/packages/SystemUI/src/com/android/systemui/statusbar/BatteryStatusChip.kt
index 37140ec..5209767 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BatteryStatusChip.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BatteryStatusChip.kt
@@ -37,8 +37,8 @@
init {
inflate(context, R.layout.battery_status_chip, this)
- roundedContainer = findViewById(R.id.rounded_container)
- batteryMeterView = findViewById(R.id.battery_meter_view)
+ roundedContainer = requireViewById(R.id.rounded_container)
+ batteryMeterView = requireViewById(R.id.battery_meter_view)
updateResources()
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
index 823bb35..3120128 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
@@ -14,9 +14,11 @@
import android.os.Trace
import android.util.AttributeSet
import android.util.MathUtils.lerp
+import android.view.MotionEvent
import android.view.View
import android.view.animation.PathInterpolator
import com.android.app.animation.Interpolators
+import com.android.systemui.shade.TouchLogger
import com.android.systemui.statusbar.LightRevealEffect.Companion.getPercentPastThreshold
import com.android.systemui.util.getColorWithAlpha
import com.android.systemui.util.leak.RotationUtils
@@ -234,6 +236,8 @@
}
}
+private const val TAG = "LightRevealScrim"
+
/**
* Scrim view that partially reveals the content underneath it using a [RadialGradient] with a
* transparent center. The center position, size, and stops of the gradient can be manipulated to
@@ -419,15 +423,14 @@
revealGradientCenter.y = top + (revealGradientHeight / 2f)
}
- override fun onDraw(canvas: Canvas?) {
+ override fun onDraw(canvas: Canvas) {
if (
- canvas == null ||
- revealGradientWidth <= 0 ||
- revealGradientHeight <= 0 ||
- revealAmount == 0f
+ revealGradientWidth <= 0 ||
+ revealGradientHeight <= 0 ||
+ revealAmount == 0f
) {
if (revealAmount < 1f) {
- canvas?.drawColor(revealGradientEndColor)
+ canvas.drawColor(revealGradientEndColor)
}
return
}
@@ -447,6 +450,10 @@
canvas.drawRect(0f, 0f, width.toFloat(), height.toFloat(), gradientPaint)
}
+ override fun dispatchTouchEvent(event: MotionEvent): Boolean {
+ return TouchLogger.logDispatchTouch(TAG, event, super.dispatchTouchEvent(event))
+ }
+
private fun setPaintColorFilter() {
gradientPaint.colorFilter =
PorterDuffColorFilter(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
index 4710574..672796a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
@@ -474,7 +474,7 @@
}
if (endlistener != null) {
dragDownAnimator.addListener(object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator?) {
+ override fun onAnimationEnd(animation: Animator) {
endlistener.invoke()
}
})
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/MediaArtworkProcessor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/MediaArtworkProcessor.kt
index 750272d..17b4e3b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/MediaArtworkProcessor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/MediaArtworkProcessor.kt
@@ -66,7 +66,7 @@
inBitmap = oldIn.copy(Bitmap.Config.ARGB_8888, false /* isMutable */)
oldIn.recycle()
}
- val outBitmap = Bitmap.createBitmap(inBitmap.width, inBitmap.height,
+ val outBitmap = Bitmap.createBitmap(inBitmap?.width ?: 0, inBitmap?.height ?: 0,
Bitmap.Config.ARGB_8888)
input = Allocation.createFromBitmap(renderScript, inBitmap,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
index 0e20df6..6ad1395 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
@@ -272,7 +272,7 @@
blurUtils.blurRadiusOfRatio(animation.animatedValue as Float)
}
addListener(object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator?) {
+ override fun onAnimationEnd(animation: Animator) {
keyguardAnimator = null
wakeAndUnlockBlurRadius = 0f
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
index eddb683..d1e0a71 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
@@ -234,7 +234,7 @@
}
// Set the dot's view gravity to hug the status bar
- (corner.findViewById<View>(R.id.privacy_dot)
+ (corner.requireViewById<View>(R.id.privacy_dot)
.layoutParams as FrameLayout.LayoutParams)
.gravity = rotatedCorner.innerGravity()
}
@@ -255,7 +255,7 @@
// in every rotation. The only thing we need to check is rtl
val rtl = state.layoutRtl
val size = Point()
- tl.context.display.getRealSize(size)
+ tl.context.display?.getRealSize(size)
val currentRotation = RotationUtils.getExactRotation(tl.context)
val displayWidth: Int
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 6e8b8bd..1ad4620 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt
@@ -168,10 +168,8 @@
}
val keyFrame1Height = dotSize * 2
- val v = currentAnimatedView!!.view
- val chipVerticalCenter = v.top + v.measuredHeight / 2
- val height1 = ValueAnimator.ofInt(
- currentAnimatedView!!.view.measuredHeight, keyFrame1Height).apply {
+ val chipVerticalCenter = chipBounds.top + chipBounds.height() / 2
+ val height1 = ValueAnimator.ofInt(chipBounds.height(), keyFrame1Height).apply {
startDelay = 8.frames
duration = 6.frames
interpolator = STATUS_CHIP_HEIGHT_TO_DOT_KEYFRAME_1
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventCoordinator.kt
index 23edf17..2403920 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventCoordinator.kt
@@ -164,7 +164,9 @@
}
private fun isChipAnimationEnabled(): Boolean {
- return DeviceConfig.getBoolean(NAMESPACE_PRIVACY, CHIP_ANIMATION_ENABLED, true)
+ val defaultValue =
+ context.resources.getBoolean(R.bool.config_enablePrivacyChipAnimation)
+ return DeviceConfig.getBoolean(NAMESPACE_PRIVACY, CHIP_ANIMATION_ENABLED, defaultValue)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt
index e018397..f40f570 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt
@@ -243,7 +243,7 @@
if (!event.showAnimation && event.forceVisible) {
// If animations are turned off, we'll transition directly to the dot
animationState.value = SHOWING_PERSISTENT_DOT
- notifyTransitionToPersistentDot()
+ notifyTransitionToPersistentDot(event)
return
}
@@ -335,7 +335,7 @@
}
animators.add(chipAnimationController.onSystemEventAnimationFinish(hasPersistentDot))
if (hasPersistentDot) {
- val dotAnim = notifyTransitionToPersistentDot()
+ val dotAnim = notifyTransitionToPersistentDot(currentlyDisplayedEvent)
if (dotAnim != null) {
animators.add(dotAnim)
}
@@ -344,12 +344,12 @@
return AnimatorSet().also { it.playTogether(animators) }
}
- private fun notifyTransitionToPersistentDot(): Animator? {
+ private fun notifyTransitionToPersistentDot(event: StatusEvent?): Animator? {
logger?.logTransitionToPersistentDotCallbackInvoked()
val anims: List<Animator> =
listeners.mapNotNull {
it.onSystemStatusAnimationTransitionToPersistentDot(
- currentlyDisplayedEvent?.contentDescription
+ event?.contentDescription
)
}
if (anims.isNotEmpty()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
index 903d485..c5de165 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
@@ -179,15 +179,20 @@
}
if (weatherTarget != null) {
val clickIntent = weatherTarget.headerAction?.intent
- val weatherData = WeatherData.fromBundle(weatherTarget.baseAction.extras, { v ->
- if (!falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
- activityStarter.startActivity(
- clickIntent,
- true, /* dismissShade */
- null,
- false)
+ val weatherData = weatherTarget.baseAction?.extras?.let { extras ->
+ WeatherData.fromBundle(
+ extras,
+ ) { _ ->
+ if (!falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
+ activityStarter.startActivity(
+ clickIntent,
+ true, /* dismissShade */
+ null,
+ false)
+ }
}
- })
+ }
+
if (weatherData != null) {
keyguardUpdateMonitor.sendWeatherData(weatherData)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ViewGroupFadeHelper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ViewGroupFadeHelper.kt
index 16f1a45..1b43922 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ViewGroupFadeHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ViewGroupFadeHelper.kt
@@ -74,7 +74,7 @@
root.setTag(R.id.view_group_fade_helper_previous_value_tag, newAlpha)
}
addListener(object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator?) {
+ override fun onAnimationEnd(animation: Animator) {
endRunnable?.run()
}
})
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinator.java
deleted file mode 100644
index f04b24e..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinator.java
+++ /dev/null
@@ -1,130 +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.statusbar.notification.collection.coordinator;
-
-import static android.app.NotificationManager.IMPORTANCE_MIN;
-
-import android.app.Notification;
-import android.service.notification.StatusBarNotification;
-
-import com.android.systemui.ForegroundServiceController;
-import com.android.systemui.appops.AppOpsController;
-import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.statusbar.notification.collection.ListEntry;
-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 com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner;
-import com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt;
-import com.android.systemui.util.concurrency.DelayableExecutor;
-
-import javax.inject.Inject;
-
-/**
- * Handles ForegroundService and AppOp interactions with notifications.
- * Tags notifications with appOps
- * Lifetime extends notifications associated with an ongoing ForegroundService.
- * Filters out notifications that represent foreground services that are no longer running
- * Puts foreground service notifications into the FGS section. See {@link NotifCoordinators} for
- * section ordering priority.
- *
- * Previously this logic lived in
- * frameworks/base/packages/SystemUI/src/com/android/systemui/ForegroundServiceController
- * frameworks/base/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener
- * frameworks/base/packages/SystemUI/src/com/android/systemui/ForegroundServiceLifetimeExtender
- */
-@CoordinatorScope
-public class AppOpsCoordinator implements Coordinator {
- private static final String TAG = "AppOpsCoordinator";
-
- private final ForegroundServiceController mForegroundServiceController;
- private final AppOpsController mAppOpsController;
- private final DelayableExecutor mMainExecutor;
-
- private NotifPipeline mNotifPipeline;
-
- @Inject
- public AppOpsCoordinator(
- ForegroundServiceController foregroundServiceController,
- AppOpsController appOpsController,
- @Main DelayableExecutor mainExecutor) {
- mForegroundServiceController = foregroundServiceController;
- mAppOpsController = appOpsController;
- mMainExecutor = mainExecutor;
- }
-
- @Override
- public void attach(NotifPipeline pipeline) {
- mNotifPipeline = pipeline;
-
- // filter out foreground service notifications that aren't necessary anymore
- mNotifPipeline.addPreGroupFilter(mNotifFilter);
-
- }
-
- public NotifSectioner getSectioner() {
- return mNotifSectioner;
- }
-
- /**
- * Filters out notifications that represent foreground services that are no longer running or
- * that already have an app notification with the appOps tagged to
- */
- private final NotifFilter mNotifFilter = new NotifFilter(TAG) {
- @Override
- public boolean shouldFilterOut(NotificationEntry entry, long now) {
- StatusBarNotification sbn = entry.getSbn();
-
- // Filters out system-posted disclosure notifications when unneeded
- if (mForegroundServiceController.isDisclosureNotification(sbn)
- && !mForegroundServiceController.isDisclosureNeededForUser(
- sbn.getUser().getIdentifier())) {
- return true;
- }
- return false;
- }
- };
-
- /**
- * Puts colorized foreground service and call notifications into its own section.
- */
- private final NotifSectioner mNotifSectioner = new NotifSectioner("ForegroundService",
- NotificationPriorityBucketKt.BUCKET_FOREGROUND_SERVICE) {
- @Override
- public boolean isInSection(ListEntry entry) {
- NotificationEntry notificationEntry = entry.getRepresentativeEntry();
- if (notificationEntry != null) {
- return isColorizedForegroundService(notificationEntry) || isCall(notificationEntry);
- }
- return false;
- }
-
- private boolean isColorizedForegroundService(NotificationEntry entry) {
- Notification notification = entry.getSbn().getNotification();
- return notification.isForegroundService()
- && notification.isColorized()
- && entry.getImportance() > IMPORTANCE_MIN;
- }
-
- private boolean isCall(NotificationEntry entry) {
- Notification notification = entry.getSbn().getNotification();
- return entry.getImportance() > IMPORTANCE_MIN
- && notification.isStyle(Notification.CallStyle.class);
- }
- };
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ColorizedFgsCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ColorizedFgsCoordinator.java
new file mode 100644
index 0000000..63997f8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ColorizedFgsCoordinator.java
@@ -0,0 +1,81 @@
+/*
+ * 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.statusbar.notification.collection.coordinator;
+
+import static android.app.NotificationManager.IMPORTANCE_MIN;
+
+import android.app.Notification;
+
+import com.android.systemui.statusbar.notification.collection.ListEntry;
+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.NotifSectioner;
+import com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt;
+
+import javax.inject.Inject;
+
+/**
+ * Handles sectioning for foreground service notifications.
+ * Puts non-min colorized foreground service notifications into the FGS section. See
+ * {@link NotifCoordinators} for section ordering priority.
+ */
+@CoordinatorScope
+public class ColorizedFgsCoordinator implements Coordinator {
+ private static final String TAG = "ColorizedCoordinator";
+
+ @Inject
+ public ColorizedFgsCoordinator() {
+ }
+
+ @Override
+ public void attach(NotifPipeline pipeline) {
+ }
+
+ public NotifSectioner getSectioner() {
+ return mNotifSectioner;
+ }
+
+
+ /**
+ * Puts colorized foreground service and call notifications into its own section.
+ */
+ private final NotifSectioner mNotifSectioner = new NotifSectioner("ColorizedSectioner",
+ NotificationPriorityBucketKt.BUCKET_FOREGROUND_SERVICE) {
+ @Override
+ public boolean isInSection(ListEntry entry) {
+ NotificationEntry notificationEntry = entry.getRepresentativeEntry();
+ if (notificationEntry != null) {
+ return isColorizedForegroundService(notificationEntry) || isCall(notificationEntry);
+ }
+ return false;
+ }
+
+ private boolean isColorizedForegroundService(NotificationEntry entry) {
+ Notification notification = entry.getSbn().getNotification();
+ return notification.isForegroundService()
+ && notification.isColorized()
+ && entry.getImportance() > IMPORTANCE_MIN;
+ }
+
+ private boolean isCall(NotificationEntry entry) {
+ Notification notification = entry.getSbn().getNotification();
+ return entry.getImportance() > IMPORTANCE_MIN
+ && notification.isStyle(Notification.CallStyle.class);
+ }
+ };
+}
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 0ccab9e..226a957 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
@@ -33,34 +33,34 @@
@CoordinatorScope
class NotifCoordinatorsImpl @Inject constructor(
- 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,
+ sectionStyleProvider: SectionStyleProvider,
+ featureFlags: FeatureFlags,
+ dataStoreCoordinator: DataStoreCoordinator,
+ hideLocallyDismissedNotifsCoordinator: HideLocallyDismissedNotifsCoordinator,
+ hideNotifsForOtherUsersCoordinator: HideNotifsForOtherUsersCoordinator,
+ keyguardCoordinator: KeyguardCoordinator,
+ rankingCoordinator: RankingCoordinator,
+ colorizedFgsCoordinator: ColorizedFgsCoordinator,
+ 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()
@@ -79,7 +79,7 @@
mCoordinators.add(hideNotifsForOtherUsersCoordinator)
mCoordinators.add(keyguardCoordinator)
mCoordinators.add(rankingCoordinator)
- mCoordinators.add(appOpsCoordinator)
+ mCoordinators.add(colorizedFgsCoordinator)
mCoordinators.add(deviceProvisionedCoordinator)
mCoordinators.add(bubbleCoordinator)
mCoordinators.add(debugModeCoordinator)
@@ -106,7 +106,7 @@
// Manually add Ordered Sections
mOrderedSections.add(headsUpCoordinator.sectioner) // HeadsUp
- mOrderedSections.add(appOpsCoordinator.sectioner) // ForegroundService
+ mOrderedSections.add(colorizedFgsCoordinator.sectioner) // ForegroundService
mOrderedSections.add(conversationCoordinator.peopleAlertingSectioner) // People Alerting
mOrderedSections.add(conversationCoordinator.peopleSilentSectioner) // People Silent
mOrderedSections.add(rankingCoordinator.alertingSectioner) // Alerting
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
index 106d11f..7d1cca8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
@@ -17,7 +17,6 @@
package com.android.systemui.statusbar.notification.init
import android.service.notification.StatusBarNotification
-import com.android.systemui.ForegroundServiceNotificationListener
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.people.widget.PeopleSpaceWidgetManager
@@ -70,7 +69,6 @@
private val animatedImageNotificationManager: AnimatedImageNotificationManager,
private val peopleSpaceWidgetManager: PeopleSpaceWidgetManager,
private val bubblesOptional: Optional<Bubbles>,
- private val fgsNotifListener: ForegroundServiceNotificationListener,
private val featureFlags: FeatureFlags
) : NotificationsController {
@@ -105,7 +103,6 @@
notificationsMediaManager.setUpWithPresenter(presenter)
notificationLogger.setUpWithContainer(listContainer)
peopleSpaceWidgetManager.attach(notificationListener)
- fgsNotifListener.init()
}
// TODO: Convert all functions below this line into listeners instead of public methods
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorListView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorListView.kt
index 38a1579..9c4aa07 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorListView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorListView.kt
@@ -60,7 +60,7 @@
override fun onFinishInflate() {
super.onFinishInflate()
- appControlRow = findViewById(R.id.app_control)
+ appControlRow = requireViewById(R.id.app_control)
}
/**
@@ -143,9 +143,9 @@
lateinit var switch: Switch
override fun onFinishInflate() {
- iconView = findViewById(R.id.icon)
- channelName = findViewById(R.id.app_name)
- switch = findViewById(R.id.toggle)
+ iconView = requireViewById(R.id.icon)
+ channelName = requireViewById(R.id.app_name)
+ switch = requireViewById(R.id.toggle)
setOnClickListener { switch.toggle() }
}
@@ -174,9 +174,9 @@
override fun onFinishInflate() {
super.onFinishInflate()
- channelName = findViewById(R.id.channel_name)
- channelDescription = findViewById(R.id.channel_description)
- switch = findViewById(R.id.toggle)
+ channelName = requireViewById(R.id.channel_name)
+ channelDescription = requireViewById(R.id.channel_description)
+ switch = requireViewById(R.id.toggle)
switch.setOnCheckedChangeListener { _, b ->
channel?.let {
controller.proposeEditForChannel(it, if (b) it.importance else IMPORTANCE_NONE)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index ed489a6c..d92d11b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -1894,6 +1894,9 @@
return traceTag;
}
+ if (isSummaryWithChildren()) {
+ return traceTag + "(summary)";
+ }
Class<? extends Notification.Style> style =
getEntry().getSbn().getNotification().getNotificationStyle();
if (style == null) {
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 d71bc2f..5e3a67e 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
@@ -93,6 +93,7 @@
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
import com.android.systemui.shade.ShadeController;
+import com.android.systemui.shade.TouchLogger;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.EmptyShadeView;
import com.android.systemui.statusbar.NotificationShelf;
@@ -3480,6 +3481,11 @@
return super.onTouchEvent(ev);
}
+ @Override
+ public boolean dispatchTouchEvent(MotionEvent ev) {
+ return TouchLogger.logDispatchTouch(TAG, ev, super.dispatchTouchEvent(ev));
+ }
+
void dispatchDownEventToScroller(MotionEvent ev) {
MotionEvent downEvent = MotionEvent.obtain(ev);
downEvent.setAction(MotionEvent.ACTION_DOWN);
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 2ccbc9f..baeae79 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
@@ -914,8 +914,8 @@
val packages: Array<String> =
context.resources.getStringArray(R.array.system_ui_packages)
for (pkg in packages) {
- if (intent.component == null) break
- if (pkg == intent.component.packageName) {
+ val componentName = intent.component ?: break
+ if (pkg == componentName.packageName) {
return UserHandle(UserHandle.myUserId())
}
}
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 632f241..127569d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -634,7 +634,7 @@
private final ActivityIntentHelper mActivityIntentHelper;
- private final NotificationStackScrollLayoutController mStackScrollerController;
+ public final NotificationStackScrollLayoutController mStackScrollerController;
private final ColorExtractor.OnColorsChangedListener mOnColorsChangedListener =
(extractor, which) -> updateTheme();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt
index 7dcdc0b..97cb45a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt
@@ -22,8 +22,6 @@
import android.view.View.LAYOUT_DIRECTION_RTL
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.statusbar.policy.ConfigurationController
-
-import java.util.ArrayList
import javax.inject.Inject
@SysUISingleton
@@ -40,6 +38,7 @@
private var localeList: LocaleList? = null
private val context: Context
private var layoutDirection: Int
+ private var orientation = Configuration.ORIENTATION_UNDEFINED
init {
val currentConfig = context.resources.configuration
@@ -134,8 +133,18 @@
it.onThemeChanged()
}
}
+
+ val newOrientation = newConfig.orientation
+ if (orientation != newOrientation) {
+ orientation = newOrientation
+ listeners.filterForEach({ this.listeners.contains(it) }) {
+ it.onOrientationChanged(orientation)
+ }
+ }
}
+
+
override fun addCallback(listener: ConfigurationController.ConfigurationListener) {
listeners.add(listener)
listener.onDensityOrFontScaleChanged()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
index f15dcc3..a1f12b8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
@@ -81,6 +81,7 @@
private final BiConsumer<Float, Float> mSetExpandedHeight = this::setAppearFraction;
private final KeyguardBypassController mBypassController;
private final StatusBarStateController mStatusBarStateController;
+ private final PhoneStatusBarTransitions mPhoneStatusBarTransitions;
private final CommandQueue mCommandQueue;
private final NotificationWakeUpCoordinator mWakeUpCoordinator;
@@ -109,6 +110,7 @@
NotificationIconAreaController notificationIconAreaController,
HeadsUpManagerPhone headsUpManager,
StatusBarStateController stateController,
+ PhoneStatusBarTransitions phoneStatusBarTransitions,
KeyguardBypassController bypassController,
NotificationWakeUpCoordinator wakeUpCoordinator,
DarkIconDispatcher darkIconDispatcher,
@@ -156,6 +158,7 @@
});
mBypassController = bypassController;
mStatusBarStateController = stateController;
+ mPhoneStatusBarTransitions = phoneStatusBarTransitions;
mWakeUpCoordinator = wakeUpCoordinator;
mCommandQueue = commandQueue;
mKeyguardStateController = keyguardStateController;
@@ -203,6 +206,7 @@
@Override
public void onHeadsUpStateChanged(@NonNull NotificationEntry entry, boolean isHeadsUp) {
updateHeadsUpAndPulsingRoundness(entry);
+ mPhoneStatusBarTransitions.onHeadsUpStateChanged(isHeadsUp);
}
private void updateTopEntry() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.kt
index 34bbd13..cdd410e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.kt
@@ -32,6 +32,7 @@
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.statusbar.VibratorHelper
+import com.android.systemui.util.animation.requiresRemeasuring
/**
* Renders the bottom area of the lock-screen. Concerned primarily with the quick affordance UI
@@ -98,7 +99,7 @@
ambientIndicationArea?.let { nonNullAmbientIndicationArea ->
// remove old ambient indication from its parent
val originalAmbientIndicationView =
- oldBottomArea.findViewById<View>(R.id.ambient_indication_container)
+ oldBottomArea.requireViewById<View>(R.id.ambient_indication_container)
(originalAmbientIndicationView.parent as ViewGroup).removeView(
originalAmbientIndicationView
)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
index 5c1f824b..38c3815 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
@@ -87,6 +87,7 @@
private int mStatusBarPaddingEnd;
private int mMinDotWidth;
private View mSystemIconsContainer;
+ private View mSystemIcons;
private final MutableStateFlow<DarkChange> mDarkChange = StateFlowKt.MutableStateFlow(
DarkChange.EMPTY);
@@ -119,6 +120,7 @@
protected void onFinishInflate() {
super.onFinishInflate();
mSystemIconsContainer = findViewById(R.id.system_icons_container);
+ mSystemIcons = findViewById(R.id.system_icons);
mMultiUserAvatar = findViewById(R.id.multi_user_avatar);
mCarrierLabel = findViewById(R.id.keyguard_carrier_text);
mBatteryView = mSystemIconsContainer.findViewById(R.id.battery);
@@ -167,6 +169,13 @@
mStatusIconContainer.getPaddingBottom()
);
+ mSystemIcons.setPaddingRelative(
+ getResources().getDimensionPixelSize(R.dimen.status_bar_icons_padding_start),
+ getResources().getDimensionPixelSize(R.dimen.status_bar_icons_padding_top),
+ getResources().getDimensionPixelSize(R.dimen.status_bar_icons_padding_end),
+ getResources().getDimensionPixelSize(R.dimen.status_bar_icons_padding_bottom)
+ );
+
// Respect font size setting.
mCarrierLabel.setTextSize(TypedValue.COMPLEX_UNIT_PX,
getResources().getDimensionPixelSize(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitions.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitions.java
index 15c6dcf..cc38405 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitions.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitions.java
@@ -31,6 +31,8 @@
private final float mIconAlphaWhenOpaque;
+ private boolean mIsHeadsUp;
+
private View mStartSide, mStatusIcons, mBattery;
private Animator mCurrentAnimation;
@@ -52,15 +54,32 @@
return ObjectAnimator.ofFloat(v, "alpha", v.getAlpha(), toAlpha);
}
- private float getNonBatteryClockAlphaFor(int mode) {
- return isLightsOut(mode) ? ICON_ALPHA_WHEN_LIGHTS_OUT_NON_BATTERY_CLOCK
- : !isOpaque(mode) ? ICON_ALPHA_WHEN_NOT_OPAQUE
- : mIconAlphaWhenOpaque;
+ private float getStatusIconsAlphaFor(int mode) {
+ return getDefaultAlphaFor(mode);
+ }
+
+ private float getStartSideAlphaFor(int mode) {
+ // When there's a heads up notification, we need the start side icons to show regardless of
+ // lights out mode.
+ if (mIsHeadsUp) {
+ return getIconAlphaBasedOnOpacity(mode);
+ }
+ return getDefaultAlphaFor(mode);
}
private float getBatteryClockAlpha(int mode) {
return isLightsOut(mode) ? ICON_ALPHA_WHEN_LIGHTS_OUT_BATTERY_CLOCK
- : getNonBatteryClockAlphaFor(mode);
+ : getIconAlphaBasedOnOpacity(mode);
+ }
+
+ private float getDefaultAlphaFor(int mode) {
+ return isLightsOut(mode) ? ICON_ALPHA_WHEN_LIGHTS_OUT_NON_BATTERY_CLOCK
+ : getIconAlphaBasedOnOpacity(mode);
+ }
+
+ private float getIconAlphaBasedOnOpacity(int mode) {
+ return !isOpaque(mode) ? ICON_ALPHA_WHEN_NOT_OPAQUE
+ : mIconAlphaWhenOpaque;
}
private boolean isOpaque(int mode) {
@@ -74,19 +93,28 @@
applyMode(newMode, animate);
}
+ /** Informs this controller that the heads up notification state has changed. */
+ public void onHeadsUpStateChanged(boolean isHeadsUp) {
+ mIsHeadsUp = isHeadsUp;
+ // We want the icon to be fully visible when the HUN appears, so just immediately change the
+ // icon visibility and don't animate.
+ applyMode(getMode(), /* animate= */ false);
+ }
+
private void applyMode(int mode, boolean animate) {
if (mStartSide == null) return; // pre-init
- float newAlpha = getNonBatteryClockAlphaFor(mode);
- float newAlphaBC = getBatteryClockAlpha(mode);
+ float newStartSideAlpha = getStartSideAlphaFor(mode);
+ float newStatusIconsAlpha = getStatusIconsAlphaFor(mode);
+ float newBatteryAlpha = getBatteryClockAlpha(mode);
if (mCurrentAnimation != null) {
mCurrentAnimation.cancel();
}
if (animate) {
AnimatorSet anims = new AnimatorSet();
anims.playTogether(
- animateTransitionTo(mStartSide, newAlpha),
- animateTransitionTo(mStatusIcons, newAlpha),
- animateTransitionTo(mBattery, newAlphaBC)
+ animateTransitionTo(mStartSide, newStartSideAlpha),
+ animateTransitionTo(mStatusIcons, newStatusIconsAlpha),
+ animateTransitionTo(mBattery, newBatteryAlpha)
);
if (isLightsOut(mode)) {
anims.setDuration(LIGHTS_OUT_DURATION);
@@ -94,9 +122,9 @@
anims.start();
mCurrentAnimation = anims;
} else {
- mStartSide.setAlpha(newAlpha);
- mStatusIcons.setAlpha(newAlpha);
- mBattery.setAlpha(newAlphaBC);
+ mStartSide.setAlpha(newStartSideAlpha);
+ mStatusIcons.setAlpha(newStatusIconsAlpha);
+ mBattery.setAlpha(newBatteryAlpha);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
index d546a84..83a040c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
@@ -208,25 +208,29 @@
ViewGroup.LayoutParams layoutParams = getLayoutParams();
mStatusBarHeight = SystemBarUtils.getStatusBarHeight(mContext);
layoutParams.height = mStatusBarHeight - waterfallTopInset;
+ updatePaddings();
+ setLayoutParams(layoutParams);
+ }
- int statusBarPaddingTop = getResources().getDimensionPixelSize(
- R.dimen.status_bar_padding_top);
+ private void updatePaddings() {
int statusBarPaddingStart = getResources().getDimensionPixelSize(
R.dimen.status_bar_padding_start);
- int statusBarPaddingEnd = getResources().getDimensionPixelSize(
- R.dimen.status_bar_padding_end);
- View sbContents = findViewById(R.id.status_bar_contents);
- sbContents.setPaddingRelative(
+ findViewById(R.id.status_bar_contents).setPaddingRelative(
statusBarPaddingStart,
- statusBarPaddingTop,
- statusBarPaddingEnd,
+ getResources().getDimensionPixelSize(R.dimen.status_bar_padding_top),
+ getResources().getDimensionPixelSize(R.dimen.status_bar_padding_end),
0);
findViewById(R.id.notification_lights_out)
.setPaddingRelative(0, statusBarPaddingStart, 0, 0);
- setLayoutParams(layoutParams);
+ findViewById(R.id.system_icons).setPaddingRelative(
+ getResources().getDimensionPixelSize(R.dimen.status_bar_icons_padding_start),
+ getResources().getDimensionPixelSize(R.dimen.status_bar_icons_padding_top),
+ getResources().getDimensionPixelSize(R.dimen.status_bar_icons_padding_end),
+ getResources().getDimensionPixelSize(R.dimen.status_bar_icons_padding_bottom)
+ );
}
private void updateLayoutForCutout() {
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 2affb817..931aedd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
@@ -75,13 +75,13 @@
}
override fun onViewAttached() {
- statusContainer = mView.findViewById(R.id.system_icons)
+ statusContainer = mView.requireViewById(R.id.system_icons)
statusContainer.setOnHoverListener(
statusOverlayHoverListenerFactory.createDarkAwareListener(statusContainer))
if (moveFromCenterAnimationController == null) return
- val statusBarLeftSide: View = mView.findViewById(R.id.status_bar_start_side_except_heads_up)
- val systemIconArea: ViewGroup = mView.findViewById(R.id.status_bar_end_side_content)
+ val statusBarLeftSide: View = mView.requireViewById(R.id.status_bar_start_side_except_heads_up)
+ val systemIconArea: ViewGroup = mView.requireViewById(R.id.status_bar_end_side_content)
val viewsToAnimate = arrayOf(
statusBarLeftSide,
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 e82ac59..fc66138 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -370,6 +370,9 @@
mScrimBehind = behindScrim;
mScrimInFront = scrimInFront;
updateThemeColors();
+ mNotificationsScrim.setScrimName(getScrimName(mNotificationsScrim));
+ mScrimBehind.setScrimName(getScrimName(mScrimBehind));
+ mScrimInFront.setScrimName(getScrimName(mScrimInFront));
behindScrim.enableBottomEdgeConcave(mClipsQsScrim);
mNotificationsScrim.enableRoundedCorners(true);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt
index c850d4f..ad18170 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt
@@ -117,11 +117,11 @@
* status bar area is contiguous.
*/
fun currentRotationHasCornerCutout(): Boolean {
- val cutout = context.display.cutout ?: return false
+ val cutout = checkNotNull(context.display).cutout ?: return false
val topBounds = cutout.boundingRectTop
val point = Point()
- context.display.getRealSize(point)
+ checkNotNull(context.display).getRealSize(point)
return topBounds.left <= 0 || topBounds.right >= point.x
}
@@ -161,7 +161,7 @@
*/
fun getStatusBarContentInsetsForRotation(@Rotation rotation: Int): Pair<Int, Int> =
traceSection(tag = "StatusBarContentInsetsProvider.getStatusBarContentInsetsForRotation") {
- val displayCutout = context.display.cutout
+ val displayCutout = checkNotNull(context.display).cutout
val key = getCacheKey(rotation, displayCutout)
val screenBounds = context.resources.configuration.windowConfiguration.maxBounds
@@ -198,7 +198,7 @@
fun getStatusBarContentAreaForRotation(
@Rotation rotation: Int
): Rect {
- val displayCutout = context.display.cutout
+ val displayCutout = checkNotNull(context.display).cutout
val key = getCacheKey(rotation, displayCutout)
return insetsCache[key] ?: getAndSetCalculatedAreaForRotation(
rotation, displayCutout, getResourcesForRotation(rotation, context), key)
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 5c1dfbe..ea57eb4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -22,6 +22,8 @@
import static com.android.systemui.plugins.ActivityStarter.OnDismissAction;
import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK;
import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING;
+import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
+import static com.android.systemui.util.kotlin.JavaAdapterKt.combineFlows;
import android.content.Context;
import android.content.res.ColorStateList;
@@ -60,10 +62,14 @@
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
import com.android.systemui.bouncer.ui.BouncerView;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dock.DockManager;
import com.android.systemui.dreams.DreamOverlayStateController;
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.domain.interactor.KeyguardTransitionInteractor;
+import com.android.systemui.keyguard.domain.interactor.WindowManagerLockscreenVisibilityInteractor;
import com.android.systemui.navigationbar.NavigationBarView;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.navigationbar.TaskbarDelegate;
@@ -86,8 +92,6 @@
import com.android.systemui.unfold.FoldAodAnimationController;
import com.android.systemui.unfold.SysUIUnfoldComponent;
-import dagger.Lazy;
-
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashSet;
@@ -97,6 +101,9 @@
import javax.inject.Inject;
+import dagger.Lazy;
+import kotlinx.coroutines.CoroutineDispatcher;
+
/**
* Manages creating, showing, hiding and resetting the keyguard within the status bar. Calls back
* via {@link ViewMediatorCallback} to poke the wake lock and report that the keyguard is done,
@@ -281,6 +288,9 @@
private int mLastBiometricMode;
private boolean mLastScreenOffAnimationPlaying;
private float mQsExpansion;
+
+ private FeatureFlags mFlags;
+
final Set<KeyguardViewManagerCallback> mCallbacks = new HashSet<>();
private boolean mIsBackAnimationEnabled;
private final boolean mUdfpsNewTouchDetectionEnabled;
@@ -326,6 +336,7 @@
}
}
};
+ private Lazy<WindowManagerLockscreenVisibilityInteractor> mWmLockscreenVisibilityInteractor;
@Inject
public StatusBarKeyguardViewManager(
@@ -352,7 +363,10 @@
BouncerView primaryBouncerView,
AlternateBouncerInteractor alternateBouncerInteractor,
UdfpsOverlayInteractor udfpsOverlayInteractor,
- ActivityStarter activityStarter
+ ActivityStarter activityStarter,
+ KeyguardTransitionInteractor keyguardTransitionInteractor,
+ @Main CoroutineDispatcher mainDispatcher,
+ Lazy<WindowManagerLockscreenVisibilityInteractor> wmLockscreenVisibilityInteractor
) {
mContext = context;
mViewMediatorCallback = callback;
@@ -370,6 +384,7 @@
mShadeController = shadeController;
mLatencyTracker = latencyTracker;
mKeyguardSecurityModel = keyguardSecurityModel;
+ mFlags = featureFlags;
mPrimaryBouncerCallbackInteractor = primaryBouncerCallbackInteractor;
mPrimaryBouncerInteractor = primaryBouncerInteractor;
mPrimaryBouncerView = primaryBouncerView;
@@ -381,8 +396,14 @@
mUdfpsNewTouchDetectionEnabled = featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION);
mUdfpsOverlayInteractor = udfpsOverlayInteractor;
mActivityStarter = activityStarter;
+ mKeyguardTransitionInteractor = keyguardTransitionInteractor;
+ mMainDispatcher = mainDispatcher;
+ mWmLockscreenVisibilityInteractor = wmLockscreenVisibilityInteractor;
}
+ KeyguardTransitionInteractor mKeyguardTransitionInteractor;
+ CoroutineDispatcher mMainDispatcher;
+
@Override
public void registerCentralSurfaces(CentralSurfaces centralSurfaces,
ShadeViewController shadeViewController,
@@ -429,6 +450,14 @@
}
}
+ private KeyguardStateController.Callback mKeyguardStateControllerCallback =
+ new KeyguardStateController.Callback() {
+ @Override
+ public void onUnlockedChanged() {
+ updateAlternateBouncerShowing(mAlternateBouncerInteractor.maybeHide());
+ }
+ };
+
private void registerListeners() {
mKeyguardUpdateManager.registerCallback(mUpdateMonitorCallback);
mStatusBarStateController.addCallback(this);
@@ -442,6 +471,32 @@
mDockManager.addListener(mDockEventListener);
mIsDocked = mDockManager.isDocked();
}
+ mKeyguardStateController.addCallback(mKeyguardStateControllerCallback);
+
+ if (mFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ mShadeViewController.postToView(() ->
+ collectFlow(
+ getViewRootImpl().getView(),
+ combineFlows(
+ mKeyguardTransitionInteractor.getFinishedKeyguardState(),
+ mWmLockscreenVisibilityInteractor.get()
+ .getUsingKeyguardGoingAwayAnimation(),
+ (finishedState, animating) ->
+ KeyguardInteractor.Companion.isKeyguardVisibleInState(
+ finishedState)
+ || animating),
+ this::consumeShowStatusBarKeyguardView));
+ }
+ }
+
+ private void consumeShowStatusBarKeyguardView(boolean show) {
+ if (show != mLastShowing) {
+ if (show) {
+ show(null);
+ } else {
+ hide(0, 0);
+ }
+ }
}
/** Register a callback, to be invoked by the Predictive Back system. */
@@ -1313,6 +1368,10 @@
hideAlternateBouncer(false);
executeAfterKeyguardGoneAction();
}
+
+ if (mFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ mKeyguardTransitionInteractor.startDismissKeyguardTransition();
+ }
}
/** Display security message to relevant KeyguardMessageArea. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
index e8da951..1bceb29 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
@@ -105,18 +105,18 @@
}
}
addListener(object : AnimatorListenerAdapter() {
- override fun onAnimationCancel(animation: Animator?) {
+ override fun onAnimationCancel(animation: Animator) {
if (lightRevealScrim.revealEffect !is CircleReveal) {
lightRevealScrim.revealAmount = 1f
}
}
- override fun onAnimationEnd(animation: Animator?) {
+ override fun onAnimationEnd(animation: Animator) {
lightRevealAnimationPlaying = false
interactionJankMonitor.end(CUJ_SCREEN_OFF)
}
- override fun onAnimationStart(animation: Animator?) {
+ override fun onAnimationStart(animation: Animator) {
interactionJankMonitor.begin(
notifShadeWindowControllerLazy.get().windowRootView, CUJ_SCREEN_OFF)
}
@@ -345,7 +345,7 @@
// portrait. If we're in another orientation, disable the screen off animation so we don't
// animate in the keyguard AOD UI sideways or upside down.
if (!keyguardStateController.isKeyguardScreenRotationAllowed &&
- context.display.rotation != Surface.ROTATION_0) {
+ context.display?.rotation != Surface.ROTATION_0) {
return false
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherContainer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherContainer.kt
index 270c592..1259477 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherContainer.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherContainer.kt
@@ -34,7 +34,7 @@
override fun onFinishInflate() {
super.onFinishInflate()
- text = findViewById(R.id.current_user_name)
- avatar = findViewById(R.id.current_user_avatar)
+ text = requireViewById(R.id.current_user_name)
+ avatar = requireViewById(R.id.current_user_avatar)
}
}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationController.java
index 6b80a9d..b2ef818 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationController.java
@@ -42,5 +42,6 @@
default void onThemeChanged() {}
default void onLocaleListChanged() {}
default void onLayoutDirectionChanged(boolean isLayoutRtl) {}
+ default void onOrientationChanged(int orientation) {}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImpl.kt
index 4950482..ffb743f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImpl.kt
@@ -128,7 +128,8 @@
val prefs = userContextProvider.userContext.getSharedPreferences(
PREFS_CONTROLS_FILE, Context.MODE_PRIVATE)
- val seededPackages = prefs.getStringSet(PREFS_CONTROLS_SEEDING_COMPLETED, emptySet())
+ val seededPackages =
+ prefs.getStringSet(PREFS_CONTROLS_SEEDING_COMPLETED, emptySet()) ?: emptySet()
val controlsController = controlsComponent.getControlsController().get()
val componentsToSeed = mutableListOf<ComponentName>()
@@ -174,7 +175,8 @@
}
private fun addPackageToSeededSet(prefs: SharedPreferences, pkg: String) {
- val seededPackages = prefs.getStringSet(PREFS_CONTROLS_SEEDING_COMPLETED, emptySet())
+ val seededPackages =
+ prefs.getStringSet(PREFS_CONTROLS_SEEDING_COMPLETED, emptySet()) ?: emptySet()
val updatedPkgs = seededPackages.toMutableSet()
updatedPkgs.add(pkg)
prefs.edit().putStringSet(PREFS_CONTROLS_SEEDING_COMPLETED, updatedPkgs).apply()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
index 710588c..63dcad9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
@@ -18,6 +18,8 @@
import static android.hardware.biometrics.BiometricSourceType.FACE;
+import static com.android.systemui.flags.Flags.LOCKSCREEN_ENABLE_LANDSCAPE;
+
import android.annotation.NonNull;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -38,6 +40,7 @@
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.keyguard.KeyguardUnlockAnimationController;
import dagger.Lazy;
@@ -49,6 +52,7 @@
import javax.inject.Inject;
/**
+ *
*/
@SysUISingleton
public class KeyguardStateControllerImpl implements KeyguardStateController, Dumpable {
@@ -103,7 +107,10 @@
*/
private boolean mSnappingKeyguardBackAfterSwipe = false;
+ private FeatureFlags mFeatureFlags;
+
/**
+ *
*/
@Inject
public KeyguardStateControllerImpl(
@@ -112,13 +119,15 @@
LockPatternUtils lockPatternUtils,
Lazy<KeyguardUnlockAnimationController> keyguardUnlockAnimationController,
KeyguardUpdateMonitorLogger logger,
- DumpManager dumpManager) {
+ DumpManager dumpManager,
+ FeatureFlags featureFlags) {
mContext = context;
mLogger = logger;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mLockPatternUtils = lockPatternUtils;
mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback);
mUnlockAnimationControllerLazy = keyguardUnlockAnimationController;
+ mFeatureFlags = featureFlags;
dumpManager.registerDumpable(getClass().getSimpleName(), this);
@@ -272,7 +281,8 @@
@Override
public boolean isKeyguardScreenRotationAllowed() {
return SystemProperties.getBoolean("lockscreen.rot_override", false)
- || mContext.getResources().getBoolean(R.bool.config_enableLockScreenRotation);
+ || mContext.getResources().getBoolean(R.bool.config_enableLockScreenRotation)
+ || mFeatureFlags.isEnabled(LOCKSCREEN_ENABLE_LANDSCAPE);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt b/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt
index 27aaa68..eb7d339 100644
--- a/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt
@@ -353,8 +353,8 @@
// before CoreStartables run, and will not be removed.
// In many cases, it reports the battery level of the stylus.
registerBatteryListener(deviceId)
- } else if (device.bluetoothAddress != null) {
- onStylusBluetoothConnected(deviceId, device.bluetoothAddress)
+ } else {
+ device.bluetoothAddress?.let { onStylusBluetoothConnected(deviceId, it) }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherFullscreenDialog.kt b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherFullscreenDialog.kt
index 72786ef..5ad9630 100644
--- a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherFullscreenDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherFullscreenDialog.kt
@@ -60,7 +60,7 @@
override fun getWidth(): Int {
val displayMetrics = context.resources.displayMetrics.apply {
- context.display.getRealMetrics(this)
+ checkNotNull(context.display).getRealMetrics(this)
}
return displayMetrics.widthPixels
}
diff --git a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherPopupMenu.kt b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherPopupMenu.kt
index 088cd93..ee84580 100644
--- a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherPopupMenu.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherPopupMenu.kt
@@ -52,22 +52,22 @@
override fun show() {
// need to call show() first in order to construct the listView
super.show()
- val listView = getListView()
+ listView?.apply {
+ isVerticalScrollBarEnabled = false
+ isHorizontalScrollBarEnabled = false
- listView.setVerticalScrollBarEnabled(false)
- listView.setHorizontalScrollBarEnabled(false)
+ // Creates a transparent spacer between items
+ val shape = ShapeDrawable()
+ shape.alpha = 0
+ divider = shape
+ dividerHeight = res.getDimensionPixelSize(
+ R.dimen.bouncer_user_switcher_popup_divider_height)
- // Creates a transparent spacer between items
- val shape = ShapeDrawable()
- shape.setAlpha(0)
- listView.setDivider(shape)
- listView.setDividerHeight(res.getDimensionPixelSize(
- R.dimen.bouncer_user_switcher_popup_divider_height))
-
- val height = res.getDimensionPixelSize(R.dimen.bouncer_user_switcher_popup_header_height)
- listView.addHeaderView(createSpacer(height), null, false)
- listView.addFooterView(createSpacer(height), null, false)
- setWidth(findMaxWidth(listView))
+ val height = res.getDimensionPixelSize(R.dimen.bouncer_user_switcher_popup_header_height)
+ addHeaderView(createSpacer(height), null, false)
+ addFooterView(createSpacer(height), null, false)
+ setWidth(findMaxWidth(this))
+ }
super.show()
}
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/GuestUserInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/GuestUserInteractor.kt
index f026f0f..bfed0c4 100644
--- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/GuestUserInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/GuestUserInteractor.kt
@@ -227,7 +227,8 @@
)
}
try {
- WindowManagerGlobal.getWindowManagerService().lockNow(/* options= */ null)
+ checkNotNull(WindowManagerGlobal.getWindowManagerService())
+ .lockNow(/* options= */ null)
} catch (e: RemoteException) {
Log.e(
TAG,
diff --git a/packages/SystemUI/src/com/android/systemui/user/legacyhelper/ui/LegacyUserUiHelper.kt b/packages/SystemUI/src/com/android/systemui/user/legacyhelper/ui/LegacyUserUiHelper.kt
index e74232d..7f16e47 100644
--- a/packages/SystemUI/src/com/android/systemui/user/legacyhelper/ui/LegacyUserUiHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/legacyhelper/ui/LegacyUserUiHelper.kt
@@ -67,7 +67,7 @@
val resourceId: Int? = getGuestUserRecordNameResourceId(record)
return when {
resourceId != null -> context.getString(resourceId)
- record.info != null -> record.info.name
+ record.info != null -> checkNotNull(record.info.name)
else ->
context.getString(
getUserSwitcherActionTextResourceId(
diff --git a/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayout.kt b/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayout.kt
index 56c5d3b..7866d76 100644
--- a/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayout.kt
@@ -223,11 +223,11 @@
}
}
- override fun dispatchDraw(canvas: Canvas?) {
- canvas?.save()
- canvas?.clipRect(boundsRect)
+ override fun dispatchDraw(canvas: Canvas) {
+ canvas.save()
+ canvas.clipRect(boundsRect)
super.dispatchDraw(canvas)
- canvas?.restore()
+ canvas.restore()
}
private fun updateBounds() {
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt
index 12d7b4d..0d0a646 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt
@@ -29,7 +29,7 @@
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.launch
/** A class allowing Java classes to collect on Kotlin flows. */
@@ -75,3 +75,7 @@
repeatOnLifecycle(state) { flow.collect { consumer.accept(it) } }
}
}
+
+fun <A, B, R> combineFlows(flow1: Flow<A>, flow2: Flow<B>, bifunction: (A, B) -> R): Flow<R> {
+ return combine(flow1, flow2, bifunction)
+}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
index cb18229..9f7ab7b 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
@@ -150,7 +150,7 @@
private fun getPatternTopGuideline(): Float {
val cs = ConstraintSet()
val container =
- mKeyguardPatternView.findViewById(R.id.pattern_container) as ConstraintLayout
+ mKeyguardPatternView.requireViewById(R.id.pattern_container) as ConstraintLayout
cs.clone(container)
return cs.getConstraint(R.id.pattern_top_guideline).layout.guidePercent
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
index 4dc7652..309d9e0 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
@@ -114,7 +114,7 @@
objectKeyguardPINView =
View.inflate(mContext, R.layout.keyguard_pin_view, null)
- .findViewById(R.id.keyguard_pin_view) as KeyguardPINView
+ .requireViewById(R.id.keyguard_pin_view) as KeyguardPINView
}
private fun constructPinViewController(
@@ -175,7 +175,7 @@
private fun getPinTopGuideline(): Float {
val cs = ConstraintSet()
- val container = objectKeyguardPINView.findViewById(R.id.pin_container) as ConstraintLayout
+ val container = objectKeyguardPINView.requireViewById(R.id.pin_container) as ConstraintLayout
cs.clone(container)
return cs.getConstraint(R.id.pin_pad_top_guideline).layout.guidePercent
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
index 9ba21da..17bb3d5 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
@@ -38,6 +38,7 @@
import com.android.keyguard.KeyguardSecurityModel.SecurityMode
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
import com.android.systemui.biometrics.FaceAuthAccessibilityDelegate
import com.android.systemui.biometrics.SideFpsController
import com.android.systemui.biometrics.SideFpsUiRequestSource
@@ -46,6 +47,8 @@
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
import com.android.systemui.log.SessionTracker
import com.android.systemui.plugins.ActivityStarter.OnDismissAction
import com.android.systemui.plugins.FalsingManager
@@ -144,6 +147,8 @@
private lateinit var testableResources: TestableResources
private lateinit var sceneTestUtils: SceneTestUtils
private lateinit var sceneInteractor: SceneInteractor
+ private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
+ private lateinit var authenticationInteractor: AuthenticationInteractor
private lateinit var sceneTransitionStateFlow: MutableStateFlow<ObservableTransitionState>
private lateinit var underTest: KeyguardSecurityContainerController
@@ -182,6 +187,7 @@
featureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true)
featureFlags.set(Flags.SCENE_CONTAINER, false)
featureFlags.set(Flags.BOUNCER_USER_SWITCHER, false)
+ featureFlags.set(Flags.KEYGUARD_WM_STATE_REFACTOR, false)
keyguardPasswordViewController =
KeyguardPasswordViewController(
@@ -204,9 +210,17 @@
whenever(userInteractor.getSelectedUserId()).thenReturn(TARGET_USER_ID)
sceneTestUtils = SceneTestUtils(this)
sceneInteractor = sceneTestUtils.sceneInteractor()
+ keyguardTransitionInteractor =
+ KeyguardTransitionInteractorFactory.create(sceneTestUtils.testScope.backgroundScope)
+ .keyguardTransitionInteractor
sceneTransitionStateFlow =
MutableStateFlow(ObservableTransitionState.Idle(SceneKey.Lockscreen))
sceneInteractor.setTransitionState(sceneTransitionStateFlow)
+ authenticationInteractor =
+ sceneTestUtils.authenticationInteractor(
+ repository = sceneTestUtils.authenticationRepository(),
+ sceneInteractor = sceneInteractor
+ )
underTest =
KeyguardSecurityContainerController(
@@ -236,9 +250,9 @@
{ JavaAdapter(sceneTestUtils.testScope.backgroundScope) },
userInteractor,
faceAuthAccessibilityDelegate,
- ) {
- sceneInteractor
- }
+ keyguardTransitionInteractor,
+ { authenticationInteractor },
+ )
}
@Test
@@ -493,30 +507,6 @@
}
@Test
- fun showNextSecurityScreenOrFinish_SimPinToAnotherSimPin_None() {
- // 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.SimPin)
- 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, never()).keyguardDone(anyBoolean(), anyInt())
- }
-
- @Test
fun onSwipeUp_whenFaceDetectionIsNotRunning_initiatesFaceAuth() {
val registeredSwipeListener = registeredSwipeListener
whenever(keyguardUpdateMonitor.isFaceDetectionRunning).thenReturn(false)
@@ -753,7 +743,7 @@
}
@Test
- fun dismissesKeyguard_whenSceneChangesFromBouncerToGone() =
+ fun dismissesKeyguard_whenSceneChangesToGone() =
sceneTestUtils.testScope.runTest {
featureFlags.set(Flags.SCENE_CONTAINER, true)
@@ -790,12 +780,32 @@
runCurrent()
verify(viewMediatorCallback).keyguardDone(anyBoolean(), anyInt())
+ // While listening, moving back to the lockscreen scene does not dismiss the keyguard
+ // again.
+ clearInvocations(viewMediatorCallback)
+ sceneInteractor.changeScene(SceneModel(SceneKey.Lockscreen, null), "reason")
+ sceneTransitionStateFlow.value =
+ ObservableTransitionState.Transition(
+ SceneKey.Gone,
+ SceneKey.Lockscreen,
+ flowOf(.5f)
+ )
+ runCurrent()
+ sceneInteractor.onSceneChanged(SceneModel(SceneKey.Lockscreen, null), "reason")
+ sceneTransitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Lockscreen)
+ runCurrent()
+ verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt())
+
// While listening, moving back to the bouncer scene does not dismiss the keyguard
// again.
clearInvocations(viewMediatorCallback)
sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer, null), "reason")
sceneTransitionStateFlow.value =
- ObservableTransitionState.Transition(SceneKey.Gone, SceneKey.Bouncer, flowOf(.5f))
+ ObservableTransitionState.Transition(
+ SceneKey.Lockscreen,
+ SceneKey.Bouncer,
+ flowOf(.5f)
+ )
runCurrent()
sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer, null), "reason")
sceneTransitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Bouncer)
@@ -815,7 +825,21 @@
runCurrent()
verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt())
- // While not listening, moving back to the bouncer does not dismiss the keyguard.
+ // While not listening, moving to the lockscreen does not dismiss the keyguard.
+ sceneInteractor.changeScene(SceneModel(SceneKey.Lockscreen, null), "reason")
+ sceneTransitionStateFlow.value =
+ ObservableTransitionState.Transition(
+ SceneKey.Gone,
+ SceneKey.Lockscreen,
+ flowOf(.5f)
+ )
+ runCurrent()
+ sceneInteractor.onSceneChanged(SceneModel(SceneKey.Lockscreen, null), "reason")
+ sceneTransitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Lockscreen)
+ runCurrent()
+ verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt())
+
+ // While not listening, moving to the bouncer does not dismiss the keyguard.
sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer, null), "reason")
sceneTransitionStateFlow.value =
ObservableTransitionState.Transition(SceneKey.Gone, SceneKey.Bouncer, flowOf(.5f))
@@ -826,12 +850,15 @@
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.
+ // gone scene now does dismiss the keyguard again, this time from lockscreen.
underTest.onViewAttached()
sceneInteractor.changeScene(SceneModel(SceneKey.Gone, null), "reason")
sceneTransitionStateFlow.value =
- ObservableTransitionState.Transition(SceneKey.Bouncer, SceneKey.Gone, flowOf(.5f))
+ ObservableTransitionState.Transition(
+ SceneKey.Lockscreen,
+ SceneKey.Gone,
+ flowOf(.5f)
+ )
runCurrent()
sceneInteractor.onSceneChanged(SceneModel(SceneKey.Gone, null), "reason")
sceneTransitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Gone)
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt
index f8262d4..210f3cb 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt
@@ -21,9 +21,9 @@
private lateinit var keyguardStatusView: KeyguardStatusView
private val mediaView: View
- get() = keyguardStatusView.findViewById(R.id.status_view_media_container)
+ get() = keyguardStatusView.requireViewById(R.id.status_view_media_container)
private val statusViewContainer: ViewGroup
- get() = keyguardStatusView.findViewById(R.id.status_view_container)
+ get() = keyguardStatusView.requireViewById(R.id.status_view_container)
private val childrenExcludingMedia
get() = statusViewContainer.children.filter { it != mediaView }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java
index 956e0b81..b18137c 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java
@@ -49,7 +49,7 @@
import com.android.systemui.flags.FakeFeatureFlags;
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory;
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.StatusBarState;
@@ -164,8 +164,9 @@
mVibrator,
mAuthRippleController,
mResources,
- new KeyguardTransitionInteractor(mTransitionRepository,
- TestScopeProvider.getTestScope().getBackgroundScope()),
+ KeyguardTransitionInteractorFactory.create(
+ TestScopeProvider.getTestScope().getBackgroundScope(),
+ mTransitionRepository).getKeyguardTransitionInteractor(),
KeyguardInteractorFactory.create(mFeatureFlags).getKeyguardInteractor(),
mFeatureFlags,
mPrimaryBouncerInteractor
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/NumPadAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/NumPadAnimatorTest.kt
index 9fcb9c8..7c2550f 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/NumPadAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/NumPadAnimatorTest.kt
@@ -47,10 +47,10 @@
@Test
fun testOnLayout() {
- underTest.onLayout(100)
+ underTest.onLayout(100, 100)
verify(background).cornerRadius = 50f
reset(background)
- underTest.onLayout(100)
+ underTest.onLayout(100, 100)
verify(background, never()).cornerRadius = anyFloat()
}
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/SplitShadeTransitionAdapterTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/SplitShadeTransitionAdapterTest.kt
index 6fe8892..9f9b9a4 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/SplitShadeTransitionAdapterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/SplitShadeTransitionAdapterTest.kt
@@ -19,9 +19,11 @@
import android.testing.AndroidTestingRunner
import android.transition.TransitionValues
import android.view.View
+import android.view.ViewGroup
import androidx.test.filters.SmallTest
import com.android.keyguard.KeyguardStatusViewController.SplitShadeTransitionAdapter
import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
@@ -84,5 +86,5 @@
startValues: TransitionValues?,
endValues: TransitionValues?
): Animator? {
- return createAnimator(/* sceneRoot= */ null, startValues, endValues)
+ return createAnimator(/* sceneRoot= */ mock(), startValues, endValues)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/FaceScanningProviderFactoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/FaceScanningProviderFactoryTest.kt
index 01d3a39..ea7cc3d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/FaceScanningProviderFactoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/FaceScanningProviderFactoryTest.kt
@@ -27,6 +27,8 @@
import com.android.systemui.biometrics.AuthController
import com.android.systemui.decor.FaceScanningProviderFactory
import com.android.systemui.dump.logcatLogBuffer
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.log.ScreenDecorationsLogger
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.util.mockito.whenever
@@ -80,6 +82,8 @@
R.bool.config_fillMainBuiltInDisplayCutout,
true
)
+ val featureFlags = FakeFeatureFlags()
+ featureFlags.set(Flags.STOP_PULSING_FACE_SCANNING_ANIMATION, true)
underTest =
FaceScanningProviderFactory(
authController,
@@ -87,7 +91,8 @@
statusBarStateController,
keyguardUpdateMonitor,
mock(Executor::class.java),
- ScreenDecorationsLogger(logcatLogBuffer("FaceScanningProviderFactoryTest"))
+ ScreenDecorationsLogger(logcatLogBuffer("FaceScanningProviderFactoryTest")),
+ featureFlags,
)
whenever(authController.faceSensorLocation).thenReturn(Point(10, 10))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java
deleted file mode 100644
index b47b08c..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java
+++ /dev/null
@@ -1,487 +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;
-
-import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL;
-
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertNull;
-import static junit.framework.TestCase.fail;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.annotation.UserIdInt;
-import android.app.AppOpsManager;
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.UserHandle;
-import android.service.notification.StatusBarNotification;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-import android.widget.RemoteViews;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.internal.messages.nano.SystemMessageProto;
-import com.android.systemui.appops.AppOpsController;
-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.notifcollection.NotifCollectionListener;
-
-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;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper
-public class ForegroundServiceControllerTest extends SysuiTestCase {
- private ForegroundServiceController mFsc;
- private ForegroundServiceNotificationListener mListener;
- private NotifCollectionListener mCollectionListener;
- @Mock private AppOpsController mAppOpsController;
- @Mock private Handler mMainHandler;
- @Mock private NotifPipeline mNotifPipeline;
-
- @Before
- public void setUp() throws Exception {
- // allow the TestLooper to be asserted as the main thread these tests
- allowTestableLooperAsMainThread();
-
- MockitoAnnotations.initMocks(this);
- mFsc = new ForegroundServiceController(mAppOpsController, mMainHandler);
- mListener = new ForegroundServiceNotificationListener(
- mContext, mFsc, mNotifPipeline);
- mListener.init();
- ArgumentCaptor<NotifCollectionListener> entryListenerCaptor =
- ArgumentCaptor.forClass(NotifCollectionListener.class);
- verify(mNotifPipeline).addCollectionListener(
- entryListenerCaptor.capture());
- mCollectionListener = entryListenerCaptor.getValue();
- }
-
- @Test
- public void testAppOpsChangedCalledFromBgThread() {
- try {
- // WHEN onAppOpChanged is called from a different thread than the MainLooper
- disallowTestableLooperAsMainThread();
- NotificationEntry entry = createFgEntry();
- mFsc.onAppOpChanged(
- AppOpsManager.OP_CAMERA,
- entry.getSbn().getUid(),
- entry.getSbn().getPackageName(),
- true);
-
- // This test is run on the TestableLooper, which is not the MainLooper, so
- // we expect an exception to be thrown
- fail("onAppOpChanged shouldn't be allowed to be called from a bg thread.");
- } catch (IllegalStateException e) {
- // THEN expect an exception
- }
- }
-
- @Test
- public void testAppOpsCRUD() {
- // no crash on remove that doesn't exist
- mFsc.onAppOpChanged(9, 1000, "pkg1", false);
- assertNull(mFsc.getAppOps(0, "pkg1"));
-
- // multiuser & multipackage
- mFsc.onAppOpChanged(8, 50, "pkg1", true);
- mFsc.onAppOpChanged(1, 60, "pkg3", true);
- mFsc.onAppOpChanged(7, 500000, "pkg2", true);
-
- assertEquals(1, mFsc.getAppOps(0, "pkg1").size());
- assertTrue(mFsc.getAppOps(0, "pkg1").contains(8));
-
- assertEquals(1, mFsc.getAppOps(UserHandle.getUserId(500000), "pkg2").size());
- assertTrue(mFsc.getAppOps(UserHandle.getUserId(500000), "pkg2").contains(7));
-
- assertEquals(1, mFsc.getAppOps(0, "pkg3").size());
- assertTrue(mFsc.getAppOps(0, "pkg3").contains(1));
-
- // multiple ops for the same package
- mFsc.onAppOpChanged(9, 50, "pkg1", true);
- mFsc.onAppOpChanged(5, 50, "pkg1", true);
-
- assertEquals(3, mFsc.getAppOps(0, "pkg1").size());
- assertTrue(mFsc.getAppOps(0, "pkg1").contains(8));
- assertTrue(mFsc.getAppOps(0, "pkg1").contains(9));
- assertTrue(mFsc.getAppOps(0, "pkg1").contains(5));
-
- assertEquals(1, mFsc.getAppOps(UserHandle.getUserId(500000), "pkg2").size());
- assertTrue(mFsc.getAppOps(UserHandle.getUserId(500000), "pkg2").contains(7));
-
- // remove one of the multiples
- mFsc.onAppOpChanged(9, 50, "pkg1", false);
- assertEquals(2, mFsc.getAppOps(0, "pkg1").size());
- assertTrue(mFsc.getAppOps(0, "pkg1").contains(8));
- assertTrue(mFsc.getAppOps(0, "pkg1").contains(5));
-
- // remove last op
- mFsc.onAppOpChanged(1, 60, "pkg3", false);
- assertNull(mFsc.getAppOps(0, "pkg3"));
- }
-
- @Test
- public void testDisclosurePredicate() {
- StatusBarNotification sbn_user1_app1 = makeMockSBN(USERID_ONE, "com.example.app1",
- 5000, "monkeys", Notification.FLAG_AUTO_CANCEL);
- StatusBarNotification sbn_user1_disclosure = makeMockSBN(USERID_ONE, "android",
- SystemMessageProto.SystemMessage.NOTE_FOREGROUND_SERVICES,
- null, Notification.FLAG_NO_CLEAR);
-
- assertTrue(mFsc.isDisclosureNotification(sbn_user1_disclosure));
- assertFalse(mFsc.isDisclosureNotification(sbn_user1_app1));
- }
-
- @Test
- public void testNeedsDisclosureAfterRemovingUnrelatedNotification() {
- final String PKG1 = "com.example.app100";
-
- StatusBarNotification sbn_user1_app1 = makeMockSBN(USERID_ONE, PKG1,
- 5000, "monkeys", Notification.FLAG_AUTO_CANCEL);
- StatusBarNotification sbn_user1_app1_fg = makeMockFgSBN(USERID_ONE, PKG1);
-
- // first add a normal notification
- entryAdded(sbn_user1_app1, NotificationManager.IMPORTANCE_DEFAULT);
- // nothing required yet
- assertFalse(mFsc.isDisclosureNeededForUser(USERID_ONE));
- // now the app starts a fg service
- entryAdded(makeMockDisclosure(USERID_ONE, new String[]{PKG1}),
- NotificationManager.IMPORTANCE_DEFAULT);
- assertTrue(mFsc.isDisclosureNeededForUser(USERID_ONE)); // should be required!
- // add the fg notification
- entryAdded(sbn_user1_app1_fg, NotificationManager.IMPORTANCE_DEFAULT);
- assertFalse(mFsc.isDisclosureNeededForUser(USERID_ONE)); // app1 has got it covered
- // remove the boring notification
- entryRemoved(sbn_user1_app1);
- assertFalse(mFsc.isDisclosureNeededForUser(USERID_ONE)); // app1 has STILL got it covered
- entryRemoved(sbn_user1_app1_fg);
- assertTrue(mFsc.isDisclosureNeededForUser(USERID_ONE)); // should be required!
- }
-
- @Test
- public void testSimpleAddRemove() {
- final String PKG1 = "com.example.app1";
- final String PKG2 = "com.example.app2";
-
- StatusBarNotification sbn_user1_app1 = makeMockSBN(USERID_ONE, PKG1,
- 5000, "monkeys", Notification.FLAG_AUTO_CANCEL);
- entryAdded(sbn_user1_app1, NotificationManager.IMPORTANCE_DEFAULT);
-
- // no services are "running"
- entryAdded(makeMockDisclosure(USERID_ONE, null),
- NotificationManager.IMPORTANCE_DEFAULT);
-
- assertFalse(mFsc.isDisclosureNeededForUser(USERID_ONE));
- assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO));
-
- entryUpdated(makeMockDisclosure(USERID_ONE, new String[]{PKG1}),
- NotificationManager.IMPORTANCE_DEFAULT);
- assertTrue(mFsc.isDisclosureNeededForUser(USERID_ONE)); // should be required!
- assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO));
-
- // switch to different package
- entryUpdated(makeMockDisclosure(USERID_ONE, new String[]{PKG2}),
- NotificationManager.IMPORTANCE_DEFAULT);
- assertTrue(mFsc.isDisclosureNeededForUser(USERID_ONE));
- assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO));
-
- entryUpdated(makeMockDisclosure(USERID_TWO, new String[]{PKG1}),
- NotificationManager.IMPORTANCE_DEFAULT);
- assertTrue(mFsc.isDisclosureNeededForUser(USERID_ONE));
- assertTrue(mFsc.isDisclosureNeededForUser(USERID_TWO)); // finally user2 needs one too
-
- entryUpdated(makeMockDisclosure(USERID_ONE, new String[]{PKG2, PKG1}),
- NotificationManager.IMPORTANCE_DEFAULT);
- assertTrue(mFsc.isDisclosureNeededForUser(USERID_ONE));
- assertTrue(mFsc.isDisclosureNeededForUser(USERID_TWO));
-
- entryRemoved(makeMockDisclosure(USERID_ONE, null /*unused*/));
- assertFalse(mFsc.isDisclosureNeededForUser(USERID_ONE));
- assertTrue(mFsc.isDisclosureNeededForUser(USERID_TWO));
-
- entryRemoved(makeMockDisclosure(USERID_TWO, null /*unused*/));
- assertFalse(mFsc.isDisclosureNeededForUser(USERID_ONE));
- assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO));
- }
-
- @Test
- public void testDisclosureBasic() {
- final String PKG1 = "com.example.app0";
-
- StatusBarNotification sbn_user1_app1 = makeMockSBN(USERID_ONE, PKG1,
- 5000, "monkeys", Notification.FLAG_AUTO_CANCEL);
- StatusBarNotification sbn_user1_app1_fg = makeMockFgSBN(USERID_ONE, PKG1);
-
- entryAdded(sbn_user1_app1, NotificationManager.IMPORTANCE_DEFAULT); // not fg
- entryAdded(makeMockDisclosure(USERID_ONE, new String[]{PKG1}),
- NotificationManager.IMPORTANCE_DEFAULT);
- assertTrue(mFsc.isDisclosureNeededForUser(USERID_ONE)); // should be required!
- entryAdded(sbn_user1_app1_fg, NotificationManager.IMPORTANCE_DEFAULT);
- assertFalse(mFsc.isDisclosureNeededForUser(USERID_ONE)); // app1 has got it covered
- assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO));
-
- // let's take out the other notification and see what happens.
-
- entryRemoved(sbn_user1_app1);
- assertFalse(
- mFsc.isDisclosureNeededForUser(USERID_ONE)); // still covered by sbn_user1_app1_fg
- assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO));
-
- // let's attempt to downgrade the notification from FLAG_FOREGROUND and see what we get
- StatusBarNotification sbn_user1_app1_fg_sneaky = makeMockFgSBN(USERID_ONE, PKG1);
- sbn_user1_app1_fg_sneaky.getNotification().flags = 0;
- entryUpdated(sbn_user1_app1_fg_sneaky,
- NotificationManager.IMPORTANCE_DEFAULT);
- assertTrue(mFsc.isDisclosureNeededForUser(USERID_ONE)); // should be required!
- assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO));
-
- // ok, ok, we'll put it back
- sbn_user1_app1_fg_sneaky.getNotification().flags = Notification.FLAG_FOREGROUND_SERVICE;
- entryUpdated(sbn_user1_app1_fg, NotificationManager.IMPORTANCE_DEFAULT);
- assertFalse(mFsc.isDisclosureNeededForUser(USERID_ONE));
- assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO));
-
- entryRemoved(sbn_user1_app1_fg_sneaky);
- assertTrue(mFsc.isDisclosureNeededForUser(USERID_ONE)); // should be required!
- assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO));
-
- // now let's test an upgrade
- entryAdded(sbn_user1_app1, NotificationManager.IMPORTANCE_DEFAULT);
- assertTrue(mFsc.isDisclosureNeededForUser(USERID_ONE));
- assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO));
- sbn_user1_app1.getNotification().flags |= Notification.FLAG_FOREGROUND_SERVICE;
- entryUpdated(sbn_user1_app1,
- NotificationManager.IMPORTANCE_DEFAULT); // this is now a fg notification
-
- assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO));
- assertFalse(mFsc.isDisclosureNeededForUser(USERID_ONE));
-
- // remove it, make sure we're out of compliance again
- entryRemoved(sbn_user1_app1); // was fg, should return true
- entryRemoved(sbn_user1_app1);
- assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO));
- assertTrue(mFsc.isDisclosureNeededForUser(USERID_ONE));
-
- // importance upgrade
- entryAdded(sbn_user1_app1_fg, NotificationManager.IMPORTANCE_MIN);
- assertTrue(mFsc.isDisclosureNeededForUser(USERID_ONE));
- assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO));
- sbn_user1_app1.getNotification().flags |= Notification.FLAG_FOREGROUND_SERVICE;
- entryUpdated(sbn_user1_app1_fg,
- NotificationManager.IMPORTANCE_DEFAULT); // this is now a fg notification
-
- // finally, let's turn off the service
- entryAdded(makeMockDisclosure(USERID_ONE, null),
- NotificationManager.IMPORTANCE_DEFAULT);
-
- assertFalse(mFsc.isDisclosureNeededForUser(USERID_ONE));
- assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO));
- }
-
- @Test
- public void testNoNotifsNorAppOps_noSystemAlertWarningRequired() {
- // no notifications nor app op signals that this package/userId requires system alert
- // warning
- assertFalse(mFsc.isSystemAlertWarningNeeded(USERID_ONE, "any"));
- }
-
- @Test
- public void testCustomLayouts_systemAlertWarningRequired() {
- // GIVEN a notification with a custom layout
- final String pkg = "com.example.app0";
- StatusBarNotification customLayoutNotif = makeMockSBN(USERID_ONE, pkg, 0,
- false);
-
- // WHEN the custom layout entry is added
- entryAdded(customLayoutNotif, NotificationManager.IMPORTANCE_MIN);
-
- // THEN a system alert warning is required since there aren't any notifications that can
- // display the app ops
- assertTrue(mFsc.isSystemAlertWarningNeeded(USERID_ONE, pkg));
- }
-
- @Test
- public void testStandardLayoutExists_noSystemAlertWarningRequired() {
- // GIVEN two notifications (one with a custom layout, the other with a standard layout)
- final String pkg = "com.example.app0";
- StatusBarNotification customLayoutNotif = makeMockSBN(USERID_ONE, pkg, 0,
- false);
- StatusBarNotification standardLayoutNotif = makeMockSBN(USERID_ONE, pkg, 1, true);
-
- // WHEN the entries are added
- entryAdded(customLayoutNotif, NotificationManager.IMPORTANCE_MIN);
- entryAdded(standardLayoutNotif, NotificationManager.IMPORTANCE_MIN);
-
- // THEN no system alert warning is required, since there is at least one notification
- // with a standard layout that can display the app ops on the notification
- assertFalse(mFsc.isSystemAlertWarningNeeded(USERID_ONE, pkg));
- }
-
- @Test
- public void testStandardLayoutRemoved_systemAlertWarningRequired() {
- // GIVEN two notifications (one with a custom layout, the other with a standard layout)
- final String pkg = "com.example.app0";
- StatusBarNotification customLayoutNotif = makeMockSBN(USERID_ONE, pkg, 0,
- false);
- StatusBarNotification standardLayoutNotif = makeMockSBN(USERID_ONE, pkg, 1, true);
-
- // WHEN the entries are added and then the standard layout notification is removed
- entryAdded(customLayoutNotif, NotificationManager.IMPORTANCE_MIN);
- entryAdded(standardLayoutNotif, NotificationManager.IMPORTANCE_MIN);
- entryRemoved(standardLayoutNotif);
-
- // THEN a system alert warning is required since there aren't any notifications that can
- // display the app ops
- assertTrue(mFsc.isSystemAlertWarningNeeded(USERID_ONE, pkg));
- }
-
- @Test
- public void testStandardLayoutUpdatedToCustomLayout_systemAlertWarningRequired() {
- // GIVEN a standard layout notification and then an updated version with a customLayout
- final String pkg = "com.example.app0";
- StatusBarNotification standardLayoutNotif = makeMockSBN(USERID_ONE, pkg, 1, true);
- StatusBarNotification updatedToCustomLayoutNotif = makeMockSBN(USERID_ONE, pkg, 1, false);
-
- // WHEN the entries is added and then updated to a custom layout
- entryAdded(standardLayoutNotif, NotificationManager.IMPORTANCE_MIN);
- entryUpdated(updatedToCustomLayoutNotif, NotificationManager.IMPORTANCE_MIN);
-
- // THEN a system alert warning is required since there aren't any notifications that can
- // display the app ops
- assertTrue(mFsc.isSystemAlertWarningNeeded(USERID_ONE, pkg));
- }
-
- private StatusBarNotification makeMockSBN(int userId, String pkg, int id, String tag,
- int flags) {
- final Notification n = mock(Notification.class);
- n.extras = new Bundle();
- n.flags = flags;
- return makeMockSBN(userId, pkg, id, tag, n);
- }
-
- private StatusBarNotification makeMockSBN(int userid, String pkg, int id, String tag,
- Notification n) {
- final StatusBarNotification sbn = mock(StatusBarNotification.class);
- when(sbn.getNotification()).thenReturn(n);
- when(sbn.getId()).thenReturn(id);
- when(sbn.getPackageName()).thenReturn(pkg);
- when(sbn.getTag()).thenReturn(tag);
- when(sbn.getUserId()).thenReturn(userid);
- when(sbn.getUser()).thenReturn(new UserHandle(userid));
- when(sbn.getKey()).thenReturn("MOCK:"+userid+"|"+pkg+"|"+id+"|"+tag);
- return sbn;
- }
-
- private StatusBarNotification makeMockSBN(int uid, String pkg, int id,
- boolean usesStdLayout) {
- StatusBarNotification sbn = makeMockSBN(uid, pkg, id, "foo", 0);
- if (usesStdLayout) {
- sbn.getNotification().contentView = null;
- sbn.getNotification().headsUpContentView = null;
- sbn.getNotification().bigContentView = null;
- } else {
- sbn.getNotification().contentView = mock(RemoteViews.class);
- }
- return sbn;
- }
-
- private StatusBarNotification makeMockFgSBN(int uid, String pkg, int id,
- boolean usesStdLayout) {
- StatusBarNotification sbn =
- makeMockSBN(uid, pkg, id, "foo", Notification.FLAG_FOREGROUND_SERVICE);
- if (usesStdLayout) {
- sbn.getNotification().contentView = null;
- sbn.getNotification().headsUpContentView = null;
- sbn.getNotification().bigContentView = null;
- } else {
- sbn.getNotification().contentView = mock(RemoteViews.class);
- }
- return sbn;
- }
-
- private StatusBarNotification makeMockFgSBN(int uid, String pkg) {
- return makeMockSBN(uid, pkg, 1000, "foo", Notification.FLAG_FOREGROUND_SERVICE);
- }
-
- private StatusBarNotification makeMockDisclosure(int userid, String[] pkgs) {
- final Notification n = mock(Notification.class);
- n.flags = Notification.FLAG_ONGOING_EVENT;
- final Bundle extras = new Bundle();
- if (pkgs != null) extras.putStringArray(Notification.EXTRA_FOREGROUND_APPS, pkgs);
- n.extras = extras;
- n.when = System.currentTimeMillis() - 10000; // ten seconds ago
- final StatusBarNotification sbn = makeMockSBN(userid, "android",
- SystemMessageProto.SystemMessage.NOTE_FOREGROUND_SERVICES,
- null, n);
- sbn.getNotification().extras = extras;
- return sbn;
- }
-
- private NotificationEntry addFgEntry() {
- NotificationEntry entry = createFgEntry();
- mCollectionListener.onEntryAdded(entry);
- return entry;
- }
-
- private NotificationEntry createFgEntry() {
- return new NotificationEntryBuilder()
- .setSbn(makeMockFgSBN(0, TEST_PACKAGE_NAME, 1000, true))
- .setImportance(NotificationManager.IMPORTANCE_DEFAULT)
- .build();
- }
-
- private void entryRemoved(StatusBarNotification notification) {
- mCollectionListener.onEntryRemoved(
- new NotificationEntryBuilder()
- .setSbn(notification)
- .build(),
- REASON_APP_CANCEL);
- }
-
- private void entryAdded(StatusBarNotification notification, int importance) {
- NotificationEntry entry = new NotificationEntryBuilder()
- .setSbn(notification)
- .setImportance(importance)
- .build();
- mCollectionListener.onEntryAdded(entry);
- }
-
- private void entryUpdated(StatusBarNotification notification, int importance) {
- NotificationEntry entry = new NotificationEntryBuilder()
- .setSbn(notification)
- .setImportance(importance)
- .build();
- mCollectionListener.onEntryUpdated(entry);
- }
-
- @UserIdInt private static final int USERID_ONE = 10; // UserManagerService.MIN_USER_ID;
- @UserIdInt private static final int USERID_TWO = USERID_ONE + 1;
- private static final String TEST_PACKAGE_NAME = "test";
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
index 796e665..f81ef10 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
@@ -92,6 +92,8 @@
import com.android.systemui.decor.PrivacyDotCornerDecorProviderImpl;
import com.android.systemui.decor.PrivacyDotDecorProviderFactory;
import com.android.systemui.decor.RoundedCornerResDelegate;
+import com.android.systemui.flags.FakeFeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.log.ScreenDecorationsLogger;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.settings.FakeDisplayTracker;
@@ -226,13 +228,16 @@
doAnswer(it -> !(mMockCutoutList.isEmpty())).when(mCutoutFactory).getHasProviders();
doReturn(mMockCutoutList).when(mCutoutFactory).getProviders();
+ FakeFeatureFlags featureFlags = new FakeFeatureFlags();
+ featureFlags.set(Flags.STOP_PULSING_FACE_SCANNING_ANIMATION, true);
mFaceScanningDecorProvider = spy(new FaceScanningOverlayProviderImpl(
BOUNDS_POSITION_TOP,
mAuthController,
mStatusBarStateController,
mKeyguardUpdateMonitor,
mExecutor,
- new ScreenDecorationsLogger(logcatLogBuffer("TestLogBuffer"))));
+ new ScreenDecorationsLogger(logcatLogBuffer("TestLogBuffer")),
+ featureFlags));
mScreenDecorations = spy(new ScreenDecorations(mContext, mSecureSettings,
mCommandRegistry, mUserTracker, mDisplayTracker, mDotViewController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
index 56f8160..e96ad87 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
@@ -682,6 +682,36 @@
}
@Test
+ public void windowMagnifierEditMode_performA11yClickAction_exitEditMode() {
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+ Float.NaN);
+ mWindowMagnificationController.setEditMagnifierSizeMode(true);
+ });
+
+ View closeButton = getInternalView(R.id.close_button);
+ View bottomRightCorner = getInternalView(R.id.bottom_right_corner);
+ View bottomLeftCorner = getInternalView(R.id.bottom_left_corner);
+ View topRightCorner = getInternalView(R.id.top_right_corner);
+ View topLeftCorner = getInternalView(R.id.top_left_corner);
+
+ assertEquals(View.VISIBLE, closeButton.getVisibility());
+ assertEquals(View.VISIBLE, bottomRightCorner.getVisibility());
+ assertEquals(View.VISIBLE, bottomLeftCorner.getVisibility());
+ assertEquals(View.VISIBLE, topRightCorner.getVisibility());
+ assertEquals(View.VISIBLE, topLeftCorner.getVisibility());
+
+ final View mirrorView = mWindowManager.getAttachedView();
+ mirrorView.performAccessibilityAction(AccessibilityAction.ACTION_CLICK.getId(), null);
+
+ assertEquals(View.GONE, closeButton.getVisibility());
+ assertEquals(View.GONE, bottomRightCorner.getVisibility());
+ assertEquals(View.GONE, bottomLeftCorner.getVisibility());
+ assertEquals(View.GONE, topRightCorner.getVisibility());
+ assertEquals(View.GONE, topLeftCorner.getVisibility());
+ }
+
+ @Test
public void enableWindowMagnification_hasA11yWindowTitle() {
mInstrumentation.runOnMainSync(() -> {
mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt
index 316de59..2233e322 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt
@@ -70,7 +70,7 @@
assertTrue(dialog.isShowing)
// The dialog is now fullscreen.
- val window = dialog.window
+ val window = checkNotNull(dialog.window)
val decorView = window.decorView as DecorView
assertEquals(MATCH_PARENT, window.attributes.width)
assertEquals(MATCH_PARENT, window.attributes.height)
@@ -172,14 +172,15 @@
// Important: the power menu animation relies on this behavior to know when to animate (see
// http://ag/16774605).
val dialog = runOnMainThreadAndWaitForIdleSync { TestDialog(context) }
- dialog.window.setWindowAnimations(0)
- assertEquals(0, dialog.window.attributes.windowAnimations)
+ val window = checkNotNull(dialog.window)
+ window.setWindowAnimations(0)
+ assertEquals(0, window.attributes.windowAnimations)
val touchSurface = createTouchSurface()
runOnMainThreadAndWaitForIdleSync {
dialogLaunchAnimator.showFromView(dialog, touchSurface)
}
- assertNotEquals(0, dialog.window.attributes.windowAnimations)
+ assertNotEquals(0, window.attributes.windowAnimations)
}
@Test
@@ -351,13 +352,14 @@
init {
// We need to set the window type for dialogs shown by SysUI, otherwise WM will throw.
- window.setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL)
+ checkNotNull(window).setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(contentView)
+ val window = checkNotNull(window)
window.setLayout(DIALOG_WIDTH, DIALOG_HEIGHT)
window.setBackgroundDrawable(windowBackground)
}
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 fc7d20a..707b1b3 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
@@ -33,6 +33,7 @@
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
@@ -605,7 +606,68 @@
assertThat(hintedPinLength).isNull()
}
- private fun switchToScene(sceneKey: SceneKey) {
- sceneInteractor.changeScene(SceneModel(sceneKey), "reason")
+ @Test
+ fun isLockscreenDismissed() =
+ testScope.runTest {
+ val isLockscreenDismissed by collectLastValue(underTest.isLockscreenDismissed)
+ // Start on lockscreen.
+ switchToScene(SceneKey.Lockscreen)
+ assertThat(isLockscreenDismissed).isFalse()
+
+ // The user swipes down to reveal shade.
+ switchToScene(SceneKey.Shade)
+ assertThat(isLockscreenDismissed).isFalse()
+
+ // The user swipes down to reveal quick settings.
+ switchToScene(SceneKey.QuickSettings)
+ assertThat(isLockscreenDismissed).isFalse()
+
+ // The user swipes up to go back to shade.
+ switchToScene(SceneKey.Shade)
+ assertThat(isLockscreenDismissed).isFalse()
+
+ // The user swipes up to reveal bouncer.
+ switchToScene(SceneKey.Bouncer)
+ assertThat(isLockscreenDismissed).isFalse()
+
+ // The user hits back to return to lockscreen.
+ switchToScene(SceneKey.Lockscreen)
+ assertThat(isLockscreenDismissed).isFalse()
+
+ // The user swipes up to reveal bouncer.
+ switchToScene(SceneKey.Bouncer)
+ assertThat(isLockscreenDismissed).isFalse()
+
+ // The user enters correct credentials and goes to gone.
+ switchToScene(SceneKey.Gone)
+ assertThat(isLockscreenDismissed).isTrue()
+
+ // The user swipes down to reveal shade.
+ switchToScene(SceneKey.Shade)
+ assertThat(isLockscreenDismissed).isTrue()
+
+ // The user swipes down to reveal quick settings.
+ switchToScene(SceneKey.QuickSettings)
+ assertThat(isLockscreenDismissed).isTrue()
+
+ // The user swipes up to go back to shade.
+ switchToScene(SceneKey.Shade)
+ assertThat(isLockscreenDismissed).isTrue()
+
+ // The user swipes up to go back to gone.
+ switchToScene(SceneKey.Gone)
+ assertThat(isLockscreenDismissed).isTrue()
+
+ // The device goes to sleep, returning to the lockscreen.
+ switchToScene(SceneKey.Lockscreen)
+ assertThat(isLockscreenDismissed).isFalse()
+ }
+
+ private fun TestScope.switchToScene(sceneKey: SceneKey) {
+ val model = SceneModel(sceneKey)
+ val loggingReason = "reason"
+ sceneInteractor.changeScene(model, loggingReason)
+ sceneInteractor.onSceneChanged(model, loggingReason)
+ runCurrent()
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
index e3e6130..4e52e64 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
@@ -139,6 +139,7 @@
@Before
fun setup() {
featureFlags.set(Flags.BIOMETRIC_BP_STRONG, useNewBiometricPrompt)
+ featureFlags.set(Flags.ONE_WAY_HAPTICS_API_MIGRATION, false)
}
@After
@@ -151,7 +152,10 @@
@Test
fun testNotifiesAnimatedIn() {
initializeFingerprintContainer()
- verify(callback).onDialogAnimatedIn(authContainer?.requestId ?: 0L, true /* startFingerprintNow */)
+ verify(callback).onDialogAnimatedIn(
+ authContainer?.requestId ?: 0L,
+ true /* startFingerprintNow */
+ )
}
@Test
@@ -196,7 +200,10 @@
waitForIdleSync()
// attaching the view resets the state and allows this to happen again
- verify(callback).onDialogAnimatedIn(authContainer?.requestId ?: 0L, true /* startFingerprintNow */)
+ verify(callback).onDialogAnimatedIn(
+ authContainer?.requestId ?: 0L,
+ true /* startFingerprintNow */
+ )
}
@Test
@@ -211,7 +218,10 @@
// the first time is triggered by initializeFingerprintContainer()
// the second time was triggered by dismissWithoutCallback()
- verify(callback, times(2)).onDialogAnimatedIn(authContainer?.requestId ?: 0L, true /* startFingerprintNow */)
+ verify(callback, times(2)).onDialogAnimatedIn(
+ authContainer?.requestId ?: 0L,
+ true /* startFingerprintNow */
+ )
}
@Test
@@ -517,10 +527,11 @@
{ authBiometricFingerprintViewModel },
{ promptSelectorInteractor },
{ bpCredentialInteractor },
- PromptViewModel(promptSelectorInteractor, vibrator),
+ PromptViewModel(promptSelectorInteractor, vibrator, featureFlags),
{ credentialViewModel },
Handler(TestableLooper.get(this).looper),
- fakeExecutor
+ fakeExecutor,
+ vibrator
) {
override fun postOnAnimation(runnable: Runnable) {
runnable.run()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
index 3d4171f..48e5131 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
@@ -197,6 +197,8 @@
private ArgumentCaptor<String> mMessageCaptor;
@Mock
private Resources mResources;
+ @Mock
+ private VibratorHelper mVibratorHelper;
private TestableContext mContextSpy;
private Execution mExecution;
@@ -515,7 +517,7 @@
assertThat(mModalityCaptor.getValue().intValue()).isEqualTo(modality);
assertThat(mMessageCaptor.getValue()).isEqualTo(
- mContext.getString(R.string.biometric_face_not_recognized));
+ mContext.getString(R.string.fingerprint_dialog_use_fingerprint_instead));
}
@Test
@@ -1097,7 +1099,7 @@
() -> mBiometricPromptCredentialInteractor, () -> mPromptSelectionInteractor,
() -> mCredentialViewModel, () -> mPromptViewModel,
mInteractionJankMonitor, mHandler,
- mBackgroundExecutor, mUdfpsUtils);
+ mBackgroundExecutor, mUdfpsUtils, mVibratorHelper);
}
@Override
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 2b08c66..994db46 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
@@ -164,7 +164,7 @@
context.addMockSystemService(WindowManager::class.java, windowManager)
whenEver(layoutInflater.inflate(R.layout.sidefps_view, null, false)).thenReturn(sideFpsView)
- whenEver(sideFpsView.findViewById<LottieAnimationView>(eq(R.id.sidefps_animation)))
+ whenEver(sideFpsView.requireViewById<LottieAnimationView>(eq(R.id.sidefps_animation)))
.thenReturn(mock(LottieAnimationView::class.java))
with(mock(ViewPropertyAnimator::class.java)) {
whenEver(sideFpsView.animate()).thenReturn(this)
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 40b1f20..4d19543 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
@@ -19,6 +19,7 @@
import android.hardware.biometrics.PromptInfo
import android.hardware.face.FaceSensorPropertiesInternal
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
+import android.view.HapticFeedbackConstants
import androidx.test.filters.SmallTest
import com.android.internal.widget.LockPatternUtils
import com.android.systemui.SysuiTestCase
@@ -33,6 +34,8 @@
import com.android.systemui.biometrics.shared.model.BiometricModality
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION
import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.util.mockito.any
import com.google.common.truth.Truth.assertThat
@@ -71,13 +74,15 @@
private lateinit var selector: PromptSelectorInteractor
private lateinit var viewModel: PromptViewModel
+ private val featureFlags = FakeFeatureFlags()
@Before
fun setup() {
selector = PromptSelectorInteractorImpl(promptRepository, lockPatternUtils)
selector.resetPrompt()
- viewModel = PromptViewModel(selector, vibrator)
+ viewModel = PromptViewModel(selector, vibrator, featureFlags)
+ featureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false)
}
@Test
@@ -149,6 +154,29 @@
verify(vibrator, never()).vibrateAuthError(any())
}
+ @Test
+ fun playSuccessHaptic_onwayHapticsEnabled_SetsConfirmConstant() = runGenericTest {
+ featureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true)
+ val expectConfirmation = testCase.expectConfirmation(atLeastOneFailure = false)
+ viewModel.showAuthenticated(testCase.authenticatedModality, 1_000L)
+
+ if (expectConfirmation) {
+ viewModel.confirmAuthenticated()
+ }
+
+ val currentConstant by collectLastValue(viewModel.hapticsToPlay)
+ assertThat(currentConstant).isEqualTo(HapticFeedbackConstants.CONFIRM)
+ }
+
+ @Test
+ fun playErrorHaptic_onwayHapticsEnabled_SetsRejectConstant() = runGenericTest {
+ featureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true)
+ viewModel.showTemporaryError("test", "messageAfterError", false)
+
+ val currentConstant by collectLastValue(viewModel.hapticsToPlay)
+ assertThat(currentConstant).isEqualTo(HapticFeedbackConstants.REJECT)
+ }
+
private suspend fun TestScope.showAuthenticated(
authenticatedModality: BiometricModality,
expectConfirmation: Boolean,
@@ -499,6 +527,7 @@
val messageVisible by collectLastValue(viewModel.isIndicatorMessageVisible)
val size by collectLastValue(viewModel.size)
val legacyState by collectLastValue(viewModel.legacyState)
+ val confirmationRequired by collectLastValue(viewModel.isConfirmationRequired)
if (testCase.isCoex && testCase.authenticatedByFingerprint) {
viewModel.ensureFingerprintHasStarted(isDelayed = true)
@@ -507,7 +536,11 @@
viewModel.showHelp(helpMessage)
assertThat(size).isEqualTo(PromptSize.MEDIUM)
- assertThat(legacyState).isEqualTo(AuthBiometricView.STATE_PENDING_CONFIRMATION)
+ if (confirmationRequired == true) {
+ assertThat(legacyState).isEqualTo(AuthBiometricView.STATE_PENDING_CONFIRMATION)
+ } else {
+ assertThat(legacyState).isEqualTo(AuthBiometricView.STATE_AUTHENTICATED)
+ }
assertThat(message).isEqualTo(PromptMessage.Help(helpMessage))
assertThat(messageVisible).isTrue()
assertThat(authenticating).isFalse()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/charging/WiredChargingRippleControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/charging/WiredChargingRippleControllerTest.kt
index d6cafcb..5a5c058 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/charging/WiredChargingRippleControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/charging/WiredChargingRippleControllerTest.kt
@@ -211,7 +211,7 @@
context.resources.getFloat(R.dimen.physical_charger_port_location_normalized_y)
val expectedCenterX: Float
val expectedCenterY: Float
- when (context.display.rotation) {
+ when (checkNotNull(context.display).rotation) {
Surface.ROTATION_90 -> {
expectedCenterX = width * normalizedPortPosY
expectedCenterY = height * (1 - normalizedPortPosX)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlViewHolderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlViewHolderTest.kt
index 42f28c8..2ae342a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlViewHolderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlViewHolderTest.kt
@@ -125,7 +125,7 @@
control
)
cvh.bindData(cws, false)
- val chevronIcon = baseLayout.findViewById<View>(R.id.chevron_icon)
+ val chevronIcon = baseLayout.requireViewById<View>(R.id.chevron_icon)
assertThat(chevronIcon.visibility).isEqualTo(View.VISIBLE)
}
@@ -138,4 +138,4 @@
private val DRAWABLE = GradientDrawable()
private val COLOR = ColorStateList.valueOf(0xffff00)
private val DEFAULT_CONTROL = Control.StatelessBuilder(
- CONTROL_ID, mock(PendingIntent::class.java)).build()
\ No newline at end of file
+ CONTROL_ID, mock(PendingIntent::class.java)).build()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt
index fcd6568..a400ff9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt
@@ -365,7 +365,8 @@
val selectedItems =
listOf(
SelectedItem.StructureItem(
- StructureInfo(ComponentName.unflattenFromString("pkg/.cls1"), "a", ArrayList())
+ StructureInfo(checkNotNull(ComponentName.unflattenFromString("pkg/.cls1")),
+ "a", ArrayList())
),
)
preferredPanelRepository.setSelectedComponent(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeUiTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeUiTest.java
index 49cdfa7..7311f4a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeUiTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeUiTest.java
@@ -33,15 +33,14 @@
import android.app.AlarmManager;
import android.os.Handler;
import android.os.HandlerThread;
+import android.testing.TestableLooper;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
-import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.systemui.DejankUtils;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.phone.DozeParameters;
-import com.android.systemui.tuner.TunerService;
import com.android.systemui.util.wakelock.WakeLockFake;
import org.junit.After;
@@ -53,6 +52,7 @@
@RunWith(AndroidJUnit4.class)
@SmallTest
+@TestableLooper.RunWithLooper
public class DozeUiTest extends SysuiTestCase {
@Mock
@@ -62,23 +62,19 @@
@Mock
private DozeParameters mDozeParameters;
@Mock
- private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
- @Mock
private DozeHost mHost;
@Mock
private DozeLog mDozeLog;
- @Mock
- private TunerService mTunerService;
private WakeLockFake mWakeLock;
private Handler mHandler;
private HandlerThread mHandlerThread;
private DozeUi mDozeUi;
- @Mock
- private StatusBarStateController mStatusBarStateController;
@Before
public void setUp() throws Exception {
+ allowTestableLooperAsMainThread();
MockitoAnnotations.initMocks(this);
+ DejankUtils.setImmediate(true);
mHandlerThread = new HandlerThread("DozeUiTest");
mHandlerThread.start();
@@ -86,12 +82,13 @@
mHandler = mHandlerThread.getThreadHandler();
mDozeUi = new DozeUi(mContext, mAlarmManager, mWakeLock, mHost, mHandler,
- mDozeParameters, mStatusBarStateController, mDozeLog);
+ mDozeParameters, mDozeLog);
mDozeUi.setDozeMachine(mMachine);
}
@After
public void tearDown() throws Exception {
+ DejankUtils.setImmediate(false);
mHandlerThread.quit();
mHandler = null;
mHandlerThread = null;
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 d8d3f92..b05de48 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -224,7 +224,7 @@
mScreenOffAnimationController, mAuthController, mShadeExpansionStateManager,
mShadeWindowLogger);
mFeatureFlags = new FakeFeatureFlags();
-
+ mFeatureFlags.set(Flags.KEYGUARD_WM_STATE_REFACTOR, false);
DejankUtils.setImmediate(true);
@@ -593,6 +593,7 @@
TestableLooper.get(this).processAllMessages();
assertFalse(mViewMediator.isShowingAndNotOccluded());
+ verify(mKeyguardUnlockAnimationController).notifyFinishedKeyguardExitAnimation(false);
}
@Test
@@ -609,6 +610,7 @@
TestableLooper.get(this).processAllMessages();
assertTrue(mViewMediator.isShowingAndNotOccluded());
+ verify(mKeyguardUnlockAnimationController).notifyFinishedKeyguardExitAnimation(true);
}
@Test
@@ -617,6 +619,9 @@
startMockKeyguardExitAnimation();
cancelMockKeyguardExitAnimation();
+ // Calling cancel above results in keyguard not visible, as there is no pending lock
+ verify(mKeyguardUnlockAnimationController).notifyFinishedKeyguardExitAnimation(false);
+
mViewMediator.maybeHandlePendingLock();
TestableLooper.get(this).processAllMessages();
@@ -631,9 +636,15 @@
@Test
@TestableLooper.RunWithLooper(setAsMainLooper = true)
- public void testStartKeyguardExitAnimation_expectSurfaceBehindRemoteAnimation() {
+ public void testStartKeyguardExitAnimation_expectSurfaceBehindRemoteAnimationAndExits() {
startMockKeyguardExitAnimation();
assertTrue(mViewMediator.isAnimatingBetweenKeyguardAndSurfaceBehind());
+
+ mViewMediator.mViewMediatorCallback.keyguardDonePending(true,
+ mUpdateMonitor.getCurrentUser());
+ mViewMediator.mViewMediatorCallback.readyForKeyguardDone();
+ TestableLooper.get(this).processAllMessages();
+ verify(mKeyguardUnlockAnimationController).notifyFinishedKeyguardExitAnimation(false);
}
/**
@@ -946,7 +957,8 @@
mSystemClock,
mDispatcher,
() -> mDreamingToLockscreenTransitionViewModel,
- mSystemPropertiesHelper);
+ mSystemPropertiesHelper,
+ () -> mock(WindowManagerLockscreenVisibilityManager.class));
mViewMediator.start();
mViewMediator.registerCentralSurfaces(mCentralSurfaces, null, null, null, null, null);
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 85ee0e4..fe5b812 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
@@ -52,6 +52,7 @@
import com.android.systemui.dump.logcatLogBuffer
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR
+import com.android.systemui.flags.Flags.KEYGUARD_WM_STATE_REFACTOR
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
@@ -168,7 +169,11 @@
biometricSettingsRepository = FakeBiometricSettingsRepository()
deviceEntryFingerprintAuthRepository = FakeDeviceEntryFingerprintAuthRepository()
trustRepository = FakeTrustRepository()
- featureFlags = FakeFeatureFlags().apply { set(FACE_AUTH_REFACTOR, true) }
+ featureFlags =
+ FakeFeatureFlags().apply {
+ set(FACE_AUTH_REFACTOR, true)
+ set(KEYGUARD_WM_STATE_REFACTOR, false)
+ }
val withDeps =
KeyguardInteractorFactory.create(
featureFlags = featureFlags,
@@ -332,9 +337,6 @@
)
.isFalse()
- whenever(faceManager.sensorPropertiesInternal).thenReturn(null)
- assertThat(createDeviceEntryFaceAuthRepositoryImpl().isDetectionSupported).isFalse()
-
whenever(faceManager.sensorPropertiesInternal).thenReturn(listOf())
assertThat(createDeviceEntryFaceAuthRepositoryImpl().isDetectionSupported).isFalse()
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 5e3376a..5ead16b 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
@@ -63,6 +63,7 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
+import org.mockito.Mockito.atLeastOnce
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@@ -193,7 +194,7 @@
assertThat(underTest.isKeyguardShowing()).isFalse()
val captor = argumentCaptor<KeyguardStateController.Callback>()
- verify(keyguardStateController).addCallback(captor.capture())
+ verify(keyguardStateController, atLeastOnce()).addCallback(captor.capture())
whenever(keyguardStateController.isShowing).thenReturn(true)
captor.value.onKeyguardShowingChanged()
@@ -255,7 +256,7 @@
assertThat(latest).isFalse()
val captor = argumentCaptor<KeyguardStateController.Callback>()
- verify(keyguardStateController).addCallback(captor.capture())
+ verify(keyguardStateController, atLeastOnce()).addCallback(captor.capture())
whenever(keyguardStateController.isOccluded).thenReturn(true)
captor.value.onKeyguardShowingChanged()
@@ -280,7 +281,7 @@
assertThat(isKeyguardUnlocked).isFalse()
val captor = argumentCaptor<KeyguardStateController.Callback>()
- verify(keyguardStateController).addCallback(captor.capture())
+ verify(keyguardStateController, atLeastOnce()).addCallback(captor.capture())
whenever(keyguardStateController.isUnlocked).thenReturn(true)
captor.value.onUnlockedChanged()
@@ -454,7 +455,7 @@
assertThat(latest).isFalse()
val captor = argumentCaptor<KeyguardStateController.Callback>()
- verify(keyguardStateController).addCallback(captor.capture())
+ verify(keyguardStateController, atLeastOnce()).addCallback(captor.capture())
whenever(keyguardStateController.isKeyguardGoingAway).thenReturn(true)
captor.value.onKeyguardGoingAwayChanged()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardSurfaceBehindRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardSurfaceBehindRepositoryImplTest.kt
new file mode 100644
index 0000000..bed959f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardSurfaceBehindRepositoryImplTest.kt
@@ -0,0 +1,60 @@
+/*
+ * 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.data.repository
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectValues
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+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
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class KeyguardSurfaceBehindRepositoryImplTest : SysuiTestCase() {
+ private val testScope = TestScope()
+
+ private lateinit var underTest: KeyguardSurfaceBehindRepositoryImpl
+
+ @Before
+ fun setUp() {
+ underTest = KeyguardSurfaceBehindRepositoryImpl()
+ }
+
+ @Test
+ fun testSetAnimatingSurface() {
+ testScope.runTest {
+ val values by collectValues(underTest.isAnimatingSurface)
+
+ runCurrent()
+ underTest.setAnimatingSurface(true)
+ runCurrent()
+ underTest.setAnimatingSurface(false)
+ runCurrent()
+
+ // Default (first) value should be false.
+ assertThat(values).isEqualTo(listOf(false, true, false))
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt
new file mode 100644
index 0000000..e2bf2f8
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt
@@ -0,0 +1,190 @@
+/*
+ * 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.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.shade.data.repository.FakeShadeRepository
+import dagger.Lazy
+import junit.framework.Assert.assertEquals
+import junit.framework.Assert.assertTrue
+import junit.framework.Assert.fail
+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
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class FromLockscreenTransitionInteractorTest : KeyguardTransitionInteractorTestCase() {
+ private lateinit var underTest: FromLockscreenTransitionInteractor
+
+ // Override the fromLockscreenTransitionInteractor provider from the superclass so our underTest
+ // interactor is provided to any classes that need it.
+ override var fromLockscreenTransitionInteractorLazy: Lazy<FromLockscreenTransitionInteractor>? =
+ Lazy {
+ underTest
+ }
+
+ @Before
+ override fun setUp() {
+ super.setUp()
+
+ underTest =
+ FromLockscreenTransitionInteractor(
+ transitionRepository = super.transitionRepository,
+ transitionInteractor = super.transitionInteractor,
+ scope = super.testScope.backgroundScope,
+ keyguardInteractor = super.keyguardInteractor,
+ flags = FakeFeatureFlags(),
+ shadeRepository = FakeShadeRepository(),
+ )
+ }
+
+ @Test
+ fun testSurfaceBehindVisibility_nonNullOnlyForRelevantTransitions() =
+ testScope.runTest {
+ val values by collectValues(underTest.surfaceBehindVisibility)
+ runCurrent()
+
+ // Transition-specific surface visibility should be null ("don't care") initially.
+ assertEquals(
+ listOf(
+ null,
+ ),
+ values
+ )
+
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.STARTED,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ )
+ )
+
+ runCurrent()
+
+ assertEquals(
+ listOf(
+ null, // LOCKSCREEN -> AOD does not have any specific surface visibility.
+ ),
+ values
+ )
+
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.STARTED,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GONE,
+ )
+ )
+
+ runCurrent()
+
+ assertEquals(
+ listOf(
+ null,
+ true, // Surface is made visible immediately during LOCKSCREEN -> GONE
+ ),
+ values
+ )
+ }
+
+ @Test
+ fun testSurfaceBehindModel() =
+ testScope.runTest {
+ val values by collectValues(underTest.surfaceBehindModel)
+ runCurrent()
+
+ assertEquals(
+ values,
+ listOf(
+ null, // We should start null ("don't care").
+ )
+ )
+
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.STARTED,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ )
+ )
+ runCurrent()
+
+ assertEquals(
+ listOf(
+ null, // LOCKSCREEN -> AOD does not have specific view params.
+ ),
+ values
+ )
+
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.STARTED,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GONE,
+ )
+ )
+ runCurrent()
+
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.RUNNING,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GONE,
+ value = 0.01f,
+ )
+ )
+ runCurrent()
+
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.RUNNING,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GONE,
+ value = 0.99f,
+ )
+ )
+ runCurrent()
+
+ assertEquals(3, values.size)
+ val model1percent = values[1]
+ val model99percent = values[2]
+
+ try {
+ // We should initially have an alpha of 0f when unlocking, so the surface is not
+ // visible
+ // while lockscreen UI animates out.
+ assertEquals(0f, model1percent!!.alpha)
+
+ // By the end it should probably be visible.
+ assertTrue(model99percent!!.alpha > 0f)
+ } catch (e: NullPointerException) {
+ fail("surfaceBehindModel was unexpectedly null.")
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt
new file mode 100644
index 0000000..85bc374
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt
@@ -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.systemui.keyguard.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.util.mockito.mock
+import dagger.Lazy
+import junit.framework.Assert.assertEquals
+import junit.framework.Assert.assertTrue
+import junit.framework.Assert.fail
+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
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class FromPrimaryBouncerTransitionInteractorTest : KeyguardTransitionInteractorTestCase() {
+ private lateinit var underTest: FromPrimaryBouncerTransitionInteractor
+
+ // Override the fromPrimaryBouncerTransitionInteractor provider from the superclass so our
+ // underTest interactor is provided to any classes that need it.
+ override var fromPrimaryBouncerTransitionInteractorLazy:
+ Lazy<FromPrimaryBouncerTransitionInteractor>? =
+ Lazy {
+ underTest
+ }
+
+ @Before
+ override fun setUp() {
+ super.setUp()
+
+ underTest =
+ FromPrimaryBouncerTransitionInteractor(
+ transitionRepository = super.transitionRepository,
+ transitionInteractor = super.transitionInteractor,
+ scope = super.testScope.backgroundScope,
+ keyguardInteractor = super.keyguardInteractor,
+ flags = FakeFeatureFlags(),
+ keyguardSecurityModel = mock(),
+ )
+ }
+
+ @Test
+ fun testSurfaceBehindVisibility() =
+ testScope.runTest {
+ val values by collectValues(underTest.surfaceBehindVisibility)
+ runCurrent()
+
+ // Transition-specific surface visibility should be null ("don't care") initially.
+ assertEquals(
+ listOf(
+ null,
+ ),
+ values
+ )
+
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.STARTED,
+ from = KeyguardState.PRIMARY_BOUNCER,
+ to = KeyguardState.LOCKSCREEN,
+ )
+ )
+
+ runCurrent()
+
+ assertEquals(
+ listOf(
+ null, // PRIMARY_BOUNCER -> LOCKSCREEN does not have any specific visibility.
+ ),
+ values
+ )
+
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.STARTED,
+ from = KeyguardState.PRIMARY_BOUNCER,
+ to = KeyguardState.GONE,
+ )
+ )
+
+ runCurrent()
+
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.RUNNING,
+ from = KeyguardState.PRIMARY_BOUNCER,
+ to = KeyguardState.GONE,
+ value = 0.01f,
+ )
+ )
+
+ runCurrent()
+
+ assertEquals(
+ listOf(
+ null,
+ false, // Surface is only made visible once the bouncer UI animates out.
+ ),
+ values
+ )
+
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.STARTED,
+ from = KeyguardState.PRIMARY_BOUNCER,
+ to = KeyguardState.GONE,
+ value = 0.99f,
+ )
+ )
+
+ runCurrent()
+
+ assertEquals(
+ listOf(
+ null,
+ false,
+ true, // Surface should eventually be visible.
+ ),
+ values
+ )
+ }
+
+ @Test
+ fun testSurfaceBehindModel() =
+ testScope.runTest {
+ val values by collectValues(underTest.surfaceBehindModel)
+
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.STARTED,
+ from = KeyguardState.PRIMARY_BOUNCER,
+ to = KeyguardState.LOCKSCREEN,
+ )
+ )
+ runCurrent()
+
+ assertEquals(
+ listOf(
+ null, // PRIMARY_BOUNCER -> LOCKSCREEN does not have specific view params.
+ ),
+ values
+ )
+
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.STARTED,
+ from = KeyguardState.PRIMARY_BOUNCER,
+ to = KeyguardState.GONE,
+ )
+ )
+ runCurrent()
+
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.RUNNING,
+ from = KeyguardState.PRIMARY_BOUNCER,
+ to = KeyguardState.GONE,
+ value = 0.01f,
+ )
+ )
+ runCurrent()
+
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.RUNNING,
+ from = KeyguardState.PRIMARY_BOUNCER,
+ to = KeyguardState.GONE,
+ value = 0.99f,
+ )
+ )
+ runCurrent()
+
+ assertEquals(3, values.size)
+ val model1percent = values[1]
+ val model99percent = values[2]
+
+ try {
+ // We should initially have an alpha of 0f when unlocking, so the surface is not
+ // visible
+ // while lockscreen UI animates out.
+ assertEquals(0f, model1percent!!.alpha)
+
+ // By the end it should probably be visible.
+ assertTrue(model99percent!!.alpha > 0f)
+ } catch (e: NullPointerException) {
+ fail("surfaceBehindModel was unexpectedly null.")
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorTest.kt
new file mode 100644
index 0000000..fdcc66b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorTest.kt
@@ -0,0 +1,169 @@
+/*
+ * 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.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.keyguard.data.repository.FakeKeyguardSurfaceBehindRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.KeyguardSurfaceBehindModel
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.util.mockito.whenever
+import junit.framework.Assert.assertEquals
+import junit.framework.Assert.assertTrue
+import kotlinx.coroutines.flow.flowOf
+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
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations.initMocks
+
+@SmallTest
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
+@kotlinx.coroutines.ExperimentalCoroutinesApi
+class KeyguardSurfaceBehindInteractorTest : SysuiTestCase() {
+
+ private lateinit var underTest: KeyguardSurfaceBehindInteractor
+ private lateinit var repository: FakeKeyguardSurfaceBehindRepository
+
+ @Mock
+ private lateinit var fromLockscreenTransitionInteractor: FromLockscreenTransitionInteractor
+ @Mock
+ private lateinit var fromPrimaryBouncerTransitionInteractor:
+ FromPrimaryBouncerTransitionInteractor
+
+ private val lockscreenSurfaceBehindModel = KeyguardSurfaceBehindModel(alpha = 0.33f)
+ private val primaryBouncerSurfaceBehindModel = KeyguardSurfaceBehindModel(alpha = 0.66f)
+
+ private val testScope = TestScope()
+
+ private lateinit var transitionRepository: FakeKeyguardTransitionRepository
+ private lateinit var transitionInteractor: KeyguardTransitionInteractor
+
+ @Before
+ fun setUp() {
+ initMocks(this)
+
+ whenever(fromLockscreenTransitionInteractor.surfaceBehindModel)
+ .thenReturn(flowOf(lockscreenSurfaceBehindModel))
+ whenever(fromPrimaryBouncerTransitionInteractor.surfaceBehindModel)
+ .thenReturn(flowOf(primaryBouncerSurfaceBehindModel))
+
+ transitionRepository = FakeKeyguardTransitionRepository()
+
+ transitionInteractor =
+ KeyguardTransitionInteractorFactory.create(
+ scope = testScope.backgroundScope,
+ repository = transitionRepository,
+ )
+ .keyguardTransitionInteractor
+
+ repository = FakeKeyguardSurfaceBehindRepository()
+ underTest =
+ KeyguardSurfaceBehindInteractor(
+ repository = repository,
+ fromLockscreenInteractor = fromLockscreenTransitionInteractor,
+ fromPrimaryBouncerInteractor = fromPrimaryBouncerTransitionInteractor,
+ transitionInteractor = transitionInteractor,
+ )
+ }
+
+ @Test
+ fun viewParamsSwitchToCorrectFlow() =
+ testScope.runTest {
+ val values by collectValues(underTest.viewParams)
+
+ // Start on the LOCKSCREEN.
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.STARTED,
+ from = KeyguardState.AOD,
+ to = KeyguardState.LOCKSCREEN,
+ )
+ )
+
+ runCurrent()
+
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.FINISHED,
+ from = KeyguardState.AOD,
+ to = KeyguardState.LOCKSCREEN,
+ )
+ )
+
+ runCurrent()
+
+ // We're on LOCKSCREEN; we should be using the default params.
+ assertEquals(1, values.size)
+ assertTrue(values[0].alpha == 0f)
+
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.STARTED,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GONE,
+ )
+ )
+
+ runCurrent()
+
+ // We're going from LOCKSCREEN -> GONE, we should be using the lockscreen interactor's
+ // surface behind model.
+ assertEquals(2, values.size)
+ assertEquals(values[1], lockscreenSurfaceBehindModel)
+
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.STARTED,
+ from = KeyguardState.PRIMARY_BOUNCER,
+ to = KeyguardState.GONE,
+ )
+ )
+
+ runCurrent()
+
+ // We're going from PRIMARY_BOUNCER -> GONE, we should be using the bouncer interactor's
+ // surface behind model.
+ assertEquals(3, values.size)
+ assertEquals(values[2], primaryBouncerSurfaceBehindModel)
+
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.FINISHED,
+ from = KeyguardState.PRIMARY_BOUNCER,
+ to = KeyguardState.GONE,
+ )
+ )
+
+ runCurrent()
+
+ // Once PRIMARY_BOUNCER -> GONE finishes, we should be using default params, which is
+ // alpha=1f when we're GONE.
+ assertEquals(4, values.size)
+ assertEquals(1f, values[3].alpha)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTestCase.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTestCase.kt
new file mode 100644
index 0000000..8db19ae
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTestCase.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.keyguard.domain.interactor
+
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.util.mockito.mock
+import dagger.Lazy
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+
+open class KeyguardTransitionInteractorTestCase : SysuiTestCase() {
+ val testDispatcher = StandardTestDispatcher()
+ val testScope = TestScope(testDispatcher)
+
+ lateinit var keyguardRepository: FakeKeyguardRepository
+ lateinit var transitionRepository: FakeKeyguardTransitionRepository
+
+ lateinit var keyguardInteractor: KeyguardInteractor
+ lateinit var transitionInteractor: KeyguardTransitionInteractor
+
+ /**
+ * Replace these lazy providers with non-null ones if you want test dependencies to use a real
+ * instance of the interactor for the test.
+ */
+ open var fromLockscreenTransitionInteractorLazy: Lazy<FromLockscreenTransitionInteractor>? =
+ null
+ open var fromPrimaryBouncerTransitionInteractorLazy:
+ Lazy<FromPrimaryBouncerTransitionInteractor>? =
+ null
+
+ open fun setUp() {
+ keyguardRepository = FakeKeyguardRepository()
+ transitionRepository = FakeKeyguardTransitionRepository()
+
+ keyguardInteractor =
+ KeyguardInteractorFactory.create(repository = keyguardRepository).keyguardInteractor
+
+ transitionInteractor =
+ KeyguardTransitionInteractorFactory.create(
+ repository = transitionRepository,
+ keyguardInteractor = keyguardInteractor,
+ scope = testScope.backgroundScope,
+ fromLockscreenTransitionInteractor = fromLockscreenTransitionInteractorLazy
+ ?: Lazy { mock() },
+ fromPrimaryBouncerTransitionInteractor =
+ fromPrimaryBouncerTransitionInteractorLazy ?: Lazy { mock() },
+ )
+ .also {
+ fromLockscreenTransitionInteractorLazy = it.fromLockscreenTransitionInteractor
+ fromPrimaryBouncerTransitionInteractorLazy =
+ it.fromPrimaryBouncerTransitionInteractor
+ }
+ .keyguardTransitionInteractor
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index aa6bd4e..4b221a0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -104,12 +104,21 @@
whenever(keyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(PIN)
- featureFlags = FakeFeatureFlags().apply { set(Flags.FACE_AUTH_REFACTOR, true) }
+ featureFlags =
+ FakeFeatureFlags().apply {
+ set(Flags.FACE_AUTH_REFACTOR, true)
+ set(Flags.KEYGUARD_WM_STATE_REFACTOR, false)
+ }
transitionInteractor =
KeyguardTransitionInteractorFactory.create(
scope = testScope,
repository = transitionRepository,
+ keyguardInteractor = createKeyguardInteractor(),
+ fromLockscreenTransitionInteractor = { fromLockscreenTransitionInteractor },
+ fromPrimaryBouncerTransitionInteractor = {
+ fromPrimaryBouncerTransitionInteractor
+ },
)
.keyguardTransitionInteractor
@@ -119,6 +128,7 @@
keyguardInteractor = createKeyguardInteractor(),
transitionRepository = transitionRepository,
transitionInteractor = transitionInteractor,
+ flags = featureFlags,
shadeRepository = shadeRepository,
)
.apply { start() }
@@ -129,6 +139,7 @@
keyguardInteractor = createKeyguardInteractor(),
transitionRepository = transitionRepository,
transitionInteractor = transitionInteractor,
+ flags = featureFlags,
keyguardSecurityModel = keyguardSecurityModel,
)
.apply { start() }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt
index baa5ee8..1dcb55d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt
@@ -44,6 +44,8 @@
import com.android.systemui.keyguard.util.IndicationHelper
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.ActivityStarter.OnDismissAction
+import com.android.systemui.power.data.repository.FakePowerRepository
+import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
@@ -80,6 +82,7 @@
private lateinit var configurationRepository: FakeConfigurationRepository
private lateinit var featureFlags: FakeFeatureFlags
private lateinit var trustRepository: FakeTrustRepository
+ private lateinit var powerRepository: FakePowerRepository
@Mock private lateinit var indicationHelper: IndicationHelper
@Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
@@ -102,6 +105,7 @@
set(Flags.DELAY_BOUNCER, false)
}
trustRepository = FakeTrustRepository()
+ powerRepository = FakePowerRepository()
underTest =
OccludingAppDeviceEntryInteractor(
BiometricMessageInteractor(
@@ -145,6 +149,14 @@
testScope.backgroundScope,
mockedContext,
activityStarter,
+ PowerInteractor(
+ powerRepository,
+ keyguardRepository,
+ falsingCollector = mock(),
+ screenOffAnimationController = mock(),
+ statusBarStateController = mock(),
+ ),
+ FakeFeatureFlags().apply { set(Flags.FP_LISTEN_OCCLUDING_APPS, true) },
)
}
@@ -160,6 +172,18 @@
}
@Test
+ fun fingerprintSuccess_notInteractive_doesNotGoToHomeScreen() =
+ testScope.runTest {
+ givenOnOccludingApp(true)
+ powerRepository.setInteractive(false)
+ fingerprintAuthRepository.setAuthenticationStatus(
+ SuccessFingerprintAuthenticationStatus(0, true)
+ )
+ runCurrent()
+ verifyNeverGoToHomeScreen()
+ }
+
+ @Test
fun fingerprintSuccess_notOnOccludingApp_doesNotGoToHomeScreen() =
testScope.runTest {
givenOnOccludingApp(false)
@@ -291,6 +315,7 @@
}
private fun givenOnOccludingApp(isOnOccludingApp: Boolean) {
+ powerRepository.setInteractive(true)
keyguardRepository.setKeyguardOccluded(isOnOccludingApp)
keyguardRepository.setKeyguardShowing(isOnOccludingApp)
bouncerRepository.setPrimaryShow(!isOnOccludingApp)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt
new file mode 100644
index 0000000..73ecae5
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt
@@ -0,0 +1,412 @@
+/*
+ * 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.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.util.mockito.whenever
+import junit.framework.Assert.assertEquals
+import kotlinx.coroutines.flow.MutableStateFlow
+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
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations.initMocks
+
+@SmallTest
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
+@kotlinx.coroutines.ExperimentalCoroutinesApi
+class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() {
+
+ private lateinit var underTest: WindowManagerLockscreenVisibilityInteractor
+
+ @Mock private lateinit var surfaceBehindInteractor: KeyguardSurfaceBehindInteractor
+ @Mock
+ private lateinit var fromLockscreenTransitionInteractor: FromLockscreenTransitionInteractor
+ @Mock
+ private lateinit var fromPrimaryBouncerTransitionInteractor:
+ FromPrimaryBouncerTransitionInteractor
+
+ private val lockscreenSurfaceVisibilityFlow = MutableStateFlow<Boolean?>(false)
+ private val primaryBouncerSurfaceVisibilityFlow = MutableStateFlow<Boolean?>(false)
+ private val surfaceBehindIsAnimatingFlow = MutableStateFlow(false)
+
+ private val testScope = TestScope()
+
+ private lateinit var keyguardInteractor: KeyguardInteractor
+ private lateinit var transitionRepository: FakeKeyguardTransitionRepository
+ private lateinit var transitionInteractor: KeyguardTransitionInteractor
+
+ @Before
+ fun setUp() {
+ initMocks(this)
+
+ whenever(fromLockscreenTransitionInteractor.surfaceBehindVisibility)
+ .thenReturn(lockscreenSurfaceVisibilityFlow)
+ whenever(fromPrimaryBouncerTransitionInteractor.surfaceBehindVisibility)
+ .thenReturn(primaryBouncerSurfaceVisibilityFlow)
+ whenever(surfaceBehindInteractor.isAnimatingSurface)
+ .thenReturn(surfaceBehindIsAnimatingFlow)
+
+ transitionRepository = FakeKeyguardTransitionRepository()
+
+ transitionInteractor =
+ KeyguardTransitionInteractorFactory.create(
+ scope = testScope.backgroundScope,
+ repository = transitionRepository,
+ )
+ .also { keyguardInteractor = it.keyguardInteractor }
+ .keyguardTransitionInteractor
+
+ underTest =
+ WindowManagerLockscreenVisibilityInteractor(
+ keyguardInteractor = keyguardInteractor,
+ transitionInteractor = transitionInteractor,
+ surfaceBehindInteractor = surfaceBehindInteractor,
+ fromLockscreenTransitionInteractor,
+ fromPrimaryBouncerTransitionInteractor,
+ )
+ }
+
+ @Test
+ fun surfaceBehindVisibility_switchesToCorrectFlow() =
+ testScope.runTest {
+ val values by collectValues(underTest.surfaceBehindVisibility)
+
+ // Start on LOCKSCREEN.
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.STARTED,
+ from = KeyguardState.AOD,
+ to = KeyguardState.LOCKSCREEN,
+ )
+ )
+
+ runCurrent()
+
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.FINISHED,
+ from = KeyguardState.AOD,
+ to = KeyguardState.LOCKSCREEN,
+ )
+ )
+
+ runCurrent()
+
+ assertEquals(
+ listOf(
+ false, // We should start with the surface invisible on LOCKSCREEN.
+ ),
+ values
+ )
+
+ val lockscreenSpecificSurfaceVisibility = true
+ lockscreenSurfaceVisibilityFlow.emit(lockscreenSpecificSurfaceVisibility)
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.STARTED,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GONE,
+ )
+ )
+
+ runCurrent()
+
+ // We started a transition from LOCKSCREEN, we should be using the value emitted by the
+ // lockscreenSurfaceVisibilityFlow.
+ assertEquals(
+ listOf(
+ false,
+ lockscreenSpecificSurfaceVisibility,
+ ),
+ values
+ )
+
+ // Go back to LOCKSCREEN, since we won't emit 'true' twice in a row.
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.STARTED,
+ from = KeyguardState.GONE,
+ to = KeyguardState.LOCKSCREEN,
+ )
+ )
+ runCurrent()
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.FINISHED,
+ from = KeyguardState.GONE,
+ to = KeyguardState.LOCKSCREEN,
+ )
+ )
+ runCurrent()
+
+ assertEquals(
+ listOf(
+ false,
+ lockscreenSpecificSurfaceVisibility,
+ false, // FINISHED (LOCKSCREEN)
+ ),
+ values
+ )
+
+ val bouncerSpecificVisibility = true
+ primaryBouncerSurfaceVisibilityFlow.emit(bouncerSpecificVisibility)
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.STARTED,
+ from = KeyguardState.PRIMARY_BOUNCER,
+ to = KeyguardState.GONE,
+ )
+ )
+
+ runCurrent()
+
+ // We started a transition from PRIMARY_BOUNCER, we should be using the value emitted by
+ // the
+ // primaryBouncerSurfaceVisibilityFlow.
+ assertEquals(
+ listOf(
+ false,
+ lockscreenSpecificSurfaceVisibility,
+ false,
+ bouncerSpecificVisibility,
+ ),
+ values
+ )
+ }
+
+ @Test
+ fun testUsingGoingAwayAnimation_duringTransitionToGone() =
+ testScope.runTest {
+ val values by collectValues(underTest.usingKeyguardGoingAwayAnimation)
+
+ // Start on LOCKSCREEN.
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.STARTED,
+ from = KeyguardState.AOD,
+ to = KeyguardState.LOCKSCREEN,
+ )
+ )
+ runCurrent()
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.FINISHED,
+ from = KeyguardState.AOD,
+ to = KeyguardState.LOCKSCREEN,
+ )
+ )
+ runCurrent()
+
+ assertEquals(
+ listOf(
+ false, // Not using the animation when we're just sitting on LOCKSCREEN.
+ ),
+ values
+ )
+
+ surfaceBehindIsAnimatingFlow.emit(true)
+ runCurrent()
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.FINISHED,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GONE,
+ )
+ )
+ runCurrent()
+
+ assertEquals(
+ listOf(
+ false,
+ true, // Still true when we're FINISHED -> GONE, since we're still animating.
+ ),
+ values
+ )
+
+ surfaceBehindIsAnimatingFlow.emit(false)
+ runCurrent()
+
+ assertEquals(
+ listOf(
+ false,
+ true,
+ false, // False once the animation ends.
+ ),
+ values
+ )
+ }
+
+ @Test
+ fun testNotUsingGoingAwayAnimation_evenWhenAnimating_ifStateIsNotGone() =
+ testScope.runTest {
+ val values by collectValues(underTest.usingKeyguardGoingAwayAnimation)
+
+ // Start on LOCKSCREEN.
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.STARTED,
+ from = KeyguardState.AOD,
+ to = KeyguardState.LOCKSCREEN,
+ )
+ )
+ runCurrent()
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.FINISHED,
+ from = KeyguardState.AOD,
+ to = KeyguardState.LOCKSCREEN,
+ )
+ )
+ runCurrent()
+
+ assertEquals(
+ listOf(
+ false, // Not using the animation when we're just sitting on LOCKSCREEN.
+ ),
+ values
+ )
+
+ surfaceBehindIsAnimatingFlow.emit(true)
+ runCurrent()
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.STARTED,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GONE,
+ )
+ )
+ runCurrent()
+
+ assertEquals(
+ listOf(
+ false,
+ true, // We're happily animating while transitioning to gone.
+ ),
+ values
+ )
+
+ // Oh no, we're still surfaceBehindAnimating=true, but no longer transitioning to GONE.
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.STARTED,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ )
+ )
+ runCurrent()
+
+ assertEquals(
+ listOf(
+ false,
+ true,
+ false, // Despite the animator still running, this should be false.
+ ),
+ values
+ )
+
+ surfaceBehindIsAnimatingFlow.emit(false)
+ runCurrent()
+
+ assertEquals(
+ listOf(
+ false,
+ true,
+ false, // The animator ending should have no effect.
+ ),
+ values
+ )
+ }
+
+ @Test
+ fun lockscreenVisibility_visibleWhenGone() =
+ testScope.runTest {
+ val values by collectValues(underTest.lockscreenVisibility)
+
+ // Start on LOCKSCREEN.
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.STARTED,
+ from = KeyguardState.AOD,
+ to = KeyguardState.LOCKSCREEN,
+ )
+ )
+ runCurrent()
+
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.FINISHED,
+ from = KeyguardState.AOD,
+ to = KeyguardState.LOCKSCREEN,
+ )
+ )
+ runCurrent()
+
+ assertEquals(
+ listOf(
+ true, // Unsurprisingly, we should start with the lockscreen visible on
+ // LOCKSCREEN.
+ ),
+ values
+ )
+
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.STARTED,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GONE,
+ )
+ )
+ runCurrent()
+
+ assertEquals(
+ listOf(
+ true, // Lockscreen remains visible while we're transitioning to GONE.
+ ),
+ values
+ )
+
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.FINISHED,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GONE,
+ )
+ )
+ runCurrent()
+
+ assertEquals(
+ listOf(
+ true,
+ false, // Once we're fully GONE, the lockscreen should not be visible.
+ ),
+ values
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindParamsApplierTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindParamsApplierTest.kt
new file mode 100644
index 0000000..a22f603
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindParamsApplierTest.kt
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.binder
+
+import android.testing.TestableLooper.RunWithLooper
+import android.view.RemoteAnimationTarget
+import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardViewController
+import com.android.systemui.RoboPilotTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.AnimatorTestRule
+import com.android.systemui.keyguard.domain.interactor.KeyguardSurfaceBehindInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardSurfaceBehindModel
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.time.FakeSystemClock
+import junit.framework.Assert.assertFalse
+import junit.framework.Assert.assertNull
+import junit.framework.Assert.assertTrue
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.Mockito.anyBoolean
+import org.mockito.Mockito.doAnswer
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RoboPilotTest
+@RunWithLooper(setAsMainLooper = true)
+@kotlinx.coroutines.ExperimentalCoroutinesApi
+class KeyguardSurfaceBehindParamsApplierTest : SysuiTestCase() {
+ @get:Rule val animatorTestRule = AnimatorTestRule()
+
+ private lateinit var underTest: KeyguardSurfaceBehindParamsApplier
+ private lateinit var executor: FakeExecutor
+
+ @Mock private lateinit var keyguardViewController: KeyguardViewController
+
+ @Mock private lateinit var interactor: KeyguardSurfaceBehindInteractor
+
+ @Mock private lateinit var remoteAnimationTarget: RemoteAnimationTarget
+
+ private var isAnimatingSurface: Boolean? = null
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ executor = FakeExecutor(FakeSystemClock())
+ underTest =
+ KeyguardSurfaceBehindParamsApplier(
+ executor = executor,
+ keyguardViewController = keyguardViewController,
+ interactor = interactor,
+ )
+
+ doAnswer {
+ (it.arguments[0] as Boolean).let { animating -> isAnimatingSurface = animating }
+ }
+ .whenever(interactor)
+ .setAnimatingSurface(anyBoolean())
+ }
+
+ @After
+ fun tearDown() {
+ animatorTestRule.advanceTimeBy(1000.toLong())
+ }
+
+ @Test
+ fun testNotAnimating_setParamsWithNoAnimation() {
+ underTest.viewParams =
+ KeyguardSurfaceBehindModel(
+ alpha = 0.3f,
+ translationY = 300f,
+ )
+
+ // A surface has not yet been provided, so we shouldn't have set animating to false OR true
+ // just yet.
+ assertNull(isAnimatingSurface)
+
+ underTest.applyParamsToSurface(remoteAnimationTarget)
+
+ // We should now explicitly not be animating the surface.
+ assertFalse(checkNotNull(isAnimatingSurface))
+ }
+
+ @Test
+ fun testAnimating_paramsThenSurfaceProvided() {
+ underTest.viewParams =
+ KeyguardSurfaceBehindModel(
+ animateFromAlpha = 0f,
+ alpha = 0.3f,
+ animateFromTranslationY = 0f,
+ translationY = 300f,
+ )
+
+ // A surface has not yet been provided, so we shouldn't have set animating to false OR true
+ // just yet.
+ assertNull(isAnimatingSurface)
+
+ underTest.applyParamsToSurface(remoteAnimationTarget)
+
+ // We should now be animating the surface.
+ assertTrue(checkNotNull(isAnimatingSurface))
+ }
+
+ @Test
+ fun testAnimating_surfaceThenParamsProvided() {
+ underTest.applyParamsToSurface(remoteAnimationTarget)
+
+ // The default params (which do not animate) should have been applied, so we're explicitly
+ // NOT animating yet.
+ assertFalse(checkNotNull(isAnimatingSurface))
+
+ underTest.viewParams =
+ KeyguardSurfaceBehindModel(
+ animateFromAlpha = 0f,
+ alpha = 0.3f,
+ animateFromTranslationY = 0f,
+ translationY = 300f,
+ )
+
+ // We should now be animating the surface.
+ assertTrue(checkNotNull(isAnimatingSurface))
+ }
+
+ @Test
+ fun testAnimating_thenReleased_animatingIsFalse() {
+ underTest.viewParams =
+ KeyguardSurfaceBehindModel(
+ animateFromAlpha = 0f,
+ alpha = 0.3f,
+ animateFromTranslationY = 0f,
+ translationY = 300f,
+ )
+ underTest.applyParamsToSurface(remoteAnimationTarget)
+
+ assertTrue(checkNotNull(isAnimatingSurface))
+
+ underTest.notifySurfaceReleased()
+
+ // Releasing the surface should immediately cancel animators.
+ assertFalse(checkNotNull(isAnimatingSurface))
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt
new file mode 100644
index 0000000..623c877
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt
@@ -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.systemui.keyguard.ui.binder
+
+import android.app.IActivityTaskManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.WindowManagerLockscreenVisibilityManager
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
+@kotlinx.coroutines.ExperimentalCoroutinesApi
+class WindowManagerLockscreenVisibilityManagerTest : SysuiTestCase() {
+ private lateinit var underTest: WindowManagerLockscreenVisibilityManager
+ private lateinit var executor: FakeExecutor
+
+ @Mock private lateinit var activityTaskManagerService: IActivityTaskManager
+
+ @Mock private lateinit var keyguardStateController: KeyguardStateController
+
+ @Mock private lateinit var keyguardSurfaceBehindAnimator: KeyguardSurfaceBehindParamsApplier
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ executor = FakeExecutor(FakeSystemClock())
+
+ underTest =
+ WindowManagerLockscreenVisibilityManager(
+ executor = executor,
+ activityTaskManagerService = activityTaskManagerService,
+ keyguardStateController = keyguardStateController,
+ keyguardSurfaceBehindAnimator = keyguardSurfaceBehindAnimator,
+ )
+ }
+
+ @Test
+ fun testLockscreenVisible_andAodVisible() {
+ underTest.setLockscreenShown(true)
+ underTest.setAodVisible(true)
+
+ verify(activityTaskManagerService).setLockScreenShown(true, true)
+ verifyNoMoreInteractions(activityTaskManagerService)
+ }
+
+ @Test
+ fun testGoingAway_whenLockscreenVisible_thenSurfaceMadeVisible() {
+ underTest.setLockscreenShown(true)
+ underTest.setAodVisible(true)
+
+ verify(activityTaskManagerService).setLockScreenShown(true, true)
+ verifyNoMoreInteractions(activityTaskManagerService)
+
+ underTest.setSurfaceBehindVisibility(true)
+
+ verify(activityTaskManagerService).keyguardGoingAway(anyInt())
+ verifyNoMoreInteractions(activityTaskManagerService)
+ }
+
+ @Test
+ fun testSurfaceVisible_whenLockscreenNotShowing_doesNotTriggerGoingAway() {
+ underTest.setLockscreenShown(false)
+ underTest.setAodVisible(false)
+
+ verify(activityTaskManagerService).setLockScreenShown(false, false)
+ verifyNoMoreInteractions(activityTaskManagerService)
+
+ underTest.setSurfaceBehindVisibility(true)
+
+ verifyNoMoreInteractions(activityTaskManagerService)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt
index c67f535..bfc6f31 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt
@@ -21,9 +21,7 @@
import com.android.systemui.RoboPilotTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
-import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsFingerprintViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsFingerprintViewModelTest.kt
index 80ab418..0ad14d0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsFingerprintViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsFingerprintViewModelTest.kt
@@ -31,7 +31,7 @@
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.domain.interactor.BurnInInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
import com.android.systemui.keyguard.domain.interactor.UdfpsKeyguardInteractor
import com.android.systemui.shade.data.repository.FakeShadeRepository
import com.android.systemui.statusbar.phone.SystemUIDialogManager
@@ -83,16 +83,21 @@
bouncerRepository = FakeKeyguardBouncerRepository()
transitionRepository = FakeKeyguardTransitionRepository()
shadeRepository = FakeShadeRepository()
- val transitionInteractor =
- KeyguardTransitionInteractor(
- transitionRepository,
- testScope.backgroundScope,
- )
val keyguardInteractor =
KeyguardInteractorFactory.create(
+ repository = keyguardRepository,
featureFlags = featureFlags,
)
.keyguardInteractor
+
+ val transitionInteractor =
+ KeyguardTransitionInteractorFactory.create(
+ scope = testScope.backgroundScope,
+ repository = transitionRepository,
+ keyguardInteractor = keyguardInteractor,
+ )
+ .keyguardTransitionInteractor
+
underTest =
FingerprintViewModel(
context,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModelTest.kt
index 0456824..edcaa1d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModelTest.kt
@@ -30,7 +30,7 @@
import com.android.systemui.keyguard.domain.interactor.BurnInInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
import com.android.systemui.keyguard.domain.interactor.UdfpsKeyguardInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.StatusBarState
@@ -98,15 +98,20 @@
bouncerRepository = it.bouncerRepository
}
+ val transitionInteractor =
+ KeyguardTransitionInteractorFactory.create(
+ scope = testScope.backgroundScope,
+ repository = transitionRepository,
+ keyguardInteractor = keyguardInteractor,
+ )
+ .keyguardTransitionInteractor
+
underTest =
UdfpsLockscreenViewModel(
context,
lockscreenColorResId,
alternateBouncerResId,
- KeyguardTransitionInteractor(
- transitionRepository,
- testScope.backgroundScope,
- ),
+ transitionInteractor,
UdfpsKeyguardInteractor(
configRepository,
BurnInInteractor(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
index 5b8272b0..ef51e47 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
@@ -30,6 +30,7 @@
import com.android.internal.logging.InstanceId
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardUpdateMonitorCallback
+import com.android.keyguard.TestScopeProvider
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingCollector
@@ -37,6 +38,7 @@
import com.android.systemui.dump.DumpManager
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
@@ -66,7 +68,6 @@
import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertTrue
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -132,7 +133,7 @@
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
- context.resources.configuration.locales = LocaleList(Locale.US, Locale.UK)
+ context.resources.configuration.setLocales(LocaleList(Locale.US, Locale.UK))
transitionRepository = FakeKeyguardTransitionRepository()
mediaCarouselController =
MediaCarouselController(
@@ -152,7 +153,11 @@
debugLogger,
mediaFlags,
keyguardUpdateMonitor,
- KeyguardTransitionInteractor(transitionRepository, TestScope().backgroundScope),
+ KeyguardTransitionInteractorFactory.create(
+ scope = TestScopeProvider.getTestScope().backgroundScope,
+ repository = transitionRepository,
+ )
+ .keyguardTransitionInteractor,
globalSettings
)
verify(configurationController).addCallback(capture(configListener))
@@ -730,13 +735,13 @@
@Test
fun testOnLocaleListChanged_playersAreAddedBack() {
- context.resources.configuration.locales = LocaleList(Locale.US, Locale.UK, Locale.CANADA)
+ context.resources.configuration.setLocales(LocaleList(Locale.US, Locale.UK, Locale.CANADA))
testConfigurationChange(configListener.value::onLocaleListChanged)
verify(pageIndicator, never()).tintList =
ColorStateList.valueOf(context.getColor(R.color.media_paging_indicator))
- context.resources.configuration.locales = LocaleList(Locale.UK, Locale.US, Locale.CANADA)
+ context.resources.configuration.setLocales(LocaleList(Locale.UK, Locale.US, Locale.CANADA))
testConfigurationChange(configListener.value::onLocaleListChanged)
verify(pageIndicator).tintList =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
index ab24c46..db00e09 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
@@ -278,6 +278,21 @@
}
@Test
+ public void
+ whenNotBroadcasting_verifyLeBroadcastServiceCallBackIsUnregisteredIfProfileEnabled() {
+ when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
+ mLocalBluetoothLeBroadcast);
+ mIsBroadcasting = true;
+
+ mMediaOutputBaseDialogImpl.start();
+ verify(mLocalBluetoothLeBroadcast).registerServiceCallBack(any(), any());
+
+ mIsBroadcasting = false;
+ mMediaOutputBaseDialogImpl.stop();
+ verify(mLocalBluetoothLeBroadcast).unregisterServiceCallBack(any());
+ }
+
+ @Test
public void refresh_checkStopText() {
mStopText = "test_string";
mMediaOutputBaseDialogImpl.refresh();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
index 3e69a29..bfc8c83 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
@@ -256,6 +256,30 @@
}
@Test
+ public void isBroadcastSupported_noBleDeviceAndEnabledBroadcast_returnsTrue() {
+ when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
+ mLocalBluetoothLeBroadcast);
+ when(mLocalBluetoothLeBroadcast.isEnabled(any())).thenReturn(true);
+ FeatureFlagUtils.setEnabled(mContext,
+ FeatureFlagUtils.SETTINGS_NEED_CONNECTED_BLE_DEVICE_FOR_BROADCAST, true);
+ when(mMediaDevice.isBLEDevice()).thenReturn(false);
+
+ assertThat(mMediaOutputDialog.isBroadcastSupported()).isTrue();
+ }
+
+ @Test
+ public void isBroadcastSupported_noBleDeviceAndDisabledBroadcast_returnsFalse() {
+ when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
+ mLocalBluetoothLeBroadcast);
+ when(mLocalBluetoothLeBroadcast.isEnabled(any())).thenReturn(false);
+ FeatureFlagUtils.setEnabled(mContext,
+ FeatureFlagUtils.SETTINGS_NEED_CONNECTED_BLE_DEVICE_FOR_BROADCAST, true);
+ when(mMediaDevice.isBLEDevice()).thenReturn(false);
+
+ assertThat(mMediaOutputDialog.isBroadcastSupported()).isFalse();
+ }
+
+ @Test
public void getBroadcastIconVisibility_isBroadcasting_returnVisible() {
when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
mLocalBluetoothLeBroadcast);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProviderTest.kt
index ee3b80a..906420d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProviderTest.kt
@@ -123,7 +123,7 @@
private fun givenDisplay(width: Int, height: Int, isTablet: Boolean = false) {
val bounds = Rect(0, 0, width, height)
- val windowMetrics = WindowMetrics(bounds, null)
+ val windowMetrics = WindowMetrics(bounds, { null }, 1.0f)
whenever(windowManager.maximumWindowMetrics).thenReturn(windowMetrics)
whenever(windowManager.currentWindowMetrics).thenReturn(windowMetrics)
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 3a74c72..7bd97ce 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
@@ -108,20 +108,6 @@
}
@Test
- fun mediaProjectionState_onSessionSet_tokenNull_emitsEntireScreen() =
- testScope.runTest {
- val state by collectLastValue(repo.mediaProjectionState)
- runCurrent()
-
- fakeMediaProjectionManager.dispatchOnSessionSet(
- session =
- ContentRecordingSession.createTaskSession(/* taskWindowContainerToken= */ null)
- )
-
- assertThat(state).isEqualTo(MediaProjectionState.EntireScreen)
- }
-
- @Test
fun mediaProjectionState_sessionSet_taskWithToken_noMatchingRunningTask_emitsEntireScreen() =
testScope.runTest {
val state by collectLastValue(repo.mediaProjectionState)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt
index fab1de0..2d3dc58 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt
@@ -73,7 +73,7 @@
context,
windowManager,
ViewConfiguration.get(context),
- Handler.createAsync(Looper.myLooper()),
+ Handler.createAsync(checkNotNull(Looper.myLooper())),
vibratorHelper,
configurationController,
latencyTracker,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
index d933b57..1536c17 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
@@ -69,6 +69,7 @@
import com.android.wm.shell.bubbles.Bubbles
import com.google.common.truth.Truth.assertThat
import java.util.Optional
+import kotlin.test.assertNotNull
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.UnconfinedTestDispatcher
@@ -672,7 +673,7 @@
extras().bool(EXTRA_USE_STYLUS_MODE).isTrue()
}
iconCaptor.value?.let { icon ->
- assertThat(icon).isNotNull()
+ assertNotNull(icon)
assertThat(icon.resId).isEqualTo(R.drawable.ic_note_task_shortcut_widget)
}
}
@@ -755,7 +756,7 @@
assertThat(shortLabel).isEqualTo(NOTE_TASK_SHORT_LABEL)
assertThat(longLabel).isEqualTo(NOTE_TASK_LONG_LABEL)
assertThat(isLongLived).isEqualTo(true)
- assertThat(icon.resId).isEqualTo(R.drawable.ic_note_task_shortcut_widget)
+ assertThat(icon?.resId).isEqualTo(R.drawable.ic_note_task_shortcut_widget)
assertThat(extras?.getString(EXTRA_SHORTCUT_BADGE_OVERRIDE_PACKAGE))
.isEqualTo(NOTE_TASK_PACKAGE_NAME)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
new file mode 100644
index 0000000..53c04cc
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -0,0 +1,538 @@
+/*
+ * 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.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.scene
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
+import com.android.systemui.authentication.domain.model.AuthenticationMethodModel as DomainLayerAuthenticationMethodModel
+import com.android.systemui.authentication.domain.model.AuthenticationMethodModel
+import com.android.systemui.bouncer.ui.viewmodel.PinBouncerViewModel
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.shared.model.WakefulnessState
+import com.android.systemui.keyguard.ui.viewmodel.LockscreenSceneViewModel
+import com.android.systemui.model.SysUiState
+import com.android.systemui.scene.SceneTestUtils.Companion.toDataLayer
+import com.android.systemui.scene.domain.startable.SceneContainerStartable
+import com.android.systemui.scene.shared.model.ObservableTransitionState
+import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
+import com.android.systemui.settings.FakeDisplayTracker
+import com.android.systemui.shade.ui.viewmodel.ShadeSceneViewModel
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+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
+import org.junit.runners.JUnit4
+
+/**
+ * Integration test cases for the Scene Framework.
+ *
+ * **Principles**
+ * * All test cases here should be done from the perspective of the view-models of the system.
+ * * Focus on happy paths, let smaller unit tests focus on failure cases.
+ * * These are _integration_ tests and, as such, are larger and harder to maintain than unit tests.
+ * Therefore, when adding or modifying test cases, consider whether what you're testing is better
+ * covered by a more granular unit test.
+ * * Please reuse the helper methods in this class (for example, [putDeviceToSleep] or
+ * [emulateUserDrivenTransition]).
+ * * All tests start with the device locked and with a PIN auth method. The class offers useful
+ * methods like [setAuthMethod], [unlockDevice], [lockDevice], etc. to help you set up a starting
+ * state that makes more sense for your test case.
+ * * All helper methods in this class make assertions that are meant to make sure that they're only
+ * being used when the state is as required (e.g. cannot unlock an already unlocked device, cannot
+ * put to sleep a device that's already asleep, etc.).
+ */
+@SmallTest
+@RunWith(JUnit4::class)
+class SceneFrameworkIntegrationTest : SysuiTestCase() {
+
+ private val utils = SceneTestUtils(this)
+ private val testScope = utils.testScope
+
+ private val sceneContainerConfig = utils.fakeSceneContainerConfig()
+ private val sceneRepository =
+ utils.fakeSceneContainerRepository(
+ containerConfig = sceneContainerConfig,
+ )
+ private val sceneInteractor =
+ utils.sceneInteractor(
+ repository = sceneRepository,
+ )
+
+ private val authenticationRepository = utils.authenticationRepository()
+ private val authenticationInteractor =
+ utils.authenticationInteractor(
+ repository = authenticationRepository,
+ sceneInteractor = sceneInteractor,
+ )
+
+ private val transitionState =
+ MutableStateFlow<ObservableTransitionState>(
+ ObservableTransitionState.Idle(sceneContainerConfig.initialSceneKey)
+ )
+ private val sceneContainerViewModel =
+ SceneContainerViewModel(
+ interactor = sceneInteractor,
+ )
+ .apply { setTransitionState(transitionState) }
+
+ private val bouncerInteractor =
+ utils.bouncerInteractor(
+ authenticationInteractor = authenticationInteractor,
+ sceneInteractor = sceneInteractor,
+ )
+ private val bouncerViewModel =
+ utils.bouncerViewModel(
+ bouncerInteractor = bouncerInteractor,
+ authenticationInteractor = authenticationInteractor,
+ )
+
+ private val lockscreenSceneViewModel =
+ LockscreenSceneViewModel(
+ applicationScope = testScope.backgroundScope,
+ authenticationInteractor = authenticationInteractor,
+ bouncerInteractor = bouncerInteractor,
+ )
+
+ private val shadeSceneViewModel =
+ ShadeSceneViewModel(
+ applicationScope = testScope.backgroundScope,
+ authenticationInteractor = authenticationInteractor,
+ bouncerInteractor = bouncerInteractor,
+ )
+
+ private val keyguardRepository = utils.keyguardRepository()
+ private val keyguardInteractor =
+ utils.keyguardInteractor(
+ repository = keyguardRepository,
+ )
+
+ @Before
+ fun setUp() {
+ val featureFlags = FakeFeatureFlags().apply { set(Flags.SCENE_CONTAINER, true) }
+
+ authenticationRepository.setUnlocked(false)
+
+ val displayTracker = FakeDisplayTracker(context)
+ val sysUiState = SysUiState(displayTracker)
+ val startable =
+ SceneContainerStartable(
+ applicationScope = testScope.backgroundScope,
+ sceneInteractor = sceneInteractor,
+ authenticationInteractor = authenticationInteractor,
+ keyguardInteractor = keyguardInteractor,
+ featureFlags = featureFlags,
+ sysUiState = sysUiState,
+ displayId = displayTracker.defaultDisplayId,
+ sceneLogger = mock(),
+ )
+ startable.start()
+
+ assertWithMessage("Initial scene key mismatch!")
+ .that(sceneContainerViewModel.currentScene.value.key)
+ .isEqualTo(sceneContainerConfig.initialSceneKey)
+ assertWithMessage("Initial scene container visibility mismatch!")
+ .that(sceneContainerViewModel.isVisible.value)
+ .isTrue()
+ }
+
+ @Test
+ fun clickLockButtonAndEnterCorrectPin_unlocksDevice() =
+ testScope.runTest {
+ lockscreenSceneViewModel.onLockButtonClicked()
+ assertCurrentScene(SceneKey.Bouncer)
+ emulateUiSceneTransition()
+
+ enterPin()
+ assertCurrentScene(SceneKey.Gone)
+ emulateUiSceneTransition(
+ expectedVisible = false,
+ )
+ }
+
+ @Test
+ fun swipeUpOnLockscreen_enterCorrectPin_unlocksDevice() =
+ testScope.runTest {
+ val upDestinationSceneKey by
+ collectLastValue(lockscreenSceneViewModel.upDestinationSceneKey)
+ assertThat(upDestinationSceneKey).isEqualTo(SceneKey.Bouncer)
+ emulateUserDrivenTransition(
+ to = upDestinationSceneKey,
+ )
+
+ enterPin()
+ assertCurrentScene(SceneKey.Gone)
+ emulateUiSceneTransition(
+ expectedVisible = false,
+ )
+ }
+
+ @Test
+ fun swipeUpOnLockscreen_withAuthMethodSwipe_dismissesLockscreen() =
+ testScope.runTest {
+ setAuthMethod(DomainLayerAuthenticationMethodModel.Swipe)
+
+ val upDestinationSceneKey by
+ collectLastValue(lockscreenSceneViewModel.upDestinationSceneKey)
+ assertThat(upDestinationSceneKey).isEqualTo(SceneKey.Gone)
+ emulateUserDrivenTransition(
+ to = upDestinationSceneKey,
+ )
+ }
+
+ @Test
+ fun swipeUpOnShadeScene_withAuthMethodSwipe_lockscreenNotDismissed_goesToLockscreen() =
+ testScope.runTest {
+ val upDestinationSceneKey by collectLastValue(shadeSceneViewModel.upDestinationSceneKey)
+ setAuthMethod(DomainLayerAuthenticationMethodModel.Swipe)
+ assertCurrentScene(SceneKey.Lockscreen)
+
+ // Emulate a user swipe to the shade scene.
+ emulateUserDrivenTransition(to = SceneKey.Shade)
+ assertCurrentScene(SceneKey.Shade)
+
+ assertThat(upDestinationSceneKey).isEqualTo(SceneKey.Lockscreen)
+ emulateUserDrivenTransition(
+ to = upDestinationSceneKey,
+ )
+ }
+
+ @Test
+ fun swipeUpOnShadeScene_withAuthMethodSwipe_lockscreenDismissed_goesToGone() =
+ testScope.runTest {
+ val upDestinationSceneKey by collectLastValue(shadeSceneViewModel.upDestinationSceneKey)
+ setAuthMethod(DomainLayerAuthenticationMethodModel.Swipe)
+ assertCurrentScene(SceneKey.Lockscreen)
+
+ // Emulate a user swipe to dismiss the lockscreen.
+ emulateUserDrivenTransition(to = SceneKey.Gone)
+ assertCurrentScene(SceneKey.Gone)
+
+ // Emulate a user swipe to the shade scene.
+ emulateUserDrivenTransition(to = SceneKey.Shade)
+ assertCurrentScene(SceneKey.Shade)
+
+ assertThat(upDestinationSceneKey).isEqualTo(SceneKey.Gone)
+ emulateUserDrivenTransition(
+ to = upDestinationSceneKey,
+ )
+ }
+
+ @Test
+ fun withAuthMethodNone_deviceWakeUp_skipsLockscreen() =
+ testScope.runTest {
+ setAuthMethod(AuthenticationMethodModel.None)
+ putDeviceToSleep(instantlyLockDevice = false)
+ assertCurrentScene(SceneKey.Lockscreen)
+
+ wakeUpDevice()
+ assertCurrentScene(SceneKey.Gone)
+ }
+
+ @Test
+ fun withAuthMethodSwipe_deviceWakeUp_doesNotSkipLockscreen() =
+ testScope.runTest {
+ setAuthMethod(AuthenticationMethodModel.Swipe)
+ putDeviceToSleep(instantlyLockDevice = false)
+ assertCurrentScene(SceneKey.Lockscreen)
+
+ wakeUpDevice()
+ assertCurrentScene(SceneKey.Lockscreen)
+ }
+
+ @Test
+ fun deviceGoesToSleep_switchesToLockscreen() =
+ testScope.runTest {
+ unlockDevice()
+ assertCurrentScene(SceneKey.Gone)
+
+ putDeviceToSleep()
+ assertCurrentScene(SceneKey.Lockscreen)
+ }
+
+ @Test
+ fun deviceGoesToSleep_wakeUp_unlock() =
+ testScope.runTest {
+ unlockDevice()
+ assertCurrentScene(SceneKey.Gone)
+ putDeviceToSleep()
+ assertCurrentScene(SceneKey.Lockscreen)
+ wakeUpDevice()
+ assertCurrentScene(SceneKey.Lockscreen)
+
+ unlockDevice()
+ assertCurrentScene(SceneKey.Gone)
+ }
+
+ @Test
+ fun deviceWakesUpWhileUnlocked_dismissesLockscreen() =
+ testScope.runTest {
+ unlockDevice()
+ assertCurrentScene(SceneKey.Gone)
+ putDeviceToSleep(instantlyLockDevice = false)
+ assertCurrentScene(SceneKey.Lockscreen)
+ wakeUpDevice()
+ assertCurrentScene(SceneKey.Gone)
+ }
+
+ @Test
+ fun swipeUpOnLockscreenWhileUnlocked_dismissesLockscreen() =
+ testScope.runTest {
+ unlockDevice()
+ val upDestinationSceneKey by
+ collectLastValue(lockscreenSceneViewModel.upDestinationSceneKey)
+ assertThat(upDestinationSceneKey).isEqualTo(SceneKey.Gone)
+ }
+
+ @Test
+ fun deviceGoesToSleep_withLockTimeout_staysOnLockscreen() =
+ testScope.runTest {
+ unlockDevice()
+ assertCurrentScene(SceneKey.Gone)
+ putDeviceToSleep(instantlyLockDevice = false)
+ assertCurrentScene(SceneKey.Lockscreen)
+
+ // Pretend like the timeout elapsed and now lock the device.
+ lockDevice()
+ assertCurrentScene(SceneKey.Lockscreen)
+ }
+
+ /**
+ * Asserts that the current scene in the view-model matches what's expected.
+ *
+ * Note that this doesn't assert what the current scene is in the UI.
+ */
+ private fun TestScope.assertCurrentScene(expected: SceneKey) {
+ runCurrent()
+ assertWithMessage("Current scene mismatch!")
+ .that(sceneContainerViewModel.currentScene.value.key)
+ .isEqualTo(expected)
+ }
+
+ /**
+ * Returns the [SceneKey] of the current scene as displayed in the UI.
+ *
+ * This can be different than the value in [SceneContainerViewModel.currentScene], by design, as
+ * the UI must gradually transition between scenes.
+ */
+ private fun getCurrentSceneInUi(): SceneKey {
+ return when (val state = transitionState.value) {
+ is ObservableTransitionState.Idle -> state.scene
+ is ObservableTransitionState.Transition -> state.fromScene
+ }
+ }
+
+ /** Updates the current authentication method and related states in the data layer. */
+ private fun TestScope.setAuthMethod(
+ authMethod: DomainLayerAuthenticationMethodModel,
+ ) {
+ // Set the lockscreen enabled bit _before_ set the auth method as the code picks up on the
+ // lockscreen enabled bit _after_ the auth method is changed and the lockscreen enabled bit
+ // is not an observable that can trigger a new evaluation.
+ authenticationRepository.setLockscreenEnabled(authMethod !is AuthenticationMethodModel.None)
+ authenticationRepository.setAuthenticationMethod(authMethod.toDataLayer())
+ if (!authMethod.isSecure) {
+ // When the auth method is not secure, the device is never considered locked.
+ authenticationRepository.setUnlocked(true)
+ }
+ runCurrent()
+ }
+
+ /**
+ * Emulates a complete transition in the UI from whatever the current scene is in the UI to
+ * whatever the current scene should be, based on the value in
+ * [SceneContainerViewModel.onSceneChanged].
+ *
+ * This should post a series of values into [transitionState] to emulate a gradual scene
+ * transition and culminate with a call to [SceneContainerViewModel.onSceneChanged].
+ *
+ * The method asserts that a transition is actually required. E.g. it will fail if the current
+ * scene in [transitionState] is already caught up with the scene in
+ * [SceneContainerViewModel.currentScene].
+ *
+ * @param expectedVisible Whether [SceneContainerViewModel.isVisible] should be set at the end
+ * of the UI transition.
+ */
+ private fun TestScope.emulateUiSceneTransition(
+ expectedVisible: Boolean = true,
+ ) {
+ val to = sceneContainerViewModel.currentScene.value
+ val from = getCurrentSceneInUi()
+ assertWithMessage("Cannot transition to ${to.key} as the UI is already on that scene!")
+ .that(to.key)
+ .isNotEqualTo(from)
+
+ // Begin to transition.
+ val progressFlow = MutableStateFlow(0f)
+ transitionState.value =
+ ObservableTransitionState.Transition(
+ fromScene = getCurrentSceneInUi(),
+ toScene = to.key,
+ progress = progressFlow,
+ )
+ runCurrent()
+
+ // Report progress of transition.
+ while (progressFlow.value < 1f) {
+ progressFlow.value += 0.2f
+ runCurrent()
+ }
+
+ // End the transition and report the change.
+ transitionState.value = ObservableTransitionState.Idle(to.key)
+
+ sceneContainerViewModel.onSceneChanged(to)
+ runCurrent()
+
+ assertWithMessage("Visibility mismatch after scene transition from $from to ${to.key}!")
+ .that(sceneContainerViewModel.isVisible.value)
+ .isEqualTo(expectedVisible)
+ }
+
+ /**
+ * Emulates a fire-and-forget user action (a fling or back, not a pointer-tracking swipe) that
+ * causes a scene change to the [to] scene.
+ *
+ * This also includes the emulation of the resulting UI transition that culminates with the UI
+ * catching up with the requested scene change (see [emulateUiSceneTransition]).
+ *
+ * @param to The scene to transition to.
+ */
+ private fun TestScope.emulateUserDrivenTransition(
+ to: SceneKey?,
+ ) {
+ checkNotNull(to)
+
+ sceneInteractor.changeScene(SceneModel(to), "reason")
+ assertThat(sceneContainerViewModel.currentScene.value.key).isEqualTo(to)
+
+ emulateUiSceneTransition(
+ expectedVisible = to != SceneKey.Gone,
+ )
+ }
+
+ /**
+ * Locks the device immediately (without delay).
+ *
+ * Asserts the device to be lockable (e.g. that the current authentication is secure).
+ *
+ * Not to be confused with [putDeviceToSleep], which may also instantly lock the device.
+ */
+ private suspend fun TestScope.lockDevice() {
+ val authMethod = authenticationInteractor.getAuthenticationMethod()
+ assertWithMessage("The authentication method of $authMethod is not secure, cannot lock!")
+ .that(authMethod.isSecure)
+ .isTrue()
+
+ authenticationRepository.setUnlocked(false)
+ runCurrent()
+ }
+
+ /** Unlocks the device by entering the correct PIN. Ends up in the Gone scene. */
+ private fun TestScope.unlockDevice() {
+ assertWithMessage("Cannot unlock a device that's already unlocked!")
+ .that(authenticationInteractor.isUnlocked.value)
+ .isFalse()
+
+ lockscreenSceneViewModel.onLockButtonClicked()
+ runCurrent()
+ emulateUiSceneTransition()
+
+ enterPin()
+ emulateUiSceneTransition(
+ expectedVisible = false,
+ )
+ }
+
+ /**
+ * Enters the correct PIN in the bouncer UI.
+ *
+ * Asserts that the current scene is [SceneKey.Bouncer] and that the current bouncer UI is a PIN
+ * before proceeding.
+ *
+ * Does not assert that the device is locked or unlocked.
+ */
+ private fun TestScope.enterPin() {
+ assertWithMessage("Cannot enter PIN when not on the Bouncer scene!")
+ .that(getCurrentSceneInUi())
+ .isEqualTo(SceneKey.Bouncer)
+ val authMethodViewModel by collectLastValue(bouncerViewModel.authMethod)
+ assertWithMessage("Cannot enter PIN when not using a PIN authentication method!")
+ .that(authMethodViewModel)
+ .isInstanceOf(PinBouncerViewModel::class.java)
+
+ val pinBouncerViewModel = authMethodViewModel as PinBouncerViewModel
+ FakeAuthenticationRepository.DEFAULT_PIN.forEach { digit ->
+ pinBouncerViewModel.onPinButtonClicked(digit)
+ }
+ pinBouncerViewModel.onAuthenticateButtonClicked()
+ runCurrent()
+ }
+
+ /** Changes device wakefulness state from asleep to awake, going through intermediary states. */
+ private fun TestScope.wakeUpDevice() {
+ val wakefulnessModel = keyguardRepository.wakefulness.value
+ assertWithMessage("Cannot wake up device as it's already awake!")
+ .that(wakefulnessModel.isStartingToWakeOrAwake())
+ .isFalse()
+
+ keyguardRepository.setWakefulnessModel(
+ wakefulnessModel.copy(state = WakefulnessState.STARTING_TO_WAKE)
+ )
+ runCurrent()
+ keyguardRepository.setWakefulnessModel(
+ wakefulnessModel.copy(state = WakefulnessState.AWAKE)
+ )
+ runCurrent()
+ }
+
+ /** Changes device wakefulness state from awake to asleep, going through intermediary states. */
+ private suspend fun TestScope.putDeviceToSleep(
+ instantlyLockDevice: Boolean = true,
+ ) {
+ val wakefulnessModel = keyguardRepository.wakefulness.value
+ assertWithMessage("Cannot put device to sleep as it's already asleep!")
+ .that(wakefulnessModel.isStartingToWakeOrAwake())
+ .isTrue()
+
+ keyguardRepository.setWakefulnessModel(
+ wakefulnessModel.copy(state = WakefulnessState.STARTING_TO_SLEEP)
+ )
+ runCurrent()
+ keyguardRepository.setWakefulnessModel(
+ wakefulnessModel.copy(state = WakefulnessState.ASLEEP)
+ )
+ runCurrent()
+
+ if (instantlyLockDevice) {
+ lockDevice()
+ }
+ }
+}
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 0a93a7c..16cc924 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
@@ -28,9 +28,6 @@
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
@@ -105,6 +102,40 @@
}
@Test
+ fun transitioningTo() =
+ testScope.runTest {
+ val transitionState =
+ MutableStateFlow<ObservableTransitionState>(
+ ObservableTransitionState.Idle(underTest.desiredScene.value.key)
+ )
+ underTest.setTransitionState(transitionState)
+
+ val transitionTo by collectLastValue(underTest.transitioningTo)
+ assertThat(transitionTo).isNull()
+
+ underTest.changeScene(SceneModel(SceneKey.Shade), "reason")
+ assertThat(transitionTo).isNull()
+
+ val progress = MutableStateFlow(0f)
+ transitionState.value =
+ ObservableTransitionState.Transition(
+ fromScene = underTest.desiredScene.value.key,
+ toScene = SceneKey.Shade,
+ progress = progress,
+ )
+ assertThat(transitionTo).isEqualTo(SceneKey.Shade)
+
+ progress.value = 0.5f
+ assertThat(transitionTo).isEqualTo(SceneKey.Shade)
+
+ progress.value = 1f
+ assertThat(transitionTo).isEqualTo(SceneKey.Shade)
+
+ transitionState.value = ObservableTransitionState.Idle(SceneKey.Shade)
+ assertThat(transitionTo).isNull()
+ }
+
+ @Test
fun isVisible() =
testScope.runTest {
val isVisible by collectLastValue(underTest.isVisible)
@@ -118,81 +149,6 @@
}
@Test
- fun finishedSceneTransitions() =
- testScope.runTest {
- val transitionState =
- MutableStateFlow<ObservableTransitionState>(
- ObservableTransitionState.Idle(SceneKey.Lockscreen)
- )
- underTest.setTransitionState(transitionState)
- var transitionCount = 0
- val job = launch {
- underTest
- .finishedSceneTransitions(
- from = SceneKey.Shade,
- to = SceneKey.QuickSettings,
- )
- .collect { transitionCount++ }
- }
-
- assertThat(transitionCount).isEqualTo(0)
-
- underTest.changeScene(SceneModel(SceneKey.Shade), "reason")
- transitionState.value =
- ObservableTransitionState.Transition(
- fromScene = SceneKey.Lockscreen,
- toScene = SceneKey.Shade,
- progress = flowOf(0.5f),
- )
- runCurrent()
- underTest.onSceneChanged(SceneModel(SceneKey.Shade), "reason")
- transitionState.value = ObservableTransitionState.Idle(SceneKey.Shade)
- runCurrent()
- assertThat(transitionCount).isEqualTo(0)
-
- underTest.changeScene(SceneModel(SceneKey.QuickSettings), "reason")
- transitionState.value =
- ObservableTransitionState.Transition(
- fromScene = SceneKey.Shade,
- toScene = SceneKey.QuickSettings,
- progress = flowOf(0.5f),
- )
- runCurrent()
- underTest.onSceneChanged(SceneModel(SceneKey.QuickSettings), "reason")
- transitionState.value = ObservableTransitionState.Idle(SceneKey.QuickSettings)
- runCurrent()
- assertThat(transitionCount).isEqualTo(1)
-
- underTest.changeScene(SceneModel(SceneKey.Shade), "reason")
- transitionState.value =
- ObservableTransitionState.Transition(
- fromScene = SceneKey.QuickSettings,
- toScene = SceneKey.Shade,
- progress = flowOf(0.5f),
- )
- runCurrent()
- underTest.onSceneChanged(SceneModel(SceneKey.Shade), "reason")
- transitionState.value = ObservableTransitionState.Idle(SceneKey.Shade)
- runCurrent()
- assertThat(transitionCount).isEqualTo(1)
-
- underTest.changeScene(SceneModel(SceneKey.QuickSettings), "reason")
- transitionState.value =
- ObservableTransitionState.Transition(
- fromScene = SceneKey.Shade,
- toScene = SceneKey.QuickSettings,
- progress = flowOf(0.5f),
- )
- runCurrent()
- underTest.onSceneChanged(SceneModel(SceneKey.QuickSettings), "reason")
- transitionState.value = ObservableTransitionState.Idle(SceneKey.QuickSettings)
- runCurrent()
- assertThat(transitionCount).isEqualTo(2)
-
- job.cancel()
- }
-
- @Test
fun remoteUserInput() =
testScope.runTest {
val remoteUserInput by collectLastValue(underTest.remoteUserInput)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
index 45db7a0..951cadd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
@@ -21,7 +21,7 @@
import android.view.Display
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.authentication.data.model.AuthenticationMethodModel
+import com.android.systemui.authentication.domain.model.AuthenticationMethodModel
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.shared.model.WakeSleepReason
@@ -29,6 +29,7 @@
import com.android.systemui.keyguard.shared.model.WakefulnessState
import com.android.systemui.model.SysUiState
import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.scene.SceneTestUtils.Companion.toDataLayer
import com.android.systemui.scene.shared.model.ObservableTransitionState
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
@@ -80,14 +81,13 @@
)
@Test
- fun hydrateVisibility_featureEnabled() =
+ fun hydrateVisibility() =
testScope.runTest {
val currentDesiredSceneKey by
collectLastValue(sceneInteractor.desiredScene.map { it.key })
val isVisible by collectLastValue(sceneInteractor.isVisible)
val transitionStateFlow =
prepareState(
- isFeatureEnabled = true,
isDeviceUnlocked = true,
initialSceneKey = SceneKey.Gone,
)
@@ -123,44 +123,10 @@
}
@Test
- fun hydrateVisibility_featureDisabled() =
- testScope.runTest {
- val currentDesiredSceneKey by
- collectLastValue(sceneInteractor.desiredScene.map { it.key })
- val isVisible by collectLastValue(sceneInteractor.isVisible)
- val transitionStateFlow =
- prepareState(
- isFeatureEnabled = false,
- isDeviceUnlocked = true,
- initialSceneKey = SceneKey.Gone,
- )
- assertThat(currentDesiredSceneKey).isEqualTo(SceneKey.Gone)
- assertThat(isVisible).isTrue()
-
- underTest.start()
-
- assertThat(isVisible).isTrue()
-
- sceneInteractor.changeScene(SceneModel(SceneKey.Shade), "reason")
- transitionStateFlow.value =
- ObservableTransitionState.Transition(
- fromScene = SceneKey.Gone,
- toScene = SceneKey.Shade,
- progress = flowOf(0.5f),
- )
- assertThat(isVisible).isTrue()
-
- sceneInteractor.onSceneChanged(SceneModel(SceneKey.Shade), "reason")
- transitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Shade)
- assertThat(isVisible).isTrue()
- }
-
- @Test
- fun switchToLockscreenWhenDeviceLocks_featureEnabled() =
+ fun switchToLockscreenWhenDeviceLocks() =
testScope.runTest {
val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
prepareState(
- isFeatureEnabled = true,
isDeviceUnlocked = true,
initialSceneKey = SceneKey.Gone,
)
@@ -173,28 +139,10 @@
}
@Test
- fun switchToLockscreenWhenDeviceLocks_featureDisabled() =
+ fun switchFromBouncerToGoneWhenDeviceUnlocked() =
testScope.runTest {
val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.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.desiredScene.map { it.key })
- prepareState(
- isFeatureEnabled = true,
isDeviceUnlocked = false,
initialSceneKey = SceneKey.Bouncer,
)
@@ -207,28 +155,10 @@
}
@Test
- fun switchFromBouncerToGoneWhenDeviceUnlocked_featureDisabled() =
+ fun switchFromLockscreenToGoneWhenDeviceUnlocksWithBypassOn() =
testScope.runTest {
val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.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.desiredScene.map { it.key })
- prepareState(
- isFeatureEnabled = true,
isBypassEnabled = true,
initialSceneKey = SceneKey.Lockscreen,
)
@@ -241,11 +171,10 @@
}
@Test
- fun switchFromLockscreenToGoneWhenDeviceUnlocksWithBypassOn_featureOn_bypassOff() =
+ fun stayOnLockscreenWhenDeviceUnlocksWithBypassOff() =
testScope.runTest {
val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
prepareState(
- isFeatureEnabled = true,
isBypassEnabled = false,
initialSceneKey = SceneKey.Lockscreen,
)
@@ -258,28 +187,10 @@
}
@Test
- fun switchFromLockscreenToGoneWhenDeviceUnlocksWithBypassOn_featureOff_bypassOn() =
+ fun switchToLockscreenWhenDeviceSleepsLocked() =
testScope.runTest {
val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.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 switchToLockscreenWhenDeviceSleepsLocked_featureEnabled() =
- testScope.runTest {
- val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
- prepareState(
- isFeatureEnabled = true,
isDeviceUnlocked = false,
initialSceneKey = SceneKey.Shade,
)
@@ -292,23 +203,6 @@
}
@Test
- fun switchToLockscreenWhenDeviceSleepsLocked_featureDisabled() =
- testScope.runTest {
- val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
- prepareState(
- isFeatureEnabled = false,
- isDeviceUnlocked = false,
- initialSceneKey = SceneKey.Shade,
- )
- assertThat(currentSceneKey).isEqualTo(SceneKey.Shade)
- underTest.start()
-
- keyguardRepository.setWakefulnessModel(STARTING_TO_SLEEP)
-
- assertThat(currentSceneKey).isEqualTo(SceneKey.Shade)
- }
-
- @Test
fun hydrateSystemUiState() =
testScope.runTest {
val transitionStateFlow = prepareState()
@@ -339,11 +233,10 @@
}
@Test
- fun switchToGoneWhenDeviceStartsToWakeUp_authMethodNone_featureEnabled() =
+ fun switchToGoneWhenDeviceStartsToWakeUp_authMethodNone() =
testScope.runTest {
val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
prepareState(
- isFeatureEnabled = true,
initialSceneKey = SceneKey.Lockscreen,
authenticationMethod = AuthenticationMethodModel.None,
)
@@ -356,11 +249,26 @@
}
@Test
- fun switchToGoneWhenDeviceStartsToWakeUp_authMethodNotNone_featureEnabled() =
+ fun stayOnLockscreenWhenDeviceStartsToWakeUp_authMethodSwipe() =
testScope.runTest {
val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
prepareState(
- isFeatureEnabled = true,
+ initialSceneKey = SceneKey.Lockscreen,
+ authenticationMethod = AuthenticationMethodModel.Swipe,
+ )
+ assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
+ underTest.start()
+
+ keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE)
+
+ assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
+ }
+
+ @Test
+ fun doesNotSwitchToGoneWhenDeviceStartsToWakeUp_authMethodSecure() =
+ testScope.runTest {
+ val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
+ prepareState(
initialSceneKey = SceneKey.Lockscreen,
authenticationMethod = AuthenticationMethodModel.Pin,
)
@@ -373,30 +281,31 @@
}
@Test
- fun switchToGoneWhenDeviceStartsToWakeUp_authMethodNone_featureDisabled() =
+ fun switchToGoneWhenDeviceStartsToWakeUp_authMethodSecure_deviceUnlocked() =
testScope.runTest {
val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
prepareState(
- isFeatureEnabled = false,
initialSceneKey = SceneKey.Lockscreen,
- authenticationMethod = AuthenticationMethodModel.None,
+ authenticationMethod = AuthenticationMethodModel.Pin,
+ isDeviceUnlocked = false,
)
assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
underTest.start()
+ authenticationRepository.setUnlocked(true)
+ runCurrent()
keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE)
- assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
+ assertThat(currentSceneKey).isEqualTo(SceneKey.Gone)
}
private fun prepareState(
- isFeatureEnabled: Boolean = true,
isDeviceUnlocked: Boolean = false,
isBypassEnabled: Boolean = false,
initialSceneKey: SceneKey? = null,
authenticationMethod: AuthenticationMethodModel? = null,
): MutableStateFlow<ObservableTransitionState> {
- featureFlags.set(Flags.SCENE_CONTAINER, isFeatureEnabled)
+ featureFlags.set(Flags.SCENE_CONTAINER, true)
authenticationRepository.setUnlocked(isDeviceUnlocked)
keyguardRepository.setBypassEnabled(isBypassEnabled)
val transitionStateFlow =
@@ -410,7 +319,7 @@
sceneInteractor.onSceneChanged(SceneModel(it), "reason")
}
authenticationMethod?.let {
- authenticationRepository.setAuthenticationMethod(authenticationMethod)
+ authenticationRepository.setAuthenticationMethod(authenticationMethod.toDataLayer())
authenticationRepository.setLockscreenEnabled(
authenticationMethod != AuthenticationMethodModel.None
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt
index 07feedf..ad6909d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt
@@ -126,6 +126,7 @@
private fun onSpinnerItemSelected(position: Int) {
val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_spinner)
- spinner.onItemSelectedListener.onItemSelected(spinner, mock(), position, /* id= */ 0)
+ checkNotNull(spinner.onItemSelectedListener)
+ .onItemSelected(spinner, mock(), position, /* id= */ 0)
}
}
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 7b3e89d..1edeeff 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -95,7 +95,7 @@
@Mock private lateinit var notificationShadeDepthController: NotificationShadeDepthController
@Mock private lateinit var notificationShadeWindowController: NotificationShadeWindowController
@Mock private lateinit var keyguardUnlockAnimationController: KeyguardUnlockAnimationController
- @Mock private lateinit var shadeController: ShadeController
+ @Mock private lateinit var shadeLogger: ShadeLogger
@Mock private lateinit var ambientState: AmbientState
@Mock private lateinit var keyguardBouncerViewModel: KeyguardBouncerViewModel
@Mock private lateinit var stackScrollLayoutController: NotificationStackScrollLayoutController
@@ -150,44 +150,45 @@
testScope = TestScope()
underTest =
NotificationShadeWindowViewController(
- lockscreenShadeTransitionController,
- FalsingCollectorFake(),
- sysuiStatusBarStateController,
- dockManager,
- notificationShadeDepthController,
- view,
- notificationPanelViewController,
- ShadeExpansionStateManager(),
- stackScrollLayoutController,
- statusBarKeyguardViewManager,
- statusBarWindowStateController,
- lockIconViewController,
- centralSurfaces,
- backActionInteractor,
- powerInteractor,
- notificationShadeWindowController,
- unfoldTransitionProgressProvider,
- keyguardUnlockAnimationController,
- notificationInsetsController,
- ambientState,
- pulsingGestureListener,
- mLockscreenHostedDreamGestureListener,
- keyguardBouncerViewModel,
- keyguardBouncerComponentFactory,
- mock(KeyguardMessageAreaController.Factory::class.java),
- keyguardTransitionInteractor,
- primaryBouncerToGoneTransitionViewModel,
- notificationExpansionRepository,
- featureFlags,
- FakeSystemClock(),
- BouncerMessageInteractor(
- FakeBouncerMessageRepository(),
- mock(BouncerMessageFactory::class.java),
- FakeUserRepository(),
- CountDownTimerUtil(),
- featureFlags
- ),
- BouncerLogger(logcatLogBuffer("BouncerLog"))
+ lockscreenShadeTransitionController,
+ FalsingCollectorFake(),
+ sysuiStatusBarStateController,
+ dockManager,
+ notificationShadeDepthController,
+ view,
+ notificationPanelViewController,
+ ShadeExpansionStateManager(),
+ stackScrollLayoutController,
+ statusBarKeyguardViewManager,
+ statusBarWindowStateController,
+ lockIconViewController,
+ centralSurfaces,
+ backActionInteractor,
+ powerInteractor,
+ notificationShadeWindowController,
+ unfoldTransitionProgressProvider,
+ keyguardUnlockAnimationController,
+ notificationInsetsController,
+ ambientState,
+ shadeLogger,
+ pulsingGestureListener,
+ mLockscreenHostedDreamGestureListener,
+ keyguardBouncerViewModel,
+ keyguardBouncerComponentFactory,
+ mock(KeyguardMessageAreaController.Factory::class.java),
+ keyguardTransitionInteractor,
+ primaryBouncerToGoneTransitionViewModel,
+ notificationExpansionRepository,
+ featureFlags,
+ FakeSystemClock(),
+ BouncerMessageInteractor(
+ FakeBouncerMessageRepository(),
+ mock(BouncerMessageFactory::class.java),
+ FakeUserRepository(),
+ CountDownTimerUtil(),
+ featureFlags
+ ),
+ BouncerLogger(logcatLogBuffer("BouncerLog"))
)
underTest.setupExpandedStatusBar()
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 5c3ce71..829184c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
@@ -105,6 +105,7 @@
@Mock private lateinit var lockIconViewController: LockIconViewController
@Mock private lateinit var keyguardUnlockAnimationController: KeyguardUnlockAnimationController
@Mock private lateinit var ambientState: AmbientState
+ @Mock private lateinit var shadeLogger: ShadeLogger
@Mock private lateinit var pulsingGestureListener: PulsingGestureListener
@Mock
private lateinit var mLockscreenHostedDreamGestureListener: LockscreenHostedDreamGestureListener
@@ -179,6 +180,7 @@
keyguardUnlockAnimationController,
notificationInsetsController,
ambientState,
+ shadeLogger,
pulsingGestureListener,
mLockscreenHostedDreamGestureListener,
keyguardBouncerViewModel,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt
index 112a09b..577b6e0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt
@@ -584,7 +584,7 @@
private fun emptyInsets() = mock(WindowInsets::class.java)
private fun WindowInsets.withCutout(): WindowInsets {
- whenever(displayCutout.safeInsetBottom).thenReturn(CUTOUT_HEIGHT)
+ whenever(checkNotNull(displayCutout).safeInsetBottom).thenReturn(CUTOUT_HEIGHT)
return this
}
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 8d3c4b2..405199e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt
@@ -567,7 +567,7 @@
private fun emptyInsets() = mock(WindowInsets::class.java)
private fun WindowInsets.withCutout(): WindowInsets {
- whenever(displayCutout.safeInsetBottom).thenReturn(CUTOUT_HEIGHT)
+ whenever(checkNotNull(displayCutout).safeInsetBottom).thenReturn(CUTOUT_HEIGHT)
return this
}
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 52e0c9c..6a14a00 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt
@@ -22,6 +22,7 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.assist.AssistManager
+import com.android.systemui.log.LogBuffer
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.NotificationShadeWindowController
@@ -59,6 +60,7 @@
@Mock private lateinit var shadeViewController: ShadeViewController
@Mock private lateinit var nswvc: NotificationShadeWindowViewController
@Mock private lateinit var display: Display
+ @Mock private lateinit var touchLog: LogBuffer
private lateinit var shadeController: ShadeControllerImpl
@@ -71,6 +73,7 @@
ShadeControllerImpl(
commandQueue,
FakeExecutor(FakeSystemClock()),
+ touchLog,
keyguardStateController,
statusBarStateController,
statusBarKeyguardViewManager,
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 2501f85..8f8b840 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt
@@ -138,19 +138,19 @@
@Before
fun setup() {
- whenever<Clock>(view.findViewById(R.id.clock)).thenReturn(clock)
+ whenever<Clock>(view.requireViewById(R.id.clock)).thenReturn(clock)
whenever(clock.context).thenReturn(mockedContext)
- whenever<TextView>(view.findViewById(R.id.date)).thenReturn(date)
+ whenever<TextView>(view.requireViewById(R.id.date)).thenReturn(date)
whenever(date.context).thenReturn(mockedContext)
- whenever<ShadeCarrierGroup>(view.findViewById(R.id.carrier_group)).thenReturn(carrierGroup)
+ whenever<ShadeCarrierGroup>(view.requireViewById(R.id.carrier_group)).thenReturn(carrierGroup)
- whenever<BatteryMeterView>(view.findViewById(R.id.batteryRemainingIcon))
+ whenever<BatteryMeterView>(view.requireViewById(R.id.batteryRemainingIcon))
.thenReturn(batteryMeterView)
- whenever<StatusIconContainer>(view.findViewById(R.id.statusIcons)).thenReturn(statusIcons)
- whenever<View>(view.findViewById(R.id.shade_header_system_icons)).thenReturn(systemIcons)
+ whenever<StatusIconContainer>(view.requireViewById(R.id.statusIcons)).thenReturn(statusIcons)
+ whenever<View>(view.requireViewById(R.id.shade_header_system_icons)).thenReturn(systemIcons)
viewContext = Mockito.spy(context)
whenever(view.context).thenReturn(viewContext)
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 7443097..69b9525 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
@@ -42,6 +42,7 @@
private val authenticationInteractor =
utils.authenticationInteractor(
repository = utils.authenticationRepository(),
+ sceneInteractor = sceneInteractor,
)
private val underTest =
@@ -76,6 +77,30 @@
}
@Test
+ fun upTransitionSceneKey_authMethodSwipe_lockscreenNotDismissed_goesToLockscreen() =
+ testScope.runTest {
+ val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
+ utils.authenticationRepository.setLockscreenEnabled(true)
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
+ sceneInteractor.changeScene(SceneModel(SceneKey.Lockscreen), "reason")
+ sceneInteractor.onSceneChanged(SceneModel(SceneKey.Lockscreen), "reason")
+
+ assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Lockscreen)
+ }
+
+ @Test
+ fun upTransitionSceneKey_authMethodSwipe_lockscreenDismissed_goesToGone() =
+ testScope.runTest {
+ val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
+ utils.authenticationRepository.setLockscreenEnabled(true)
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
+ sceneInteractor.changeScene(SceneModel(SceneKey.Gone), "reason")
+ sceneInteractor.onSceneChanged(SceneModel(SceneKey.Gone), "reason")
+
+ assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Gone)
+ }
+
+ @Test
fun onContentClicked_deviceUnlocked_switchesToGone() =
testScope.runTest {
val currentScene by collectLastValue(sceneInteractor.desiredScene)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt
index 58b44ae..19dc72d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt
@@ -236,7 +236,8 @@
`when`(precondition.conditionsMet()).thenReturn(true)
// Given a session is created
- val weatherView = controller.buildAndConnectWeatherView(fakeParent, customView)
+ val weatherView =
+ checkNotNull(controller.buildAndConnectWeatherView(fakeParent, customView))
controller.stateChangeListener.onViewAttachedToWindow(weatherView)
verify(smartspaceManager).createSmartspaceSession(any())
@@ -258,7 +259,8 @@
// Given a session is created
val customView = Mockito.mock(TestView::class.java)
- val weatherView = controller.buildAndConnectWeatherView(fakeParent, customView)
+ val weatherView =
+ checkNotNull(controller.buildAndConnectWeatherView(fakeParent, customView))
controller.stateChangeListener.onViewAttachedToWindow(weatherView)
verify(smartspaceManager).createSmartspaceSession(any())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java
index b6da20f..280897d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java
@@ -17,21 +17,19 @@
package com.android.systemui.statusbar;
-import static android.app.Notification.FLAG_FSI_REQUESTED_BUT_DENIED;
-
import static com.android.systemui.dump.LogBufferHelperKt.logcatLogBuffer;
import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED;
+import static com.google.common.truth.Truth.assertThat;
+
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
import static org.junit.Assert.assertEquals;
-import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import android.app.ActivityManager;
import android.app.Notification;
-import android.app.PendingIntent;
import android.os.Handler;
import android.os.Looper;
import android.os.UserHandle;
@@ -67,17 +65,15 @@
private static final String TEST_PACKAGE_NAME = "test";
private static final int TEST_UID = 0;
- protected static final int TEST_MINIMUM_DISPLAY_TIME = 200;
- protected static final int TEST_STICKY_DISPLAY_TIME = 1000;
- protected static final int TEST_AUTO_DISMISS_TIME = 500;
+ protected static final int TEST_MINIMUM_DISPLAY_TIME = 400;
+ protected static final int TEST_AUTO_DISMISS_TIME = 600;
+ protected static final int TEST_STICKY_AUTO_DISMISS_TIME = 800;
// Number of notifications to use in tests requiring multiple notifications
private static final int TEST_NUM_NOTIFICATIONS = 4;
- protected static final int TEST_TIMEOUT_TIME = 15000;
+ protected static final int TEST_TIMEOUT_TIME = 2_000;
protected final Runnable mTestTimeoutRunnable = () -> mTimedOut = true;
- protected NotificationEntry mEntry;
protected Handler mTestHandler;
- private StatusBarNotification mSbn;
protected boolean mTimedOut = false;
@Mock protected ExpandableNotificationRow mRow;
@@ -88,8 +84,8 @@
private TestableAlertingNotificationManager(Handler handler) {
super(new HeadsUpManagerLogger(logcatLogBuffer()), handler);
mMinimumDisplayTime = TEST_MINIMUM_DISPLAY_TIME;
- mStickyDisplayTime = TEST_STICKY_DISPLAY_TIME;
mAutoDismissNotificationDecay = TEST_AUTO_DISMISS_TIME;
+ mStickyDisplayTime = TEST_STICKY_AUTO_DISMISS_TIME;
}
@Override
@@ -114,7 +110,7 @@
return new TestableAlertingNotificationManager(mTestHandler);
}
- protected StatusBarNotification createNewSbn(int id, Notification n) {
+ protected StatusBarNotification createSbn(int id, Notification n) {
return new StatusBarNotification(
TEST_PACKAGE_NAME /* pkg */,
TEST_PACKAGE_NAME,
@@ -128,45 +124,53 @@
0 /* postTime */);
}
- protected StatusBarNotification createNewSbn(int id, Notification.Builder n) {
- return new StatusBarNotification(
- TEST_PACKAGE_NAME /* pkg */,
- TEST_PACKAGE_NAME,
- id,
- null /* tag */,
- TEST_UID,
- 0 /* initialPid */,
- n.build(),
- new UserHandle(ActivityManager.getCurrentUser()),
- null /* overrideGroupKey */,
- 0 /* postTime */);
+ protected StatusBarNotification createSbn(int id, Notification.Builder n) {
+ return createSbn(id, n.build());
}
- protected StatusBarNotification createNewNotification(int id) {
- Notification.Builder n = new Notification.Builder(mContext, "")
+ protected StatusBarNotification createSbn(int id) {
+ final Notification.Builder b = new Notification.Builder(mContext, "")
.setSmallIcon(R.drawable.ic_person)
.setContentTitle("Title")
.setContentText("Text");
- return createNewSbn(id, n);
+ return createSbn(id, b);
}
- protected StatusBarNotification createStickySbn(int id) {
- Notification stickyHun = new Notification.Builder(mContext, "")
- .setSmallIcon(R.drawable.ic_person)
- .setFullScreenIntent(mock(PendingIntent.class), /* highPriority */ true)
- .build();
- stickyHun.flags |= FLAG_FSI_REQUESTED_BUT_DENIED;
- return createNewSbn(id, stickyHun);
+ protected NotificationEntry createEntry(int id, Notification n) {
+ return new NotificationEntryBuilder().setSbn(createSbn(id, n)).build();
+ }
+
+ protected NotificationEntry createEntry(int id) {
+ return new NotificationEntryBuilder().setSbn(createSbn(id)).build();
+ }
+
+ protected void verifyAlertingAtTime(AlertingNotificationManager anm, NotificationEntry entry,
+ boolean shouldBeAlerting, int whenToCheckAlertingMillis, String whenCondition) {
+ final Boolean[] wasAlerting = {null};
+ final Runnable checkAlerting =
+ () -> wasAlerting[0] = anm.isAlerting(entry.getKey());
+
+ mTestHandler.postDelayed(checkAlerting, whenToCheckAlertingMillis);
+ mTestHandler.postDelayed(mTestTimeoutRunnable, TEST_TIMEOUT_TIME);
+ TestableLooper.get(this).processMessages(2);
+
+ assertFalse("Test timed out", mTimedOut);
+ if (shouldBeAlerting) {
+ assertTrue("Should still be alerting after " + whenCondition, wasAlerting[0]);
+ } else {
+ assertFalse("Should not still be alerting after " + whenCondition, wasAlerting[0]);
+ }
+ assertFalse("Should not still be alerting after processing",
+ anm.isAlerting(entry.getKey()));
}
@Before
public void setUp() {
mTestHandler = Handler.createAsync(Looper.myLooper());
- mSbn = createNewNotification(0 /* id */);
- mEntry = new NotificationEntryBuilder()
- .setSbn(mSbn)
- .build();
- mEntry.setRow(mRow);
+
+ assertThat(TEST_MINIMUM_DISPLAY_TIME).isLessThan(TEST_AUTO_DISMISS_TIME);
+ assertThat(TEST_AUTO_DISMISS_TIME).isLessThan(TEST_STICKY_AUTO_DISMISS_TIME);
+ assertThat(TEST_STICKY_AUTO_DISMISS_TIME).isLessThan(TEST_TIMEOUT_TIME);
}
@After
@@ -176,59 +180,64 @@
@Test
public void testShowNotification_addsEntry() {
- AlertingNotificationManager alm = createAlertingNotificationManager();
+ final AlertingNotificationManager alm = createAlertingNotificationManager();
+ final NotificationEntry entry = createEntry(/* id = */ 0);
- alm.showNotification(mEntry);
+ alm.showNotification(entry);
- assertTrue(alm.isAlerting(mEntry.getKey()));
+ assertTrue(alm.isAlerting(entry.getKey()));
assertTrue(alm.hasNotifications());
- assertEquals(mEntry, alm.getEntry(mEntry.getKey()));
+ assertEquals(entry, alm.getEntry(entry.getKey()));
}
@Test
public void testShowNotification_autoDismisses() {
- AlertingNotificationManager alm = createAlertingNotificationManager();
+ final AlertingNotificationManager alm = createAlertingNotificationManager();
+ final NotificationEntry entry = createEntry(/* id = */ 0);
- alm.showNotification(mEntry);
- mTestHandler.postDelayed(mTestTimeoutRunnable, TEST_TIMEOUT_TIME);
+ alm.showNotification(entry);
- // Wait for remove runnable and then process it immediately
- TestableLooper.get(this).processMessages(1);
+ verifyAlertingAtTime(alm, entry, false, TEST_AUTO_DISMISS_TIME * 3 / 2,
+ "auto dismiss time");
- assertFalse("Test timed out", mTimedOut);
- assertFalse(alm.isAlerting(mEntry.getKey()));
+ assertFalse(alm.isAlerting(entry.getKey()));
}
@Test
public void testRemoveNotification_removeDeferred() {
- AlertingNotificationManager alm = createAlertingNotificationManager();
- alm.showNotification(mEntry);
+ final AlertingNotificationManager alm = createAlertingNotificationManager();
+ final NotificationEntry entry = createEntry(/* id = */ 0);
+
+ alm.showNotification(entry);
// Try to remove but defer, since the notification has not been shown long enough.
- alm.removeNotification(mEntry.getKey(), false /* releaseImmediately */);
+ final boolean removedImmediately = alm.removeNotification(entry.getKey(),
+ false /* releaseImmediately */);
- assertTrue(alm.isAlerting(mEntry.getKey()));
+ assertFalse(removedImmediately);
+ assertTrue(alm.isAlerting(entry.getKey()));
}
@Test
public void testRemoveNotification_forceRemove() {
- AlertingNotificationManager alm = createAlertingNotificationManager();
- alm.showNotification(mEntry);
+ final AlertingNotificationManager alm = createAlertingNotificationManager();
+ final NotificationEntry entry = createEntry(/* id = */ 0);
+
+ alm.showNotification(entry);
// Remove forcibly with releaseImmediately = true.
- alm.removeNotification(mEntry.getKey(), true /* releaseImmediately */);
+ final boolean removedImmediately = alm.removeNotification(entry.getKey(),
+ true /* releaseImmediately */);
- assertFalse(alm.isAlerting(mEntry.getKey()));
+ assertTrue(removedImmediately);
+ assertFalse(alm.isAlerting(entry.getKey()));
}
@Test
public void testReleaseAllImmediately() {
- AlertingNotificationManager alm = createAlertingNotificationManager();
+ final AlertingNotificationManager alm = createAlertingNotificationManager();
for (int i = 0; i < TEST_NUM_NOTIFICATIONS; i++) {
- StatusBarNotification sbn = createNewNotification(i);
- NotificationEntry entry = new NotificationEntryBuilder()
- .setSbn(sbn)
- .build();
+ final NotificationEntry entry = createEntry(i);
entry.setRow(mRow);
alm.showNotification(entry);
}
@@ -240,10 +249,12 @@
@Test
public void testCanRemoveImmediately_notShownLongEnough() {
- AlertingNotificationManager alm = createAlertingNotificationManager();
- alm.showNotification(mEntry);
+ final AlertingNotificationManager alm = createAlertingNotificationManager();
+ final NotificationEntry entry = createEntry(/* id = */ 0);
+
+ alm.showNotification(entry);
// The entry has just been added so we should not remove immediately.
- assertFalse(alm.canRemoveImmediately(mEntry.getKey()));
+ assertFalse(alm.canRemoveImmediately(entry.getKey()));
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/MediaArtworkProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/MediaArtworkProcessorTest.kt
index 724ea02..e4da53a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/MediaArtworkProcessorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/MediaArtworkProcessorTest.kt
@@ -47,7 +47,7 @@
processor = MediaArtworkProcessor()
val point = Point()
- context.display.getSize(point)
+ checkNotNull(context.display).getSize(point)
screenWidth = point.x
screenHeight = point.y
}
@@ -106,4 +106,4 @@
// THEN the processed bitmap is null
assertThat(background).isNull()
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
index 2de5705..9036f22 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
@@ -278,7 +278,7 @@
`when`(deviceProvisionedController.isCurrentUserSetup).thenReturn(false)
// WHEN a connection attempt is made and view is attached
- val view = controller.buildAndConnectView(fakeParent)
+ val view = controller.buildAndConnectView(fakeParent)!!
controller.stateChangeListener.onViewAttachedToWindow(view)
// THEN no session is created
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ColorizedFgsCoordinatorTest.java
similarity index 69%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinatorTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ColorizedFgsCoordinatorTest.java
index bc32759..f2207af 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ColorizedFgsCoordinatorTest.java
@@ -24,97 +24,53 @@
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
import android.app.Notification;
import android.app.PendingIntent;
import android.app.Person;
import android.content.Intent;
import android.graphics.Color;
import android.os.UserHandle;
-import android.service.notification.StatusBarNotification;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import androidx.test.filters.SmallTest;
-import com.android.systemui.ForegroundServiceController;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.appops.AppOpsController;
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.NotifSectioner;
-import com.android.systemui.util.concurrency.FakeExecutor;
-import com.android.systemui.util.time.FakeSystemClock;
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;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
-public class AppOpsCoordinatorTest extends SysuiTestCase {
- private static final String TEST_PKG = "test_pkg";
- private static final int NOTIF_USER_ID = 0;
+public class ColorizedFgsCoordinatorTest extends SysuiTestCase {
- @Mock private ForegroundServiceController mForegroundServiceController;
- @Mock private AppOpsController mAppOpsController;
+ private static final int NOTIF_USER_ID = 0;
@Mock private NotifPipeline mNotifPipeline;
private NotificationEntryBuilder mEntryBuilder;
- private AppOpsCoordinator mAppOpsCoordinator;
- private NotifFilter mForegroundFilter;
+ private ColorizedFgsCoordinator mColorizedFgsCoordinator;
private NotifSectioner mFgsSection;
- private FakeSystemClock mClock = new FakeSystemClock();
- private FakeExecutor mExecutor = new FakeExecutor(mClock);
-
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
allowTestableLooperAsMainThread();
- mAppOpsCoordinator =
- new AppOpsCoordinator(
- mForegroundServiceController,
- mAppOpsController,
- mExecutor);
+ mColorizedFgsCoordinator = new ColorizedFgsCoordinator();
mEntryBuilder = new NotificationEntryBuilder()
.setUser(new UserHandle(NOTIF_USER_ID));
- mAppOpsCoordinator.attach(mNotifPipeline);
+ mColorizedFgsCoordinator.attach(mNotifPipeline);
- // capture filter
- ArgumentCaptor<NotifFilter> filterCaptor = ArgumentCaptor.forClass(NotifFilter.class);
- verify(mNotifPipeline, times(1)).addPreGroupFilter(filterCaptor.capture());
- mForegroundFilter = filterCaptor.getValue();
-
- mFgsSection = mAppOpsCoordinator.getSectioner();
- }
-
- @Test
- public void filterTest_disclosureUnnecessary() {
- NotificationEntry entry = mEntryBuilder.build();
- StatusBarNotification sbn = entry.getSbn();
-
- // GIVEN the notification is a disclosure notification
- when(mForegroundServiceController.isDisclosureNotification(sbn)).thenReturn(true);
-
- // GIVEN the disclosure isn't needed for this user
- when(mForegroundServiceController.isDisclosureNeededForUser(sbn.getUserId()))
- .thenReturn(false);
-
- // THEN filter out the notification
- assertTrue(mForegroundFilter.shouldFilterOut(entry, 0));
+ mFgsSection = mColorizedFgsCoordinator.getSectioner();
}
@Test
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 61da901..39b2948 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
@@ -115,6 +115,7 @@
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.keyguard.ui.viewmodel.LightRevealScrimViewModel;
+import com.android.systemui.log.LogBuffer;
import com.android.systemui.navigationbar.NavigationBarController;
import com.android.systemui.notetask.NoteTaskController;
import com.android.systemui.plugins.ActivityStarter;
@@ -441,6 +442,7 @@
mShadeController = spy(new ShadeControllerImpl(
mCommandQueue,
mMainExecutor,
+ mock(LogBuffer.class),
mKeyguardStateController,
mStatusBarStateController,
mStatusBarKeyguardViewManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt
index 6155e3c..56d2397 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt
@@ -212,13 +212,13 @@
@Test
fun localeListChanged_listenerNotified() {
val config = mContext.resources.configuration
- config.locales = LocaleList(Locale.CANADA, Locale.GERMANY)
+ config.setLocales(LocaleList(Locale.CANADA, Locale.GERMANY))
mConfigurationController.onConfigurationChanged(config)
val listener = createAndAddListener()
// WHEN the locales are updated
- config.locales = LocaleList(Locale.FRANCE, Locale.JAPAN, Locale.CHINESE)
+ config.setLocales(LocaleList(Locale.FRANCE, Locale.JAPAN, Locale.CHINESE))
mConfigurationController.onConfigurationChanged(config)
// THEN the listener is notified
@@ -274,6 +274,23 @@
}
@Test
+ fun orientationUpdated_listenerNotified() {
+ val config = mContext.resources.configuration
+ config.orientation = Configuration.ORIENTATION_LANDSCAPE
+ mConfigurationController.onConfigurationChanged(config)
+
+ val listener = createAndAddListener()
+
+ // WHEN the orientation is updated
+ config.orientation = Configuration.ORIENTATION_PORTRAIT
+ mConfigurationController.onConfigurationChanged(config)
+
+ // THEN the listener is notified
+ assertThat(listener.orientationChanged).isTrue()
+ }
+
+
+ @Test
fun multipleUpdates_listenerNotifiedOfAll() {
val config = mContext.resources.configuration
config.densityDpi = 14
@@ -325,6 +342,7 @@
var themeChanged = false
var localeListChanged = false
var layoutDirectionChanged = false
+ var orientationChanged = false
override fun onConfigChanged(newConfig: Configuration?) {
changedConfig = newConfig
@@ -350,6 +368,9 @@
override fun onLayoutDirectionChanged(isLayoutRtl: Boolean) {
layoutDirectionChanged = true
}
+ override fun onOrientationChanged(orientation: Int) {
+ orientationChanged = true
+ }
fun assertNoMethodsCalled() {
assertThat(densityOrFontScaleChanged).isFalse()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
index 2f1e372..ec6286b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
@@ -75,6 +75,7 @@
private HeadsUpManagerPhone mHeadsUpManager;
private View mOperatorNameView;
private StatusBarStateController mStatusbarStateController;
+ private PhoneStatusBarTransitions mPhoneStatusBarTransitions;
private KeyguardBypassController mBypassController;
private NotificationWakeUpCoordinator mWakeUpCoordinator;
private KeyguardStateController mKeyguardStateController;
@@ -95,6 +96,7 @@
mHeadsUpManager = mock(HeadsUpManagerPhone.class);
mOperatorNameView = new View(mContext);
mStatusbarStateController = mock(StatusBarStateController.class);
+ mPhoneStatusBarTransitions = mock(PhoneStatusBarTransitions.class);
mBypassController = mock(KeyguardBypassController.class);
mWakeUpCoordinator = mock(NotificationWakeUpCoordinator.class);
mKeyguardStateController = mock(KeyguardStateController.class);
@@ -105,6 +107,7 @@
mock(NotificationIconAreaController.class),
mHeadsUpManager,
mStatusbarStateController,
+ mPhoneStatusBarTransitions,
mBypassController,
mWakeUpCoordinator,
mDarkIconDispatcher,
@@ -188,6 +191,7 @@
mock(NotificationIconAreaController.class),
mHeadsUpManager,
mStatusbarStateController,
+ mPhoneStatusBarTransitions,
mBypassController,
mWakeUpCoordinator,
mDarkIconDispatcher,
@@ -283,4 +287,18 @@
/* delta = */ 0.001
);
}
+
+ @Test
+ public void onHeadsUpStateChanged_true_transitionsNotified() {
+ mHeadsUpAppearanceController.onHeadsUpStateChanged(mEntry, true);
+
+ verify(mPhoneStatusBarTransitions).onHeadsUpStateChanged(true);
+ }
+
+ @Test
+ public void onHeadsUpStateChanged_false_transitionsNotified() {
+ mHeadsUpAppearanceController.onHeadsUpStateChanged(mEntry, false);
+
+ verify(mPhoneStatusBarTransitions).onHeadsUpStateChanged(false);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
index 72522ca..bb20d18 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
@@ -39,7 +39,6 @@
import com.android.systemui.statusbar.AlertingNotificationManagerTest;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider;
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
@@ -62,8 +61,6 @@
public class HeadsUpManagerPhoneTest extends AlertingNotificationManagerTest {
@Rule public MockitoRule rule = MockitoJUnit.rule();
- private HeadsUpManagerPhone mHeadsUpManager;
-
private final HeadsUpManagerLogger mHeadsUpManagerLogger = new HeadsUpManagerLogger(
logcatLogBuffer());
@Mock private GroupMembershipManager mGroupManager;
@@ -108,25 +105,8 @@
}
}
- @Override
- protected AlertingNotificationManager createAlertingNotificationManager() {
- return mHeadsUpManager;
- }
-
- @Before
- @Override
- public void setUp() {
- AccessibilityManagerWrapper accessibilityMgr =
- mDependency.injectMockDependency(AccessibilityManagerWrapper.class);
- when(accessibilityMgr.getRecommendedTimeoutMillis(anyInt(), anyInt()))
- .thenReturn(TEST_AUTO_DISMISS_TIME);
- when(mVSProvider.isReorderingAllowed()).thenReturn(true);
- mDependency.injectMockDependency(NotificationShadeWindowController.class);
- mContext.getOrCreateTestableResources().addOverride(
- R.integer.ambient_notification_extension_time, 500);
-
- super.setUp();
- mHeadsUpManager = new TestableHeadsUpManagerPhone(
+ private HeadsUpManagerPhone createHeadsUpManagerPhone() {
+ return new TestableHeadsUpManagerPhone(
mContext,
mHeadsUpManagerLogger,
mGroupManager,
@@ -141,6 +121,26 @@
);
}
+ @Override
+ protected AlertingNotificationManager createAlertingNotificationManager() {
+ return createHeadsUpManagerPhone();
+ }
+
+ @Before
+ @Override
+ public void setUp() {
+ final AccessibilityManagerWrapper accessibilityMgr =
+ mDependency.injectMockDependency(AccessibilityManagerWrapper.class);
+ when(accessibilityMgr.getRecommendedTimeoutMillis(anyInt(), anyInt()))
+ .thenReturn(TEST_AUTO_DISMISS_TIME);
+ when(mVSProvider.isReorderingAllowed()).thenReturn(true);
+ mDependency.injectMockDependency(NotificationShadeWindowController.class);
+ mContext.getOrCreateTestableResources().addOverride(
+ R.integer.ambient_notification_extension_time, 500);
+
+ super.setUp();
+ }
+
@After
@Override
public void tearDown() {
@@ -149,63 +149,67 @@
@Test
public void testSnooze() {
- mHeadsUpManager.showNotification(mEntry);
+ final HeadsUpManagerPhone hmp = createHeadsUpManagerPhone();
+ final NotificationEntry entry = createEntry(/* id = */ 0);
- mHeadsUpManager.snooze();
+ hmp.showNotification(entry);
+ hmp.snooze();
- assertTrue(mHeadsUpManager.isSnoozed(mEntry.getSbn().getPackageName()));
+ assertTrue(hmp.isSnoozed(entry.getSbn().getPackageName()));
}
@Test
public void testSwipedOutNotification() {
- mHeadsUpManager.showNotification(mEntry);
- mHeadsUpManager.addSwipedOutNotification(mEntry.getKey());
+ final HeadsUpManagerPhone hmp = createHeadsUpManagerPhone();
+ final NotificationEntry entry = createEntry(/* id = */ 0);
+
+ hmp.showNotification(entry);
+ hmp.addSwipedOutNotification(entry.getKey());
// Remove should succeed because the notification is swiped out
- mHeadsUpManager.removeNotification(mEntry.getKey(), false /* releaseImmediately */);
+ final boolean removedImmediately = hmp.removeNotification(entry.getKey(),
+ /* releaseImmediately = */ false);
- assertFalse(mHeadsUpManager.isAlerting(mEntry.getKey()));
+ assertTrue(removedImmediately);
+ assertFalse(hmp.isAlerting(entry.getKey()));
}
@Test
public void testCanRemoveImmediately_swipedOut() {
- mHeadsUpManager.showNotification(mEntry);
- mHeadsUpManager.addSwipedOutNotification(mEntry.getKey());
+ final HeadsUpManagerPhone hmp = createHeadsUpManagerPhone();
+ final NotificationEntry entry = createEntry(/* id = */ 0);
+
+ hmp.showNotification(entry);
+ hmp.addSwipedOutNotification(entry.getKey());
// Notification is swiped so it can be immediately removed.
- assertTrue(mHeadsUpManager.canRemoveImmediately(mEntry.getKey()));
+ assertTrue(hmp.canRemoveImmediately(entry.getKey()));
}
@Ignore("b/141538055")
@Test
public void testCanRemoveImmediately_notTopEntry() {
- NotificationEntry laterEntry = new NotificationEntryBuilder()
- .setSbn(createNewNotification(1))
- .build();
+ final HeadsUpManagerPhone hmp = createHeadsUpManagerPhone();
+ final NotificationEntry earlierEntry = createEntry(/* id = */ 0);
+ final NotificationEntry laterEntry = createEntry(/* id = */ 1);
laterEntry.setRow(mRow);
- mHeadsUpManager.showNotification(mEntry);
- mHeadsUpManager.showNotification(laterEntry);
+
+ hmp.showNotification(earlierEntry);
+ hmp.showNotification(laterEntry);
// Notification is "behind" a higher priority notification so we can remove it immediately.
- assertTrue(mHeadsUpManager.canRemoveImmediately(mEntry.getKey()));
+ assertTrue(hmp.canRemoveImmediately(earlierEntry.getKey()));
}
@Test
public void testExtendHeadsUp() {
- mHeadsUpManager.showNotification(mEntry);
- Runnable pastNormalTimeRunnable =
- () -> mLivesPastNormalTime = mHeadsUpManager.isAlerting(mEntry.getKey());
- mTestHandler.postDelayed(pastNormalTimeRunnable,
- TEST_AUTO_DISMISS_TIME + mHeadsUpManager.mExtensionTime / 2);
- mTestHandler.postDelayed(mTestTimeoutRunnable, TEST_TIMEOUT_TIME);
+ final HeadsUpManagerPhone hmp = createHeadsUpManagerPhone();
+ final NotificationEntry entry = createEntry(/* id = */ 0);
- mHeadsUpManager.extendHeadsUp();
+ hmp.showNotification(entry);
+ hmp.extendHeadsUp();
- // Wait for normal time runnable and extended remove runnable and process them on arrival.
- TestableLooper.get(this).processMessages(2);
-
- assertFalse("Test timed out", mTimedOut);
- assertTrue("Pulse was not extended", mLivesPastNormalTime);
- assertFalse(mHeadsUpManager.isAlerting(mEntry.getKey()));
+ final int pastNormalTimeMillis = TEST_AUTO_DISMISS_TIME + hmp.mExtensionTime / 2;
+ verifyAlertingAtTime(hmp, entry, true, pastNormalTimeMillis, "normal time");
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitionsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitionsTest.kt
new file mode 100644
index 0000000..4af1b24
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitionsTest.kt
@@ -0,0 +1,237 @@
+/*
+ * 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 android.testing.TestableLooper
+import android.view.View
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT
+import com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT
+import com.android.systemui.statusbar.phone.BarTransitions.MODE_OPAQUE
+import com.android.systemui.statusbar.phone.BarTransitions.MODE_SEMI_TRANSPARENT
+import com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSLUCENT
+import com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSPARENT
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.verify
+
+@SmallTest
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+class PhoneStatusBarTransitionsTest : SysuiTestCase() {
+
+ // PhoneStatusBarView does a lot of non-standard things when inflating, so just use mocks.
+ private val batteryView = mock<View>()
+ private val statusIcons = mock<View>()
+ private val startIcons = mock<View>()
+ private val statusBarView =
+ mock<PhoneStatusBarView>().apply {
+ whenever(this.context).thenReturn(mContext)
+ whenever(this.findViewById<View>(R.id.battery)).thenReturn(batteryView)
+ whenever(this.findViewById<View>(R.id.statusIcons)).thenReturn(statusIcons)
+ whenever(this.findViewById<View>(R.id.status_bar_start_side_except_heads_up))
+ .thenReturn(startIcons)
+ }
+ private val backgroundView = mock<View>().apply { whenever(this.context).thenReturn(mContext) }
+
+ private val underTest: PhoneStatusBarTransitions by lazy {
+ PhoneStatusBarTransitions(statusBarView, backgroundView).also {
+ // The views' alphas will be set when PhoneStatusBarTransitions is created and we want
+ // to ignore those in the tests, so clear those verifications here.
+ reset(batteryView)
+ reset(statusIcons)
+ reset(startIcons)
+ }
+ }
+
+ @Before
+ fun setUp() {
+ context.orCreateTestableResources.addOverride(
+ R.dimen.status_bar_icon_drawing_alpha,
+ RESOURCE_ALPHA,
+ )
+ }
+
+ @Test
+ fun transitionTo_lightsOutMode_batteryTranslucent() {
+ underTest.transitionTo(/* mode= */ MODE_LIGHTS_OUT, /* animate= */ false)
+
+ val alpha = batteryView.capturedAlpha()
+ assertThat(alpha).isGreaterThan(0)
+ assertThat(alpha).isLessThan(1)
+ }
+
+ @Test
+ fun transitionTo_lightsOutMode_statusIconsHidden() {
+ underTest.transitionTo(/* mode= */ MODE_LIGHTS_OUT, /* animate= */ false)
+
+ assertThat(statusIcons.capturedAlpha()).isEqualTo(0)
+ }
+
+ @Test
+ fun transitionTo_lightsOutMode_startIconsHidden() {
+ underTest.transitionTo(/* mode= */ MODE_LIGHTS_OUT, /* animate= */ false)
+
+ assertThat(startIcons.capturedAlpha()).isEqualTo(0)
+ }
+
+ @Test
+ fun transitionTo_lightsOutTransparentMode_batteryTranslucent() {
+ underTest.transitionTo(/* mode= */ MODE_LIGHTS_OUT_TRANSPARENT, /* animate= */ false)
+
+ val alpha = batteryView.capturedAlpha()
+ assertThat(alpha).isGreaterThan(0)
+ assertThat(alpha).isLessThan(1)
+ }
+
+ @Test
+ fun transitionTo_lightsOutTransparentMode_statusIconsHidden() {
+ underTest.transitionTo(/* mode= */ MODE_LIGHTS_OUT_TRANSPARENT, /* animate= */ false)
+
+ assertThat(statusIcons.capturedAlpha()).isEqualTo(0)
+ }
+
+ @Test
+ fun transitionTo_lightsOutTransparentMode_startIconsHidden() {
+ underTest.transitionTo(/* mode= */ MODE_LIGHTS_OUT_TRANSPARENT, /* animate= */ false)
+
+ assertThat(startIcons.capturedAlpha()).isEqualTo(0)
+ }
+
+ @Test
+ fun transitionTo_translucentMode_batteryIconShown() {
+ underTest.transitionTo(/* mode= */ MODE_TRANSLUCENT, /* animate= */ false)
+
+ assertThat(batteryView.capturedAlpha()).isEqualTo(1)
+ }
+
+ @Test
+ fun transitionTo_semiTransparentMode_statusIconsShown() {
+ underTest.transitionTo(/* mode= */ MODE_SEMI_TRANSPARENT, /* animate= */ false)
+
+ assertThat(statusIcons.capturedAlpha()).isEqualTo(1)
+ }
+
+ @Test
+ fun transitionTo_transparentMode_startIconsShown() {
+ // Transparent is the default, so we need to switch to a different mode first
+ underTest.transitionTo(/* mode= */ MODE_OPAQUE, /* animate= */ false)
+ reset(startIcons)
+
+ underTest.transitionTo(/* mode= */ MODE_TRANSPARENT, /* animate= */ false)
+
+ assertThat(startIcons.capturedAlpha()).isEqualTo(1)
+ }
+
+ @Test
+ fun transitionTo_opaqueMode_batteryIconUsesResourceAlpha() {
+ underTest.transitionTo(/* mode= */ MODE_OPAQUE, /* animate= */ false)
+
+ assertThat(batteryView.capturedAlpha()).isEqualTo(RESOURCE_ALPHA)
+ }
+
+ @Test
+ fun transitionTo_opaqueMode_statusIconsUseResourceAlpha() {
+ underTest.transitionTo(/* mode= */ MODE_OPAQUE, /* animate= */ false)
+
+ assertThat(statusIcons.capturedAlpha()).isEqualTo(RESOURCE_ALPHA)
+ }
+
+ @Test
+ fun transitionTo_opaqueMode_startIconsUseResourceAlpha() {
+ underTest.transitionTo(/* mode= */ MODE_OPAQUE, /* animate= */ false)
+
+ assertThat(startIcons.capturedAlpha()).isEqualTo(RESOURCE_ALPHA)
+ }
+
+ @Test
+ fun onHeadsUpStateChanged_true_semiTransparentMode_startIconsShown() {
+ underTest.transitionTo(/* mode= */ MODE_SEMI_TRANSPARENT, /* animate= */ false)
+ reset(startIcons)
+
+ underTest.onHeadsUpStateChanged(true)
+
+ assertThat(startIcons.capturedAlpha()).isEqualTo(1)
+ }
+
+ @Test
+ fun onHeadsUpStateChanged_true_opaqueMode_startIconsUseResourceAlpha() {
+ underTest.transitionTo(/* mode= */ MODE_OPAQUE, /* animate= */ false)
+ reset(startIcons)
+
+ underTest.onHeadsUpStateChanged(true)
+
+ assertThat(startIcons.capturedAlpha()).isEqualTo(RESOURCE_ALPHA)
+ }
+
+ /** Regression test for b/291173113. */
+ @Test
+ fun onHeadsUpStateChanged_true_lightsOutMode_startIconsUseResourceAlpha() {
+ underTest.transitionTo(/* mode= */ MODE_LIGHTS_OUT, /* animate= */ false)
+ reset(startIcons)
+
+ underTest.onHeadsUpStateChanged(true)
+
+ assertThat(startIcons.capturedAlpha()).isEqualTo(RESOURCE_ALPHA)
+ }
+
+ @Test
+ fun onHeadsUpStateChanged_false_semiTransparentMode_startIconsShown() {
+ underTest.transitionTo(/* mode= */ MODE_SEMI_TRANSPARENT, /* animate= */ false)
+ reset(startIcons)
+
+ underTest.onHeadsUpStateChanged(false)
+
+ assertThat(startIcons.capturedAlpha()).isEqualTo(1)
+ }
+
+ @Test
+ fun onHeadsUpStateChanged_false_opaqueMode_startIconsUseResourceAlpha() {
+ underTest.transitionTo(/* mode= */ MODE_OPAQUE, /* animate= */ false)
+ reset(startIcons)
+
+ underTest.onHeadsUpStateChanged(false)
+
+ assertThat(startIcons.capturedAlpha()).isEqualTo(RESOURCE_ALPHA)
+ }
+
+ @Test
+ fun onHeadsUpStateChanged_false_lightsOutMode_startIconsHidden() {
+ underTest.transitionTo(/* mode= */ MODE_LIGHTS_OUT, /* animate= */ false)
+ reset(startIcons)
+
+ underTest.onHeadsUpStateChanged(false)
+
+ assertThat(startIcons.capturedAlpha()).isEqualTo(0)
+ }
+
+ private fun View.capturedAlpha(): Float {
+ val captor = argumentCaptor<Float>()
+ verify(this).alpha = captor.capture()
+ return captor.value
+ }
+
+ private companion object {
+ const val RESOURCE_ALPHA = 0.34f
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt
index 1759fb7..210c5ab 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt
@@ -463,10 +463,10 @@
val provider = StatusBarContentInsetsProvider(contextMock, configurationController,
mock(DumpManager::class.java))
- configuration.windowConfiguration.maxBounds = Rect(0, 0, 1080, 2160)
+ configuration.windowConfiguration.setMaxBounds(Rect(0, 0, 1080, 2160))
val firstDisplayInsets = provider.getStatusBarContentAreaForRotation(ROTATION_NONE)
- configuration.windowConfiguration.maxBounds = Rect(0, 0, 800, 600)
+ configuration.windowConfiguration.setMaxBounds(Rect(0, 0, 800, 600))
// WHEN: get insets on the second display
val secondDisplayInsets = provider.getStatusBarContentAreaForRotation(ROTATION_NONE)
@@ -482,14 +482,14 @@
val provider = StatusBarContentInsetsProvider(contextMock, configurationController,
mock(DumpManager::class.java))
- configuration.windowConfiguration.maxBounds = Rect(0, 0, 1080, 2160)
+ configuration.windowConfiguration.setMaxBounds(Rect(0, 0, 1080, 2160))
val firstDisplayInsetsFirstCall = provider
.getStatusBarContentAreaForRotation(ROTATION_NONE)
- configuration.windowConfiguration.maxBounds = Rect(0, 0, 800, 600)
+ configuration.windowConfiguration.setMaxBounds(Rect(0, 0, 800, 600))
provider.getStatusBarContentAreaForRotation(ROTATION_NONE)
- configuration.windowConfiguration.maxBounds = Rect(0, 0, 1080, 2160)
+ configuration.windowConfiguration.setMaxBounds(Rect(0, 0, 1080, 2160))
// WHEN: get insets on the first display again
val firstDisplayInsetsSecondCall = provider
@@ -577,4 +577,4 @@
" expected=$expected actual=$actual",
expected.equals(actual))
}
-}
\ No newline at end of file
+}
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 ed9cf3f..0da7360 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,8 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import static kotlinx.coroutines.test.TestCoroutineDispatchersKt.StandardTestDispatcher;
+
import android.service.trust.TrustAgentService;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -73,6 +75,8 @@
import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
+import com.android.systemui.keyguard.domain.interactor.WindowManagerLockscreenVisibilityInteractor;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.navigationbar.TaskbarDelegate;
import com.android.systemui.plugins.ActivityStarter;
@@ -201,7 +205,10 @@
mBouncerView,
mAlternateBouncerInteractor,
mUdfpsOverlayInteractor,
- mActivityStarter) {
+ mActivityStarter,
+ mock(KeyguardTransitionInteractor.class),
+ StandardTestDispatcher(null, null),
+ () -> mock(WindowManagerLockscreenVisibilityInteractor.class)) {
@Override
public ViewRootImpl getViewRootImpl() {
return mViewRootImpl;
@@ -701,7 +708,10 @@
mBouncerView,
mAlternateBouncerInteractor,
mUdfpsOverlayInteractor,
- mActivityStarter) {
+ mActivityStarter,
+ mock(KeyguardTransitionInteractor.class),
+ StandardTestDispatcher(null, null),
+ () -> mock(WindowManagerLockscreenVisibilityInteractor.class)) {
@Override
public ViewRootImpl getViewRootImpl() {
return mViewRootImpl;
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 9c52788..34c4ac1 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
@@ -32,7 +32,6 @@
import androidx.test.filters.SmallTest;
import com.android.internal.logging.testing.FakeMetricsLogger;
-import com.android.systemui.ForegroundServiceNotificationListener;
import com.android.systemui.InitController;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.ActivityStarter;
@@ -94,7 +93,6 @@
mDependency.injectTestDependency(ShadeController.class, mShadeController);
mDependency.injectMockDependency(NotificationRemoteInputManager.Callback.class);
mDependency.injectMockDependency(NotificationShadeWindowController.class);
- mDependency.injectMockDependency(ForegroundServiceNotificationListener.class);
NotificationShadeWindowView notificationShadeWindowView =
mock(NotificationShadeWindowView.class);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java
index 14edf3d..e5bbead 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.policy;
+import static android.app.Notification.FLAG_FSI_REQUESTED_BUT_DENIED;
+
import static com.android.systemui.dump.LogBufferHelperKt.logcatLogBuffer;
import static com.google.common.truth.Truth.assertThat;
@@ -40,7 +42,6 @@
import android.content.Context;
import android.content.Intent;
import android.os.Handler;
-import android.service.notification.StatusBarNotification;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -48,6 +49,7 @@
import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.testing.UiEventLoggerFake;
+import com.android.systemui.R;
import com.android.systemui.statusbar.AlertingNotificationManager;
import com.android.systemui.statusbar.AlertingNotificationManagerTest;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -63,16 +65,11 @@
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
public class HeadsUpManagerTest extends AlertingNotificationManagerTest {
- private static final int TEST_A11Y_AUTO_DISMISS_TIME = 600;
- private static final int TEST_A11Y_TIMEOUT_TIME = 5_000;
+ private static final int TEST_TOUCH_ACCEPTANCE_TIME = 200;
+ private static final int TEST_A11Y_AUTO_DISMISS_TIME = 1_000;
+ private static final int TEST_A11Y_TIMEOUT_TIME = 3_000;
- private HeadsUpManager mHeadsUpManager;
- private boolean mLivesPastNormalTime;
private UiEventLoggerFake mUiEventLoggerFake = new UiEventLoggerFake();
- @Mock private HeadsUpManager.HeadsUpEntry mAlertEntry;
- @Mock private NotificationEntry mEntry;
- @Mock private StatusBarNotification mSbn;
- @Mock private Notification mNotification;
private final HeadsUpManagerLogger mLogger = spy(new HeadsUpManagerLogger(logcatLogBuffer()));
@Mock private AccessibilityManagerWrapper mAccessibilityMgr;
@@ -83,27 +80,81 @@
AccessibilityManagerWrapper accessibilityManagerWrapper,
UiEventLogger uiEventLogger) {
super(context, logger, handler, accessibilityManagerWrapper, uiEventLogger);
+ mTouchAcceptanceDelay = TEST_TOUCH_ACCEPTANCE_TIME;
mMinimumDisplayTime = TEST_MINIMUM_DISPLAY_TIME;
- mStickyDisplayTime = TEST_STICKY_DISPLAY_TIME;
mAutoDismissNotificationDecay = TEST_AUTO_DISMISS_TIME;
+ mStickyDisplayTime = TEST_STICKY_AUTO_DISMISS_TIME;
}
}
+ private HeadsUpManager createHeadsUpManager() {
+ return new TestableHeadsUpManager(mContext, mLogger, mTestHandler, mAccessibilityMgr,
+ mUiEventLoggerFake);
+ }
+
@Override
protected AlertingNotificationManager createAlertingNotificationManager() {
- return mHeadsUpManager;
+ return createHeadsUpManager();
}
+ private NotificationEntry createStickyEntry(int id) {
+ final Notification notif = new Notification.Builder(mContext, "")
+ .setSmallIcon(R.drawable.ic_person)
+ .setFullScreenIntent(mock(PendingIntent.class), /* highPriority */ true)
+ .build();
+ return createEntry(id, notif);
+ }
+
+ private NotificationEntry createStickyForSomeTimeEntry(int id) {
+ final Notification notif = new Notification.Builder(mContext, "")
+ .setSmallIcon(R.drawable.ic_person)
+ .setFlag(FLAG_FSI_REQUESTED_BUT_DENIED, true)
+ .build();
+ return createEntry(id, notif);
+ }
+
+ private PendingIntent createFullScreenIntent() {
+ return PendingIntent.getActivity(
+ getContext(), 0, new Intent(getContext(), this.getClass()),
+ PendingIntent.FLAG_MUTABLE_UNAUDITED);
+ }
+
+ private NotificationEntry createFullScreenIntentEntry(int id) {
+ final Notification notif = new Notification.Builder(mContext, "")
+ .setSmallIcon(R.drawable.ic_person)
+ .setFullScreenIntent(createFullScreenIntent(), /* highPriority */ true)
+ .build();
+ return createEntry(id, notif);
+ }
+
+
+ private void useAccessibilityTimeout(boolean use) {
+ if (use) {
+ doReturn(TEST_A11Y_AUTO_DISMISS_TIME).when(mAccessibilityMgr)
+ .getRecommendedTimeoutMillis(anyInt(), anyInt());
+ } else {
+ when(mAccessibilityMgr.getRecommendedTimeoutMillis(anyInt(), anyInt())).then(
+ i -> i.getArgument(0));
+ }
+ }
+
+
@Before
@Override
public void setUp() {
initMocks(this);
- when(mEntry.getSbn()).thenReturn(mSbn);
- when(mEntry.getKey()).thenReturn("entryKey");
- when(mSbn.getNotification()).thenReturn(mNotification);
super.setUp();
- mHeadsUpManager = new TestableHeadsUpManager(mContext, mLogger, mTestHandler,
- mAccessibilityMgr, mUiEventLoggerFake);
+
+ assertThat(TEST_MINIMUM_DISPLAY_TIME).isLessThan(TEST_AUTO_DISMISS_TIME);
+ assertThat(TEST_AUTO_DISMISS_TIME).isLessThan(TEST_STICKY_AUTO_DISMISS_TIME);
+ assertThat(TEST_STICKY_AUTO_DISMISS_TIME).isLessThan(TEST_A11Y_AUTO_DISMISS_TIME);
+
+ assertThat(TEST_TOUCH_ACCEPTANCE_TIME + TEST_AUTO_DISMISS_TIME).isLessThan(
+ TEST_TIMEOUT_TIME);
+ assertThat(TEST_TOUCH_ACCEPTANCE_TIME + TEST_STICKY_AUTO_DISMISS_TIME).isLessThan(
+ TEST_TIMEOUT_TIME);
+ assertThat(TEST_TOUCH_ACCEPTANCE_TIME + TEST_A11Y_AUTO_DISMISS_TIME).isLessThan(
+ TEST_A11Y_TIMEOUT_TIME);
}
@After
@@ -114,193 +165,327 @@
@Test
public void testHunRemovedLogging() {
- mAlertEntry.mEntry = mEntry;
- mHeadsUpManager.onAlertEntryRemoved(mAlertEntry);
- verify(mLogger, times(1)).logNotificationActuallyRemoved(eq(mEntry));
+ final HeadsUpManager hum = createHeadsUpManager();
+ final NotificationEntry notifEntry = createEntry(/* id = */ 0);
+ final HeadsUpManager.HeadsUpEntry headsUpEntry = mock(HeadsUpManager.HeadsUpEntry.class);
+ headsUpEntry.mEntry = notifEntry;
+
+ hum.onAlertEntryRemoved(headsUpEntry);
+
+ verify(mLogger, times(1)).logNotificationActuallyRemoved(eq(notifEntry));
}
@Test
public void testShouldHeadsUpBecomePinned_hasFSI_notUnpinned_true() {
- // Set up NotifEntry with FSI
- NotificationEntry notifEntry = new NotificationEntryBuilder()
- .setSbn(createNewNotification(/* id= */ 0))
- .build();
- notifEntry.getSbn().getNotification().fullScreenIntent = PendingIntent.getActivity(
- getContext(), 0, new Intent(getContext(), this.getClass()),
- PendingIntent.FLAG_MUTABLE_UNAUDITED);
+ final HeadsUpManager hum = createHeadsUpManager();
+ final NotificationEntry notifEntry = createFullScreenIntentEntry(/* id = */ 0);
// Add notifEntry to ANM mAlertEntries map and make it NOT unpinned
- mHeadsUpManager.showNotification(notifEntry);
- HeadsUpManager.HeadsUpEntry headsUpEntry =
- mHeadsUpManager.getHeadsUpEntry(notifEntry.getKey());
+ hum.showNotification(notifEntry);
+
+ final HeadsUpManager.HeadsUpEntry headsUpEntry = hum.getHeadsUpEntry(notifEntry.getKey());
headsUpEntry.wasUnpinned = false;
- assertTrue(mHeadsUpManager.shouldHeadsUpBecomePinned(notifEntry));
+ assertTrue(hum.shouldHeadsUpBecomePinned(notifEntry));
}
@Test
public void testShouldHeadsUpBecomePinned_wasUnpinned_false() {
- // Set up NotifEntry with FSI
- NotificationEntry notifEntry = new NotificationEntryBuilder()
- .setSbn(createNewNotification(/* id= */ 0))
- .build();
- notifEntry.getSbn().getNotification().fullScreenIntent = PendingIntent.getActivity(
- getContext(), 0, new Intent(getContext(), this.getClass()),
- PendingIntent.FLAG_MUTABLE_UNAUDITED);
+ final HeadsUpManager hum = createHeadsUpManager();
+ final NotificationEntry notifEntry = createFullScreenIntentEntry(/* id = */ 0);
// Add notifEntry to ANM mAlertEntries map and make it unpinned
- mHeadsUpManager.showNotification(notifEntry);
- HeadsUpManager.HeadsUpEntry headsUpEntry =
- mHeadsUpManager.getHeadsUpEntry(notifEntry.getKey());
+ hum.showNotification(notifEntry);
+
+ final HeadsUpManager.HeadsUpEntry headsUpEntry = hum.getHeadsUpEntry(notifEntry.getKey());
headsUpEntry.wasUnpinned = true;
- assertFalse(mHeadsUpManager.shouldHeadsUpBecomePinned(notifEntry));
+ assertFalse(hum.shouldHeadsUpBecomePinned(notifEntry));
}
@Test
public void testShouldHeadsUpBecomePinned_noFSI_false() {
- // Set up NotifEntry with no FSI
- NotificationEntry notifEntry = new NotificationEntryBuilder()
- .setSbn(createNewNotification(/* id= */ 0))
- .build();
+ final HeadsUpManager hum = createHeadsUpManager();
+ final NotificationEntry entry = createEntry(/* id = */ 0);
- assertFalse(mHeadsUpManager.shouldHeadsUpBecomePinned(notifEntry));
+ assertFalse(hum.shouldHeadsUpBecomePinned(entry));
}
+
+ @Test
+ public void testShowNotification_autoDismissesIncludingTouchAcceptanceDelay() {
+ final HeadsUpManager hum = createHeadsUpManager();
+ final NotificationEntry entry = createEntry(/* id = */ 0);
+ useAccessibilityTimeout(false);
+
+ hum.showNotification(entry);
+
+ final int pastJustAutoDismissMillis =
+ TEST_TOUCH_ACCEPTANCE_TIME / 2 + TEST_AUTO_DISMISS_TIME;
+ verifyAlertingAtTime(hum, entry, true, pastJustAutoDismissMillis, "just auto dismiss");
+ }
+
+
+ @Test
+ public void testShowNotification_autoDismissesWithDefaultTimeout() {
+ final HeadsUpManager hum = createHeadsUpManager();
+ final NotificationEntry entry = createEntry(/* id = */ 0);
+ useAccessibilityTimeout(false);
+
+ hum.showNotification(entry);
+
+ final int pastDefaultTimeoutMillis = TEST_TOUCH_ACCEPTANCE_TIME
+ + (TEST_AUTO_DISMISS_TIME + TEST_A11Y_AUTO_DISMISS_TIME) / 2;
+ verifyAlertingAtTime(hum, entry, false, pastDefaultTimeoutMillis, "default timeout");
+ }
+
+
+ @Test
+ public void testShowNotification_stickyForSomeTime_autoDismissesWithStickyTimeout() {
+ final HeadsUpManager hum = createHeadsUpManager();
+ final NotificationEntry entry = createStickyForSomeTimeEntry(/* id = */ 0);
+ useAccessibilityTimeout(false);
+
+ hum.showNotification(entry);
+
+ final int pastDefaultTimeoutMillis = TEST_TOUCH_ACCEPTANCE_TIME
+ + (TEST_AUTO_DISMISS_TIME + TEST_STICKY_AUTO_DISMISS_TIME) / 2;
+ verifyAlertingAtTime(hum, entry, true, pastDefaultTimeoutMillis, "default timeout");
+ }
+
+
+ @Test
+ public void testShowNotification_sticky_neverAutoDismisses() {
+ final HeadsUpManager hum = createHeadsUpManager();
+ final NotificationEntry entry = createStickyEntry(/* id = */ 0);
+ useAccessibilityTimeout(false);
+
+ hum.showNotification(entry);
+
+ final int pastLongestAutoDismissMillis =
+ TEST_TOUCH_ACCEPTANCE_TIME + 2 * TEST_A11Y_AUTO_DISMISS_TIME;
+ final Boolean[] wasAlerting = {null};
+ final Runnable checkAlerting =
+ () -> wasAlerting[0] = hum.isAlerting(entry.getKey());
+ mTestHandler.postDelayed(checkAlerting, pastLongestAutoDismissMillis);
+ TestableLooper.get(this).processMessages(1);
+
+ assertTrue("Should still be alerting past longest auto-dismiss", wasAlerting[0]);
+ assertTrue("Should still be alerting after processing",
+ hum.isAlerting(entry.getKey()));
+ }
+
+
@Test
public void testShowNotification_autoDismissesWithAccessibilityTimeout() {
- doReturn(TEST_A11Y_AUTO_DISMISS_TIME).when(mAccessibilityMgr)
- .getRecommendedTimeoutMillis(anyInt(), anyInt());
- mHeadsUpManager.showNotification(mEntry);
- Runnable pastNormalTimeRunnable =
- () -> mLivesPastNormalTime = mHeadsUpManager.isAlerting(mEntry.getKey());
- mTestHandler.postDelayed(pastNormalTimeRunnable,
- (TEST_A11Y_AUTO_DISMISS_TIME + TEST_AUTO_DISMISS_TIME) / 2);
- mTestHandler.postDelayed(mTestTimeoutRunnable, TEST_A11Y_TIMEOUT_TIME);
+ final HeadsUpManager hum = createHeadsUpManager();
+ final NotificationEntry entry = createEntry(/* id = */ 0);
+ useAccessibilityTimeout(true);
- TestableLooper.get(this).processMessages(2);
+ hum.showNotification(entry);
- assertFalse("Test timed out", mTimedOut);
- assertTrue("Heads up should live long enough", mLivesPastNormalTime);
- assertFalse(mHeadsUpManager.isAlerting(mEntry.getKey()));
+ final int pastDefaultTimeoutMillis = TEST_TOUCH_ACCEPTANCE_TIME
+ + (TEST_AUTO_DISMISS_TIME + TEST_A11Y_AUTO_DISMISS_TIME) / 2;
+ verifyAlertingAtTime(hum, entry, true, pastDefaultTimeoutMillis, "default timeout");
+ }
+
+
+ @Test
+ public void testShowNotification_stickyForSomeTime_autoDismissesWithAccessibilityTimeout() {
+ final HeadsUpManager hum = createHeadsUpManager();
+ final NotificationEntry entry = createStickyForSomeTimeEntry(/* id = */ 0);
+ useAccessibilityTimeout(true);
+
+ hum.showNotification(entry);
+
+ final int pastStickyTimeoutMillis = TEST_TOUCH_ACCEPTANCE_TIME
+ + (TEST_STICKY_AUTO_DISMISS_TIME + TEST_A11Y_AUTO_DISMISS_TIME) / 2;
+ verifyAlertingAtTime(hum, entry, true, pastStickyTimeoutMillis, "sticky timeout");
+ }
+
+
+ @Test
+ public void testRemoveNotification_beforeMinimumDisplayTime() {
+ final HeadsUpManager hum = createHeadsUpManager();
+ final NotificationEntry entry = createEntry(/* id = */ 0);
+ useAccessibilityTimeout(false);
+
+ hum.showNotification(entry);
+
+ // Try to remove but defer, since the notification has not been shown long enough.
+ final boolean removedImmediately = hum.removeNotification(
+ entry.getKey(), false /* releaseImmediately */);
+
+ assertFalse("HUN should not be removed before minimum display time", removedImmediately);
+ assertTrue("HUN should still be alerting before minimum display time",
+ hum.isAlerting(entry.getKey()));
+
+ final int pastMinimumDisplayTimeMillis =
+ (TEST_MINIMUM_DISPLAY_TIME + TEST_AUTO_DISMISS_TIME) / 2;
+ verifyAlertingAtTime(hum, entry, false, pastMinimumDisplayTimeMillis,
+ "minimum display time");
+ }
+
+
+ @Test
+ public void testRemoveNotification_afterMinimumDisplayTime() {
+ final HeadsUpManager hum = createHeadsUpManager();
+ final NotificationEntry entry = createEntry(/* id = */ 0);
+ useAccessibilityTimeout(false);
+
+ hum.showNotification(entry);
+
+ // After the minimum display time:
+ // 1. Check whether the notification is still alerting.
+ // 2. Try to remove it and check whether the remove succeeded.
+ // 3. Check whether it is still alerting after trying to remove it.
+ final Boolean[] livedPastMinimumDisplayTime = {null};
+ final Boolean[] removedAfterMinimumDisplayTime = {null};
+ final Boolean[] livedPastRemoveAfterMinimumDisplayTime = {null};
+ final Runnable pastMinimumDisplayTimeRunnable = () -> {
+ livedPastMinimumDisplayTime[0] = hum.isAlerting(entry.getKey());
+ removedAfterMinimumDisplayTime[0] = hum.removeNotification(
+ entry.getKey(), /* releaseImmediately = */ false);
+ livedPastRemoveAfterMinimumDisplayTime[0] = hum.isAlerting(entry.getKey());
+ };
+ final int pastMinimumDisplayTimeMillis =
+ (TEST_MINIMUM_DISPLAY_TIME + TEST_AUTO_DISMISS_TIME) / 2;
+ mTestHandler.postDelayed(pastMinimumDisplayTimeRunnable, pastMinimumDisplayTimeMillis);
+ // Wait until the minimum display time has passed before attempting removal.
+ TestableLooper.get(this).processMessages(1);
+
+ assertTrue("HUN should live past minimum display time",
+ livedPastMinimumDisplayTime[0]);
+ assertTrue("HUN should be removed immediately past minimum display time",
+ removedAfterMinimumDisplayTime[0]);
+ assertFalse("HUN should not live after being removed past minimum display time",
+ livedPastRemoveAfterMinimumDisplayTime[0]);
+ assertFalse(hum.isAlerting(entry.getKey()));
+ }
+
+
+ @Test
+ public void testRemoveNotification_releaseImmediately() {
+ final HeadsUpManager hum = createHeadsUpManager();
+ final NotificationEntry entry = createEntry(/* id = */ 0);
+
+ hum.showNotification(entry);
+
+ // Remove forcibly with releaseImmediately = true.
+ final boolean removedImmediately = hum.removeNotification(
+ entry.getKey(), /* releaseImmediately = */ true);
+
+ assertTrue(removedImmediately);
+ assertFalse(hum.isAlerting(entry.getKey()));
}
@Test
public void testIsSticky_rowPinnedAndExpanded_true() {
- NotificationEntry notifEntry = new NotificationEntryBuilder()
- .setSbn(createNewNotification(/* id= */ 0))
- .build();
-
+ final HeadsUpManager hum = createHeadsUpManager();
+ final NotificationEntry notifEntry = createEntry(/* id = */ 0);
when(mRow.isPinned()).thenReturn(true);
notifEntry.setRow(mRow);
- mHeadsUpManager.showNotification(notifEntry);
+ hum.showNotification(notifEntry);
- HeadsUpManager.HeadsUpEntry headsUpEntry =
- mHeadsUpManager.getHeadsUpEntry(notifEntry.getKey());
+ final HeadsUpManager.HeadsUpEntry headsUpEntry = hum.getHeadsUpEntry(notifEntry.getKey());
headsUpEntry.setExpanded(true);
- assertTrue(mHeadsUpManager.isSticky(notifEntry.getKey()));
+ assertTrue(hum.isSticky(notifEntry.getKey()));
}
@Test
public void testIsSticky_remoteInputActive_true() {
- NotificationEntry notifEntry = new NotificationEntryBuilder()
- .setSbn(createNewNotification(/* id= */ 0))
- .build();
+ final HeadsUpManager hum = createHeadsUpManager();
+ final NotificationEntry notifEntry = createEntry(/* id = */ 0);
- mHeadsUpManager.showNotification(notifEntry);
+ hum.showNotification(notifEntry);
- HeadsUpManager.HeadsUpEntry headsUpEntry =
- mHeadsUpManager.getHeadsUpEntry(notifEntry.getKey());
-
+ final HeadsUpManager.HeadsUpEntry headsUpEntry = hum.getHeadsUpEntry(notifEntry.getKey());
headsUpEntry.remoteInputActive = true;
- assertTrue(mHeadsUpManager.isSticky(notifEntry.getKey()));
+ assertTrue(hum.isSticky(notifEntry.getKey()));
}
@Test
public void testIsSticky_hasFullScreenIntent_true() {
- NotificationEntry notifEntry = new NotificationEntryBuilder()
- .setSbn(createNewNotification(/* id= */ 0))
- .build();
+ final HeadsUpManager hum = createHeadsUpManager();
+ final NotificationEntry notifEntry = createFullScreenIntentEntry(/* id = */ 0);
- mHeadsUpManager.showNotification(notifEntry);
+ hum.showNotification(notifEntry);
- HeadsUpManager.HeadsUpEntry headsUpEntry =
- mHeadsUpManager.getHeadsUpEntry(notifEntry.getKey());
-
- notifEntry.getSbn().getNotification().fullScreenIntent = PendingIntent.getActivity(
- getContext(), 0, new Intent(getContext(), this.getClass()),
- PendingIntent.FLAG_MUTABLE_UNAUDITED);
-
- assertTrue(mHeadsUpManager.isSticky(notifEntry.getKey()));
+ assertTrue(hum.isSticky(notifEntry.getKey()));
}
+
@Test
- public void testIsSticky_stickyAndNotDemoted_true() {
- NotificationEntry alertEntry = new NotificationEntryBuilder()
- .setSbn(createStickySbn(0))
- .build();
+ public void testIsSticky_stickyForSomeTime_false() {
+ final HeadsUpManager hum = createHeadsUpManager();
+ final NotificationEntry entry = createStickyForSomeTimeEntry(/* id = */ 0);
- mHeadsUpManager.showNotification(alertEntry);
+ hum.showNotification(entry);
- assertTrue(mHeadsUpManager.isSticky(alertEntry.getKey()));
+ assertFalse(hum.isSticky(entry.getKey()));
}
+
@Test
public void testIsSticky_false() {
- NotificationEntry notifEntry = new NotificationEntryBuilder()
- .setSbn(createNewNotification(/* id= */ 0))
- .build();
+ final HeadsUpManager hum = createHeadsUpManager();
+ final NotificationEntry notifEntry = createEntry(/* id = */ 0);
- mHeadsUpManager.showNotification(notifEntry);
+ hum.showNotification(notifEntry);
- HeadsUpManager.HeadsUpEntry headsUpEntry =
- mHeadsUpManager.getHeadsUpEntry(notifEntry.getKey());
+ final HeadsUpManager.HeadsUpEntry headsUpEntry = hum.getHeadsUpEntry(notifEntry.getKey());
headsUpEntry.setExpanded(false);
headsUpEntry.remoteInputActive = false;
- assertFalse(mHeadsUpManager.isSticky(notifEntry.getKey()));
+ assertFalse(hum.isSticky(notifEntry.getKey()));
}
@Test
public void testCompareTo_withNullEntries() {
- NotificationEntry alertEntry = new NotificationEntryBuilder().setTag("alert").build();
- mHeadsUpManager.showNotification(alertEntry);
+ final HeadsUpManager hum = createHeadsUpManager();
+ final NotificationEntry alertEntry = new NotificationEntryBuilder().setTag("alert").build();
- assertThat(mHeadsUpManager.compare(alertEntry, null)).isLessThan(0);
- assertThat(mHeadsUpManager.compare(null, alertEntry)).isGreaterThan(0);
- assertThat(mHeadsUpManager.compare(null, null)).isEqualTo(0);
+ hum.showNotification(alertEntry);
+
+ assertThat(hum.compare(alertEntry, null)).isLessThan(0);
+ assertThat(hum.compare(null, alertEntry)).isGreaterThan(0);
+ assertThat(hum.compare(null, null)).isEqualTo(0);
}
@Test
public void testCompareTo_withNonAlertEntries() {
- NotificationEntry nonAlertEntry1 = new NotificationEntryBuilder().setTag("nae1").build();
- NotificationEntry nonAlertEntry2 = new NotificationEntryBuilder().setTag("nae2").build();
- NotificationEntry alertEntry = new NotificationEntryBuilder().setTag("alert").build();
- mHeadsUpManager.showNotification(alertEntry);
+ final HeadsUpManager hum = createHeadsUpManager();
- assertThat(mHeadsUpManager.compare(alertEntry, nonAlertEntry1)).isLessThan(0);
- assertThat(mHeadsUpManager.compare(nonAlertEntry1, alertEntry)).isGreaterThan(0);
- assertThat(mHeadsUpManager.compare(nonAlertEntry1, nonAlertEntry2)).isEqualTo(0);
+ final NotificationEntry nonAlertEntry1 = new NotificationEntryBuilder().setTag(
+ "nae1").build();
+ final NotificationEntry nonAlertEntry2 = new NotificationEntryBuilder().setTag(
+ "nae2").build();
+ final NotificationEntry alertEntry = new NotificationEntryBuilder().setTag("alert").build();
+ hum.showNotification(alertEntry);
+
+ assertThat(hum.compare(alertEntry, nonAlertEntry1)).isLessThan(0);
+ assertThat(hum.compare(nonAlertEntry1, alertEntry)).isGreaterThan(0);
+ assertThat(hum.compare(nonAlertEntry1, nonAlertEntry2)).isEqualTo(0);
}
@Test
public void testAlertEntryCompareTo_ongoingCallLessThanActiveRemoteInput() {
- HeadsUpManager.HeadsUpEntry ongoingCall = mHeadsUpManager.new HeadsUpEntry();
+ final HeadsUpManager hum = createHeadsUpManager();
+
+ final HeadsUpManager.HeadsUpEntry ongoingCall = hum.new HeadsUpEntry();
ongoingCall.setEntry(new NotificationEntryBuilder()
- .setSbn(createNewSbn(0,
+ .setSbn(createSbn(/* id = */ 0,
new Notification.Builder(mContext, "")
.setCategory(Notification.CATEGORY_CALL)
.setOngoing(true)))
.build());
- HeadsUpManager.HeadsUpEntry activeRemoteInput = mHeadsUpManager.new HeadsUpEntry();
- activeRemoteInput.setEntry(new NotificationEntryBuilder()
- .setSbn(createNewNotification(1))
- .build());
+ final HeadsUpManager.HeadsUpEntry activeRemoteInput = hum.new HeadsUpEntry();
+ activeRemoteInput.setEntry(createEntry(/* id = */ 1));
activeRemoteInput.remoteInputActive = true;
assertThat(ongoingCall.compareTo(activeRemoteInput)).isLessThan(0);
@@ -309,20 +494,20 @@
@Test
public void testAlertEntryCompareTo_incomingCallLessThanActiveRemoteInput() {
- HeadsUpManager.HeadsUpEntry incomingCall = mHeadsUpManager.new HeadsUpEntry();
- Person person = new Person.Builder().setName("person").build();
- PendingIntent intent = mock(PendingIntent.class);
+ final HeadsUpManager hum = createHeadsUpManager();
+
+ final HeadsUpManager.HeadsUpEntry incomingCall = hum.new HeadsUpEntry();
+ final Person person = new Person.Builder().setName("person").build();
+ final PendingIntent intent = mock(PendingIntent.class);
incomingCall.setEntry(new NotificationEntryBuilder()
- .setSbn(createNewSbn(0,
+ .setSbn(createSbn(/* id = */ 0,
new Notification.Builder(mContext, "")
.setStyle(Notification.CallStyle
.forIncomingCall(person, intent, intent))))
.build());
- HeadsUpManager.HeadsUpEntry activeRemoteInput = mHeadsUpManager.new HeadsUpEntry();
- activeRemoteInput.setEntry(new NotificationEntryBuilder()
- .setSbn(createNewNotification(1))
- .build());
+ final HeadsUpManager.HeadsUpEntry activeRemoteInput = hum.new HeadsUpEntry();
+ activeRemoteInput.setEntry(createEntry(/* id = */ 1));
activeRemoteInput.remoteInputActive = true;
assertThat(incomingCall.compareTo(activeRemoteInput)).isLessThan(0);
@@ -331,22 +516,18 @@
@Test
public void testPinEntry_logsPeek() {
- // Needs full screen intent in order to be pinned
- final PendingIntent fullScreenIntent = PendingIntent.getActivity(mContext, 0,
- new Intent().setPackage(mContext.getPackageName()), PendingIntent.FLAG_MUTABLE);
+ final HeadsUpManager hum = createHeadsUpManager();
- HeadsUpManager.HeadsUpEntry entryToPin = mHeadsUpManager.new HeadsUpEntry();
- entryToPin.setEntry(new NotificationEntryBuilder()
- .setSbn(createNewSbn(0,
- new Notification.Builder(mContext, "")
- .setFullScreenIntent(fullScreenIntent, true)))
- .build());
+ // Needs full screen intent in order to be pinned
+ final HeadsUpManager.HeadsUpEntry entryToPin = hum.new HeadsUpEntry();
+ entryToPin.setEntry(createFullScreenIntentEntry(/* id = */ 0));
+
// Note: the standard way to show a notification would be calling showNotification rather
// than onAlertEntryAdded. However, in practice showNotification in effect adds
// the notification and then updates it; in order to not log twice, the entry needs
// to have a functional ExpandableNotificationRow that can keep track of whether it's
// pinned or not (via isRowPinned()). That feels like a lot to pull in to test this one bit.
- mHeadsUpManager.onAlertEntryAdded(entryToPin);
+ hum.onAlertEntryAdded(entryToPin);
assertEquals(1, mUiEventLoggerFake.numLogs());
assertEquals(HeadsUpManager.NotificationPeekEvent.NOTIFICATION_PEEK.getId(),
@@ -355,14 +536,15 @@
@Test
public void testSetUserActionMayIndirectlyRemove() {
- NotificationEntry notifEntry = new NotificationEntryBuilder()
- .setSbn(createNewNotification(/* id= */ 0))
- .build();
+ final HeadsUpManager hum = createHeadsUpManager();
+ final NotificationEntry notifEntry = createEntry(/* id = */ 0);
- mHeadsUpManager.showNotification(notifEntry);
- assertFalse(mHeadsUpManager.canRemoveImmediately(notifEntry.getKey()));
+ hum.showNotification(notifEntry);
- mHeadsUpManager.setUserActionMayIndirectlyRemove(notifEntry);
- assertTrue(mHeadsUpManager.canRemoveImmediately(notifEntry.getKey()));
+ assertFalse(hum.canRemoveImmediately(notifEntry.getKey()));
+
+ hum.setUserActionMayIndirectlyRemove(notifEntry);
+
+ assertTrue(hum.canRemoveImmediately(notifEntry.getKey()));
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java
index 5cabcd4..cae892f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java
@@ -36,6 +36,7 @@
import com.android.keyguard.logging.KeyguardUpdateMonitorLogger;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import dagger.Lazy;
@@ -67,6 +68,8 @@
private Lazy<KeyguardUnlockAnimationController> mKeyguardUnlockAnimationControllerLazy;
@Mock
private KeyguardUpdateMonitorLogger mLogger;
+ @Mock
+ private FeatureFlags mFeatureFlags;
@Captor
private ArgumentCaptor<KeyguardUpdateMonitorCallback> mUpdateCallbackCaptor;
@@ -80,7 +83,8 @@
mLockPatternUtils,
mKeyguardUnlockAnimationControllerLazy,
mLogger,
- mDumpManager);
+ mDumpManager,
+ mFeatureFlags);
}
@Test
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardSurfaceBehindRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardSurfaceBehindRepository.kt
new file mode 100644
index 0000000..823f29a
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardSurfaceBehindRepository.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.data.repository
+
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+class FakeKeyguardSurfaceBehindRepository : KeyguardSurfaceBehindRepository {
+ private val _isAnimatingSurface = MutableStateFlow(false)
+ override val isAnimatingSurface = _isAnimatingSurface.asStateFlow()
+
+ override fun setAnimatingSurface(animating: Boolean) {
+ _isAnimatingSurface.value = animating
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorFactory.kt
index 312ade5..23faaf3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorFactory.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorFactory.kt
@@ -18,6 +18,8 @@
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
+import com.android.systemui.util.mockito.mock
+import dagger.Lazy
import kotlinx.coroutines.CoroutineScope
/**
@@ -30,18 +32,36 @@
fun create(
scope: CoroutineScope,
repository: KeyguardTransitionRepository = FakeKeyguardTransitionRepository(),
+ keyguardInteractor: KeyguardInteractor =
+ KeyguardInteractorFactory.create().keyguardInteractor,
+ fromLockscreenTransitionInteractor: Lazy<FromLockscreenTransitionInteractor> = Lazy {
+ mock<FromLockscreenTransitionInteractor>()
+ },
+ fromPrimaryBouncerTransitionInteractor: Lazy<FromPrimaryBouncerTransitionInteractor> =
+ Lazy {
+ mock<FromPrimaryBouncerTransitionInteractor>()
+ },
): WithDependencies {
return WithDependencies(
repository = repository,
+ keyguardInteractor = keyguardInteractor,
+ fromLockscreenTransitionInteractor = fromLockscreenTransitionInteractor,
+ fromPrimaryBouncerTransitionInteractor = fromPrimaryBouncerTransitionInteractor,
KeyguardTransitionInteractor(
scope = scope,
repository = repository,
+ keyguardInteractor = { keyguardInteractor },
+ fromLockscreenTransitionInteractor = fromLockscreenTransitionInteractor,
+ fromPrimaryBouncerTransitionInteractor = fromPrimaryBouncerTransitionInteractor,
)
)
}
data class WithDependencies(
val repository: KeyguardTransitionRepository,
+ val keyguardInteractor: KeyguardInteractor,
+ val fromLockscreenTransitionInteractor: Lazy<FromLockscreenTransitionInteractor>,
+ val fromPrimaryBouncerTransitionInteractor: Lazy<FromPrimaryBouncerTransitionInteractor>,
val keyguardTransitionInteractor: KeyguardTransitionInteractor,
)
}
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 0829f31..dd45331 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
@@ -18,9 +18,11 @@
import android.content.pm.UserInfo
import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.data.model.AuthenticationMethodModel as DataLayerAuthenticationMethodModel
import com.android.systemui.authentication.data.repository.AuthenticationRepository
import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
+import com.android.systemui.authentication.domain.model.AuthenticationMethodModel as DomainLayerAuthenticationMethodModel
import com.android.systemui.bouncer.data.repository.BouncerRepository
import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
@@ -123,6 +125,7 @@
repository: SceneContainerRepository = fakeSceneContainerRepository()
): SceneInteractor {
return SceneInteractor(
+ applicationScope = applicationScope(),
repository = repository,
logger = mock(),
)
@@ -201,5 +204,18 @@
RemoteUserInput(10f, 40f, RemoteUserInputAction.MOVE),
RemoteUserInput(10f, 40f, RemoteUserInputAction.UP),
)
+
+ fun DomainLayerAuthenticationMethodModel.toDataLayer(): DataLayerAuthenticationMethodModel {
+ return when (this) {
+ DomainLayerAuthenticationMethodModel.None -> DataLayerAuthenticationMethodModel.None
+ DomainLayerAuthenticationMethodModel.Swipe ->
+ DataLayerAuthenticationMethodModel.None
+ DomainLayerAuthenticationMethodModel.Pin -> DataLayerAuthenticationMethodModel.Pin
+ DomainLayerAuthenticationMethodModel.Password ->
+ DataLayerAuthenticationMethodModel.Password
+ DomainLayerAuthenticationMethodModel.Pattern ->
+ DataLayerAuthenticationMethodModel.Pattern
+ }
+ }
}
}
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 70aff05..d6702b7 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -134,6 +134,7 @@
import android.service.autofill.CompositeUserData;
import android.service.autofill.Dataset;
import android.service.autofill.Dataset.DatasetEligibleReason;
+import android.service.autofill.Field;
import android.service.autofill.FieldClassification;
import android.service.autofill.FieldClassification.Match;
import android.service.autofill.FieldClassificationUserData;
@@ -189,6 +190,7 @@
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
+import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
@@ -1780,11 +1782,11 @@
*/
private static class DatasetComputationContainer {
// List of all autofill ids that have a corresponding datasets
- Set<AutofillId> mAutofillIds = new ArraySet<>();
+ Set<AutofillId> mAutofillIds = new LinkedHashSet<>();
// Set of datasets. Kept separately, to be able to be used directly for composing
// FillResponse.
Set<Dataset> mDatasets = new LinkedHashSet<>();
- ArrayMap<AutofillId, Set<Dataset>> mAutofillIdToDatasetMap = new ArrayMap<>();
+ Map<AutofillId, Set<Dataset>> mAutofillIdToDatasetMap = new LinkedHashMap<>();
public String toString() {
final StringBuilder builder = new StringBuilder("DatasetComputationContainer[");
@@ -1822,7 +1824,7 @@
// for now to keep safe. TODO(b/266379948): Revisit this logic.
Set<Dataset> datasets = c2.mAutofillIdToDatasetMap.get(id);
- Set<Dataset> copyDatasets = new ArraySet<>(datasets);
+ Set<Dataset> copyDatasets = new LinkedHashSet<>(datasets);
c1.mAutofillIds.add(id);
c1.mAutofillIdToDatasetMap.put(id, copyDatasets);
c1.mDatasets.addAll(copyDatasets);
@@ -1838,6 +1840,13 @@
}
}
+ /**
+ * Computes datasets that are eligible to be shown based on provider detections.
+ * Datasets are populated in the provided container for them to be later merged with the
+ * PCC eligible datasets based on preference strategy.
+ * @param response
+ * @param container
+ */
private void computeDatasetsForProviderAndUpdateContainer(
FillResponse response, DatasetComputationContainer container) {
@DatasetEligibleReason int globalPickReason = PICK_REASON_UNKNOWN;
@@ -1849,9 +1858,9 @@
}
List<Dataset> datasets = response.getDatasets();
if (datasets == null) return;
- ArrayMap<AutofillId, Set<Dataset>> autofillIdToDatasetMap = new ArrayMap<>();
+ Map<AutofillId, Set<Dataset>> autofillIdToDatasetMap = new LinkedHashMap<>();
Set<Dataset> eligibleDatasets = new LinkedHashSet<>();
- Set<AutofillId> eligibleAutofillIds = new ArraySet<>();
+ Set<AutofillId> eligibleAutofillIds = new LinkedHashSet<>();
for (Dataset dataset : response.getDatasets()) {
if (dataset.getFieldIds() == null || dataset.getFieldIds().isEmpty()) continue;
@DatasetEligibleReason int pickReason = globalPickReason;
@@ -1927,7 +1936,7 @@
eligibleAutofillIds.add(id);
Set<Dataset> datasetForIds = autofillIdToDatasetMap.get(id);
if (datasetForIds == null) {
- datasetForIds = new ArraySet<>();
+ datasetForIds = new LinkedHashSet<>();
}
datasetForIds.add(dataset);
autofillIdToDatasetMap.put(id, datasetForIds);
@@ -1938,23 +1947,30 @@
container.mAutofillIds = eligibleAutofillIds;
}
+ /**
+ * Computes datasets that are eligible to be shown based on PCC detections.
+ * Datasets are populated in the provided container for them to be later merged with the
+ * provider eligible datasets based on preference strategy.
+ * @param response
+ * @param container
+ */
private void computeDatasetsForPccAndUpdateContainer(
FillResponse response, DatasetComputationContainer container) {
List<Dataset> datasets = response.getDatasets();
if (datasets == null) return;
synchronized (mLock) {
- ArrayMap<String, Set<AutofillId>> hintsToAutofillIdMap =
+ Map<String, Set<AutofillId>> hintsToAutofillIdMap =
mClassificationState.mHintsToAutofillIdMap;
// TODO(266379948): Handle group hints too.
- ArrayMap<String, Set<AutofillId>> groupHintsToAutofillIdMap =
+ Map<String, Set<AutofillId>> groupHintsToAutofillIdMap =
mClassificationState.mGroupHintsToAutofillIdMap;
- ArrayMap<AutofillId, Set<Dataset>> map = new ArrayMap<>();
+ Map<AutofillId, Set<Dataset>> map = new LinkedHashMap<>();
Set<Dataset> eligibleDatasets = new LinkedHashSet<>();
- Set<AutofillId> eligibleAutofillIds = new ArraySet<>();
+ Set<AutofillId> eligibleAutofillIds = new LinkedHashSet<>();
for (int i = 0; i < datasets.size(); i++) {
@@ -1970,13 +1986,35 @@
ArrayList<InlinePresentation> fieldInlinePresentations = new ArrayList<>();
ArrayList<InlinePresentation> fieldInlineTooltipPresentations = new ArrayList<>();
ArrayList<Dataset.DatasetFieldFilter> fieldFilters = new ArrayList<>();
- Set<AutofillId> datasetAutofillIds = new ArraySet<>();
+ Set<AutofillId> datasetAutofillIds = new LinkedHashSet<>();
+
+ boolean isDatasetAvailable = false;
+ Set<AutofillId> additionalDatasetAutofillIds = new LinkedHashSet<>();
+ Set<AutofillId> additionalEligibleAutofillIds = new LinkedHashSet<>();
for (int j = 0; j < dataset.getAutofillDatatypes().size(); j++) {
if (dataset.getAutofillDatatypes().get(j) == null) {
+ // TODO : revisit pickReason logic
if (dataset.getFieldIds() != null && dataset.getFieldIds().get(j) != null) {
pickReason = PICK_REASON_PCC_DETECTION_PREFERRED_WITH_PROVIDER;
}
+ // Check if the autofill id at this index is detected by PCC.
+ // If not, add that id here, otherwise, we can have duplicates when later
+ // merging with provider datasets.
+ // Howover, this doesn't make datasetAvailable for PCC on its own.
+ // For that, there has to be a datatype detected by PCC, and the dataset
+ // for that datatype provided by the provider.
+ AutofillId autofillId = dataset.getFieldIds().get(j);
+ if (!mClassificationState.mClassificationCombinedHintsMap
+ .containsKey(autofillId)) {
+ additionalEligibleAutofillIds.add(autofillId);
+ additionalDatasetAutofillIds.add(autofillId);
+ // For each of the field, copy over values.
+ copyFieldsFromDataset(dataset, j, autofillId, fieldIds, fieldValues,
+ fieldPresentations, fieldDialogPresentations,
+ fieldInlinePresentations, fieldInlineTooltipPresentations,
+ fieldFilters);
+ }
continue;
}
String hint = dataset.getAutofillDatatypes().get(j);
@@ -1984,22 +2022,18 @@
if (hintsToAutofillIdMap.containsKey(hint)) {
ArrayList<AutofillId> tempIds =
new ArrayList<>(hintsToAutofillIdMap.get(hint));
-
+ if (tempIds.isEmpty()) {
+ continue;
+ }
+ isDatasetAvailable = true;
for (AutofillId autofillId : tempIds) {
eligibleAutofillIds.add(autofillId);
datasetAutofillIds.add(autofillId);
// For each of the field, copy over values.
- fieldIds.add(autofillId);
- fieldValues.add(dataset.getFieldValues().get(j));
- // TODO(b/266379948): might need to make it more efficient by not
- // copying over value if it didn't exist. This would require creating
- // a getter for the presentations arraylist.
- fieldPresentations.add(dataset.getFieldPresentation(j));
- fieldDialogPresentations.add(dataset.getFieldDialogPresentation(j));
- fieldInlinePresentations.add(dataset.getFieldInlinePresentation(j));
- fieldInlineTooltipPresentations.add(
- dataset.getFieldInlineTooltipPresentation(j));
- fieldFilters.add(dataset.getFilter(j));
+ copyFieldsFromDataset(dataset, j, autofillId, fieldIds, fieldValues,
+ fieldPresentations, fieldDialogPresentations,
+ fieldInlinePresentations, fieldInlineTooltipPresentations,
+ fieldFilters);
}
}
// TODO(b/266379948): handle the case:
@@ -2008,34 +2042,38 @@
// TODO(b/266379948): also handle the case where there could be more types in
// the dataset, provided by the provider, however, they aren't applicable.
}
- Dataset newDataset =
- new Dataset(
- fieldIds,
- fieldValues,
- fieldPresentations,
- fieldDialogPresentations,
- fieldInlinePresentations,
- fieldInlineTooltipPresentations,
- fieldFilters,
- new ArrayList<>(),
- dataset.getFieldContent(),
- null,
- null,
- null,
- null,
- dataset.getId(),
- dataset.getAuthentication());
- newDataset.setEligibleReasonReason(pickReason);
- eligibleDatasets.add(newDataset);
- Set<Dataset> newDatasets;
- for (AutofillId autofillId : datasetAutofillIds) {
- if (map.containsKey(autofillId)) {
- newDatasets = map.get(autofillId);
- } else {
- newDatasets = new ArraySet<>();
+ if (isDatasetAvailable) {
+ datasetAutofillIds.addAll(additionalDatasetAutofillIds);
+ eligibleAutofillIds.addAll(additionalEligibleAutofillIds);
+ Dataset newDataset =
+ new Dataset(
+ fieldIds,
+ fieldValues,
+ fieldPresentations,
+ fieldDialogPresentations,
+ fieldInlinePresentations,
+ fieldInlineTooltipPresentations,
+ fieldFilters,
+ new ArrayList<>(),
+ dataset.getFieldContent(),
+ null,
+ null,
+ null,
+ null,
+ dataset.getId(),
+ dataset.getAuthentication());
+ newDataset.setEligibleReasonReason(pickReason);
+ eligibleDatasets.add(newDataset);
+ Set<Dataset> newDatasets;
+ for (AutofillId autofillId : datasetAutofillIds) {
+ if (map.containsKey(autofillId)) {
+ newDatasets = map.get(autofillId);
+ } else {
+ newDatasets = new LinkedHashSet<>();
+ }
+ newDatasets.add(newDataset);
+ map.put(autofillId, newDatasets);
}
- newDatasets.add(newDataset);
- map.put(autofillId, newDatasets);
}
}
container.mAutofillIds = eligibleAutofillIds;
@@ -2044,6 +2082,31 @@
}
}
+ private void copyFieldsFromDataset(
+ Dataset dataset,
+ int index,
+ AutofillId autofillId,
+ ArrayList<AutofillId> fieldIds,
+ ArrayList<AutofillValue> fieldValues,
+ ArrayList<RemoteViews> fieldPresentations,
+ ArrayList<RemoteViews> fieldDialogPresentations,
+ ArrayList<InlinePresentation> fieldInlinePresentations,
+ ArrayList<InlinePresentation> fieldInlineTooltipPresentations,
+ ArrayList<Dataset.DatasetFieldFilter> fieldFilters) {
+ // copy over values
+ fieldIds.add(autofillId);
+ fieldValues.add(dataset.getFieldValues().get(index));
+ // TODO(b/266379948): might need to make it more efficient by not
+ // copying over value if it didn't exist. This would require creating
+ // a getter for the presentations arraylist.
+ fieldPresentations.add(dataset.getFieldPresentation(index));
+ fieldDialogPresentations.add(dataset.getFieldDialogPresentation(index));
+ fieldInlinePresentations.add(dataset.getFieldInlinePresentation(index));
+ fieldInlineTooltipPresentations.add(
+ dataset.getFieldInlineTooltipPresentation(index));
+ fieldFilters.add(dataset.getFilter(index));
+ }
+
// FillServiceCallbacks
@Override
@SuppressWarnings("GuardedBy")
@@ -2577,10 +2640,7 @@
if (sDebug) Slog.d(TAG, "Updating client state from auth dataset");
mClientState = newClientState;
}
- Dataset dataset = (Dataset) result;
- FillResponse temp = new FillResponse.Builder().addDataset(dataset).build();
- temp = getEffectiveFillResponse(temp);
- dataset = temp.getDatasets().get(0);
+ Dataset dataset = getEffectiveDatasetForAuthentication((Dataset) result);
final Dataset oldDataset = authenticatedResponse.getDatasets().get(datasetIdx);
if (!isAuthResultDatasetEphemeral(oldDataset, data)) {
authenticatedResponse.getDatasets().set(datasetIdx, dataset);
@@ -2606,6 +2666,39 @@
}
}
+ Dataset getEffectiveDatasetForAuthentication(Dataset authenticatedDataset) {
+ FillResponse response = new FillResponse.Builder().addDataset(authenticatedDataset).build();
+ response = getEffectiveFillResponse(response);
+ if (DBG) {
+ Slog.d(TAG, "DBG: authenticated effective response: " + response);
+ }
+ if (response == null || response.getDatasets().size() == 0) {
+ Log.wtf(TAG, "No datasets in fill response on authentication. response = "
+ + (response == null ? "null" : response.toString()));
+ return authenticatedDataset;
+ }
+ List<Dataset> datasets = response.getDatasets();
+ Dataset result = response.getDatasets().get(0);
+ if (datasets.size() > 1) {
+ Dataset.Builder builder = new Dataset.Builder();
+ for (Dataset dataset : datasets) {
+ if (!dataset.getFieldIds().isEmpty()) {
+ for (int i = 0; i < dataset.getFieldIds().size(); i++) {
+ builder.setField(dataset.getFieldIds().get(i),
+ new Field.Builder().setValue(dataset.getFieldValues().get(i))
+ .build());
+ }
+ }
+ }
+ result = builder.setId(authenticatedDataset.getId()).build();
+ }
+
+ if (DBG) {
+ Slog.d(TAG, "DBG: authenticated effective dataset after auth: " + result);
+ }
+ return result;
+ }
+
/**
* Returns whether the dataset returned from the authentication result is ephemeral or not.
* See {@link AutofillManager#EXTRA_AUTHENTICATION_RESULT_EPHEMERAL_DATASET} for more
diff --git a/services/core/java/com/android/server/PendingIntentUtils.java b/services/core/java/com/android/server/PendingIntentUtils.java
index 1600101..a72a4d25 100644
--- a/services/core/java/com/android/server/PendingIntentUtils.java
+++ b/services/core/java/com/android/server/PendingIntentUtils.java
@@ -34,6 +34,7 @@
public static Bundle createDontSendToRestrictedAppsBundle(@Nullable Bundle bundle) {
final BroadcastOptions options = BroadcastOptions.makeBasic();
options.setDontSendToRestrictedApps(true);
+ options.setPendingIntentBackgroundActivityLaunchAllowed(false);
if (bundle == null) {
return options.toBundle();
}
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 992c18a..66ea4d0 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -2501,12 +2501,12 @@
FGS_STOP_REASON_STOP_FOREGROUND,
FGS_TYPE_POLICY_CHECK_UNKNOWN);
- // foregroundServiceType is used in logFGSStateChangeLocked(), so we can't clear it
- // earlier.
- r.foregroundServiceType = 0;
synchronized (mFGSLogger) {
mFGSLogger.logForegroundServiceStop(r.appInfo.uid, r);
}
+ // foregroundServiceType is used in logFGSStateChangeLocked(), so we can't clear it
+ // earlier.
+ r.foregroundServiceType = 0;
r.mFgsNotificationWasDeferred = false;
signalForegroundServiceObserversLocked(r);
resetFgsRestrictionLocked(r);
diff --git a/services/core/java/com/android/server/am/CoreSettingsObserver.java b/services/core/java/com/android/server/am/CoreSettingsObserver.java
index 7482e64..182205a 100644
--- a/services/core/java/com/android/server/am/CoreSettingsObserver.java
+++ b/services/core/java/com/android/server/am/CoreSettingsObserver.java
@@ -30,6 +30,7 @@
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import java.util.ArrayList;
import java.util.HashMap;
@@ -161,6 +162,12 @@
WidgetFlags.MAGNIFIER_ASPECT_RATIO_DEFAULT));
sDeviceConfigEntries.add(new DeviceConfigEntry<Boolean>(
+ DeviceConfig.NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags.REMOTEVIEWS_ADAPTER_CONVERSION,
+ SystemUiDeviceConfigFlags.KEY_REMOTEVIEWS_ADAPTER_CONVERSION, boolean.class,
+ SystemUiDeviceConfigFlags.REMOTEVIEWS_ADAPTER_CONVERSION_DEFAULT));
+
+ sDeviceConfigEntries.add(new DeviceConfigEntry<Boolean>(
TextFlags.NAMESPACE, TextFlags.ENABLE_NEW_CONTEXT_MENU,
TextFlags.KEY_ENABLE_NEW_CONTEXT_MENU, boolean.class,
TextFlags.ENABLE_NEW_CONTEXT_MENU_DEFAULT));
diff --git a/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java b/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java
index 786e1cc..f6859d1 100644
--- a/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java
+++ b/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java
@@ -141,6 +141,10 @@
// grab the appropriate types
final IntArray apiTypes =
convertFgsTypeToApiTypes(record.foregroundServiceType);
+ if (apiTypes.size() == 0) {
+ Slog.w(TAG, "Foreground service start for UID: "
+ + uid + " does not have any types");
+ }
// now we need to iterate through the types
// and insert the new record as needed
final IntArray apiTypesFound = new IntArray();
@@ -201,6 +205,9 @@
// and also clean up the start calls stack by UID
final IntArray apiTypes = convertFgsTypeToApiTypes(record.foregroundServiceType);
final UidState uidState = mUids.get(uid);
+ if (apiTypes.size() == 0) {
+ Slog.w(TAG, "FGS stop call for: " + uid + " has no types!");
+ }
if (uidState == null) {
Slog.w(TAG, "FGS stop call being logged with no start call for UID for UID "
+ uid
@@ -460,16 +467,17 @@
public void logFgsApiEvent(ServiceRecord r, int fgsState,
@FgsApiState int apiState,
@ForegroundServiceApiType int apiType, long timestamp) {
- long apiDurationBeforeFgsStart = r.createRealTime - timestamp;
- long apiDurationAfterFgsEnd = timestamp - r.mFgsExitTime;
+ long apiDurationBeforeFgsStart = 0;
+ long apiDurationAfterFgsEnd = 0;
UidState uidState = mUids.get(r.appInfo.uid);
- if (uidState != null) {
- if (uidState.mFirstFgsTimeStamp.contains(apiType)) {
- apiDurationBeforeFgsStart = uidState.mFirstFgsTimeStamp.get(apiType) - timestamp;
- }
- if (uidState.mLastFgsTimeStamp.contains(apiType)) {
- apiDurationAfterFgsEnd = timestamp - uidState.mLastFgsTimeStamp.get(apiType);
- }
+ if (uidState == null) {
+ return;
+ }
+ if (uidState.mFirstFgsTimeStamp.contains(apiType)) {
+ apiDurationBeforeFgsStart = uidState.mFirstFgsTimeStamp.get(apiType) - timestamp;
+ }
+ if (uidState.mLastFgsTimeStamp.contains(apiType)) {
+ apiDurationAfterFgsEnd = timestamp - uidState.mLastFgsTimeStamp.get(apiType);
}
final int[] apiTypes = new int[1];
apiTypes[0] = apiType;
@@ -525,10 +533,11 @@
@ForegroundServiceApiType int apiType, long timestamp) {
long apiDurationAfterFgsEnd = 0;
UidState uidState = mUids.get(uid);
- if (uidState != null) {
- if (uidState.mLastFgsTimeStamp.contains(apiType)) {
- apiDurationAfterFgsEnd = timestamp - uidState.mLastFgsTimeStamp.get(apiType);
- }
+ if (uidState == null) {
+ return;
+ }
+ if (uidState.mLastFgsTimeStamp.contains(apiType)) {
+ apiDurationAfterFgsEnd = timestamp - uidState.mLastFgsTimeStamp.get(apiType);
}
final int[] apiTypes = new int[1];
apiTypes[0] = apiType;
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index f7bbc8b..8a8e2af 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -286,6 +286,12 @@
+ ")");
}
+ private String getFgsInfoForWtf() {
+ return " cmp: " + this.getComponentName().toShortString()
+ + " sdk: " + this.appInfo.targetSdkVersion
+ ;
+ }
+
void maybeLogFgsLogicChange() {
final int origWiu = reasonOr(mAllowWhileInUsePermissionInFgsReasonNoBinding,
mAllowWIUInBindService);
@@ -311,7 +317,8 @@
+ " OS:" // Orig-start
+ changeMessage(mAllowStartForegroundNoBinding, mAllowStartInBindService)
+ " NS:" // New-start
- + changeMessage(mAllowStartForegroundNoBinding, mAllowStartByBindings);
+ + changeMessage(mAllowStartForegroundNoBinding, mAllowStartByBindings)
+ + getFgsInfoForWtf();
Slog.wtf(TAG_SERVICE, message);
}
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index d8266ec..9f9e2eb 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -435,7 +435,7 @@
// LE Audio it stays the same and we must trigger the proper stream volume alignment, if
// LE Audio communication device is activated after the audio system has already switched to
// MODE_IN_CALL mode.
- if (isBluetoothLeAudioRequested()) {
+ if (isBluetoothLeAudioRequested() && device != null) {
final int streamType = mAudioService.getBluetoothContextualVolumeStream();
final int leAudioVolIndex = getVssVolumeForDevice(streamType, device.getInternalType());
final int leAudioMaxVolIndex = getMaxVssVolumeForStream(streamType);
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index 5a92cb4..13e3fc7 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -2070,12 +2070,18 @@
@GuardedBy("mDevicesLock")
private void makeLeAudioDeviceAvailable(
AudioDeviceBroker.BtDeviceInfo btInfo, int streamType, String eventSource) {
- final String address = btInfo.mDevice.getAddress();
- final String name = BtHelper.getName(btInfo.mDevice);
final int volumeIndex = btInfo.mVolume == -1 ? -1 : btInfo.mVolume * 10;
final int device = btInfo.mAudioSystemDevice;
if (device != AudioSystem.DEVICE_NONE) {
+ final String address = btInfo.mDevice.getAddress();
+ String name = BtHelper.getName(btInfo.mDevice);
+
+ // The BT Stack does not provide a name for LE Broadcast devices
+ if (device == AudioSystem.DEVICE_OUT_BLE_BROADCAST && name.equals("")) {
+ name = "Broadcast";
+ }
+
/* Audio Policy sees Le Audio similar to A2DP. Let's make sure
* AUDIO_POLICY_FORCE_NO_BT_A2DP is not set
*/
diff --git a/services/core/java/com/android/server/biometrics/AuthSession.java b/services/core/java/com/android/server/biometrics/AuthSession.java
index cb5e7f1..2ae3118 100644
--- a/services/core/java/com/android/server/biometrics/AuthSession.java
+++ b/services/core/java/com/android/server/biometrics/AuthSession.java
@@ -150,6 +150,10 @@
// Timestamp when hardware authentication occurred
private long mAuthenticatedTimeMs;
+ @NonNull
+ private final OperationContextExt mOperationContext;
+
+
AuthSession(@NonNull Context context,
@NonNull BiometricContext biometricContext,
@NonNull IStatusBarService statusBarService,
@@ -215,6 +219,7 @@
mFingerprintSensorProperties = fingerprintSensorProperties;
mCancelled = false;
mBiometricFrameworkStatsLogger = logger;
+ mOperationContext = new OperationContextExt(true /* isBP */);
try {
mClientReceiver.asBinder().linkToDeath(this, 0 /* flags */);
@@ -581,6 +586,8 @@
} else {
Slog.d(TAG, "delaying fingerprint sensor start");
}
+
+ mBiometricContext.updateContext(mOperationContext, isCrypto());
}
// call once anytime after onDialogAnimatedIn() to indicate it's appropriate to start the
@@ -743,12 +750,12 @@
+ ", Client: " + BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT
+ ", RequireConfirmation: " + mPreAuthInfo.confirmationRequested
+ ", State: " + FrameworkStatsLog.BIOMETRIC_AUTHENTICATED__STATE__CONFIRMED
- + ", Latency: " + latency);
+ + ", Latency: " + latency
+ + ", SessionId: " + mOperationContext.getId());
}
mBiometricFrameworkStatsLogger.authenticate(
- mBiometricContext.updateContext(new OperationContextExt(true /* isBP */),
- isCrypto()),
+ mOperationContext,
statsModality(),
BiometricsProtoEnums.ACTION_UNKNOWN,
BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT,
@@ -780,13 +787,13 @@
+ ", Client: " + BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT
+ ", Reason: " + reason
+ ", Error: " + error
- + ", Latency: " + latency);
+ + ", Latency: " + latency
+ + ", SessionId: " + mOperationContext.getId());
}
// Auth canceled
if (error != 0) {
mBiometricFrameworkStatsLogger.error(
- mBiometricContext.updateContext(new OperationContextExt(true /* isBP */),
- isCrypto()),
+ mOperationContext,
statsModality(),
BiometricsProtoEnums.ACTION_AUTHENTICATE,
BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT,
diff --git a/services/core/java/com/android/server/biometrics/AuthenticationStats.java b/services/core/java/com/android/server/biometrics/AuthenticationStats.java
new file mode 100644
index 0000000..137a418
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/AuthenticationStats.java
@@ -0,0 +1,90 @@
+/*
+ * 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;
+
+/**
+ * Utility class for on-device biometric authentication data, including total authentication,
+ * rejections, and the number of sent enrollment notifications.
+ */
+public class AuthenticationStats {
+
+ private final int mUserId;
+ private int mTotalAttempts;
+ private int mRejectedAttempts;
+ private int mEnrollmentNotifications;
+ private final int mModality;
+
+ public AuthenticationStats(final int userId, int totalAttempts, int rejectedAttempts,
+ int enrollmentNotifications, final int modality) {
+ mUserId = userId;
+ mTotalAttempts = totalAttempts;
+ mRejectedAttempts = rejectedAttempts;
+ mEnrollmentNotifications = enrollmentNotifications;
+ mModality = modality;
+ }
+
+ public AuthenticationStats(final int userId, final int modality) {
+ mUserId = userId;
+ mTotalAttempts = 0;
+ mRejectedAttempts = 0;
+ mEnrollmentNotifications = 0;
+ mModality = modality;
+ }
+
+ public int getUserId() {
+ return mUserId;
+ }
+
+ public int getTotalAttempts() {
+ return mTotalAttempts;
+ }
+
+ public int getRejectedAttempts() {
+ return mRejectedAttempts;
+ }
+
+ public int getEnrollmentNotifications() {
+ return mEnrollmentNotifications;
+ }
+
+ public int getModality() {
+ return mModality;
+ }
+
+ /** Calculate FRR. */
+ public float getFrr() {
+ if (mTotalAttempts > 0) {
+ return mRejectedAttempts / (float) mTotalAttempts;
+ } else {
+ return -1.0f;
+ }
+ }
+
+ /** Update total authentication attempts and rejections. */
+ public void authenticate(boolean authenticated) {
+ if (!authenticated) {
+ mRejectedAttempts++;
+ }
+ mTotalAttempts++;
+ }
+
+ /** Reset total authentication attempts and rejections. */
+ public void resetData() {
+ mTotalAttempts = 0;
+ mRejectedAttempts = 0;
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java b/services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java
new file mode 100644
index 0000000..c9cd814
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java
@@ -0,0 +1,106 @@
+/*
+ * 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 android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Calculate and collect on-device False Rejection Rates (FRR).
+ * FRR = All [given biometric modality] unlock failures / all [given biometric modality] unlock
+ * attempts.
+ */
+public class AuthenticationStatsCollector {
+
+ private static final String TAG = "AuthenticationStatsCollector";
+
+ // The minimum number of attempts that will calculate the FRR and trigger the notification.
+ private static final int MINIMUM_ATTEMPTS = 500;
+ // The maximum number of eligible biometric enrollment notification can be sent.
+ private static final int MAXIMUM_ENROLLMENT_NOTIFICATIONS = 2;
+
+ private final float mThreshold;
+ private final int mModality;
+
+ @NonNull private final Map<Integer, AuthenticationStats> mUserAuthenticationStatsMap;
+
+ public AuthenticationStatsCollector(@NonNull Context context, int modality) {
+ mThreshold = context.getResources()
+ .getFraction(R.fraction.config_biometricNotificationFrrThreshold, 1, 1);
+ mUserAuthenticationStatsMap = new HashMap<>();
+ mModality = modality;
+ }
+
+ /** Update total authentication and rejected attempts. */
+ public void authenticate(int userId, boolean authenticated) {
+ // Check if this is a new user.
+ if (!mUserAuthenticationStatsMap.containsKey(userId)) {
+ mUserAuthenticationStatsMap.put(userId, new AuthenticationStats(userId, mModality));
+ }
+
+ AuthenticationStats authenticationStats = mUserAuthenticationStatsMap.get(userId);
+
+ authenticationStats.authenticate(authenticated);
+
+ persistDataIfNeeded(userId);
+ sendNotificationIfNeeded(userId);
+ }
+
+ private void sendNotificationIfNeeded(int userId) {
+ AuthenticationStats authenticationStats = mUserAuthenticationStatsMap.get(userId);
+ if (authenticationStats.getTotalAttempts() >= MINIMUM_ATTEMPTS) {
+ // Send notification if FRR exceeds the threshold
+ if (authenticationStats.getEnrollmentNotifications() < MAXIMUM_ENROLLMENT_NOTIFICATIONS
+ && authenticationStats.getFrr() >= mThreshold) {
+ // TODO(wenhuiy): Send notifications.
+ }
+
+ authenticationStats.resetData();
+ }
+ }
+
+ private void persistDataIfNeeded(int userId) {
+ AuthenticationStats authenticationStats = mUserAuthenticationStatsMap.get(userId);
+ if (authenticationStats.getTotalAttempts() % 50 == 0) {
+ // TODO(wenhuiy): Persist data.
+ }
+ }
+
+ /**
+ * Only being used in tests. Callers should not make any changes to the returned
+ * authentication stats.
+ *
+ * @return AuthenticationStats of the user, or null if the stats doesn't exist.
+ */
+ @Nullable
+ @VisibleForTesting
+ AuthenticationStats getAuthenticationStatsForUser(int userId) {
+ return mUserAuthenticationStatsMap.getOrDefault(userId, null);
+ }
+
+ @VisibleForTesting
+ void setAuthenticationStatsForUser(int userId, AuthenticationStats authenticationStats) {
+ mUserAuthenticationStatsMap.put(userId, authenticationStats);
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/log/BiometricLogger.java b/services/core/java/com/android/server/biometrics/log/BiometricLogger.java
index c76a2e3..87037af 100644
--- a/services/core/java/com/android/server/biometrics/log/BiometricLogger.java
+++ b/services/core/java/com/android/server/biometrics/log/BiometricLogger.java
@@ -27,6 +27,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.FrameworkStatsLog;
+import com.android.server.biometrics.AuthenticationStatsCollector;
import com.android.server.biometrics.Utils;
/**
@@ -41,6 +42,7 @@
private final int mStatsAction;
private final int mStatsClient;
private final BiometricFrameworkStatsLogger mSink;
+ @NonNull private final AuthenticationStatsCollector mAuthenticationStatsCollector;
@NonNull private final ALSProbe mALSProbe;
private long mFirstAcquireTimeMs;
@@ -49,7 +51,8 @@
/** Get a new logger with all unknown fields (for operations that do not require logs). */
public static BiometricLogger ofUnknown(@NonNull Context context) {
return new BiometricLogger(context, BiometricsProtoEnums.MODALITY_UNKNOWN,
- BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN);
+ BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN,
+ null /* AuthenticationStatsCollector */);
}
/**
@@ -64,26 +67,32 @@
* @param statsClient One of {@link BiometricsProtoEnums} CLIENT_* constants.
*/
public BiometricLogger(
- @NonNull Context context, int statsModality, int statsAction, int statsClient) {
+ @NonNull Context context, int statsModality, int statsAction, int statsClient,
+ AuthenticationStatsCollector authenticationStatsCollector) {
this(statsModality, statsAction, statsClient,
BiometricFrameworkStatsLogger.getInstance(),
+ authenticationStatsCollector,
context.getSystemService(SensorManager.class));
}
@VisibleForTesting
BiometricLogger(
int statsModality, int statsAction, int statsClient,
- BiometricFrameworkStatsLogger logSink, SensorManager sensorManager) {
+ BiometricFrameworkStatsLogger logSink,
+ @NonNull AuthenticationStatsCollector statsCollector,
+ SensorManager sensorManager) {
mStatsModality = statsModality;
mStatsAction = statsAction;
mStatsClient = statsClient;
mSink = logSink;
+ mAuthenticationStatsCollector = statsCollector;
mALSProbe = new ALSProbe(sensorManager);
}
/** Creates a new logger with the action replaced with the new action. */
public BiometricLogger swapAction(@NonNull Context context, int statsAction) {
- return new BiometricLogger(context, mStatsModality, statsAction, mStatsClient);
+ return new BiometricLogger(context, mStatsModality, statsAction, mStatsClient,
+ null /* AuthenticationStatsCollector */);
}
/** Disable logging metrics and only log critical events, such as system health issues. */
@@ -192,10 +201,13 @@
public void logOnAuthenticated(Context context, OperationContextExt operationContext,
boolean authenticated, boolean requireConfirmation, int targetUserId,
boolean isBiometricPrompt) {
+ // Do not log metrics when fingerprint enrollment reason is ENROLL_FIND_SENSOR
if (!mShouldLogMetrics) {
return;
}
+ mAuthenticationStatsCollector.authenticate(targetUserId, authenticated);
+
int authState = FrameworkStatsLog.BIOMETRIC_AUTHENTICATED__STATE__UNKNOWN;
if (!authenticated) {
authState = FrameworkStatsLog.BIOMETRIC_AUTHENTICATED__STATE__REJECTED;
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
index 33ed63c..a7d160c 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
@@ -49,6 +49,7 @@
import android.view.Surface;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.biometrics.AuthenticationStatsCollector;
import com.android.server.biometrics.Utils;
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.log.BiometricLogger;
@@ -112,6 +113,8 @@
private final BiometricContext mBiometricContext;
@NonNull
private final AuthSessionCoordinator mAuthSessionCoordinator;
+ @NonNull
+ private final AuthenticationStatsCollector mAuthenticationStatsCollector;
@Nullable
private IFace mDaemon;
@@ -173,6 +176,9 @@
mAuthSessionCoordinator = mBiometricContext.getAuthSessionCoordinator();
mDaemon = daemon;
+ mAuthenticationStatsCollector = new AuthenticationStatsCollector(mContext,
+ BiometricsProtoEnums.MODALITY_FACE);
+
for (SensorProps prop : props) {
final int sensorId = prop.commonProps.sensorId;
@@ -283,7 +289,8 @@
mContext, mFaceSensors.get(sensorId).getLazySession(), userId,
mContext.getOpPackageName(), sensorId,
createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
- BiometricsProtoEnums.CLIENT_UNKNOWN),
+ BiometricsProtoEnums.CLIENT_UNKNOWN,
+ mAuthenticationStatsCollector),
mBiometricContext,
mFaceSensors.get(sensorId).getAuthenticatorIds());
@@ -341,7 +348,8 @@
final FaceInvalidationClient client = new FaceInvalidationClient(mContext,
mFaceSensors.get(sensorId).getLazySession(), userId, sensorId,
createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
- BiometricsProtoEnums.CLIENT_UNKNOWN),
+ BiometricsProtoEnums.CLIENT_UNKNOWN,
+ mAuthenticationStatsCollector),
mBiometricContext,
mFaceSensors.get(sensorId).getAuthenticatorIds(), callback);
scheduleForSensor(sensorId, client);
@@ -372,7 +380,8 @@
mFaceSensors.get(sensorId).getLazySession(), token,
new ClientMonitorCallbackConverter(receiver), userId, opPackageName, sensorId,
createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
- BiometricsProtoEnums.CLIENT_UNKNOWN),
+ BiometricsProtoEnums.CLIENT_UNKNOWN,
+ mAuthenticationStatsCollector),
mBiometricContext);
scheduleForSensor(sensorId, client);
});
@@ -386,7 +395,8 @@
mFaceSensors.get(sensorId).getLazySession(), token, userId,
opPackageName, sensorId,
createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
- BiometricsProtoEnums.CLIENT_UNKNOWN),
+ BiometricsProtoEnums.CLIENT_UNKNOWN,
+ mAuthenticationStatsCollector),
mBiometricContext, challenge);
scheduleForSensor(sensorId, client);
});
@@ -407,7 +417,8 @@
opPackageName, id, FaceUtils.getInstance(sensorId), disabledFeatures,
ENROLL_TIMEOUT_SEC, previewSurface, sensorId,
createLogger(BiometricsProtoEnums.ACTION_ENROLL,
- BiometricsProtoEnums.CLIENT_UNKNOWN),
+ BiometricsProtoEnums.CLIENT_UNKNOWN,
+ mAuthenticationStatsCollector),
mBiometricContext, maxTemplatesPerUser, debugConsent);
scheduleForSensor(sensorId, client, new ClientMonitorCompositeCallback(
mBiometricStateCallback, new ClientMonitorCallback() {
@@ -443,7 +454,8 @@
final FaceDetectClient client = new FaceDetectClient(mContext,
mFaceSensors.get(sensorId).getLazySession(),
token, id, callback, options,
- createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
+ createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
+ mAuthenticationStatsCollector),
mBiometricContext, isStrongBiometric);
scheduleForSensor(sensorId, client, mBiometricStateCallback);
});
@@ -471,7 +483,8 @@
mContext, mFaceSensors.get(sensorId).getLazySession(), token, requestId,
callback, operationId, restricted, options, cookie,
false /* requireConfirmation */,
- createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
+ createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
+ mAuthenticationStatsCollector),
mBiometricContext, isStrongBiometric,
mUsageStats, mFaceSensors.get(sensorId).getLockoutCache(),
allowBackgroundAuthentication, Utils.getCurrentStrength(sensorId));
@@ -540,7 +553,8 @@
new ClientMonitorCallbackConverter(receiver), faceIds, userId,
opPackageName, FaceUtils.getInstance(sensorId), sensorId,
createLogger(BiometricsProtoEnums.ACTION_REMOVE,
- BiometricsProtoEnums.CLIENT_UNKNOWN),
+ BiometricsProtoEnums.CLIENT_UNKNOWN,
+ mAuthenticationStatsCollector),
mBiometricContext,
mFaceSensors.get(sensorId).getAuthenticatorIds());
scheduleForSensor(sensorId, client, mBiometricStateCallback);
@@ -554,7 +568,8 @@
mContext, mFaceSensors.get(sensorId).getLazySession(), userId,
mContext.getOpPackageName(), sensorId,
createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
- BiometricsProtoEnums.CLIENT_UNKNOWN),
+ BiometricsProtoEnums.CLIENT_UNKNOWN,
+ mAuthenticationStatsCollector),
mBiometricContext, hardwareAuthToken,
mFaceSensors.get(sensorId).getLockoutCache(), mLockoutResetDispatcher,
Utils.getCurrentStrength(sensorId));
@@ -624,7 +639,8 @@
mFaceSensors.get(sensorId).getLazySession(), userId,
mContext.getOpPackageName(), sensorId,
createLogger(BiometricsProtoEnums.ACTION_ENUMERATE,
- BiometricsProtoEnums.CLIENT_UNKNOWN),
+ BiometricsProtoEnums.CLIENT_UNKNOWN,
+ mAuthenticationStatsCollector),
mBiometricContext,
FaceUtils.getInstance(sensorId),
mFaceSensors.get(sensorId).getAuthenticatorIds());
@@ -636,9 +652,10 @@
});
}
- private BiometricLogger createLogger(int statsAction, int statsClient) {
+ private BiometricLogger createLogger(int statsAction, int statsClient,
+ AuthenticationStatsCollector authenticationStatsCollector) {
return new BiometricLogger(mContext, BiometricsProtoEnums.MODALITY_FACE,
- statsAction, statsClient);
+ statsAction, statsClient, authenticationStatsCollector);
}
@Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
index 1e33c96..10991d5 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
@@ -52,6 +52,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.FrameworkStatsLog;
+import com.android.server.biometrics.AuthenticationStatsCollector;
import com.android.server.biometrics.SensorServiceStateProto;
import com.android.server.biometrics.SensorStateProto;
import com.android.server.biometrics.UserStateProto;
@@ -124,6 +125,7 @@
@Nullable private IBiometricsFace mDaemon;
@NonNull private final HalResultController mHalResultController;
@NonNull private final BiometricContext mBiometricContext;
+ @NonNull private final AuthenticationStatsCollector mAuthenticationStatsCollector;
// for requests that do not use biometric prompt
@NonNull private final AtomicLong mRequestCounter = new AtomicLong(0);
private int mCurrentUserId = UserHandle.USER_NULL;
@@ -364,6 +366,9 @@
mCurrentUserId = UserHandle.USER_NULL;
});
+ mAuthenticationStatsCollector = new AuthenticationStatsCollector(mContext,
+ BiometricsProtoEnums.MODALITY_FACE);
+
try {
ActivityManager.getService().registerUserSwitchObserver(mUserSwitchObserver, TAG);
} catch (RemoteException e) {
@@ -554,7 +559,7 @@
mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), userId,
opPackageName, mSensorId,
createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
- BiometricsProtoEnums.CLIENT_UNKNOWN),
+ BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
mBiometricContext, sSystemClock.millis());
mGeneratedChallengeCache = client;
mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
@@ -586,7 +591,7 @@
final FaceRevokeChallengeClient client = new FaceRevokeChallengeClient(mContext,
mLazyDaemon, token, userId, opPackageName, mSensorId,
createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
- BiometricsProtoEnums.CLIENT_UNKNOWN),
+ BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
mBiometricContext);
mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
@Override
@@ -617,7 +622,7 @@
opPackageName, id, FaceUtils.getLegacyInstance(mSensorId), disabledFeatures,
ENROLL_TIMEOUT_SEC, previewSurface, mSensorId,
createLogger(BiometricsProtoEnums.ACTION_ENROLL,
- BiometricsProtoEnums.CLIENT_UNKNOWN),
+ BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
mBiometricContext);
mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
@@ -677,7 +682,8 @@
final FaceAuthenticationClient client = new FaceAuthenticationClient(mContext,
mLazyDaemon, token, requestId, receiver, operationId, restricted,
options, cookie, false /* requireConfirmation */,
- createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
+ createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
+ mAuthenticationStatsCollector),
mBiometricContext, isStrongBiometric, mLockoutTracker,
mUsageStats, allowBackgroundAuthentication,
Utils.getCurrentStrength(mSensorId));
@@ -713,7 +719,7 @@
new ClientMonitorCallbackConverter(receiver), faceId, userId, opPackageName,
FaceUtils.getLegacyInstance(mSensorId), mSensorId,
createLogger(BiometricsProtoEnums.ACTION_REMOVE,
- BiometricsProtoEnums.CLIENT_UNKNOWN),
+ BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
mBiometricContext, mAuthenticatorIds);
mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
});
@@ -731,7 +737,7 @@
opPackageName,
FaceUtils.getLegacyInstance(mSensorId), mSensorId,
createLogger(BiometricsProtoEnums.ACTION_REMOVE,
- BiometricsProtoEnums.CLIENT_UNKNOWN),
+ BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
mBiometricContext, mAuthenticatorIds);
mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
});
@@ -750,7 +756,7 @@
final FaceResetLockoutClient client = new FaceResetLockoutClient(mContext,
mLazyDaemon, userId, mContext.getOpPackageName(), mSensorId,
createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
- BiometricsProtoEnums.CLIENT_UNKNOWN),
+ BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
mBiometricContext, hardwareAuthToken);
mScheduler.scheduleClientMonitor(client);
});
@@ -821,7 +827,7 @@
final FaceInternalCleanupClient client = new FaceInternalCleanupClient(mContext,
mLazyDaemon, userId, mContext.getOpPackageName(), mSensorId,
createLogger(BiometricsProtoEnums.ACTION_ENUMERATE,
- BiometricsProtoEnums.CLIENT_UNKNOWN),
+ BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
mBiometricContext,
FaceUtils.getLegacyInstance(mSensorId), mAuthenticatorIds);
mScheduler.scheduleClientMonitor(client, new ClientMonitorCompositeCallback(callback,
@@ -953,7 +959,7 @@
final FaceUpdateActiveUserClient client = new FaceUpdateActiveUserClient(mContext,
mLazyDaemon, targetUserId, mContext.getOpPackageName(), mSensorId,
createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
- BiometricsProtoEnums.CLIENT_UNKNOWN),
+ BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
mBiometricContext, hasEnrolled, mAuthenticatorIds);
mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
@Override
@@ -968,9 +974,10 @@
});
}
- private BiometricLogger createLogger(int statsAction, int statsClient) {
+ private BiometricLogger createLogger(int statsAction, int statsClient,
+ AuthenticationStatsCollector authenticationStatsCollector) {
return new BiometricLogger(mContext, BiometricsProtoEnums.MODALITY_FACE,
- statsAction, statsClient);
+ statsAction, statsClient, authenticationStatsCollector);
}
/**
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
index 0421d78..2d062db 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
@@ -57,6 +57,7 @@
import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.biometrics.AuthenticationStatsCollector;
import com.android.server.biometrics.Utils;
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.log.BiometricLogger;
@@ -122,6 +123,7 @@
@Nullable private IUdfpsOverlayController mUdfpsOverlayController;
@Nullable private ISidefpsController mSidefpsController;
private AuthSessionCoordinator mAuthSessionCoordinator;
+ @NonNull private final AuthenticationStatsCollector mAuthenticationStatsCollector;
private final class BiometricTaskStackListener extends TaskStackListener {
@Override
@@ -181,6 +183,9 @@
mAuthSessionCoordinator = mBiometricContext.getAuthSessionCoordinator();
mDaemon = daemon;
+ mAuthenticationStatsCollector = new AuthenticationStatsCollector(mContext,
+ BiometricsProtoEnums.MODALITY_FINGERPRINT);
+
final List<SensorLocationInternal> workaroundLocations = getWorkaroundSensorProps(context);
for (SensorProps prop : props) {
@@ -338,7 +343,8 @@
mFingerprintSensors.get(sensorId).getLazySession(), userId,
mContext.getOpPackageName(), sensorId,
createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
- BiometricsProtoEnums.CLIENT_UNKNOWN),
+ BiometricsProtoEnums.CLIENT_UNKNOWN,
+ mAuthenticationStatsCollector),
mBiometricContext,
mFingerprintSensors.get(sensorId).getAuthenticatorIds());
scheduleForSensor(sensorId, client);
@@ -363,7 +369,8 @@
mContext, mFingerprintSensors.get(sensorId).getLazySession(), userId,
mContext.getOpPackageName(), sensorId,
createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
- BiometricsProtoEnums.CLIENT_UNKNOWN),
+ BiometricsProtoEnums.CLIENT_UNKNOWN,
+ mAuthenticationStatsCollector),
mBiometricContext, hardwareAuthToken,
mFingerprintSensors.get(sensorId).getLockoutCache(), mLockoutResetDispatcher,
Utils.getCurrentStrength(sensorId));
@@ -380,7 +387,7 @@
mFingerprintSensors.get(sensorId).getLazySession(), token,
new ClientMonitorCallbackConverter(receiver), userId, opPackageName,
sensorId, createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
- BiometricsProtoEnums.CLIENT_UNKNOWN),
+ BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
mBiometricContext);
scheduleForSensor(sensorId, client);
});
@@ -395,7 +402,8 @@
mFingerprintSensors.get(sensorId).getLazySession(), token,
userId, opPackageName, sensorId,
createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
- BiometricsProtoEnums.CLIENT_UNKNOWN),
+ BiometricsProtoEnums.CLIENT_UNKNOWN,
+ mAuthenticationStatsCollector),
mBiometricContext, challenge);
scheduleForSensor(sensorId, client);
});
@@ -415,7 +423,7 @@
new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken,
opPackageName, FingerprintUtils.getInstance(sensorId), sensorId,
createLogger(BiometricsProtoEnums.ACTION_ENROLL,
- BiometricsProtoEnums.CLIENT_UNKNOWN),
+ BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
mBiometricContext,
mFingerprintSensors.get(sensorId).getSensorProperties(),
mUdfpsOverlayController, mSidefpsController,
@@ -455,7 +463,8 @@
final FingerprintDetectClient client = new FingerprintDetectClient(mContext,
mFingerprintSensors.get(sensorId).getLazySession(), token, id, callback,
options,
- createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
+ createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
+ mAuthenticationStatsCollector),
mBiometricContext, mUdfpsOverlayController, isStrongBiometric);
scheduleForSensor(sensorId, client, mBiometricStateCallback);
});
@@ -477,7 +486,8 @@
mContext, mFingerprintSensors.get(sensorId).getLazySession(), token, requestId,
callback, operationId, restricted, options, cookie,
false /* requireConfirmation */,
- createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
+ createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
+ mAuthenticationStatsCollector),
mBiometricContext, isStrongBiometric,
mTaskStackListener, mFingerprintSensors.get(sensorId).getLockoutCache(),
mUdfpsOverlayController, mSidefpsController,
@@ -566,7 +576,8 @@
new ClientMonitorCallbackConverter(receiver), fingerprintIds, userId,
opPackageName, FingerprintUtils.getInstance(sensorId), sensorId,
createLogger(BiometricsProtoEnums.ACTION_REMOVE,
- BiometricsProtoEnums.CLIENT_UNKNOWN),
+ BiometricsProtoEnums.CLIENT_UNKNOWN,
+ mAuthenticationStatsCollector),
mBiometricContext,
mFingerprintSensors.get(sensorId).getAuthenticatorIds());
scheduleForSensor(sensorId, client, mBiometricStateCallback);
@@ -588,7 +599,8 @@
mFingerprintSensors.get(sensorId).getLazySession(), userId,
mContext.getOpPackageName(), sensorId,
createLogger(BiometricsProtoEnums.ACTION_ENUMERATE,
- BiometricsProtoEnums.CLIENT_UNKNOWN),
+ BiometricsProtoEnums.CLIENT_UNKNOWN,
+ mAuthenticationStatsCollector),
mBiometricContext,
FingerprintUtils.getInstance(sensorId),
mFingerprintSensors.get(sensorId).getAuthenticatorIds());
@@ -600,9 +612,10 @@
});
}
- private BiometricLogger createLogger(int statsAction, int statsClient) {
+ private BiometricLogger createLogger(int statsAction, int statsClient,
+ AuthenticationStatsCollector authenticationStatsCollector) {
return new BiometricLogger(mContext, BiometricsProtoEnums.MODALITY_FINGERPRINT,
- statsAction, statsClient);
+ statsAction, statsClient, authenticationStatsCollector);
}
@Override
@@ -635,7 +648,8 @@
new FingerprintInvalidationClient(mContext,
mFingerprintSensors.get(sensorId).getLazySession(), userId, sensorId,
createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
- BiometricsProtoEnums.CLIENT_UNKNOWN),
+ BiometricsProtoEnums.CLIENT_UNKNOWN,
+ mAuthenticationStatsCollector),
mBiometricContext,
mFingerprintSensors.get(sensorId).getAuthenticatorIds(), callback);
scheduleForSensor(sensorId, client);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
index 92b216d..4b07dca 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
@@ -52,6 +52,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.FrameworkStatsLog;
+import com.android.server.biometrics.AuthenticationStatsCollector;
import com.android.server.biometrics.SensorServiceStateProto;
import com.android.server.biometrics.SensorStateProto;
import com.android.server.biometrics.UserStateProto;
@@ -123,6 +124,7 @@
@Nullable private IUdfpsOverlayController mUdfpsOverlayController;
@Nullable private ISidefpsController mSidefpsController;
@NonNull private final BiometricContext mBiometricContext;
+ @NonNull private final AuthenticationStatsCollector mAuthenticationStatsCollector;
// for requests that do not use biometric prompt
@NonNull private final AtomicLong mRequestCounter = new AtomicLong(0);
private int mCurrentUserId = UserHandle.USER_NULL;
@@ -351,6 +353,9 @@
mCurrentUserId = UserHandle.USER_NULL;
});
+ mAuthenticationStatsCollector = new AuthenticationStatsCollector(mContext,
+ BiometricsProtoEnums.MODALITY_FINGERPRINT);
+
try {
ActivityManager.getService().registerUserSwitchObserver(mUserSwitchObserver, TAG);
} catch (RemoteException e) {
@@ -497,7 +502,8 @@
new FingerprintUpdateActiveUserClient(mContext, mLazyDaemon, targetUserId,
mContext.getOpPackageName(), mSensorProperties.sensorId,
createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
- BiometricsProtoEnums.CLIENT_UNKNOWN),
+ BiometricsProtoEnums.CLIENT_UNKNOWN,
+ mAuthenticationStatsCollector),
mBiometricContext,
this::getCurrentUser, hasEnrolled, mAuthenticatorIds, force);
mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
@@ -544,7 +550,8 @@
final FingerprintResetLockoutClient client = new FingerprintResetLockoutClient(mContext,
userId, mContext.getOpPackageName(), sensorId,
createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
- BiometricsProtoEnums.CLIENT_UNKNOWN),
+ BiometricsProtoEnums.CLIENT_UNKNOWN,
+ mAuthenticationStatsCollector),
mBiometricContext, mLockoutTracker);
mScheduler.scheduleClientMonitor(client);
});
@@ -559,7 +566,8 @@
new ClientMonitorCallbackConverter(receiver), userId, opPackageName,
mSensorProperties.sensorId,
createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
- BiometricsProtoEnums.CLIENT_UNKNOWN),
+ BiometricsProtoEnums.CLIENT_UNKNOWN,
+ mAuthenticationStatsCollector),
mBiometricContext);
mScheduler.scheduleClientMonitor(client);
});
@@ -573,7 +581,8 @@
mContext, mLazyDaemon, token, userId, opPackageName,
mSensorProperties.sensorId,
createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
- BiometricsProtoEnums.CLIENT_UNKNOWN),
+ BiometricsProtoEnums.CLIENT_UNKNOWN,
+ mAuthenticationStatsCollector),
mBiometricContext);
mScheduler.scheduleClientMonitor(client);
});
@@ -594,7 +603,7 @@
FingerprintUtils.getLegacyInstance(mSensorId), ENROLL_TIMEOUT_SEC,
mSensorProperties.sensorId,
createLogger(BiometricsProtoEnums.ACTION_ENROLL,
- BiometricsProtoEnums.CLIENT_UNKNOWN),
+ BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
mBiometricContext, mUdfpsOverlayController, mSidefpsController, enrollReason);
mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
@Override
@@ -639,7 +648,8 @@
final boolean isStrongBiometric = Utils.isStrongBiometric(mSensorProperties.sensorId);
final FingerprintDetectClient client = new FingerprintDetectClient(mContext,
mLazyDaemon, token, id, listener, options,
- createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
+ createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
+ mAuthenticationStatsCollector),
mBiometricContext, mUdfpsOverlayController, isStrongBiometric);
mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
});
@@ -660,7 +670,8 @@
final FingerprintAuthenticationClient client = new FingerprintAuthenticationClient(
mContext, mLazyDaemon, token, requestId, listener, operationId,
restricted, options, cookie, false /* requireConfirmation */,
- createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
+ createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
+ mAuthenticationStatsCollector),
mBiometricContext, isStrongBiometric,
mTaskStackListener, mLockoutTracker,
mUdfpsOverlayController, mSidefpsController,
@@ -706,7 +717,7 @@
userId, opPackageName, FingerprintUtils.getLegacyInstance(mSensorId),
mSensorProperties.sensorId,
createLogger(BiometricsProtoEnums.ACTION_REMOVE,
- BiometricsProtoEnums.CLIENT_UNKNOWN),
+ BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
mBiometricContext, mAuthenticatorIds);
mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
});
@@ -726,7 +737,7 @@
FingerprintUtils.getLegacyInstance(mSensorId),
mSensorProperties.sensorId,
createLogger(BiometricsProtoEnums.ACTION_REMOVE,
- BiometricsProtoEnums.CLIENT_UNKNOWN),
+ BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
mBiometricContext, mAuthenticatorIds);
mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
});
@@ -741,7 +752,7 @@
mContext, mLazyDaemon, userId, mContext.getOpPackageName(),
mSensorProperties.sensorId,
createLogger(BiometricsProtoEnums.ACTION_ENUMERATE,
- BiometricsProtoEnums.CLIENT_UNKNOWN),
+ BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
mBiometricContext,
FingerprintUtils.getLegacyInstance(mSensorId), mAuthenticatorIds);
mScheduler.scheduleClientMonitor(client, callback);
@@ -762,9 +773,10 @@
mBiometricStateCallback));
}
- private BiometricLogger createLogger(int statsAction, int statsClient) {
+ private BiometricLogger createLogger(int statsAction, int statsClient,
+ AuthenticationStatsCollector authenticationStatsCollector) {
return new BiometricLogger(mContext, BiometricsProtoEnums.MODALITY_FINGERPRINT,
- statsAction, statsClient);
+ statsAction, statsClient, authenticationStatsCollector);
}
@Override
diff --git a/services/core/java/com/android/server/display/DisplayBrightnessState.java b/services/core/java/com/android/server/display/DisplayBrightnessState.java
index da51569..1c1b69b 100644
--- a/services/core/java/com/android/server/display/DisplayBrightnessState.java
+++ b/services/core/java/com/android/server/display/DisplayBrightnessState.java
@@ -134,6 +134,13 @@
}
/**
+ * Helper methods to create builder
+ */
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ /**
* A DisplayBrightnessState's builder class.
*/
public static class Builder {
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 626502e..898a3c4 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -1546,6 +1546,7 @@
if (displayId != Display.INVALID_DISPLAY && virtualDevice != null && dwpc != null) {
mDisplayWindowPolicyControllers.put(
displayId, Pair.create(virtualDevice, dwpc));
+ Slog.d(TAG, "Virtual Display: successfully created virtual display");
}
}
@@ -1592,19 +1593,25 @@
if (!getProjectionService().setContentRecordingSession(session, projection)) {
// Unable to start mirroring, so release VirtualDisplay. Projection service
// handles stopping the projection.
+ Slog.w(TAG, "Content Recording: failed to start mirroring - "
+ + "releasing virtual display " + displayId);
releaseVirtualDisplayInternal(callback.asBinder());
return Display.INVALID_DISPLAY;
} else if (projection != null) {
// Indicate that this projection has been used to record, and can't be used
// again.
+ Slog.d(TAG, "Content Recording: notifying MediaProjection of successful"
+ + " VirtualDisplay creation.");
projection.notifyVirtualDisplayCreated(displayId);
}
} catch (RemoteException e) {
Slog.e(TAG, "Unable to tell MediaProjectionManagerService to set the "
+ "content recording session", e);
+ return displayId;
}
+ Slog.d(TAG, "Virtual Display: successfully set up virtual display "
+ + displayId);
}
-
return displayId;
} finally {
Binder.restoreCallingIdentity(secondToken);
@@ -1628,10 +1635,13 @@
return -1;
}
+
+ Slog.d(TAG, "Virtual Display: creating DisplayDevice with VirtualDisplayAdapter");
DisplayDevice device = mVirtualDisplayAdapter.createVirtualDisplayLocked(
callback, projection, callingUid, packageName, surface, flags,
virtualDisplayConfig);
if (device == null) {
+ Slog.w(TAG, "Virtual Display: VirtualDisplayAdapter failed to create DisplayDevice");
return -1;
}
@@ -1709,6 +1719,7 @@
DisplayDevice device =
mVirtualDisplayAdapter.releaseVirtualDisplayLocked(appToken);
+ Slog.d(TAG, "Virtual Display: Display Device released");
if (device != null) {
// TODO: multi-display - handle virtual displays the same as other display adapters.
mDisplayDeviceRepo.onDisplayDeviceEvent(device,
diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java
index 32c37b0..4db8777 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController2.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController2.java
@@ -347,7 +347,7 @@
private boolean mDozing;
private boolean mAppliedDimming;
- private boolean mAppliedLowPower;
+
private boolean mAppliedThrottling;
// Reason for which the brightness was last changed. See {@link BrightnessReason} for more
@@ -1465,24 +1465,13 @@
slowChange = false;
mAppliedDimming = false;
}
- // If low power mode is enabled, scale brightness by screenLowPowerBrightnessFactor
- // as long as it is above the minimum threshold.
- if (mPowerRequest.lowPowerMode) {
- if (brightnessState > PowerManager.BRIGHTNESS_MIN) {
- final float brightnessFactor =
- Math.min(mPowerRequest.screenLowPowerBrightnessFactor, 1);
- final float lowPowerBrightnessFloat = (brightnessState * brightnessFactor);
- brightnessState = Math.max(lowPowerBrightnessFloat, PowerManager.BRIGHTNESS_MIN);
- mBrightnessReasonTemp.addModifier(BrightnessReason.MODIFIER_LOW_POWER);
- }
- if (!mAppliedLowPower) {
- slowChange = false;
- }
- mAppliedLowPower = true;
- } else if (mAppliedLowPower) {
- slowChange = false;
- mAppliedLowPower = false;
- }
+
+ DisplayBrightnessState clampedState = mBrightnessClamperController.clamp(mPowerRequest,
+ brightnessState, slowChange);
+
+ brightnessState = clampedState.getBrightness();
+ slowChange = clampedState.isSlowChange();
+ mBrightnessReasonTemp.addModifier(clampedState.getBrightnessReason().getModifier());
// The current brightness to use has been calculated at this point, and HbmController should
// be notified so that it can accurately calculate HDR or HBM levels. We specifically do it
@@ -1540,8 +1529,6 @@
// 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.
@@ -2408,7 +2395,6 @@
pw.println(" mPowerRequest=" + mPowerRequest);
pw.println(" mBrightnessReason=" + mBrightnessReason);
pw.println(" mAppliedDimming=" + mAppliedDimming);
- pw.println(" mAppliedLowPower=" + mAppliedLowPower);
pw.println(" mAppliedThrottling=" + mAppliedThrottling);
pw.println(" mDozing=" + mDozing);
pw.println(" mSkipRampState=" + skipRampStateToString(mSkipRampState));
diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
index 4f7a2ba..9f480b6 100644
--- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
@@ -141,9 +141,12 @@
try {
if (projection != null) {
projection.registerCallback(mediaProjectionCallback);
+ Slog.d(TAG, "Virtual Display: registered media projection callback for new "
+ + "VirtualDisplayDevice");
}
appToken.linkToDeath(device, 0);
} catch (RemoteException ex) {
+ Slog.e(TAG, "Virtual Display: error while setting up VirtualDisplayDevice", ex);
mVirtualDisplayDevices.remove(appToken);
device.destroyLocked(false);
return null;
@@ -439,6 +442,7 @@
}
public void stopLocked() {
+ Slog.d(TAG, "Virtual Display: stopping device " + mName);
setSurfaceLocked(null);
mStopped = true;
}
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
index 9345a3d..54a280f 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java
@@ -21,6 +21,9 @@
import java.io.PrintWriter;
+/**
+ * Provides max allowed brightness
+ */
abstract class BrightnessClamper<T> {
protected float mBrightnessCap = PowerManager.BRIGHTNESS_MAX;
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
index d0f28c3..f19d00b 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
@@ -20,6 +20,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.hardware.display.DisplayManagerInternal;
import android.os.Handler;
import android.os.HandlerExecutor;
import android.os.PowerManager;
@@ -28,6 +29,7 @@
import android.util.IndentingPrintWriter;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.display.DisplayBrightnessState;
import com.android.server.display.DisplayDeviceConfig;
import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData;
import com.android.server.display.feature.DeviceConfigParameterProvider;
@@ -42,7 +44,7 @@
*/
public class BrightnessClamperController {
- private static final boolean ENABLED = false;
+ private static final boolean THERMAL_ENABLED = false;
private final DeviceConfigParameterProvider mDeviceConfigParameterProvider;
private final Handler mHandler;
@@ -50,6 +52,8 @@
private final Executor mExecutor;
private final List<BrightnessClamper<? super DisplayDeviceData>> mClampers = new ArrayList<>();
+
+ private final List<BrightnessModifier> mModifiers = new ArrayList<>();
private final DeviceConfig.OnPropertiesChangedListener mOnPropertiesChangedListener =
properties -> mClampers.forEach(BrightnessClamper::onDeviceConfigChanged);
private float mBrightnessCap = PowerManager.BRIGHTNESS_MAX;
@@ -77,11 +81,12 @@
}
};
- if (ENABLED) {
+ if (THERMAL_ENABLED) {
mClampers.add(
new BrightnessThermalClamper(handler, clamperChangeListenerInternal, data));
- start();
}
+ mModifiers.add(new BrightnessLowPowerModeModifier());
+ start();
}
/**
@@ -95,8 +100,18 @@
* Applies clamping
* Called in DisplayControllerHandler
*/
- public float clamp(float value) {
- return Math.min(value, mBrightnessCap);
+ public DisplayBrightnessState clamp(DisplayManagerInternal.DisplayPowerRequest request,
+ float brightnessValue, boolean slowChange) {
+ float cappedBrightness = Math.min(brightnessValue, mBrightnessCap);
+
+ DisplayBrightnessState.Builder builder = DisplayBrightnessState.builder();
+ builder.setIsSlowChange(slowChange);
+ builder.setBrightness(cappedBrightness);
+
+ for (int i = 0; i < mModifiers.size(); i++) {
+ mModifiers.get(i).apply(request, builder);
+ }
+ return builder.build();
}
/**
@@ -108,6 +123,7 @@
writer.println(" mClamperType: " + mClamperType);
IndentingPrintWriter ipw = new IndentingPrintWriter(writer, " ");
mClampers.forEach(clamper -> clamper.dump(ipw));
+ mModifiers.forEach(modifier -> modifier.dump(ipw));
}
/**
@@ -144,8 +160,10 @@
}
private void start() {
- mDeviceConfigParameterProvider.addOnPropertiesChangedListener(
- mExecutor, mOnPropertiesChangedListener);
+ if (!mClampers.isEmpty()) {
+ mDeviceConfigParameterProvider.addOnPropertiesChangedListener(
+ mExecutor, mOnPropertiesChangedListener);
+ }
}
/**
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessLowPowerModeModifier.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessLowPowerModeModifier.java
new file mode 100644
index 0000000..f48ad2f
--- /dev/null
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessLowPowerModeModifier.java
@@ -0,0 +1,61 @@
+/*
+ * 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.hardware.display.DisplayManagerInternal;
+import android.os.PowerManager;
+
+import com.android.server.display.DisplayBrightnessState;
+import com.android.server.display.brightness.BrightnessReason;
+
+import java.io.PrintWriter;
+
+class BrightnessLowPowerModeModifier implements BrightnessModifier {
+
+ private boolean mAppliedLowPower = false;
+
+ @Override
+ public void apply(DisplayManagerInternal.DisplayPowerRequest request,
+ DisplayBrightnessState.Builder stateBuilder) {
+ // If low power mode is enabled, scale brightness by screenLowPowerBrightnessFactor
+ // as long as it is above the minimum threshold.
+ if (request.lowPowerMode) {
+ float value = stateBuilder.getBrightness();
+ if (value > PowerManager.BRIGHTNESS_MIN) {
+ final float brightnessFactor =
+ Math.min(request.screenLowPowerBrightnessFactor, 1);
+ final float lowPowerBrightnessFloat = Math.max((value * brightnessFactor),
+ PowerManager.BRIGHTNESS_MIN);
+ stateBuilder.setBrightness(lowPowerBrightnessFloat);
+ stateBuilder.getBrightnessReason().addModifier(BrightnessReason.MODIFIER_LOW_POWER);
+ }
+ if (!mAppliedLowPower) {
+ stateBuilder.setIsSlowChange(false);
+ }
+ mAppliedLowPower = true;
+ } else if (mAppliedLowPower) {
+ stateBuilder.setIsSlowChange(false);
+ mAppliedLowPower = false;
+ }
+ }
+
+ @Override
+ public void dump(PrintWriter pw) {
+ pw.println("BrightnessLowPowerModeModifier:");
+ pw.println(" mAppliedLowPower=" + mAppliedLowPower);
+ }
+}
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessModifier.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessModifier.java
new file mode 100644
index 0000000..3a33df6
--- /dev/null
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessModifier.java
@@ -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.server.display.brightness.clamper;
+
+import android.hardware.display.DisplayManagerInternal;
+
+import com.android.server.display.DisplayBrightnessState;
+
+import java.io.PrintWriter;
+
+/**
+ * Modifies current brightness based on request
+ */
+interface BrightnessModifier {
+
+ void apply(DisplayManagerInternal.DisplayPowerRequest request,
+ DisplayBrightnessState.Builder builder);
+
+ void dump(PrintWriter pw);
+}
diff --git a/services/core/java/com/android/server/logcat/LogcatManagerService.java b/services/core/java/com/android/server/logcat/LogcatManagerService.java
index 497ed03..fee54f5 100644
--- a/services/core/java/com/android/server/logcat/LogcatManagerService.java
+++ b/services/core/java/com/android/server/logcat/LogcatManagerService.java
@@ -28,6 +28,7 @@
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Build;
+import android.os.DeadObjectException;
import android.os.Handler;
import android.os.ILogd;
import android.os.Looper;
@@ -518,7 +519,15 @@
Slog.d(TAG, "Approving log access: " + request);
}
try {
- getLogdService().approve(request.mUid, request.mGid, request.mPid, request.mFd);
+ try {
+ getLogdService().approve(request.mUid, request.mGid, request.mPid, request.mFd);
+ } catch (DeadObjectException e) {
+ // This can happen if logd restarts, so force getting a new connection
+ // to logd and try once more.
+ Slog.w(TAG, "Logd connection no longer valid while approving, trying once more.");
+ mLogdService = null;
+ getLogdService().approve(request.mUid, request.mGid, request.mPid, request.mFd);
+ }
Integer activeCount = mActiveLogAccessCount.getOrDefault(client, 0);
mActiveLogAccessCount.put(client, activeCount + 1);
} catch (RemoteException e) {
@@ -531,7 +540,15 @@
Slog.d(TAG, "Declining log access: " + request);
}
try {
- getLogdService().decline(request.mUid, request.mGid, request.mPid, request.mFd);
+ try {
+ getLogdService().decline(request.mUid, request.mGid, request.mPid, request.mFd);
+ } catch (DeadObjectException e) {
+ // This can happen if logd restarts, so force getting a new connection
+ // to logd and try once more.
+ Slog.w(TAG, "Logd connection no longer valid while declining, trying once more.");
+ mLogdService = null;
+ getLogdService().decline(request.mUid, request.mGid, request.mPid, request.mFd);
+ }
} catch (RemoteException e) {
Slog.e(TAG, "Fails to call remote functions", e);
}
diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
index fb3f0b3..802a7f2 100644
--- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
@@ -225,6 +225,7 @@
mMediaRouter.rebindAsUser(to.getUserIdentifier());
synchronized (mLock) {
if (mProjectionGrant != null) {
+ Slog.d(TAG, "Content Recording: Stopped MediaProjection due to user switching");
mProjectionGrant.stop();
}
}
@@ -260,6 +261,8 @@
}
synchronized (mLock) {
+ Slog.d(TAG,
+ "Content Recording: Stopped MediaProjection due to foreground service change");
if (mProjectionGrant != null) {
mProjectionGrant.stop();
}
@@ -268,6 +271,8 @@
private void startProjectionLocked(final MediaProjection projection) {
if (mProjectionGrant != null) {
+ Slog.d(TAG, "Content Recording: Stopped MediaProjection to start new "
+ + "incoming projection");
mProjectionGrant.stop();
}
if (mMediaRouteInfo != null) {
@@ -279,6 +284,8 @@
}
private void stopProjectionLocked(final MediaProjection projection) {
+ Slog.d(TAG, "Content Recording: Stopped active MediaProjection and "
+ + "dispatching stop to callbacks");
mProjectionToken = null;
mProjectionGrant = null;
dispatchStop(projection);
@@ -351,6 +358,13 @@
if (!setSessionSucceeded) {
// Unable to start mirroring, so tear down this projection.
if (mProjectionGrant != null) {
+ String projectionType = incomingSession != null
+ ? ContentRecordingSession.recordContentToString(
+ incomingSession.getContentToRecord()) : "none";
+ Slog.w(TAG, "Content Recording: Stopped MediaProjection due to failing to set "
+ + "ContentRecordingSession - id= "
+ + mProjectionGrant.getVirtualDisplayId() + "type=" + projectionType);
+
mProjectionGrant.stop();
}
return false;
@@ -464,6 +478,9 @@
// The grant may now be null if setting the session failed.
if (mProjectionGrant != null) {
// Always stop the projection.
+ Slog.w(TAG, "Content Recording: Stopped MediaProjection due to user "
+ + "consent result of CANCEL - "
+ + "id= " + mProjectionGrant.getVirtualDisplayId());
mProjectionGrant.stop();
}
break;
@@ -672,6 +689,7 @@
try {
synchronized (mLock) {
if (mProjectionGrant != null) {
+ Slog.d(TAG, "Content Recording: Stopping active projection");
mProjectionGrant.stop();
}
}
@@ -882,6 +900,10 @@
MEDIA_PROJECTION_TOKEN_EVENT_CREATED);
}
+ int getVirtualDisplayId() {
+ return mVirtualDisplayId;
+ }
+
@Override // Binder call
public boolean canProjectVideo() {
return mType == MediaProjectionManager.TYPE_MIRRORING ||
@@ -958,12 +980,11 @@
registerCallback(mCallback);
try {
mToken = callback.asBinder();
- mDeathEater = new IBinder.DeathRecipient() {
- @Override
- public void binderDied() {
- mCallbackDelegate.remove(callback);
- stop();
- }
+ mDeathEater = () -> {
+ Slog.d(TAG, "Content Recording: MediaProjection stopped by Binder death - "
+ + "id= " + mVirtualDisplayId);
+ mCallbackDelegate.remove(callback);
+ stop();
};
mToken.linkToDeath(mDeathEater, 0);
} catch (RemoteException e) {
@@ -1033,6 +1054,9 @@
Binder.restoreCallingIdentity(token);
}
}
+ Slog.d(TAG, "Content Recording: handling stopping this projection token"
+ + " createTime= " + mCreateTimeMs
+ + " countStarts= " + mCountStarts);
stopProjectionLocked(this);
mToken.unlinkToDeath(mDeathEater, 0);
mToken = null;
@@ -1158,6 +1182,8 @@
if ((type & MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY) != 0) {
mMediaRouteInfo = info;
if (mProjectionGrant != null) {
+ Slog.d(TAG, "Content Recording: Stopped MediaProjection due to "
+ + "route type of REMOTE_DISPLAY not selected");
mProjectionGrant.stop();
}
}
@@ -1329,7 +1355,7 @@
try {
mCallback.onStart(mInfo);
} catch (RemoteException e) {
- Slog.w(TAG, "Failed to notify media projection has stopped", e);
+ Slog.w(TAG, "Failed to notify media projection has started", e);
}
}
}
@@ -1403,7 +1429,8 @@
return "TYPE_MIRRORING";
case MediaProjectionManager.TYPE_PRESENTATION:
return "TYPE_PRESENTATION";
+ default:
+ return Integer.toString(type);
}
- return Integer.toString(type);
}
}
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 1bb1092..3f799dc 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -2608,7 +2608,12 @@
for (NotificationChannel channel : r.channels.values()) {
if (!channel.isSoundRestored()) {
Uri uri = channel.getSound();
- Uri restoredUri = channel.restoreSoundUri(mContext, uri, true);
+ Uri restoredUri =
+ channel.restoreSoundUri(
+ mContext,
+ uri,
+ true,
+ channel.getAudioAttributes().getUsage());
if (Settings.System.DEFAULT_NOTIFICATION_URI.equals(
restoredUri)) {
Log.w(TAG,
diff --git a/services/core/java/com/android/server/pm/TEST_MAPPING b/services/core/java/com/android/server/pm/TEST_MAPPING
index a622d07..bb6e11d 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 @@
]
}
],
- "presubmit-large": [
+ "postsubmit": [
{
"name": "CtsContentTestCases",
"options": [
@@ -104,9 +104,7 @@
"include-filter": "android.content.pm.cts"
}
]
- }
- ],
- "postsubmit": [
+ },
{
"name": "CtsPermissionTestCases",
"options": [
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 62273b5..5a6851e 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -108,7 +108,6 @@
import static android.os.InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
import static android.os.Process.SYSTEM_UID;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
-import static android.view.Display.COLOR_MODE_DEFAULT;
import static android.view.Display.INVALID_DISPLAY;
import static android.view.Surface.ROTATION_270;
import static android.view.Surface.ROTATION_90;
@@ -2785,6 +2784,9 @@
} else if (isEmbedded()) {
associateStartingWindowWithTaskIfNeeded();
}
+ if (mTransitionController.isCollecting()) {
+ mStartingData.mTransitionId = mTransitionController.getCollectingTransitionId();
+ }
}
}
@@ -4691,26 +4693,11 @@
}
/**
- * @return Whether we are allowed to show non-starting windows at the moment. We disallow
- * showing windows while the transition animation is playing in case we have windows
- * that have wide-color-gamut color mode set to avoid jank in the middle of the
- * animation.
+ * @return Whether we are allowed to show non-starting windows at the moment.
*/
boolean canShowWindows() {
- final boolean drawn = mTransitionController.isShellTransitionsEnabled()
+ return mTransitionController.isShellTransitionsEnabled()
? mSyncState != SYNC_STATE_WAITING_FOR_DRAW : allDrawn;
- final boolean animating = mTransitionController.isShellTransitionsEnabled()
- ? mTransitionController.inPlayingTransition(this)
- : isAnimating(PARENTS, ANIMATION_TYPE_APP_TRANSITION);
- return drawn && !(animating && hasNonDefaultColorWindow());
- }
-
- /**
- * @return true if we have a window that has a non-default color mode set; false otherwise.
- */
- private boolean hasNonDefaultColorWindow() {
- return forAllWindows(ws -> ws.mAttrs.getColorMode() != COLOR_MODE_DEFAULT,
- true /* topToBottom */);
}
@Override
diff --git a/services/core/java/com/android/server/wm/ActivitySnapshotController.java b/services/core/java/com/android/server/wm/ActivitySnapshotController.java
index 105b2bb..148bf9b 100644
--- a/services/core/java/com/android/server/wm/ActivitySnapshotController.java
+++ b/services/core/java/com/android/server/wm/ActivitySnapshotController.java
@@ -16,16 +16,14 @@
package com.android.server.wm;
-import static com.android.server.wm.SnapshotController.ACTIVITY_CLOSE;
-import static com.android.server.wm.SnapshotController.ACTIVITY_OPEN;
-import static com.android.server.wm.SnapshotController.TASK_CLOSE;
-import static com.android.server.wm.SnapshotController.TASK_OPEN;
+import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.os.Environment;
import android.os.SystemProperties;
+import android.os.Trace;
import android.util.ArraySet;
import android.util.Slog;
import android.util.SparseArray;
@@ -35,7 +33,6 @@
import com.android.server.LocalServices;
import com.android.server.pm.UserManagerInternal;
import com.android.server.wm.BaseAppSnapshotPersister.PersistInfoProvider;
-import com.android.server.wm.SnapshotController.TransitionState;
import java.io.File;
import java.util.ArrayList;
@@ -62,12 +59,6 @@
static final String SNAPSHOTS_DIRNAME = "activity_snapshots";
/**
- * The pending activities which should capture snapshot when process transition finish.
- */
- @VisibleForTesting
- final ArraySet<ActivityRecord> mPendingCaptureActivity = new ArraySet<>();
-
- /**
* The pending activities which should remove snapshot from memory when process transition
* finish.
*/
@@ -86,6 +77,10 @@
@VisibleForTesting
final ArraySet<ActivityRecord> mPendingLoadActivity = new ArraySet<>();
+ private final ArraySet<ActivityRecord> mOnBackPressedActivities = new ArraySet<>();
+
+ private final ArrayList<ActivityRecord> mTmpBelowActivities = new ArrayList<>();
+ private final ArrayList<WindowContainer> mTmpTransitionParticipants = new ArrayList<>();
private final SnapshotPersistQueue mSnapshotPersistQueue;
private final PersistInfoProvider mPersistInfoProvider;
private final AppSnapshotLoader mSnapshotLoader;
@@ -117,20 +112,6 @@
setSnapshotEnabled(snapshotEnabled);
}
- void systemReady() {
- if (shouldDisableSnapshots()) {
- return;
- }
- mService.mSnapshotController.registerTransitionStateConsumer(
- ACTIVITY_OPEN, this::handleOpenActivityTransition);
- mService.mSnapshotController.registerTransitionStateConsumer(
- ACTIVITY_CLOSE, this::handleCloseActivityTransition);
- mService.mSnapshotController.registerTransitionStateConsumer(
- TASK_OPEN, this::handleOpenTaskTransition);
- mService.mSnapshotController.registerTransitionStateConsumer(
- TASK_CLOSE, this::handleCloseTaskTransition);
- }
-
@Override
protected float initSnapshotScale() {
final float config = mService.mContext.getResources().getFloat(
@@ -173,6 +154,7 @@
@Override
void write() {
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "cleanUpUserFiles");
final File file = mPersistInfoProvider.getDirectory(userId);
if (file.exists()) {
final File[] contents = file.listFiles();
@@ -182,15 +164,30 @@
}
}
}
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
});
}
}
+ void addOnBackPressedActivity(ActivityRecord ar) {
+ if (shouldDisableSnapshots()) {
+ return;
+ }
+ mOnBackPressedActivities.add(ar);
+ }
+
+ void clearOnBackPressedActivities() {
+ if (shouldDisableSnapshots()) {
+ return;
+ }
+ mOnBackPressedActivities.clear();
+ }
+
/**
- * Prepare to handle on transition start. Clear all temporary fields.
+ * Prepare to collect any change for snapshots processing. Clear all temporary fields.
*/
- void preTransitionStart() {
+ void beginSnapshotProcess() {
if (shouldDisableSnapshots()) {
return;
}
@@ -198,18 +195,22 @@
}
/**
- * on transition start has notified, start process data.
+ * End collect any change for snapshots processing, start process data.
*/
- void postTransitionStart() {
+ void endSnapshotProcess() {
if (shouldDisableSnapshots()) {
return;
}
- onCommitTransition();
+ for (int i = mOnBackPressedActivities.size() - 1; i >= 0; --i) {
+ handleActivityTransition(mOnBackPressedActivities.valueAt(i));
+ }
+ mOnBackPressedActivities.clear();
+ mTmpTransitionParticipants.clear();
+ postProcess();
}
@VisibleForTesting
void resetTmpFields() {
- mPendingCaptureActivity.clear();
mPendingRemoveActivity.clear();
mPendingDeleteActivity.clear();
mPendingLoadActivity.clear();
@@ -218,31 +219,13 @@
/**
* Start process all pending activities for a transition.
*/
- private void onCommitTransition() {
+ private void postProcess() {
if (DEBUG) {
- Slog.d(TAG, "ActivitySnapshotController#onCommitTransition result:"
- + " capture " + mPendingCaptureActivity
+ Slog.d(TAG, "ActivitySnapshotController#postProcess result:"
+ " remove " + mPendingRemoveActivity
+ " delete " + mPendingDeleteActivity
+ " load " + mPendingLoadActivity);
}
- // task snapshots
- for (int i = mPendingCaptureActivity.size() - 1; i >= 0; i--) {
- recordSnapshot(mPendingCaptureActivity.valueAt(i));
- }
- // clear mTmpRemoveActivity from cache
- for (int i = mPendingRemoveActivity.size() - 1; i >= 0; i--) {
- final ActivityRecord ar = mPendingRemoveActivity.valueAt(i);
- final int code = getSystemHashCode(ar);
- mCache.onIdRemoved(code);
- }
- // clear snapshot on cache and delete files
- for (int i = mPendingDeleteActivity.size() - 1; i >= 0; i--) {
- final ActivityRecord ar = mPendingDeleteActivity.valueAt(i);
- final int code = getSystemHashCode(ar);
- mCache.onIdRemoved(code);
- removeIfUserSavedFileExist(code, ar.mUserId);
- }
// load snapshot to cache
for (int i = mPendingLoadActivity.size() - 1; i >= 0; i--) {
final ActivityRecord ar = mPendingLoadActivity.valueAt(i);
@@ -258,6 +241,8 @@
new SnapshotPersistQueue.WriteQueueItem(mPersistInfoProvider) {
@Override
void write() {
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER,
+ "load_activity_snapshot");
final TaskSnapshot snapshot = mSnapshotLoader.loadTask(code,
userId, false /* loadLowResolutionBitmap */);
synchronized (mService.getWindowManagerLock()) {
@@ -265,16 +250,36 @@
mCache.putSnapshot(ar, snapshot);
}
}
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
});
}
}
}
+ // clear mTmpRemoveActivity from cache
+ for (int i = mPendingRemoveActivity.size() - 1; i >= 0; i--) {
+ final ActivityRecord ar = mPendingRemoveActivity.valueAt(i);
+ final int code = getSystemHashCode(ar);
+ mCache.onIdRemoved(code);
+ }
+ // clear snapshot on cache and delete files
+ for (int i = mPendingDeleteActivity.size() - 1; i >= 0; i--) {
+ final ActivityRecord ar = mPendingDeleteActivity.valueAt(i);
+ final int code = getSystemHashCode(ar);
+ mCache.onIdRemoved(code);
+ removeIfUserSavedFileExist(code, ar.mUserId);
+ }
// don't keep any reference
resetTmpFields();
}
- private void recordSnapshot(ActivityRecord activity) {
+ void recordSnapshot(ActivityRecord activity) {
+ if (shouldDisableSnapshots()) {
+ return;
+ }
+ if (DEBUG) {
+ Slog.d(TAG, "ActivitySnapshotController#recordSnapshot " + activity);
+ }
final TaskSnapshot snapshot = recordSnapshotInner(activity, false /* allowSnapshotHome */);
if (snapshot != null) {
final int code = getSystemHashCode(activity);
@@ -285,15 +290,20 @@
/**
* Called when the visibility of an app changes outside the regular app transition flow.
*/
- void notifyAppVisibilityChanged(ActivityRecord appWindowToken, boolean visible) {
+ void notifyAppVisibilityChanged(ActivityRecord ar, boolean visible) {
if (shouldDisableSnapshots()) {
return;
}
+ final Task task = ar.getTask();
+ if (task == null) {
+ return;
+ }
+ // Doesn't need to capture activity snapshot when it converts from translucent.
if (!visible) {
resetTmpFields();
- addBelowTopActivityIfExist(appWindowToken.getTask(), mPendingRemoveActivity,
+ addBelowActivityIfExist(ar, mPendingRemoveActivity, false,
"remove-snapshot");
- onCommitTransition();
+ postProcess();
}
}
@@ -301,65 +311,146 @@
return System.identityHashCode(activity);
}
- void handleOpenActivityTransition(TransitionState<ActivityRecord> transitionState) {
- ArraySet<ActivityRecord> participant = transitionState.getParticipant(false /* open */);
- for (ActivityRecord ar : participant) {
- mPendingCaptureActivity.add(ar);
- // remove the snapshot for the one below close
- final ActivityRecord below = ar.getTask().getActivityBelow(ar);
- if (below != null) {
- mPendingRemoveActivity.add(below);
+ @VisibleForTesting
+ void handleTransitionFinish(@NonNull ArrayList<WindowContainer> windows) {
+ mTmpTransitionParticipants.clear();
+ mTmpTransitionParticipants.addAll(windows);
+ for (int i = mTmpTransitionParticipants.size() - 1; i >= 0; --i) {
+ final WindowContainer next = mTmpTransitionParticipants.get(i);
+ if (next.asTask() != null) {
+ handleTaskTransition(next.asTask());
+ } else if (next.asTaskFragment() != null) {
+ final TaskFragment tf = next.asTaskFragment();
+ final ActivityRecord ar = tf.getTopMostActivity();
+ if (ar != null) {
+ handleActivityTransition(ar);
+ }
+ } else if (next.asActivityRecord() != null) {
+ handleActivityTransition(next.asActivityRecord());
}
}
}
- void handleCloseActivityTransition(TransitionState<ActivityRecord> transitionState) {
- ArraySet<ActivityRecord> participant = transitionState.getParticipant(true /* open */);
- for (ActivityRecord ar : participant) {
+ private void handleActivityTransition(@NonNull ActivityRecord ar) {
+ if (shouldDisableSnapshots()) {
+ return;
+ }
+ if (ar.isVisibleRequested()) {
mPendingDeleteActivity.add(ar);
// load next one if exists.
- final ActivityRecord below = ar.getTask().getActivityBelow(ar);
- if (below != null) {
- mPendingLoadActivity.add(below);
- }
+ addBelowActivityIfExist(ar, mPendingLoadActivity, true, "load-snapshot");
+ } else {
+ // remove the snapshot for the one below close
+ addBelowActivityIfExist(ar, mPendingRemoveActivity, true, "remove-snapshot");
}
}
- void handleCloseTaskTransition(TransitionState<Task> closeTaskTransitionRecord) {
- ArraySet<Task> participant = closeTaskTransitionRecord.getParticipant(false /* open */);
- for (Task close : participant) {
- // this is close task transition
- // remove the N - 1 from cache
- addBelowTopActivityIfExist(close, mPendingRemoveActivity, "remove-snapshot");
+ private void handleTaskTransition(Task task) {
+ if (shouldDisableSnapshots()) {
+ return;
}
- }
-
- void handleOpenTaskTransition(TransitionState<Task> openTaskTransitionRecord) {
- ArraySet<Task> participant = openTaskTransitionRecord.getParticipant(true /* open */);
- for (Task open : participant) {
- // this is close task transition
- // remove the N - 1 from cache
- addBelowTopActivityIfExist(open, mPendingLoadActivity, "load-snapshot");
+ final ActivityRecord topActivity = task.getTopMostActivity();
+ if (topActivity == null) {
+ return;
+ }
+ if (task.isVisibleRequested()) {
+ // this is open task transition
+ // load the N - 1 to cache
+ addBelowActivityIfExist(topActivity, mPendingLoadActivity, true, "load-snapshot");
// Move the activities to top of mSavedFilesInOrder, so when purge happen, there
// will trim the persisted files from the most non-accessed.
- adjustSavedFileOrder(open);
+ adjustSavedFileOrder(task);
+ } else {
+ // this is close task transition
+ // remove the N - 1 from cache
+ addBelowActivityIfExist(topActivity, mPendingRemoveActivity, true, "remove-snapshot");
}
}
- // Add the top -1 activity to a set if it exists.
- private void addBelowTopActivityIfExist(Task task, ArraySet<ActivityRecord> set,
- String debugMessage) {
- final ActivityRecord topActivity = task.getTopMostActivity();
- if (topActivity != null) {
- final ActivityRecord below = task.getActivityBelow(topActivity);
- if (below != null) {
- set.add(below);
- if (DEBUG) {
- Slog.d(TAG, "ActivitySnapshotController#addBelowTopActivityIfExist "
- + below + " from " + debugMessage);
- }
+ /**
+ * Add the top -1 activity to a set if it exists.
+ * @param inTransition true if the activity must participant in transition.
+ */
+ private void addBelowActivityIfExist(ActivityRecord currentActivity,
+ ArraySet<ActivityRecord> set, boolean inTransition, String debugMessage) {
+ getActivityBelow(currentActivity, inTransition, mTmpBelowActivities);
+ for (int i = mTmpBelowActivities.size() - 1; i >= 0; --i) {
+ set.add(mTmpBelowActivities.get(i));
+ if (DEBUG) {
+ Slog.d(TAG, "ActivitySnapshotController#addBelowTopActivityIfExist "
+ + mTmpBelowActivities.get(i) + " from " + debugMessage);
}
}
+ mTmpBelowActivities.clear();
+ }
+
+ private void getActivityBelow(ActivityRecord currentActivity, boolean inTransition,
+ ArrayList<ActivityRecord> result) {
+ final Task currentTask = currentActivity.getTask();
+ if (currentTask == null) {
+ return;
+ }
+ final ActivityRecord initPrev = currentTask.getActivityBelow(currentActivity);
+ if (initPrev == null) {
+ return;
+ }
+ final TaskFragment currTF = currentActivity.getTaskFragment();
+ final TaskFragment prevTF = initPrev.getTaskFragment();
+ final TaskFragment prevAdjacentTF = prevTF != null
+ ? prevTF.getAdjacentTaskFragment() : null;
+ if (currTF == prevTF && currTF != null || prevAdjacentTF == null) {
+ // Current activity and previous one is in the same task fragment, or
+ // previous activity is not in a task fragment, or
+ // previous activity's task fragment doesn't adjacent to any others.
+ if (!inTransition || isInParticipant(initPrev, mTmpTransitionParticipants)) {
+ result.add(initPrev);
+ }
+ return;
+ }
+
+ if (prevAdjacentTF == currTF) {
+ // previous activity A is adjacent to current activity B.
+ // Try to find anyone below previous activityA, which are C and D if exists.
+ // A | B
+ // C (| D)
+ getActivityBelow(initPrev, inTransition, result);
+ } else {
+ // previous activity C isn't adjacent to current activity A.
+ // A
+ // B | C
+ final Task prevAdjacentTask = prevAdjacentTF.getTask();
+ if (prevAdjacentTask == currentTask) {
+ final int currentIndex = currTF != null
+ ? currentTask.mChildren.indexOf(currTF)
+ : currentTask.mChildren.indexOf(currentActivity);
+ final int prevAdjacentIndex =
+ prevAdjacentTask.mChildren.indexOf(prevAdjacentTF);
+ // prevAdjacentTF already above currentActivity
+ if (prevAdjacentIndex > currentIndex) {
+ return;
+ }
+ }
+ if (!inTransition || isInParticipant(initPrev, mTmpTransitionParticipants)) {
+ result.add(initPrev);
+ }
+ // prevAdjacentTF is adjacent to another one
+ final ActivityRecord prevAdjacentActivity = prevAdjacentTF.getTopMostActivity();
+ if (prevAdjacentActivity != null && (!inTransition
+ || isInParticipant(prevAdjacentActivity, mTmpTransitionParticipants))) {
+ result.add(prevAdjacentActivity);
+ }
+ }
+ }
+
+ static boolean isInParticipant(ActivityRecord ar,
+ ArrayList<WindowContainer> transitionParticipants) {
+ for (int i = transitionParticipants.size() - 1; i >= 0; --i) {
+ final WindowContainer wc = transitionParticipants.get(i);
+ if (ar == wc || ar.isDescendantOf(wc)) {
+ return true;
+ }
+ }
+ return false;
}
private void adjustSavedFileOrder(Task nextTopTask) {
@@ -376,6 +467,9 @@
@Override
void onAppRemoved(ActivityRecord activity) {
+ if (shouldDisableSnapshots()) {
+ return;
+ }
super.onAppRemoved(activity);
final int code = getSystemHashCode(activity);
removeIfUserSavedFileExist(code, activity.mUserId);
@@ -386,6 +480,9 @@
@Override
void onAppDied(ActivityRecord activity) {
+ if (shouldDisableSnapshots()) {
+ return;
+ }
super.onAppDied(activity);
final int code = getSystemHashCode(activity);
removeIfUserSavedFileExist(code, activity.mUserId);
@@ -440,7 +537,7 @@
private void removeIfUserSavedFileExist(int code, int userId) {
final UserSavedFile usf = getUserFiles(userId).get(code);
if (usf != null) {
- mUserSavedFiles.remove(code);
+ mUserSavedFiles.get(userId).remove(code);
mSavedFilesInOrder.remove(usf);
mPersister.removeSnap(code, userId);
}
@@ -490,11 +587,13 @@
new SnapshotPersistQueue.WriteQueueItem(mPersistInfoProvider) {
@Override
void write() {
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "activity_remove_files");
for (int i = files.size() - 1; i >= 0; --i) {
final UserSavedFile usf = files.get(i);
mSnapshotPersistQueue.deleteSnapshot(
usf.mFileId, usf.mUserId, mPersistInfoProvider);
}
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
});
}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 78da5de..fcf6587 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -3019,9 +3019,11 @@
// Set to activity manager directly to make sure the state can be seen by the subsequent
// update of scheduling group.
proc.setRunningAnimationUnsafe();
- mH.removeMessages(H.UPDATE_PROCESS_ANIMATING_STATE, proc);
- mH.sendMessageDelayed(mH.obtainMessage(H.UPDATE_PROCESS_ANIMATING_STATE, proc),
+ mH.sendMessage(mH.obtainMessage(H.ADD_WAKEFULNESS_ANIMATING_REASON, proc));
+ mH.removeMessages(H.REMOVE_WAKEFULNESS_ANIMATING_REASON, proc);
+ mH.sendMessageDelayed(mH.obtainMessage(H.REMOVE_WAKEFULNESS_ANIMATING_REASON, proc),
DOZE_ANIMATING_STATE_RETAIN_TIME_MS);
+ Trace.instant(TRACE_TAG_WINDOW_MANAGER, "requestWakefulnessAnimating");
}
@Override
@@ -5646,9 +5648,10 @@
final class H extends Handler {
static final int REPORT_TIME_TRACKER_MSG = 1;
- static final int UPDATE_PROCESS_ANIMATING_STATE = 2;
static final int END_POWER_MODE_UNKNOWN_VISIBILITY_MSG = 3;
static final int RESUME_FG_APP_SWITCH_MSG = 4;
+ static final int ADD_WAKEFULNESS_ANIMATING_REASON = 5;
+ static final int REMOVE_WAKEFULNESS_ANIMATING_REASON = 6;
static final int FIRST_ACTIVITY_TASK_MSG = 100;
static final int FIRST_SUPERVISOR_TASK_MSG = 200;
@@ -5665,13 +5668,23 @@
tracker.deliverResult(mContext);
}
break;
- case UPDATE_PROCESS_ANIMATING_STATE: {
+ case ADD_WAKEFULNESS_ANIMATING_REASON: {
final WindowProcessController proc = (WindowProcessController) msg.obj;
synchronized (mGlobalLock) {
- proc.updateRunningRemoteOrRecentsAnimation();
+ proc.addAnimatingReason(
+ WindowProcessController.ANIMATING_REASON_WAKEFULNESS_CHANGE);
}
}
break;
+ case REMOVE_WAKEFULNESS_ANIMATING_REASON: {
+ final WindowProcessController proc = (WindowProcessController) msg.obj;
+ synchronized (mGlobalLock) {
+ proc.removeAnimatingReason(
+ WindowProcessController.ANIMATING_REASON_WAKEFULNESS_CHANGE);
+ }
+ Trace.instant(TRACE_TAG_WINDOW_MANAGER, "finishWakefulnessAnimating");
+ }
+ break;
case END_POWER_MODE_UNKNOWN_VISIBILITY_MSG: {
synchronized (mGlobalLock) {
mRetainPowerModeAndTopProcessState = false;
diff --git a/services/core/java/com/android/server/wm/AsyncRotationController.java b/services/core/java/com/android/server/wm/AsyncRotationController.java
index 4ce21bd..2eceecc 100644
--- a/services/core/java/com/android/server/wm/AsyncRotationController.java
+++ b/services/core/java/com/android/server/wm/AsyncRotationController.java
@@ -33,6 +33,7 @@
import com.android.internal.R;
+import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.function.Consumer;
@@ -224,7 +225,15 @@
* operation directly to avoid waiting until timeout.
*/
void updateTargetWindows() {
- if (mTransitionOp == OP_LEGACY || !mIsStartTransactionCommitted) return;
+ if (mTransitionOp == OP_LEGACY) return;
+ if (!mIsStartTransactionCommitted) {
+ if (mTimeoutRunnable == null && !mDisplayContent.hasTopFixedRotationLaunchingApp()
+ && !mDisplayContent.isRotationChanging() && !mDisplayContent.inTransition()) {
+ Slog.d(TAG, "Cancel for no change");
+ mDisplayContent.finishAsyncRotationIfPossible();
+ }
+ return;
+ }
for (int i = mTargetWindowTokens.size() - 1; i >= 0; i--) {
final Operation op = mTargetWindowTokens.valueAt(i);
if (op.mIsCompletionPending || op.mAction == Operation.ACTION_SEAMLESS) {
@@ -608,6 +617,16 @@
return op.mAction != Operation.ACTION_SEAMLESS;
}
+ void dump(PrintWriter pw, String prefix) {
+ pw.println(prefix + "AsyncRotationController");
+ prefix += " ";
+ pw.println(prefix + "mTransitionOp=" + mTransitionOp);
+ pw.println(prefix + "mIsStartTransactionCommitted=" + mIsStartTransactionCommitted);
+ pw.println(prefix + "mIsSyncDrawRequested=" + mIsSyncDrawRequested);
+ pw.println(prefix + "mOriginalRotation=" + mOriginalRotation);
+ pw.println(prefix + "mTargetWindowTokens=" + mTargetWindowTokens);
+ }
+
/** The operation to control the rotation appearance associated with window token. */
private static class Operation {
@Retention(RetentionPolicy.SOURCE)
@@ -635,5 +654,10 @@
Operation(@Action int action) {
mAction = action;
}
+
+ @Override
+ public String toString() {
+ return "Operation{a=" + mAction + " pending=" + mIsCompletionPending + '}';
+ }
}
}
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index 976641b..993c016 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -1177,6 +1177,8 @@
if (!composeAnimations(mCloseTarget, mOpenTarget, openActivity)) {
return null;
}
+ mCloseTarget.mTransitionController.mSnapshotController
+ .mActivitySnapshotController.clearOnBackPressedActivities();
applyPreviewStrategy(mOpenAdaptor, openActivity);
final IBackAnimationFinishedCallback callback = makeAnimationFinishedCallback();
@@ -1222,6 +1224,8 @@
// Call it again to make sure the activity could be visible while handling the pending
// animation.
activity.commitVisibility(true, true);
+ activity.mTransitionController.mSnapshotController
+ .mActivitySnapshotController.addOnBackPressedActivity(activity);
}
activity.mLaunchTaskBehind = true;
@@ -1248,6 +1252,9 @@
// Restore the launch-behind state.
activity.mTaskSupervisor.scheduleLaunchTaskBehindComplete(activity.token);
activity.mLaunchTaskBehind = false;
+ // Ignore all change
+ activity.mTransitionController.mSnapshotController
+ .mActivitySnapshotController.clearOnBackPressedActivities();
ProtoLog.d(WM_DEBUG_BACK_PREVIEW,
"Setting Activity.mLauncherTaskBehind to false. Activity=%s",
activity);
diff --git a/services/core/java/com/android/server/wm/ContentRecorder.java b/services/core/java/com/android/server/wm/ContentRecorder.java
index 2ecbf8a..5aa7c97 100644
--- a/services/core/java/com/android/server/wm/ContentRecorder.java
+++ b/services/core/java/com/android/server/wm/ContentRecorder.java
@@ -87,7 +87,7 @@
private int mLastOrientation = ORIENTATION_UNDEFINED;
ContentRecorder(@NonNull DisplayContent displayContent) {
- this(displayContent, new RemoteMediaProjectionManagerWrapper());
+ this(displayContent, new RemoteMediaProjectionManagerWrapper(displayContent.mDisplayId));
}
@VisibleForTesting
@@ -556,8 +556,14 @@
private static final class RemoteMediaProjectionManagerWrapper implements
MediaProjectionManagerWrapper {
+
+ private final int mDisplayId;
@Nullable private IMediaProjectionManager mIMediaProjectionManager = null;
+ RemoteMediaProjectionManagerWrapper(int displayId) {
+ mDisplayId = displayId;
+ }
+
@Override
public void stopActiveProjection() {
fetchMediaProjectionManager();
@@ -565,12 +571,15 @@
return;
}
try {
+ ProtoLog.e(WM_DEBUG_CONTENT_RECORDING,
+ "Content Recording: stopping active projection for display %d",
+ mDisplayId);
mIMediaProjectionManager.stopActiveProjection();
} catch (RemoteException e) {
ProtoLog.e(WM_DEBUG_CONTENT_RECORDING,
"Content Recording: Unable to tell MediaProjectionManagerService to stop "
- + "the active projection: %s",
- e);
+ + "the active projection for display %d: %s",
+ mDisplayId, e);
}
}
diff --git a/services/core/java/com/android/server/wm/ContentRecordingController.java b/services/core/java/com/android/server/wm/ContentRecordingController.java
index f24ba5a..b589085 100644
--- a/services/core/java/com/android/server/wm/ContentRecordingController.java
+++ b/services/core/java/com/android/server/wm/ContentRecordingController.java
@@ -117,10 +117,11 @@
}
incomingDisplayContent.setContentRecordingSession(incomingSession);
// Updating scenario: Explicitly ask ContentRecorder to update, since no config or
- // display change will trigger an update from the DisplayContent.
- if (hasSessionUpdatedWithConsent) {
- incomingDisplayContent.updateRecording();
- }
+ // display change will trigger an update from the DisplayContent. There exists a
+ // scenario where a DisplayContent is created, but it's ContentRecordingSession hasn't
+ // been set yet due to a race condition. On creation, updateRecording fails to start
+ // recording, so now this call guarantees recording will be started from somewhere.
+ incomingDisplayContent.updateRecording();
}
// Takeover and stopping scenario: stop recording on the pre-existing session.
if (mSession != null && !hasSessionUpdatedWithConsent) {
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 64c2c5d..ee3014c 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -3454,6 +3454,9 @@
if (mFixedRotationLaunchingApp != null) {
setSeamlessTransitionForFixedRotation(controller.getCollectingTransition());
}
+ } else if (mAsyncRotationController != null && !isRotationChanging()) {
+ Slog.i(TAG, "Finish AsyncRotation for previous intermediate change");
+ finishAsyncRotationIfPossible();
}
return;
}
@@ -3627,6 +3630,9 @@
if (mFixedRotationLaunchingApp != null) {
pw.println(" mFixedRotationLaunchingApp=" + mFixedRotationLaunchingApp);
}
+ if (mAsyncRotationController != null) {
+ mAsyncRotationController.dump(pw, prefix);
+ }
pw.println();
pw.print(prefix + "mHoldScreenWindow="); pw.print(mHoldScreenWindow);
diff --git a/services/core/java/com/android/server/wm/Letterbox.java b/services/core/java/com/android/server/wm/Letterbox.java
index 3551370..f9fa9e6 100644
--- a/services/core/java/com/android/server/wm/Letterbox.java
+++ b/services/core/java/com/android/server/wm/Letterbox.java
@@ -20,7 +20,6 @@
import static android.view.SurfaceControl.HIDDEN;
import static android.window.TaskConstants.TASK_CHILD_LAYER_LETTERBOX_BACKGROUND;
-import android.content.Context;
import android.graphics.Color;
import android.graphics.Point;
import android.graphics.Rect;
@@ -256,11 +255,11 @@
private final GestureDetector mDoubleTapDetector;
private final DoubleTapListener mDoubleTapListener;
- TapEventReceiver(InputChannel inputChannel, Context context) {
+ TapEventReceiver(InputChannel inputChannel, WindowManagerService wmService) {
super(inputChannel, UiThread.getHandler().getLooper());
- mDoubleTapListener = new DoubleTapListener();
+ mDoubleTapListener = new DoubleTapListener(wmService);
mDoubleTapDetector = new GestureDetector(
- context, mDoubleTapListener, UiThread.getHandler());
+ wmService.mContext, mDoubleTapListener, UiThread.getHandler());
}
@Override
@@ -271,14 +270,24 @@
}
private class DoubleTapListener extends GestureDetector.SimpleOnGestureListener {
+ private final WindowManagerService mWmService;
+
+ private DoubleTapListener(WindowManagerService wmService) {
+ mWmService = wmService;
+ }
+
@Override
public boolean onDoubleTapEvent(MotionEvent e) {
- if (e.getAction() == MotionEvent.ACTION_UP) {
- mDoubleTapCallbackX.accept((int) e.getRawX());
- mDoubleTapCallbackY.accept((int) e.getRawY());
- return true;
+ synchronized (mWmService.mGlobalLock) {
+ // This check prevents late events to be handled in case the Letterbox has been
+ // already destroyed and so mOuter.isEmpty() is true.
+ if (!mOuter.isEmpty() && e.getAction() == MotionEvent.ACTION_UP) {
+ mDoubleTapCallbackX.accept((int) e.getRawX());
+ mDoubleTapCallbackY.accept((int) e.getRawY());
+ return true;
+ }
+ return false;
}
- return false;
}
}
@@ -294,7 +303,7 @@
mWmService = win.mWmService;
final String name = namePrefix + (win.mActivityRecord != null ? win.mActivityRecord : win);
mClientChannel = mWmService.mInputManager.createInputChannel(name);
- mInputEventReceiver = new TapEventReceiver(mClientChannel, mWmService.mContext);
+ mInputEventReceiver = new TapEventReceiver(mClientChannel, mWmService);
mToken = mClientChannel.getToken();
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index 7d3c87a..ba242ec 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -63,6 +63,8 @@
import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE;
import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE;
import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES;
+import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE;
+import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE;
import static android.view.WindowManager.PROPERTY_COMPAT_ENABLE_FAKE_FOCUS;
import static android.view.WindowManager.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION;
@@ -215,6 +217,11 @@
@Nullable
private final Boolean mBooleanPropertyAllowForceResizeOverride;
+ @Nullable
+ private final Boolean mBooleanPropertyAllowUserAspectRatioOverride;
+ @Nullable
+ private final Boolean mBooleanPropertyAllowUserAspectRatioFullscreenOverride;
+
/*
* WindowContainerListener responsible to make translucent activities inherit
* constraints from the first opaque activity beneath them. It's null for not
@@ -335,6 +342,15 @@
/* gatingCondition */ null,
PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES);
+ mBooleanPropertyAllowUserAspectRatioOverride =
+ readComponentProperty(packageManager, mActivityRecord.packageName,
+ () -> mLetterboxConfiguration.isUserAppAspectRatioSettingsEnabled(),
+ PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE);
+ mBooleanPropertyAllowUserAspectRatioFullscreenOverride =
+ readComponentProperty(packageManager, mActivityRecord.packageName,
+ () -> mLetterboxConfiguration.isUserAppAspectRatioFullscreenEnabled(),
+ PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE);
+
mIsOverrideAnyOrientationEnabled = isCompatChangeEnabled(OVERRIDE_ANY_ORIENTATION);
mIsOverrideToPortraitOrientationEnabled =
isCompatChangeEnabled(OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT);
@@ -1109,7 +1125,8 @@
}
boolean shouldApplyUserMinAspectRatioOverride() {
- if (!mLetterboxConfiguration.isUserAppAspectRatioSettingsEnabled()
+ if (FALSE.equals(mBooleanPropertyAllowUserAspectRatioOverride)
+ || !mLetterboxConfiguration.isUserAppAspectRatioSettingsEnabled()
|| mActivityRecord.mDisplayContent == null
|| !mActivityRecord.mDisplayContent.getIgnoreOrientationRequest()) {
return false;
@@ -1122,7 +1139,9 @@
}
boolean shouldApplyUserFullscreenOverride() {
- if (!mLetterboxConfiguration.isUserAppAspectRatioFullscreenEnabled()
+ if (FALSE.equals(mBooleanPropertyAllowUserAspectRatioOverride)
+ || FALSE.equals(mBooleanPropertyAllowUserAspectRatioFullscreenOverride)
+ || !mLetterboxConfiguration.isUserAppAspectRatioFullscreenEnabled()
|| mActivityRecord.mDisplayContent == null
|| !mActivityRecord.mDisplayContent.getIgnoreOrientationRequest()) {
return false;
@@ -1151,7 +1170,8 @@
}
}
- private int getUserMinAspectRatioOverrideCode() {
+ @VisibleForTesting
+ int getUserMinAspectRatioOverrideCode() {
try {
return mActivityRecord.mAtmService.getPackageManager()
.getUserMinAspectRatio(mActivityRecord.packageName, mActivityRecord.mUserId);
diff --git a/services/core/java/com/android/server/wm/SnapshotController.java b/services/core/java/com/android/server/wm/SnapshotController.java
index badcfa9..37f9730 100644
--- a/services/core/java/com/android/server/wm/SnapshotController.java
+++ b/services/core/java/com/android/server/wm/SnapshotController.java
@@ -16,185 +16,36 @@
package com.android.server.wm;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_FIRST_CUSTOM;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
-import android.annotation.IntDef;
-import android.util.ArraySet;
-import android.util.Slog;
-import android.util.SparseArray;
+import android.os.Trace;
import android.view.WindowManager;
-import com.android.internal.annotations.VisibleForTesting;
-
import java.io.PrintWriter;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
-import java.util.function.Consumer;
/**
* Integrates common functionality from TaskSnapshotController and ActivitySnapshotController.
*/
class SnapshotController {
- private static final boolean DEBUG = false;
- private static final String TAG = AbsAppSnapshotController.TAG;
-
- static final int ACTIVITY_OPEN = 1;
- static final int ACTIVITY_CLOSE = 2;
- static final int TASK_OPEN = 4;
- static final int TASK_CLOSE = 8;
- /** @hide */
- @Retention(RetentionPolicy.SOURCE)
- @IntDef(
- value = {ACTIVITY_OPEN,
- ACTIVITY_CLOSE,
- TASK_OPEN,
- TASK_CLOSE})
- @interface TransitionStateType {}
-
private final SnapshotPersistQueue mSnapshotPersistQueue;
final TaskSnapshotController mTaskSnapshotController;
final ActivitySnapshotController mActivitySnapshotController;
- private final ArraySet<Task> mTmpCloseTasks = new ArraySet<>();
- private final ArraySet<Task> mTmpOpenTasks = new ArraySet<>();
-
- private final SparseArray<TransitionState> mTmpOpenCloseRecord = new SparseArray<>();
- private final ArraySet<Integer> mTmpAnalysisRecord = new ArraySet<>();
- private final SparseArray<ArrayList<Consumer<TransitionState>>> mTransitionStateConsumer =
- new SparseArray<>();
- private int mActivatedType;
-
- private final ActivityOrderCheck mActivityOrderCheck = new ActivityOrderCheck();
- private final ActivityOrderCheck.AnalysisResult mResultHandler = (type, close, open) -> {
- addTransitionRecord(type, true/* open */, open);
- addTransitionRecord(type, false/* open */, close);
- };
-
- private static class ActivityOrderCheck {
- private ActivityRecord mOpenActivity;
- private ActivityRecord mCloseActivity;
- private int mOpenIndex = -1;
- private int mCloseIndex = -1;
-
- private void reset() {
- mOpenActivity = null;
- mCloseActivity = null;
- mOpenIndex = -1;
- mCloseIndex = -1;
- }
-
- private void setTarget(boolean open, ActivityRecord ar, int index) {
- if (open) {
- mOpenActivity = ar;
- mOpenIndex = index;
- } else {
- mCloseActivity = ar;
- mCloseIndex = index;
- }
- }
-
- void analysisOrder(ArraySet<ActivityRecord> closeApps,
- ArraySet<ActivityRecord> openApps, Task task, AnalysisResult result) {
- for (int j = closeApps.size() - 1; j >= 0; j--) {
- final ActivityRecord ar = closeApps.valueAt(j);
- if (ar.getTask() == task) {
- setTarget(false, ar, task.mChildren.indexOf(ar));
- break;
- }
- }
- for (int j = openApps.size() - 1; j >= 0; j--) {
- final ActivityRecord ar = openApps.valueAt(j);
- if (ar.getTask() == task) {
- setTarget(true, ar, task.mChildren.indexOf(ar));
- break;
- }
- }
- if (mOpenIndex > mCloseIndex && mCloseIndex != -1) {
- result.onCheckResult(ACTIVITY_OPEN, mCloseActivity, mOpenActivity);
- } else if (mOpenIndex < mCloseIndex && mOpenIndex != -1) {
- result.onCheckResult(ACTIVITY_CLOSE, mCloseActivity, mOpenActivity);
- }
- reset();
- }
- private interface AnalysisResult {
- void onCheckResult(@TransitionStateType int type,
- ActivityRecord close, ActivityRecord open);
- }
- }
-
- private void addTransitionRecord(int type, boolean open, WindowContainer target) {
- TransitionState record = mTmpOpenCloseRecord.get(type);
- if (record == null) {
- record = new TransitionState();
- mTmpOpenCloseRecord.set(type, record);
- }
- record.addParticipant(target, open);
- mTmpAnalysisRecord.add(type);
- }
-
- private void clearRecord() {
- mTmpOpenCloseRecord.clear();
- mTmpAnalysisRecord.clear();
- }
-
- static class TransitionState<TYPE extends WindowContainer> {
- private final ArraySet<TYPE> mOpenParticipant = new ArraySet<>();
- private final ArraySet<TYPE> mCloseParticipant = new ArraySet<>();
-
- void addParticipant(TYPE target, boolean open) {
- final ArraySet<TYPE> participant = open
- ? mOpenParticipant : mCloseParticipant;
- participant.add(target);
- }
-
- ArraySet<TYPE> getParticipant(boolean open) {
- return open ? mOpenParticipant : mCloseParticipant;
- }
- }
-
SnapshotController(WindowManagerService wms) {
mSnapshotPersistQueue = new SnapshotPersistQueue();
mTaskSnapshotController = new TaskSnapshotController(wms, mSnapshotPersistQueue);
mActivitySnapshotController = new ActivitySnapshotController(wms, mSnapshotPersistQueue);
}
- void registerTransitionStateConsumer(@TransitionStateType int type,
- Consumer<TransitionState> consumer) {
- ArrayList<Consumer<TransitionState>> consumers = mTransitionStateConsumer.get(type);
- if (consumers == null) {
- consumers = new ArrayList<>();
- mTransitionStateConsumer.set(type, consumers);
- }
- if (!consumers.contains(consumer)) {
- consumers.add(consumer);
- }
- mActivatedType |= type;
- }
-
- void unregisterTransitionStateConsumer(int type, Consumer<TransitionState> consumer) {
- final ArrayList<Consumer<TransitionState>> consumers = mTransitionStateConsumer.get(type);
- if (consumers == null) {
- return;
- }
- consumers.remove(consumer);
- if (consumers.size() == 0) {
- mActivatedType &= ~type;
- }
- }
-
- private boolean hasTransitionStateConsumer(@TransitionStateType int type) {
- return (mActivatedType & type) != 0;
- }
-
void systemReady() {
mSnapshotPersistQueue.systemReady();
- mTaskSnapshotController.systemReady();
- mActivitySnapshotController.systemReady();
}
void setPause(boolean paused) {
@@ -212,47 +63,69 @@
}
void notifyAppVisibilityChanged(ActivityRecord appWindowToken, boolean visible) {
- if (!visible && hasTransitionStateConsumer(TASK_CLOSE)) {
- final Task task = appWindowToken.getTask();
- if (task == null || task.isVisibleRequested()) {
- return;
+ mActivitySnapshotController.notifyAppVisibilityChanged(appWindowToken, visible);
+ }
+
+ // For legacy transition, which won't support activity snapshot
+ void onTransitionStarting(DisplayContent displayContent) {
+ mTaskSnapshotController.handleClosingApps(displayContent.mClosingApps);
+ }
+
+ // For shell transition, record snapshots before transaction start.
+ void onTransactionReady(@WindowManager.TransitionType int type,
+ ArrayList<Transition.ChangeInfo> changeInfos) {
+ final boolean isTransitionOpen = isTransitionOpen(type);
+ final boolean isTransitionClose = isTransitionClose(type);
+ if (!isTransitionOpen && !isTransitionClose && type < TRANSIT_FIRST_CUSTOM) {
+ return;
+ }
+ for (int i = changeInfos.size() - 1; i >= 0; --i) {
+ Transition.ChangeInfo info = changeInfos.get(i);
+ // Intentionally skip record snapshot for changes originated from PiP.
+ if (info.mWindowingMode == WINDOWING_MODE_PINNED) continue;
+ if (info.mContainer.asTask() != null && !info.mContainer.isVisibleRequested()) {
+ mTaskSnapshotController.recordSnapshot(info.mContainer.asTask(),
+ false /* allowSnapshotHome */);
}
- // close task transition
- addTransitionRecord(TASK_CLOSE, false /*open*/, task);
- mActivitySnapshotController.preTransitionStart();
- notifyTransition(TASK_CLOSE);
- mActivitySnapshotController.postTransitionStart();
- clearRecord();
+ // Won't need to capture activity snapshot in close transition.
+ if (isTransitionClose) {
+ continue;
+ }
+ if (info.mContainer.asActivityRecord() != null
+ || info.mContainer.asTaskFragment() != null) {
+ final TaskFragment tf = info.mContainer.asTaskFragment();
+ final ActivityRecord ar = tf != null ? tf.getTopMostActivity()
+ : info.mContainer.asActivityRecord();
+ final boolean taskVis = ar != null && ar.getTask().isVisibleRequested();
+ if (ar != null && !ar.isVisibleRequested() && taskVis) {
+ mActivitySnapshotController.recordSnapshot(ar);
+ }
+ }
}
}
- // For legacy transition
- void onTransitionStarting(DisplayContent displayContent) {
- handleAppTransition(displayContent.mClosingApps, displayContent.mOpeningApps);
- }
-
- // For shell transition, adapt to legacy transition.
- void onTransitionReady(@WindowManager.TransitionType int type,
- ArraySet<WindowContainer> participants) {
+ void onTransitionFinish(@WindowManager.TransitionType int type,
+ ArrayList<Transition.ChangeInfo> changeInfos) {
final boolean isTransitionOpen = isTransitionOpen(type);
final boolean isTransitionClose = isTransitionClose(type);
if (!isTransitionOpen && !isTransitionClose && type < TRANSIT_FIRST_CUSTOM
- || (mActivatedType == 0)) {
+ || (changeInfos.isEmpty())) {
return;
}
- final ArraySet<ActivityRecord> openingApps = new ArraySet<>();
- final ArraySet<ActivityRecord> closingApps = new ArraySet<>();
-
- for (int i = participants.size() - 1; i >= 0; --i) {
- final ActivityRecord ar = participants.valueAt(i).asActivityRecord();
- if (ar == null || ar.getTask() == null) continue;
- if (ar.isVisibleRequested()) {
- openingApps.add(ar);
- } else {
- closingApps.add(ar);
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "SnapshotController_analysis");
+ mActivitySnapshotController.beginSnapshotProcess();
+ final ArrayList<WindowContainer> windows = new ArrayList<>();
+ for (int i = changeInfos.size() - 1; i >= 0; --i) {
+ final WindowContainer wc = changeInfos.get(i).mContainer;
+ if (wc.asTask() == null && wc.asTaskFragment() == null
+ && wc.asActivityRecord() == null) {
+ continue;
}
+ windows.add(wc);
}
- handleAppTransition(closingApps, openingApps);
+ mActivitySnapshotController.handleTransitionFinish(windows);
+ mActivitySnapshotController.endSnapshotProcess();
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
private static boolean isTransitionOpen(int type) {
@@ -262,78 +135,6 @@
return type == TRANSIT_CLOSE || type == TRANSIT_TO_BACK;
}
- @VisibleForTesting
- void handleAppTransition(ArraySet<ActivityRecord> closingApps,
- ArraySet<ActivityRecord> openApps) {
- if (mActivatedType == 0) {
- return;
- }
- analysisTransition(closingApps, openApps);
- mActivitySnapshotController.preTransitionStart();
- for (Integer transitionType : mTmpAnalysisRecord) {
- notifyTransition(transitionType);
- }
- mActivitySnapshotController.postTransitionStart();
- clearRecord();
- }
-
- private void notifyTransition(int transitionType) {
- final TransitionState record = mTmpOpenCloseRecord.get(transitionType);
- final ArrayList<Consumer<TransitionState>> consumers =
- mTransitionStateConsumer.get(transitionType);
- for (Consumer<TransitionState> consumer : consumers) {
- consumer.accept(record);
- }
- }
-
- private void analysisTransition(ArraySet<ActivityRecord> closingApps,
- ArraySet<ActivityRecord> openingApps) {
- getParticipantTasks(closingApps, mTmpCloseTasks, false /* isOpen */);
- getParticipantTasks(openingApps, mTmpOpenTasks, true /* isOpen */);
- if (DEBUG) {
- Slog.d(TAG, "AppSnapshotController#analysisTransition participants"
- + " mTmpCloseTasks " + mTmpCloseTasks
- + " mTmpOpenTasks " + mTmpOpenTasks);
- }
- for (int i = mTmpCloseTasks.size() - 1; i >= 0; i--) {
- final Task closeTask = mTmpCloseTasks.valueAt(i);
- if (mTmpOpenTasks.contains(closeTask)) {
- if (hasTransitionStateConsumer(ACTIVITY_OPEN)
- || hasTransitionStateConsumer(ACTIVITY_CLOSE)) {
- mActivityOrderCheck.analysisOrder(closingApps, openingApps, closeTask,
- mResultHandler);
- }
- } else if (hasTransitionStateConsumer(TASK_CLOSE)) {
- // close task transition
- addTransitionRecord(TASK_CLOSE, false /*open*/, closeTask);
- }
- }
- if (hasTransitionStateConsumer(TASK_OPEN)) {
- for (int i = mTmpOpenTasks.size() - 1; i >= 0; i--) {
- final Task openTask = mTmpOpenTasks.valueAt(i);
- if (!mTmpCloseTasks.contains(openTask)) {
- // this is open task transition
- addTransitionRecord(TASK_OPEN, true /*open*/, openTask);
- }
- }
- }
- mTmpCloseTasks.clear();
- mTmpOpenTasks.clear();
- }
-
- private void getParticipantTasks(ArraySet<ActivityRecord> activityRecords, ArraySet<Task> tasks,
- boolean isOpen) {
- for (int i = activityRecords.size() - 1; i >= 0; i--) {
- final ActivityRecord activity = activityRecords.valueAt(i);
- final Task task = activity.getTask();
- if (task == null) continue;
-
- if (isOpen == activity.isVisibleRequested()) {
- tasks.add(task);
- }
- }
- }
-
void dump(PrintWriter pw, String prefix) {
mTaskSnapshotController.dump(pw, prefix);
mActivitySnapshotController.dump(pw, prefix);
diff --git a/services/core/java/com/android/server/wm/SnapshotPersistQueue.java b/services/core/java/com/android/server/wm/SnapshotPersistQueue.java
index 58e1c54..f4f641f 100644
--- a/services/core/java/com/android/server/wm/SnapshotPersistQueue.java
+++ b/services/core/java/com/android/server/wm/SnapshotPersistQueue.java
@@ -17,6 +17,7 @@
package com.android.server.wm;
import static android.graphics.Bitmap.CompressFormat.JPEG;
+import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
@@ -26,6 +27,7 @@
import android.graphics.Bitmap;
import android.os.Process;
import android.os.SystemClock;
+import android.os.Trace;
import android.util.AtomicFile;
import android.util.Slog;
import android.window.TaskSnapshot;
@@ -249,6 +251,7 @@
@Override
void write() {
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "StoreWriteQueueItem");
if (!mPersistInfoProvider.createDirectory(mUserId)) {
Slog.e(TAG, "Unable to create snapshot directory for user dir="
+ mPersistInfoProvider.getDirectory(mUserId));
@@ -263,6 +266,7 @@
if (failed) {
deleteSnapshot(mId, mUserId, mPersistInfoProvider);
}
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
boolean writeProto() {
@@ -373,7 +377,9 @@
@Override
void write() {
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "DeleteWriteQueueItem");
deleteSnapshot(mId, mUserId, mPersistInfoProvider);
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
}
}
diff --git a/services/core/java/com/android/server/wm/StartingData.java b/services/core/java/com/android/server/wm/StartingData.java
index 2b22d75..34806bd 100644
--- a/services/core/java/com/android/server/wm/StartingData.java
+++ b/services/core/java/com/android/server/wm/StartingData.java
@@ -65,6 +65,9 @@
/** Whether to prepare the removal animation. */
boolean mPrepareRemoveAnimation;
+ /** Non-zero if this starting window is added in a collecting transition. */
+ int mTransitionId;
+
protected StartingData(WindowManagerService service, int typeParams) {
mService = service;
mTypeParams = typeParams;
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 6caccdd..8fbaac2 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -2878,8 +2878,8 @@
// No need to check if allowed if it's leaving dragResize
if (dragResizing
&& !(getRootTask().getWindowingMode() == WINDOWING_MODE_FREEFORM)) {
- throw new IllegalArgumentException("Drag resize not allow for root task id="
- + getRootTaskId());
+ Slog.e(TAG, "Drag resize isn't allowed for root task id=" + getRootTaskId());
+ return;
}
mDragResizing = dragResizing;
resetDragResizingChangeReported();
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java
index c747c09..4eb4290 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotController.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java
@@ -16,7 +16,6 @@
package com.android.server.wm;
-import static com.android.server.wm.SnapshotController.TASK_CLOSE;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SCREENSHOT;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
@@ -77,13 +76,6 @@
setSnapshotEnabled(snapshotEnabled);
}
- void systemReady() {
- if (!shouldDisableSnapshots()) {
- mService.mSnapshotController.registerTransitionStateConsumer(TASK_CLOSE,
- this::handleTaskClose);
- }
- }
-
static PersistInfoProvider createPersistInfoProvider(WindowManagerService service,
BaseAppSnapshotPersister.DirectoryResolver resolver) {
final float highResTaskSnapshotScale = service.mContext.getResources().getFloat(
@@ -116,20 +108,23 @@
enableLowResSnapshots, lowResScaleFactor, use16BitFormat);
}
- void handleTaskClose(SnapshotController.TransitionState<Task> closeTaskTransitionRecord) {
+ // Still needed for legacy transition.(AppTransitionControllerTest)
+ void handleClosingApps(ArraySet<ActivityRecord> closingApps) {
if (shouldDisableSnapshots()) {
return;
}
+ // We need to take a snapshot of the task if and only if all activities of the task are
+ // either closing or hidden.
mTmpTasks.clear();
- final ArraySet<Task> tasks = closeTaskTransitionRecord.getParticipant(false /* open */);
- if (mService.mAtmService.getTransitionController().isShellTransitionsEnabled()) {
- mTmpTasks.addAll(tasks);
- } else {
- for (Task task : tasks) {
- getClosingTasksInner(task, mTmpTasks);
- }
+ for (int i = closingApps.size() - 1; i >= 0; i--) {
+ final ActivityRecord activity = closingApps.valueAt(i);
+ final Task task = activity.getTask();
+ if (task == null) continue;
+
+ getClosingTasksInner(task, mTmpTasks);
}
snapshotTasks(mTmpTasks);
+ mTmpTasks.clear();
mSkipClosingAppSnapshotTasks.clear();
}
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotPersister.java b/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
index cd15119..3e8c017 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
@@ -16,6 +16,9 @@
package com.android.server.wm;
+import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
+
+import android.os.Trace;
import android.util.ArraySet;
import android.window.TaskSnapshot;
@@ -102,6 +105,7 @@
@Override
void write() {
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "RemoveObsoleteFilesQueueItem");
final ArraySet<Integer> newPersistedTaskIds;
synchronized (mLock) {
newPersistedTaskIds = new ArraySet<>(mPersistedTaskIdsSinceLastRemoveObsolete);
@@ -120,6 +124,7 @@
}
}
}
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
@VisibleForTesting
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index fd25edf..f1fb17b 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -1191,8 +1191,6 @@
" Skipping post-transition snapshot for task %d",
task.mTaskId);
}
- snapController.mActivitySnapshotController
- .notifyAppVisibilityChanged(ar, false /* visible */);
}
ar.commitVisibility(false /* visible */, false /* performLayout */,
true /* fromTransition */);
@@ -1389,6 +1387,7 @@
// Handle back animation if it's already started.
mController.mAtm.mBackNavigationController.onTransitionFinish(mTargets, this);
mController.mFinishingTransition = null;
+ mController.mSnapshotController.onTransitionFinish(mType, mTargets);
}
void abort() {
@@ -1593,16 +1592,7 @@
// transferred. If transition is transient, IME won't be moved during the transition and
// the tasks are still live, so we take the snapshot at the end of the transition instead.
if (mTransientLaunches == null) {
- for (int i = mParticipants.size() - 1; i >= 0; --i) {
- final ActivityRecord ar = mParticipants.valueAt(i).asActivityRecord();
- if (ar == null || ar.getTask() == null
- || ar.getTask().isVisibleRequested()) continue;
- final ChangeInfo change = mChanges.get(ar);
- // Intentionally skip record snapshot for changes originated from PiP.
- if (change != null && change.mWindowingMode == WINDOWING_MODE_PINNED) continue;
- mController.mSnapshotController.mTaskSnapshotController.recordSnapshot(
- ar.getTask(), false /* allowSnapshotHome */);
- }
+ mController.mSnapshotController.onTransactionReady(mType, mTargets);
}
// This is non-null only if display has changes. It handles the visible windows that don't
diff --git a/services/core/java/com/android/server/wm/TransitionTracer.java b/services/core/java/com/android/server/wm/TransitionTracer.java
index af8fb02..c59d2d3 100644
--- a/services/core/java/com/android/server/wm/TransitionTracer.java
+++ b/services/core/java/com/android/server/wm/TransitionTracer.java
@@ -145,6 +145,27 @@
}
}
+ void logRemovingStartingWindow(@NonNull StartingData startingData) {
+ if (startingData.mTransitionId == 0) {
+ return;
+ }
+ try {
+ final ProtoOutputStream outputStream = new ProtoOutputStream(CHUNK_SIZE);
+ final long protoToken = outputStream
+ .start(com.android.server.wm.shell.TransitionTraceProto.TRANSITIONS);
+ outputStream.write(com.android.server.wm.shell.Transition.ID,
+ startingData.mTransitionId);
+ outputStream.write(
+ com.android.server.wm.shell.Transition.STARTING_WINDOW_REMOVE_TIME_NS,
+ SystemClock.elapsedRealtimeNanos());
+ outputStream.end(protoToken);
+
+ mTraceBuffer.add(outputStream);
+ } catch (Exception e) {
+ Log.e(LOG_TAG, "Unexpected exception thrown while logging transitions", e);
+ }
+ }
+
private void dumpTransitionTargetsToProto(ProtoOutputStream outputStream,
Transition transition, ArrayList<ChangeInfo> targets) {
Trace.beginSection("TransitionTracer#dumpTransitionTargetsToProto");
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 0388496..9375b29 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -1657,9 +1657,18 @@
}
private int setAdjacentRootsHierarchyOp(WindowContainerTransaction.HierarchyOp hop) {
- final TaskFragment root1 = WindowContainer.fromBinder(hop.getContainer()).asTaskFragment();
- final TaskFragment root2 =
- WindowContainer.fromBinder(hop.getAdjacentRoot()).asTaskFragment();
+ final WindowContainer wc1 = WindowContainer.fromBinder(hop.getContainer());
+ if (wc1 == null || !wc1.isAttached()) {
+ Slog.e(TAG, "Attempt to operate on unknown or detached container: " + wc1);
+ return TRANSACT_EFFECTS_NONE;
+ }
+ final TaskFragment root1 = wc1.asTaskFragment();
+ final WindowContainer wc2 = WindowContainer.fromBinder(hop.getAdjacentRoot());
+ if (wc2 == null || !wc2.isAttached()) {
+ Slog.e(TAG, "Attempt to operate on unknown or detached container: " + wc2);
+ return TRANSACT_EFFECTS_NONE;
+ }
+ final TaskFragment root2 = wc2.asTaskFragment();
if (!root1.mCreatedByOrganizer || !root2.mCreatedByOrganizer) {
throw new IllegalArgumentException("setAdjacentRootsHierarchyOp: Not created by"
+ " organizer root1=" + root1 + " root2=" + root2);
@@ -1672,7 +1681,12 @@
}
private int clearAdjacentRootsHierarchyOp(WindowContainerTransaction.HierarchyOp hop) {
- final TaskFragment root = WindowContainer.fromBinder(hop.getContainer()).asTaskFragment();
+ final WindowContainer wc = WindowContainer.fromBinder(hop.getContainer());
+ if (wc == null || !wc.isAttached()) {
+ Slog.e(TAG, "Attempt to operate on unknown or detached container: " + wc);
+ return TRANSACT_EFFECTS_NONE;
+ }
+ final TaskFragment root = wc.asTaskFragment();
if (!root.mCreatedByOrganizer) {
throw new IllegalArgumentException("clearAdjacentRootsHierarchyOp: Not created by"
+ " organizer root=" + root);
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index d7d2b4e..caec45c5 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -48,6 +48,7 @@
import static java.util.Objects.requireNonNull;
import android.Manifest;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
@@ -75,7 +76,6 @@
import android.util.Log;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
-import android.view.IRemoteAnimationRunner;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -87,6 +87,8 @@
import java.io.IOException;
import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
@@ -249,11 +251,30 @@
@Nullable
private ArrayMap<ActivityRecord, int[]> mRemoteActivities;
- /** Whether our process is currently running a {@link RecentsAnimation} */
- private boolean mRunningRecentsAnimation;
+ /**
+ * It can be set for a running transition player ({@link android.window.ITransitionPlayer}) or
+ * remote animators (running {@link android.window.IRemoteTransition}).
+ */
+ static final int ANIMATING_REASON_REMOTE_ANIMATION = 1;
+ /** It is set for wakefulness transition. */
+ static final int ANIMATING_REASON_WAKEFULNESS_CHANGE = 1 << 1;
+ /** Whether the legacy {@link RecentsAnimation} is running. */
+ static final int ANIMATING_REASON_LEGACY_RECENT_ANIMATION = 1 << 2;
- /** Whether our process is currently running a {@link IRemoteAnimationRunner} */
- private boolean mRunningRemoteAnimation;
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ ANIMATING_REASON_REMOTE_ANIMATION,
+ ANIMATING_REASON_WAKEFULNESS_CHANGE,
+ ANIMATING_REASON_LEGACY_RECENT_ANIMATION,
+ })
+ @interface AnimatingReason {}
+
+ /**
+ * Non-zero if this process is currently running an important animation. This should be never
+ * set for system server.
+ */
+ @AnimatingReason
+ private int mAnimatingReasons;
// The bits used for mActivityStateFlags.
private static final int ACTIVITY_STATE_FLAG_IS_VISIBLE = 1 << 16;
@@ -1847,30 +1868,45 @@
}
void setRunningRecentsAnimation(boolean running) {
- if (mRunningRecentsAnimation == running) {
- return;
+ if (running) {
+ addAnimatingReason(ANIMATING_REASON_LEGACY_RECENT_ANIMATION);
+ } else {
+ removeAnimatingReason(ANIMATING_REASON_LEGACY_RECENT_ANIMATION);
}
- mRunningRecentsAnimation = running;
- updateRunningRemoteOrRecentsAnimation();
}
void setRunningRemoteAnimation(boolean running) {
- if (mRunningRemoteAnimation == running) {
- return;
+ if (running) {
+ addAnimatingReason(ANIMATING_REASON_REMOTE_ANIMATION);
+ } else {
+ removeAnimatingReason(ANIMATING_REASON_REMOTE_ANIMATION);
}
- mRunningRemoteAnimation = running;
- updateRunningRemoteOrRecentsAnimation();
}
- void updateRunningRemoteOrRecentsAnimation() {
+ void addAnimatingReason(@AnimatingReason int reason) {
+ final int prevReasons = mAnimatingReasons;
+ mAnimatingReasons |= reason;
+ if (prevReasons == 0) {
+ setAnimating(true);
+ }
+ }
+
+ void removeAnimatingReason(@AnimatingReason int reason) {
+ final int prevReasons = mAnimatingReasons;
+ mAnimatingReasons &= ~reason;
+ if (prevReasons != 0 && mAnimatingReasons == 0) {
+ setAnimating(false);
+ }
+ }
+
+ /** Applies the animating state to activity manager for updating process priority. */
+ private void setAnimating(boolean animating) {
// Posting on handler so WM lock isn't held when we call into AM.
- mAtm.mH.sendMessage(PooledLambda.obtainMessage(
- WindowProcessListener::setRunningRemoteAnimation, mListener,
- isRunningRemoteTransition()));
+ mAtm.mH.post(() -> mListener.setRunningRemoteAnimation(animating));
}
boolean isRunningRemoteTransition() {
- return mRunningRecentsAnimation || mRunningRemoteAnimation;
+ return (mAnimatingReasons & ANIMATING_REASON_REMOTE_ANIMATION) != 0;
}
/** Adjusts scheduling group for animation. This method MUST NOT be called inside WM lock. */
@@ -1924,6 +1960,21 @@
pw.println(prefix + " mLastReportedConfiguration=" + (mHasCachedConfiguration
? ("(cached) " + mLastReportedConfiguration) : mLastReportedConfiguration));
+ final int animatingReasons = mAnimatingReasons;
+ if (animatingReasons != 0) {
+ pw.print(prefix + " mAnimatingReasons=");
+ if ((animatingReasons & ANIMATING_REASON_REMOTE_ANIMATION) != 0) {
+ pw.print("remote-animation|");
+ }
+ if ((animatingReasons & ANIMATING_REASON_WAKEFULNESS_CHANGE) != 0) {
+ pw.print("wakefulness|");
+ }
+ if ((animatingReasons & ANIMATING_REASON_LEGACY_RECENT_ANIMATION) != 0) {
+ pw.print("legacy-recents");
+ }
+ pw.println();
+ }
+
final int stateFlags = mActivityStateFlags;
if (stateFlags != ACTIVITY_STATE_FLAG_MASK_MIN_TASK_LAYER) {
pw.print(prefix + " mActivityStateFlags=");
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 8faeebc..c09e6a3 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -2412,7 +2412,7 @@
ProtoLog.v(WM_DEBUG_ADD_REMOVE,
"removeIfPossible: %s callers=%s", this, Debug.getCallers(5));
- final boolean startingWindow = mAttrs.type == TYPE_APPLICATION_STARTING;
+ final boolean startingWindow = mStartingData != null;
if (startingWindow) {
ProtoLog.d(WM_DEBUG_STARTING_WINDOW, "Starting window removed %s", this);
// Cancel the remove starting window animation on shell. The main window might changed
@@ -2426,6 +2426,7 @@
return false;
}, true);
}
+ mTransitionController.mTransitionTracer.logRemovingStartingWindow(mStartingData);
} else if (mAttrs.type == TYPE_BASE_APPLICATION
&& isSelfAnimating(0, ANIMATION_TYPE_STARTING_REVEAL)) {
// Cancel the remove starting window animation in case the binder dead before remove
diff --git a/services/people/java/com/android/server/people/prediction/ShareTargetPredictor.java b/services/people/java/com/android/server/people/prediction/ShareTargetPredictor.java
index b620407..f5360eb 100644
--- a/services/people/java/com/android/server/people/prediction/ShareTargetPredictor.java
+++ b/services/people/java/com/android/server/people/prediction/ShareTargetPredictor.java
@@ -30,6 +30,7 @@
import android.app.prediction.AppTarget;
import android.app.prediction.AppTargetEvent;
import android.app.prediction.AppTargetId;
+import android.content.ComponentName;
import android.content.Context;
import android.content.IntentFilter;
import android.content.pm.ShortcutInfo;
@@ -39,6 +40,7 @@
import android.util.Log;
import android.util.Slog;
+import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.ChooserActivity;
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
@@ -63,6 +65,7 @@
private static final String REMOTE_APP_PREDICTOR_KEY = "remote_app_predictor";
private final IntentFilter mIntentFilter;
private final AppPredictor mRemoteAppPredictor;
+ @Nullable private final String mChooserActivity;
ShareTargetPredictor(@NonNull AppPredictionContext predictionContext,
@NonNull Consumer<List<AppTarget>> updatePredictionsMethod,
@@ -81,6 +84,9 @@
} else {
mRemoteAppPredictor = null;
}
+ ComponentName component = ComponentName.unflattenFromString(
+ context.getResources().getString(R.string.config_chooserActivity));
+ mChooserActivity = (component == null) ? null : component.getShortClassName();
}
/** Reports chosen history of direct/app share targets. */
@@ -138,7 +144,7 @@
SharesheetModelScorer.computeScoreForAppShare(shareTargets,
getShareEventType(mIntentFilter), getPredictionContext().getPredictedTargetCount(),
System.currentTimeMillis(), getDataManager(),
- mCallingUserId);
+ mCallingUserId, mChooserActivity);
Collections.sort(shareTargets, (t1, t2) -> -Float.compare(t1.getScore(), t2.getScore()));
List<AppTarget> appTargetList = new ArrayList<>();
for (ShareTarget shareTarget : shareTargets) {
diff --git a/services/people/java/com/android/server/people/prediction/SharesheetModelScorer.java b/services/people/java/com/android/server/people/prediction/SharesheetModelScorer.java
index c77843c..b2f1e21 100644
--- a/services/people/java/com/android/server/people/prediction/SharesheetModelScorer.java
+++ b/services/people/java/com/android/server/people/prediction/SharesheetModelScorer.java
@@ -26,7 +26,6 @@
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.app.ChooserActivity;
import com.android.server.people.data.AppUsageStatsData;
import com.android.server.people.data.DataManager;
import com.android.server.people.data.Event;
@@ -55,8 +54,6 @@
private static final float FREQUENTLY_USED_APP_SCORE_INITIAL_DECAY = 0.3F;
@VisibleForTesting
static final float FOREGROUND_APP_WEIGHT = 0F;
- @VisibleForTesting
- static final String CHOOSER_ACTIVITY = ChooserActivity.class.getSimpleName();
// Keep constructor private to avoid class being instantiated.
private SharesheetModelScorer() {
@@ -169,13 +166,14 @@
*/
static void computeScoreForAppShare(List<ShareTargetPredictor.ShareTarget> shareTargets,
int shareEventType, int targetsLimit, long now, @NonNull DataManager dataManager,
- @UserIdInt int callingUserId) {
+ @UserIdInt int callingUserId, @Nullable String chooserActivity) {
computeScore(shareTargets, shareEventType, now);
- postProcess(shareTargets, targetsLimit, dataManager, callingUserId);
+ postProcess(shareTargets, targetsLimit, dataManager, callingUserId, chooserActivity);
}
private static void postProcess(List<ShareTargetPredictor.ShareTarget> shareTargets,
- int targetsLimit, @NonNull DataManager dataManager, @UserIdInt int callingUserId) {
+ int targetsLimit, @NonNull DataManager dataManager, @UserIdInt int callingUserId,
+ @Nullable String chooserActivity) {
// Populates a map which key is package name and value is list of shareTargets descended
// on total score.
Map<String, List<ShareTargetPredictor.ShareTarget>> shareTargetMap = new ArrayMap<>();
@@ -192,7 +190,7 @@
}
targetsList.add(index, shareTarget);
}
- promoteForegroundApp(shareTargetMap, dataManager, callingUserId);
+ promoteForegroundApp(shareTargetMap, dataManager, callingUserId, chooserActivity);
promoteMostChosenAndFrequentlyUsedApps(shareTargetMap, targetsLimit, dataManager,
callingUserId);
}
@@ -272,9 +270,10 @@
*/
private static void promoteForegroundApp(
Map<String, List<ShareTargetPredictor.ShareTarget>> shareTargetMap,
- @NonNull DataManager dataManager, @UserIdInt int callingUserId) {
+ @NonNull DataManager dataManager, @UserIdInt int callingUserId,
+ @Nullable String chooserActivity) {
String sharingForegroundApp = findSharingForegroundApp(shareTargetMap, dataManager,
- callingUserId);
+ callingUserId, chooserActivity);
if (sharingForegroundApp != null) {
ShareTargetPredictor.ShareTarget target = shareTargetMap.get(sharingForegroundApp).get(
0);
@@ -297,7 +296,8 @@
@Nullable
private static String findSharingForegroundApp(
Map<String, List<ShareTargetPredictor.ShareTarget>> shareTargetMap,
- @NonNull DataManager dataManager, @UserIdInt int callingUserId) {
+ @NonNull DataManager dataManager, @UserIdInt int callingUserId,
+ @Nullable String chooserActivity) {
String sharingForegroundApp = null;
long now = System.currentTimeMillis();
List<UsageEvents.Event> events = dataManager.queryAppMovingToForegroundEvents(
@@ -306,8 +306,8 @@
for (int i = events.size() - 1; i >= 0; i--) {
String className = events.get(i).getClassName();
String packageName = events.get(i).getPackageName();
- if (packageName == null || (className != null && className.contains(CHOOSER_ACTIVITY))
- || packageName.contains(CHOOSER_ACTIVITY)) {
+ if (packageName == null || (className != null && chooserActivity != null
+ && className.contains(chooserActivity))) {
continue;
}
if (sourceApp == null) {
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessLowPowerModeModifierTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessLowPowerModeModifierTest.java
new file mode 100644
index 0000000..266f5c1
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessLowPowerModeModifierTest.java
@@ -0,0 +1,126 @@
+/*
+ * 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 android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.display.DisplayBrightnessState;
+import com.android.server.display.brightness.BrightnessReason;
+
+import org.junit.Before;
+import org.junit.Test;
+
+@SmallTest
+public class BrightnessLowPowerModeModifierTest {
+ private static final float FLOAT_TOLERANCE = 0.001f;
+ private static final float DEFAULT_BRIGHTNESS = 0.5f;
+ private static final float LOW_POWER_BRIGHTNESS_FACTOR = 0.8f;
+ private static final float EXPECTED_LOW_POWER_BRIGHTNESS =
+ DEFAULT_BRIGHTNESS * LOW_POWER_BRIGHTNESS_FACTOR;
+ private final DisplayPowerRequest mRequest = new DisplayPowerRequest();
+ private final DisplayBrightnessState.Builder mBuilder = prepareBuilder();
+ private BrightnessLowPowerModeModifier mClamper;
+
+ @Before
+ public void setUp() {
+ mClamper = new BrightnessLowPowerModeModifier();
+ mRequest.screenLowPowerBrightnessFactor = LOW_POWER_BRIGHTNESS_FACTOR;
+ mRequest.lowPowerMode = true;
+ }
+
+ @Test
+ public void testApply_lowPowerModeOff() {
+ mRequest.lowPowerMode = false;
+
+ mClamper.apply(mRequest, mBuilder);
+
+ assertEquals(DEFAULT_BRIGHTNESS, mBuilder.getBrightness(), FLOAT_TOLERANCE);
+ assertEquals(0, mBuilder.getBrightnessReason().getModifier());
+ assertTrue(mBuilder.isSlowChange());
+ }
+
+ @Test
+ public void testApply_lowPowerModeOn() {
+ mClamper.apply(mRequest, mBuilder);
+
+ assertEquals(EXPECTED_LOW_POWER_BRIGHTNESS, mBuilder.getBrightness(), FLOAT_TOLERANCE);
+ assertEquals(BrightnessReason.MODIFIER_LOW_POWER,
+ mBuilder.getBrightnessReason().getModifier());
+ assertFalse(mBuilder.isSlowChange());
+ }
+
+ @Test
+ public void testApply_lowPowerModeOnAndLowPowerBrightnessFactorHigh() {
+ mRequest.screenLowPowerBrightnessFactor = 1.1f;
+
+ mClamper.apply(mRequest, mBuilder);
+
+ assertEquals(DEFAULT_BRIGHTNESS, mBuilder.getBrightness(), FLOAT_TOLERANCE);
+ assertEquals(BrightnessReason.MODIFIER_LOW_POWER,
+ mBuilder.getBrightnessReason().getModifier());
+ assertFalse(mBuilder.isSlowChange());
+ }
+
+ @Test
+ public void testApply_lowPowerModeOnAndMinBrightness() {
+ mBuilder.setBrightness(0.0f);
+ mClamper.apply(mRequest, mBuilder);
+
+ assertEquals(0.0f, mBuilder.getBrightness(), FLOAT_TOLERANCE);
+ assertEquals(0, mBuilder.getBrightnessReason().getModifier());
+ assertFalse(mBuilder.isSlowChange());
+ }
+
+ @Test
+ public void testApply_lowPowerModeOnAndLowPowerAlreadyApplied() {
+ mClamper.apply(mRequest, mBuilder);
+ DisplayBrightnessState.Builder builder = prepareBuilder();
+
+ mClamper.apply(mRequest, builder);
+
+ assertEquals(EXPECTED_LOW_POWER_BRIGHTNESS, builder.getBrightness(), FLOAT_TOLERANCE);
+ assertEquals(BrightnessReason.MODIFIER_LOW_POWER,
+ builder.getBrightnessReason().getModifier());
+ assertTrue(builder.isSlowChange());
+ }
+
+ @Test
+ public void testApply_lowPowerModeOffAfterLowPowerOn() {
+ mClamper.apply(mRequest, mBuilder);
+ mRequest.lowPowerMode = false;
+ DisplayBrightnessState.Builder builder = prepareBuilder();
+
+ mClamper.apply(mRequest, builder);
+
+ assertEquals(DEFAULT_BRIGHTNESS, builder.getBrightness(), FLOAT_TOLERANCE);
+ assertEquals(0, builder.getBrightnessReason().getModifier());
+ assertFalse(builder.isSlowChange());
+ }
+
+ private DisplayBrightnessState.Builder prepareBuilder() {
+ DisplayBrightnessState.Builder builder = DisplayBrightnessState.builder();
+ builder.setBrightness(DEFAULT_BRIGHTNESS);
+ builder.setIsSlowChange(true);
+ return builder;
+ }
+}
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 0cfddd3..769be17 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
@@ -351,6 +351,8 @@
assertEquals(startFingerprintNow ? BiometricSensor.STATE_AUTHENTICATING
: BiometricSensor.STATE_COOKIE_RETURNED,
session.mPreAuthInfo.eligibleSensors.get(fingerprintSensorId).getSensorState());
+ verify(mBiometricContext).updateContext((OperationContextExt) anyObject(),
+ eq(session.isCrypto()));
// start fingerprint sensor if it was delayed
if (!startFingerprintNow) {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsCollectorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsCollectorTest.java
new file mode 100644
index 0000000..99d66c5
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsCollectorTest.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.biometrics;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.res.Resources;
+
+import com.android.internal.R;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+public class AuthenticationStatsCollectorTest {
+
+ private AuthenticationStatsCollector mAuthenticationStatsCollector;
+ private static final float FRR_THRESHOLD = 0.2f;
+ private static final int USER_ID_1 = 1;
+
+ @Mock
+ private Context mContext;
+ @Mock
+ private Resources mResources;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ when(mContext.getResources()).thenReturn(mResources);
+ when(mResources.getFraction(R.fraction.config_biometricNotificationFrrThreshold, 1, 1))
+ .thenReturn(FRR_THRESHOLD);
+
+ mAuthenticationStatsCollector = new AuthenticationStatsCollector(mContext,
+ 0 /* modality */);
+ }
+
+
+ @Test
+ public void authenticate_authenticationSucceeded_mapShouldBeUpdated() {
+ // Assert that the user doesn't exist in the map initially.
+ assertNull(mAuthenticationStatsCollector.getAuthenticationStatsForUser(USER_ID_1));
+
+ mAuthenticationStatsCollector.authenticate(USER_ID_1, true /* authenticated*/);
+
+ AuthenticationStats authenticationStats =
+ mAuthenticationStatsCollector.getAuthenticationStatsForUser(USER_ID_1);
+ assertEquals(USER_ID_1, authenticationStats.getUserId());
+ assertEquals(1, authenticationStats.getTotalAttempts());
+ assertEquals(0, authenticationStats.getRejectedAttempts());
+ assertEquals(0, authenticationStats.getEnrollmentNotifications());
+ }
+
+ @Test
+ public void authenticate_authenticationFailed_mapShouldBeUpdated() {
+ // Assert that the user doesn't exist in the map initially.
+ assertNull(mAuthenticationStatsCollector.getAuthenticationStatsForUser(USER_ID_1));
+
+ mAuthenticationStatsCollector.authenticate(USER_ID_1, false /* authenticated*/);
+
+ AuthenticationStats authenticationStats =
+ mAuthenticationStatsCollector.getAuthenticationStatsForUser(USER_ID_1);
+ assertEquals(USER_ID_1, authenticationStats.getUserId());
+ assertEquals(1, authenticationStats.getTotalAttempts());
+ assertEquals(1, authenticationStats.getRejectedAttempts());
+ assertEquals(0, authenticationStats.getEnrollmentNotifications());
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsTest.java
new file mode 100644
index 0000000..e8e72cb
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsTest.java
@@ -0,0 +1,42 @@
+/*
+ * 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 org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+
+public class AuthenticationStatsTest {
+
+ @Test
+ public void authenticate_statsShouldBeUpdated() {
+ AuthenticationStats authenticationStats =
+ new AuthenticationStats(1 /* userId */ , 0 /* totalAttempts */,
+ 0 /* rejectedAttempts */, 0 /* enrollmentNotifications */,
+ 0 /* modality */);
+
+ authenticationStats.authenticate(true /* authenticated */);
+
+ assertEquals(authenticationStats.getTotalAttempts(), 1);
+ assertEquals(authenticationStats.getRejectedAttempts(), 0);
+
+ authenticationStats.authenticate(false /* authenticated */);
+
+ assertEquals(authenticationStats.getTotalAttempts(), 2);
+ assertEquals(authenticationStats.getRejectedAttempts(), 1);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricLoggerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricLoggerTest.java
index 612f717..a508718 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricLoggerTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricLoggerTest.java
@@ -39,6 +39,7 @@
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
+import com.android.server.biometrics.AuthenticationStatsCollector;
import com.android.server.biometrics.sensors.BaseClientMonitor;
import org.junit.Before;
@@ -65,6 +66,8 @@
@Mock
private BiometricFrameworkStatsLogger mSink;
@Mock
+ private AuthenticationStatsCollector mAuthenticationStatsCollector;
+ @Mock
private SensorManager mSensorManager;
@Mock
private BaseClientMonitor mClient;
@@ -87,7 +90,8 @@
}
private BiometricLogger createLogger(int statsModality, int statsAction, int statsClient) {
- return new BiometricLogger(statsModality, statsAction, statsClient, mSink, mSensorManager);
+ return new BiometricLogger(statsModality, statsAction, statsClient, mSink,
+ mAuthenticationStatsCollector, mSensorManager);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
index d1d6e9d..f43120d 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
@@ -27,6 +27,7 @@
import static org.mockito.Mockito.when;
import android.content.Context;
+import android.content.res.Resources;
import android.hardware.biometrics.common.CommonProps;
import android.hardware.biometrics.face.IFace;
import android.hardware.biometrics.face.ISession;
@@ -39,6 +40,7 @@
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
+import com.android.internal.R;
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.sensors.BaseClientMonitor;
import com.android.server.biometrics.sensors.BiometricScheduler;
@@ -59,11 +61,15 @@
private static final String TAG = "FaceProviderTest";
+ private static final float FRR_THRESHOLD = 0.2f;
+
@Mock
private Context mContext;
@Mock
private UserManager mUserManager;
@Mock
+ private Resources mResources;
+ @Mock
private IFace mDaemon;
@Mock
private BiometricContext mBiometricContext;
@@ -86,6 +92,10 @@
when(mUserManager.getAliveUsers()).thenReturn(new ArrayList<>());
when(mDaemon.createSession(anyInt(), anyInt(), any())).thenReturn(mock(ISession.class));
+ when(mContext.getResources()).thenReturn(mResources);
+ when(mResources.getFraction(R.fraction.config_biometricNotificationFrrThreshold, 1, 1))
+ .thenReturn(FRR_THRESHOLD);
+
final SensorProps sensor1 = new SensorProps();
sensor1.commonProps = new CommonProps();
sensor1.commonProps.sensorId = 0;
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java
index d174533..e558c4d 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java
@@ -26,6 +26,7 @@
import static org.mockito.Mockito.when;
import android.content.Context;
+import android.content.res.Resources;
import android.hardware.biometrics.ComponentInfoInternal;
import android.hardware.biometrics.SensorProperties;
import android.hardware.face.FaceSensorProperties;
@@ -41,6 +42,7 @@
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
+import com.android.internal.R;
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.sensors.BiometricScheduler;
import com.android.server.biometrics.sensors.BiometricStateCallback;
@@ -65,12 +67,15 @@
private static final String TAG = "Face10Test";
private static final int SENSOR_ID = 1;
private static final int USER_ID = 20;
+ private static final float FRR_THRESHOLD = 0.2f;
@Mock
private Context mContext;
@Mock
private UserManager mUserManager;
@Mock
+ private Resources mResources;
+ @Mock
private BiometricScheduler mScheduler;
@Mock
private BiometricContext mBiometricContext;
@@ -93,6 +98,10 @@
when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
when(mUserManager.getAliveUsers()).thenReturn(new ArrayList<>());
+ when(mContext.getResources()).thenReturn(mResources);
+ when(mResources.getFraction(R.fraction.config_biometricNotificationFrrThreshold, 1, 1))
+ .thenReturn(FRR_THRESHOLD);
+
mLockoutResetDispatcher = new LockoutResetDispatcher(mContext);
final int maxEnrollmentsPerUser = 1;
diff --git a/services/tests/servicestests/src/com/android/server/people/prediction/SharesheetModelScorerTest.java b/services/tests/servicestests/src/com/android/server/people/prediction/SharesheetModelScorerTest.java
index 45fff48..2cd9198 100644
--- a/services/tests/servicestests/src/com/android/server/people/prediction/SharesheetModelScorerTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/prediction/SharesheetModelScorerTest.java
@@ -57,6 +57,7 @@
private static final String PACKAGE_3 = "pkg3";
private static final String CLASS_1 = "cls1";
private static final String CLASS_2 = "cls2";
+ private static final String CHOOSER_ACTIVITY = "ChooserActivity";
private static final double DELTA = 1e-6;
private static final long NOW = System.currentTimeMillis();
private static final Range<Long> WITHIN_ONE_DAY = new Range(
@@ -246,7 +247,7 @@
SharesheetModelScorer.computeScoreForAppShare(
List.of(mShareTarget1, mShareTarget2, mShareTarget3, mShareTarget4, mShareTarget5,
mShareTarget6),
- Event.TYPE_SHARE_TEXT, 20, NOW, mDataManager, USER_ID);
+ Event.TYPE_SHARE_TEXT, 20, NOW, mDataManager, USER_ID, CHOOSER_ACTIVITY);
// Verification
assertEquals(0.514f, mShareTarget1.getScore(), DELTA);
@@ -278,7 +279,7 @@
SharesheetModelScorer.computeScoreForAppShare(
List.of(mShareTarget1, mShareTarget2, mShareTarget3, mShareTarget4, mShareTarget5,
mShareTarget6),
- Event.TYPE_SHARE_TEXT, 20, NOW, mDataManager, USER_ID);
+ Event.TYPE_SHARE_TEXT, 20, NOW, mDataManager, USER_ID, CHOOSER_ACTIVITY);
verify(mDataManager, times(1)).queryAppUsageStats(anyInt(), anyLong(), anyLong(),
anySet());
@@ -311,7 +312,7 @@
SharesheetModelScorer.computeScoreForAppShare(
List.of(mShareTarget1, mShareTarget2, mShareTarget3, mShareTarget4, mShareTarget5,
mShareTarget6),
- Event.TYPE_SHARE_TEXT, 20, NOW, mDataManager, USER_ID);
+ Event.TYPE_SHARE_TEXT, 20, NOW, mDataManager, USER_ID, CHOOSER_ACTIVITY);
verify(mDataManager, times(1)).queryAppUsageStats(anyInt(), anyLong(), anyLong(),
anySet());
@@ -349,7 +350,7 @@
SharesheetModelScorer.computeScoreForAppShare(
List.of(mShareTarget1, mShareTarget2, mShareTarget3, mShareTarget4, mShareTarget5,
mShareTarget6),
- Event.TYPE_SHARE_TEXT, 4, NOW, mDataManager, USER_ID);
+ Event.TYPE_SHARE_TEXT, 4, NOW, mDataManager, USER_ID, CHOOSER_ACTIVITY);
verify(mDataManager, never()).queryAppUsageStats(anyInt(), anyLong(), anyLong(),
anySet());
@@ -377,7 +378,7 @@
anyLong())).thenReturn(
List.of(createUsageEvent(PACKAGE_2),
createUsageEvent(PACKAGE_3),
- createUsageEvent(SharesheetModelScorer.CHOOSER_ACTIVITY),
+ createUsageEvent(CHOOSER_ACTIVITY),
createUsageEvent(PACKAGE_3),
createUsageEvent(PACKAGE_3))
);
@@ -385,7 +386,7 @@
SharesheetModelScorer.computeScoreForAppShare(
List.of(mShareTarget1, mShareTarget2, mShareTarget3, mShareTarget4, mShareTarget5,
mShareTarget6),
- Event.TYPE_SHARE_TEXT, 20, NOW, mDataManager, USER_ID);
+ Event.TYPE_SHARE_TEXT, 20, NOW, mDataManager, USER_ID, CHOOSER_ACTIVITY);
verify(mDataManager, times(1)).queryAppMovingToForegroundEvents(anyInt(), anyLong(),
anyLong());
@@ -413,7 +414,7 @@
anyLong())).thenReturn(
List.of(createUsageEvent(PACKAGE_3),
createUsageEvent(PACKAGE_3),
- createUsageEvent(SharesheetModelScorer.CHOOSER_ACTIVITY),
+ createUsageEvent(CHOOSER_ACTIVITY),
createUsageEvent(PACKAGE_3),
createUsageEvent(PACKAGE_3))
);
@@ -421,7 +422,7 @@
SharesheetModelScorer.computeScoreForAppShare(
List.of(mShareTarget1, mShareTarget2, mShareTarget3, mShareTarget4, mShareTarget5,
mShareTarget6),
- Event.TYPE_SHARE_TEXT, 20, NOW, mDataManager, USER_ID);
+ Event.TYPE_SHARE_TEXT, 20, NOW, mDataManager, USER_ID, CHOOSER_ACTIVITY);
verify(mDataManager, times(1)).queryAppMovingToForegroundEvents(anyInt(), anyLong(),
anyLong());
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index c242554..81d939f 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -600,7 +600,7 @@
NotificationChannel channel2 =
new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
channel2.setDescription("descriptions for all");
- channel2.setSound(SOUND_URI, mAudioAttributes);
+ channel2.setSound(CANONICAL_SOUND_URI, mAudioAttributes);
channel2.enableLights(true);
channel2.setBypassDnd(true);
channel2.setLockscreenVisibility(VISIBILITY_SECRET);
@@ -1374,6 +1374,8 @@
.when(mTestIContentProvider).uncanonicalize(any(), eq(CANONICAL_SOUND_URI));
doReturn(localUri)
.when(mTestIContentProvider).uncanonicalize(any(), eq(canonicalBasedOnLocal));
+ doReturn(canonicalBasedOnLocal)
+ .when(mTestIContentProvider).canonicalize(any(), eq(localUri));
NotificationChannel channel =
new NotificationChannel("id", "name", IMPORTANCE_LOW);
@@ -1387,7 +1389,7 @@
NotificationChannel actualChannel = mHelper.getNotificationChannel(
PKG_N_MR1, UID_N_MR1, channel.getId(), false);
- assertEquals(localUri, actualChannel.getSound());
+ assertEquals(canonicalBasedOnLocal, actualChannel.getSound());
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivitySnapshotControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivitySnapshotControllerTests.java
index 0eca8c9..98f1843 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivitySnapshotControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivitySnapshotControllerTests.java
@@ -18,6 +18,9 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+
import static org.junit.Assert.assertEquals;
import android.platform.test.annotations.Presubmit;
@@ -28,6 +31,8 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.ArrayList;
+
/**
* Test class for {@link ActivitySnapshotController}.
*
@@ -42,13 +47,13 @@
private ActivitySnapshotController mActivitySnapshotController;
@Before
public void setUp() throws Exception {
+ spyOn(mWm.mSnapshotController.mActivitySnapshotController);
mActivitySnapshotController = mWm.mSnapshotController.mActivitySnapshotController;
+ doReturn(false).when(mActivitySnapshotController).shouldDisableSnapshots();
mActivitySnapshotController.resetTmpFields();
}
@Test
public void testOpenActivityTransition() {
- final SnapshotController.TransitionState transitionState =
- new SnapshotController.TransitionState();
final Task task = createTask(mDisplayContent);
// note for createAppWindow: the new child is added at index 0
final WindowState openingWindow = createAppWindow(task,
@@ -59,14 +64,12 @@
"closingWindow");
closingWindow.mActivityRecord.commitVisibility(
false /* visible */, true /* performLayout */);
- transitionState.addParticipant(closingWindow.mActivityRecord, false);
- transitionState.addParticipant(openingWindow.mActivityRecord, true);
- mActivitySnapshotController.handleOpenActivityTransition(transitionState);
+ final ArrayList<WindowContainer> windows = new ArrayList<>();
+ windows.add(openingWindow.mActivityRecord);
+ windows.add(closingWindow.mActivityRecord);
+ mActivitySnapshotController.handleTransitionFinish(windows);
- assertEquals(1, mActivitySnapshotController.mPendingCaptureActivity.size());
assertEquals(0, mActivitySnapshotController.mPendingRemoveActivity.size());
- assertEquals(closingWindow.mActivityRecord,
- mActivitySnapshotController.mPendingCaptureActivity.valueAt(0));
mActivitySnapshotController.resetTmpFields();
// simulate three activity
@@ -74,19 +77,15 @@
"belowClose");
belowClose.mActivityRecord.commitVisibility(
false /* visible */, true /* performLayout */);
- mActivitySnapshotController.handleOpenActivityTransition(transitionState);
- assertEquals(1, mActivitySnapshotController.mPendingCaptureActivity.size());
+ windows.add(belowClose.mActivityRecord);
+ mActivitySnapshotController.handleTransitionFinish(windows);
assertEquals(1, mActivitySnapshotController.mPendingRemoveActivity.size());
- assertEquals(closingWindow.mActivityRecord,
- mActivitySnapshotController.mPendingCaptureActivity.valueAt(0));
assertEquals(belowClose.mActivityRecord,
mActivitySnapshotController.mPendingRemoveActivity.valueAt(0));
}
@Test
public void testCloseActivityTransition() {
- final SnapshotController.TransitionState transitionState =
- new SnapshotController.TransitionState();
final Task task = createTask(mDisplayContent);
// note for createAppWindow: the new child is added at index 0
final WindowState closingWindow = createAppWindow(task, ACTIVITY_TYPE_STANDARD,
@@ -97,10 +96,10 @@
ACTIVITY_TYPE_STANDARD, "openingWindow");
openingWindow.mActivityRecord.commitVisibility(
true /* visible */, true /* performLayout */);
- transitionState.addParticipant(closingWindow.mActivityRecord, false);
- transitionState.addParticipant(openingWindow.mActivityRecord, true);
- mActivitySnapshotController.handleCloseActivityTransition(transitionState);
- assertEquals(0, mActivitySnapshotController.mPendingCaptureActivity.size());
+ final ArrayList<WindowContainer> windows = new ArrayList<>();
+ windows.add(openingWindow.mActivityRecord);
+ windows.add(closingWindow.mActivityRecord);
+ mActivitySnapshotController.handleTransitionFinish(windows);
assertEquals(1, mActivitySnapshotController.mPendingDeleteActivity.size());
assertEquals(openingWindow.mActivityRecord,
mActivitySnapshotController.mPendingDeleteActivity.valueAt(0));
@@ -111,8 +110,8 @@
"belowOpen");
belowOpen.mActivityRecord.commitVisibility(
false /* visible */, true /* performLayout */);
- mActivitySnapshotController.handleCloseActivityTransition(transitionState);
- assertEquals(0, mActivitySnapshotController.mPendingCaptureActivity.size());
+ windows.add(belowOpen.mActivityRecord);
+ mActivitySnapshotController.handleTransitionFinish(windows);
assertEquals(1, mActivitySnapshotController.mPendingDeleteActivity.size());
assertEquals(1, mActivitySnapshotController.mPendingLoadActivity.size());
assertEquals(openingWindow.mActivityRecord,
@@ -123,10 +122,6 @@
@Test
public void testTaskTransition() {
- final SnapshotController.TransitionState taskCloseTransition =
- new SnapshotController.TransitionState();
- final SnapshotController.TransitionState taskOpenTransition =
- new SnapshotController.TransitionState();
final Task closeTask = createTask(mDisplayContent);
// note for createAppWindow: the new child is added at index 0
final WindowState closingWindow = createAppWindow(closeTask, ACTIVITY_TYPE_STANDARD,
@@ -147,10 +142,10 @@
"openingWindowBelow");
openingWindowBelow.mActivityRecord.commitVisibility(
false /* visible */, true /* performLayout */);
- taskCloseTransition.addParticipant(closeTask, false);
- taskOpenTransition.addParticipant(openTask, true);
- mActivitySnapshotController.handleCloseTaskTransition(taskCloseTransition);
- mActivitySnapshotController.handleOpenTaskTransition(taskOpenTransition);
+ final ArrayList<WindowContainer> windows = new ArrayList<>();
+ windows.add(closeTask);
+ windows.add(openTask);
+ mActivitySnapshotController.handleTransitionFinish(windows);
assertEquals(1, mActivitySnapshotController.mPendingRemoveActivity.size());
assertEquals(closingWindowBelow.mActivityRecord,
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppSnapshotControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/AppSnapshotControllerTests.java
deleted file mode 100644
index 83af1814..0000000
--- a/services/tests/wmtests/src/com/android/server/wm/AppSnapshotControllerTests.java
+++ /dev/null
@@ -1,171 +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.wm;
-
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
-
-import static com.android.server.wm.SnapshotController.ACTIVITY_CLOSE;
-import static com.android.server.wm.SnapshotController.ACTIVITY_OPEN;
-import static com.android.server.wm.SnapshotController.TASK_CLOSE;
-import static com.android.server.wm.SnapshotController.TASK_OPEN;
-
-import static org.junit.Assert.assertTrue;
-
-import android.platform.test.annotations.Presubmit;
-import android.util.ArraySet;
-
-import androidx.test.filters.SmallTest;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-
-/**
- * Test class for {@link SnapshotController}.
- *
- * Build/Install/Run:
- * * atest WmTests:AppSnapshotControllerTests
- */
-@SmallTest
-@Presubmit
-@RunWith(WindowTestRunner.class)
-public class AppSnapshotControllerTests extends WindowTestsBase {
- final ArraySet<ActivityRecord> mClosingApps = new ArraySet<>();
- final ArraySet<ActivityRecord> mOpeningApps = new ArraySet<>();
-
- final TransitionMonitor mOpenActivityMonitor = new TransitionMonitor();
- final TransitionMonitor mCloseActivityMonitor = new TransitionMonitor();
- final TransitionMonitor mOpenTaskMonitor = new TransitionMonitor();
- final TransitionMonitor mCloseTaskMonitor = new TransitionMonitor();
-
- @Before
- public void setUp() throws Exception {
- resetStatus();
- mWm.mSnapshotController.registerTransitionStateConsumer(
- ACTIVITY_CLOSE, mCloseActivityMonitor::handleTransition);
- mWm.mSnapshotController.registerTransitionStateConsumer(
- ACTIVITY_OPEN, mOpenActivityMonitor::handleTransition);
- mWm.mSnapshotController.registerTransitionStateConsumer(
- TASK_CLOSE, mCloseTaskMonitor::handleTransition);
- mWm.mSnapshotController.registerTransitionStateConsumer(
- TASK_OPEN, mOpenTaskMonitor::handleTransition);
- }
-
- @After
- public void tearDown() throws Exception {
- mWm.mSnapshotController.unregisterTransitionStateConsumer(
- ACTIVITY_CLOSE, mCloseActivityMonitor::handleTransition);
- mWm.mSnapshotController.unregisterTransitionStateConsumer(
- ACTIVITY_OPEN, mOpenActivityMonitor::handleTransition);
- mWm.mSnapshotController.unregisterTransitionStateConsumer(
- TASK_CLOSE, mCloseTaskMonitor::handleTransition);
- mWm.mSnapshotController.unregisterTransitionStateConsumer(
- TASK_OPEN, mOpenTaskMonitor::handleTransition);
- }
-
- private static class TransitionMonitor {
- private final ArraySet<WindowContainer> mOpenParticipant = new ArraySet<>();
- private final ArraySet<WindowContainer> mCloseParticipant = new ArraySet<>();
- void handleTransition(SnapshotController.TransitionState<ActivityRecord> state) {
- mOpenParticipant.addAll(state.getParticipant(true /* open */));
- mCloseParticipant.addAll(state.getParticipant(false /* close */));
- }
- void reset() {
- mOpenParticipant.clear();
- mCloseParticipant.clear();
- }
- }
-
- private void resetStatus() {
- mClosingApps.clear();
- mOpeningApps.clear();
- mOpenActivityMonitor.reset();
- mCloseActivityMonitor.reset();
- mOpenTaskMonitor.reset();
- mCloseTaskMonitor.reset();
- }
-
- @Test
- public void testHandleAppTransition_openActivityTransition() {
- final Task task = createTask(mDisplayContent);
- // note for createAppWindow: the new child is added at index 0
- final WindowState openingWindow = createAppWindow(task,
- ACTIVITY_TYPE_STANDARD, "openingWindow");
- openingWindow.mActivityRecord.commitVisibility(
- true /* visible */, true /* performLayout */);
- final WindowState closingWindow = createAppWindow(task, ACTIVITY_TYPE_STANDARD,
- "closingWindow");
- closingWindow.mActivityRecord.commitVisibility(
- false /* visible */, true /* performLayout */);
- mClosingApps.add(closingWindow.mActivityRecord);
- mOpeningApps.add(openingWindow.mActivityRecord);
- mWm.mSnapshotController.handleAppTransition(mClosingApps, mOpeningApps);
- assertTrue(mOpenActivityMonitor.mCloseParticipant.contains(closingWindow.mActivityRecord));
- assertTrue(mOpenActivityMonitor.mOpenParticipant.contains(openingWindow.mActivityRecord));
- }
-
- @Test
- public void testHandleAppTransition_closeActivityTransition() {
- final Task task = createTask(mDisplayContent);
- // note for createAppWindow: the new child is added at index 0
- final WindowState closingWindow = createAppWindow(task, ACTIVITY_TYPE_STANDARD,
- "closingWindow");
- closingWindow.mActivityRecord.commitVisibility(
- false /* visible */, true /* performLayout */);
- final WindowState openingWindow = createAppWindow(task,
- ACTIVITY_TYPE_STANDARD, "openingWindow");
- openingWindow.mActivityRecord.commitVisibility(
- true /* visible */, true /* performLayout */);
- mClosingApps.add(closingWindow.mActivityRecord);
- mOpeningApps.add(openingWindow.mActivityRecord);
- mWm.mSnapshotController.handleAppTransition(mClosingApps, mOpeningApps);
- assertTrue(mCloseActivityMonitor.mCloseParticipant.contains(closingWindow.mActivityRecord));
- assertTrue(mCloseActivityMonitor.mOpenParticipant.contains(openingWindow.mActivityRecord));
- }
-
- @Test
- public void testHandleAppTransition_TaskTransition() {
- final Task closeTask = createTask(mDisplayContent);
- // note for createAppWindow: the new child is added at index 0
- final WindowState closingWindow = createAppWindow(closeTask, ACTIVITY_TYPE_STANDARD,
- "closingWindow");
- closingWindow.mActivityRecord.commitVisibility(
- false /* visible */, true /* performLayout */);
- final WindowState closingWindowBelow = createAppWindow(closeTask, ACTIVITY_TYPE_STANDARD,
- "closingWindowBelow");
- closingWindowBelow.mActivityRecord.commitVisibility(
- false /* visible */, true /* performLayout */);
-
- final Task openTask = createTask(mDisplayContent);
- final WindowState openingWindow = createAppWindow(openTask, ACTIVITY_TYPE_STANDARD,
- "openingWindow");
- openingWindow.mActivityRecord.commitVisibility(
- true /* visible */, true /* performLayout */);
- final WindowState openingWindowBelow = createAppWindow(openTask, ACTIVITY_TYPE_STANDARD,
- "openingWindowBelow");
- openingWindowBelow.mActivityRecord.commitVisibility(
- false /* visible */, true /* performLayout */);
-
- mClosingApps.add(closingWindow.mActivityRecord);
- mOpeningApps.add(openingWindow.mActivityRecord);
- mWm.mSnapshotController.handleAppTransition(mClosingApps, mOpeningApps);
- assertTrue(mCloseTaskMonitor.mCloseParticipant.contains(closeTask));
- assertTrue(mOpenTaskMonitor.mOpenParticipant.contains(openTask));
- }
-}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ContentRecordingControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/ContentRecordingControllerTests.java
index 52226c2..4473a31 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ContentRecordingControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ContentRecordingControllerTests.java
@@ -123,6 +123,7 @@
controller.setContentRecordingSessionLocked(mWaitingDisplaySession, mWm);
verify(mVirtualDisplayContent, atLeastOnce()).setContentRecordingSession(
mWaitingDisplaySession);
+ verify(mVirtualDisplayContent).updateRecording();
// WHEN updating the session on the same display, so no longer waiting to record.
ContentRecordingSession sessionUpdate = ContentRecordingSession.createTaskSession(
@@ -135,7 +136,7 @@
// THEN the session was accepted.
assertThat(resultingSession).isEqualTo(sessionUpdate);
verify(mVirtualDisplayContent, atLeastOnce()).setContentRecordingSession(sessionUpdate);
- verify(mVirtualDisplayContent).updateRecording();
+ verify(mVirtualDisplayContent, atLeastOnce()).updateRecording();
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index bdd178b..9cc4117 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -2054,6 +2054,17 @@
assertNotEquals(testPlayer.mLastReady.getChange(dcToken).getEndRotation(),
testPlayer.mLastReady.getChange(dcToken).getStartRotation());
testPlayer.finish();
+
+ // The AsyncRotationController should only exist if there is an ongoing rotation change.
+ dc.finishAsyncRotationIfPossible();
+ dc.setLastHasContent();
+ doReturn(dr.getRotation() + 1).when(dr).rotationForOrientation(anyInt(), anyInt());
+ dr.updateRotationUnchecked(true /* forceUpdate */);
+ assertNotNull(dc.getAsyncRotationController());
+ doReturn(dr.getRotation() - 1).when(dr).rotationForOrientation(anyInt(), anyInt());
+ dr.updateRotationUnchecked(true /* forceUpdate */);
+ assertNull("Cancel AsyncRotationController for the intermediate rotation changes 0->1->0",
+ dc.getAsyncRotationController());
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
index 72ab18d..2ad9fa0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
@@ -37,6 +37,8 @@
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_USER;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_3_2;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_FULLSCREEN;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.view.InsetsSource.FLAG_INSETS_ROUNDED_CORNER;
@@ -48,6 +50,8 @@
import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE;
import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE;
import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES;
+import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE;
+import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE;
import static android.view.WindowManager.PROPERTY_COMPAT_ENABLE_FAKE_FOCUS;
import static android.view.WindowManager.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION;
@@ -807,6 +811,108 @@
/* candidate */ SCREEN_ORIENTATION_PORTRAIT), SCREEN_ORIENTATION_PORTRAIT);
}
+ // shouldApplyUser...Override
+ @Test
+ public void testShouldApplyUserFullscreenOverride_trueProperty_returnsFalse() throws Exception {
+ mockThatProperty(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE,
+ /* value */ true);
+
+ mController = new LetterboxUiController(mWm, mActivity);
+ doReturn(false).when(mLetterboxConfiguration).isUserAppAspectRatioFullscreenEnabled();
+
+ assertFalse(mController.shouldApplyUserFullscreenOverride());
+ }
+
+ @Test
+ public void testShouldApplyUserFullscreenOverride_falseFullscreenProperty_returnsFalse()
+ throws Exception {
+ prepareActivityThatShouldApplyUserFullscreenOverride();
+ mockThatProperty(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE,
+ /* value */ false);
+
+ mController = new LetterboxUiController(mWm, mActivity);
+
+ assertFalse(mController.shouldApplyUserFullscreenOverride());
+ }
+
+ @Test
+ public void testShouldApplyUserFullscreenOverride_falseSettingsProperty_returnsFalse()
+ throws Exception {
+ prepareActivityThatShouldApplyUserFullscreenOverride();
+ mockThatProperty(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE, /* value */ false);
+
+ mController = new LetterboxUiController(mWm, mActivity);
+
+ assertFalse(mController.shouldApplyUserFullscreenOverride());
+ }
+
+ @Test
+ public void testShouldApplyUserFullscreenOverride_disabledIgnoreOrientationRequest() {
+ prepareActivityThatShouldApplyUserFullscreenOverride();
+ mDisplayContent.setIgnoreOrientationRequest(false);
+
+ assertFalse(mController.shouldApplyUserFullscreenOverride());
+ }
+
+ @Test
+ public void testShouldApplyUserFullscreenOverride_returnsTrue() {
+ prepareActivityThatShouldApplyUserFullscreenOverride();
+
+ assertTrue(mController.shouldApplyUserFullscreenOverride());
+ }
+
+ @Test
+ public void testShouldApplyUserMinAspectRatioOverride_falseProperty_returnsFalse()
+ throws Exception {
+ prepareActivityThatShouldApplyUserMinAspectRatioOverride();
+ mockThatProperty(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE, /* value */ false);
+
+ mController = new LetterboxUiController(mWm, mActivity);
+
+ assertFalse(mController.shouldApplyUserMinAspectRatioOverride());
+ }
+
+ @Test
+ public void testShouldApplyUserMinAspectRatioOverride_trueProperty_returnsFalse()
+ throws Exception {
+ doReturn(false).when(mLetterboxConfiguration).isUserAppAspectRatioSettingsEnabled();
+ mockThatProperty(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE, /* value */ true);
+
+ mController = new LetterboxUiController(mWm, mActivity);
+
+ assertFalse(mController.shouldApplyUserMinAspectRatioOverride());
+ }
+
+ @Test
+ public void testShouldApplyUserMinAspectRatioOverride_disabledIgnoreOrientationRequest() {
+ prepareActivityThatShouldApplyUserMinAspectRatioOverride();
+ mDisplayContent.setIgnoreOrientationRequest(false);
+
+ assertFalse(mController.shouldApplyUserMinAspectRatioOverride());
+ }
+
+ @Test
+ public void testShouldApplyUserMinAspectRatioOverride_returnsTrue() {
+ prepareActivityThatShouldApplyUserMinAspectRatioOverride();
+
+ assertTrue(mController.shouldApplyUserMinAspectRatioOverride());
+ }
+
+ private void prepareActivityThatShouldApplyUserMinAspectRatioOverride() {
+ spyOn(mController);
+ doReturn(true).when(mLetterboxConfiguration).isUserAppAspectRatioSettingsEnabled();
+ mDisplayContent.setIgnoreOrientationRequest(true);
+ doReturn(USER_MIN_ASPECT_RATIO_3_2).when(mController).getUserMinAspectRatioOverrideCode();
+ }
+
+ private void prepareActivityThatShouldApplyUserFullscreenOverride() {
+ spyOn(mController);
+ doReturn(true).when(mLetterboxConfiguration).isUserAppAspectRatioFullscreenEnabled();
+ mDisplayContent.setIgnoreOrientationRequest(true);
+ doReturn(USER_MIN_ASPECT_RATIO_FULLSCREEN).when(mController)
+ .getUserMinAspectRatioOverrideCode();
+ }
+
// shouldUseDisplayLandscapeNaturalOrientation
@Test
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 ca5d8fe..b4f1176 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -46,7 +46,6 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
-import static com.android.server.wm.SnapshotController.TASK_CLOSE;
import static com.android.server.wm.WindowContainer.POSITION_TOP;
import static org.junit.Assert.assertEquals;
@@ -1383,8 +1382,6 @@
@Test
public void testTransientLaunch() {
spyOn(mWm.mSnapshotController.mTaskSnapshotController);
- mWm.mSnapshotController.registerTransitionStateConsumer(TASK_CLOSE,
- mWm.mSnapshotController.mTaskSnapshotController::handleTaskClose);
final ArrayList<ActivityRecord> enteringAnimReports = new ArrayList<>();
final TransitionController controller = new TestTransitionController(mAtm) {
@Override
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
index cf83981..ebe40b0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
@@ -170,9 +170,13 @@
}
@Test
- public void testSetRunningRecentsAnimation() {
- mWpc.setRunningRecentsAnimation(true);
- mWpc.setRunningRecentsAnimation(false);
+ public void testSetAnimatingReason() {
+ mWpc.addAnimatingReason(WindowProcessController.ANIMATING_REASON_REMOTE_ANIMATION);
+ assertTrue(mWpc.isRunningRemoteTransition());
+ mWpc.addAnimatingReason(WindowProcessController.ANIMATING_REASON_WAKEFULNESS_CHANGE);
+ mWpc.removeAnimatingReason(WindowProcessController.ANIMATING_REASON_REMOTE_ANIMATION);
+ assertFalse(mWpc.isRunningRemoteTransition());
+ mWpc.removeAnimatingReason(WindowProcessController.ANIMATING_REASON_WAKEFULNESS_CHANGE);
waitHandlerIdle(mAtm.mH);
InOrder orderVerifier = Mockito.inOrder(mMockListener);
@@ -201,7 +205,7 @@
waitHandlerIdle(mAtm.mH);
InOrder orderVerifier = Mockito.inOrder(mMockListener);
- orderVerifier.verify(mMockListener, times(3)).setRunningRemoteAnimation(eq(true));
+ orderVerifier.verify(mMockListener, times(1)).setRunningRemoteAnimation(eq(true));
orderVerifier.verify(mMockListener, times(1)).setRunningRemoteAnimation(eq(false));
orderVerifier.verifyNoMoreInteractions();
}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
index ccc4ac2..58da4b43 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
@@ -368,29 +368,29 @@
/**
* This method is only used by VisualQueryDetector.
*/
- void startPerceivingLocked(IVisualQueryDetectionVoiceInteractionCallback callback) {
+ boolean startPerceivingLocked(IVisualQueryDetectionVoiceInteractionCallback callback) {
if (DEBUG) {
Slog.d(TAG, "startPerceivingLocked");
}
final VisualQueryDetectorSession session = getVisualQueryDetectorSessionLocked();
if (session == null) {
- return;
+ return false;
}
- session.startPerceivingLocked(callback);
+ return session.startPerceivingLocked(callback);
}
/**
* This method is only used by VisaulQueryDetector.
*/
- void stopPerceivingLocked() {
+ boolean stopPerceivingLocked() {
if (DEBUG) {
Slog.d(TAG, "stopPerceivingLocked");
}
final VisualQueryDetectorSession session = getVisualQueryDetectorSessionLocked();
if (session == null) {
- return;
+ return false;
}
- session.stopPerceivingLocked();
+ return session.stopPerceivingLocked();
}
public void startListeningFromExternalSourceLocked(
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VisualQueryDetectorSession.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VisualQueryDetectorSession.java
index 2e05e20..4720d27 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VisualQueryDetectorSession.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VisualQueryDetectorSession.java
@@ -95,7 +95,7 @@
}
@SuppressWarnings("GuardedBy")
- void startPerceivingLocked(IVisualQueryDetectionVoiceInteractionCallback callback) {
+ boolean startPerceivingLocked(IVisualQueryDetectionVoiceInteractionCallback callback) {
if (DEBUG) {
Slog.d(TAG, "startPerceivingLocked");
}
@@ -198,15 +198,16 @@
mQueryStreaming = false;
}
};
- mRemoteDetectionService.run(service -> service.detectWithVisualSignals(internalCallback));
+ return mRemoteDetectionService.run(
+ service -> service.detectWithVisualSignals(internalCallback));
}
@SuppressWarnings("GuardedBy")
- void stopPerceivingLocked() {
+ boolean stopPerceivingLocked() {
if (DEBUG) {
Slog.d(TAG, "stopPerceivingLocked");
}
- mRemoteDetectionService.run(ISandboxedDetectionService::stopDetection);
+ return mRemoteDetectionService.run(ISandboxedDetectionService::stopDetection);
}
@Override
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index 3574ef8..0de9255 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -23,7 +23,6 @@
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
-import android.app.ActivityThread;
import android.app.AppGlobals;
import android.app.role.OnRoleHoldersChangedListener;
import android.app.role.RoleManager;
@@ -51,7 +50,6 @@
import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig;
import android.media.AudioFormat;
import android.media.permission.Identity;
-import android.media.permission.IdentityContext;
import android.media.permission.PermissionUtil;
import android.media.permission.SafeCloseable;
import android.os.Binder;
@@ -61,7 +59,6 @@
import android.os.Parcel;
import android.os.ParcelFileDescriptor;
import android.os.PersistableBundle;
-import android.os.Process;
import android.os.RemoteCallback;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
@@ -88,6 +85,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.app.IHotwordRecognitionStatusCallback;
import com.android.internal.app.IVisualQueryDetectionAttentionListener;
+import com.android.internal.app.IVisualQueryRecognitionStatusListener;
import com.android.internal.app.IVoiceActionCheckCallback;
import com.android.internal.app.IVoiceInteractionManagerService;
import com.android.internal.app.IVoiceInteractionSessionListener;
@@ -139,6 +137,7 @@
private final RemoteCallbackList<IVoiceInteractionSessionListener>
mVoiceInteractionSessionListeners = new RemoteCallbackList<>();
+ private IVisualQueryRecognitionStatusListener mVisualQueryRecognitionStatusListener;
public VoiceInteractionManagerService(Context context) {
super(context);
@@ -1346,6 +1345,17 @@
@android.annotation.EnforcePermission(
android.Manifest.permission.ACCESS_VOICE_INTERACTION_SERVICE)
@Override
+ public void subscribeVisualQueryRecognitionStatus(IVisualQueryRecognitionStatusListener
+ listener) {
+ super.subscribeVisualQueryRecognitionStatus_enforcePermission();
+ synchronized (this) {
+ mVisualQueryRecognitionStatusListener = listener;
+ }
+ }
+
+ @android.annotation.EnforcePermission(
+ android.Manifest.permission.ACCESS_VOICE_INTERACTION_SERVICE)
+ @Override
public void enableVisualQueryDetection(
IVisualQueryDetectionAttentionListener listener) {
super.enableVisualQueryDetection_enforcePermission();
@@ -1391,7 +1401,10 @@
}
final long caller = Binder.clearCallingIdentity();
try {
- mImpl.startPerceivingLocked(callback);
+ boolean success = mImpl.startPerceivingLocked(callback);
+ if (success && mVisualQueryRecognitionStatusListener != null) {
+ mVisualQueryRecognitionStatusListener.onStartPerceiving();
+ }
} finally {
Binder.restoreCallingIdentity(caller);
}
@@ -1409,7 +1422,10 @@
}
final long caller = Binder.clearCallingIdentity();
try {
- mImpl.stopPerceivingLocked();
+ boolean success = mImpl.stopPerceivingLocked();
+ if (success && mVisualQueryRecognitionStatusListener != null) {
+ mVisualQueryRecognitionStatusListener.onStopPerceiving();
+ }
} finally {
Binder.restoreCallingIdentity(caller);
}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
index 5d88a65..471acc1 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
@@ -784,30 +784,30 @@
mHotwordDetectionConnection.setVisualQueryDetectionAttentionListenerLocked(listener);
}
- public void startPerceivingLocked(IVisualQueryDetectionVoiceInteractionCallback callback) {
+ public boolean startPerceivingLocked(IVisualQueryDetectionVoiceInteractionCallback callback) {
if (DEBUG) {
Slog.d(TAG, "startPerceivingLocked");
}
if (mHotwordDetectionConnection == null) {
// TODO: callback.onError();
- return;
+ return false;
}
- mHotwordDetectionConnection.startPerceivingLocked(callback);
+ return mHotwordDetectionConnection.startPerceivingLocked(callback);
}
- public void stopPerceivingLocked() {
+ public boolean stopPerceivingLocked() {
if (DEBUG) {
Slog.d(TAG, "stopPerceivingLocked");
}
if (mHotwordDetectionConnection == null) {
Slog.w(TAG, "stopPerceivingLocked() called but connection isn't established");
- return;
+ return false;
}
- mHotwordDetectionConnection.stopPerceivingLocked();
+ return mHotwordDetectionConnection.stopPerceivingLocked();
}
public void startListeningFromMicLocked(
diff --git a/tests/Internal/src/com/android/internal/app/LocaleStoreTest.java b/tests/Internal/src/com/android/internal/app/LocaleStoreTest.java
index f656881..c0d7cb4 100644
--- a/tests/Internal/src/com/android/internal/app/LocaleStoreTest.java
+++ b/tests/Internal/src/com/android/internal/app/LocaleStoreTest.java
@@ -63,6 +63,25 @@
}
@Test
+ public void testTransformImeLanguageTagToLocaleInfo_duplicateTagFilter() {
+ List<InputMethodSubtype> list = List.of(
+ new InputMethodSubtypeBuilder().setLanguageTag("en-US").build(),
+ new InputMethodSubtypeBuilder().setLanguageTag("en-US").build(),
+ new InputMethodSubtypeBuilder().setLanguageTag("en-US").build(),
+ new InputMethodSubtypeBuilder().setLanguageTag("zh-TW").build(),
+ new InputMethodSubtypeBuilder().setLanguageTag("ja-JP").build());
+
+ Set<LocaleInfo> localeSet = LocaleStore.transformImeLanguageTagToLocaleInfo(list);
+
+ Set<String> expectedLanguageTag = Set.of("en-US", "zh-TW", "ja-JP");
+ assertEquals(localeSet.size(), expectedLanguageTag.size());
+ for (LocaleInfo info : localeSet) {
+ assertEquals(info.mSuggestionFlags, LocaleInfo.SUGGESTION_TYPE_IME_LANGUAGE);
+ assertTrue(expectedLanguageTag.contains(info.getId()));
+ }
+ }
+
+ @Test
public void convertExplicitLocales_noExplicitLcoales_returnEmptyHashMap() {
Collection<LocaleInfo> supportedLocale = getFakeSupportedLocales();