Merge "Include descriptor in Binder token for AppPredictor"
diff --git a/apct-tests/perftests/core/src/android/graphics/perftests/TypefaceSerializationPerfTest.java b/apct-tests/perftests/core/src/android/graphics/perftests/TypefaceSerializationPerfTest.java
index d3fcaa1..1e2650d 100644
--- a/apct-tests/perftests/core/src/android/graphics/perftests/TypefaceSerializationPerfTest.java
+++ b/apct-tests/perftests/core/src/android/graphics/perftests/TypefaceSerializationPerfTest.java
@@ -97,6 +97,10 @@
buffer.position(0);
Typeface.deserializeFontMap(buffer, out);
elapsedTime = System.nanoTime() - startTime;
+ for (Typeface typeface : out.values()) {
+ typeface.releaseNativeObjectForTest();
+ }
+ out.clear();
}
}
diff --git a/core/java/Android.bp b/core/java/Android.bp
index 2fffaab7..af0fa39 100644
--- a/core/java/Android.bp
+++ b/core/java/Android.bp
@@ -187,8 +187,6 @@
"com/android/internal/util/IndentingPrintWriter.java",
"com/android/internal/util/MessageUtils.java",
"com/android/internal/util/WakeupMessage.java",
- // TODO: delete as soon as NetworkStatsFactory stops using
- "com/android/internal/util/ProcFileReader.java",
],
}
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index d8a48c9..25ef6e8 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -308,7 +308,7 @@
* <li>The <b>foreground lifetime</b> of an activity happens between a call to
* {@link android.app.Activity#onResume} until a corresponding call to
* {@link android.app.Activity#onPause}. During this time the activity is
- * in visible, active and interacting with the user. An activity
+ * visible, active and interacting with the user. An activity
* can frequently go between the resumed and paused states -- for example when
* the device goes to sleep, when an activity result is delivered, when a new
* intent is delivered -- so the code in these methods should be fairly
diff --git a/core/java/android/app/compat/ChangeIdStateCache.java b/core/java/android/app/compat/ChangeIdStateCache.java
index acd404b..dea4e9c8 100644
--- a/core/java/android/app/compat/ChangeIdStateCache.java
+++ b/core/java/android/app/compat/ChangeIdStateCache.java
@@ -32,7 +32,7 @@
public final class ChangeIdStateCache
extends PropertyInvalidatedCache<ChangeIdStateQuery, Boolean> {
private static final String CACHE_KEY = "cache_key.is_compat_change_enabled";
- private static final int MAX_ENTRIES = 20;
+ private static final int MAX_ENTRIES = 64;
private static boolean sDisabled = false;
private volatile IPlatformCompat mPlatformCompat;
diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java
index fd94969..269ca89 100644
--- a/core/java/android/appwidget/AppWidgetManager.java
+++ b/core/java/android/appwidget/AppWidgetManager.java
@@ -586,6 +586,9 @@
* The extras can be used to embed additional information about this widget to be accessed
* by the associated widget's AppWidgetProvider.
*
+ * <p>
+ * The new options are merged into existing options using {@link Bundle#putAll} semantics.
+ *
* @see #getAppWidgetOptions(int)
*
* @param appWidgetId The AppWidget instances for which to set the RemoteViews.
diff --git a/core/java/android/appwidget/AppWidgetProvider.java b/core/java/android/appwidget/AppWidgetProvider.java
index 3344ebc..e7e8ef9 100644
--- a/core/java/android/appwidget/AppWidgetProvider.java
+++ b/core/java/android/appwidget/AppWidgetProvider.java
@@ -127,7 +127,8 @@
/**
* Called in response to the {@link AppWidgetManager#ACTION_APPWIDGET_OPTIONS_CHANGED}
- * broadcast when this widget has been layed out at a new size.
+ * broadcast when this widget has been layed out at a new size or its options changed via
+ * {@link AppWidgetManager#updateAppWidgetOptions}.
*
* {@more}
*
@@ -136,7 +137,7 @@
* @param appWidgetManager A {@link AppWidgetManager} object you can call {@link
* AppWidgetManager#updateAppWidget} on.
* @param appWidgetId The appWidgetId of the widget whose size changed.
- * @param newOptions The appWidgetId of the widget whose size changed.
+ * @param newOptions The new options of the changed widget.
*
* @see AppWidgetManager#ACTION_APPWIDGET_OPTIONS_CHANGED
*/
diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java
index da486ee..0c171ad 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -19,7 +19,7 @@
import static android.Manifest.permission.INTERACT_ACROSS_USERS;
import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
import static android.os.Process.myUserHandle;
-import static android.os.Trace.TRACE_TAG_DATABASE;
+import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -278,7 +278,7 @@
// Return an empty cursor for all columns.
return new MatrixCursor(cursor.getColumnNames(), 0);
}
- traceBegin(TRACE_TAG_DATABASE, "query: ", uri.getAuthority());
+ traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "query: ", uri.getAuthority());
final AttributionSource original = setCallingAttributionSource(
attributionSource);
try {
@@ -289,7 +289,7 @@
throw e.rethrowAsRuntimeException();
} finally {
setCallingAttributionSource(original);
- Trace.traceEnd(TRACE_TAG_DATABASE);
+ Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
}
}
@@ -298,13 +298,13 @@
// getCallingPackage() isn't available in getType(), as the javadoc states.
uri = validateIncomingUri(uri);
uri = maybeGetUriWithoutUserId(uri);
- traceBegin(TRACE_TAG_DATABASE, "getType: ", uri.getAuthority());
+ traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "getType: ", uri.getAuthority());
try {
return mInterface.getType(uri);
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
} finally {
- Trace.traceEnd(TRACE_TAG_DATABASE);
+ Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
}
}
@@ -336,7 +336,7 @@
setCallingAttributionSource(original);
}
}
- traceBegin(TRACE_TAG_DATABASE, "insert: ", uri.getAuthority());
+ traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "insert: ", uri.getAuthority());
final AttributionSource original = setCallingAttributionSource(
attributionSource);
try {
@@ -345,7 +345,7 @@
throw e.rethrowAsRuntimeException();
} finally {
setCallingAttributionSource(original);
- Trace.traceEnd(TRACE_TAG_DATABASE);
+ Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
}
}
@@ -358,7 +358,7 @@
!= PermissionChecker.PERMISSION_GRANTED) {
return 0;
}
- traceBegin(TRACE_TAG_DATABASE, "bulkInsert: ", uri.getAuthority());
+ traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "bulkInsert: ", uri.getAuthority());
final AttributionSource original = setCallingAttributionSource(
attributionSource);
try {
@@ -367,7 +367,7 @@
throw e.rethrowAsRuntimeException();
} finally {
setCallingAttributionSource(original);
- Trace.traceEnd(TRACE_TAG_DATABASE);
+ Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
}
}
@@ -404,7 +404,7 @@
}
}
}
- traceBegin(TRACE_TAG_DATABASE, "applyBatch: ", authority);
+ traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "applyBatch: ", authority);
final AttributionSource original = setCallingAttributionSource(
attributionSource);
try {
@@ -423,7 +423,7 @@
throw e.rethrowAsRuntimeException();
} finally {
setCallingAttributionSource(original);
- Trace.traceEnd(TRACE_TAG_DATABASE);
+ Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
}
}
@@ -436,7 +436,7 @@
!= PermissionChecker.PERMISSION_GRANTED) {
return 0;
}
- traceBegin(TRACE_TAG_DATABASE, "delete: ", uri.getAuthority());
+ traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "delete: ", uri.getAuthority());
final AttributionSource original = setCallingAttributionSource(
attributionSource);
try {
@@ -445,7 +445,7 @@
throw e.rethrowAsRuntimeException();
} finally {
setCallingAttributionSource(original);
- Trace.traceEnd(TRACE_TAG_DATABASE);
+ Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
}
}
@@ -458,7 +458,7 @@
!= PermissionChecker.PERMISSION_GRANTED) {
return 0;
}
- traceBegin(TRACE_TAG_DATABASE, "update: ", uri.getAuthority());
+ traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "update: ", uri.getAuthority());
final AttributionSource original = setCallingAttributionSource(
attributionSource);
try {
@@ -467,7 +467,7 @@
throw e.rethrowAsRuntimeException();
} finally {
setCallingAttributionSource(original);
- Trace.traceEnd(TRACE_TAG_DATABASE);
+ Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
}
}
@@ -478,7 +478,7 @@
uri = validateIncomingUri(uri);
uri = maybeGetUriWithoutUserId(uri);
enforceFilePermission(attributionSource, uri, mode);
- traceBegin(TRACE_TAG_DATABASE, "openFile: ", uri.getAuthority());
+ traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "openFile: ", uri.getAuthority());
final AttributionSource original = setCallingAttributionSource(
attributionSource);
try {
@@ -488,7 +488,7 @@
throw e.rethrowAsRuntimeException();
} finally {
setCallingAttributionSource(original);
- Trace.traceEnd(TRACE_TAG_DATABASE);
+ Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
}
}
@@ -499,7 +499,7 @@
uri = validateIncomingUri(uri);
uri = maybeGetUriWithoutUserId(uri);
enforceFilePermission(attributionSource, uri, mode);
- traceBegin(TRACE_TAG_DATABASE, "openAssetFile: ", uri.getAuthority());
+ traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "openAssetFile: ", uri.getAuthority());
final AttributionSource original = setCallingAttributionSource(
attributionSource);
try {
@@ -509,7 +509,7 @@
throw e.rethrowAsRuntimeException();
} finally {
setCallingAttributionSource(original);
- Trace.traceEnd(TRACE_TAG_DATABASE);
+ Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
}
}
@@ -518,7 +518,7 @@
String method, @Nullable String arg, @Nullable Bundle extras) {
validateIncomingAuthority(authority);
Bundle.setDefusable(extras, true);
- traceBegin(TRACE_TAG_DATABASE, "call: ", authority);
+ traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "call: ", authority);
final AttributionSource original = setCallingAttributionSource(
attributionSource);
try {
@@ -527,7 +527,7 @@
throw e.rethrowAsRuntimeException();
} finally {
setCallingAttributionSource(original);
- Trace.traceEnd(TRACE_TAG_DATABASE);
+ Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
}
}
@@ -536,13 +536,13 @@
// getCallingPackage() isn't available in getType(), as the javadoc states.
uri = validateIncomingUri(uri);
uri = maybeGetUriWithoutUserId(uri);
- traceBegin(TRACE_TAG_DATABASE, "getStreamTypes: ", uri.getAuthority());
+ traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "getStreamTypes: ", uri.getAuthority());
try {
return mInterface.getStreamTypes(uri, mimeTypeFilter);
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
} finally {
- Trace.traceEnd(TRACE_TAG_DATABASE);
+ Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
}
}
@@ -554,7 +554,7 @@
uri = validateIncomingUri(uri);
uri = maybeGetUriWithoutUserId(uri);
enforceFilePermission(attributionSource, uri, "r");
- traceBegin(TRACE_TAG_DATABASE, "openTypedAssetFile: ", uri.getAuthority());
+ traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "openTypedAssetFile: ", uri.getAuthority());
final AttributionSource original = setCallingAttributionSource(
attributionSource);
try {
@@ -564,7 +564,7 @@
throw e.rethrowAsRuntimeException();
} finally {
setCallingAttributionSource(original);
- Trace.traceEnd(TRACE_TAG_DATABASE);
+ Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
}
}
@@ -582,7 +582,7 @@
!= PermissionChecker.PERMISSION_GRANTED) {
return null;
}
- traceBegin(TRACE_TAG_DATABASE, "canonicalize: ", uri.getAuthority());
+ traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "canonicalize: ", uri.getAuthority());
final AttributionSource original = setCallingAttributionSource(
attributionSource);
try {
@@ -591,7 +591,7 @@
throw e.rethrowAsRuntimeException();
} finally {
setCallingAttributionSource(original);
- Trace.traceEnd(TRACE_TAG_DATABASE);
+ Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
}
}
@@ -618,7 +618,7 @@
!= PermissionChecker.PERMISSION_GRANTED) {
return null;
}
- traceBegin(TRACE_TAG_DATABASE, "uncanonicalize: ", uri.getAuthority());
+ traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "uncanonicalize: ", uri.getAuthority());
final AttributionSource original = setCallingAttributionSource(
attributionSource);
try {
@@ -627,7 +627,7 @@
throw e.rethrowAsRuntimeException();
} finally {
setCallingAttributionSource(original);
- Trace.traceEnd(TRACE_TAG_DATABASE);
+ Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
}
}
@@ -654,7 +654,7 @@
!= PermissionChecker.PERMISSION_GRANTED) {
return false;
}
- traceBegin(TRACE_TAG_DATABASE, "refresh: ", uri.getAuthority());
+ traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "refresh: ", uri.getAuthority());
final AttributionSource original = setCallingAttributionSource(
attributionSource);
try {
@@ -662,7 +662,7 @@
CancellationSignal.fromTransport(cancellationSignal));
} finally {
setCallingAttributionSource(original);
- Trace.traceEnd(TRACE_TAG_DATABASE);
+ Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
}
}
@@ -671,7 +671,7 @@
int uid, int modeFlags) {
uri = validateIncomingUri(uri);
uri = maybeGetUriWithoutUserId(uri);
- traceBegin(TRACE_TAG_DATABASE, "checkUriPermission: ", uri.getAuthority());
+ traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "checkUriPermission: ", uri.getAuthority());
final AttributionSource original = setCallingAttributionSource(
attributionSource);
try {
@@ -680,7 +680,7 @@
throw e.rethrowAsRuntimeException();
} finally {
setCallingAttributionSource(original);
- Trace.traceEnd(TRACE_TAG_DATABASE);
+ Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
}
}
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index ccc2441..dd517c9 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -10422,7 +10422,7 @@
private static final PropertyInvalidatedCache<ApplicationInfoQuery, ApplicationInfo>
sApplicationInfoCache =
new PropertyInvalidatedCache<ApplicationInfoQuery, ApplicationInfo>(
- 16, PermissionManager.CACHE_KEY_PACKAGE_INFO,
+ 32, PermissionManager.CACHE_KEY_PACKAGE_INFO,
"getApplicationInfo") {
@Override
public ApplicationInfo recompute(ApplicationInfoQuery query) {
@@ -10523,7 +10523,7 @@
private static final PropertyInvalidatedCache<PackageInfoQuery, PackageInfo>
sPackageInfoCache =
new PropertyInvalidatedCache<PackageInfoQuery, PackageInfo>(
- 32, PermissionManager.CACHE_KEY_PACKAGE_INFO,
+ 64, PermissionManager.CACHE_KEY_PACKAGE_INFO,
"getPackageInfo") {
@Override
public PackageInfo recompute(PackageInfoQuery query) {
diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
index 336ef7a..41822e7 100644
--- a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
@@ -1693,8 +1693,10 @@
((i != idx) || notifyCurrentIndex)) {
TotalCaptureResult result = previewMap.valueAt(i).second;
Long timestamp = result.get(CaptureResult.SENSOR_TIMESTAMP);
- mCaptureResultHandler.onCaptureCompleted(timestamp,
- initializeFilteredResults(result));
+ if (mCaptureResultHandler != null) {
+ mCaptureResultHandler.onCaptureCompleted(timestamp,
+ initializeFilteredResults(result));
+ }
Log.w(TAG, "Preview frame drop with timestamp: " + previewMap.keyAt(i));
final long ident = Binder.clearCallingIdentity();
diff --git a/core/java/android/service/voice/VoiceInteractionSession.java b/core/java/android/service/voice/VoiceInteractionSession.java
index 42500b4..b7ec486 100644
--- a/core/java/android/service/voice/VoiceInteractionSession.java
+++ b/core/java/android/service/voice/VoiceInteractionSession.java
@@ -84,12 +84,12 @@
/**
* An active voice interaction session, providing a facility for the implementation
- * to interact with the user in the voice interaction layer. The user interface is
- * initially shown by default, and can be created be overriding {@link #onCreateContentView()}
+ * to interact with the user in the voice interaction layer. The user interface is
+ * initially shown by default, and can be created by overriding {@link #onCreateContentView()}
* in which the UI can be built.
*
* <p>A voice interaction session can be self-contained, ultimately calling {@link #finish}
- * when done. It can also initiate voice interactions with applications by calling
+ * when done. It can also initiate voice interactions with applications by calling
* {@link #startVoiceActivity}</p>.
*/
public class VoiceInteractionSession implements KeyEvent.Callback, ComponentCallbacks2 {
diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java
index c198098..9f426a1 100644
--- a/core/java/android/view/InsetsState.java
+++ b/core/java/android/view/InsetsState.java
@@ -81,7 +81,9 @@
ITYPE_BOTTOM_MANDATORY_GESTURES,
ITYPE_LEFT_MANDATORY_GESTURES,
ITYPE_RIGHT_MANDATORY_GESTURES,
+ ITYPE_LEFT_TAPPABLE_ELEMENT,
ITYPE_TOP_TAPPABLE_ELEMENT,
+ ITYPE_RIGHT_TAPPABLE_ELEMENT,
ITYPE_BOTTOM_TAPPABLE_ELEMENT,
ITYPE_LEFT_DISPLAY_CUTOUT,
ITYPE_TOP_DISPLAY_CUTOUT,
diff --git a/core/java/android/view/RemoteAnimationTarget.java b/core/java/android/view/RemoteAnimationTarget.java
index a468951..44f419a 100644
--- a/core/java/android/view/RemoteAnimationTarget.java
+++ b/core/java/android/view/RemoteAnimationTarget.java
@@ -45,6 +45,7 @@
import android.os.Parcel;
import android.os.Parcelable;
import android.util.proto.ProtoOutputStream;
+import android.window.TaskSnapshot;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
@@ -234,6 +235,12 @@
*/
public @ColorInt int backgroundColor;
+ /**
+ * Whether the activity is going to show IME on the target window after the app transition.
+ * @see TaskSnapshot#hasImeSurface() that used the task snapshot during animating task.
+ */
+ public boolean willShowImeOnTarget;
+
public RemoteAnimationTarget(int taskId, int mode, SurfaceControl leash, boolean isTranslucent,
Rect clipRect, Rect contentInsets, int prefixOrderIndex, Point position,
Rect localBounds, Rect screenSpaceBounds,
@@ -294,12 +301,21 @@
hasAnimatingParent = in.readBoolean();
backgroundColor = in.readInt();
showBackdrop = in.readBoolean();
+ willShowImeOnTarget = in.readBoolean();
}
public void setShowBackdrop(boolean shouldShowBackdrop) {
showBackdrop = shouldShowBackdrop;
}
+ public void setWillShowImeOnTarget(boolean showImeOnTarget) {
+ willShowImeOnTarget = showImeOnTarget;
+ }
+
+ public boolean willShowImeOnTarget() {
+ return willShowImeOnTarget;
+ }
+
@Override
public int describeContents() {
return 0;
@@ -328,6 +344,7 @@
dest.writeBoolean(hasAnimatingParent);
dest.writeInt(backgroundColor);
dest.writeBoolean(showBackdrop);
+ dest.writeBoolean(willShowImeOnTarget);
}
public void dump(PrintWriter pw, String prefix) {
@@ -350,6 +367,7 @@
pw.print(prefix); pw.print("hasAnimatingParent="); pw.print(hasAnimatingParent);
pw.print(prefix); pw.print("backgroundColor="); pw.print(backgroundColor);
pw.print(prefix); pw.print("showBackdrop="); pw.print(showBackdrop);
+ pw.print(prefix); pw.print("willShowImeOnTarget="); pw.print(willShowImeOnTarget);
}
public void dumpDebug(ProtoOutputStream proto, long fieldId) {
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index de02680..1bedbfc 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -848,6 +848,8 @@
private int mLastTransformHint = Integer.MIN_VALUE;
+ private AccessibilityWindowAttributes mAccessibilityWindowAttributes;
+
/**
* A temporary object used so relayoutWindow can return the latest SyncSeqId
* system. The SyncSeqId system was designed to work without synchronous relayout
@@ -1412,8 +1414,12 @@
if (registered) {
final AccessibilityWindowAttributes attributes = new AccessibilityWindowAttributes(
mWindowAttributes);
- mAccessibilityManager.setAccessibilityWindowAttributes(getDisplayId(),
- mAttachInfo.mAccessibilityWindowId, attributes);
+ if (!attributes.equals(mAccessibilityWindowAttributes)) {
+ mAccessibilityWindowAttributes = attributes;
+ mAccessibilityManager.setAccessibilityWindowAttributes(getDisplayId(),
+ mAttachInfo.mAccessibilityWindowId, attributes);
+ }
+
}
}
@@ -10365,6 +10371,7 @@
!= AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
if (registered) {
mAttachInfo.mAccessibilityWindowId = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
+ mAccessibilityWindowAttributes = null;
mAccessibilityManager.removeAccessibilityInteractionConnection(mWindow);
}
}
@@ -10927,8 +10934,8 @@
private void registerCompatOnBackInvokedCallback() {
mCompatOnBackInvokedCallback = () -> {
- sendBackKeyEvent(KeyEvent.ACTION_DOWN);
- sendBackKeyEvent(KeyEvent.ACTION_UP);
+ sendBackKeyEvent(KeyEvent.ACTION_DOWN);
+ sendBackKeyEvent(KeyEvent.ACTION_UP);
};
mOnBackInvokedDispatcher.registerOnBackInvokedCallback(
OnBackInvokedDispatcher.PRIORITY_DEFAULT, mCompatOnBackInvokedCallback);
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index fb298c7..b233e54 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -709,7 +709,7 @@
}
getPositionListener().addSubscriber(mCursorAnchorInfoNotifier, true);
- resumeBlink();
+ makeBlink();
}
void onDetachedFromWindow() {
@@ -1685,17 +1685,12 @@
void onWindowFocusChanged(boolean hasWindowFocus) {
if (hasWindowFocus) {
- if (mBlink != null) {
- mBlink.uncancel();
- makeBlink();
- }
+ resumeBlink();
if (mTextView.hasSelection() && !extractedTextModeWillBeStarted()) {
refreshTextActionMode();
}
} else {
- if (mBlink != null) {
- mBlink.cancel();
- }
+ suspendBlink();
if (mInputContentType != null) {
mInputContentType.enterDown = false;
}
@@ -2851,7 +2846,8 @@
* @return True when the TextView isFocused and has a valid zero-length selection (cursor).
*/
private boolean shouldBlink() {
- if (!isCursorVisible() || !mTextView.isFocused()) return false;
+ if (!isCursorVisible() || !mTextView.isFocused()
+ || mTextView.getWindowVisibility() != mTextView.VISIBLE) return false;
final int start = mTextView.getSelectionStart();
if (start < 0) return false;
diff --git a/core/java/android/window/ProxyOnBackInvokedDispatcher.java b/core/java/android/window/ProxyOnBackInvokedDispatcher.java
index 8ad1093..49acde9 100644
--- a/core/java/android/window/ProxyOnBackInvokedDispatcher.java
+++ b/core/java/android/window/ProxyOnBackInvokedDispatcher.java
@@ -122,7 +122,7 @@
}
for (Pair<OnBackInvokedCallback, Integer> callbackPair : mCallbacks) {
int priority = callbackPair.second;
- if (priority >= 0) {
+ if (priority >= PRIORITY_DEFAULT) {
mActualDispatcher.registerOnBackInvokedCallback(priority, callbackPair.first);
} else {
mActualDispatcher.registerSystemOnBackInvokedCallback(callbackPair.first);
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
index 1f3841a..b263b08 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -116,6 +116,9 @@
/** The first unused bit. This can be used by remotes to attach custom flags to this change. */
public static final int FLAG_FIRST_CUSTOM = 1 << 10;
+ /** The container is going to show IME on its task after the transition. */
+ public static final int FLAG_WILL_IME_SHOWN = 1 << 11;
+
/** @hide */
@IntDef(prefix = { "FLAG_" }, value = {
FLAG_NONE,
@@ -129,7 +132,8 @@
FLAG_DISPLAY_HAS_ALERT_WINDOWS,
FLAG_IS_INPUT_METHOD,
FLAG_IS_EMBEDDED,
- FLAG_FIRST_CUSTOM
+ FLAG_FIRST_CUSTOM,
+ FLAG_WILL_IME_SHOWN
})
public @interface ChangeFlags {}
diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java
index 5a0d11d..2126166 100644
--- a/core/java/android/window/WindowContainerTransaction.java
+++ b/core/java/android/window/WindowContainerTransaction.java
@@ -670,6 +670,39 @@
}
/**
+ * Sets/removes the always on top flag for this {@code windowContainer}. See
+ * {@link com.android.server.wm.ConfigurationContainer#setAlwaysOnTop(boolean)}.
+ * Please note that this method is only intended to be used for a
+ * {@link com.android.server.wm.DisplayArea}.
+ *
+ * <p>
+ * Setting always on top to {@code True} will also make the {@code windowContainer} to move
+ * to the top.
+ * </p>
+ * <p>
+ * Setting always on top to {@code False} will make this {@code windowContainer} to move
+ * below the other always on top sibling containers.
+ * </p>
+ *
+ * @param windowContainer the container which the flag need to be updated for.
+ * @param alwaysOnTop denotes whether or not always on top flag should be set.
+ * @hide
+ */
+ @NonNull
+ public WindowContainerTransaction setAlwaysOnTop(
+ @NonNull WindowContainerToken windowContainer,
+ boolean alwaysOnTop) {
+ final HierarchyOp hierarchyOp =
+ new HierarchyOp.Builder(
+ HierarchyOp.HIERARCHY_OP_TYPE_SET_ALWAYS_ON_TOP)
+ .setContainer(windowContainer.asBinder())
+ .setAlwaysOnTop(alwaysOnTop)
+ .build();
+ mHierarchyOps.add(hierarchyOp);
+ return this;
+ }
+
+ /**
* When this {@link WindowContainerTransaction} failed to finish on the server side, it will
* trigger callback with this {@param errorCallbackToken}.
* @param errorCallbackToken client provided token that will be passed back as parameter in
@@ -1078,6 +1111,7 @@
public static final int HIERARCHY_OP_TYPE_ADD_RECT_INSETS_PROVIDER = 16;
public static final int HIERARCHY_OP_TYPE_REMOVE_INSETS_PROVIDER = 17;
public static final int HIERARCHY_OP_TYPE_REQUEST_FOCUS_ON_TASK_FRAGMENT = 18;
+ public static final int HIERARCHY_OP_TYPE_SET_ALWAYS_ON_TOP = 19;
// The following key(s) are for use with mLaunchOptions:
// When launching a task (eg. from recents), this is the taskId to be launched.
@@ -1128,6 +1162,8 @@
@Nullable
private ShortcutInfo mShortcutInfo;
+ private boolean mAlwaysOnTop;
+
public static HierarchyOp createForReparent(
@NonNull IBinder container, @Nullable IBinder reparent, boolean toTop) {
return new HierarchyOp.Builder(HIERARCHY_OP_TYPE_REPARENT)
@@ -1225,6 +1261,7 @@
mTaskFragmentCreationOptions = copy.mTaskFragmentCreationOptions;
mPendingIntent = copy.mPendingIntent;
mShortcutInfo = copy.mShortcutInfo;
+ mAlwaysOnTop = copy.mAlwaysOnTop;
}
protected HierarchyOp(Parcel in) {
@@ -1246,6 +1283,7 @@
mTaskFragmentCreationOptions = in.readTypedObject(TaskFragmentCreationParams.CREATOR);
mPendingIntent = in.readTypedObject(PendingIntent.CREATOR);
mShortcutInfo = in.readTypedObject(ShortcutInfo.CREATOR);
+ mAlwaysOnTop = in.readBoolean();
}
public int getType() {
@@ -1311,6 +1349,10 @@
return mActivityIntent;
}
+ public boolean isAlwaysOnTop() {
+ return mAlwaysOnTop;
+ }
+
@Nullable
public TaskFragmentCreationParams getTaskFragmentCreationOptions() {
return mTaskFragmentCreationOptions;
@@ -1379,6 +1421,9 @@
+ " insetsType=" + Arrays.toString(mInsetsTypes) + "}";
case HIERARCHY_OP_TYPE_REQUEST_FOCUS_ON_TASK_FRAGMENT:
return "{requestFocusOnTaskFragment: container=" + mContainer + "}";
+ case HIERARCHY_OP_TYPE_SET_ALWAYS_ON_TOP:
+ return "{setAlwaysOnTop: container=" + mContainer
+ + " alwaysOnTop=" + mAlwaysOnTop + "}";
default:
return "{mType=" + mType + " container=" + mContainer + " reparent=" + mReparent
+ " mToTop=" + mToTop
@@ -1408,6 +1453,7 @@
dest.writeTypedObject(mTaskFragmentCreationOptions, flags);
dest.writeTypedObject(mPendingIntent, flags);
dest.writeTypedObject(mShortcutInfo, flags);
+ dest.writeBoolean(mAlwaysOnTop);
}
@Override
@@ -1466,6 +1512,8 @@
@Nullable
private ShortcutInfo mShortcutInfo;
+ private boolean mAlwaysOnTop;
+
Builder(int type) {
mType = type;
}
@@ -1525,6 +1573,11 @@
return this;
}
+ Builder setAlwaysOnTop(boolean alwaysOnTop) {
+ mAlwaysOnTop = alwaysOnTop;
+ return this;
+ }
+
Builder setTaskFragmentCreationOptions(
@Nullable TaskFragmentCreationParams taskFragmentCreationOptions) {
mTaskFragmentCreationOptions = taskFragmentCreationOptions;
@@ -1553,6 +1606,7 @@
hierarchyOp.mLaunchOptions = mLaunchOptions;
hierarchyOp.mActivityIntent = mActivityIntent;
hierarchyOp.mPendingIntent = mPendingIntent;
+ hierarchyOp.mAlwaysOnTop = mAlwaysOnTop;
hierarchyOp.mTaskFragmentCreationOptions = mTaskFragmentCreationOptions;
hierarchyOp.mShortcutInfo = mShortcutInfo;
diff --git a/core/java/com/android/internal/util/ArrayUtils.java b/core/java/com/android/internal/util/ArrayUtils.java
index 6eae34b..b64771b 100644
--- a/core/java/com/android/internal/util/ArrayUtils.java
+++ b/core/java/com/android/internal/util/ArrayUtils.java
@@ -39,8 +39,7 @@
import java.util.function.IntFunction;
/**
- * ArrayUtils contains some methods that you can call to find out
- * the most efficient increments by which to grow arrays.
+ * Static utility methods for arrays that aren't already included in {@link java.util.Arrays}.
*/
public class ArrayUtils {
private static final int CACHE_SIZE = 73;
@@ -351,15 +350,16 @@
}
/**
- * Combine multiple arrays into a single array.
+ * Returns the concatenation of the given arrays. Only works for object arrays, not for
+ * primitive arrays. See {@link #concat(byte[]...)} for a variant that works on byte arrays.
*
* @param kind The class of the array elements
- * @param arrays The arrays to combine
+ * @param arrays The arrays to concatenate. Null arrays are treated as empty.
* @param <T> The class of the array elements (inferred from kind).
* @return A single array containing all the elements of the parameter arrays.
*/
@SuppressWarnings("unchecked")
- public static @NonNull <T> T[] concatElements(Class<T> kind, @Nullable T[]... arrays) {
+ public static @NonNull <T> T[] concat(Class<T> kind, @Nullable T[]... arrays) {
if (arrays == null || arrays.length == 0) {
return createEmptyArray(kind);
}
@@ -400,6 +400,29 @@
return (T[]) Array.newInstance(kind, 0);
}
+ /**
+ * Returns the concatenation of the given byte arrays. Null arrays are treated as empty.
+ */
+ public static @NonNull byte[] concat(@Nullable byte[]... arrays) {
+ if (arrays == null) {
+ return new byte[0];
+ }
+ int totalLength = 0;
+ for (byte[] a : arrays) {
+ if (a != null) {
+ totalLength += a.length;
+ }
+ }
+ final byte[] result = new byte[totalLength];
+ int pos = 0;
+ for (byte[] a : arrays) {
+ if (a != null) {
+ System.arraycopy(a, 0, result, pos, a.length);
+ pos += a.length;
+ }
+ }
+ return result;
+ }
/**
* Adds value to given array if not already present, providing set-like
diff --git a/core/java/com/android/internal/util/LatencyTracker.java b/core/java/com/android/internal/util/LatencyTracker.java
index ca40a40..14a6d5e 100644
--- a/core/java/com/android/internal/util/LatencyTracker.java
+++ b/core/java/com/android/internal/util/LatencyTracker.java
@@ -142,6 +142,11 @@
*/
public static final int ACTION_LOAD_SHARE_SHEET = 16;
+ /**
+ * Time it takes to show AOD display after folding the device.
+ */
+ public static final int ACTION_FOLD_TO_AOD = 17;
+
private static final int[] ACTIONS_ALL = {
ACTION_EXPAND_PANEL,
ACTION_TOGGLE_RECENTS,
@@ -160,6 +165,7 @@
ACTION_UDFPS_ILLUMINATE,
ACTION_SHOW_BACK_ARROW,
ACTION_LOAD_SHARE_SHEET,
+ ACTION_FOLD_TO_AOD,
};
/** @hide */
@@ -181,6 +187,7 @@
ACTION_UDFPS_ILLUMINATE,
ACTION_SHOW_BACK_ARROW,
ACTION_LOAD_SHARE_SHEET,
+ ACTION_FOLD_TO_AOD
})
@Retention(RetentionPolicy.SOURCE)
public @interface Action {
@@ -204,6 +211,7 @@
FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_UDFPS_ILLUMINATE,
FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_SHOW_BACK_ARROW,
FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_LOAD_SHARE_SHEET,
+ FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_FOLD_TO_AOD
};
private static LatencyTracker sLatencyTracker;
@@ -297,6 +305,8 @@
return "ACTION_SHOW_BACK_ARROW";
case 17:
return "ACTION_LOAD_SHARE_SHEET";
+ case 19:
+ return "ACTION_FOLD_TO_AOD";
default:
throw new IllegalArgumentException("Invalid action");
}
diff --git a/core/java/com/android/internal/util/OWNERS b/core/java/com/android/internal/util/OWNERS
index 354dd9a..1808bd5 100644
--- a/core/java/com/android/internal/util/OWNERS
+++ b/core/java/com/android/internal/util/OWNERS
@@ -5,3 +5,4 @@
per-file Protocol* = etancohen@google.com, lorenzo@google.com
per-file State* = jchalard@google.com, lorenzo@google.com, satk@google.com
per-file *Dump* = file:/core/java/com/android/internal/util/dump/OWNERS
+per-file *Screenshot* = file:/packages/SystemUI/src/com/android/systemui/screenshot/OWNERS
diff --git a/core/java/com/android/internal/util/ScreenshotHelper.java b/core/java/com/android/internal/util/ScreenshotHelper.java
index 518fa53..8e54746 100644
--- a/core/java/com/android/internal/util/ScreenshotHelper.java
+++ b/core/java/com/android/internal/util/ScreenshotHelper.java
@@ -1,7 +1,6 @@
package com.android.internal.util;
import static android.content.Intent.ACTION_USER_SWITCHED;
-import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_OTHER;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -29,6 +28,10 @@
import android.os.UserHandle;
import android.util.Log;
import android.view.WindowManager;
+import android.view.WindowManager.ScreenshotSource;
+import android.view.WindowManager.ScreenshotType;
+
+import com.android.internal.annotations.VisibleForTesting;
import java.util.Objects;
import java.util.function.Consumer;
@@ -42,24 +45,28 @@
* Describes a screenshot request (to make it easier to pass data through to the handler).
*/
public static class ScreenshotRequest implements Parcelable {
- private int mSource;
- private boolean mHasStatusBar;
- private boolean mHasNavBar;
- private Bundle mBitmapBundle;
- private Rect mBoundsInScreen;
- private Insets mInsets;
- private int mTaskId;
- private int mUserId;
- private ComponentName mTopComponent;
+ private final int mSource;
+ private final Bundle mBitmapBundle;
+ private final Rect mBoundsInScreen;
+ private final Insets mInsets;
+ private final int mTaskId;
+ private final int mUserId;
+ private final ComponentName mTopComponent;
- ScreenshotRequest(int source, boolean hasStatus, boolean hasNav) {
+ @VisibleForTesting
+ public ScreenshotRequest(int source) {
mSource = source;
- mHasStatusBar = hasStatus;
- mHasNavBar = hasNav;
+ mBitmapBundle = null;
+ mBoundsInScreen = null;
+ mInsets = null;
+ mTaskId = -1;
+ mUserId = -1;
+ mTopComponent = null;
}
- ScreenshotRequest(int source, Bundle bitmapBundle, Rect boundsInScreen, Insets insets,
- int taskId, int userId, ComponentName topComponent) {
+ @VisibleForTesting
+ public ScreenshotRequest(int source, Bundle bitmapBundle, Rect boundsInScreen,
+ Insets insets, int taskId, int userId, ComponentName topComponent) {
mSource = source;
mBitmapBundle = bitmapBundle;
mBoundsInScreen = boundsInScreen;
@@ -71,16 +78,21 @@
ScreenshotRequest(Parcel in) {
mSource = in.readInt();
- mHasStatusBar = in.readBoolean();
- mHasNavBar = in.readBoolean();
-
if (in.readInt() == 1) {
mBitmapBundle = in.readBundle(getClass().getClassLoader());
- mBoundsInScreen = in.readParcelable(Rect.class.getClassLoader(), android.graphics.Rect.class);
- mInsets = in.readParcelable(Insets.class.getClassLoader(), android.graphics.Insets.class);
+ mBoundsInScreen = in.readParcelable(Rect.class.getClassLoader(), Rect.class);
+ mInsets = in.readParcelable(Insets.class.getClassLoader(), Insets.class);
mTaskId = in.readInt();
mUserId = in.readInt();
- mTopComponent = in.readParcelable(ComponentName.class.getClassLoader(), android.content.ComponentName.class);
+ mTopComponent = in.readParcelable(ComponentName.class.getClassLoader(),
+ ComponentName.class);
+ } else {
+ mBitmapBundle = null;
+ mBoundsInScreen = null;
+ mInsets = null;
+ mTaskId = -1;
+ mUserId = -1;
+ mTopComponent = null;
}
}
@@ -88,14 +100,6 @@
return mSource;
}
- public boolean getHasStatusBar() {
- return mHasStatusBar;
- }
-
- public boolean getHasNavBar() {
- return mHasNavBar;
- }
-
public Bundle getBitmapBundle() {
return mBitmapBundle;
}
@@ -112,7 +116,6 @@
return mTaskId;
}
-
public int getUserId() {
return mUserId;
}
@@ -129,8 +132,6 @@
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(mSource);
- dest.writeBoolean(mHasStatusBar);
- dest.writeBoolean(mHasNavBar);
if (mBitmapBundle == null) {
dest.writeInt(0);
} else {
@@ -144,7 +145,8 @@
}
}
- public static final @NonNull Parcelable.Creator<ScreenshotRequest> CREATOR =
+ @NonNull
+ public static final Parcelable.Creator<ScreenshotRequest> CREATOR =
new Parcelable.Creator<ScreenshotRequest>() {
@Override
@@ -254,113 +256,71 @@
/**
* Request a screenshot be taken.
- *
+ * <p>
* Added to support reducing unit test duration; the method variant without a timeout argument
* is recommended for general use.
*
- * @param screenshotType The type of screenshot, for example either
- * {@link android.view.WindowManager#TAKE_SCREENSHOT_FULLSCREEN}
- * or
- * {@link android.view.WindowManager#TAKE_SCREENSHOT_SELECTED_REGION}
- * @param hasStatus {@code true} if the status bar is currently showing. {@code false}
- * if not.
- * @param hasNav {@code true} if the navigation bar is currently showing. {@code
- * false} if not.
- * @param source The source of the screenshot request. One of
- * {SCREENSHOT_GLOBAL_ACTIONS, SCREENSHOT_KEY_CHORD,
- * SCREENSHOT_OVERVIEW, SCREENSHOT_OTHER}
- * @param handler A handler used in case the screenshot times out
- * @param completionConsumer Consumes `false` if a screenshot was not taken, and `true` if the
- * screenshot was taken.
+ * @param screenshotType The type of screenshot, defined by {@link ScreenshotType}
+ * @param source The source of the screenshot request, defined by {@link ScreenshotSource}
+ * @param handler used to process messages received from the screenshot service
+ * @param completionConsumer receives the URI of the captured screenshot, once saved or
+ * null if no screenshot was saved
*/
- public void takeScreenshot(final int screenshotType, final boolean hasStatus,
- final boolean hasNav, int source, @NonNull Handler handler,
- @Nullable Consumer<Uri> completionConsumer) {
- ScreenshotRequest screenshotRequest = new ScreenshotRequest(source, hasStatus, hasNav);
- takeScreenshot(screenshotType, SCREENSHOT_TIMEOUT_MS, handler, screenshotRequest,
+ public void takeScreenshot(@ScreenshotType int screenshotType, @ScreenshotSource int source,
+ @NonNull Handler handler, @Nullable Consumer<Uri> completionConsumer) {
+ ScreenshotRequest screenshotRequest = new ScreenshotRequest(source);
+ takeScreenshot(screenshotType, handler, screenshotRequest, SCREENSHOT_TIMEOUT_MS,
completionConsumer);
}
/**
- * Request a screenshot be taken, with provided reason.
- *
- * @param screenshotType The type of screenshot, for example either
- * {@link android.view.WindowManager#TAKE_SCREENSHOT_FULLSCREEN}
- * or
- * {@link android.view.WindowManager#TAKE_SCREENSHOT_SELECTED_REGION}
- * @param hasStatus {@code true} if the status bar is currently showing. {@code false}
- * if
- * not.
- * @param hasNav {@code true} if the navigation bar is currently showing. {@code
- * false}
- * if not.
- * @param handler A handler used in case the screenshot times out
- * @param completionConsumer Consumes `false` if a screenshot was not taken, and `true` if the
- * screenshot was taken.
- */
- public void takeScreenshot(final int screenshotType, final boolean hasStatus,
- final boolean hasNav, @NonNull Handler handler,
- @Nullable Consumer<Uri> completionConsumer) {
- takeScreenshot(screenshotType, hasStatus, hasNav, SCREENSHOT_TIMEOUT_MS, handler,
- completionConsumer);
- }
-
- /**
- * Request a screenshot be taken with a specific timeout.
- *
+ * Request a screenshot be taken.
+ * <p>
* Added to support reducing unit test duration; the method variant without a timeout argument
* is recommended for general use.
*
- * @param screenshotType The type of screenshot, for example either
- * {@link android.view.WindowManager#TAKE_SCREENSHOT_FULLSCREEN}
- * or
- * {@link android.view.WindowManager#TAKE_SCREENSHOT_SELECTED_REGION}
- * @param hasStatus {@code true} if the status bar is currently showing. {@code false}
- * if
- * not.
- * @param hasNav {@code true} if the navigation bar is currently showing. {@code
- * false}
- * if not.
- * @param timeoutMs If the screenshot hasn't been completed within this time period,
- * the screenshot attempt will be cancelled and `completionConsumer`
- * will be run.
- * @param handler A handler used in case the screenshot times out
- * @param completionConsumer Consumes `false` if a screenshot was not taken, and `true` if the
- * screenshot was taken.
+ * @param screenshotType The type of screenshot, defined by {@link ScreenshotType}
+ * @param source The source of the screenshot request, defined by {@link ScreenshotSource}
+ * @param handler used to process messages received from the screenshot service
+ * @param timeoutMs time limit for processing, intended only for testing
+ * @param completionConsumer receives the URI of the captured screenshot, once saved or
+ * null if no screenshot was saved
*/
- public void takeScreenshot(final int screenshotType, final boolean hasStatus,
- final boolean hasNav, long timeoutMs, @NonNull Handler handler,
- @Nullable Consumer<Uri> completionConsumer) {
- ScreenshotRequest screenshotRequest = new ScreenshotRequest(SCREENSHOT_OTHER, hasStatus,
- hasNav);
- takeScreenshot(screenshotType, timeoutMs, handler, screenshotRequest, completionConsumer);
+ @VisibleForTesting
+ public void takeScreenshot(@ScreenshotType int screenshotType, @ScreenshotSource int source,
+ @NonNull Handler handler, long timeoutMs, @Nullable Consumer<Uri> completionConsumer) {
+ ScreenshotRequest screenshotRequest = new ScreenshotRequest(source);
+ takeScreenshot(screenshotType, handler, screenshotRequest, timeoutMs, completionConsumer);
}
/**
* Request that provided image be handled as if it was a screenshot.
*
- * @param screenshotBundle Bundle containing the buffer and color space of the screenshot.
- * @param boundsInScreen The bounds in screen coordinates that the bitmap orginated from.
- * @param insets The insets that the image was shown with, inside the screenbounds.
- * @param taskId The taskId of the task that the screen shot was taken of.
- * @param userId The userId of user running the task provided in taskId.
- * @param topComponent The component name of the top component running in the task.
- * @param handler A handler used in case the screenshot times out
- * @param completionConsumer Consumes `false` if a screenshot was not taken, and `true` if the
- * screenshot was taken.
+ * @param screenshotBundle Bundle containing the buffer and color space of the screenshot.
+ * @param boundsInScreen The bounds in screen coordinates that the bitmap originated from.
+ * @param insets The insets that the image was shown with, inside the screen bounds.
+ * @param taskId The taskId of the task that the screen shot was taken of.
+ * @param userId The userId of user running the task provided in taskId.
+ * @param topComponent The component name of the top component running in the task.
+ * @param source The source of the screenshot request, defined by {@link ScreenshotSource}
+ * @param handler A handler used in case the screenshot times out
+ * @param completionConsumer receives the URI of the captured screenshot, once saved or
+ * null if no screenshot was saved
*/
public void provideScreenshot(@NonNull Bundle screenshotBundle, @NonNull Rect boundsInScreen,
- @NonNull Insets insets, int taskId, int userId, ComponentName topComponent, int source,
- @NonNull Handler handler, @Nullable Consumer<Uri> completionConsumer) {
- ScreenshotRequest screenshotRequest =
- new ScreenshotRequest(source, screenshotBundle, boundsInScreen, insets, taskId,
- userId, topComponent);
- takeScreenshot(WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_TIMEOUT_MS,
- handler, screenshotRequest, completionConsumer);
+ @NonNull Insets insets, int taskId, int userId, ComponentName topComponent,
+ @ScreenshotSource int source, @NonNull Handler handler,
+ @Nullable Consumer<Uri> completionConsumer) {
+ ScreenshotRequest screenshotRequest = new ScreenshotRequest(source, screenshotBundle,
+ boundsInScreen, insets, taskId, userId, topComponent);
+ takeScreenshot(WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE, handler, screenshotRequest,
+ SCREENSHOT_TIMEOUT_MS,
+ completionConsumer);
}
- private void takeScreenshot(final int screenshotType, long timeoutMs, @NonNull Handler handler,
- ScreenshotRequest screenshotRequest, @Nullable Consumer<Uri> completionConsumer) {
+ private void takeScreenshot(@ScreenshotType int screenshotType, @NonNull Handler handler,
+ ScreenshotRequest screenshotRequest, long timeoutMs,
+ @Nullable Consumer<Uri> completionConsumer) {
synchronized (mScreenshotLock) {
final Runnable mScreenshotTimeout = () -> {
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index cc076ab..3e0a6cb 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -166,10 +166,6 @@
private static final String ENABLED_TRUST_AGENTS = "lockscreen.enabledtrustagents";
private static final String IS_TRUST_USUALLY_MANAGED = "lockscreen.istrustusuallymanaged";
- public static final String PROFILE_KEY_NAME_ENCRYPT = "profile_key_name_encrypt_";
- public static final String PROFILE_KEY_NAME_DECRYPT = "profile_key_name_decrypt_";
- public static final String SYNTHETIC_PASSWORD_KEY_PREFIX = "synthetic_password_";
-
public static final String CURRENT_LSKF_BASED_PROTECTOR_ID_KEY = "sp-handle";
public static final String PASSWORD_HISTORY_DELIMITER = ",";
diff --git a/core/java/com/android/internal/widget/LockscreenCredential.java b/core/java/com/android/internal/widget/LockscreenCredential.java
index 40164a4..9f3f335 100644
--- a/core/java/com/android/internal/widget/LockscreenCredential.java
+++ b/core/java/com/android/internal/widget/LockscreenCredential.java
@@ -29,6 +29,7 @@
import android.os.storage.StorageManager;
import android.text.TextUtils;
+import com.android.internal.util.ArrayUtils;
import com.android.internal.util.Preconditions;
import libcore.util.HexEncoding;
@@ -278,58 +279,36 @@
try {
MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
sha256.update(hashFactor);
- byte[] saltedPassword = Arrays.copyOf(passwordToHash, passwordToHash.length
- + salt.length);
- System.arraycopy(salt, 0, saltedPassword, passwordToHash.length, salt.length);
- sha256.update(saltedPassword);
- Arrays.fill(saltedPassword, (byte) 0);
- return new String(HexEncoding.encode(sha256.digest()));
+ sha256.update(passwordToHash);
+ sha256.update(salt);
+ return HexEncoding.encodeToString(sha256.digest());
} catch (NoSuchAlgorithmException e) {
throw new AssertionError("Missing digest algorithm: ", e);
}
}
/**
- * Generate a hash for the given password. To avoid brute force attacks, we use a salted hash.
- * Not the most secure, but it is at least a second level of protection. First level is that
- * the file is in a location only readable by the system process.
+ * Hash the given password for the password history, using the legacy algorithm.
*
- * @return the hash of the pattern in a byte array.
+ * @deprecated This algorithm is insecure because the password can be easily bruteforced, given
+ * the hash and salt. Use {@link #passwordToHistoryHash(byte[], byte[], byte[])}
+ * instead, which incorporates an SP-derived secret into the hash.
+ *
+ * @return the legacy password hash
*/
- public String legacyPasswordToHash(byte[] salt) {
- return legacyPasswordToHash(mCredential, salt);
- }
-
- /**
- * Generate a hash for the given password. To avoid brute force attacks, we use a salted hash.
- * Not the most secure, but it is at least a second level of protection. First level is that
- * the file is in a location only readable by the system process.
- *
- * @param password the gesture pattern.
- *
- * @return the hash of the pattern in a byte array.
- */
+ @Deprecated
public static String legacyPasswordToHash(byte[] password, byte[] salt) {
if (password == null || password.length == 0 || salt == null) {
return null;
}
try {
- // Previously the password was passed as a String with the following code:
- // byte[] saltedPassword = (password + salt).getBytes();
- // The code below creates the identical digest preimage using byte arrays:
- byte[] saltedPassword = Arrays.copyOf(password, password.length + salt.length);
- System.arraycopy(salt, 0, saltedPassword, password.length, salt.length);
+ byte[] saltedPassword = ArrayUtils.concat(password, salt);
byte[] sha1 = MessageDigest.getInstance("SHA-1").digest(saltedPassword);
byte[] md5 = MessageDigest.getInstance("MD5").digest(saltedPassword);
- byte[] combined = new byte[sha1.length + md5.length];
- System.arraycopy(sha1, 0, combined, 0, sha1.length);
- System.arraycopy(md5, 0, combined, sha1.length, md5.length);
-
- final char[] hexEncoded = HexEncoding.encode(combined);
Arrays.fill(saltedPassword, (byte) 0);
- return new String(hexEncoded);
+ return HexEncoding.encodeToString(ArrayUtils.concat(sha1, md5));
} catch (NoSuchAlgorithmException e) {
throw new AssertionError("Missing digest algorithm: ", e);
}
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 5cb0de3..cbc3462 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -2127,6 +2127,8 @@
// Reset the fd to the unsolicited zygote socket
gSystemServerSocketFd = -1;
+ } else if (pid == -1) {
+ ALOGE("Failed to fork child process: %s (%d)", strerror(errno), errno);
} else {
ALOGD("Forked child process %d", pid);
}
diff --git a/core/res/res/drawable/ic_sd_card_48dp.xml b/core/res/res/drawable/ic_sd_card_48dp.xml
index 90bab47..10fd120 100644
--- a/core/res/res/drawable/ic_sd_card_48dp.xml
+++ b/core/res/res/drawable/ic_sd_card_48dp.xml
@@ -19,6 +19,6 @@
android:viewportWidth="48.0"
android:viewportHeight="48.0">
<path
- android:fillColor="#FF000000"
+ android:fillColor="?android:attr/colorAccent"
android:pathData="M36 4H20L8.04 16 8 40c0 2.2 1.8 4 4 4h24c2.2 0 4,-1.8 4,-4V8c0,-2.2,-1.8,-4,-4,-4zM24 16h-4V8h4v8zm6 0h-4V8h4v8zm6 0h-4V8h4v8z"/>
</vector>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index d3dc301..646dedd 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -3971,7 +3971,7 @@
<string name="ext_media_new_notification_message" product="automotive">You may need to reformat the device. Tap to eject.</string>
<!-- Notification body when external media is ready for use [CHAR LIMIT=NONE] -->
- <string name="ext_media_ready_notification_message">For transferring photos and media</string>
+ <string name="ext_media_ready_notification_message">For storing photos, videos, music and more</string>
<!-- TV specific notification body when external media is ready for use [CHAR LIMIT=75] -->
<string name="ext_media_ready_notification_message" product="tv">Browse media files</string>
@@ -3987,11 +3987,11 @@
<string name="ext_media_unmountable_notification_message" product="automotive">You may need to reformat the device. Tap to eject.</string>
<!-- Notification title when external media is unsupported [CHAR LIMIT=30] -->
- <string name="ext_media_unsupported_notification_title">Unsupported <xliff:g id="name" example="SD card">%s</xliff:g></string>
+ <string name="ext_media_unsupported_notification_title"><xliff:g id="name" example="SD card">%s</xliff:g> detected </string>
<!-- Automotive specific notification title when external media is unsupported [CHAR LIMIT=30] -->
<string name="ext_media_unsupported_notification_title" product="automotive"><xliff:g id="name" example="SD card">%s</xliff:g> isn\u2019t working</string>
<!-- Notification body when external media is unsupported [CHAR LIMIT=NONE] -->
- <string name="ext_media_unsupported_notification_message">This device doesn\u2019t support this <xliff:g id="name" example="SD card">%s</xliff:g>. Tap to set up in a supported format.</string>
+ <string name="ext_media_unsupported_notification_message">Tap to set up .</string>
<!-- TV-specific notification body when external media is unsupported [CHAR LIMIT=75] -->
<string name="ext_media_unsupported_notification_message" product="tv">Select to set up <xliff:g id="name" example="SD card">%s</xliff:g> in a supported format.</string>
<!-- Automotive specific notification body when external media is unsupported [CHAR LIMIT=NONE] -->
@@ -4022,7 +4022,7 @@
<!-- Notification action to transfer media [CHAR LIMIT=40] -->
<string name="ext_media_seamless_action">Switch output</string>
- <!-- Notification title when external media is missing [CHAR LIMIT=30] -->
+ <!-- Notification title when adoptable storage media is ejected [CHAR LIMIT=30] -->
<string name="ext_media_missing_title"><xliff:g id="name" example="SD card">%s</xliff:g> missing</string>
<!-- Notification body when external media is missing [CHAR LIMIT=30] -->
<string name="ext_media_missing_message">Insert device again</string>
diff --git a/core/tests/coretests/src/android/database/SQLiteOpenHelperTest.java b/core/tests/coretests/src/android/database/SQLiteOpenHelperTest.java
index 730a3cb..9bad6d2 100644
--- a/core/tests/coretests/src/android/database/SQLiteOpenHelperTest.java
+++ b/core/tests/coretests/src/android/database/SQLiteOpenHelperTest.java
@@ -161,7 +161,7 @@
boolean dbStatFound = false;
SQLiteDebug.PagerStats info = SQLiteDebug.getDatabaseInfo();
for (SQLiteDebug.DbStats dbStat : info.dbStats) {
- if (dbStat.dbName.endsWith(dbName)) {
+ if (dbStat.dbName.endsWith(dbName) && !dbStat.arePoolStats) {
dbStatFound = true;
Log.i(TAG, "Lookaside for " + dbStat.dbName + " " + dbStat.lookaside);
if (expectDisabled) {
diff --git a/core/tests/coretests/src/com/android/internal/widget/LockscreenCredentialTest.java b/core/tests/coretests/src/com/android/internal/widget/LockscreenCredentialTest.java
index 9d77d16..a47868d 100644
--- a/core/tests/coretests/src/com/android/internal/widget/LockscreenCredentialTest.java
+++ b/core/tests/coretests/src/com/android/internal/widget/LockscreenCredentialTest.java
@@ -223,15 +223,11 @@
public void testLegacyPasswordToHash() {
String password = "1234";
- LockscreenCredential credential = LockscreenCredential.createPassword(password);
String salt = "6d5331dd120077a0";
String expectedHash =
"2DD04348ADBF8F4CABD7F722DC2E2887FAD4B6020A0C3E02C831E09946F0554FDC13B155";
assertThat(
- credential.legacyPasswordToHash(salt.getBytes()))
- .isEqualTo(expectedHash);
- assertThat(
LockscreenCredential.legacyPasswordToHash(
password.getBytes(), salt.getBytes()))
.isEqualTo(expectedHash);
@@ -239,10 +235,8 @@
public void testLegacyPasswordToHashInvalidInput() {
String password = "1234";
- LockscreenCredential credential = LockscreenCredential.createPassword(password);
String salt = "6d5331dd120077a0";
- assertThat(credential.legacyPasswordToHash(/* salt= */ null)).isNull();
assertThat(LockscreenCredential.legacyPasswordToHash(
password.getBytes(), /* salt= */ null)).isNull();
diff --git a/core/tests/screenshothelpertests/OWNERS b/core/tests/screenshothelpertests/OWNERS
new file mode 100644
index 0000000..23dc8fb
--- /dev/null
+++ b/core/tests/screenshothelpertests/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 801321
+file:/packages/SystemUI/src/com/android/systemui/screenshot/OWNERS
diff --git a/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotHelperTest.java b/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotHelperTest.java
index 4b81737..fd4fb13 100644
--- a/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotHelperTest.java
+++ b/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotHelperTest.java
@@ -80,13 +80,14 @@
@Test
public void testFullscreenScreenshot() {
- mScreenshotHelper.takeScreenshot(TAKE_SCREENSHOT_FULLSCREEN, false, false, mHandler, null);
+ mScreenshotHelper.takeScreenshot(TAKE_SCREENSHOT_FULLSCREEN,
+ WindowManager.ScreenshotSource.SCREENSHOT_OTHER, mHandler, null);
}
@Test
public void testSelectedRegionScreenshot() {
- mScreenshotHelper.takeScreenshot(TAKE_SCREENSHOT_SELECTED_REGION, false, false, mHandler,
- null);
+ mScreenshotHelper.takeScreenshot(TAKE_SCREENSHOT_SELECTED_REGION,
+ WindowManager.ScreenshotSource.SCREENSHOT_OTHER, mHandler, null);
}
@Test
@@ -101,8 +102,10 @@
long timeoutMs = 10;
CountDownLatch lock = new CountDownLatch(1);
- mScreenshotHelper.takeScreenshot(TAKE_SCREENSHOT_FULLSCREEN, false, false, timeoutMs,
+ mScreenshotHelper.takeScreenshot(TAKE_SCREENSHOT_FULLSCREEN,
+ WindowManager.ScreenshotSource.SCREENSHOT_OTHER,
mHandler,
+ timeoutMs,
uri -> {
assertNull(uri);
lock.countDown();
diff --git a/core/tests/utiltests/src/com/android/internal/util/ArrayUtilsTest.java b/core/tests/utiltests/src/com/android/internal/util/ArrayUtilsTest.java
index cb30b3f..c66a743 100644
--- a/core/tests/utiltests/src/com/android/internal/util/ArrayUtilsTest.java
+++ b/core/tests/utiltests/src/com/android/internal/util/ArrayUtilsTest.java
@@ -16,8 +16,6 @@
package com.android.internal.util;
-import static com.android.internal.util.ArrayUtils.concatElements;
-
import static org.junit.Assert.assertArrayEquals;
import junit.framework.TestCase;
@@ -156,61 +154,107 @@
ArrayUtils.removeLong(new long[] { 1, 2, 3, 1 }, 1));
}
- public void testConcatEmpty() throws Exception {
- assertArrayEquals(new Long[] {},
- concatElements(Long.class, null, null));
- assertArrayEquals(new Long[] {},
- concatElements(Long.class, new Long[] {}, null));
- assertArrayEquals(new Long[] {},
- concatElements(Long.class, null, new Long[] {}));
- assertArrayEquals(new Long[] {},
- concatElements(Long.class, new Long[] {}, new Long[] {}));
+ public void testConcat_zeroObjectArrays() {
+ // empty varargs array
+ assertArrayEquals(new String[] {}, ArrayUtils.concat(String.class));
+ // null varargs array
+ assertArrayEquals(new String[] {}, ArrayUtils.concat(String.class, (String[][]) null));
}
- public void testconcatElements() throws Exception {
+ public void testConcat_oneObjectArray() {
+ assertArrayEquals(new String[] { "1", "2" },
+ ArrayUtils.concat(String.class, new String[] { "1", "2" }));
+ }
+
+ public void testConcat_oneEmptyObjectArray() {
+ assertArrayEquals(new String[] {}, ArrayUtils.concat(String.class, (String[]) null));
+ assertArrayEquals(new String[] {}, ArrayUtils.concat(String.class, new String[] {}));
+ }
+
+ public void testConcat_twoObjectArrays() {
assertArrayEquals(new Long[] { 1L },
- concatElements(Long.class, new Long[] { 1L }, new Long[] {}));
+ ArrayUtils.concat(Long.class, new Long[] { 1L }, new Long[] {}));
assertArrayEquals(new Long[] { 1L },
- concatElements(Long.class, new Long[] {}, new Long[] { 1L }));
+ ArrayUtils.concat(Long.class, new Long[] {}, new Long[] { 1L }));
assertArrayEquals(new Long[] { 1L, 2L },
- concatElements(Long.class, new Long[] { 1L }, new Long[] { 2L }));
+ ArrayUtils.concat(Long.class, new Long[] { 1L }, new Long[] { 2L }));
assertArrayEquals(new Long[] { 1L, 2L, 3L, 4L },
- concatElements(Long.class, new Long[] { 1L, 2L }, new Long[] { 3L, 4L }));
+ ArrayUtils.concat(Long.class, new Long[] { 1L, 2L }, new Long[] { 3L, 4L }));
}
- public void testConcatElements_threeWay() {
+ public void testConcat_twoEmptyObjectArrays() {
+ assertArrayEquals(new Long[] {}, ArrayUtils.concat(Long.class, null, null));
+ assertArrayEquals(new Long[] {}, ArrayUtils.concat(Long.class, new Long[] {}, null));
+ assertArrayEquals(new Long[] {}, ArrayUtils.concat(Long.class, null, new Long[] {}));
+ assertArrayEquals(new Long[] {},
+ ArrayUtils.concat(Long.class, new Long[] {}, new Long[] {}));
+ }
+
+ public void testConcat_threeObjectArrays() {
String[] array1 = { "1", "2" };
String[] array2 = { "3", "4" };
String[] array3 = { "5", "6" };
- String[] expectation = {"1", "2", "3", "4", "5", "6"};
+ String[] expectation = { "1", "2", "3", "4", "5", "6" };
- String[] concatResult = ArrayUtils.concatElements(String.class, array1, array2, array3);
- assertArrayEquals(expectation, concatResult);
+ assertArrayEquals(expectation, ArrayUtils.concat(String.class, array1, array2, array3));
}
-
- public void testConcatElements_threeWayWithNull() {
+ public void testConcat_threeObjectArraysWithNull() {
String[] array1 = { "1", "2" };
String[] array2 = null;
String[] array3 = { "5", "6" };
- String[] expectation = {"1", "2", "5", "6"};
+ String[] expectation = { "1", "2", "5", "6" };
- String[] concatResult = ArrayUtils.concatElements(String.class, array1, array2, array3);
- assertArrayEquals(expectation, concatResult);
+ assertArrayEquals(expectation, ArrayUtils.concat(String.class, array1, array2, array3));
}
- public void testConcatElements_zeroElements() {
- String[] expectation = new String[0];
-
- String[] concatResult = ArrayUtils.concatElements(String.class);
- assertArrayEquals(expectation, concatResult);
+ public void testConcat_zeroByteArrays() {
+ // empty varargs array
+ assertArrayEquals(new byte[] {}, ArrayUtils.concat());
+ // null varargs array
+ assertArrayEquals(new byte[] {}, ArrayUtils.concat((byte[][]) null));
}
- public void testConcatElements_oneNullElement() {
- String[] expectation = new String[0];
-
- String[] concatResult = ArrayUtils.concatElements(String.class, null);
- assertArrayEquals(expectation, concatResult);
+ public void testConcat_oneByteArray() {
+ assertArrayEquals(new byte[] { 1, 2 }, ArrayUtils.concat(new byte[] { 1, 2 }));
}
+ public void testConcat_oneEmptyByteArray() {
+ assertArrayEquals(new byte[] {}, ArrayUtils.concat((byte[]) null));
+ assertArrayEquals(new byte[] {}, ArrayUtils.concat(new byte[] {}));
+ }
+
+ public void testConcat_twoByteArrays() {
+ assertArrayEquals(new byte[] { 1 }, ArrayUtils.concat(new byte[] { 1 }, new byte[] {}));
+ assertArrayEquals(new byte[] { 1 }, ArrayUtils.concat(new byte[] {}, new byte[] { 1 }));
+ assertArrayEquals(new byte[] { 1, 2 },
+ ArrayUtils.concat(new byte[] { 1 }, new byte[] { 2 }));
+ assertArrayEquals(new byte[] { 1, 2, 3, 4 },
+ ArrayUtils.concat(new byte[] { 1, 2 }, new byte[] { 3, 4 }));
+ }
+
+ public void testConcat_twoEmptyByteArrays() {
+ assertArrayEquals(new byte[] {}, ArrayUtils.concat((byte[]) null, null));
+ assertArrayEquals(new byte[] {}, ArrayUtils.concat(new byte[] {}, null));
+ assertArrayEquals(new byte[] {}, ArrayUtils.concat((byte[]) null, new byte[] {}));
+ assertArrayEquals(new byte[] {}, ArrayUtils.concat(new byte[] {}, new byte[] {}));
+ }
+
+ public void testConcat_threeByteArrays() {
+ byte[] array1 = { 1, 2 };
+ byte[] array2 = { 3, 4 };
+ byte[] array3 = { 5, 6 };
+ byte[] expectation = { 1, 2, 3, 4, 5, 6 };
+
+ assertArrayEquals(expectation, ArrayUtils.concat(array1, array2, array3));
+ }
+
+ public void testConcat_threeByteArraysWithNull() {
+ byte[] array1 = { 1, 2 };
+ byte[] array2 = null;
+ byte[] array3 = { 5, 6 };
+ byte[] expectation = { 1, 2, 5, 6 };
+
+ assertArrayEquals(expectation, ArrayUtils.concat(array1, array2, array3));
+ }
}
diff --git a/data/etc/platform.xml b/data/etc/platform.xml
index 6897c01..9a1b8a9 100644
--- a/data/etc/platform.xml
+++ b/data/etc/platform.xml
@@ -171,10 +171,11 @@
<assign-permission name="android.permission.UPDATE_DEVICE_STATS" uid="audioserver" />
<assign-permission name="android.permission.UPDATE_APP_OPS_STATS" uid="audioserver" />
<assign-permission name="android.permission.PACKAGE_USAGE_STATS" uid="audioserver" />
- <assign-permission name="android.permission.INTERACT_ACROSS_USERS" uid="audioserver" />
+ <assign-permission name="android.permission.INTERACT_ACROSS_USERS_FULL" uid="audioserver" />
<assign-permission name="android.permission.OBSERVE_SENSOR_PRIVACY" uid="audioserver" />
<assign-permission name="android.permission.MODIFY_AUDIO_SETTINGS" uid="cameraserver" />
+ <assign-permission name="android.permission.INTERACT_ACROSS_USERS_FULL" uid="cameraserver" />
<assign-permission name="android.permission.ACCESS_SURFACE_FLINGER" uid="cameraserver" />
<assign-permission name="android.permission.WAKE_LOCK" uid="cameraserver" />
<assign-permission name="android.permission.UPDATE_DEVICE_STATS" uid="cameraserver" />
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index 80cf8c3..d3dc7e8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -30,15 +30,20 @@
import android.graphics.Point;
import android.graphics.PointF;
import android.hardware.HardwareBuffer;
+import android.hardware.input.InputManager;
import android.net.Uri;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
+import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.provider.Settings.Global;
import android.util.Log;
import android.view.IWindowFocusObserver;
+import android.view.InputDevice;
+import android.view.KeyCharacterMap;
+import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
@@ -295,6 +300,9 @@
} else if (keyAction == MotionEvent.ACTION_UP || keyAction == MotionEvent.ACTION_CANCEL) {
ProtoLog.d(WM_SHELL_BACK_PREVIEW,
"Finishing gesture with event action: %d", keyAction);
+ if (keyAction == MotionEvent.ACTION_CANCEL) {
+ mTriggerBack = false;
+ }
onGestureFinished(true);
}
}
@@ -324,7 +332,6 @@
ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Received backNavigationInfo:%s", backNavigationInfo);
if (backNavigationInfo == null) {
Log.e(TAG, "Received BackNavigationInfo is null.");
- finishAnimation();
return;
}
int backType = backNavigationInfo.getType();
@@ -400,6 +407,25 @@
dispatchOnBackProgressed(targetCallback, backEvent);
}
+ private void injectBackKey() {
+ sendBackEvent(KeyEvent.ACTION_DOWN);
+ sendBackEvent(KeyEvent.ACTION_UP);
+ }
+
+ private void sendBackEvent(int action) {
+ final long when = SystemClock.uptimeMillis();
+ final KeyEvent ev = new KeyEvent(when, when, action, KeyEvent.KEYCODE_BACK, 0 /* repeat */,
+ 0 /* metaState */, KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /* scancode */,
+ KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY,
+ InputDevice.SOURCE_KEYBOARD);
+
+ ev.setDisplayId(mContext.getDisplay().getDisplayId());
+ if (!InputManager.getInstance()
+ .injectInputEvent(ev, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC)) {
+ Log.e(TAG, "Inject input event fail");
+ }
+ }
+
private void onGestureFinished(boolean fromTouch) {
ProtoLog.d(WM_SHELL_BACK_PREVIEW, "onGestureFinished() mTriggerBack == %s", mTriggerBack);
if (fromTouch) {
@@ -408,7 +434,17 @@
mBackGestureStarted = false;
}
- if (mTransitionInProgress || mBackNavigationInfo == null) {
+ if (mTransitionInProgress) {
+ return;
+ }
+
+ if (mBackNavigationInfo == null) {
+ // No focus window found or core are running recents animation, inject back key as
+ // legacy behavior.
+ if (mTriggerBack) {
+ injectBackKey();
+ }
+ finishAnimation();
return;
}
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 d7f1292..1c2f0d8 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
@@ -70,12 +70,10 @@
import android.os.UserManager;
import android.service.notification.NotificationListenerService;
import android.service.notification.NotificationListenerService.RankingMap;
-import android.util.ArraySet;
import android.util.Log;
import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
-import android.util.SparseSetArray;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowInsets;
@@ -109,8 +107,10 @@
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
import java.util.Optional;
+import java.util.Set;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
import java.util.function.IntConsumer;
@@ -177,8 +177,8 @@
private int mCurrentUserId;
// Current profiles of the user (e.g. user with a workprofile)
private SparseArray<UserInfo> mCurrentProfiles;
- // Saves notification keys of active bubbles when users are switched.
- private final SparseSetArray<String> mSavedBubbleKeysPerUser;
+ // Saves data about active bubbles when users are switched.
+ private final SparseArray<UserBubbleData> mSavedUserBubbleData;
// Used when ranking updates occur and we check if things should bubble / unbubble
private NotificationListenerService.Ranking mTmpRanking;
@@ -271,7 +271,7 @@
mCurrentUserId = ActivityManager.getCurrentUser();
mBubblePositioner = positioner;
mBubbleData = data;
- mSavedBubbleKeysPerUser = new SparseSetArray<>();
+ mSavedUserBubbleData = new SparseArray<>();
mBubbleIconFactory = new BubbleIconFactory(context);
mBubbleBadgeIconFactory = new BubbleBadgeIconFactory(context);
mDisplayController = displayController;
@@ -420,6 +420,13 @@
List<UserInfo> users = mUserManager.getAliveUsers();
mDataRepository.sanitizeBubbles(users);
+ // Init profiles
+ SparseArray<UserInfo> userProfiles = new SparseArray<>();
+ for (UserInfo user : mUserManager.getProfiles(mCurrentUserId)) {
+ userProfiles.put(user.id, user);
+ }
+ mCurrentProfiles = userProfiles;
+
mShellController.addConfigurationChangeListener(this);
}
@@ -774,11 +781,13 @@
*/
private void saveBubbles(@UserIdInt int userId) {
// First clear any existing keys that might be stored.
- mSavedBubbleKeysPerUser.remove(userId);
+ mSavedUserBubbleData.remove(userId);
+ UserBubbleData userBubbleData = new UserBubbleData();
// Add in all active bubbles for the current user.
for (Bubble bubble : mBubbleData.getBubbles()) {
- mSavedBubbleKeysPerUser.add(userId, bubble.getKey());
+ userBubbleData.add(bubble.getKey(), bubble.showInShade());
}
+ mSavedUserBubbleData.put(userId, userBubbleData);
}
/**
@@ -787,22 +796,23 @@
* @param userId the id of the user
*/
private void restoreBubbles(@UserIdInt int userId) {
- ArraySet<String> savedBubbleKeys = mSavedBubbleKeysPerUser.get(userId);
- if (savedBubbleKeys == null) {
+ UserBubbleData savedBubbleData = mSavedUserBubbleData.get(userId);
+ if (savedBubbleData == null) {
// There were no bubbles saved for this used.
return;
}
- mSysuiProxy.getShouldRestoredEntries(savedBubbleKeys, (entries) -> {
+ mSysuiProxy.getShouldRestoredEntries(savedBubbleData.getKeys(), (entries) -> {
mMainExecutor.execute(() -> {
for (BubbleEntry e : entries) {
if (canLaunchInTaskView(mContext, e)) {
- updateBubble(e, true /* suppressFlyout */, false /* showInShade */);
+ boolean showInShade = savedBubbleData.isShownInShade(e.getKey());
+ updateBubble(e, true /* suppressFlyout */, showInShade);
}
}
});
});
// Finally, remove the entries for this user now that bubbles are restored.
- mSavedBubbleKeysPerUser.remove(userId);
+ mSavedUserBubbleData.remove(userId);
}
@Override
@@ -993,7 +1003,19 @@
*/
@VisibleForTesting
public void updateBubble(BubbleEntry notif) {
- updateBubble(notif, false /* suppressFlyout */, true /* showInShade */);
+ int bubbleUserId = notif.getStatusBarNotification().getUserId();
+ if (isCurrentProfile(bubbleUserId)) {
+ updateBubble(notif, false /* suppressFlyout */, true /* showInShade */);
+ } else {
+ // Skip update, but store it in user bubbles so it gets restored after user switch
+ mSavedUserBubbleData.get(bubbleUserId, new UserBubbleData()).add(notif.getKey(),
+ true /* shownInShade */);
+ if (DEBUG_BUBBLE_CONTROLLER) {
+ Log.d(TAG,
+ "Ignore update to bubble for not active user. Bubble userId=" + bubbleUserId
+ + " current userId=" + mCurrentUserId);
+ }
+ }
}
/**
@@ -1842,4 +1864,33 @@
}
}
}
+
+ /**
+ * Bubble data that is stored per user.
+ * Used to store and restore active bubbles during user switching.
+ */
+ private static class UserBubbleData {
+ private final Map<String, Boolean> mKeyToShownInShadeMap = new HashMap<>();
+
+ /**
+ * Add bubble key and whether it should be shown in notification shade
+ */
+ void add(String key, boolean shownInShade) {
+ mKeyToShownInShadeMap.put(key, shownInShade);
+ }
+
+ /**
+ * Get all bubble keys stored for this user
+ */
+ Set<String> getKeys() {
+ return mKeyToShownInShadeMap.keySet();
+ }
+
+ /**
+ * Check if this bubble with the given key should be shown in the notification shade
+ */
+ boolean isShownInShade(String key) {
+ return mKeyToShownInShadeMap.get(key);
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
index f8ccf23..cf792cd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
@@ -23,12 +23,10 @@
import android.app.NotificationChannel;
import android.content.pm.UserInfo;
-import android.content.res.Configuration;
import android.os.Bundle;
import android.os.UserHandle;
import android.service.notification.NotificationListenerService;
import android.service.notification.NotificationListenerService.RankingMap;
-import android.util.ArraySet;
import android.util.Pair;
import android.util.SparseArray;
@@ -42,6 +40,7 @@
import java.lang.annotation.Target;
import java.util.HashMap;
import java.util.List;
+import java.util.Set;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
import java.util.function.IntConsumer;
@@ -284,7 +283,7 @@
void getPendingOrActiveEntry(String key, Consumer<BubbleEntry> callback);
- void getShouldRestoredEntries(ArraySet<String> savedBubbleKeys,
+ void getShouldRestoredEntries(Set<String> savedBubbleKeys,
Consumer<List<BubbleEntry>> callback);
void setNotificationInterruption(String key);
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 7fb961f..2008a75 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
@@ -32,6 +32,7 @@
import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
import android.app.ActivityManager;
+import android.app.ActivityOptions;
import android.app.ActivityTaskManager;
import android.app.PendingIntent;
import android.content.ActivityNotFoundException;
@@ -46,6 +47,7 @@
import android.util.ArrayMap;
import android.util.Slog;
import android.view.IRemoteAnimationFinishedCallback;
+import android.view.IRemoteAnimationRunner;
import android.view.RemoteAnimationAdapter;
import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
@@ -334,17 +336,37 @@
public void startShortcut(String packageName, String shortcutId, @SplitPosition int position,
@Nullable Bundle options, UserHandle user) {
+ IRemoteAnimationRunner wrapper = new IRemoteAnimationRunner.Stub() {
+ @Override
+ public void onAnimationStart(@WindowManager.TransitionOldType int transit,
+ RemoteAnimationTarget[] apps,
+ RemoteAnimationTarget[] wallpapers,
+ RemoteAnimationTarget[] nonApps,
+ final IRemoteAnimationFinishedCallback finishedCallback) {
+ try {
+ finishedCallback.onAnimationFinished();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to invoke onAnimationFinished", e);
+ }
+ final WindowContainerTransaction evictWct = new WindowContainerTransaction();
+ mStageCoordinator.prepareEvictNonOpeningChildTasks(position, apps, evictWct);
+ mSyncQueue.queue(evictWct);
+ }
+ @Override
+ public void onAnimationCancelled(boolean isKeyguardOccluded) {
+ }
+ };
options = mStageCoordinator.resolveStartStage(STAGE_TYPE_UNDEFINED, position, options,
null /* wct */);
- final WindowContainerTransaction evictWct = new WindowContainerTransaction();
- mStageCoordinator.prepareEvictChildTasks(position, evictWct);
+ RemoteAnimationAdapter wrappedAdapter = new RemoteAnimationAdapter(wrapper,
+ 0 /* duration */, 0 /* statusBarTransitionDelay */);
+ ActivityOptions activityOptions = ActivityOptions.fromBundle(options);
+ activityOptions.update(ActivityOptions.makeRemoteAnimation(wrappedAdapter));
try {
- LauncherApps launcherApps =
- mContext.getSystemService(LauncherApps.class);
+ LauncherApps launcherApps = mContext.getSystemService(LauncherApps.class);
launcherApps.startShortcut(packageName, shortcutId, null /* sourceBounds */,
- options, user);
- mSyncQueue.queue(evictWct);
+ activityOptions.toBundle(), user);
} catch (ActivityNotFoundException e) {
Slog.e(TAG, "Failed to launch shortcut", e);
}
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 3c7db33..2b3b61b 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
@@ -68,7 +68,6 @@
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityOptions;
-import android.app.ActivityTaskManager;
import android.app.PendingIntent;
import android.app.WindowConfiguration;
import android.content.Context;
@@ -522,14 +521,8 @@
finishedCallback.onAnimationFinished();
}
};
+ Transitions.setRunningRemoteTransitionDelegate(adapter.getCallingApplication());
try {
- try {
- ActivityTaskManager.getService().setRunningRemoteTransitionDelegate(
- adapter.getCallingApplication());
- } catch (SecurityException e) {
- Slog.e(TAG, "Unable to boost animation thread. This should only happen"
- + " during unit tests");
- }
adapter.getRunner().onAnimationStart(transit, apps, wallpapers,
augmentedNonApps, wrapCallback);
} catch (RemoteException e) {
@@ -617,6 +610,15 @@
}
}
+ void prepareEvictNonOpeningChildTasks(@SplitPosition int position, RemoteAnimationTarget[] apps,
+ WindowContainerTransaction wct) {
+ if (position == mSideStagePosition) {
+ mSideStage.evictNonOpeningChildren(apps, wct);
+ } else {
+ mMainStage.evictNonOpeningChildren(apps, wct);
+ }
+ }
+
void prepareEvictInvisibleChildTasks(WindowContainerTransaction wct) {
mMainStage.evictInvisibleChildren(wct);
mSideStage.evictInvisibleChildren(wct);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
index f6dc68b..f414d69 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
@@ -20,6 +20,7 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.view.RemoteAnimationTarget.MODE_OPENING;
import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_ACTIVITY_TYPES;
import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_WINDOWING_MODES_WHEN_ACTIVE;
@@ -33,6 +34,7 @@
import android.graphics.Rect;
import android.os.IBinder;
import android.util.SparseArray;
+import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
import android.view.SurfaceSession;
import android.window.WindowContainerToken;
@@ -334,6 +336,19 @@
}
}
+ void evictNonOpeningChildren(RemoteAnimationTarget[] apps, WindowContainerTransaction wct) {
+ final SparseArray<ActivityManager.RunningTaskInfo> toBeEvict = mChildrenTaskInfo.clone();
+ for (int i = 0; i < apps.length; i++) {
+ if (apps[i].mode == MODE_OPENING) {
+ toBeEvict.remove(apps[i].taskId);
+ }
+ }
+ for (int i = toBeEvict.size() - 1; i >= 0; i--) {
+ final ActivityManager.RunningTaskInfo taskInfo = toBeEvict.valueAt(i);
+ wct.reparent(taskInfo.token, null /* parent */, false /* onTop */);
+ }
+ }
+
void evictInvisibleChildren(WindowContainerTransaction wct) {
for (int i = mChildrenTaskInfo.size() - 1; i >= 0; i--) {
final ActivityManager.RunningTaskInfo taskInfo = mChildrenTaskInfo.valueAt(i);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
index ebaece2..4e1fa29 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
@@ -18,11 +18,9 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.app.ActivityTaskManager;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
-import android.util.Slog;
import android.view.SurfaceControl;
import android.window.IRemoteTransition;
import android.window.IRemoteTransitionFinishedCallback;
@@ -87,17 +85,11 @@
});
}
};
+ Transitions.setRunningRemoteTransitionDelegate(mRemote.getAppThread());
try {
if (mRemote.asBinder() != null) {
mRemote.asBinder().linkToDeath(remoteDied, 0 /* flags */);
}
- try {
- ActivityTaskManager.getService().setRunningRemoteTransitionDelegate(
- mRemote.getAppThread());
- } catch (SecurityException e) {
- Slog.e(Transitions.TAG, "Unable to boost animation thread. This should only happen"
- + " during unit tests");
- }
mRemote.getRemoteTransition().startAnimation(transition, info, startTransaction, cb);
// assume that remote will apply the start transaction.
startTransaction.clear();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
index b15c48c..cedb340 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
@@ -18,7 +18,6 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.app.ActivityTaskManager;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.ArrayMap;
@@ -129,15 +128,9 @@
});
}
};
+ Transitions.setRunningRemoteTransitionDelegate(remote.getAppThread());
try {
handleDeath(remote.asBinder(), finishCallback);
- try {
- ActivityTaskManager.getService().setRunningRemoteTransitionDelegate(
- remote.getAppThread());
- } catch (SecurityException e) {
- Log.e(Transitions.TAG, "Unable to boost animation thread. This should only happen"
- + " during unit tests");
- }
remote.getRemoteTransition().startAnimation(transition, info, startTransaction, cb);
// assume that remote will apply the start transaction.
startTransaction.clear();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index 8e36154..881b7a1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -31,6 +31,8 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.ActivityTaskManager;
+import android.app.IApplicationThread;
import android.content.ContentResolver;
import android.content.Context;
import android.database.ContentObserver;
@@ -234,6 +236,19 @@
mRemoteTransitionHandler.removeFiltered(remoteTransition);
}
+ /** Boosts the process priority of remote animation player. */
+ public static void setRunningRemoteTransitionDelegate(IApplicationThread appThread) {
+ if (appThread == null) return;
+ try {
+ ActivityTaskManager.getService().setRunningRemoteTransitionDelegate(appThread);
+ } catch (SecurityException e) {
+ Log.e(TAG, "Unable to boost animation process. This should only happen"
+ + " during unit tests");
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
/**
* Runs the given {@code runnable} when the last active transition has finished, or immediately
* if there are currently no active transitions.
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
index c1d743b..1e4d23c 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
@@ -47,6 +47,14 @@
layerBecomesVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
}
+fun FlickerTestParameter.splitScreenDividerBecomesInvisible() {
+ assertLayers {
+ this.isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
+ .then()
+ .isInvisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
+ }
+}
+
fun FlickerTestParameter.layerBecomesVisible(
component: IComponentMatcher
) {
@@ -57,6 +65,16 @@
}
}
+fun FlickerTestParameter.layerBecomesInvisible(
+ component: IComponentMatcher
+) {
+ assertLayers {
+ this.isVisible(component)
+ .then()
+ .isInvisible(component)
+ }
+}
+
fun FlickerTestParameter.layerIsVisibleAtEnd(
component: IComponentMatcher
) {
@@ -66,7 +84,6 @@
}
fun FlickerTestParameter.splitAppLayerBoundsBecomesVisible(
- rotation: Int,
component: IComponentMatcher,
splitLeftTop: Boolean
) {
@@ -75,29 +92,50 @@
this.isInvisible(component)
.then()
.invoke("splitAppLayerBoundsBecomesVisible") {
- it.visibleRegion(component).overlaps(
+ it.visibleRegion(component).coversAtMost(
if (splitLeftTop) {
- getSplitLeftTopRegion(dividerRegion, rotation)
+ getSplitLeftTopRegion(dividerRegion, endRotation)
} else {
- getSplitRightBottomRegion(dividerRegion, rotation)
+ getSplitRightBottomRegion(dividerRegion, endRotation)
}
)
}
}
}
+fun FlickerTestParameter.splitAppLayerBoundsBecomesInvisible(
+ component: IComponentMatcher,
+ splitLeftTop: Boolean
+) {
+ assertLayers {
+ val dividerRegion = this.first().layer(SPLIT_SCREEN_DIVIDER_COMPONENT).visibleRegion.region
+ this.invoke("splitAppLayerBoundsBecomesVisible") {
+ it.visibleRegion(component).coversAtMost(
+ if (splitLeftTop) {
+ getSplitLeftTopRegion(dividerRegion, endRotation)
+ } else {
+ getSplitRightBottomRegion(dividerRegion, endRotation)
+ }
+ )
+ }
+ .then()
+ .isVisible(component, true)
+ .then()
+ .isInvisible(component)
+ }
+}
+
fun FlickerTestParameter.splitAppLayerBoundsIsVisibleAtEnd(
- rotation: Int,
component: IComponentMatcher,
splitLeftTop: Boolean
) {
assertLayersEnd {
val dividerRegion = layer(SPLIT_SCREEN_DIVIDER_COMPONENT).visibleRegion.region
- visibleRegion(component).overlaps(
+ visibleRegion(component).coversAtMost(
if (splitLeftTop) {
- getSplitLeftTopRegion(dividerRegion, rotation)
+ getSplitLeftTopRegion(dividerRegion, endRotation)
} else {
- getSplitRightBottomRegion(dividerRegion, rotation)
+ getSplitRightBottomRegion(dividerRegion, endRotation)
}
)
}
@@ -113,6 +151,16 @@
}
}
+fun FlickerTestParameter.appWindowBecomesInvisible(
+ component: IComponentMatcher
+) {
+ assertWm {
+ this.isAppWindowVisible(component)
+ .then()
+ .isAppWindowInvisible(component)
+ }
+}
+
fun FlickerTestParameter.appWindowIsVisibleAtEnd(
component: IComponentMatcher
) {
@@ -226,9 +274,17 @@
fun getSplitLeftTopRegion(dividerRegion: Region, rotation: Int): Region {
val displayBounds = WindowUtils.getDisplayBounds(rotation)
return if (displayBounds.width > displayBounds.height) {
- Region.from(0, 0, dividerRegion.bounds.left, displayBounds.bounds.bottom)
+ Region.from(
+ 0,
+ 0,
+ (dividerRegion.bounds.left + dividerRegion.bounds.right) / 2,
+ displayBounds.bounds.bottom)
} else {
- Region.from(0, 0, displayBounds.bounds.right, dividerRegion.bounds.top)
+ Region.from(
+ 0,
+ 0,
+ displayBounds.bounds.right,
+ (dividerRegion.bounds.top + dividerRegion.bounds.bottom) / 2)
}
}
@@ -236,12 +292,16 @@
val displayBounds = WindowUtils.getDisplayBounds(rotation)
return if (displayBounds.width > displayBounds.height) {
Region.from(
- dividerRegion.bounds.right, 0, displayBounds.bounds.right,
+ (dividerRegion.bounds.left + dividerRegion.bounds.right) / 2,
+ 0,
+ displayBounds.bounds.right,
displayBounds.bounds.bottom
)
} else {
Region.from(
- 0, dividerRegion.bounds.bottom, displayBounds.bounds.right,
+ 0,
+ (dividerRegion.bounds.top + dividerRegion.bounds.bottom) / 2,
+ displayBounds.bounds.right,
displayBounds.bounds.bottom
)
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt
index ccd5f02..6c9ea7b 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt
@@ -78,6 +78,19 @@
Components.SendNotificationActivity.COMPONENT.toFlickerComponent()
)
+ fun waitForSplitComplete(
+ wmHelper: WindowManagerStateHelper,
+ primaryApp: IComponentMatcher,
+ secondaryApp: IComponentMatcher,
+ ) {
+ wmHelper.StateSyncBuilder()
+ .withAppTransitionIdle()
+ .withWindowSurfaceAppeared(primaryApp)
+ .withWindowSurfaceAppeared(secondaryApp)
+ .withSplitDividerVisible()
+ .waitForAndVerify()
+ }
+
fun dragFromNotificationToSplit(
instrumentation: Instrumentation,
device: UiDevice,
@@ -190,12 +203,11 @@
}
fun createShortcutOnHotseatIfNotExist(
- taplInstrumentation: LauncherInstrumentation,
+ tapl: LauncherInstrumentation,
appName: String
) {
- taplInstrumentation.workspace
- .deleteAppIcon(taplInstrumentation.workspace.getHotseatAppIcon(0))
- val allApps = taplInstrumentation.workspace.switchToAllApps()
+ tapl.workspace.deleteAppIcon(tapl.workspace.getHotseatAppIcon(0))
+ val allApps = tapl.workspace.switchToAllApps()
allApps.freeze()
try {
allApps.getAppIcon(appName).dragToHotseat(0)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt
new file mode 100644
index 0000000..4f48ff0
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt
@@ -0,0 +1,191 @@
+/*
+ * 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.wm.shell.flicker.splitscreen
+
+import android.platform.test.annotations.Postsubmit
+import android.platform.test.annotations.Presubmit
+import android.view.WindowManagerPolicyConstants
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.FlickerParametersRunnerFactory
+import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.FlickerTestParameterFactory
+import com.android.server.wm.flicker.annotation.Group1
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.wm.shell.flicker.appWindowBecomesInvisible
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+import com.android.wm.shell.flicker.layerBecomesInvisible
+import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesInvisible
+import com.android.wm.shell.flicker.splitScreenDividerBecomesInvisible
+import org.junit.Assume
+import org.junit.Before
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test dismiss split screen by go home.
+ *
+ * To run this test: `atest WMShellFlickerTests:DismissSplitScreenByGoHome`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Group1
+class DismissSplitScreenByGoHome(
+ testSpec: FlickerTestParameter
+) : SplitScreenBase(testSpec) {
+
+ // TODO(b/231399940): Remove this once we can use recent shortcut to enter split.
+ @Before
+ open fun before() {
+ Assume.assumeTrue(tapl.isTablet)
+ }
+
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ super.transition(this)
+ setup {
+ eachRun {
+ primaryApp.launchViaIntent(wmHelper)
+ // TODO(b/231399940): Use recent shortcut to enter split.
+ tapl.launchedAppState.taskbar
+ .openAllApps()
+ .getAppIcon(secondaryApp.appName)
+ .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
+ SplitScreenHelper.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+ }
+ }
+ transitions {
+ tapl.goHome()
+ wmHelper.StateSyncBuilder()
+ .withAppTransitionIdle()
+ .withHomeActivityVisible()
+ .waitForAndVerify()
+ }
+ }
+
+ @Presubmit
+ @Test
+ fun splitScreenDividerBecomesVisible() = testSpec.splitScreenDividerBecomesInvisible()
+
+ @Presubmit
+ @Test
+ fun primaryAppLayerBecomesInvisible() = testSpec.layerBecomesInvisible(primaryApp)
+
+ @Presubmit
+ @Test
+ fun secondaryAppLayerBecomesInvisible() = testSpec.layerBecomesInvisible(primaryApp)
+
+ @Presubmit
+ @Test
+ fun primaryAppBoundsBecomesInvisible() = testSpec.splitAppLayerBoundsBecomesInvisible(
+ primaryApp, splitLeftTop = false)
+
+ @Presubmit
+ @Test
+ fun secondaryAppBoundsBecomesInvisible() = testSpec.splitAppLayerBoundsBecomesInvisible(
+ secondaryApp, splitLeftTop = true)
+
+ @Presubmit
+ @Test
+ fun primaryAppWindowBecomesInvisible() = testSpec.appWindowBecomesInvisible(primaryApp)
+
+ @Presubmit
+ @Test
+ fun secondaryAppWindowBecomesInvisible() = testSpec.appWindowBecomesInvisible(secondaryApp)
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun entireScreenCovered() =
+ super.entireScreenCovered()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun navBarLayerIsVisibleAtStartAndEnd() =
+ super.navBarLayerIsVisibleAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun navBarLayerPositionAtStartAndEnd() =
+ super.navBarLayerPositionAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun navBarWindowIsAlwaysVisible() =
+ super.navBarWindowIsAlwaysVisible()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun statusBarLayerIsVisibleAtStartAndEnd() =
+ super.statusBarLayerIsVisibleAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun statusBarLayerPositionAtStartAndEnd() =
+ super.statusBarLayerPositionAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun statusBarWindowIsAlwaysVisible() =
+ super.statusBarWindowIsAlwaysVisible()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun taskBarLayerIsVisibleAtStartAndEnd() =
+ super.taskBarLayerIsVisibleAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun taskBarWindowIsAlwaysVisible() =
+ super.taskBarWindowIsAlwaysVisible()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
+ super.visibleLayersShownMoreThanOneConsecutiveEntry()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
+ super.visibleWindowsShownMoreThanOneConsecutiveEntry()
+
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): List<FlickerTestParameter> {
+ return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
+ repetitions = SplitScreenHelper.TEST_REPETITIONS,
+ // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
+ supportedNavigationModes =
+ listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY))
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt
index 779be0a..9564d97 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt
@@ -72,15 +72,16 @@
}
transitions {
tapl.launchedAppState.taskbar
- .openAllApps()
- .getAppIcon(secondaryApp.appName)
- .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
+ .openAllApps()
+ .getAppIcon(secondaryApp.appName)
+ .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
+ SplitScreenHelper.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
}
}
@Presubmit
@Test
- fun dividerBecomesVisible() = testSpec.splitScreenDividerBecomesVisible()
+ fun splitScreenDividerBecomesVisible() = testSpec.splitScreenDividerBecomesVisible()
@Presubmit
@Test
@@ -93,12 +94,12 @@
@Presubmit
@Test
fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
- testSpec.endRotation, primaryApp, false /* splitLeftTop */)
+ primaryApp, splitLeftTop = false)
@Presubmit
@Test
fun secondaryAppBoundsBecomesVisible() = testSpec.splitAppLayerBoundsBecomesVisible(
- testSpec.endRotation, secondaryApp, true /* splitLeftTop */)
+ secondaryApp, splitLeftTop = true)
@Presubmit
@Test
@@ -106,8 +107,7 @@
@Presubmit
@Test
- fun secondaryAppWindowBecomesVisible() =
- testSpec.appWindowBecomesVisible(secondaryApp)
+ fun secondaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(secondaryApp)
/** {@inheritDoc} */
@Postsubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt
index d47d81b..3b59716 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt
@@ -84,6 +84,7 @@
}
transitions {
SplitScreenHelper.dragFromNotificationToSplit(instrumentation, device, wmHelper)
+ SplitScreenHelper.waitForSplitComplete(wmHelper, primaryApp, sendNotificationApp)
}
teardown {
eachRun {
@@ -94,7 +95,7 @@
@Presubmit
@Test
- fun dividerBecomesVisible() = testSpec.splitScreenDividerBecomesVisible()
+ fun splitScreenDividerBecomesVisible() = testSpec.splitScreenDividerBecomesVisible()
@Presubmit
@Test
@@ -108,14 +109,12 @@
@Presubmit
@Test
fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
- testSpec.endRotation, primaryApp, false /* splitLeftTop */
- )
+ primaryApp, splitLeftTop = false)
@Presubmit
@Test
fun secondaryAppBoundsBecomesVisible() = testSpec.splitAppLayerBoundsBecomesVisible(
- testSpec.endRotation, sendNotificationApp, true /* splitLeftTop */
- )
+ sendNotificationApp, splitLeftTop = true)
@Presubmit
@Test
@@ -123,8 +122,7 @@
@Presubmit
@Test
- fun secondaryAppWindowIsVisibleAtEnd() =
- testSpec.appWindowIsVisibleAtEnd(sendNotificationApp)
+ fun secondaryAppWindowIsVisibleAtEnd() = testSpec.appWindowIsVisibleAtEnd(sendNotificationApp)
/** {@inheritDoc} */
@Postsubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt
index 1493d1f..3de9872 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt
@@ -77,16 +77,14 @@
transitions {
tapl.launchedAppState.taskbar
.getAppIcon(secondaryApp.appName)
- .dragToSplitscreen(
- secondaryApp.`package`,
- primaryApp.`package`
- )
+ .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
+ SplitScreenHelper.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
}
}
@Presubmit
@Test
- fun dividerBecomesVisible() = testSpec.splitScreenDividerBecomesVisible()
+ fun splitScreenDividerBecomesVisible() = testSpec.splitScreenDividerBecomesVisible()
@Presubmit
@Test
@@ -99,14 +97,12 @@
@Presubmit
@Test
fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
- testSpec.endRotation, primaryApp, splitLeftTop = false
- )
+ primaryApp, splitLeftTop = false)
@Presubmit
@Test
fun secondaryAppBoundsBecomesVisible() = testSpec.splitAppLayerBoundsBecomesVisible(
- testSpec.endRotation, secondaryApp, splitLeftTop = true
- )
+ secondaryApp, splitLeftTop = true)
@Presubmit
@Test
@@ -114,8 +110,7 @@
@Presubmit
@Test
- fun secondaryAppWindowBecomesVisible() =
- testSpec.appWindowBecomesVisible(secondaryApp)
+ fun secondaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(secondaryApp)
/** {@inheritDoc} */
@Postsubmit
diff --git a/libs/hwui/jni/android_graphics_Canvas.cpp b/libs/hwui/jni/android_graphics_Canvas.cpp
index 61bb665..fb7d5f7 100644
--- a/libs/hwui/jni/android_graphics_Canvas.cpp
+++ b/libs/hwui/jni/android_graphics_Canvas.cpp
@@ -419,14 +419,28 @@
indices = (const uint16_t*)(indexA.ptr() + indexIndex);
}
- SkVertices::VertexMode mode = static_cast<SkVertices::VertexMode>(modeHandle);
+ SkVertices::VertexMode vertexMode = static_cast<SkVertices::VertexMode>(modeHandle);
const Paint* paint = reinterpret_cast<Paint*>(paintHandle);
- get_canvas(canvasHandle)->drawVertices(SkVertices::MakeCopy(mode, vertexCount,
- reinterpret_cast<const SkPoint*>(verts),
- reinterpret_cast<const SkPoint*>(texs),
- reinterpret_cast<const SkColor*>(colors),
- indexCount, indices).get(),
- SkBlendMode::kModulate, *paint);
+
+ // Preserve legacy Skia behavior: ignore the shader if there are no texs set.
+ Paint noShaderPaint;
+ if (jtexs == NULL) {
+ noShaderPaint = Paint(*paint);
+ noShaderPaint.setShader(nullptr);
+ paint = &noShaderPaint;
+ }
+ // Since https://skia-review.googlesource.com/c/skia/+/473676, Skia will blend paint and vertex
+ // colors when no shader is provided. This ternary uses kDst to mimic the old behavior of
+ // ignoring the paint and using the vertex colors directly when no shader is provided.
+ SkBlendMode blendMode = paint->getShader() ? SkBlendMode::kModulate : SkBlendMode::kDst;
+
+ get_canvas(canvasHandle)
+ ->drawVertices(SkVertices::MakeCopy(
+ vertexMode, vertexCount, reinterpret_cast<const SkPoint*>(verts),
+ reinterpret_cast<const SkPoint*>(texs),
+ reinterpret_cast<const SkColor*>(colors), indexCount, indices)
+ .get(),
+ blendMode, *paint);
}
static void drawNinePatch(JNIEnv* env, jobject, jlong canvasHandle, jlong bitmapHandle,
diff --git a/media/native/midi/include/amidi/AMidi.h b/media/native/midi/include/amidi/AMidi.h
index fbb7fb3..2b31703 100644
--- a/media/native/midi/include/amidi/AMidi.h
+++ b/media/native/midi/include/amidi/AMidi.h
@@ -66,7 +66,7 @@
*
* Introduced in API 33.
*/
-enum AMidiDevice_Protocol : int32_t {
+typedef enum AMidiDevice_Protocol : int32_t {
/**
* Constant representing a default protocol with Universal MIDI Packets (UMP).
* UMP is defined in "Universal MIDI Packet (UMP) Format and MIDI 2.0 Protocol" spec.
@@ -131,7 +131,7 @@
* MIDI-CI is defined in "MIDI Capability Inquiry (MIDI-CI)" spec.
*/
AMIDI_DEVICE_PROTOCOL_UNKNOWN = -1
-};
+} AMidiDevice_Protocol;
/*
* Device API
diff --git a/packages/SettingsLib/ActivityEmbedding/AndroidManifest.xml b/packages/SettingsLib/ActivityEmbedding/AndroidManifest.xml
index 5ce08b7..2742558 100644
--- a/packages/SettingsLib/ActivityEmbedding/AndroidManifest.xml
+++ b/packages/SettingsLib/ActivityEmbedding/AndroidManifest.xml
@@ -16,7 +16,7 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.settingslib.activityembedding">
+ package="com.android.settingslib.widget">
<uses-sdk android:minSdkVersion="21" />
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/AndroidManifest.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/AndroidManifest.xml
index 1c47f5f..244b367 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/AndroidManifest.xml
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/AndroidManifest.xml
@@ -16,7 +16,7 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.settingslib.collapsingtoolbar">
+ package="com.android.settingslib.widget">
<uses-sdk android:minSdkVersion="29" />
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/BasePreferencesFragment.java b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/BasePreferencesFragment.java
index 8ebbac3..3582897 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/BasePreferencesFragment.java
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/BasePreferencesFragment.java
@@ -20,6 +20,7 @@
import androidx.preference.PreferenceFragmentCompat;
import com.android.settingslib.utils.BuildCompatUtils;
+import com.android.settingslib.widget.R;
import com.google.android.material.appbar.AppBarLayout;
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java
index 77d6583..8c8b478 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java
@@ -27,6 +27,7 @@
import androidx.fragment.app.FragmentActivity;
import com.android.settingslib.utils.BuildCompatUtils;
+import com.android.settingslib.widget.R;
import com.google.android.material.appbar.AppBarLayout;
import com.google.android.material.appbar.CollapsingToolbarLayout;
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarDelegate.java b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarDelegate.java
index 31e8cc7..ec091bf 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarDelegate.java
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarDelegate.java
@@ -30,6 +30,8 @@
import androidx.annotation.Nullable;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
+import com.android.settingslib.widget.R;
+
import com.google.android.material.appbar.AppBarLayout;
import com.google.android.material.appbar.CollapsingToolbarLayout;
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayout.java b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayout.java
index 1ead2f3..a8c7a3f 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayout.java
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayout.java
@@ -36,7 +36,7 @@
import androidx.appcompat.app.AppCompatActivity;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
-import com.android.settingslib.collapsingtoolbar.R;
+import com.android.settingslib.widget.R;
import com.google.android.material.appbar.AppBarLayout;
import com.google.android.material.appbar.CollapsingToolbarLayout;
diff --git a/packages/SettingsLib/SettingsTransition/AndroidManifest.xml b/packages/SettingsLib/SettingsTransition/AndroidManifest.xml
index b6aff53..244b367 100644
--- a/packages/SettingsLib/SettingsTransition/AndroidManifest.xml
+++ b/packages/SettingsLib/SettingsTransition/AndroidManifest.xml
@@ -16,7 +16,7 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.settingslib.transition">
+ package="com.android.settingslib.widget">
<uses-sdk android:minSdkVersion="29" />
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
index d3afccc..6aa08f2 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
@@ -875,16 +875,16 @@
String[] whitelist;
Map<String, Validator> validators = null;
if (contentUri.equals(Settings.Secure.CONTENT_URI)) {
- whitelist = ArrayUtils.concatElements(String.class, SecureSettings.SETTINGS_TO_BACKUP,
+ whitelist = ArrayUtils.concat(String.class, SecureSettings.SETTINGS_TO_BACKUP,
Settings.Secure.LEGACY_RESTORE_SETTINGS,
DeviceSpecificSettings.DEVICE_SPECIFIC_SETTINGS_TO_BACKUP);
validators = SecureSettingsValidators.VALIDATORS;
} else if (contentUri.equals(Settings.System.CONTENT_URI)) {
- whitelist = ArrayUtils.concatElements(String.class, SystemSettings.SETTINGS_TO_BACKUP,
+ whitelist = ArrayUtils.concat(String.class, SystemSettings.SETTINGS_TO_BACKUP,
Settings.System.LEGACY_RESTORE_SETTINGS);
validators = SystemSettingsValidators.VALIDATORS;
} else if (contentUri.equals(Settings.Global.CONTENT_URI)) {
- whitelist = ArrayUtils.concatElements(String.class, GlobalSettings.SETTINGS_TO_BACKUP,
+ whitelist = ArrayUtils.concat(String.class, GlobalSettings.SETTINGS_TO_BACKUP,
Settings.Global.LEGACY_RESTORE_SETTINGS);
validators = GlobalSettingsValidators.VALIDATORS;
} else {
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 4aa4006..634df39 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -146,25 +146,10 @@
filegroup {
name: "SystemUI-tests-utils",
srcs: [
- "tests/src/com/android/systemui/SysuiBaseFragmentTest.java",
- "tests/src/com/android/systemui/SysuiTestCase.java",
- "tests/src/com/android/systemui/TestableDependency.java",
- "tests/src/com/android/systemui/classifier/FalsingManagerFake.java",
- "tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilder.java",
- "tests/src/com/android/systemui/statusbar/RankingBuilder.java",
- "tests/src/com/android/systemui/statusbar/SbnBuilder.java",
- "tests/src/com/android/systemui/SysuiTestableContext.java",
- "tests/src/com/android/systemui/util/**/*Fake.java",
- "tests/src/com/android/systemui/utils/leaks/BaseLeakChecker.java",
- "tests/src/com/android/systemui/utils/leaks/LeakCheckedTest.java",
- "tests/src/com/android/systemui/**/Fake*.java",
- "tests/src/com/android/systemui/**/Fake*.kt",
+ "tests/utils/src/**/*.java",
+ "tests/utils/src/**/*.kt",
],
- exclude_srcs: [
- "tests/src/com/android/systemui/**/*Test.java",
- "tests/src/com/android/systemui/**/*Test.kt",
- ],
- path: "tests/src",
+ path: "tests/utils/src",
}
java_library {
@@ -172,8 +157,8 @@
srcs: [
"src/com/android/systemui/util/concurrency/DelayableExecutor.java",
"src/com/android/systemui/util/time/SystemClock.java",
- "tests/src/com/android/systemui/util/concurrency/FakeExecutor.java",
- "tests/src/com/android/systemui/util/time/FakeSystemClock.java",
+ "tests/utils/src/com/android/systemui/util/concurrency/FakeExecutor.java",
+ "tests/utils/src/com/android/systemui/util/time/FakeSystemClock.java",
],
jarjar_rules: ":jarjar-rules-shared",
}
@@ -196,6 +181,7 @@
"src/**/*.java",
"src/**/I*.aidl",
":ReleaseJavaFiles",
+ ":SystemUI-tests-utils",
],
static_libs: [
"WifiTrackerLib",
@@ -226,6 +212,7 @@
"androidx.exifinterface_exifinterface",
"kotlinx-coroutines-android",
"kotlinx-coroutines-core",
+ "kotlinx_coroutines_test",
"iconloader_base",
"SystemUI-tags",
"SystemUI-proto",
diff --git a/packages/SystemUI/proguard.flags b/packages/SystemUI/proguard.flags
index 84ebe77..a3b4b38 100644
--- a/packages/SystemUI/proguard.flags
+++ b/packages/SystemUI/proguard.flags
@@ -14,6 +14,11 @@
-keep class * extends com.android.systemui.CoreStartable
-keep class * implements com.android.systemui.CoreStartable$Injector
+# Needed for builds to properly initialize KeyFrames from xml scene
+-keepclassmembers class * extends androidx.constraintlayout.motion.widget.Key {
+ public <init>();
+}
+
-keepclasseswithmembers class * {
public <init>(android.content.Context, android.util.AttributeSet);
}
diff --git a/packages/SystemUI/res/layout/keyguard_bottom_area.xml b/packages/SystemUI/res/layout/keyguard_bottom_area.xml
index 12dfa10..0ca19d9 100644
--- a/packages/SystemUI/res/layout/keyguard_bottom_area.xml
+++ b/packages/SystemUI/res/layout/keyguard_bottom_area.xml
@@ -60,6 +60,30 @@
</LinearLayout>
<ImageView
+ android:id="@+id/start_button"
+ android:layout_height="@dimen/keyguard_affordance_fixed_height"
+ android:layout_width="@dimen/keyguard_affordance_fixed_width"
+ android:layout_gravity="bottom|start"
+ android:scaleType="center"
+ android:tint="?android:attr/textColorPrimary"
+ android:background="@drawable/keyguard_bottom_affordance_bg"
+ android:layout_marginStart="@dimen/keyguard_affordance_horizontal_offset"
+ android:layout_marginBottom="@dimen/keyguard_affordance_vertical_offset"
+ android:visibility="gone" />
+
+ <ImageView
+ android:id="@+id/end_button"
+ android:layout_height="@dimen/keyguard_affordance_fixed_height"
+ android:layout_width="@dimen/keyguard_affordance_fixed_width"
+ android:layout_gravity="bottom|end"
+ android:scaleType="center"
+ android:tint="?android:attr/textColorPrimary"
+ android:background="@drawable/keyguard_bottom_affordance_bg"
+ android:layout_marginEnd="@dimen/keyguard_affordance_horizontal_offset"
+ android:layout_marginBottom="@dimen/keyguard_affordance_vertical_offset"
+ android:visibility="gone" />
+
+ <ImageView
android:id="@+id/wallet_button"
android:layout_height="@dimen/keyguard_affordance_fixed_height"
android:layout_width="@dimen/keyguard_affordance_fixed_width"
diff --git a/packages/SystemUI/res/values-sw600dp/config.xml b/packages/SystemUI/res/values-sw600dp/config.xml
index 36cc0ad..c0071cb 100644
--- a/packages/SystemUI/res/values-sw600dp/config.xml
+++ b/packages/SystemUI/res/values-sw600dp/config.xml
@@ -40,4 +40,6 @@
<bool name="config_use_large_screen_shade_header">true</bool>
+ <!-- Whether to show the side fps hint while on bouncer -->
+ <bool name="config_show_sidefps_hint_on_bouncer">true</bool>
</resources>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 82a3b58..ec22c60 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -548,6 +548,9 @@
<!-- Package name of the preferred system app to perform eSOS action -->
<string name="config_preferredEmergencySosPackage" translatable="false"></string>
+ <!-- Whether to show the side fps hint while on bouncer -->
+ <bool name="config_show_sidefps_hint_on_bouncer">false</bool>
+
<!-- Whether to use the split 2-column notification shade -->
<bool name="config_use_split_notification_shade">false</bool>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
index afa255a..ef9e095 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
@@ -79,6 +79,8 @@
// Fields used only to unrap into RemoteAnimationTarget
private final Rect startBounds;
+ public final boolean willShowImeOnTarget;
+
public RemoteAnimationTargetCompat(RemoteAnimationTarget app) {
taskId = app.taskId;
mode = app.mode;
@@ -102,6 +104,7 @@
windowType = app.windowType;
windowConfiguration = app.windowConfiguration;
startBounds = app.startBounds;
+ willShowImeOnTarget = app.willShowImeOnTarget;
}
private static int newModeToLegacyMode(int newMode) {
@@ -118,14 +121,15 @@
}
public RemoteAnimationTarget unwrap() {
- return new RemoteAnimationTarget(
+ final RemoteAnimationTarget target = new RemoteAnimationTarget(
taskId, mode, leash, isTranslucent, clipRect, contentInsets,
prefixOrderIndex, position, localBounds, screenSpaceBounds, windowConfiguration,
isNotInRecents, mStartLeash, startBounds, taskInfo, allowEnterPip, windowType
);
+ target.setWillShowImeOnTarget(willShowImeOnTarget);
+ return target;
}
-
/**
* Almost a copy of Transitions#setupStartState.
* TODO: remove when there is proper cross-process transaction sync.
@@ -234,6 +238,7 @@
? change.getTaskInfo().configuration.windowConfiguration
: new WindowConfiguration();
startBounds = change.getStartAbsBounds();
+ willShowImeOnTarget = (change.getFlags() & TransitionInfo.FLAG_WILL_IME_SHOWN) != 0;
}
public static RemoteAnimationTargetCompat[] wrap(RemoteAnimationTarget[] apps) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 5ee659b..0b3fe46 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -343,7 +343,8 @@
if (!mSidefpsController.isPresent()) {
return;
}
- if (mBouncerVisible && mView.isSidedSecurityMode()
+ if (mBouncerVisible
+ && getResources().getBoolean(R.bool.config_show_sidefps_hint_on_bouncer)
&& mUpdateMonitor.isFingerprintDetectionRunning()
&& !mUpdateMonitor.userNeedsStrongAuth()) {
mSidefpsController.get().show();
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 758ec54..489b4be 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -65,7 +65,6 @@
import android.hardware.fingerprint.FingerprintManager.AuthenticationResult;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.nfc.NfcAdapter;
-import android.os.Build;
import android.os.CancellationSignal;
import android.os.Handler;
import android.os.IRemoteCallback;
@@ -95,6 +94,7 @@
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.util.LatencyTracker;
import com.android.internal.widget.LockPatternUtils;
+import com.android.keyguard.logging.KeyguardUpdateMonitorLogger;
import com.android.settingslib.WirelessUtils;
import com.android.settingslib.fuelgauge.BatteryStatus;
import com.android.systemui.Dumpable;
@@ -140,12 +140,6 @@
public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpable {
private static final String TAG = "KeyguardUpdateMonitor";
- private static final boolean DEBUG = KeyguardConstants.DEBUG;
- private static final boolean DEBUG_SIM_STATES = KeyguardConstants.DEBUG_SIM_STATES;
- private static final boolean DEBUG_FACE = Build.IS_DEBUGGABLE;
- private static final boolean DEBUG_FINGERPRINT = Build.IS_DEBUGGABLE;
- private static final boolean DEBUG_ACTIVE_UNLOCK = Build.IS_DEBUGGABLE;
- private static final boolean DEBUG_SPEW = false;
private static final int BIOMETRIC_LOCKOUT_RESET_DELAY_MS = 600;
// Callback messages
@@ -225,6 +219,7 @@
"com.android.settings", "com.android.settings.FallbackHome");
private final Context mContext;
+ private final KeyguardUpdateMonitorLogger mLogger;
private final boolean mIsPrimaryUser;
private final AuthController mAuthController;
private final StatusBarStateController mStatusBarStateController;
@@ -321,17 +316,9 @@
private static final int HAL_ERROR_RETRY_TIMEOUT = 500; // ms
private static final int HAL_ERROR_RETRY_MAX = 20;
- private final Runnable mFpCancelNotReceived = () -> {
- Log.e(TAG, "Fp cancellation not received, transitioning to STOPPED");
- mFingerprintRunningState = BIOMETRIC_STATE_STOPPED;
- updateFingerprintListeningState(BIOMETRIC_ACTION_STOP);
- };
+ private final Runnable mFpCancelNotReceived = this::onFingerprintCancelNotReceived;
- private final Runnable mFaceCancelNotReceived = () -> {
- Log.e(TAG, "Face cancellation not received, transitioning to STOPPED");
- mFaceRunningState = BIOMETRIC_STATE_STOPPED;
- updateFaceListeningState(BIOMETRIC_ACTION_STOP);
- };
+ private final Runnable mFaceCancelNotReceived = this::onFaceCancelNotReceived;
private final Handler mHandler;
@@ -454,17 +441,15 @@
private void handleSimSubscriptionInfoChanged() {
Assert.isMainThread();
- if (DEBUG_SIM_STATES) {
- Log.v(TAG, "onSubscriptionInfoChanged()");
- List<SubscriptionInfo> sil = mSubscriptionManager
- .getCompleteActiveSubscriptionInfoList();
- if (sil != null) {
- for (SubscriptionInfo subInfo : sil) {
- Log.v(TAG, "SubInfo:" + subInfo);
- }
- } else {
- Log.v(TAG, "onSubscriptionInfoChanged: list is null");
+ mLogger.v("onSubscriptionInfoChanged()");
+ List<SubscriptionInfo> sil = mSubscriptionManager
+ .getCompleteActiveSubscriptionInfoList();
+ if (sil != null) {
+ for (SubscriptionInfo subInfo : sil) {
+ mLogger.logSubInfo(subInfo);
}
+ } else {
+ mLogger.v("onSubscriptionInfoChanged: list is null");
}
List<SubscriptionInfo> subscriptionInfos = getSubscriptionInfo(true /* forceReload */);
@@ -488,8 +473,7 @@
while (iter.hasNext()) {
Map.Entry<Integer, SimData> simData = iter.next();
if (!activeSubIds.contains(simData.getKey())) {
- Log.i(TAG, "Previously active sub id " + simData.getKey() + " is now invalid, "
- + "will remove");
+ mLogger.logInvalidSubId(simData.getKey());
iter.remove();
SimData data = simData.getValue();
@@ -674,7 +658,7 @@
try {
mDreamManager.awaken();
} catch (RemoteException e) {
- Log.e(TAG, "Unable to awaken from dream");
+ mLogger.logException(e, "Unable to awaken from dream");
}
}
}
@@ -758,15 +742,15 @@
try {
userId = ActivityManager.getService().getCurrentUser().id;
} catch (RemoteException e) {
- Log.e(TAG, "Failed to get current user id: ", e);
+ mLogger.logException(e, "Failed to get current user id");
return;
}
if (userId != authUserId) {
- Log.d(TAG, "Fingerprint authenticated for wrong user: " + authUserId);
+ mLogger.logFingerprintAuthForWrongUser(authUserId);
return;
}
if (isFingerprintDisabled(userId)) {
- Log.d(TAG, "Fingerprint disabled by DPM for userId: " + userId);
+ mLogger.logFingerprintDisabledForUser(userId);
return;
}
onFingerprintAuthenticated(userId, isStrongBiometric);
@@ -789,8 +773,7 @@
private Runnable mRetryFingerprintAuthentication = new Runnable() {
@Override
public void run() {
- Log.w(TAG,
- "Retrying fingerprint attempt: " + mHardwareFingerprintUnavailableRetryCount);
+ mLogger.logRetryAfterFpHwUnavailable(mHardwareFingerprintUnavailableRetryCount);
if (mFpm.isHardwareDetected()) {
updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
} else if (mHardwareFingerprintUnavailableRetryCount < HAL_ERROR_RETRY_MAX) {
@@ -800,6 +783,12 @@
}
};
+ private void onFingerprintCancelNotReceived() {
+ mLogger.e("Fp cancellation not received, transitioning to STOPPED");
+ mFingerprintRunningState = BIOMETRIC_STATE_STOPPED;
+ KeyguardUpdateMonitor.this.updateFingerprintListeningState(BIOMETRIC_ACTION_STOP);
+ }
+
private void handleFingerprintError(int msgId, String errString) {
Assert.isMainThread();
if (mHandler.hasCallbacks(mFpCancelNotReceived)) {
@@ -827,7 +816,7 @@
if (msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT) {
lockedOutStateChanged |= !mFingerprintLockedOutPermanent;
mFingerprintLockedOutPermanent = true;
- Log.d(TAG, "Fingerprint locked out - requiring strong auth");
+ mLogger.d("Fingerprint locked out - requiring strong auth");
mLockPatternUtils.requireStrongAuth(
STRONG_AUTH_REQUIRED_AFTER_LOCKOUT, getCurrentUser());
}
@@ -855,7 +844,7 @@
}
private void handleFingerprintLockoutReset(@LockoutMode int mode) {
- Log.d(TAG, "handleFingerprintLockoutReset: " + mode);
+ mLogger.logFingerprintLockoutReset(mode);
final boolean wasLockout = mFingerprintLockedOut;
final boolean wasLockoutPermanent = mFingerprintLockedOutPermanent;
mFingerprintLockedOut = (mode == BIOMETRIC_LOCKOUT_TIMED)
@@ -886,7 +875,7 @@
boolean wasRunning = mFingerprintRunningState == BIOMETRIC_STATE_RUNNING;
boolean isRunning = fingerprintRunningState == BIOMETRIC_STATE_RUNNING;
mFingerprintRunningState = fingerprintRunningState;
- Log.d(TAG, "fingerprintRunningState: " + mFingerprintRunningState);
+ mLogger.logFingerprintRunningState(mFingerprintRunningState);
// Clients of KeyguardUpdateMonitor don't care about the internal state about the
// asynchronousness of the cancel cycle. So only notify them if the actually running state
// has changed.
@@ -953,7 +942,7 @@
private void handleFaceAcquired(int acquireInfo) {
Assert.isMainThread();
- if (DEBUG_FACE) Log.d(TAG, "Face acquired acquireInfo=" + acquireInfo);
+ mLogger.logFaceAcquired(acquireInfo);
for (int i = 0; i < mCallbacks.size(); i++) {
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
if (cb != null) {
@@ -966,25 +955,25 @@
Trace.beginSection("KeyGuardUpdateMonitor#handlerFaceAuthenticated");
try {
if (mGoingToSleep) {
- Log.d(TAG, "Aborted successful auth because device is going to sleep.");
+ mLogger.d("Aborted successful auth because device is going to sleep.");
return;
}
final int userId;
try {
userId = ActivityManager.getService().getCurrentUser().id;
} catch (RemoteException e) {
- Log.e(TAG, "Failed to get current user id: ", e);
+ mLogger.logException(e, "Failed to get current user id");
return;
}
if (userId != authUserId) {
- Log.d(TAG, "Face authenticated for wrong user: " + authUserId);
+ mLogger.logFaceAuthForWrongUser(authUserId);
return;
}
if (isFaceDisabled(userId)) {
- Log.d(TAG, "Face authentication disabled by DPM for userId: " + userId);
+ mLogger.logFaceAuthDisabledForUser(userId);
return;
}
- if (DEBUG_FACE) Log.d(TAG, "Face auth succeeded for user " + userId);
+ mLogger.logFaceAuthSuccess(userId);
onFaceAuthenticated(userId, isStrongBiometric);
} finally {
setFaceRunningState(BIOMETRIC_STATE_STOPPED);
@@ -994,7 +983,7 @@
private void handleFaceHelp(int msgId, String helpString) {
Assert.isMainThread();
- if (DEBUG_FACE) Log.d(TAG, "Face help received: " + helpString);
+ mLogger.logFaceAuthHelpMsg(msgId, helpString);
for (int i = 0; i < mCallbacks.size(); i++) {
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
if (cb != null) {
@@ -1006,15 +995,21 @@
private Runnable mRetryFaceAuthentication = new Runnable() {
@Override
public void run() {
- Log.w(TAG, "Retrying face after HW unavailable, attempt " +
- mHardwareFaceUnavailableRetryCount);
+ mLogger.logRetryingAfterFaceHwUnavailable(mHardwareFaceUnavailableRetryCount);
updateFaceListeningState(BIOMETRIC_ACTION_UPDATE);
}
};
- private void handleFaceError(int msgId, String errString) {
+ private void onFaceCancelNotReceived() {
+ mLogger.e("Face cancellation not received, transitioning to STOPPED");
+ mFaceRunningState = BIOMETRIC_STATE_STOPPED;
+ KeyguardUpdateMonitor.this.updateFaceListeningState(BIOMETRIC_ACTION_STOP);
+ }
+
+ private void handleFaceError(int msgId, final String originalErrMsg) {
Assert.isMainThread();
- if (DEBUG_FACE) Log.d(TAG, "Face error received: " + errString + " msgId=" + msgId);
+ String errString = originalErrMsg;
+ mLogger.logFaceAuthError(msgId, originalErrMsg);
if (mHandler.hasCallbacks(mFaceCancelNotReceived)) {
mHandler.removeCallbacks(mFaceCancelNotReceived);
}
@@ -1071,7 +1066,7 @@
}
private void handleFaceLockoutReset(@LockoutMode int mode) {
- Log.d(TAG, "handleFaceLockoutReset: " + mode);
+ mLogger.logFaceLockoutReset(mode);
final boolean wasLockoutPermanent = mFaceLockedOutPermanent;
mFaceLockedOutPermanent = (mode == BIOMETRIC_LOCKOUT_PERMANENT);
final boolean changed = (mFaceLockedOutPermanent != wasLockoutPermanent);
@@ -1089,7 +1084,7 @@
boolean wasRunning = mFaceRunningState == BIOMETRIC_STATE_RUNNING;
boolean isRunning = faceRunningState == BIOMETRIC_STATE_RUNNING;
mFaceRunningState = faceRunningState;
- Log.d(TAG, "faceRunningState: " + mFaceRunningState);
+ mLogger.logFaceRunningState(mFaceRunningState);
// Clients of KeyguardUpdateMonitor don't care about the internal state or about the
// asynchronousness of the cancel cycle. So only notify them if the actually running state
// has changed.
@@ -1205,8 +1200,7 @@
mDevicePolicyManager.getProfileOwnerOrDeviceOwnerSupervisionComponent(
UserHandle.of(userId));
if (supervisorComponent == null) {
- Log.e(TAG, "No Profile Owner or Device Owner supervision app found for User "
- + userId);
+ mLogger.logMissingSupervisorAppError(userId);
} else {
Intent intent =
new Intent(DevicePolicyManager.ACTION_BIND_SECONDARY_LOCKSCREEN_SERVICE)
@@ -1338,7 +1332,7 @@
@Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
- if (DEBUG) Log.d(TAG, "received broadcast " + action);
+ mLogger.logBroadcastReceived(action);
if (Intent.ACTION_TIME_TICK.equals(action)
|| Intent.ACTION_TIME_CHANGED.equals(action)) {
@@ -1365,12 +1359,10 @@
}
return;
}
- if (DEBUG_SIM_STATES) {
- Log.v(TAG, "action " + action
- + " state: " + intent.getStringExtra(
- Intent.EXTRA_SIM_STATE)
- + " slotId: " + args.slotId + " subid: " + args.subId);
- }
+ mLogger.logSimStateFromIntent(action,
+ intent.getStringExtra(Intent.EXTRA_SIM_STATE),
+ args.slotId,
+ args.subId);
mHandler.obtainMessage(MSG_SIM_STATE_CHANGE, args.subId, args.slotId, args.simState)
.sendToTarget();
} else if (TelephonyManager.ACTION_PHONE_STATE_CHANGED.equals(action)) {
@@ -1382,10 +1374,7 @@
ServiceState serviceState = ServiceState.newFromBundle(intent.getExtras());
int subId = intent.getIntExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX,
SubscriptionManager.INVALID_SUBSCRIPTION_ID);
- if (DEBUG) {
- Log.v(TAG, "action " + action + " serviceState=" + serviceState + " subId="
- + subId);
- }
+ mLogger.logServiceStateIntent(action, serviceState, subId);
mHandler.sendMessage(
mHandler.obtainMessage(MSG_SERVICE_STATE_CHANGE, subId, 0, serviceState));
} else if (TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED.equals(action)) {
@@ -1505,7 +1494,7 @@
*/
@Override
public void onUdfpsPointerDown(int sensorId) {
- Log.d(TAG, "onUdfpsPointerDown, sensorId: " + sensorId);
+ mLogger.logUdfpsPointerDown(sensorId);
requestFaceAuth(true);
}
@@ -1514,7 +1503,7 @@
*/
@Override
public void onUdfpsPointerUp(int sensorId) {
- Log.d(TAG, "onUdfpsPointerUp, sensorId: " + sensorId);
+ mLogger.logUdfpsPointerUp(sensorId);
}
};
@@ -1810,7 +1799,9 @@
TelephonyListenerManager telephonyListenerManager,
InteractionJankMonitor interactionJankMonitor,
LatencyTracker latencyTracker,
- ActiveUnlockConfig activeUnlockConfiguration) {
+ ActiveUnlockConfig activeUnlockConfiguration,
+ KeyguardUpdateMonitorLogger logger) {
+ mLogger = logger;
mContext = context;
mSubscriptionManager = SubscriptionManager.from(context);
mTelephonyListenerManager = telephonyListenerManager;
@@ -2154,13 +2145,13 @@
|| mFingerprintRunningState == BIOMETRIC_STATE_CANCELLING_RESTARTING;
if (runningOrRestarting && !shouldListenForFingerprint) {
if (action == BIOMETRIC_ACTION_START) {
- Log.v(TAG, "Ignoring stopListeningForFingerprint()");
+ mLogger.v("Ignoring stopListeningForFingerprint()");
return;
}
stopListeningForFingerprint();
} else if (!runningOrRestarting && shouldListenForFingerprint) {
if (action == BIOMETRIC_ACTION_STOP) {
- Log.v(TAG, "Ignoring startListeningForFingerprint()");
+ mLogger.v("Ignoring startListeningForFingerprint()");
return;
}
startListeningForFingerprint();
@@ -2186,7 +2177,7 @@
* @param active If the interrupt started or ended.
*/
public void onAuthInterruptDetected(boolean active) {
- if (DEBUG) Log.d(TAG, "onAuthInterruptDetected(" + active + ")");
+ mLogger.logAuthInterruptDetected(active);
if (mAuthInterruptActive == active) {
return;
}
@@ -2201,7 +2192,7 @@
* @param userInitiatedRequest true if the user explicitly requested face auth
*/
public void requestFaceAuth(boolean userInitiatedRequest) {
- if (DEBUG) Log.d(TAG, "requestFaceAuth() userInitiated=" + userInitiatedRequest);
+ mLogger.logFaceAuthRequested(userInitiatedRequest);
mIsFaceAuthUserRequested |= userInitiatedRequest;
updateFaceListeningState(BIOMETRIC_ACTION_START);
}
@@ -2227,14 +2218,14 @@
boolean shouldListenForFace = shouldListenForFace();
if (mFaceRunningState == BIOMETRIC_STATE_RUNNING && !shouldListenForFace) {
if (action == BIOMETRIC_ACTION_START) {
- Log.v(TAG, "Ignoring stopListeningForFace()");
+ mLogger.v("Ignoring stopListeningForFace()");
return;
}
mIsFaceAuthUserRequested = false;
stopListeningForFace();
} else if (mFaceRunningState != BIOMETRIC_STATE_RUNNING && shouldListenForFace) {
if (action == BIOMETRIC_ACTION_STOP) {
- Log.v(TAG, "Ignoring startListeningForFace()");
+ mLogger.v("Ignoring startListeningForFace()");
return;
}
startListeningForFace();
@@ -2251,9 +2242,7 @@
}
if (shouldTriggerActiveUnlock()) {
- if (DEBUG_ACTIVE_UNLOCK) {
- Log.d("ActiveUnlock", "initiate active unlock triggerReason=" + reason);
- }
+ mLogger.logActiveUnlockTriggered(reason);
mTrustManager.reportUserMayRequestUnlock(KeyguardUpdateMonitor.getCurrentUser());
}
}
@@ -2281,12 +2270,7 @@
}
if (allowRequest && shouldTriggerActiveUnlock()) {
- if (DEBUG_ACTIVE_UNLOCK) {
- Log.d("ActiveUnlock", "reportUserRequestedUnlock"
- + " origin=" + requestOrigin.name()
- + " reason=" + reason
- + " dismissKeyguard=" + dismissKeyguard);
- }
+ mLogger.logUserRequestedUnlock(requestOrigin, reason, dismissKeyguard);
mTrustManager.reportUserRequestedUnlock(KeyguardUpdateMonitor.getCurrentUser(),
dismissKeyguard);
}
@@ -2436,32 +2420,30 @@
final boolean shouldListen = shouldListenKeyguardState && shouldListenUserState
&& shouldListenBouncerState && shouldListenUdfpsState && !isFingerprintLockedOut();
- if (DEBUG_FINGERPRINT || DEBUG_SPEW) {
- maybeLogListenerModelData(
- new KeyguardFingerprintListenModel(
- System.currentTimeMillis(),
- user,
- shouldListen,
- biometricEnabledForUser,
- mBouncerIsOrWillBeShowing,
- userCanSkipBouncer,
- mCredentialAttempted,
- mDeviceInteractive,
- mIsDreaming,
- isEncryptedOrLockdownForUser,
- fingerprintDisabledForUser,
- mFingerprintLockedOut,
- mGoingToSleep,
- mKeyguardGoingAway,
- mKeyguardIsVisible,
- mKeyguardOccluded,
- mOccludingAppRequestingFp,
- mIsPrimaryUser,
- shouldListenForFingerprintAssistant,
- mSwitchingUser,
- isUdfps,
- userDoesNotHaveTrust));
- }
+ maybeLogListenerModelData(
+ new KeyguardFingerprintListenModel(
+ System.currentTimeMillis(),
+ user,
+ shouldListen,
+ biometricEnabledForUser,
+ mBouncerIsOrWillBeShowing,
+ userCanSkipBouncer,
+ mCredentialAttempted,
+ mDeviceInteractive,
+ mIsDreaming,
+ isEncryptedOrLockdownForUser,
+ fingerprintDisabledForUser,
+ mFingerprintLockedOut,
+ mGoingToSleep,
+ mKeyguardGoingAway,
+ mKeyguardIsVisible,
+ mKeyguardOccluded,
+ mOccludingAppRequestingFp,
+ mIsPrimaryUser,
+ shouldListenForFingerprintAssistant,
+ mSwitchingUser,
+ isUdfps,
+ userDoesNotHaveTrust));
return shouldListen;
}
@@ -2536,59 +2518,49 @@
&& !fpLockedout;
// Aggregate relevant fields for debug logging.
- if (DEBUG_FACE || DEBUG_SPEW) {
- maybeLogListenerModelData(
- new KeyguardFaceListenModel(
- System.currentTimeMillis(),
- user,
- shouldListen,
- mAuthInterruptActive,
- becauseCannotSkipBouncer,
- biometricEnabledForUser,
- mBouncerFullyShown,
- faceAuthenticated,
- faceDisabledForUser,
- mGoingToSleep,
- awakeKeyguard,
- mKeyguardGoingAway,
- shouldListenForFaceAssistant,
- mOccludingAppRequestingFace,
- mIsPrimaryUser,
- strongAuthAllowsScanning,
- mSecureCameraLaunched,
- mSwitchingUser,
- mUdfpsBouncerShowing));
- }
+ maybeLogListenerModelData(
+ new KeyguardFaceListenModel(
+ System.currentTimeMillis(),
+ user,
+ shouldListen,
+ mAuthInterruptActive,
+ becauseCannotSkipBouncer,
+ biometricEnabledForUser,
+ mBouncerFullyShown,
+ faceAuthenticated,
+ faceDisabledForUser,
+ mGoingToSleep,
+ awakeKeyguard,
+ mKeyguardGoingAway,
+ shouldListenForFaceAssistant,
+ mOccludingAppRequestingFace,
+ mIsPrimaryUser,
+ strongAuthAllowsScanning,
+ mSecureCameraLaunched,
+ mSwitchingUser,
+ mUdfpsBouncerShowing));
return shouldListen;
}
private void maybeLogListenerModelData(KeyguardListenModel model) {
- // Too chatty, but very useful when debugging issues.
- if (DEBUG_SPEW) {
- Log.v(TAG, model.toString());
- }
+ mLogger.logKeyguardListenerModel(model);
- if (DEBUG_ACTIVE_UNLOCK
- && model instanceof KeyguardActiveUnlockModel) {
+ if (model instanceof KeyguardActiveUnlockModel) {
mListenModels.add(model);
return;
}
// Add model data to the historical buffer.
final boolean notYetRunning =
- (DEBUG_FACE
- && model instanceof KeyguardFaceListenModel
- && mFaceRunningState != BIOMETRIC_STATE_RUNNING)
- || (DEBUG_FINGERPRINT
- && model instanceof KeyguardFingerprintListenModel
- && mFingerprintRunningState != BIOMETRIC_STATE_RUNNING);
+ (model instanceof KeyguardFaceListenModel
+ && mFaceRunningState != BIOMETRIC_STATE_RUNNING)
+ || (model instanceof KeyguardFingerprintListenModel
+ && mFingerprintRunningState != BIOMETRIC_STATE_RUNNING);
final boolean running =
- (DEBUG_FACE
- && model instanceof KeyguardFaceListenModel
+ (model instanceof KeyguardFaceListenModel
&& mFaceRunningState == BIOMETRIC_STATE_RUNNING)
- || (DEBUG_FINGERPRINT
- && model instanceof KeyguardFingerprintListenModel
+ || (model instanceof KeyguardFingerprintListenModel
&& mFingerprintRunningState == BIOMETRIC_STATE_RUNNING);
if (notYetRunning && model.getListening()
|| running && !model.getListening()) {
@@ -2600,9 +2572,9 @@
final int userId = getCurrentUser();
final boolean unlockPossible = isUnlockWithFingerprintPossible(userId);
if (mFingerprintCancelSignal != null) {
- Log.e(TAG, "Cancellation signal is not null, high chance of bug in fp auth lifecycle"
- + " management. FP state: " + mFingerprintRunningState
- + ", unlockPossible: " + unlockPossible);
+ mLogger.logUnexpectedFpCancellationSignalState(
+ mFingerprintRunningState,
+ unlockPossible);
}
if (mFingerprintRunningState == BIOMETRIC_STATE_CANCELLING) {
@@ -2613,7 +2585,7 @@
// Waiting for restart via handleFingerprintError().
return;
}
- if (DEBUG) Log.v(TAG, "startListeningForFingerprint()");
+ mLogger.v("startListeningForFingerprint()");
if (unlockPossible) {
mFingerprintCancelSignal = new CancellationSignal();
@@ -2634,9 +2606,7 @@
final int userId = getCurrentUser();
final boolean unlockPossible = isUnlockWithFacePossible(userId);
if (mFaceCancelSignal != null) {
- Log.e(TAG, "Cancellation signal is not null, high chance of bug in face auth lifecycle"
- + " management. Face state: " + mFaceRunningState
- + ", unlockPossible: " + unlockPossible);
+ mLogger.logUnexpectedFaceCancellationSignalState(mFaceRunningState, unlockPossible);
}
if (mFaceRunningState == BIOMETRIC_STATE_CANCELLING) {
@@ -2646,7 +2616,7 @@
// Waiting for ERROR_CANCELED before requesting auth again
return;
}
- if (DEBUG) Log.v(TAG, "startListeningForFace(): " + mFaceRunningState);
+ mLogger.logStartedListeningForFace(mFaceRunningState);
if (unlockPossible) {
mFaceCancelSignal = new CancellationSignal();
@@ -2712,7 +2682,7 @@
}
private void stopListeningForFingerprint() {
- if (DEBUG) Log.v(TAG, "stopListeningForFingerprint()");
+ mLogger.v("stopListeningForFingerprint()");
if (mFingerprintRunningState == BIOMETRIC_STATE_RUNNING) {
if (mFingerprintCancelSignal != null) {
mFingerprintCancelSignal.cancel();
@@ -2728,7 +2698,7 @@
}
private void stopListeningForFace() {
- if (DEBUG) Log.v(TAG, "stopListeningForFace()");
+ mLogger.v("stopListeningForFace()");
if (mFaceRunningState == BIOMETRIC_STATE_RUNNING) {
if (mFaceCancelSignal != null) {
mFaceCancelSignal.cancel();
@@ -2757,7 +2727,7 @@
if (mDeviceProvisioned) {
mHandler.sendEmptyMessage(MSG_DEVICE_PROVISIONED);
}
- if (DEBUG) Log.d(TAG, "DEVICE_PROVISIONED state = " + mDeviceProvisioned);
+ mLogger.logDeviceProvisionedState(mDeviceProvisioned);
}
};
@@ -2862,7 +2832,7 @@
*/
private void handlePhoneStateChanged(String newState) {
Assert.isMainThread();
- if (DEBUG) Log.d(TAG, "handlePhoneStateChanged(" + newState + ")");
+ mLogger.logPhoneStateChanged(newState);
if (TelephonyManager.EXTRA_STATE_IDLE.equals(newState)) {
mPhoneState = TelephonyManager.CALL_STATE_IDLE;
} else if (TelephonyManager.EXTRA_STATE_OFFHOOK.equals(newState)) {
@@ -2883,7 +2853,7 @@
*/
private void handleTimeUpdate() {
Assert.isMainThread();
- if (DEBUG) Log.d(TAG, "handleTimeUpdate");
+ mLogger.d("handleTimeUpdate");
for (int i = 0; i < mCallbacks.size(); i++) {
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
if (cb != null) {
@@ -2897,7 +2867,7 @@
*/
private void handleTimeZoneUpdate(String timeZone) {
Assert.isMainThread();
- if (DEBUG) Log.d(TAG, "handleTimeZoneUpdate");
+ mLogger.d("handleTimeZoneUpdate");
for (int i = 0; i < mCallbacks.size(); i++) {
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
if (cb != null) {
@@ -2915,7 +2885,7 @@
*/
private void handleTimeFormatUpdate(String timeFormat) {
Assert.isMainThread();
- if (DEBUG) Log.d(TAG, "handleTimeFormatUpdate timeFormat=" + timeFormat);
+ mLogger.logTimeFormatChanged(timeFormat);
for (int i = 0; i < mCallbacks.size(); i++) {
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
if (cb != null) {
@@ -2929,7 +2899,7 @@
*/
private void handleBatteryUpdate(BatteryStatus status) {
Assert.isMainThread();
- if (DEBUG) Log.d(TAG, "handleBatteryUpdate");
+ mLogger.d("handleBatteryUpdate");
final boolean batteryUpdateInteresting = isBatteryUpdateInteresting(mBatteryStatus, status);
mBatteryStatus = status;
if (batteryUpdateInteresting) {
@@ -2966,14 +2936,11 @@
@VisibleForTesting
void handleSimStateChange(int subId, int slotId, int state) {
Assert.isMainThread();
- if (DEBUG_SIM_STATES) {
- Log.d(TAG, "handleSimStateChange(subId=" + subId + ", slotId="
- + slotId + ", state=" + state + ")");
- }
+ mLogger.logSimState(subId, slotId, state);
boolean becameAbsent = false;
if (!SubscriptionManager.isValidSubscriptionId(subId)) {
- Log.w(TAG, "invalid subId in handleSimStateChange()");
+ mLogger.w("invalid subId in handleSimStateChange()");
/* Only handle No SIM(ABSENT) and Card Error(CARD_IO_ERROR) due to
* handleServiceStateChange() handle other case */
if (state == TelephonyManager.SIM_STATE_ABSENT) {
@@ -3022,13 +2989,10 @@
*/
@VisibleForTesting
void handleServiceStateChange(int subId, ServiceState serviceState) {
- if (DEBUG) {
- Log.d(TAG,
- "handleServiceStateChange(subId=" + subId + ", serviceState=" + serviceState);
- }
+ mLogger.logServiceStateChange(subId, serviceState);
if (!SubscriptionManager.isValidSubscriptionId(subId)) {
- Log.w(TAG, "invalid subId in handleServiceStateChange()");
+ mLogger.w("invalid subId in handleServiceStateChange()");
return;
} else {
updateTelephonyCapable(true);
@@ -3050,7 +3014,7 @@
*/
public void onKeyguardVisibilityChanged(boolean showing) {
Assert.isMainThread();
- Log.d(TAG, "onKeyguardVisibilityChanged(" + showing + ")");
+ mLogger.logKeyguardVisibilityChanged(showing);
mKeyguardIsVisible = showing;
if (showing) {
@@ -3070,7 +3034,7 @@
* Handle {@link #MSG_KEYGUARD_RESET}
*/
private void handleKeyguardReset() {
- if (DEBUG) Log.d(TAG, "handleKeyguardReset");
+ mLogger.d("handleKeyguardReset");
updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE);
mNeedsSlowUnlockTransition = resolveNeedsSlowUnlockTransition();
}
@@ -3084,8 +3048,8 @@
0 /* flags */, getCurrentUser());
if (resolveInfo == null) {
- Log.w(TAG, "resolveNeedsSlowUnlockTransition: returning false since activity "
- + "could not be resolved.");
+ mLogger.w("resolveNeedsSlowUnlockTransition: returning false since activity could "
+ + "not be resolved.");
return false;
}
@@ -3103,11 +3067,7 @@
final boolean wasBouncerFullyShown = mBouncerFullyShown;
mBouncerIsOrWillBeShowing = bouncerIsOrWillBeShowing == 1;
mBouncerFullyShown = bouncerFullyShown == 1;
- if (DEBUG) {
- Log.d(TAG, "handleKeyguardBouncerChanged"
- + " bouncerIsOrWillBeShowing=" + mBouncerIsOrWillBeShowing
- + " bouncerFullyShowing=" + mBouncerFullyShown);
- }
+ mLogger.logKeyguardBouncerChanged(mBouncerIsOrWillBeShowing, mBouncerFullyShown);
if (mBouncerFullyShown) {
// If the bouncer is shown, always clear this flag. This can happen in the following
@@ -3227,9 +3187,7 @@
*/
public void removeCallback(KeyguardUpdateMonitorCallback callback) {
Assert.isMainThread();
- if (DEBUG) {
- Log.v(TAG, "*** unregister callback for " + callback);
- }
+ mLogger.logUnregisterCallback(callback);
mCallbacks.removeIf(el -> el.get() == callback);
}
@@ -3242,15 +3200,14 @@
*/
public void registerCallback(KeyguardUpdateMonitorCallback callback) {
Assert.isMainThread();
- if (DEBUG) Log.v(TAG, "*** register callback for " + callback);
+ mLogger.logRegisterCallback(callback);
// Prevent adding duplicate callbacks
for (int i = 0; i < mCallbacks.size(); i++) {
if (mCallbacks.get(i).get() == callback) {
- if (DEBUG) {
- Log.e(TAG, "Object tried to add another callback",
- new Exception("Called by"));
- }
+ mLogger.logException(
+ new Exception("Called by"),
+ "Object tried to add another callback");
return;
}
}
@@ -3300,11 +3257,7 @@
*/
public void sendKeyguardBouncerChanged(boolean bouncerIsOrWillBeShowing,
boolean bouncerFullyShown) {
- if (DEBUG) {
- Log.d(TAG, "sendKeyguardBouncerChanged"
- + " bouncerIsOrWillBeShowing=" + bouncerIsOrWillBeShowing
- + " bouncerFullyShown=" + bouncerFullyShown);
- }
+ mLogger.logSendKeyguardBouncerChanged(bouncerIsOrWillBeShowing, bouncerFullyShown);
Message message = mHandler.obtainMessage(MSG_KEYGUARD_BOUNCER_CHANGED);
message.arg1 = bouncerIsOrWillBeShowing ? 1 : 0;
message.arg2 = bouncerFullyShown ? 1 : 0;
@@ -3321,7 +3274,7 @@
*/
@MainThread
public void reportSimUnlocked(int subId) {
- if (DEBUG_SIM_STATES) Log.v(TAG, "reportSimUnlocked(subId=" + subId + ")");
+ mLogger.logSimUnlocked(subId);
handleSimStateChange(subId, getSlotId(subId), TelephonyManager.SIM_STATE_READY);
}
@@ -3598,7 +3551,9 @@
try {
ActivityManager.getService().unregisterUserSwitchObserver(mUserSwitchObserver);
} catch (RemoteException e) {
- Log.d(TAG, "RemoteException onDestroy. cannot unregister userSwitchObserver");
+ mLogger.logException(
+ e,
+ "RemoteException onDestroy. cannot unregister userSwitchObserver");
}
TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mTaskStackListener);
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
new file mode 100644
index 0000000..035b7f0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
@@ -0,0 +1,326 @@
+/*
+ * 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.keyguard.logging
+
+import android.hardware.biometrics.BiometricConstants.LockoutMode
+import android.telephony.ServiceState
+import android.telephony.SubscriptionInfo
+import com.android.keyguard.ActiveUnlockConfig
+import com.android.keyguard.KeyguardListenModel
+import com.android.keyguard.KeyguardUpdateMonitorCallback
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
+import com.android.systemui.log.LogLevel.DEBUG
+import com.android.systemui.log.LogLevel.ERROR
+import com.android.systemui.log.LogLevel.INFO
+import com.android.systemui.log.LogLevel.VERBOSE
+import com.android.systemui.log.LogLevel.WARNING
+import com.android.systemui.log.dagger.KeyguardUpdateMonitorLog
+import com.google.errorprone.annotations.CompileTimeConstant
+import javax.inject.Inject
+
+private const val TAG = "KeyguardUpdateMonitorLog"
+
+/**
+ * Helper class for logging for [com.android.keyguard.KeyguardUpdateMonitor]
+ */
+class KeyguardUpdateMonitorLogger @Inject constructor(
+ @KeyguardUpdateMonitorLog private val logBuffer: LogBuffer
+) {
+ fun d(@CompileTimeConstant msg: String) = log(msg, DEBUG)
+
+ fun e(@CompileTimeConstant msg: String) = log(msg, ERROR)
+
+ fun v(@CompileTimeConstant msg: String) = log(msg, ERROR)
+
+ fun w(@CompileTimeConstant msg: String) = log(msg, WARNING)
+
+ fun log(@CompileTimeConstant msg: String, level: LogLevel) = logBuffer.log(TAG, level, msg)
+
+ fun logActiveUnlockTriggered(reason: String) {
+ logBuffer.log("ActiveUnlock", DEBUG,
+ { str1 = reason },
+ { "initiate active unlock triggerReason=$str1" })
+ }
+
+ fun logAuthInterruptDetected(active: Boolean) {
+ logBuffer.log(TAG, DEBUG,
+ { bool1 = active },
+ { "onAuthInterruptDetected($bool1)" })
+ }
+
+ fun logBroadcastReceived(action: String?) {
+ logBuffer.log(TAG, DEBUG, { str1 = action }, { "received broadcast $str1" })
+ }
+
+ fun logDeviceProvisionedState(deviceProvisioned: Boolean) {
+ logBuffer.log(TAG, DEBUG,
+ { bool1 = deviceProvisioned },
+ { "DEVICE_PROVISIONED state = $bool1" })
+ }
+
+ fun logException(ex: Exception, @CompileTimeConstant logMsg: String) {
+ logBuffer.log(TAG, ERROR, {}, { logMsg }, exception = ex)
+ }
+
+ fun logFaceAcquired(acquireInfo: Int) {
+ logBuffer.log(TAG, DEBUG,
+ { int1 = acquireInfo },
+ { "Face acquired acquireInfo=$int1" })
+ }
+
+ fun logFaceAuthDisabledForUser(userId: Int) {
+ logBuffer.log(TAG, DEBUG,
+ { int1 = userId },
+ { "Face authentication disabled by DPM for userId: $int1" })
+ }
+ fun logFaceAuthError(msgId: Int, originalErrMsg: String) {
+ logBuffer.log(TAG, DEBUG, {
+ str1 = originalErrMsg
+ int1 = msgId
+ }, { "Face error received: $str1 msgId= $int1" })
+ }
+
+ fun logFaceAuthForWrongUser(authUserId: Int) {
+ logBuffer.log(TAG, DEBUG,
+ { int1 = authUserId },
+ { "Face authenticated for wrong user: $int1" })
+ }
+
+ fun logFaceAuthHelpMsg(msgId: Int, helpMsg: String) {
+ logBuffer.log(TAG, DEBUG, {
+ int1 = msgId
+ str1 = helpMsg
+ }, { "Face help received, msgId: $int1 msg: $str1" })
+ }
+
+ fun logFaceAuthRequested(userInitiatedRequest: Boolean) {
+ logBuffer.log(TAG, DEBUG,
+ { bool1 = userInitiatedRequest },
+ { "requestFaceAuth() userInitiated=$bool1" })
+ }
+
+ fun logFaceAuthSuccess(userId: Int) {
+ logBuffer.log(TAG, DEBUG,
+ { int1 = userId },
+ { "Face auth succeeded for user $int1" })
+ }
+
+ fun logFaceLockoutReset(@LockoutMode mode: Int) {
+ logBuffer.log(TAG, DEBUG, { int1 = mode }, { "handleFaceLockoutReset: $int1" })
+ }
+
+ fun logFaceRunningState(faceRunningState: Int) {
+ logBuffer.log(TAG, DEBUG, { int1 = faceRunningState }, { "faceRunningState: $int1" })
+ }
+
+ fun logFingerprintAuthForWrongUser(authUserId: Int) {
+ logBuffer.log(TAG, DEBUG,
+ { int1 = authUserId },
+ { "Fingerprint authenticated for wrong user: $int1" })
+ }
+
+ fun logFingerprintDisabledForUser(userId: Int) {
+ logBuffer.log(TAG, DEBUG,
+ { int1 = userId },
+ { "Fingerprint disabled by DPM for userId: $int1" })
+ }
+
+ fun logFingerprintLockoutReset(@LockoutMode mode: Int) {
+ logBuffer.log(TAG, DEBUG, { int1 = mode }, { "handleFingerprintLockoutReset: $int1" })
+ }
+
+ fun logFingerprintRunningState(fingerprintRunningState: Int) {
+ logBuffer.log(TAG, DEBUG,
+ { int1 = fingerprintRunningState },
+ { "fingerprintRunningState: $int1" })
+ }
+
+ fun logInvalidSubId(subId: Int) {
+ logBuffer.log(TAG, INFO,
+ { int1 = subId },
+ { "Previously active sub id $int1 is now invalid, will remove" })
+ }
+
+ fun logKeyguardBouncerChanged(bouncerIsOrWillBeShowing: Boolean, bouncerFullyShown: Boolean) {
+ logBuffer.log(TAG, DEBUG, {
+ bool1 = bouncerIsOrWillBeShowing
+ bool2 = bouncerFullyShown
+ }, {
+ "handleKeyguardBouncerChanged " +
+ "bouncerIsOrWillBeShowing=$bool1 bouncerFullyShowing=$bool2"
+ })
+ }
+
+ fun logKeyguardListenerModel(model: KeyguardListenModel) {
+ logBuffer.log(TAG, VERBOSE, { str1 = "$model" }, { str1!! })
+ }
+
+ fun logKeyguardVisibilityChanged(showing: Boolean) {
+ logBuffer.log(TAG, DEBUG, { bool1 = showing }, { "onKeyguardVisibilityChanged($bool1)" })
+ }
+
+ fun logMissingSupervisorAppError(userId: Int) {
+ logBuffer.log(TAG, ERROR,
+ { int1 = userId },
+ { "No Profile Owner or Device Owner supervision app found for User $int1" })
+ }
+
+ fun logPhoneStateChanged(newState: String) {
+ logBuffer.log(TAG, DEBUG,
+ { str1 = newState },
+ { "handlePhoneStateChanged($str1)" })
+ }
+
+ fun logRegisterCallback(callback: KeyguardUpdateMonitorCallback?) {
+ logBuffer.log(TAG, VERBOSE,
+ { str1 = "$callback" },
+ { "*** register callback for $str1" })
+ }
+
+ fun logRetryingAfterFaceHwUnavailable(retryCount: Int) {
+ logBuffer.log(TAG, WARNING,
+ { int1 = retryCount },
+ { "Retrying face after HW unavailable, attempt $int1" })
+ }
+
+ fun logRetryAfterFpHwUnavailable(retryCount: Int) {
+ logBuffer.log(TAG, WARNING,
+ { int1 = retryCount },
+ { "Retrying fingerprint attempt: $int1" })
+ }
+
+ fun logSendKeyguardBouncerChanged(
+ bouncerIsOrWillBeShowing: Boolean,
+ bouncerFullyShown: Boolean,
+ ) {
+ logBuffer.log(TAG, DEBUG, {
+ bool1 = bouncerIsOrWillBeShowing
+ bool2 = bouncerFullyShown
+ }, {
+ "sendKeyguardBouncerChanged bouncerIsOrWillBeShowing=$bool1 " +
+ "bouncerFullyShown=$bool2"
+ })
+ }
+
+ fun logServiceStateChange(subId: Int, serviceState: ServiceState?) {
+ logBuffer.log(TAG, DEBUG, {
+ int1 = subId
+ str1 = "$serviceState"
+ }, { "handleServiceStateChange(subId=$int1, serviceState=$str1)" })
+ }
+
+ fun logServiceStateIntent(action: String, serviceState: ServiceState?, subId: Int) {
+ logBuffer.log(TAG, VERBOSE, {
+ str1 = action
+ str2 = "$serviceState"
+ int1 = subId
+ }, { "action $str1 serviceState=$str2 subId=$int1" })
+ }
+
+ fun logSimState(subId: Int, slotId: Int, state: Int) {
+ logBuffer.log(TAG, DEBUG, {
+ int1 = subId
+ int2 = slotId
+ long1 = state.toLong()
+ }, { "handleSimStateChange(subId=$int1, slotId=$int2, state=$long1)" })
+ }
+
+ fun logSimStateFromIntent(action: String, extraSimState: String, slotId: Int, subId: Int) {
+ logBuffer.log(TAG, VERBOSE, {
+ str1 = action
+ str2 = extraSimState
+ int1 = slotId
+ int2 = subId
+ }, { "action $str1 state: $str2 slotId: $int1 subid: $int2" })
+ }
+
+ fun logSimUnlocked(subId: Int) {
+ logBuffer.log(TAG, VERBOSE, { int1 = subId }, { "reportSimUnlocked(subId=$int1)" })
+ }
+
+ fun logStartedListeningForFace(faceRunningState: Int) {
+ logBuffer.log(TAG, VERBOSE,
+ { int1 = faceRunningState },
+ { "startListeningForFace(): $int1" })
+ }
+
+ fun logSubInfo(subInfo: SubscriptionInfo?) {
+ logBuffer.log(TAG, VERBOSE,
+ { str1 = "$subInfo" },
+ { "SubInfo:$str1" })
+ }
+
+ fun logTimeFormatChanged(newTimeFormat: String) {
+ logBuffer.log(TAG, DEBUG,
+ { str1 = newTimeFormat },
+ { "handleTimeFormatUpdate timeFormat=$str1" })
+ }
+
+ fun logUdfpsPointerDown(sensorId: Int) {
+ logBuffer.log(TAG, DEBUG,
+ { int1 = sensorId },
+ { "onUdfpsPointerDown, sensorId: $int1" })
+ }
+ fun logUdfpsPointerUp(sensorId: Int) {
+ logBuffer.log(TAG, DEBUG,
+ { int1 = sensorId },
+ { "onUdfpsPointerUp, sensorId: $int1" })
+ }
+
+ fun logUnexpectedFaceCancellationSignalState(faceRunningState: Int, unlockPossible: Boolean) {
+ logBuffer.log(TAG, ERROR, {
+ int1 = faceRunningState
+ bool1 = unlockPossible
+ }, {
+ "Cancellation signal is not null, high chance of bug in " +
+ "face auth lifecycle management. " +
+ "Face state: $int1, unlockPossible: $bool1"
+ })
+ }
+
+ fun logUnexpectedFpCancellationSignalState(
+ fingerprintRunningState: Int,
+ unlockPossible: Boolean
+ ) {
+ logBuffer.log(TAG, ERROR, {
+ int1 = fingerprintRunningState
+ bool1 = unlockPossible
+ }, {
+ "Cancellation signal is not null, high chance of bug in " +
+ "fp auth lifecycle management. FP state: $int1, unlockPossible: $bool1"
+ })
+ }
+
+ fun logUnregisterCallback(callback: KeyguardUpdateMonitorCallback?) {
+ logBuffer.log(TAG, VERBOSE,
+ { str1 = "$callback" },
+ { "*** unregister callback for $str1" })
+ }
+
+ fun logUserRequestedUnlock(
+ requestOrigin: ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN,
+ reason: String,
+ dismissKeyguard: Boolean
+ ) {
+ logBuffer.log("ActiveUnlock", DEBUG, {
+ str1 = requestOrigin.name
+ str2 = reason
+ bool1 = dismissKeyguard
+ }, { "reportUserRequestedUnlock origin=$str1 reason=$str2 dismissKeyguard=$bool1" })
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index 720d708..5c84ff3 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -81,7 +81,6 @@
import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.NotificationShadeWindowController;
-import com.android.systemui.statusbar.NotificationViewHierarchyManager;
import com.android.systemui.statusbar.SmartReplyController;
import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.statusbar.events.PrivacyDotViewController;
@@ -323,7 +322,6 @@
@Inject Lazy<SmartReplyConstants> mSmartReplyConstants;
@Inject Lazy<NotificationListener> mNotificationListener;
@Inject Lazy<NotificationLogger> mNotificationLogger;
- @Inject Lazy<NotificationViewHierarchyManager> mNotificationViewHierarchyManager;
@Inject Lazy<NotificationFilter> mNotificationFilter;
@Inject Lazy<KeyguardDismissUtil> mKeyguardDismissUtil;
@Inject Lazy<SmartReplyController> mSmartReplyController;
@@ -540,8 +538,6 @@
mProviders.put(SmartReplyConstants.class, mSmartReplyConstants::get);
mProviders.put(NotificationListener.class, mNotificationListener::get);
mProviders.put(NotificationLogger.class, mNotificationLogger::get);
- mProviders.put(NotificationViewHierarchyManager.class,
- mNotificationViewHierarchyManager::get);
mProviders.put(NotificationFilter.class, mNotificationFilter::get);
mProviders.put(KeyguardDismissUtil.class, mKeyguardDismissUtil::get);
mProviders.put(SmartReplyController.class, mSmartReplyController::get);
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
index 448b99b..a1288b5 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
@@ -500,7 +500,7 @@
private void handleTakeScreenshot() {
ScreenshotHelper screenshotHelper = new ScreenshotHelper(mContext);
- screenshotHelper.takeScreenshot(WindowManager.TAKE_SCREENSHOT_FULLSCREEN, true, true,
+ screenshotHelper.takeScreenshot(WindowManager.TAKE_SCREENSHOT_FULLSCREEN,
SCREENSHOT_ACCESSIBILITY_ACTIONS, new Handler(Looper.getMainLooper()), null);
}
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistLogger.kt b/packages/SystemUI/src/com/android/systemui/assist/AssistLogger.kt
index e30f2e4..7326ab3 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistLogger.kt
@@ -25,10 +25,10 @@
import com.android.internal.logging.InstanceIdSequence
import com.android.internal.logging.UiEventLogger
import com.android.internal.util.FrameworkStatsLog
-import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.assist.AssistantInvocationEvent.Companion.deviceStateFromLegacyDeviceState
import com.android.systemui.assist.AssistantInvocationEvent.Companion.eventFromLegacyInvocationType
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.settings.UserTracker
import javax.inject.Inject
/** Class for reporting events related to Assistant sessions. */
@@ -37,7 +37,8 @@
protected val context: Context,
protected val uiEventLogger: UiEventLogger,
private val assistUtils: AssistUtils,
- private val phoneStateMonitor: PhoneStateMonitor
+ private val phoneStateMonitor: PhoneStateMonitor,
+ private val userTracker: UserTracker,
) {
private val instanceIdSequence = InstanceIdSequence(INSTANCE_ID_MAX)
@@ -78,7 +79,7 @@
FrameworkStatsLog.ASSISTANT_INVOCATION_REPORTED,
invocationEvent.id,
assistantUid,
- assistComponentFinal.flattenToString(),
+ assistComponentFinal?.flattenToString() ?: "",
getOrCreateInstanceId().id,
deviceStateFinal,
false)
@@ -91,7 +92,7 @@
uiEventLogger.logWithInstanceId(
sessionEvent,
assistantUid,
- assistantComponent.flattenToString(),
+ assistantComponent?.flattenToString(),
getOrCreateInstanceId())
if (SESSION_END_EVENTS.contains(sessionEvent)) {
@@ -112,11 +113,15 @@
currentInstanceId = null
}
- protected fun getAssistantComponentForCurrentUser(): ComponentName {
- return assistUtils.getAssistComponentForUser(KeyguardUpdateMonitor.getCurrentUser())
+ protected fun getAssistantComponentForCurrentUser(): ComponentName? {
+ return assistUtils.getAssistComponentForUser(userTracker.userId)
}
- protected fun getAssistantUid(assistantComponent: ComponentName): Int {
+ protected fun getAssistantUid(assistantComponent: ComponentName?): Int {
+ if (assistantComponent == null) {
+ return 0
+ }
+
var assistantUid = 0
try {
assistantUid = context.packageManager.getApplicationInfo(
@@ -138,4 +143,4 @@
AssistantSessionEvent.ASSISTANT_SESSION_INVOCATION_CANCELLED,
AssistantSessionEvent.ASSISTANT_SESSION_CLOSE)
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsController.kt
index bbffb73..d03106b 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsController.kt
@@ -43,6 +43,8 @@
import android.view.ViewPropertyAnimator
import android.view.WindowInsets
import android.view.WindowManager
+import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION
+import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY
import android.view.accessibility.AccessibilityEvent
import androidx.annotation.RawRes
import com.airbnb.lottie.LottieAnimationView
@@ -130,7 +132,7 @@
fitInsetsTypes = 0 // overrides default, avoiding status bars during layout
gravity = Gravity.TOP or Gravity.LEFT
layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
- privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY
+ privateFlags = PRIVATE_FLAG_TRUSTED_OVERLAY or PRIVATE_FLAG_NO_MOVE_ANIMATION
}
init {
diff --git a/packages/SystemUI/src/com/android/systemui/common/coroutine/ChannelExt.kt b/packages/SystemUI/src/com/android/systemui/common/coroutine/ChannelExt.kt
new file mode 100644
index 0000000..6f3beac
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/common/coroutine/ChannelExt.kt
@@ -0,0 +1,54 @@
+/*
+ * 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.common.coroutine
+
+import android.util.Log
+import kotlinx.coroutines.channels.SendChannel
+import kotlinx.coroutines.channels.onFailure
+
+object ChannelExt {
+
+ /**
+ * Convenience wrapper around [SendChannel.trySend] that also logs on failure. This is the
+ * equivalent of calling:
+ *
+ * ```
+ * sendChannel.trySend(element).onFailure {
+ * Log.e(
+ * loggingTag,
+ * "Failed to send $elementDescription" +
+ * " - downstream canceled or failed.",
+ * it,
+ * )
+ *}
+ * ```
+ */
+ fun <T> SendChannel<T>.trySendWithFailureLogging(
+ element: T,
+ loggingTag: String,
+ elementDescription: String = "updated state",
+ ) {
+ trySend(element).onFailure {
+ Log.e(
+ loggingTag,
+ "Failed to send $elementDescription - downstream canceled or failed.",
+ it,
+ )
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/common/coroutine/ConflatedCallbackFlow.kt b/packages/SystemUI/src/com/android/systemui/common/coroutine/ConflatedCallbackFlow.kt
new file mode 100644
index 0000000..d4a1f74
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/common/coroutine/ConflatedCallbackFlow.kt
@@ -0,0 +1,40 @@
+/*
+ * 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.common.coroutine
+
+import kotlin.experimental.ExperimentalTypeInference
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.channels.ProducerScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.buffer
+import kotlinx.coroutines.flow.callbackFlow
+
+object ConflatedCallbackFlow {
+
+ /**
+ * A [callbackFlow] that uses a buffer [Channel] that is "conflated" meaning that, if
+ * backpressure occurs (if the producer that emits new values into the flow is faster than the
+ * consumer(s) of the values in the flow), the values are buffered and, if the buffer fills up,
+ * we drop the oldest values automatically instead of suspending the producer.
+ */
+ @Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+ @OptIn(ExperimentalTypeInference::class, ExperimentalCoroutinesApi::class)
+ fun <T> conflatedCallbackFlow(
+ @BuilderInference block: suspend ProducerScope<T>.() -> Unit,
+ ): Flow<T> = callbackFlow(block).buffer(capacity = Channel.CONFLATED)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/common/data/model/Position.kt b/packages/SystemUI/src/com/android/systemui/common/data/model/Position.kt
new file mode 100644
index 0000000..7c9df10
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/common/data/model/Position.kt
@@ -0,0 +1,23 @@
+/*
+ * 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.common.data.model
+
+/** Models a two-dimensional position */
+data class Position(
+ val x: Int,
+ val y: Int,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/common/domain/model/Position.kt b/packages/SystemUI/src/com/android/systemui/common/domain/model/Position.kt
new file mode 100644
index 0000000..f697c0a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/common/domain/model/Position.kt
@@ -0,0 +1,34 @@
+/*
+ * 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.common.domain.model
+
+import com.android.systemui.common.data.model.Position as DataLayerPosition
+
+/** Models a two-dimensional position */
+data class Position(
+ val x: Int,
+ val y: Int,
+) {
+ companion object {
+ fun DataLayerPosition.toDomainLayer(): Position {
+ return Position(
+ x = x,
+ y = y,
+ )
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/containeddrawable/ContainedDrawable.kt b/packages/SystemUI/src/com/android/systemui/containeddrawable/ContainedDrawable.kt
new file mode 100644
index 0000000..d6a059d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/containeddrawable/ContainedDrawable.kt
@@ -0,0 +1,27 @@
+/*
+ * 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.containeddrawable
+
+import android.graphics.drawable.Drawable
+import androidx.annotation.DrawableRes
+
+/** Convenience container for [Drawable] or a way to load it later. */
+sealed class ContainedDrawable {
+ data class WithDrawable(val drawable: Drawable) : ContainedDrawable()
+ data class WithResource(@DrawableRes val resourceId: Int) : ContainedDrawable()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt
index 2fd3731..9e4a364 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt
@@ -21,20 +21,22 @@
import android.database.ContentObserver
import android.os.UserHandle
import android.provider.Settings
+import com.android.internal.widget.LockPatternUtils
+import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT
import com.android.systemui.controls.controller.ControlsController
+import com.android.systemui.controls.controller.ControlsTileResourceConfiguration
+import com.android.systemui.controls.controller.ControlsTileResourceConfigurationImpl
import com.android.systemui.controls.management.ControlsListingController
import com.android.systemui.controls.ui.ControlsUiController
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.settings.SecureSettings
-import com.android.internal.widget.LockPatternUtils
-import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT
-import com.android.systemui.controls.controller.ControlsTileResourceConfiguration
-import com.android.systemui.controls.controller.ControlsTileResourceConfigurationImpl
import dagger.Lazy
import java.util.Optional
import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
/**
* Pseudo-component to inject into classes outside `com.android.systemui.controls`.
@@ -59,7 +61,8 @@
private val contentResolver: ContentResolver
get() = context.contentResolver
- private var canShowWhileLockedSetting = false
+ private val _canShowWhileLockedSetting = MutableStateFlow(false)
+ val canShowWhileLockedSetting = _canShowWhileLockedSetting.asStateFlow()
private val controlsTileResourceConfiguration: ControlsTileResourceConfiguration =
optionalControlsTileResourceConfiguration.orElse(
@@ -117,7 +120,7 @@
== STRONG_AUTH_REQUIRED_AFTER_BOOT) {
return Visibility.AVAILABLE_AFTER_UNLOCK
}
- if (!canShowWhileLockedSetting && !keyguardStateController.isUnlocked()) {
+ if (!canShowWhileLockedSetting.value && !keyguardStateController.isUnlocked()) {
return Visibility.AVAILABLE_AFTER_UNLOCK
}
@@ -125,7 +128,7 @@
}
private fun updateShowWhileLocked() {
- canShowWhileLockedSetting = secureSettings.getIntForUser(
+ _canShowWhileLockedSetting.value = secureSettings.getIntForUser(
Settings.Secure.LOCKSCREEN_SHOW_CONTROLS, 0, UserHandle.USER_CURRENT) != 0
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
index 718befa..029cabb 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
@@ -31,7 +31,6 @@
import com.android.systemui.media.taptotransfer.receiver.MediaTttChipControllerReceiver;
import com.android.systemui.media.taptotransfer.sender.MediaTttChipControllerSender;
import com.android.systemui.people.PeopleProvider;
-import com.android.systemui.statusbar.pipeline.ConnectivityInfoProcessor;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.unfold.FoldStateLogger;
import com.android.systemui.unfold.FoldStateLoggingProvider;
@@ -131,7 +130,6 @@
getMediaTttCommandLineHelper();
getMediaMuteAwaitConnectionCli();
getNearbyMediaDevicesManager();
- getConnectivityInfoProcessor();
getUnfoldLatencyTracker().init();
getFoldStateLoggingProvider().ifPresent(FoldStateLoggingProvider::init);
getFoldStateLogger().ifPresent(FoldStateLogger::init);
@@ -214,9 +212,6 @@
/** */
Optional<NearbyMediaDevicesManager> getNearbyMediaDevicesManager();
- /** */
- Optional<ConnectivityInfoProcessor> getConnectivityInfoProcessor();
-
/**
* Returns {@link CoreStartable}s that should be started with the application.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/doze/util/BurnInHelperWrapper.kt b/packages/SystemUI/src/com/android/systemui/doze/util/BurnInHelperWrapper.kt
new file mode 100644
index 0000000..d853e04
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/doze/util/BurnInHelperWrapper.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2016 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.doze.util
+
+import javax.inject.Inject
+
+/** Injectable wrapper around `BurnInHelper` functions */
+class BurnInHelperWrapper @Inject constructor() {
+
+ fun burnInOffset(amplitude: Int, xAxis: Boolean): Int {
+ return getBurnInOffset(amplitude, xAxis)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamClockDateComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamClockDateComplication.java
deleted file mode 100644
index 1ca06b2..0000000
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamClockDateComplication.java
+++ /dev/null
@@ -1,111 +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.systemui.dreams.complication;
-
-import static com.android.systemui.dreams.complication.dagger.DreamClockDateComplicationModule.DREAM_CLOCK_DATE_COMPLICATION_LAYOUT_PARAMS;
-import static com.android.systemui.dreams.complication.dagger.DreamClockDateComplicationModule.DREAM_CLOCK_DATE_COMPLICATION_VIEW;
-
-import android.content.Context;
-import android.view.View;
-
-import com.android.systemui.CoreStartable;
-import com.android.systemui.dreams.DreamOverlayStateController;
-
-import javax.inject.Inject;
-import javax.inject.Named;
-import javax.inject.Provider;
-
-/**
- * Clock Date Complication that produce Clock Date view holder.
- */
-public class DreamClockDateComplication implements Complication {
- private final Provider<DreamClockDateViewHolder> mDreamClockDateViewHolderProvider;
-
- /**
- * Default constructor for {@link DreamClockDateComplication}.
- */
- @Inject
- public DreamClockDateComplication(
- Provider<DreamClockDateViewHolder> dreamClockDateViewHolderProvider) {
- mDreamClockDateViewHolderProvider = dreamClockDateViewHolderProvider;
- }
-
- @Override
- public int getRequiredTypeAvailability() {
- return COMPLICATION_TYPE_DATE;
- }
-
- /**
- * Create {@link DreamClockDateViewHolder}.
- */
- @Override
- public ViewHolder createView(ComplicationViewModel model) {
- return mDreamClockDateViewHolderProvider.get();
- }
-
- /**
- * {@link CoreStartable} responsible for registering {@link DreamClockDateComplication} with
- * SystemUI.
- */
- public static class Registrant extends CoreStartable {
- private final DreamOverlayStateController mDreamOverlayStateController;
- private final DreamClockDateComplication mComplication;
-
- /**
- * Default constructor to register {@link DreamClockDateComplication}.
- */
- @Inject
- public Registrant(Context context,
- DreamOverlayStateController dreamOverlayStateController,
- DreamClockDateComplication dreamClockDateComplication) {
- super(context);
- mDreamOverlayStateController = dreamOverlayStateController;
- mComplication = dreamClockDateComplication;
- }
-
- @Override
- public void start() {
- mDreamOverlayStateController.addComplication(mComplication);
- }
- }
-
- /**
- * {@link ViewHolder} to contain value/logic associated with {@link DreamClockDateComplication}.
- */
- public static class DreamClockDateViewHolder implements ViewHolder {
- private final View mView;
- private final ComplicationLayoutParams mLayoutParams;
-
- @Inject
- DreamClockDateViewHolder(@Named(DREAM_CLOCK_DATE_COMPLICATION_VIEW) View view,
- @Named(DREAM_CLOCK_DATE_COMPLICATION_LAYOUT_PARAMS)
- ComplicationLayoutParams layoutParams) {
- mView = view;
- mLayoutParams = layoutParams;
- }
-
- @Override
- public View getView() {
- return mView;
- }
-
- @Override
- public ComplicationLayoutParams getLayoutParams() {
- return mLayoutParams;
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamClockTimeComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamClockTimeComplication.java
index 7f67ecd..675a2f4 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamClockTimeComplication.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamClockTimeComplication.java
@@ -16,8 +16,8 @@
package com.android.systemui.dreams.complication;
-import static com.android.systemui.dreams.complication.dagger.DreamClockTimeComplicationModule.DREAM_CLOCK_TIME_COMPLICATION_LAYOUT_PARAMS;
import static com.android.systemui.dreams.complication.dagger.DreamClockTimeComplicationModule.DREAM_CLOCK_TIME_COMPLICATION_VIEW;
+import static com.android.systemui.dreams.complication.dagger.RegisteredComplicationsModule.DREAM_CLOCK_TIME_COMPLICATION_LAYOUT_PARAMS;
import android.content.Context;
import android.view.View;
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java
index 1a9d9b5..02c5de3 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java
@@ -19,8 +19,8 @@
import static com.android.systemui.controls.dagger.ControlsComponent.Visibility.AVAILABLE;
import static com.android.systemui.controls.dagger.ControlsComponent.Visibility.AVAILABLE_AFTER_UNLOCK;
import static com.android.systemui.controls.dagger.ControlsComponent.Visibility.UNAVAILABLE;
-import static com.android.systemui.dreams.complication.dagger.DreamHomeControlsComplicationComponent.DreamHomeControlsModule.DREAM_HOME_CONTROLS_CHIP_LAYOUT_PARAMS;
import static com.android.systemui.dreams.complication.dagger.DreamHomeControlsComplicationComponent.DreamHomeControlsModule.DREAM_HOME_CONTROLS_CHIP_VIEW;
+import static com.android.systemui.dreams.complication.dagger.RegisteredComplicationsModule.DREAM_HOME_CONTROLS_CHIP_LAYOUT_PARAMS;
import android.content.Context;
import android.content.Intent;
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/SmartSpaceComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/SmartSpaceComplication.java
similarity index 79%
rename from packages/SystemUI/src/com/android/systemui/dreams/SmartSpaceComplication.java
rename to packages/SystemUI/src/com/android/systemui/dreams/complication/SmartSpaceComplication.java
index be94e50..ac6edba 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/SmartSpaceComplication.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/SmartSpaceComplication.java
@@ -14,7 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.dreams;
+package com.android.systemui.dreams.complication;
+
+import static com.android.systemui.dreams.complication.dagger.RegisteredComplicationsModule.DREAM_SMARTSPACE_LAYOUT_PARAMS;
import android.content.Context;
import android.os.Parcelable;
@@ -23,21 +25,33 @@
import android.widget.FrameLayout;
import com.android.systemui.CoreStartable;
-import com.android.systemui.dreams.complication.Complication;
-import com.android.systemui.dreams.complication.ComplicationLayoutParams;
-import com.android.systemui.dreams.complication.ComplicationViewModel;
+import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.dreams.smartspace.DreamSmartspaceController;
import com.android.systemui.plugins.BcSmartspaceDataPlugin;
import java.util.List;
import javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Provider;
/**
* {@link SmartSpaceComplication} embodies the SmartSpace view found on the lockscreen as a
* {@link Complication}
*/
public class SmartSpaceComplication implements Complication {
+ private final Provider<SmartSpaceComplicationViewHolder> mViewHolderProvider;
+
+ @Inject
+ public SmartSpaceComplication(Provider<SmartSpaceComplicationViewHolder> viewHolderProvider) {
+ mViewHolderProvider = viewHolderProvider;
+ }
+
+ @Override
+ public ViewHolder createView(ComplicationViewModel model) {
+ return mViewHolderProvider.get();
+ }
+
/**
* {@link CoreStartable} responsbile for registering {@link SmartSpaceComplication} with
* SystemUI.
@@ -89,17 +103,20 @@
}
}
- private static class SmartSpaceComplicationViewHolder implements ViewHolder {
+ static class SmartSpaceComplicationViewHolder implements ViewHolder {
private View mView = null;
- private static final int SMARTSPACE_COMPLICATION_WEIGHT = 10;
private final DreamSmartspaceController mSmartSpaceController;
private final Context mContext;
+ private final ComplicationLayoutParams mLayoutParams;
+ @Inject
protected SmartSpaceComplicationViewHolder(
Context context,
- DreamSmartspaceController smartSpaceController) {
+ DreamSmartspaceController smartSpaceController,
+ @Named(DREAM_SMARTSPACE_LAYOUT_PARAMS) ComplicationLayoutParams layoutParams) {
mSmartSpaceController = smartSpaceController;
mContext = context;
+ mLayoutParams = layoutParams;
}
@Override
@@ -119,25 +136,7 @@
@Override
public ComplicationLayoutParams getLayoutParams() {
- return new ComplicationLayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT,
- ComplicationLayoutParams.POSITION_TOP | ComplicationLayoutParams.POSITION_START,
- ComplicationLayoutParams.DIRECTION_DOWN,
- SMARTSPACE_COMPLICATION_WEIGHT, true);
+ return mLayoutParams;
}
}
-
- private final DreamSmartspaceController mSmartSpaceController;
- private final Context mContext;
-
- @Inject
- public SmartSpaceComplication(Context context,
- DreamSmartspaceController smartSpaceController) {
- mContext = context;
- mSmartSpaceController = smartSpaceController;
- }
-
- @Override
- public ViewHolder createView(ComplicationViewModel model) {
- return new SmartSpaceComplicationViewHolder(mContext, mSmartSpaceController);
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamClockDateComplicationModule.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamClockDateComplicationModule.java
deleted file mode 100644
index 3ab26ce..0000000
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamClockDateComplicationModule.java
+++ /dev/null
@@ -1,71 +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.systemui.dreams.complication.dagger;
-
-
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-import com.android.internal.util.Preconditions;
-import com.android.systemui.R;
-import com.android.systemui.dreams.complication.ComplicationLayoutParams;
-import com.android.systemui.dreams.complication.DreamClockDateComplication;
-
-import javax.inject.Named;
-
-import dagger.Module;
-import dagger.Provides;
-
-/**
- * Module for providing {@link DreamClockDateComplication}.
- */
-@Module
-public interface DreamClockDateComplicationModule {
- String DREAM_CLOCK_DATE_COMPLICATION_VIEW = "clock_date_complication_view";
- String DREAM_CLOCK_DATE_COMPLICATION_LAYOUT_PARAMS =
- "clock_date_complication_layout_params";
- // Order weight of insert into parent container
- //TODO(b/217199227): move to a single location.
- int INSERT_ORDER_WEIGHT = 3;
-
- /**
- * Provides the complication view.
- */
- @Provides
- @Named(DREAM_CLOCK_DATE_COMPLICATION_VIEW)
- static View provideComplicationView(LayoutInflater layoutInflater) {
- return Preconditions.checkNotNull(
- layoutInflater.inflate(R.layout.dream_overlay_complication_clock_date,
- null, false),
- "R.layout.dream_overlay_complication_clock_date did not properly inflated");
- }
-
- /**
- * Provides the layout parameters for the complication view.
- */
- @Provides
- @Named(DREAM_CLOCK_DATE_COMPLICATION_LAYOUT_PARAMS)
- static ComplicationLayoutParams provideLayoutParams() {
- return new ComplicationLayoutParams(0,
- ViewGroup.LayoutParams.WRAP_CONTENT,
- ComplicationLayoutParams.POSITION_BOTTOM
- | ComplicationLayoutParams.POSITION_START,
- ComplicationLayoutParams.DIRECTION_END,
- INSERT_ORDER_WEIGHT, /* snapToGuide= */ true);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamClockTimeComplicationModule.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamClockTimeComplicationModule.java
index 3ad7d3d..5250d44 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamClockTimeComplicationModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamClockTimeComplicationModule.java
@@ -19,12 +19,10 @@
import android.view.LayoutInflater;
import android.view.View;
-import android.view.ViewGroup;
import android.widget.TextClock;
import com.android.internal.util.Preconditions;
import com.android.systemui.R;
-import com.android.systemui.dreams.complication.ComplicationLayoutParams;
import com.android.systemui.dreams.complication.DreamClockTimeComplication;
import javax.inject.Named;
@@ -38,11 +36,6 @@
@Module
public interface DreamClockTimeComplicationModule {
String DREAM_CLOCK_TIME_COMPLICATION_VIEW = "clock_time_complication_view";
- String DREAM_CLOCK_TIME_COMPLICATION_LAYOUT_PARAMS =
- "clock_time_complication_layout_params";
- // Order weight of insert into parent container
- //TODO(b/217199227): move to a single location.
- int INSERT_ORDER_WEIGHT = 0;
String TAG_WEIGHT = "'wght' ";
int WEIGHT = 200;
@@ -59,18 +52,4 @@
view.setFontVariationSettings(TAG_WEIGHT + WEIGHT);
return view;
}
-
- /**
- * Provides the layout parameters for the complication view.
- */
- @Provides
- @Named(DREAM_CLOCK_TIME_COMPLICATION_LAYOUT_PARAMS)
- static ComplicationLayoutParams provideLayoutParams() {
- return new ComplicationLayoutParams(0,
- ViewGroup.LayoutParams.WRAP_CONTENT,
- ComplicationLayoutParams.POSITION_BOTTOM
- | ComplicationLayoutParams.POSITION_START,
- ComplicationLayoutParams.DIRECTION_UP,
- INSERT_ORDER_WEIGHT);
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamHomeControlsComplicationComponent.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamHomeControlsComplicationComponent.java
index 033ce39..cf05d2d 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamHomeControlsComplicationComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamHomeControlsComplicationComponent.java
@@ -18,13 +18,10 @@
import static java.lang.annotation.RetentionPolicy.RUNTIME;
-import android.content.res.Resources;
import android.view.LayoutInflater;
import android.widget.ImageView;
import com.android.systemui.R;
-import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.dreams.complication.ComplicationLayoutParams;
import com.android.systemui.dreams.complication.DreamHomeControlsComplication;
import java.lang.annotation.Documented;
@@ -70,12 +67,6 @@
@Module
interface DreamHomeControlsModule {
String DREAM_HOME_CONTROLS_CHIP_VIEW = "dream_home_controls_chip_view";
- String DREAM_HOME_CONTROLS_CHIP_LAYOUT_PARAMS = "home_controls_chip_layout_params";
-
- // TODO(b/217199227): move to a single location.
- // Weight of order in the parent container. The home controls complication should have low
- // weight and be placed at the end.
- int INSERT_ORDER_WEIGHT = 0;
/**
* Provides the dream home controls chip view.
@@ -87,22 +78,5 @@
return (ImageView) layoutInflater.inflate(R.layout.dream_overlay_home_controls_chip,
null, false);
}
-
- /**
- * Provides the layout parameters for the dream home controls complication.
- */
- @Provides
- @DreamHomeControlsComplicationScope
- @Named(DREAM_HOME_CONTROLS_CHIP_LAYOUT_PARAMS)
- static ComplicationLayoutParams provideLayoutParams(@Main Resources res) {
- return new ComplicationLayoutParams(
- res.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_width),
- res.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_height),
- ComplicationLayoutParams.POSITION_BOTTOM
- | ComplicationLayoutParams.POSITION_START,
- ComplicationLayoutParams.DIRECTION_END,
- INSERT_ORDER_WEIGHT);
- }
}
-
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java
index 4a515f0..eb07238 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java
@@ -16,20 +16,79 @@
package com.android.systemui.dreams.complication.dagger;
+import android.content.res.Resources;
+import android.view.ViewGroup;
+
+import com.android.systemui.R;
import com.android.systemui.dagger.SystemUIBinder;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dreams.complication.ComplicationLayoutParams;
+
+import javax.inject.Named;
import dagger.Module;
+import dagger.Provides;
/**
* Module for all components with corresponding dream layer complications registered in
* {@link SystemUIBinder}.
*/
@Module(includes = {
- DreamClockDateComplicationModule.class,
DreamClockTimeComplicationModule.class,
},
subcomponents = {
DreamHomeControlsComplicationComponent.class,
})
public interface RegisteredComplicationsModule {
+ String DREAM_CLOCK_TIME_COMPLICATION_LAYOUT_PARAMS = "time_complication_layout_params";
+ String DREAM_SMARTSPACE_LAYOUT_PARAMS = "smartspace_layout_params";
+ String DREAM_HOME_CONTROLS_CHIP_LAYOUT_PARAMS = "home_controls_chip_layout_params";
+
+ int DREAM_CLOCK_TIME_COMPLICATION_WEIGHT = 1;
+ int DREAM_SMARTSPACE_COMPLICATION_WEIGHT = 0;
+ int DREAM_HOME_CONTROLS_CHIP_COMPLICATION_WEIGHT = 1;
+
+ /**
+ * Provides layout parameters for the clock time complication.
+ */
+ @Provides
+ @Named(DREAM_CLOCK_TIME_COMPLICATION_LAYOUT_PARAMS)
+ static ComplicationLayoutParams provideClockTimeLayoutParams() {
+ return new ComplicationLayoutParams(0,
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ ComplicationLayoutParams.POSITION_TOP
+ | ComplicationLayoutParams.POSITION_START,
+ ComplicationLayoutParams.DIRECTION_DOWN,
+ DREAM_CLOCK_TIME_COMPLICATION_WEIGHT);
+ }
+
+ /**
+ * Provides layout parameters for the home controls complication.
+ */
+ @Provides
+ @Named(DREAM_HOME_CONTROLS_CHIP_LAYOUT_PARAMS)
+ static ComplicationLayoutParams provideHomeControlsChipLayoutParams(@Main Resources res) {
+ return new ComplicationLayoutParams(
+ res.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_width),
+ res.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_height),
+ ComplicationLayoutParams.POSITION_BOTTOM
+ | ComplicationLayoutParams.POSITION_START,
+ ComplicationLayoutParams.DIRECTION_END,
+ DREAM_HOME_CONTROLS_CHIP_COMPLICATION_WEIGHT);
+ }
+
+ /**
+ * Provides layout parameters for the smartspace complication.
+ */
+ @Provides
+ @Named(DREAM_SMARTSPACE_LAYOUT_PARAMS)
+ static ComplicationLayoutParams provideSmartspaceLayoutParams() {
+ return new ComplicationLayoutParams(0,
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ ComplicationLayoutParams.POSITION_TOP
+ | ComplicationLayoutParams.POSITION_START,
+ ComplicationLayoutParams.DIRECTION_DOWN,
+ DREAM_SMARTSPACE_COMPLICATION_WEIGHT,
+ true /*snapToGuide*/);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
index b0f025d..20c8971 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
@@ -204,6 +204,10 @@
new DeviceConfigBooleanFlag(1102, "record_task_content",
NAMESPACE_WINDOW_MANAGER, false, true);
+ @Keep
+ public static final SysPropBooleanFlag HIDE_NAVBAR_WINDOW =
+ new SysPropBooleanFlag(1103, "persist.wm.debug.hide_navbar_window", false);
+
// 1200 - predictive back
@Keep
public static final SysPropBooleanFlag WM_ENABLE_PREDICTIVE_BACK = new SysPropBooleanFlag(
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
index ab30db2..ca65d12 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
@@ -947,7 +947,7 @@
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
- mScreenshotHelper.takeScreenshot(TAKE_SCREENSHOT_FULLSCREEN, true, true,
+ mScreenshotHelper.takeScreenshot(TAKE_SCREENSHOT_FULLSCREEN,
SCREENSHOT_GLOBAL_ACTIONS, mHandler, null);
mMetricsLogger.action(MetricsEvent.ACTION_SCREENSHOT_POWER_MENU);
mUiEventLogger.log(GlobalActionsEvent.GA_SCREENSHOT_PRESS);
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 7ddce62..b4f40e2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -627,13 +627,13 @@
case TelephonyManager.SIM_STATE_PUK_REQUIRED:
synchronized (KeyguardViewMediator.this) {
mSimWasLocked.append(slotId, true);
+ mPendingPinLock = true;
if (!mShowing) {
if (DEBUG_SIM_STATES) Log.d(TAG,
"INTENT_VALUE_ICC_LOCKED and keygaurd isn't "
+ "showing; need to show keyguard so user can enter sim pin");
doKeyguardLocked(null);
} else {
- mPendingPinLock = true;
resetStateLocked();
}
}
@@ -2985,6 +2985,7 @@
pw.print(" mPendingReset: "); pw.println(mPendingReset);
pw.print(" mPendingLock: "); pw.println(mPendingLock);
pw.print(" wakeAndUnlocking: "); pw.println(mWakeAndUnlocking);
+ pw.print(" mPendingPinLock: "); pw.println(mPendingPinLock);
}
/**
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 165af13..4ff008f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -42,6 +42,8 @@
import com.android.systemui.keyguard.DismissCallbackRegistry;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.KeyguardViewMediator;
+import com.android.systemui.keyguard.data.repository.KeyguardRepositoryModule;
+import com.android.systemui.keyguard.domain.usecase.KeyguardUseCaseModule;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.statusbar.NotificationShadeDepthController;
import com.android.systemui.statusbar.NotificationShadeWindowController;
@@ -66,7 +68,11 @@
KeyguardStatusBarViewComponent.class,
KeyguardStatusViewComponent.class,
KeyguardUserSwitcherComponent.class},
- includes = {FalsingModule.class})
+ includes = {
+ FalsingModule.class,
+ KeyguardRepositoryModule.class,
+ KeyguardUseCaseModule.class,
+ })
public class KeyguardModule {
/**
* Provides our instance of KeyguardViewMediator which is considered optional.
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
new file mode 100644
index 0000000..3202ecb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.data.quickaffordance
+
+import android.content.Context
+import android.content.Intent
+import androidx.annotation.DrawableRes
+import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.containeddrawable.ContainedDrawable
+import com.android.systemui.controls.ControlsServiceInfo
+import com.android.systemui.controls.controller.StructureInfo
+import com.android.systemui.controls.dagger.ControlsComponent
+import com.android.systemui.controls.management.ControlsListingController
+import com.android.systemui.controls.ui.ControlsActivity
+import com.android.systemui.controls.ui.ControlsUiController
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.util.kotlin.getOrNull
+import javax.inject.Inject
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
+
+/** Home controls quick affordance data source. */
+@SysUISingleton
+class HomeControlsKeyguardQuickAffordanceConfig
+@Inject
+constructor(
+ @Application context: Context,
+ private val component: ControlsComponent,
+) : KeyguardQuickAffordanceConfig {
+
+ private val appContext = context.applicationContext
+
+ override val state: Flow<KeyguardQuickAffordanceConfig.State> =
+ stateInternal(component.getControlsListingController().getOrNull())
+
+ override fun onQuickAffordanceClicked(
+ animationController: ActivityLaunchAnimator.Controller?,
+ ): KeyguardQuickAffordanceConfig.OnClickedResult {
+ return KeyguardQuickAffordanceConfig.OnClickedResult.StartActivity(
+ intent =
+ Intent(appContext, ControlsActivity::class.java)
+ .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
+ .putExtra(
+ ControlsUiController.EXTRA_ANIMATE,
+ true,
+ ),
+ canShowWhileLocked = component.canShowWhileLockedSetting.value,
+ )
+ }
+
+ private fun stateInternal(
+ listingController: ControlsListingController?,
+ ): Flow<KeyguardQuickAffordanceConfig.State> {
+ if (listingController == null) {
+ return flowOf(KeyguardQuickAffordanceConfig.State.Hidden)
+ }
+
+ return conflatedCallbackFlow {
+ val callback =
+ object : ControlsListingController.ControlsListingCallback {
+ override fun onServicesUpdated(serviceInfos: List<ControlsServiceInfo>) {
+ val favorites: List<StructureInfo>? =
+ component.getControlsController().getOrNull()?.getFavorites()
+
+ trySendWithFailureLogging(
+ state(
+ isFeatureEnabled = component.isEnabled(),
+ hasFavorites = favorites?.isNotEmpty() == true,
+ hasServiceInfos = serviceInfos.isNotEmpty(),
+ iconResourceId = component.getTileImageId(),
+ ),
+ TAG,
+ )
+ }
+ }
+
+ listingController.addCallback(callback)
+
+ awaitClose { listingController.removeCallback(callback) }
+ }
+ }
+
+ private fun state(
+ isFeatureEnabled: Boolean,
+ hasFavorites: Boolean,
+ hasServiceInfos: Boolean,
+ @DrawableRes iconResourceId: Int?,
+ ): KeyguardQuickAffordanceConfig.State {
+ return if (isFeatureEnabled && hasFavorites && hasServiceInfos && iconResourceId != null) {
+ KeyguardQuickAffordanceConfig.State.Visible(
+ icon = ContainedDrawable.WithResource(iconResourceId),
+ contentDescriptionResourceId = component.getTileTitleId(),
+ )
+ } else {
+ KeyguardQuickAffordanceConfig.State.Hidden
+ }
+ }
+
+ companion object {
+ private const val TAG = "HomeControlsKeyguardQuickAffordanceConfig"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt
new file mode 100644
index 0000000..67a776e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.data.quickaffordance
+
+import android.content.Intent
+import androidx.annotation.StringRes
+import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.containeddrawable.ContainedDrawable
+import kotlinx.coroutines.flow.Flow
+
+/** Defines interface that can act as data source for a single quick affordance model. */
+interface KeyguardQuickAffordanceConfig {
+
+ val state: Flow<State>
+
+ fun onQuickAffordanceClicked(
+ animationController: ActivityLaunchAnimator.Controller?
+ ): OnClickedResult
+
+ /**
+ * Encapsulates the state of a "quick affordance" in the keyguard bottom area (for example, a
+ * button on the lock-screen).
+ */
+ sealed class State {
+
+ /** No affordance should show up. */
+ object Hidden : State()
+
+ /** An affordance is visible. */
+ data class Visible(
+ /** An icon for the affordance. */
+ val icon: ContainedDrawable,
+ /**
+ * Resource ID for a string to use for the accessibility content description text of the
+ * affordance.
+ */
+ @StringRes val contentDescriptionResourceId: Int,
+ ) : State()
+ }
+
+ sealed class OnClickedResult {
+ /**
+ * Returning this as a result from the [onQuickAffordanceClicked] method means that the
+ * implementation has taken care of the click, the system will do nothing.
+ */
+ object Handled : OnClickedResult()
+
+ /**
+ * Returning this as a result from the [onQuickAffordanceClicked] method means that the
+ * implementation has _not_ taken care of the click and the system should start an activity
+ * using the given [Intent].
+ */
+ data class StartActivity(
+ val intent: Intent,
+ val canShowWhileLocked: Boolean,
+ ) : OnClickedResult()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt
new file mode 100644
index 0000000..758e411
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.data.quickaffordance
+
+import android.content.Context
+import com.android.systemui.R
+import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.containeddrawable.ContainedDrawable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.qrcodescanner.controller.QRCodeScannerController
+import javax.inject.Inject
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+
+/** QR code scanner quick affordance data source. */
+@SysUISingleton
+class QrCodeScannerKeyguardQuickAffordanceConfig
+@Inject
+constructor(
+ @Application context: Context,
+ private val controller: QRCodeScannerController,
+) : KeyguardQuickAffordanceConfig {
+
+ private val appContext = context.applicationContext
+
+ override val state: Flow<KeyguardQuickAffordanceConfig.State> = conflatedCallbackFlow {
+ val callback =
+ object : QRCodeScannerController.Callback {
+ override fun onQRCodeScannerActivityChanged() {
+ trySendWithFailureLogging(state(), TAG)
+ }
+ override fun onQRCodeScannerPreferenceChanged() {
+ trySendWithFailureLogging(state(), TAG)
+ }
+ }
+
+ controller.addCallback(callback)
+ controller.registerQRCodeScannerChangeObservers(
+ QRCodeScannerController.DEFAULT_QR_CODE_SCANNER_CHANGE,
+ QRCodeScannerController.QR_CODE_SCANNER_PREFERENCE_CHANGE
+ )
+ // Registering does not push an initial update.
+ trySendWithFailureLogging(state(), "initial state", TAG)
+
+ awaitClose {
+ controller.unregisterQRCodeScannerChangeObservers(
+ QRCodeScannerController.DEFAULT_QR_CODE_SCANNER_CHANGE,
+ QRCodeScannerController.QR_CODE_SCANNER_PREFERENCE_CHANGE
+ )
+ controller.removeCallback(callback)
+ }
+ }
+
+ override fun onQuickAffordanceClicked(
+ animationController: ActivityLaunchAnimator.Controller?,
+ ): KeyguardQuickAffordanceConfig.OnClickedResult {
+ return KeyguardQuickAffordanceConfig.OnClickedResult.StartActivity(
+ intent = controller.intent,
+ canShowWhileLocked = true,
+ )
+ }
+
+ private fun state(): KeyguardQuickAffordanceConfig.State {
+ return if (controller.isEnabledForLockScreenButton) {
+ KeyguardQuickAffordanceConfig.State.Visible(
+ icon = ContainedDrawable.WithResource(R.drawable.ic_qr_code_scanner),
+ contentDescriptionResourceId = R.string.accessibility_qr_code_scanner_button,
+ )
+ } else {
+ KeyguardQuickAffordanceConfig.State.Hidden
+ }
+ }
+
+ companion object {
+ private const val TAG = "QrCodeScannerKeyguardQuickAffordanceConfig"
+ }
+}
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
new file mode 100644
index 0000000..c686e27
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.data.quickaffordance
+
+import android.graphics.drawable.Drawable
+import android.service.quickaccesswallet.GetWalletCardsError
+import android.service.quickaccesswallet.GetWalletCardsResponse
+import android.service.quickaccesswallet.QuickAccessWalletClient
+import android.util.Log
+import com.android.systemui.R
+import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.containeddrawable.ContainedDrawable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.statusbar.policy.KeyguardStateControllerExt.isKeyguardShowing
+import com.android.systemui.wallet.controller.QuickAccessWalletController
+import javax.inject.Inject
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+
+/** Quick access wallet quick affordance data source. */
+@SysUISingleton
+class QuickAccessWalletKeyguardQuickAffordanceConfig
+@Inject
+constructor(
+ private val keyguardStateController: KeyguardStateController,
+ private val walletController: QuickAccessWalletController,
+ private val activityStarter: ActivityStarter,
+) : KeyguardQuickAffordanceConfig {
+
+ override val state: Flow<KeyguardQuickAffordanceConfig.State> =
+ keyguardStateController
+ .isKeyguardShowing(TAG)
+ .flatMapLatest { isKeyguardShowing ->
+ stateInternal(isKeyguardShowing)
+ }
+
+ override fun onQuickAffordanceClicked(
+ animationController: ActivityLaunchAnimator.Controller?,
+ ): KeyguardQuickAffordanceConfig.OnClickedResult {
+ walletController.startQuickAccessUiIntent(
+ activityStarter,
+ animationController,
+ /* hasCard= */ true,
+ )
+ return KeyguardQuickAffordanceConfig.OnClickedResult.Handled
+ }
+
+ private fun stateInternal(
+ isKeyguardShowing: Boolean
+ ): Flow<KeyguardQuickAffordanceConfig.State> {
+ if (!isKeyguardShowing) {
+ return flowOf(KeyguardQuickAffordanceConfig.State.Hidden)
+ }
+
+ return conflatedCallbackFlow {
+ val callback =
+ object : QuickAccessWalletClient.OnWalletCardsRetrievedCallback {
+ override fun onWalletCardsRetrieved(response: GetWalletCardsResponse?) {
+ trySendWithFailureLogging(
+ state(
+ isFeatureEnabled = walletController.isWalletEnabled,
+ hasCard = response?.walletCards?.isNotEmpty() == true,
+ tileIcon = walletController.walletClient.tileIcon,
+ ),
+ TAG,
+ )
+ }
+
+ override fun onWalletCardRetrievalError(error: GetWalletCardsError?) {
+ Log.e(TAG, "Wallet card retrieval error, message: \"${error?.message}\"")
+ trySendWithFailureLogging(
+ KeyguardQuickAffordanceConfig.State.Hidden,
+ TAG,
+ )
+ }
+ }
+
+ walletController.setupWalletChangeObservers(
+ callback,
+ QuickAccessWalletController.WalletChangeEvent.WALLET_PREFERENCE_CHANGE,
+ QuickAccessWalletController.WalletChangeEvent.DEFAULT_PAYMENT_APP_CHANGE
+ )
+ walletController.updateWalletPreference()
+ walletController.queryWalletCards(callback)
+
+ awaitClose {
+ walletController.unregisterWalletChangeObservers(
+ QuickAccessWalletController.WalletChangeEvent.WALLET_PREFERENCE_CHANGE,
+ QuickAccessWalletController.WalletChangeEvent.DEFAULT_PAYMENT_APP_CHANGE
+ )
+ }
+ }
+ }
+
+ private fun state(
+ isFeatureEnabled: Boolean,
+ hasCard: Boolean,
+ tileIcon: Drawable?,
+ ): KeyguardQuickAffordanceConfig.State {
+ return if (isFeatureEnabled && hasCard && tileIcon != null) {
+ KeyguardQuickAffordanceConfig.State.Visible(
+ icon = ContainedDrawable.WithDrawable(tileIcon),
+ contentDescriptionResourceId = R.string.accessibility_wallet_button,
+ )
+ } else {
+ KeyguardQuickAffordanceConfig.State.Hidden
+ }
+ }
+
+ companion object {
+ private const val TAG = "QuickAccessWalletKeyguardQuickAffordanceConfig"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceConfigs.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceConfigs.kt
new file mode 100644
index 0000000..7164215
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceConfigs.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.data.config
+
+import com.android.systemui.keyguard.data.quickaffordance.HomeControlsKeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.QrCodeScannerKeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.QuickAccessWalletKeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePosition
+import javax.inject.Inject
+import kotlin.reflect.KClass
+
+/** Injectable provider of the positioning of the known quick affordance configs. */
+interface KeyguardQuickAffordanceConfigs {
+ fun getAll(position: KeyguardQuickAffordancePosition): List<KeyguardQuickAffordanceConfig>
+ fun get(configClass: KClass<out KeyguardQuickAffordanceConfig>): KeyguardQuickAffordanceConfig
+}
+
+class KeyguardQuickAffordanceConfigsImpl
+@Inject
+constructor(
+ homeControls: HomeControlsKeyguardQuickAffordanceConfig,
+ quickAccessWallet: QuickAccessWalletKeyguardQuickAffordanceConfig,
+ qrCodeScanner: QrCodeScannerKeyguardQuickAffordanceConfig,
+) : KeyguardQuickAffordanceConfigs {
+ private val configsByPosition =
+ mapOf(
+ KeyguardQuickAffordancePosition.BOTTOM_START to
+ listOf(
+ homeControls,
+ ),
+ KeyguardQuickAffordancePosition.BOTTOM_END to
+ listOf(
+ quickAccessWallet,
+ qrCodeScanner,
+ ),
+ )
+ private val configByClass =
+ configsByPosition.values.flatten().associateBy { config -> config::class }
+
+ override fun getAll(
+ position: KeyguardQuickAffordancePosition,
+ ): List<KeyguardQuickAffordanceConfig> {
+ return configsByPosition.getValue(position)
+ }
+
+ override fun get(
+ configClass: KClass<out KeyguardQuickAffordanceConfig>
+ ): KeyguardQuickAffordanceConfig {
+ return configByClass.getValue(configClass)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt
new file mode 100644
index 0000000..43c4fa0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.data.repository
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.data.config.KeyguardQuickAffordanceConfigs
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.State
+import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordanceModel
+import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePosition
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+
+/** Defines interface for classes that encapsulate quick affordance state for the keyguard. */
+interface KeyguardQuickAffordanceRepository {
+ fun affordance(position: KeyguardQuickAffordancePosition): Flow<KeyguardQuickAffordanceModel>
+}
+
+/** Real implementation of [KeyguardQuickAffordanceRepository] */
+@SysUISingleton
+class KeyguardQuickAffordanceRepositoryImpl
+@Inject
+constructor(
+ private val configs: KeyguardQuickAffordanceConfigs,
+) : KeyguardQuickAffordanceRepository {
+
+ /** Returns an observable for the quick affordance model in the given position. */
+ override fun affordance(
+ position: KeyguardQuickAffordancePosition
+ ): Flow<KeyguardQuickAffordanceModel> {
+ val configs = configs.getAll(position)
+ return combine(configs.map { config -> config.state }) { states ->
+ val index = states.indexOfFirst { state -> state is State.Visible }
+ val visibleState =
+ if (index != -1) {
+ states[index] as State.Visible
+ } else {
+ null
+ }
+ if (visibleState != null) {
+ KeyguardQuickAffordanceModel.Visible(
+ configKey = configs[index]::class,
+ icon = visibleState.icon,
+ contentDescriptionResourceId = visibleState.contentDescriptionResourceId,
+ )
+ } else {
+ KeyguardQuickAffordanceModel.Hidden
+ }
+ }
+ }
+}
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
new file mode 100644
index 0000000..be91e51
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.data.repository
+
+import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.common.data.model.Position
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import javax.inject.Inject
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+/** Defines interface for classes that encapsulate application state for the keyguard. */
+interface KeyguardRepository {
+ /**
+ * Observable for whether the bottom area UI should animate the transition out of doze state.
+ *
+ * To learn more about doze state, please see [isDozing].
+ */
+ val animateBottomAreaDozingTransitions: StateFlow<Boolean>
+
+ /**
+ * Observable for the current amount of alpha that should be used for rendering the bottom area.
+ * UI.
+ */
+ val bottomAreaAlpha: StateFlow<Float>
+
+ /**
+ * Observable of the relative offset of the lock-screen clock from its natural position on the
+ * screen.
+ */
+ val clockPosition: StateFlow<Position>
+
+ /**
+ * Observable for whether we are in doze state.
+ *
+ * Doze state is the same as "Always on Display" or "AOD". It is the state that the device can
+ * enter to conserve battery when the device is locked and inactive.
+ *
+ * Note that it is possible for the system to be transitioning into doze while this flow still
+ * returns `false`. In order to account for that, observers should also use the [dozeAmount]
+ * flow to check if it's greater than `0`
+ */
+ val isDozing: Flow<Boolean>
+
+ /**
+ * Observable for the amount of doze we are currently in.
+ *
+ * While in doze state, this amount can change - driving a cycle of animations designed to avoid
+ * pixel burn-in, etc.
+ *
+ * Also note that the value here may be greater than `0` while [isDozing] is still `false`, this
+ * happens during an animation/transition into doze mode. An observer would be wise to account
+ * for both flows if needed.
+ */
+ val dozeAmount: Flow<Float>
+
+ /** Sets whether the bottom area UI should animate the transition out of doze state. */
+ fun setAnimateDozingTransitions(animate: Boolean)
+
+ /** Sets the current amount of alpha that should be used for rendering the bottom area. */
+ fun setBottomAreaAlpha(alpha: Float)
+
+ /**
+ * Sets the relative offset of the lock-screen clock from its natural position on the screen.
+ */
+ fun setClockPosition(x: Int, y: Int)
+}
+
+/** Encapsulates application state for the keyguard. */
+@SysUISingleton
+class KeyguardRepositoryImpl
+@Inject
+constructor(
+ statusBarStateController: StatusBarStateController,
+) : KeyguardRepository {
+ private val _animateBottomAreaDozingTransitions = MutableStateFlow(false)
+ override val animateBottomAreaDozingTransitions =
+ _animateBottomAreaDozingTransitions.asStateFlow()
+
+ private val _bottomAreaAlpha = MutableStateFlow(1f)
+ override val bottomAreaAlpha = _bottomAreaAlpha.asStateFlow()
+
+ private val _clockPosition = MutableStateFlow(Position(0, 0))
+ override val clockPosition = _clockPosition.asStateFlow()
+
+ override val isDozing: Flow<Boolean> = conflatedCallbackFlow {
+ val callback =
+ object : StatusBarStateController.StateListener {
+ override fun onDozingChanged(isDozing: Boolean) {
+ trySendWithFailureLogging(isDozing, TAG, "updated isDozing")
+ }
+ }
+
+ statusBarStateController.addCallback(callback)
+ trySendWithFailureLogging(statusBarStateController.isDozing, TAG, "initial isDozing")
+
+ awaitClose { statusBarStateController.removeCallback(callback) }
+ }
+ override val dozeAmount: Flow<Float> = conflatedCallbackFlow {
+ val callback =
+ object : StatusBarStateController.StateListener {
+ override fun onDozeAmountChanged(linear: Float, eased: Float) {
+ trySendWithFailureLogging(eased, TAG, "updated dozeAmount")
+ }
+ }
+
+ statusBarStateController.addCallback(callback)
+ trySendWithFailureLogging(statusBarStateController.dozeAmount, TAG, "initial dozeAmount")
+
+ awaitClose { statusBarStateController.removeCallback(callback) }
+ }
+
+ override fun setAnimateDozingTransitions(animate: Boolean) {
+ _animateBottomAreaDozingTransitions.value = animate
+ }
+
+ override fun setBottomAreaAlpha(alpha: Float) {
+ _bottomAreaAlpha.value = alpha
+ }
+
+ override fun setClockPosition(x: Int, y: Int) {
+ _clockPosition.value = Position(x, y)
+ }
+
+ companion object {
+ private const val TAG = "KeyguardRepositoryImpl"
+ }
+}
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
new file mode 100644
index 0000000..d2ab3e9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.data.repository
+
+import com.android.systemui.keyguard.data.config.KeyguardQuickAffordanceConfigs
+import com.android.systemui.keyguard.data.config.KeyguardQuickAffordanceConfigsImpl
+import dagger.Binds
+import dagger.Module
+
+@Module
+interface KeyguardRepositoryModule {
+ @Binds fun keyguardRepository(impl: KeyguardRepositoryImpl): KeyguardRepository
+
+ @Binds
+ fun keyguardQuickAffordanceRepository(
+ impl: KeyguardQuickAffordanceRepositoryImpl
+ ): KeyguardQuickAffordanceRepository
+
+ @Binds fun keyguardQuickAffordanceConfigs(
+ impl: KeyguardQuickAffordanceConfigsImpl
+ ): KeyguardQuickAffordanceConfigs
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/KeyguardUseCaseModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/KeyguardUseCaseModule.kt
new file mode 100644
index 0000000..c44c2c9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/KeyguardUseCaseModule.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.usecase
+
+import dagger.Binds
+import dagger.Module
+
+@Module
+interface KeyguardUseCaseModule {
+
+ @Binds
+ fun launchQuickAffordance(
+ impl: LaunchKeyguardQuickAffordanceUseCaseImpl
+ ): LaunchKeyguardQuickAffordanceUseCase
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/LaunchKeyguardQuickAffordanceUseCase.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/LaunchKeyguardQuickAffordanceUseCase.kt
new file mode 100644
index 0000000..3d60399
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/LaunchKeyguardQuickAffordanceUseCase.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.usecase
+
+import android.content.Intent
+import com.android.internal.widget.LockPatternUtils
+import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.StrongAuthFlags
+import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import javax.inject.Inject
+
+/** Defines interface for classes that can launch a quick affordance. */
+interface LaunchKeyguardQuickAffordanceUseCase {
+ operator fun invoke(
+ intent: Intent,
+ canShowWhileLocked: Boolean,
+ animationController: ActivityLaunchAnimator.Controller?,
+ )
+}
+
+/** Real implementation of [LaunchKeyguardQuickAffordanceUseCase] */
+class LaunchKeyguardQuickAffordanceUseCaseImpl
+@Inject
+constructor(
+ private val lockPatternUtils: LockPatternUtils,
+ private val keyguardStateController: KeyguardStateController,
+ private val userTracker: UserTracker,
+ private val activityStarter: ActivityStarter,
+) : LaunchKeyguardQuickAffordanceUseCase {
+ override operator fun invoke(
+ intent: Intent,
+ canShowWhileLocked: Boolean,
+ animationController: ActivityLaunchAnimator.Controller?,
+ ) {
+ @StrongAuthFlags
+ val strongAuthFlags =
+ lockPatternUtils.getStrongAuthForUser(userTracker.userHandle.identifier)
+ val needsToUnlockFirst =
+ when {
+ strongAuthFlags ==
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT -> true
+ !canShowWhileLocked && !keyguardStateController.isUnlocked -> true
+ else -> false
+ }
+ if (needsToUnlockFirst) {
+ activityStarter.postStartActivityDismissingKeyguard(
+ intent,
+ 0 /* delay */,
+ animationController
+ )
+ } else {
+ activityStarter.startActivity(
+ intent,
+ true /* dismissShade */,
+ animationController,
+ true /* showOverLockscreenWhenLocked */,
+ )
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveAnimateBottomAreaTransitionsUseCase.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveAnimateBottomAreaTransitionsUseCase.kt
new file mode 100644
index 0000000..ca37727
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveAnimateBottomAreaTransitionsUseCase.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.usecase
+
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+
+/** Use-case for observing whether doze state transitions should animate the bottom area */
+class ObserveAnimateBottomAreaTransitionsUseCase
+@Inject
+constructor(
+ private val repository: KeyguardRepository,
+) {
+ operator fun invoke(): Flow<Boolean> {
+ return repository.animateBottomAreaDozingTransitions
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveBottomAreaAlphaUseCase.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveBottomAreaAlphaUseCase.kt
new file mode 100644
index 0000000..151b704
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveBottomAreaAlphaUseCase.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.usecase
+
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+
+/** Use-case for observing the alpha of the bottom area */
+class ObserveBottomAreaAlphaUseCase
+@Inject
+constructor(
+ private val repository: KeyguardRepository,
+) {
+ operator fun invoke(): Flow<Float> {
+ return repository.bottomAreaAlpha
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveClockPositionUseCase.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveClockPositionUseCase.kt
new file mode 100644
index 0000000..02c5737
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveClockPositionUseCase.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.usecase
+
+import com.android.systemui.common.domain.model.Position
+import com.android.systemui.common.domain.model.Position.Companion.toDomainLayer
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+
+/** Use-case for observing the position of the clock. */
+class ObserveClockPositionUseCase
+@Inject
+constructor(
+ private val repository: KeyguardRepository,
+) {
+ operator fun invoke(): Flow<Position> {
+ return repository.clockPosition.map { it.toDomainLayer() }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveDozeAmountUseCase.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveDozeAmountUseCase.kt
new file mode 100644
index 0000000..56d6182
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveDozeAmountUseCase.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.usecase
+
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+
+/** Use-case for observing the amount of doze the system is in. */
+class ObserveDozeAmountUseCase
+@Inject
+constructor(
+ private val repository: KeyguardRepository,
+) {
+ operator fun invoke(): Flow<Float> {
+ return repository.dozeAmount
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveIsDozingUseCase.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveIsDozingUseCase.kt
new file mode 100644
index 0000000..1d241d9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveIsDozingUseCase.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.usecase
+
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+
+/** Use-case for observing whether we are dozing. */
+class ObserveIsDozingUseCase
+@Inject
+constructor(
+ private val repository: KeyguardRepository,
+) {
+ operator fun invoke(): Flow<Boolean> {
+ return repository.isDozing
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveKeyguardQuickAffordanceUseCase.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveKeyguardQuickAffordanceUseCase.kt
new file mode 100644
index 0000000..aac3d75
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveKeyguardQuickAffordanceUseCase.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.usecase
+
+import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
+import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordanceModel
+import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePosition
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+
+/** Use-case for observing the model of a quick affordance in the keyguard. */
+class ObserveKeyguardQuickAffordanceUseCase
+@Inject
+constructor(
+ private val repository: KeyguardQuickAffordanceRepository,
+ private val isDozingUseCase: ObserveIsDozingUseCase,
+ private val dozeAmountUseCase: ObserveDozeAmountUseCase,
+) {
+ operator fun invoke(
+ position: KeyguardQuickAffordancePosition
+ ): Flow<KeyguardQuickAffordanceModel> {
+ return combine(
+ repository.affordance(position),
+ isDozingUseCase(),
+ dozeAmountUseCase(),
+ ) { affordance, isDozing, dozeAmount ->
+ if (!isDozing && dozeAmount == 0f) {
+ affordance
+ } else {
+ KeyguardQuickAffordanceModel.Hidden
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/OnKeyguardQuickAffordanceClickedUseCase.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/OnKeyguardQuickAffordanceClickedUseCase.kt
new file mode 100644
index 0000000..f8db90f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/OnKeyguardQuickAffordanceClickedUseCase.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.usecase
+
+import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.keyguard.data.config.KeyguardQuickAffordanceConfigs
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.OnClickedResult
+import javax.inject.Inject
+import kotlin.reflect.KClass
+
+/** Use-case for handling a click on a keyguard quick affordance (e.g. bottom button). */
+class OnKeyguardQuickAffordanceClickedUseCase
+@Inject
+constructor(
+ private val configs: KeyguardQuickAffordanceConfigs,
+ private val launchAffordanceUseCase: LaunchKeyguardQuickAffordanceUseCase,
+) {
+ operator fun invoke(
+ configKey: KClass<*>,
+ animationController: ActivityLaunchAnimator.Controller?,
+ ) {
+ @Suppress("UNCHECKED_CAST")
+ val config = configs.get(configKey as KClass<out KeyguardQuickAffordanceConfig>)
+ when (val result = config.onQuickAffordanceClicked(animationController)) {
+ is OnClickedResult.StartActivity ->
+ launchAffordanceUseCase(
+ intent = result.intent,
+ canShowWhileLocked = result.canShowWhileLocked,
+ animationController = animationController
+ )
+ is OnClickedResult.Handled -> Unit
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/SetClockPositionUseCase.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/SetClockPositionUseCase.kt
new file mode 100644
index 0000000..8f746e5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/SetClockPositionUseCase.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.usecase
+
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import javax.inject.Inject
+
+/** Use-case for setting the updated clock position. */
+class SetClockPositionUseCase
+@Inject
+constructor(
+ private val keyguardRepository: KeyguardRepository,
+) {
+ operator fun invoke(x: Int, y: Int) {
+ keyguardRepository.setClockPosition(x, y)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/SetKeyguardBottomAreaAlphaUseCase.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/SetKeyguardBottomAreaAlphaUseCase.kt
new file mode 100644
index 0000000..90be1ec
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/SetKeyguardBottomAreaAlphaUseCase.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.usecase
+
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import javax.inject.Inject
+
+/** Use-case for setting the alpha that the keyguard bottom area should use */
+class SetKeyguardBottomAreaAlphaUseCase
+@Inject
+constructor(
+ private val repository: KeyguardRepository,
+) {
+ operator fun invoke(alpha: Float) {
+ repository.setBottomAreaAlpha(alpha)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/SetKeyguardBottomAreaAnimateDozingTransitionsUseCase.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/SetKeyguardBottomAreaAnimateDozingTransitionsUseCase.kt
new file mode 100644
index 0000000..007780a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/SetKeyguardBottomAreaAnimateDozingTransitionsUseCase.kt
@@ -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.keyguard.domain.usecase
+
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import javax.inject.Inject
+
+/**
+ * Use-case for setting whether the keyguard bottom area should animate the next doze transitions
+ */
+class SetKeyguardBottomAreaAnimateDozingTransitionsUseCase
+@Inject
+constructor(
+ private val repository: KeyguardRepository,
+) {
+ operator fun invoke(animate: Boolean) {
+ repository.setAnimateDozingTransitions(animate)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardQuickAffordanceModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardQuickAffordanceModel.kt
new file mode 100644
index 0000000..09785df
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardQuickAffordanceModel.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.shared.model
+
+import androidx.annotation.StringRes
+import com.android.systemui.containeddrawable.ContainedDrawable
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
+import kotlin.reflect.KClass
+
+/**
+ * Models a "quick affordance" in the keyguard bottom area (for example, a button on the
+ * lock-screen).
+ */
+sealed class KeyguardQuickAffordanceModel {
+
+ /** No affordance should show up. */
+ object Hidden : KeyguardQuickAffordanceModel()
+
+ /** A affordance is visible. */
+ data class Visible(
+ /** Identifier for the affordance this is modeling. */
+ val configKey: KClass<out KeyguardQuickAffordanceConfig>,
+ /** An icon for the affordance. */
+ val icon: ContainedDrawable,
+ /**
+ * Resource ID for a string to use for the accessibility content description text of the
+ * affordance.
+ */
+ @StringRes val contentDescriptionResourceId: Int,
+ ) : KeyguardQuickAffordanceModel()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardQuickAffordancePosition.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardQuickAffordancePosition.kt
new file mode 100644
index 0000000..b71e15d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardQuickAffordancePosition.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.shared.model
+
+/** Enumerates all possible positions for quick affordances that can appear on the lock-screen. */
+enum class KeyguardQuickAffordancePosition {
+ BOTTOM_START,
+ BOTTOM_END,
+}
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
new file mode 100644
index 0000000..04d30bf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
@@ -0,0 +1,307 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.binder
+
+import android.util.Size
+import android.util.TypedValue
+import android.view.View
+import android.view.ViewGroup
+import android.view.ViewPropertyAnimator
+import android.widget.ImageView
+import android.widget.TextView
+import androidx.core.view.isVisible
+import androidx.core.view.updateLayoutParams
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.settingslib.Utils
+import com.android.systemui.R
+import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.animation.Interpolators
+import com.android.systemui.containeddrawable.ContainedDrawable
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordanceViewModel
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.plugins.FalsingManager
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.launch
+
+/**
+ * Binds a keyguard bottom area view to its view-model.
+ *
+ * To use this properly, users should maintain a one-to-one relationship between the [View] and the
+ * view-binding, binding each view only once. It is okay and expected for the same instance of the
+ * view-model to be reused for multiple view/view-binder bindings.
+ */
+object KeyguardBottomAreaViewBinder {
+
+ private const val EXIT_DOZE_BUTTON_REVEAL_ANIMATION_DURATION_MS = 250L
+
+ /**
+ * Defines interface for an object that acts as the binding between the view and its view-model.
+ *
+ * Users of the [KeyguardBottomAreaViewBinder] class should use this to control the binder after
+ * it is bound.
+ */
+ interface Binding {
+ /**
+ * Returns a collection of [ViewPropertyAnimator] instances that can be used to animate the
+ * indication areas.
+ */
+ fun getIndicationAreaAnimators(): List<ViewPropertyAnimator>
+
+ /** Notifies that device configuration has changed. */
+ fun onConfigurationChanged()
+ }
+
+ /** Binds the view to the view-model, continuing to update the former based on the latter. */
+ @JvmStatic
+ fun bind(
+ view: ViewGroup,
+ viewModel: KeyguardBottomAreaViewModel,
+ falsingManager: FalsingManager,
+ ): Binding {
+ val indicationArea: View = view.requireViewById(R.id.keyguard_indication_area)
+ val ambientIndicationArea: View? = view.findViewById(R.id.ambient_indication_container)
+ val startButton: ImageView = view.requireViewById(R.id.start_button)
+ val endButton: ImageView = view.requireViewById(R.id.end_button)
+ val overlayContainer: View = view.requireViewById(R.id.overlay_container)
+ val indicationText: TextView = view.requireViewById(R.id.keyguard_indication_text)
+ val indicationTextBottom: TextView =
+ view.requireViewById(R.id.keyguard_indication_text_bottom)
+
+ view.clipChildren = false
+ view.clipToPadding = false
+
+ val configurationBasedDimensions = MutableStateFlow(loadFromResources(view))
+
+ view.repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ launch {
+ combine(viewModel.startButton, viewModel.animateButtonReveal) {
+ buttonModel,
+ animateReveal ->
+ Pair(buttonModel, animateReveal)
+ }
+ .collect { (buttonModel, animateReveal) ->
+ updateButton(
+ view = startButton,
+ viewModel = buttonModel,
+ animateReveal = animateReveal,
+ falsingManager = falsingManager,
+ )
+ }
+ }
+
+ launch {
+ combine(viewModel.endButton, viewModel.animateButtonReveal) {
+ buttonModel,
+ animateReveal ->
+ Pair(buttonModel, animateReveal)
+ }
+ .collect { (buttonModel, animateReveal) ->
+ updateButton(
+ view = endButton,
+ viewModel = buttonModel,
+ animateReveal = animateReveal,
+ falsingManager = falsingManager,
+ )
+ }
+ }
+
+ launch {
+ viewModel.isOverlayContainerVisible.collect { isVisible ->
+ overlayContainer.visibility =
+ if (isVisible) {
+ View.VISIBLE
+ } else {
+ View.INVISIBLE
+ }
+ }
+ }
+
+ 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
+ indicationArea.alpha = alpha
+ startButton.alpha = alpha
+ endButton.alpha = alpha
+ }
+ }
+
+ launch {
+ viewModel.indicationAreaTranslationX.collect { translationX ->
+ indicationArea.translationX = translationX
+ ambientIndicationArea?.translationX = translationX
+ }
+ }
+
+ launch {
+ combine(
+ viewModel.isIndicationAreaPadded,
+ configurationBasedDimensions.map { it.indicationAreaPaddingPx },
+ ) { isPadded, paddingIfPaddedPx ->
+ if (isPadded) {
+ paddingIfPaddedPx
+ } else {
+ 0
+ }
+ }
+ .collect { paddingPx ->
+ indicationArea.setPadding(paddingPx, 0, paddingPx, 0)
+ }
+ }
+
+ launch {
+ configurationBasedDimensions
+ .map { it.defaultBurnInPreventionYOffsetPx }
+ .flatMapLatest { defaultBurnInOffsetY ->
+ viewModel.indicationAreaTranslationY(defaultBurnInOffsetY)
+ }
+ .collect { translationY ->
+ indicationArea.translationY = translationY
+ ambientIndicationArea?.translationY = translationY
+ }
+ }
+
+ launch {
+ configurationBasedDimensions.collect { dimensions ->
+ indicationText.setTextSize(
+ TypedValue.COMPLEX_UNIT_PX,
+ dimensions.indicationTextSizePx.toFloat(),
+ )
+ indicationTextBottom.setTextSize(
+ TypedValue.COMPLEX_UNIT_PX,
+ dimensions.indicationTextSizePx.toFloat(),
+ )
+
+ startButton.updateLayoutParams<ViewGroup.LayoutParams> {
+ width = dimensions.buttonSizePx.width
+ height = dimensions.buttonSizePx.height
+ }
+ endButton.updateLayoutParams<ViewGroup.LayoutParams> {
+ width = dimensions.buttonSizePx.width
+ height = dimensions.buttonSizePx.height
+ }
+ }
+ }
+ }
+ }
+
+ return object : Binding {
+ override fun getIndicationAreaAnimators(): List<ViewPropertyAnimator> {
+ return listOf(indicationArea, ambientIndicationArea).mapNotNull { it?.animate() }
+ }
+
+ override fun onConfigurationChanged() {
+ configurationBasedDimensions.value = loadFromResources(view)
+ }
+ }
+ }
+
+ private fun updateButton(
+ view: ImageView,
+ viewModel: KeyguardQuickAffordanceViewModel,
+ animateReveal: Boolean,
+ falsingManager: FalsingManager,
+ ) {
+ if (!viewModel.isVisible) {
+ view.isVisible = false
+ return
+ }
+
+ if (!view.isVisible) {
+ view.isVisible = true
+ if (animateReveal) {
+ view.alpha = 0f
+ view.translationY = view.height / 2f
+ view
+ .animate()
+ .alpha(1f)
+ .translationY(0f)
+ .setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN)
+ .setDuration(EXIT_DOZE_BUTTON_REVEAL_ANIMATION_DURATION_MS)
+ .start()
+ }
+ }
+
+ when (viewModel.icon) {
+ is ContainedDrawable.WithDrawable -> view.setImageDrawable(viewModel.icon.drawable)
+ is ContainedDrawable.WithResource -> view.setImageResource(viewModel.icon.resourceId)
+ }
+
+ view.drawable.setTint(
+ Utils.getColorAttrDefaultColor(
+ view.context,
+ com.android.internal.R.attr.textColorPrimary
+ )
+ )
+ view.backgroundTintList =
+ Utils.getColorAttr(view.context, com.android.internal.R.attr.colorSurface)
+
+ view.contentDescription = view.context.getString(viewModel.contentDescriptionResourceId)
+ view.setOnClickListener {
+ if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
+ return@setOnClickListener
+ }
+
+ if (viewModel.configKey != null) {
+ viewModel.onClicked(
+ KeyguardQuickAffordanceViewModel.OnClickedParameters(
+ configKey = viewModel.configKey,
+ animationController = ActivityLaunchAnimator.Controller.fromView(view),
+ )
+ )
+ }
+ }
+ }
+
+ private fun loadFromResources(view: View): ConfigurationBasedDimensions {
+ return ConfigurationBasedDimensions(
+ defaultBurnInPreventionYOffsetPx =
+ view.resources.getDimensionPixelOffset(R.dimen.default_burn_in_prevention_offset),
+ indicationAreaPaddingPx =
+ view.resources.getDimensionPixelOffset(R.dimen.keyguard_indication_area_padding),
+ indicationTextSizePx =
+ view.resources.getDimensionPixelSize(
+ com.android.internal.R.dimen.text_size_small_material,
+ ),
+ buttonSizePx =
+ Size(
+ view.resources.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_width),
+ view.resources.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_height),
+ ),
+ )
+ }
+
+ private data class ConfigurationBasedDimensions(
+ val defaultBurnInPreventionYOffsetPx: Int,
+ val indicationAreaPaddingPx: Int,
+ val indicationTextSizePx: Int,
+ val buttonSizePx: Size,
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt
new file mode 100644
index 0000000..4b69a81
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.doze.util.BurnInHelperWrapper
+import com.android.systemui.keyguard.domain.usecase.ObserveAnimateBottomAreaTransitionsUseCase
+import com.android.systemui.keyguard.domain.usecase.ObserveBottomAreaAlphaUseCase
+import com.android.systemui.keyguard.domain.usecase.ObserveClockPositionUseCase
+import com.android.systemui.keyguard.domain.usecase.ObserveDozeAmountUseCase
+import com.android.systemui.keyguard.domain.usecase.ObserveIsDozingUseCase
+import com.android.systemui.keyguard.domain.usecase.ObserveKeyguardQuickAffordanceUseCase
+import com.android.systemui.keyguard.domain.usecase.OnKeyguardQuickAffordanceClickedUseCase
+import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordanceModel
+import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePosition
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
+
+/** View-model for the keyguard bottom area view */
+class KeyguardBottomAreaViewModel
+@Inject
+constructor(
+ private val observeQuickAffordanceUseCase: ObserveKeyguardQuickAffordanceUseCase,
+ private val onQuickAffordanceClickedUseCase: OnKeyguardQuickAffordanceClickedUseCase,
+ observeBottomAreaAlphaUseCase: ObserveBottomAreaAlphaUseCase,
+ observeIsDozingUseCase: ObserveIsDozingUseCase,
+ observeAnimateBottomAreaTransitionsUseCase: ObserveAnimateBottomAreaTransitionsUseCase,
+ private val observeDozeAmountUseCase: ObserveDozeAmountUseCase,
+ observeClockPositionUseCase: ObserveClockPositionUseCase,
+ private val burnInHelperWrapper: BurnInHelperWrapper,
+) {
+ /** An observable for the view-model of the "start button" quick affordance. */
+ val startButton: Flow<KeyguardQuickAffordanceViewModel> =
+ button(KeyguardQuickAffordancePosition.BOTTOM_START)
+ /** An observable for the view-model of the "end button" quick affordance. */
+ val endButton: Flow<KeyguardQuickAffordanceViewModel> =
+ button(KeyguardQuickAffordancePosition.BOTTOM_END)
+ /**
+ * An observable for whether the next time a quick action button becomes visible, it should
+ * animate.
+ */
+ val animateButtonReveal: Flow<Boolean> =
+ observeAnimateBottomAreaTransitionsUseCase().distinctUntilChanged()
+ /** An observable for whether the overlay container should be visible. */
+ val isOverlayContainerVisible: Flow<Boolean> =
+ observeIsDozingUseCase().map { !it }.distinctUntilChanged()
+ /** An observable for the alpha level for the entire bottom area. */
+ val alpha: Flow<Float> = observeBottomAreaAlphaUseCase().distinctUntilChanged()
+ /** An observable for whether the indication area should be padded. */
+ val isIndicationAreaPadded: Flow<Boolean> =
+ combine(startButton, endButton) { startButtonModel, endButtonModel ->
+ startButtonModel.isVisible || endButtonModel.isVisible
+ }
+ .distinctUntilChanged()
+ /** An observable for the x-offset by which the indication area should be translated. */
+ val indicationAreaTranslationX: Flow<Float> =
+ observeClockPositionUseCase().map { it.x.toFloat() }.distinctUntilChanged()
+
+ /** Returns an observable for the y-offset by which the indication area should be translated. */
+ fun indicationAreaTranslationY(defaultBurnInOffset: Int): Flow<Float> {
+ return observeDozeAmountUseCase()
+ .map { dozeAmount ->
+ dozeAmount *
+ (burnInHelperWrapper.burnInOffset(
+ /* amplitude = */ defaultBurnInOffset * 2,
+ /* xAxis= */ false,
+ ) - defaultBurnInOffset)
+ }
+ .distinctUntilChanged()
+ }
+
+ private fun button(
+ position: KeyguardQuickAffordancePosition
+ ): Flow<KeyguardQuickAffordanceViewModel> {
+ return observeQuickAffordanceUseCase(position)
+ .map { model -> model.toViewModel() }
+ .distinctUntilChanged()
+ }
+
+ private fun KeyguardQuickAffordanceModel.toViewModel(): KeyguardQuickAffordanceViewModel {
+ return when (this) {
+ is KeyguardQuickAffordanceModel.Visible ->
+ KeyguardQuickAffordanceViewModel(
+ configKey = configKey,
+ isVisible = true,
+ icon = icon,
+ contentDescriptionResourceId = contentDescriptionResourceId,
+ onClicked = { parameters ->
+ onQuickAffordanceClickedUseCase(
+ configKey = parameters.configKey,
+ animationController = parameters.animationController,
+ )
+ },
+ )
+ is KeyguardQuickAffordanceModel.Hidden -> KeyguardQuickAffordanceViewModel()
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt
new file mode 100644
index 0000000..2417998
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import androidx.annotation.StringRes
+import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.containeddrawable.ContainedDrawable
+import kotlin.reflect.KClass
+
+/** Models the UI state of a keyguard quick affordance button. */
+data class KeyguardQuickAffordanceViewModel(
+ val configKey: KClass<*>? = null,
+ val isVisible: Boolean = false,
+ val icon: ContainedDrawable = ContainedDrawable.WithResource(0),
+ @StringRes val contentDescriptionResourceId: Int = 0,
+ val onClicked: (OnClickedParameters) -> Unit = {},
+) {
+ data class OnClickedParameters(
+ val configKey: KClass<*>,
+ val animationController: ActivityLaunchAnimator.Controller?,
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt b/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt
new file mode 100644
index 0000000..e364918
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.lifecycle
+
+import android.view.View
+import android.view.ViewTreeObserver
+import androidx.annotation.MainThread
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.LifecycleRegistry
+import androidx.lifecycle.lifecycleScope
+import com.android.systemui.util.Assert
+import kotlin.coroutines.CoroutineContext
+import kotlin.coroutines.EmptyCoroutineContext
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.DisposableHandle
+import kotlinx.coroutines.launch
+
+/**
+ * Runs the given [block] every time the [View] becomes attached (or immediately after calling this
+ * function, if the view was already attached), automatically canceling the work when the `View`
+ * becomes detached.
+ *
+ * Only use from the main thread.
+ *
+ * When [block] is run, it is run in the context of a [ViewLifecycleOwner] which the caller can use
+ * to launch jobs, with confidence that the jobs will be properly canceled when the view is
+ * detached.
+ *
+ * The [block] may be run multiple times, running once per every time the view is attached. Each
+ * time the block is run for a new attachment event, the [ViewLifecycleOwner] provided will be a
+ * fresh one.
+ *
+ * @param coroutineContext An optional [CoroutineContext] to replace the dispatcher [block] is
+ * invoked on.
+ * @param block The block of code that should be run when the view becomes attached. It can end up
+ * being invoked multiple times if the view is reattached after being detached.
+ * @return A [DisposableHandle] to invoke when the caller of the function destroys its [View] and is
+ * no longer interested in the [block] being run the next time its attached. Calling this is an
+ * optional optimization as the logic will be properly cleaned up and destroyed each time the view
+ * is detached. Using this is not *thread-safe* and should only be used on the main thread.
+ */
+@MainThread
+fun View.repeatWhenAttached(
+ coroutineContext: CoroutineContext = EmptyCoroutineContext,
+ block: suspend LifecycleOwner.(View) -> Unit,
+): DisposableHandle {
+ Assert.isMainThread()
+ val view = this
+ // The suspend block will run on the app's main thread unless the caller supplies a different
+ // dispatcher to use. We don't want it to run on the Dispatchers.Default thread pool as
+ // default behavior. Instead, we want it to run on the view's UI thread since the user will
+ // presumably want to call view methods that require being called from said UI thread.
+ val lifecycleCoroutineContext = Dispatchers.Main + coroutineContext
+ var lifecycleOwner: ViewLifecycleOwner? = null
+ val onAttachListener =
+ object : View.OnAttachStateChangeListener {
+ override fun onViewAttachedToWindow(v: View?) {
+ Assert.isMainThread()
+ lifecycleOwner?.onDestroy()
+ lifecycleOwner =
+ createLifecycleOwnerAndRun(
+ view,
+ lifecycleCoroutineContext,
+ block,
+ )
+ }
+
+ override fun onViewDetachedFromWindow(v: View?) {
+ lifecycleOwner?.onDestroy()
+ lifecycleOwner = null
+ }
+ }
+
+ addOnAttachStateChangeListener(onAttachListener)
+ if (view.isAttachedToWindow) {
+ lifecycleOwner =
+ createLifecycleOwnerAndRun(
+ view,
+ lifecycleCoroutineContext,
+ block,
+ )
+ }
+
+ return object : DisposableHandle {
+ override fun dispose() {
+ Assert.isMainThread()
+
+ lifecycleOwner?.onDestroy()
+ lifecycleOwner = null
+ view.removeOnAttachStateChangeListener(onAttachListener)
+ }
+ }
+}
+
+private fun createLifecycleOwnerAndRun(
+ view: View,
+ coroutineContext: CoroutineContext,
+ block: suspend LifecycleOwner.(View) -> Unit,
+): ViewLifecycleOwner {
+ return ViewLifecycleOwner(view).apply {
+ onCreate()
+ lifecycleScope.launch(coroutineContext) { block(view) }
+ }
+}
+
+/**
+ * A [LifecycleOwner] for a [View] for exclusive use by the [repeatWhenAttached] extension function.
+ *
+ * The implementation requires the caller to call [onCreate] and [onDestroy] when the view is
+ * attached to or detached from a view hierarchy. After [onCreate] and before [onDestroy] is called,
+ * the implementation monitors window state in the following way
+ *
+ * * If the window is not visible, we are in the [Lifecycle.State.CREATED] state
+ * * If the window is visible but not focused, we are in the [Lifecycle.State.STARTED] state
+ * * If the window is visible and focused, we are in the [Lifecycle.State.RESUMED] state
+ *
+ * Or in table format:
+ * ```
+ * ┌───────────────┬───────────────────┬──────────────┬─────────────────┐
+ * │ View attached │ Window Visibility │ Window Focus │ Lifecycle State │
+ * ├───────────────┼───────────────────┴──────────────┼─────────────────┤
+ * │ Not attached │ Any │ N/A │
+ * ├───────────────┼───────────────────┬──────────────┼─────────────────┤
+ * │ │ Not visible │ Any │ CREATED │
+ * │ ├───────────────────┼──────────────┼─────────────────┤
+ * │ Attached │ │ No focus │ STARTED │
+ * │ │ Visible ├──────────────┼─────────────────┤
+ * │ │ │ Has focus │ RESUMED │
+ * └───────────────┴───────────────────┴──────────────┴─────────────────┘
+ * ```
+ */
+private class ViewLifecycleOwner(
+ private val view: View,
+) : LifecycleOwner {
+
+ private val windowVisibleListener =
+ ViewTreeObserver.OnWindowVisibilityChangeListener { updateState() }
+ private val windowFocusListener = ViewTreeObserver.OnWindowFocusChangeListener { updateState() }
+
+ private val registry = LifecycleRegistry(this)
+
+ fun onCreate() {
+ registry.currentState = Lifecycle.State.CREATED
+ view.viewTreeObserver.addOnWindowVisibilityChangeListener(windowVisibleListener)
+ view.viewTreeObserver.addOnWindowFocusChangeListener(windowFocusListener)
+ updateState()
+ }
+
+ fun onDestroy() {
+ view.viewTreeObserver.removeOnWindowVisibilityChangeListener(windowVisibleListener)
+ view.viewTreeObserver.removeOnWindowFocusChangeListener(windowFocusListener)
+ registry.currentState = Lifecycle.State.DESTROYED
+ }
+
+ override fun getLifecycle(): Lifecycle {
+ return registry
+ }
+
+ private fun updateState() {
+ registry.currentState =
+ when {
+ view.windowVisibility != View.VISIBLE -> Lifecycle.State.CREATED
+ !view.hasWindowFocus() -> Lifecycle.State.STARTED
+ else -> Lifecycle.State.RESUMED
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/lifecycle/WindowAddedViewLifecycleOwner.kt b/packages/SystemUI/src/com/android/systemui/lifecycle/WindowAddedViewLifecycleOwner.kt
deleted file mode 100644
index 55c7ac9..0000000
--- a/packages/SystemUI/src/com/android/systemui/lifecycle/WindowAddedViewLifecycleOwner.kt
+++ /dev/null
@@ -1,114 +0,0 @@
-package com.android.systemui.lifecycle
-
-import android.view.View
-import android.view.ViewTreeObserver
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.LifecycleOwner
-import androidx.lifecycle.LifecycleRegistry
-
-/**
- * [LifecycleOwner] for Window-added Views.
- *
- * These are [View] instances that are added to a `Window` using the `WindowManager` API.
- *
- * This implementation goes to:
- * * The <b>CREATED</b> `Lifecycle.State` when the view gets attached to the window but the window
- * is not yet visible
- * * The <b>STARTED</b> `Lifecycle.State` when the view is attached to the window and the window is
- * visible
- * * The <b>RESUMED</b> `Lifecycle.State` when the view is attached to the window and the window is
- * visible and the window receives focus
- *
- * In table format:
- * ```
- * | ----------------------------------------------------------------------------- |
- * | View attached to window | Window visible | Window has focus | Lifecycle state |
- * | ----------------------------------------------------------------------------- |
- * | not attached | Any | INITIALIZED |
- * | ----------------------------------------------------------------------------- |
- * | | not visible | Any | CREATED |
- * | ----------------------------------------------------- |
- * | attached | | not focused | STARTED |
- * | | is visible |----------------------------------- |
- * | | | has focus | RESUMED |
- * | ----------------------------------------------------------------------------- |
- * ```
- * ### Notes
- * * [dispose] must be invoked when the [LifecycleOwner] is done and won't be reused
- * * It is always better for [LifecycleOwner] implementations to be more explicit than just
- * listening to the state of the `Window`. E.g. if the code that added the `View` to the `Window`
- * already has access to the correct state to know when that `View` should become visible and when
- * it is ready to receive interaction from the user then it already knows when to move to `STARTED`
- * and `RESUMED`, respectively. In that case, it's better to implement your own `LifecycleOwner`
- * instead of relying on the `Window` callbacks.
- */
-class WindowAddedViewLifecycleOwner
-@JvmOverloads
-constructor(
- private val view: View,
- registryFactory: (LifecycleOwner) -> LifecycleRegistry = { LifecycleRegistry(it) },
-) : LifecycleOwner {
-
- private val windowAttachListener =
- object : ViewTreeObserver.OnWindowAttachListener {
- override fun onWindowAttached() {
- updateCurrentState()
- }
-
- override fun onWindowDetached() {
- updateCurrentState()
- }
- }
- private val windowFocusListener =
- ViewTreeObserver.OnWindowFocusChangeListener { updateCurrentState() }
- private val windowVisibilityListener =
- ViewTreeObserver.OnWindowVisibilityChangeListener { updateCurrentState() }
-
- private val registry = registryFactory(this)
-
- init {
- setCurrentState(Lifecycle.State.INITIALIZED)
-
- with(view.viewTreeObserver) {
- addOnWindowAttachListener(windowAttachListener)
- addOnWindowVisibilityChangeListener(windowVisibilityListener)
- addOnWindowFocusChangeListener(windowFocusListener)
- }
-
- updateCurrentState()
- }
-
- override fun getLifecycle(): Lifecycle {
- return registry
- }
-
- /**
- * Disposes of this [LifecycleOwner], performing proper clean-up.
- *
- * <p>Invoke this when the instance is finished and won't be reused.
- */
- fun dispose() {
- with(view.viewTreeObserver) {
- removeOnWindowAttachListener(windowAttachListener)
- removeOnWindowVisibilityChangeListener(windowVisibilityListener)
- removeOnWindowFocusChangeListener(windowFocusListener)
- }
- }
-
- private fun updateCurrentState() {
- val state =
- when {
- !view.isAttachedToWindow -> Lifecycle.State.INITIALIZED
- view.windowVisibility != View.VISIBLE -> Lifecycle.State.CREATED
- !view.hasWindowFocus() -> Lifecycle.State.STARTED
- else -> Lifecycle.State.RESUMED
- }
- setCurrentState(state)
- }
-
- private fun setCurrentState(state: Lifecycle.State) {
- if (registry.currentState != state) {
- registry.currentState = state
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt b/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt
index db446c3..dc23684d 100644
--- a/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt
@@ -20,14 +20,19 @@
import android.util.Log
import com.android.systemui.log.dagger.LogModule
import com.android.systemui.util.collection.RingBuffer
+import com.google.errorprone.annotations.CompileTimeConstant
import java.io.PrintWriter
import java.text.SimpleDateFormat
+import java.util.Arrays.stream
import java.util.Locale
import java.util.concurrent.ArrayBlockingQueue
import java.util.concurrent.BlockingQueue
import kotlin.concurrent.thread
import kotlin.math.max
+const val UNBOUNDED_STACK_TRACE = -1
+const val NESTED_TRACE_DEPTH = 10
+
/**
* A simple ring buffer of recyclable log messages
*
@@ -69,12 +74,18 @@
* @param maxSize The maximum number of messages to keep in memory at any one time. Buffers start
* out empty and grow up to [maxSize] as new messages are logged. Once the buffer's size reaches
* the maximum, it behaves like a ring buffer.
+ * @param rootStackTraceDepth The number of stack trace elements to be logged for an exception when
+ * the logBuffer is dumped. Defaulted to -1 [UNBOUNDED_STACK_TRACE] to print the entire stack trace.
+ * @param nestedStackTraceDepth The number of stack trace elements to be logged for any nested
+ * exceptions present in [Throwable.cause] or [Throwable.suppressedExceptions].
*/
class LogBuffer @JvmOverloads constructor(
private val name: String,
private val maxSize: Int,
private val logcatEchoTracker: LogcatEchoTracker,
- private val systrace: Boolean = true
+ private val systrace: Boolean = true,
+ private val rootStackTraceDepth: Int = UNBOUNDED_STACK_TRACE,
+ private val nestedStackTraceDepth: Int = NESTED_TRACE_DEPTH,
) {
private val buffer = RingBuffer(maxSize) { LogMessageImpl.create() }
@@ -107,11 +118,11 @@
* May also log the message to logcat if echoing is enabled for this buffer or tag.
*
* The actual string of the log message is not constructed until it is needed. To accomplish
- * this, logging a message is a two-step process. First, a fresh instance of [LogMessage] is
- * obtained and is passed to the [initializer]. The initializer stores any relevant data on the
- * message's fields. The message is then inserted into the buffer where it waits until it is
- * either pushed out by newer messages or it needs to printed. If and when this latter moment
- * occurs, the [printer] function is called on the message. It reads whatever data the
+ * this, logging a message is a two-step process. First, a fresh instance of [LogMessage] is
+ * obtained and is passed to the [messageInitializer]. The initializer stores any relevant data
+ * on the message's fields. The message is then inserted into the buffer where it waits until it
+ * is either pushed out by newer messages or it needs to printed. If and when this latter moment
+ * occurs, the [messagePrinter] function is called on the message. It reads whatever data the
* initializer stored and converts it to a human-readable log message.
*
* @param tag A string of at most 23 characters, used for grouping logs into categories or
@@ -120,27 +131,49 @@
* echoed. In general, a module should split most of its logs into either INFO or DEBUG level.
* INFO level should be reserved for information that other parts of the system might care
* about, leaving the specifics of code's day-to-day operations to DEBUG.
- * @param initializer A function that will be called immediately to store relevant data on the
- * log message. The value of `this` will be the LogMessage to be initialized.
- * @param printer A function that will be called if and when the message needs to be dumped to
- * logcat or a bug report. It should read the data stored by the initializer and convert it to
- * a human-readable string. The value of `this` will be the LogMessage to be printed.
- * **IMPORTANT:** The printer should ONLY ever reference fields on the LogMessage and NEVER any
- * variables in its enclosing scope. Otherwise, the runtime will need to allocate a new instance
- * of the printer for each call, thwarting our attempts at avoiding any sort of allocation.
+ * @param messageInitializer A function that will be called immediately to store relevant data
+ * on the log message. The value of `this` will be the LogMessage to be initialized.
+ * @param messagePrinter A function that will be called if and when the message needs to be
+ * dumped to logcat or a bug report. It should read the data stored by the initializer and
+ * convert it to a human-readable string. The value of `this` will be the LogMessage to be
+ * printed. **IMPORTANT:** The printer should ONLY ever reference fields on the LogMessage and
+ * NEVER any variables in its enclosing scope. Otherwise, the runtime will need to allocate a
+ * new instance of the printer for each call, thwarting our attempts at avoiding any sort of
+ * allocation.
+ * @param exception Provide any exception that need to be logged. This is saved as
+ * [LogMessage.exception]
*/
+ @JvmOverloads
inline fun log(
- tag: String,
- level: LogLevel,
- initializer: LogMessage.() -> Unit,
- noinline printer: LogMessage.() -> String
+ tag: String,
+ level: LogLevel,
+ messageInitializer: MessageInitializer,
+ noinline messagePrinter: MessagePrinter,
+ exception: Throwable? = null,
) {
- val message = obtain(tag, level, printer)
- initializer(message)
+ val message = obtain(tag, level, messagePrinter, exception)
+ messageInitializer(message)
commit(message)
}
/**
+ * Logs a compile-time string constant [message] to the log buffer. Use sparingly.
+ *
+ * May also log the message to logcat if echoing is enabled for this buffer or tag. This is for
+ * simpler use-cases where [message] is a compile time string constant. For use-cases where the
+ * log message is built during runtime, use the [LogBuffer.log] overloaded method that takes in
+ * an initializer and a message printer.
+ *
+ * Log buffers are limited by the number of entries, so logging more frequently
+ * will limit the time window that the LogBuffer covers in a bug report. Richer logs, on the
+ * other hand, make a bug report more actionable, so using the [log] with a messagePrinter to
+ * add more detail to every log may do more to improve overall logging than adding more logs
+ * with this method.
+ */
+ fun log(tag: String, level: LogLevel, @CompileTimeConstant message: String) =
+ log(tag, level, {str1 = message}, { str1!! })
+
+ /**
* You should call [log] instead of this method.
*
* Obtains the next [LogMessage] from the ring buffer. If the buffer is not yet at max size,
@@ -151,15 +184,16 @@
*/
@Synchronized
fun obtain(
- tag: String,
- level: LogLevel,
- printer: (LogMessage) -> String
- ): LogMessageImpl {
+ tag: String,
+ level: LogLevel,
+ messagePrinter: MessagePrinter,
+ exception: Throwable? = null,
+ ): LogMessage {
if (!mutable) {
return FROZEN_MESSAGE
}
val message = buffer.advance()
- message.reset(tag, level, System.currentTimeMillis(), printer)
+ message.reset(tag, level, System.currentTimeMillis(), messagePrinter, exception)
return message
}
@@ -230,19 +264,79 @@
}
}
- private fun dumpMessage(message: LogMessage, pw: PrintWriter) {
- pw.print(DATE_FORMAT.format(message.timestamp))
+ private fun dumpMessage(
+ message: LogMessage,
+ pw: PrintWriter
+ ) {
+ val formattedTimestamp = DATE_FORMAT.format(message.timestamp)
+ val shortLevel = message.level.shortString
+ val messageToPrint = message.messagePrinter(message)
+ val tag = message.tag
+ printLikeLogcat(pw, formattedTimestamp, shortLevel, tag, messageToPrint)
+ message.exception?.let { ex ->
+ printException(
+ pw,
+ formattedTimestamp,
+ shortLevel,
+ ex,
+ tag,
+ stackTraceDepth = rootStackTraceDepth)
+ }
+ }
+
+ private fun printException(
+ pw: PrintWriter,
+ timestamp: String,
+ level: String,
+ exception: Throwable,
+ tag: String,
+ exceptionMessagePrefix: String = "",
+ stackTraceDepth: Int = UNBOUNDED_STACK_TRACE
+ ) {
+ val message = "$exceptionMessagePrefix$exception"
+ printLikeLogcat(pw, timestamp, level, tag, message)
+ var stacktraceStream = stream(exception.stackTrace)
+ if (stackTraceDepth != UNBOUNDED_STACK_TRACE) {
+ stacktraceStream = stacktraceStream.limit(stackTraceDepth.toLong())
+ }
+ stacktraceStream.forEach { line ->
+ printLikeLogcat(pw, timestamp, level, tag, "\tat $line")
+ }
+ exception.cause?.let { cause ->
+ printException(pw, timestamp, level, cause, tag, "Caused by: ", nestedStackTraceDepth)
+ }
+ exception.suppressedExceptions.forEach { suppressed ->
+ printException(
+ pw,
+ timestamp,
+ level,
+ suppressed,
+ tag,
+ "Suppressed: ",
+ nestedStackTraceDepth
+ )
+ }
+ }
+
+ private fun printLikeLogcat(
+ pw: PrintWriter,
+ formattedTimestamp: String,
+ shortLogLevel: String,
+ tag: String,
+ message: String
+ ) {
+ pw.print(formattedTimestamp)
pw.print(" ")
- pw.print(message.level.shortString)
+ pw.print(shortLogLevel)
pw.print(" ")
- pw.print(message.tag)
+ pw.print(tag)
pw.print(": ")
- pw.println(message.printer(message))
+ pw.println(message)
}
private fun echo(message: LogMessage, toLogcat: Boolean, toSystrace: Boolean) {
if (toLogcat || toSystrace) {
- val strMessage = message.printer(message)
+ val strMessage = message.messagePrinter(message)
if (toSystrace) {
echoToSystrace(message, strMessage)
}
@@ -259,16 +353,22 @@
private fun echoToLogcat(message: LogMessage, strMessage: String) {
when (message.level) {
- LogLevel.VERBOSE -> Log.v(message.tag, strMessage)
- LogLevel.DEBUG -> Log.d(message.tag, strMessage)
- LogLevel.INFO -> Log.i(message.tag, strMessage)
- LogLevel.WARNING -> Log.w(message.tag, strMessage)
- LogLevel.ERROR -> Log.e(message.tag, strMessage)
- LogLevel.WTF -> Log.wtf(message.tag, strMessage)
+ LogLevel.VERBOSE -> Log.v(message.tag, strMessage, message.exception)
+ LogLevel.DEBUG -> Log.d(message.tag, strMessage, message.exception)
+ LogLevel.INFO -> Log.i(message.tag, strMessage, message.exception)
+ LogLevel.WARNING -> Log.w(message.tag, strMessage, message.exception)
+ LogLevel.ERROR -> Log.e(message.tag, strMessage, message.exception)
+ LogLevel.WTF -> Log.wtf(message.tag, strMessage, message.exception)
}
}
}
+/**
+ * A function that will be called immediately to store relevant data on the log message. The value
+ * of `this` will be the LogMessage to be initialized.
+ */
+typealias MessageInitializer = LogMessage.() -> Unit
+
private const val TAG = "LogBuffer"
private val DATE_FORMAT = SimpleDateFormat("MM-dd HH:mm:ss.SSS", Locale.US)
-private val FROZEN_MESSAGE = LogMessageImpl.create()
\ No newline at end of file
+private val FROZEN_MESSAGE = LogMessageImpl.create()
diff --git a/packages/SystemUI/src/com/android/systemui/log/LogMessage.kt b/packages/SystemUI/src/com/android/systemui/log/LogMessage.kt
index 2a0a2aa6..987aea8 100644
--- a/packages/SystemUI/src/com/android/systemui/log/LogMessage.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/LogMessage.kt
@@ -25,7 +25,7 @@
*
* When a message is logged, the code doing the logging stores data in one or more of the generic
* fields ([str1], [int1], etc). When it comes time to dump the message to logcat/bugreport/etc, the
- * [printer] function reads the data stored in the generic fields and converts that to a human-
+ * [messagePrinter] function reads the data stored in the generic fields and converts that to a human-
* readable string. Thus, for every log type there must be a specialized initializer function that
* stores data specific to that log type and a specialized printer function that prints that data.
*
@@ -35,7 +35,8 @@
val level: LogLevel
val tag: String
val timestamp: Long
- val printer: LogMessage.() -> String
+ val messagePrinter: MessagePrinter
+ val exception: Throwable?
var str1: String?
var str2: String?
@@ -50,3 +51,13 @@
var bool3: Boolean
var bool4: Boolean
}
+
+/**
+ * A function that will be called if and when the message needs to be dumped to
+ * logcat or a bug report. It should read the data stored by the initializer and convert it to
+ * a human-readable string. The value of `this` will be the LogMessage to be printed.
+ * **IMPORTANT:** The printer should ONLY ever reference fields on the LogMessage and NEVER any
+ * variables in its enclosing scope. Otherwise, the runtime will need to allocate a new instance
+ * of the printer for each call, thwarting our attempts at avoiding any sort of allocation.
+ */
+typealias MessagePrinter = LogMessage.() -> String
diff --git a/packages/SystemUI/src/com/android/systemui/log/LogMessageImpl.kt b/packages/SystemUI/src/com/android/systemui/log/LogMessageImpl.kt
index d33ac4b..4dd6f65 100644
--- a/packages/SystemUI/src/com/android/systemui/log/LogMessageImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/LogMessageImpl.kt
@@ -23,7 +23,8 @@
override var level: LogLevel,
override var tag: String,
override var timestamp: Long,
- override var printer: LogMessage.() -> String,
+ override var messagePrinter: MessagePrinter,
+ override var exception: Throwable?,
override var str1: String?,
override var str2: String?,
override var str3: String?,
@@ -35,19 +36,21 @@
override var bool1: Boolean,
override var bool2: Boolean,
override var bool3: Boolean,
- override var bool4: Boolean
+ override var bool4: Boolean,
) : LogMessage {
fun reset(
tag: String,
level: LogLevel,
timestamp: Long,
- renderer: LogMessage.() -> String
+ renderer: MessagePrinter,
+ exception: Throwable? = null,
) {
this.level = level
this.tag = tag
this.timestamp = timestamp
- this.printer = renderer
+ this.messagePrinter = renderer
+ this.exception = exception
str1 = null
str2 = null
str3 = null
@@ -68,7 +71,8 @@
LogLevel.DEBUG,
DEFAULT_TAG,
0,
- DEFAULT_RENDERER,
+ DEFAULT_PRINTER,
+ null,
null,
null,
null,
@@ -86,4 +90,4 @@
}
private const val DEFAULT_TAG = "UnknownTag"
-private val DEFAULT_RENDERER: LogMessage.() -> String = { "Unknown message: $this" }
+private val DEFAULT_PRINTER: MessagePrinter = { "Unknown message: $this" }
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardUpdateMonitorLog.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardUpdateMonitorLog.kt
new file mode 100644
index 0000000..323ee21
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardUpdateMonitorLog.kt
@@ -0,0 +1,4 @@
+package com.android.systemui.log.dagger
+
+/** A [com.android.systemui.log.LogBuffer] for KeyguardUpdateMonitor. */
+annotation class KeyguardUpdateMonitorLog
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 d0da18a..c858bc3 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -255,6 +255,16 @@
return factory.create("MediaCarouselCtlrLog", 20);
}
+ /**
+ * Provides a {@link LogBuffer} for use in the status bar connectivity pipeline
+ */
+ @Provides
+ @SysUISingleton
+ @StatusBarConnectivityLog
+ public static LogBuffer provideStatusBarConnectivityBuffer(LogBufferFactory factory) {
+ return factory.create("StatusBarConnectivityLog", 64);
+ }
+
/** Allows logging buffers to be tweaked via adb on debug builds but not on prod builds. */
@Provides
@SysUISingleton
@@ -277,4 +287,14 @@
public static LogBuffer provideStatusBarNetworkControllerBuffer(LogBufferFactory factory) {
return factory.create("StatusBarNetworkControllerLog", 20);
}
+
+ /**
+ * Provides a {@link LogBuffer} for use by {@link com.android.keyguard.KeyguardUpdateMonitor}.
+ */
+ @Provides
+ @SysUISingleton
+ @KeyguardUpdateMonitorLog
+ public static LogBuffer provideKeyguardUpdateMonitorLogBuffer(LogBufferFactory factory) {
+ return factory.create("KeyguardUpdateMonitorLog", 200);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/StatusBarConnectivityLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/StatusBarConnectivityLog.java
new file mode 100644
index 0000000..f03fbcb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/StatusBarConnectivityLog.java
@@ -0,0 +1,36 @@
+/*
+ * 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 com.android.systemui.statusbar.pipeline.ConnectivityInfoProcessor;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Qualifier;
+
+/**
+ * A {@link LogBuffer} for events processed by {@link ConnectivityInfoProcessor}
+ */
+@Qualifier
+@Documented
+@Retention(RUNTIME)
+public @interface StatusBarConnectivityLog {
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt
index c9fce79..79e1fb9 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt
@@ -33,11 +33,13 @@
import android.view.accessibility.AccessibilityManager.FLAG_CONTENT_CONTROLS
import android.view.accessibility.AccessibilityManager.FLAG_CONTENT_ICONS
import android.view.accessibility.AccessibilityManager.FLAG_CONTENT_TEXT
+import androidx.annotation.CallSuper
import com.android.internal.widget.CachingIconView
import com.android.settingslib.Utils
import com.android.systemui.R
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.statusbar.gesture.TapGestureDetector
+import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.systemui.util.view.ViewUtil
@@ -52,17 +54,17 @@
* display the chip in a certain state, since they receive <T> in [updateChipView].
*/
abstract class MediaTttChipControllerCommon<T : ChipInfoCommon>(
- internal val context: Context,
- internal val logger: MediaTttLogger,
- internal val windowManager: WindowManager,
- private val viewUtil: ViewUtil,
- @Main private val mainExecutor: DelayableExecutor,
- private val accessibilityManager: AccessibilityManager,
- private val tapGestureDetector: TapGestureDetector,
- private val powerManager: PowerManager,
- @LayoutRes private val chipLayoutRes: Int
+ internal val context: Context,
+ internal val logger: MediaTttLogger,
+ internal val windowManager: WindowManager,
+ private val viewUtil: ViewUtil,
+ @Main private val mainExecutor: DelayableExecutor,
+ private val accessibilityManager: AccessibilityManager,
+ private val configurationController: ConfigurationController,
+ private val tapGestureDetector: TapGestureDetector,
+ private val powerManager: PowerManager,
+ @LayoutRes private val chipLayoutRes: Int,
) {
-
/**
* Window layout params that will be used as a starting point for the [windowLayoutParams] of
* all subclasses.
@@ -89,42 +91,40 @@
/** The chip view currently being displayed. Null if the chip is not being displayed. */
private var chipView: ViewGroup? = null
+ /** The chip info currently being displayed. Null if the chip is not being displayed. */
+ internal var chipInfo: T? = null
+
/** A [Runnable] that, when run, will cancel the pending timeout of the chip. */
private var cancelChipViewTimeout: Runnable? = null
/**
- * Displays the chip with the current state.
+ * Displays the chip with the provided [newChipInfo].
*
* This method handles inflating and attaching the view, then delegates to [updateChipView] to
* display the correct information in the chip.
*/
- fun displayChip(chipInfo: T) {
- val oldChipView = chipView
- if (chipView == null) {
- chipView = LayoutInflater
- .from(context)
- .inflate(chipLayoutRes, null) as ViewGroup
- }
- val currentChipView = chipView!!
+ fun displayChip(newChipInfo: T) {
+ val currentChipView = chipView
- updateChipView(chipInfo, currentChipView)
-
- // Add view if necessary
- if (oldChipView == null) {
+ if (currentChipView != null) {
+ updateChipView(newChipInfo, currentChipView)
+ } else {
+ // The chip is new, so set up all our callbacks and inflate the view
+ configurationController.addCallback(displayScaleListener)
tapGestureDetector.addOnGestureDetectedCallback(TAG, this::onScreenTapped)
- windowManager.addView(chipView, windowLayoutParams)
// Wake the screen so the user will see the chip
powerManager.wakeUp(
SystemClock.uptimeMillis(),
PowerManager.WAKE_REASON_APPLICATION,
"com.android.systemui:media_tap_to_transfer_activated"
)
- animateChipIn(currentChipView)
+
+ inflateAndUpdateChip(newChipInfo)
}
// Cancel and re-set the chip timeout each time we get a new state.
val timeout = accessibilityManager.getRecommendedTimeoutMillis(
- chipInfo.getTimeoutMs().toInt(),
+ newChipInfo.getTimeoutMs().toInt(),
// Not all chips have controls so FLAG_CONTENT_CONTROLS might be superfluous, but
// include it just to be safe.
FLAG_CONTENT_ICONS or FLAG_CONTENT_TEXT or FLAG_CONTENT_CONTROLS
@@ -136,6 +136,32 @@
)
}
+ /** Inflates a new chip view, updates it with [newChipInfo], and adds the view to the window. */
+ private fun inflateAndUpdateChip(newChipInfo: T) {
+ val newChipView = LayoutInflater
+ .from(context)
+ .inflate(chipLayoutRes, null) as ViewGroup
+ chipView = newChipView
+ updateChipView(newChipInfo, newChipView)
+ windowManager.addView(newChipView, windowLayoutParams)
+ animateChipIn(newChipView)
+ }
+
+ /** Removes then re-inflates the chip. */
+ private fun reinflateChip() {
+ val currentChipInfo = chipInfo
+ if (chipView == null || currentChipInfo == null) { return }
+
+ windowManager.removeView(chipView)
+ inflateAndUpdateChip(currentChipInfo)
+ }
+
+ private val displayScaleListener = object : ConfigurationController.ConfigurationListener {
+ override fun onDensityOrFontScaleChanged() {
+ reinflateChip()
+ }
+ }
+
/**
* Hides the chip.
*
@@ -145,17 +171,22 @@
open fun removeChip(removalReason: String) {
if (chipView == null) { return }
logger.logChipRemoval(removalReason)
+ configurationController.removeCallback(displayScaleListener)
tapGestureDetector.removeOnGestureDetectedCallback(TAG)
windowManager.removeView(chipView)
chipView = null
+ chipInfo = null
// No need to time the chip out since it's already gone
cancelChipViewTimeout?.run()
}
/**
- * A method implemented by subclasses to update [currentChipView] based on [chipInfo].
+ * A method implemented by subclasses to update [currentChipView] based on [newChipInfo].
*/
- abstract fun updateChipView(chipInfo: T, currentChipView: ViewGroup)
+ @CallSuper
+ open fun updateChipView(newChipInfo: T, currentChipView: ViewGroup) {
+ chipInfo = newChipInfo
+ }
/**
* A method that can be implemented by subclcasses to do custom animations for when the chip
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 99a5b8b..f0e5a3a 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
@@ -42,6 +42,7 @@
import com.android.systemui.media.taptotransfer.common.MediaTttLogger
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.gesture.TapGestureDetector
+import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.animation.AnimationUtil.Companion.frames
import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.systemui.util.view.ViewUtil
@@ -54,27 +55,29 @@
*/
@SysUISingleton
class MediaTttChipControllerReceiver @Inject constructor(
- commandQueue: CommandQueue,
- context: Context,
- @MediaTttReceiverLogger logger: MediaTttLogger,
- windowManager: WindowManager,
- viewUtil: ViewUtil,
- mainExecutor: DelayableExecutor,
- accessibilityManager: AccessibilityManager,
- tapGestureDetector: TapGestureDetector,
- powerManager: PowerManager,
- @Main private val mainHandler: Handler,
- private val uiEventLogger: MediaTttReceiverUiEventLogger,
+ commandQueue: CommandQueue,
+ context: Context,
+ @MediaTttReceiverLogger logger: MediaTttLogger,
+ windowManager: WindowManager,
+ viewUtil: ViewUtil,
+ mainExecutor: DelayableExecutor,
+ accessibilityManager: AccessibilityManager,
+ configurationController: ConfigurationController,
+ tapGestureDetector: TapGestureDetector,
+ powerManager: PowerManager,
+ @Main private val mainHandler: Handler,
+ private val uiEventLogger: MediaTttReceiverUiEventLogger,
) : MediaTttChipControllerCommon<ChipReceiverInfo>(
- context,
- logger,
- windowManager,
- viewUtil,
- mainExecutor,
- accessibilityManager,
- tapGestureDetector,
- powerManager,
- R.layout.media_ttt_chip_receiver
+ context,
+ logger,
+ windowManager,
+ viewUtil,
+ mainExecutor,
+ accessibilityManager,
+ configurationController,
+ tapGestureDetector,
+ powerManager,
+ R.layout.media_ttt_chip_receiver,
) {
@SuppressLint("WrongConstant") // We're allowed to use LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
override val windowLayoutParams = commonWindowLayoutParams.apply {
@@ -140,12 +143,13 @@
)
}
- override fun updateChipView(chipInfo: ChipReceiverInfo, currentChipView: ViewGroup) {
+ override fun updateChipView(newChipInfo: ChipReceiverInfo, currentChipView: ViewGroup) {
+ super.updateChipView(newChipInfo, currentChipView)
setIcon(
currentChipView,
- chipInfo.routeInfo.packageName,
- chipInfo.appIconDrawableOverride,
- chipInfo.appNameOverride
+ newChipInfo.routeInfo.packageName,
+ newChipInfo.appIconDrawableOverride,
+ newChipInfo.appNameOverride
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
index 797a770..540798a 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
@@ -39,6 +39,7 @@
import com.android.systemui.media.taptotransfer.common.MediaTttRemovalReason
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.gesture.TapGestureDetector
+import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.systemui.util.view.ViewUtil
import javax.inject.Inject
@@ -49,33 +50,33 @@
*/
@SysUISingleton
class MediaTttChipControllerSender @Inject constructor(
- commandQueue: CommandQueue,
- context: Context,
- @MediaTttSenderLogger logger: MediaTttLogger,
- windowManager: WindowManager,
- viewUtil: ViewUtil,
- @Main mainExecutor: DelayableExecutor,
- accessibilityManager: AccessibilityManager,
- tapGestureDetector: TapGestureDetector,
- powerManager: PowerManager,
- private val uiEventLogger: MediaTttSenderUiEventLogger
+ commandQueue: CommandQueue,
+ context: Context,
+ @MediaTttSenderLogger logger: MediaTttLogger,
+ windowManager: WindowManager,
+ viewUtil: ViewUtil,
+ @Main mainExecutor: DelayableExecutor,
+ accessibilityManager: AccessibilityManager,
+ configurationController: ConfigurationController,
+ tapGestureDetector: TapGestureDetector,
+ powerManager: PowerManager,
+ private val uiEventLogger: MediaTttSenderUiEventLogger
) : MediaTttChipControllerCommon<ChipSenderInfo>(
- context,
- logger,
- windowManager,
- viewUtil,
- mainExecutor,
- accessibilityManager,
- tapGestureDetector,
- powerManager,
- R.layout.media_ttt_chip
+ context,
+ logger,
+ windowManager,
+ viewUtil,
+ mainExecutor,
+ accessibilityManager,
+ configurationController,
+ tapGestureDetector,
+ powerManager,
+ R.layout.media_ttt_chip,
) {
override val windowLayoutParams = commonWindowLayoutParams.apply {
gravity = Gravity.TOP.or(Gravity.CENTER_HORIZONTAL)
}
- private var currentlyDisplayedChipState: ChipStateSender? = null
-
private val commandQueueCallbacks = object : CommandQueue.Callbacks {
override fun updateMediaTapToTransferSenderDisplay(
@StatusBarManager.MediaTransferSenderState displayState: Int,
@@ -116,16 +117,18 @@
/** Displays the chip view for the given state. */
override fun updateChipView(
- chipInfo: ChipSenderInfo,
- currentChipView: ViewGroup) {
- val chipState = chipInfo.state
- currentlyDisplayedChipState = chipState
+ newChipInfo: ChipSenderInfo,
+ currentChipView: ViewGroup
+ ) {
+ super.updateChipView(newChipInfo, currentChipView)
+
+ val chipState = newChipInfo.state
// App icon
- setIcon(currentChipView, chipInfo.routeInfo.packageName)
+ setIcon(currentChipView, newChipInfo.routeInfo.packageName)
// Text
- val otherDeviceName = chipInfo.routeInfo.name.toString()
+ val otherDeviceName = newChipInfo.routeInfo.name.toString()
currentChipView.requireViewById<TextView>(R.id.text).apply {
text = chipState.getChipTextString(context, otherDeviceName)
}
@@ -137,7 +140,7 @@
// Undo
val undoView = currentChipView.requireViewById<View>(R.id.undo)
val undoClickListener = chipState.undoClickListener(
- this, chipInfo.routeInfo, chipInfo.undoCallback, uiEventLogger
+ this, newChipInfo.routeInfo, newChipInfo.undoCallback, uiEventLogger
)
undoView.setOnClickListener(undoClickListener)
undoView.visibility = (undoClickListener != null).visibleIfTrue()
@@ -161,12 +164,11 @@
override fun removeChip(removalReason: String) {
// Don't remove the chip if we're mid-transfer since the user should still be able to
// see the status of the transfer. (But do remove it if it's finally timed out.)
- if (currentlyDisplayedChipState?.isMidTransfer == true
- && removalReason != MediaTttRemovalReason.REASON_TIMEOUT) {
+ if (chipInfo?.state?.isMidTransfer == true &&
+ removalReason != MediaTttRemovalReason.REASON_TIMEOUT) {
return
}
super.removeChip(removalReason)
- currentlyDisplayedChipState = null
}
private fun Boolean.visibleIfTrue(): Int {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
index 2a7c871..2d7a809 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
@@ -51,6 +51,8 @@
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.model.SysUiState;
import com.android.systemui.recents.OverviewProxyService;
import com.android.systemui.shared.system.QuickStepContract;
@@ -82,6 +84,7 @@
private final Context mContext;
private final Handler mHandler;
private final NavigationBarComponent.Factory mNavigationBarComponentFactory;
+ private FeatureFlags mFeatureFlags;
private final DisplayManager mDisplayManager;
private final TaskbarDelegate mTaskbarDelegate;
private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
@@ -113,10 +116,12 @@
AutoHideController autoHideController,
LightBarController lightBarController,
Optional<Pip> pipOptional,
- Optional<BackAnimation> backAnimation) {
+ Optional<BackAnimation> backAnimation,
+ FeatureFlags featureFlags) {
mContext = context;
mHandler = mainHandler;
mNavigationBarComponentFactory = navigationBarComponentFactory;
+ mFeatureFlags = featureFlags;
mDisplayManager = mContext.getSystemService(DisplayManager.class);
commandQueue.addCallback(this);
configurationController.addCallback(this);
@@ -217,7 +222,10 @@
/** @return {@code true} if taskbar is enabled, false otherwise */
private boolean initializeTaskbarIfNecessary() {
- if (mIsTablet) {
+ // Enable for tablet or (phone AND flag is set); assuming phone = !mIsTablet
+ boolean taskbarEnabled = mIsTablet || mFeatureFlags.isEnabled(Flags.HIDE_NAVBAR_WINDOW);
+
+ if (taskbarEnabled) {
Trace.beginSection("NavigationBarController#initializeTaskbarIfNecessary");
// Remove navigation bar when taskbar is showing
removeNavigationBar(mContext.getDisplayId());
@@ -226,7 +234,7 @@
} else {
mTaskbarDelegate.destroy();
}
- return mIsTablet;
+ return taskbarEnabled;
}
@Override
@@ -294,6 +302,10 @@
*/
@VisibleForTesting
void createNavigationBar(Display display, Bundle savedState, RegisterStatusBarResult result) {
+ if (initializeTaskbarIfNecessary()) {
+ return;
+ }
+
if (display == null) {
return;
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java b/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java
index 6908e5a..209d09d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java
@@ -243,7 +243,7 @@
mActivityStarter.postStartActivityDismissingKeyguard(
new Intent(Settings.ACTION_WIRELESS_SETTINGS), 0);
};
- view.setOnClickListener(onClickListener);
+
mNoSimTextView = view.getNoSimTextView();
mNoSimTextView.setOnClickListener(onClickListener);
mMainHandler = new H(mainLooper, this::handleUpdateCarrierInfo, this::handleUpdateState);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 24448bb..58e9bb8 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -128,6 +128,10 @@
import com.android.systemui.fragments.FragmentHostManager.FragmentListener;
import com.android.systemui.fragments.FragmentService;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
+import com.android.systemui.keyguard.domain.usecase.SetClockPositionUseCase;
+import com.android.systemui.keyguard.domain.usecase.SetKeyguardBottomAreaAlphaUseCase;
+import com.android.systemui.keyguard.domain.usecase.SetKeyguardBottomAreaAnimateDozingTransitionsUseCase;
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel;
import com.android.systemui.media.KeyguardMediaController;
import com.android.systemui.media.MediaDataManager;
import com.android.systemui.media.MediaHierarchyManager;
@@ -697,6 +701,12 @@
};
private final CameraGestureHelper mCameraGestureHelper;
+ private final Provider<KeyguardBottomAreaViewModel> mKeyguardBottomAreaViewModelProvider;
+ private final Provider<SetClockPositionUseCase> mSetClockPositionUseCaseProvider;
+ private final Provider<SetKeyguardBottomAreaAlphaUseCase>
+ mSetKeyguardBottomAreaAlphaUseCaseProvider;
+ private final Provider<SetKeyguardBottomAreaAnimateDozingTransitionsUseCase>
+ mSetKeyguardBottomAreaAnimateDozingTransitionsUseCaseProvider;
@Inject
public NotificationPanelViewController(NotificationPanelView view,
@@ -767,7 +777,12 @@
UnlockedScreenOffAnimationController unlockedScreenOffAnimationController,
ShadeTransitionController shadeTransitionController,
SystemClock systemClock,
- CameraGestureHelper cameraGestureHelper) {
+ CameraGestureHelper cameraGestureHelper,
+ Provider<KeyguardBottomAreaViewModel> keyguardBottomAreaViewModelProvider,
+ Provider<SetClockPositionUseCase> setClockPositionUseCaseProvider,
+ Provider<SetKeyguardBottomAreaAlphaUseCase> setKeyguardBottomAreaAlphaUseCaseProvider,
+ Provider<SetKeyguardBottomAreaAnimateDozingTransitionsUseCase>
+ setKeyguardBottomAreaAnimateDozingTransitionsUseCaseProvider) {
super(view,
falsingManager,
dozeLog,
@@ -897,6 +912,7 @@
mQsFrameTranslateController = qsFrameTranslateController;
updateUserSwitcherFlags();
+ mKeyguardBottomAreaViewModelProvider = keyguardBottomAreaViewModelProvider;
onFinishInflate();
keyguardUnlockAnimationController.addKeyguardUnlockAnimationListener(
new KeyguardUnlockAnimationController.KeyguardUnlockAnimationListener() {
@@ -950,6 +966,10 @@
}
});
mCameraGestureHelper = cameraGestureHelper;
+ mSetClockPositionUseCaseProvider = setClockPositionUseCaseProvider;
+ mSetKeyguardBottomAreaAlphaUseCaseProvider = setKeyguardBottomAreaAlphaUseCaseProvider;
+ mSetKeyguardBottomAreaAnimateDozingTransitionsUseCaseProvider =
+ setKeyguardBottomAreaAnimateDozingTransitionsUseCaseProvider;
}
@VisibleForTesting
@@ -1274,11 +1294,17 @@
}
private void initBottomArea() {
- mKeyguardBottomArea.init(
- mFalsingManager,
- mQuickAccessWalletController,
- mControlsComponent,
- mQRCodeScannerController);
+ if (mFeatureFlags.isEnabled(Flags.MODERN_BOTTOM_AREA)) {
+ mKeyguardBottomArea.init(mKeyguardBottomAreaViewModelProvider.get(), mFalsingManager);
+ } else {
+ // TODO(b/235403546): remove this method call when the new implementation is complete
+ // and these are not needed.
+ mKeyguardBottomArea.init(
+ mFalsingManager,
+ mQuickAccessWalletController,
+ mControlsComponent,
+ mQRCodeScannerController);
+ }
}
@VisibleForTesting
@@ -1454,6 +1480,8 @@
mKeyguardStatusViewController.getClockBottom(mStatusBarHeaderHeightKeyguard),
mKeyguardStatusViewController.isClockTopAligned());
mClockPositionAlgorithm.run(mClockPositionResult);
+ mSetClockPositionUseCaseProvider.get().invoke(
+ mClockPositionResult.clockX, mClockPositionResult.clockY);
boolean animate = mNotificationStackScrollLayoutController.isAddOrRemoveAnimationPending();
boolean animateClock = (animate || mAnimateNextPositionUpdate) && shouldAnimateClockChange;
mKeyguardStatusViewController.updatePosition(
@@ -3220,6 +3248,7 @@
float alpha = Math.min(expansionAlpha, 1 - computeQsExpansionFraction());
alpha *= mBottomAreaShadeAlpha;
mKeyguardBottomArea.setComponentAlphas(alpha);
+ mSetKeyguardBottomAreaAlphaUseCaseProvider.get().invoke(alpha);
mLockIconViewController.setAlpha(alpha);
}
@@ -3419,6 +3448,7 @@
private void updateDozingVisibilities(boolean animate) {
mKeyguardBottomArea.setDozing(mDozing, animate);
+ mSetKeyguardBottomAreaAnimateDozingTransitionsUseCaseProvider.get().invoke(animate);
if (!mDozing && animate) {
mKeyguardStatusBarViewController.animateKeyguardStatusBarIn();
}
@@ -3721,6 +3751,7 @@
mDozing = dozing;
mNotificationStackScrollLayoutController.setDozing(mDozing, animate, wakeUpTouchLocation);
mKeyguardBottomArea.setDozing(mDozing, animate);
+ mSetKeyguardBottomAreaAnimateDozingTransitionsUseCaseProvider.get().invoke(animate);
mKeyguardStatusBarViewController.setDozing(mDozing);
if (dozing) {
@@ -3850,29 +3881,37 @@
}
/**
- * Starts fold to AOD animation
+ * Starts fold to AOD animation.
+ *
+ * @param startAction invoked when the animation starts.
+ * @param endAction invoked when the animation finishes, also if it was cancelled.
+ * @param cancelAction invoked when the animation is cancelled, before endAction.
*/
- public void startFoldToAodAnimation(Runnable endAction) {
+ public void startFoldToAodAnimation(Runnable startAction, Runnable endAction,
+ Runnable cancelAction) {
mView.animate()
- .translationX(0)
- .alpha(1f)
- .setDuration(ANIMATION_DURATION_FOLD_TO_AOD)
- .setInterpolator(EMPHASIZED_DECELERATE)
- .setListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationCancel(Animator animation) {
- endAction.run();
- }
+ .translationX(0)
+ .alpha(1f)
+ .setDuration(ANIMATION_DURATION_FOLD_TO_AOD)
+ .setInterpolator(EMPHASIZED_DECELERATE)
+ .setListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ startAction.run();
+ }
- @Override
- public void onAnimationEnd(Animator animation) {
- endAction.run();
- }
- })
- .setUpdateListener(anim -> {
- mKeyguardStatusViewController.animateFoldToAod(anim.getAnimatedFraction());
- })
- .start();
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ cancelAction.run();
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ endAction.run();
+ }
+ }).setUpdateListener(anim -> {
+ mKeyguardStatusViewController.animateFoldToAod(anim.getAnimatedFraction());
+ }).start();
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
index 4b71b2c..15e1129 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
@@ -44,8 +44,6 @@
import android.view.WindowManager.LayoutParams;
import android.view.WindowManagerGlobal;
-import androidx.lifecycle.ViewTreeLifecycleOwner;
-
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.Dumpable;
import com.android.systemui.R;
@@ -54,7 +52,6 @@
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.KeyguardViewMediator;
-import com.android.systemui.lifecycle.WindowAddedViewLifecycleOwner;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
import com.android.systemui.statusbar.NotificationShadeWindowController;
@@ -251,15 +248,6 @@
mWindowManager.addView(mNotificationShadeView, mLp);
- // Set up and "inject" a LifecycleOwner bound to the Window-View relationship such that all
- // views in the sub-tree rooted under this view can access the LifecycleOwner using
- // ViewTreeLifecycleOwner.get(...).
- if (ViewTreeLifecycleOwner.get(mNotificationShadeView) == null) {
- ViewTreeLifecycleOwner.set(
- mNotificationShadeView,
- new WindowAddedViewLifecycleOwner(mNotificationShadeView));
- }
-
mLpChanged.copyFrom(mLp);
onThemeChanged();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java
index 3b3b5a2..cb414ba 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java
@@ -50,16 +50,6 @@
boolean isDeviceInVrMode();
/**
- * Updates the visual representation of the notifications.
- */
- void updateNotificationViews(String reason);
-
- /**
- * Called when the row states are updated by {@link NotificationViewHierarchyManager}.
- */
- void onUpdateRowStates();
-
- /**
* @return true if the shade is collapsing.
*/
boolean isCollapsing();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
deleted file mode 100644
index 054543c..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
+++ /dev/null
@@ -1,631 +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.statusbar;
-
-import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_SILENT;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.os.Handler;
-import android.os.Trace;
-import android.os.UserHandle;
-import android.util.Log;
-import android.view.View;
-import android.view.ViewGroup;
-
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.systemui.R;
-import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.statusbar.dagger.CentralSurfacesModule;
-import com.android.systemui.statusbar.notification.AssistantFeedbackController;
-import com.android.systemui.statusbar.notification.DynamicChildBindController;
-import com.android.systemui.statusbar.notification.DynamicPrivacyController;
-import com.android.systemui.statusbar.notification.NotifPipelineFlags;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.legacy.LowPriorityInflationHelper;
-import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
-import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
-import com.android.systemui.statusbar.notification.collection.render.NotifStackController;
-import com.android.systemui.statusbar.notification.collection.render.NotifStats;
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
-import com.android.systemui.statusbar.phone.KeyguardBypassController;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.util.Assert;
-import com.android.wm.shell.bubbles.Bubbles;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Optional;
-import java.util.Stack;
-
-/**
- * NotificationViewHierarchyManager manages updating the view hierarchy of notification views based
- * on their group structure. For example, if a notification becomes bundled with another,
- * NotificationViewHierarchyManager will update the view hierarchy to reflect that. It also will
- * tell NotificationListContainer which notifications to display, and inform it of changes to those
- * notifications that might affect their display.
- */
-public class NotificationViewHierarchyManager implements DynamicPrivacyController.Listener {
- private static final String TAG = "NotificationViewHierarchyManager";
-
- private final Handler mHandler;
-
- /**
- * Re-usable map of top-level notifications to their sorted children if any.
- * If the top-level notification doesn't have children, its key will still exist in this map
- * with its value explicitly set to null.
- */
- private final HashMap<NotificationEntry, List<NotificationEntry>> mTmpChildOrderMap =
- new HashMap<>();
-
- // Dependencies:
- private final DynamicChildBindController mDynamicChildBindController;
- private final FeatureFlags mFeatureFlags;
- protected final NotificationLockscreenUserManager mLockscreenUserManager;
- protected final NotificationGroupManagerLegacy mGroupManager;
- protected final VisualStabilityManager mVisualStabilityManager;
- private final SysuiStatusBarStateController mStatusBarStateController;
- private final NotificationEntryManager mEntryManager;
- private final LowPriorityInflationHelper mLowPriorityInflationHelper;
-
- /**
- * {@code true} if notifications not part of a group should by default be rendered in their
- * expanded state. If {@code false}, then only the first notification will be expanded if
- * possible.
- */
- private final boolean mAlwaysExpandNonGroupedNotification;
- private final Optional<Bubbles> mBubblesOptional;
- private final DynamicPrivacyController mDynamicPrivacyController;
- private final KeyguardBypassController mBypassController;
- private final NotifPipelineFlags mNotifPipelineFlags;
- private AssistantFeedbackController mAssistantFeedbackController;
- private final KeyguardStateController mKeyguardStateController;
- private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
- private final Context mContext;
-
- private NotificationPresenter mPresenter;
- private NotifStackController mStackController;
- private NotificationListContainer mListContainer;
-
- // Used to help track down re-entrant calls to our update methods, which will cause bugs.
- private boolean mPerformingUpdate;
- // Hack to get around re-entrant call in onDynamicPrivacyChanged() until we can track down
- // the problem.
- private boolean mIsHandleDynamicPrivacyChangeScheduled;
-
- /**
- * Injected constructor. See {@link CentralSurfacesModule}.
- */
- public NotificationViewHierarchyManager(
- Context context,
- @Main Handler mainHandler,
- FeatureFlags featureFlags,
- NotificationLockscreenUserManager notificationLockscreenUserManager,
- NotificationGroupManagerLegacy groupManager,
- VisualStabilityManager visualStabilityManager,
- StatusBarStateController statusBarStateController,
- NotificationEntryManager notificationEntryManager,
- KeyguardBypassController bypassController,
- Optional<Bubbles> bubblesOptional,
- DynamicPrivacyController privacyController,
- DynamicChildBindController dynamicChildBindController,
- LowPriorityInflationHelper lowPriorityInflationHelper,
- AssistantFeedbackController assistantFeedbackController,
- NotifPipelineFlags notifPipelineFlags,
- KeyguardUpdateMonitor keyguardUpdateMonitor,
- KeyguardStateController keyguardStateController) {
- mContext = context;
- mHandler = mainHandler;
- mFeatureFlags = featureFlags;
- mLockscreenUserManager = notificationLockscreenUserManager;
- mBypassController = bypassController;
- mGroupManager = groupManager;
- mVisualStabilityManager = visualStabilityManager;
- mStatusBarStateController = (SysuiStatusBarStateController) statusBarStateController;
- mEntryManager = notificationEntryManager;
- mNotifPipelineFlags = notifPipelineFlags;
- Resources res = context.getResources();
- mAlwaysExpandNonGroupedNotification =
- res.getBoolean(R.bool.config_alwaysExpandNonGroupedNotifications);
- mBubblesOptional = bubblesOptional;
- mDynamicPrivacyController = privacyController;
- mDynamicChildBindController = dynamicChildBindController;
- mLowPriorityInflationHelper = lowPriorityInflationHelper;
- mAssistantFeedbackController = assistantFeedbackController;
- mKeyguardUpdateMonitor = keyguardUpdateMonitor;
- mKeyguardStateController = keyguardStateController;
- }
-
- public void setUpWithPresenter(NotificationPresenter presenter,
- NotifStackController stackController,
- NotificationListContainer listContainer) {
- mPresenter = presenter;
- mStackController = stackController;
- mListContainer = listContainer;
- if (!mNotifPipelineFlags.isNewPipelineEnabled()) {
- mDynamicPrivacyController.addListener(this);
- }
- }
-
- /**
- * Updates the visual representation of the notifications.
- */
- //TODO: Rewrite this to focus on Entries, or some other data object instead of views
- public void updateNotificationViews() {
- Assert.isMainThread();
- if (!mNotifPipelineFlags.checkLegacyPipelineEnabled()) {
- return;
- }
- Trace.beginSection("NotificationViewHierarchyManager.updateNotificationViews");
-
- beginUpdate();
-
- boolean dynamicallyUnlocked = mDynamicPrivacyController.isDynamicallyUnlocked()
- && !(mStatusBarStateController.getState() == StatusBarState.KEYGUARD
- && mKeyguardUpdateMonitor.getUserUnlockedWithBiometricAndIsBypassing(
- KeyguardUpdateMonitor.getCurrentUser()))
- && !mKeyguardStateController.isKeyguardGoingAway();
- List<NotificationEntry> activeNotifications = mEntryManager.getVisibleNotifications();
- ArrayList<ExpandableNotificationRow> toShow = new ArrayList<>(activeNotifications.size());
- final int N = activeNotifications.size();
- for (int i = 0; i < N; i++) {
- NotificationEntry ent = activeNotifications.get(i);
- if (shouldSuppressActiveNotification(ent)) {
- continue;
- }
-
- int userId = ent.getSbn().getUserId();
-
- // Display public version of the notification if we need to redact.
- // TODO: This area uses a lot of calls into NotificationLockscreenUserManager.
- // We can probably move some of this code there.
- int currentUserId = mLockscreenUserManager.getCurrentUserId();
- boolean devicePublic = mLockscreenUserManager.isLockscreenPublicMode(currentUserId);
- boolean userPublic = devicePublic
- || mLockscreenUserManager.isLockscreenPublicMode(userId);
- if (userPublic && dynamicallyUnlocked
- && (userId == currentUserId || userId == UserHandle.USER_ALL
- || !mLockscreenUserManager.needsSeparateWorkChallenge(userId))) {
- userPublic = false;
- }
- boolean needsRedaction = mLockscreenUserManager.needsRedaction(ent);
- boolean sensitive = userPublic && needsRedaction;
- boolean deviceSensitive = devicePublic
- && !mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(
- currentUserId);
- ent.setSensitive(sensitive, deviceSensitive);
- ent.getRow().setNeedsRedaction(needsRedaction);
- mLowPriorityInflationHelper.recheckLowPriorityViewAndInflate(ent, ent.getRow());
- boolean isChildInGroup = mGroupManager.isChildInGroup(ent);
-
- boolean groupChangesAllowed =
- mVisualStabilityManager.areGroupChangesAllowed() // user isn't looking at notifs
- || !ent.hasFinishedInitialization(); // notif recently added
-
- NotificationEntry parent = mGroupManager.getGroupSummary(ent);
- if (!groupChangesAllowed) {
- // We don't to change groups while the user is looking at them
- boolean wasChildInGroup = ent.isChildInGroup();
- if (isChildInGroup && !wasChildInGroup) {
- isChildInGroup = wasChildInGroup;
- mVisualStabilityManager.addGroupChangesAllowedCallback(mEntryManager,
- false /* persistent */);
- } else if (!isChildInGroup && wasChildInGroup) {
- // We allow grouping changes if the group was collapsed
- if (mGroupManager.isLogicalGroupExpanded(ent.getSbn())) {
- isChildInGroup = wasChildInGroup;
- parent = ent.getRow().getNotificationParent().getEntry();
- mVisualStabilityManager.addGroupChangesAllowedCallback(mEntryManager,
- false /* persistent */);
- }
- }
- }
-
- if (isChildInGroup) {
- List<NotificationEntry> orderedChildren = mTmpChildOrderMap.get(parent);
- if (orderedChildren == null) {
- orderedChildren = new ArrayList<>();
- mTmpChildOrderMap.put(parent, orderedChildren);
- }
- orderedChildren.add(ent);
- } else {
- // Top-level notif (either a summary or single notification)
-
- // A child may have already added its summary to mTmpChildOrderMap with a
- // list of children. This can happen since there's no guarantee summaries are
- // sorted before its children.
- if (!mTmpChildOrderMap.containsKey(ent)) {
- // mTmpChildOrderMap's keyset is used to iterate through all entries, so it's
- // necessary to add each top-level notif as a key
- mTmpChildOrderMap.put(ent, null);
- }
- toShow.add(ent.getRow());
- }
-
- }
-
- ArrayList<ExpandableNotificationRow> viewsToRemove = new ArrayList<>();
- for (int i=0; i< mListContainer.getContainerChildCount(); i++) {
- View child = mListContainer.getContainerChildAt(i);
- if (!toShow.contains(child) && child instanceof ExpandableNotificationRow) {
- ExpandableNotificationRow row = (ExpandableNotificationRow) child;
-
- // Blocking helper is effectively a detached view. Don't bother removing it from the
- // layout.
- if (!row.isBlockingHelperShowing()) {
- viewsToRemove.add((ExpandableNotificationRow) child);
- }
- }
- }
-
- for (ExpandableNotificationRow viewToRemove : viewsToRemove) {
- NotificationEntry entry = viewToRemove.getEntry();
- if (mEntryManager.getPendingOrActiveNotif(entry.getKey()) != null
- && !shouldSuppressActiveNotification(entry)) {
- // we are only transferring this notification to its parent, don't generate an
- // animation. If the notification is suppressed, this isn't a transfer.
- mListContainer.setChildTransferInProgress(true);
- }
- if (viewToRemove.isSummaryWithChildren()) {
- viewToRemove.removeAllChildren();
- }
- mListContainer.removeContainerView(viewToRemove);
- mListContainer.setChildTransferInProgress(false);
- }
-
- removeNotificationChildren();
-
- for (int i = 0; i < toShow.size(); i++) {
- View v = toShow.get(i);
- if (v.getParent() == null) {
- mVisualStabilityManager.notifyViewAddition(v);
- mListContainer.addContainerView(v);
- } else if (!mListContainer.containsView(v)) {
- // the view is added somewhere else. Let's make sure
- // the ordering works properly below, by excluding these
- toShow.remove(v);
- i--;
- }
- }
-
- addNotificationChildrenAndSort();
-
- // So after all this work notifications still aren't sorted correctly.
- // Let's do that now by advancing through toShow and mListContainer in
- // lock-step, making sure mListContainer matches what we see in toShow.
- int j = 0;
- for (int i = 0; i < mListContainer.getContainerChildCount(); i++) {
- View child = mListContainer.getContainerChildAt(i);
- if (!(child instanceof ExpandableNotificationRow)) {
- // We don't care about non-notification views.
- continue;
- }
- if (((ExpandableNotificationRow) child).isBlockingHelperShowing()) {
- // Don't count/reorder notifications that are showing the blocking helper!
- continue;
- }
-
- ExpandableNotificationRow targetChild = toShow.get(j);
- if (child != targetChild) {
- // Oops, wrong notification at this position. Put the right one
- // here and advance both lists.
- if (mVisualStabilityManager.canReorderNotification(targetChild)) {
- mListContainer.changeViewPosition(targetChild, i);
- } else {
- mVisualStabilityManager.addReorderingAllowedCallback(mEntryManager,
- false /* persistent */);
- }
- }
- j++;
-
- }
-
- mDynamicChildBindController.updateContentViews(mTmpChildOrderMap);
- mVisualStabilityManager.onReorderingFinished();
- // clear the map again for the next usage
- mTmpChildOrderMap.clear();
-
- updateRowStatesInternal();
- updateNotifStats();
-
- mListContainer.onNotificationViewUpdateFinished();
-
- endUpdate();
- Trace.endSection();
- }
-
- /**
- * In the spirit of unidirectional data flow, calculate this information when the notification
- * views are updated, and set it once, speeding up lookups later.
- * This is analogous to logic in the
- * {@link com.android.systemui.statusbar.notification.collection.coordinator.StackCoordinator}
- */
- private void updateNotifStats() {
- Trace.beginSection("NotificationViewHierarchyManager.updateNotifStats");
- boolean hasNonClearableAlertingNotifs = false;
- boolean hasClearableAlertingNotifs = false;
- boolean hasNonClearableSilentNotifs = false;
- boolean hasClearableSilentNotifs = false;
- final int childCount = mListContainer.getContainerChildCount();
- int visibleTopLevelEntries = 0;
- for (int i = 0; i < childCount; i++) {
- View child = mListContainer.getContainerChildAt(i);
- if (child == null || child.getVisibility() == View.GONE) {
- continue;
- }
- if (!(child instanceof ExpandableNotificationRow)) {
- continue;
- }
- final ExpandableNotificationRow row = (ExpandableNotificationRow) child;
- boolean isSilent = row.getEntry().getBucket() == BUCKET_SILENT;
- // NOTE: NotificationEntry.isClearable() will internally check group children to ensure
- // the group itself definitively clearable.
- boolean isClearable = row.getEntry().isClearable();
- visibleTopLevelEntries++;
- if (isSilent) {
- if (isClearable) {
- hasClearableSilentNotifs = true;
- } else { // !isClearable
- hasNonClearableSilentNotifs = true;
- }
- } else { // !isSilent
- if (isClearable) {
- hasClearableAlertingNotifs = true;
- } else { // !isClearable
- hasNonClearableAlertingNotifs = true;
- }
- }
- }
- mStackController.setNotifStats(new NotifStats(
- visibleTopLevelEntries /* numActiveNotifs */,
- hasNonClearableAlertingNotifs /* hasNonClearableAlertingNotifs */,
- hasClearableAlertingNotifs /* hasClearableAlertingNotifs */,
- hasNonClearableSilentNotifs /* hasNonClearableSilentNotifs */,
- hasClearableSilentNotifs /* hasClearableSilentNotifs */
- ));
- Trace.endSection();
- }
-
- /**
- * Should a notification entry from the active list be suppressed and not show?
- */
- private boolean shouldSuppressActiveNotification(NotificationEntry ent) {
- final boolean isBubbleNotificationSuppressedFromShade = mBubblesOptional.isPresent()
- && mBubblesOptional.get().isBubbleNotificationSuppressedFromShade(
- ent.getKey(), ent.getSbn().getGroupKey());
- if (ent.isRowDismissed() || ent.isRowRemoved()
- || isBubbleNotificationSuppressedFromShade) {
- // we want to suppress removed notifications because they could
- // temporarily become children if they were isolated before.
- return true;
- }
- return false;
- }
-
- private void addNotificationChildrenAndSort() {
- // Let's now add all notification children which are missing
- boolean orderChanged = false;
- ArrayList<ExpandableNotificationRow> orderedRows = new ArrayList<>();
- for (int i = 0; i < mListContainer.getContainerChildCount(); i++) {
- View view = mListContainer.getContainerChildAt(i);
- if (!(view instanceof ExpandableNotificationRow)) {
- // We don't care about non-notification views.
- continue;
- }
-
- ExpandableNotificationRow parent = (ExpandableNotificationRow) view;
- List<ExpandableNotificationRow> children = parent.getAttachedChildren();
- List<NotificationEntry> orderedChildren = mTmpChildOrderMap.get(parent.getEntry());
- if (orderedChildren == null) {
- // Not a group
- continue;
- }
- parent.setUntruncatedChildCount(orderedChildren.size());
- for (int childIndex = 0; childIndex < orderedChildren.size(); childIndex++) {
- ExpandableNotificationRow childView = orderedChildren.get(childIndex).getRow();
- if (children == null || !children.contains(childView)) {
- if (childView.getParent() != null) {
- Log.wtf(TAG, "trying to add a notification child that already has "
- + "a parent. class:" + childView.getParent().getClass()
- + "\n child: " + childView);
- // This shouldn't happen. We can recover by removing it though.
- ((ViewGroup) childView.getParent()).removeView(childView);
- }
- mVisualStabilityManager.notifyViewAddition(childView);
- parent.addChildNotification(childView, childIndex);
- mListContainer.notifyGroupChildAdded(childView);
- }
- orderedRows.add(childView);
- }
-
- // Finally after removing and adding has been performed we can apply the order.
- orderChanged |= parent.applyChildOrder(orderedRows, mVisualStabilityManager,
- mEntryManager);
- orderedRows.clear();
- }
- if (orderChanged) {
- mListContainer.generateChildOrderChangedEvent();
- }
- }
-
- private void removeNotificationChildren() {
- // First let's remove all children which don't belong in the parents
- ArrayList<ExpandableNotificationRow> toRemove = new ArrayList<>();
- for (int i = 0; i < mListContainer.getContainerChildCount(); i++) {
- View view = mListContainer.getContainerChildAt(i);
- if (!(view instanceof ExpandableNotificationRow)) {
- // We don't care about non-notification views.
- continue;
- }
-
- ExpandableNotificationRow parent = (ExpandableNotificationRow) view;
- List<ExpandableNotificationRow> children = parent.getAttachedChildren();
- List<NotificationEntry> orderedChildren = mTmpChildOrderMap.get(parent.getEntry());
-
- if (children != null) {
- toRemove.clear();
- for (ExpandableNotificationRow childRow : children) {
- if ((orderedChildren == null
- || !orderedChildren.contains(childRow.getEntry()))
- && !childRow.keepInParent()) {
- toRemove.add(childRow);
- }
- }
- for (ExpandableNotificationRow remove : toRemove) {
- parent.removeChildNotification(remove);
- if (mEntryManager.getActiveNotificationUnfiltered(
- remove.getEntry().getSbn().getKey()) == null) {
- // We only want to add an animation if the view is completely removed
- // otherwise it's just a transfer
- mListContainer.notifyGroupChildRemoved(remove,
- parent.getChildrenContainer());
- }
- }
- }
- }
- }
-
- /**
- * Updates expanded, dimmed and locked states of notification rows.
- */
- public void updateRowStates() {
- Assert.isMainThread();
- if (!mNotifPipelineFlags.checkLegacyPipelineEnabled()) {
- return;
- }
-
- beginUpdate();
- updateRowStatesInternal();
- endUpdate();
- }
-
- private void updateRowStatesInternal() {
- Trace.beginSection("NotificationViewHierarchyManager.updateRowStates");
- final int N = mListContainer.getContainerChildCount();
-
- int visibleNotifications = 0;
- boolean onKeyguard =
- mStatusBarStateController.getCurrentOrUpcomingState() == StatusBarState.KEYGUARD;
- Stack<ExpandableNotificationRow> stack = new Stack<>();
- for (int i = N - 1; i >= 0; i--) {
- View child = mListContainer.getContainerChildAt(i);
- if (!(child instanceof ExpandableNotificationRow)) {
- continue;
- }
- stack.push((ExpandableNotificationRow) child);
- }
- while(!stack.isEmpty()) {
- ExpandableNotificationRow row = stack.pop();
- NotificationEntry entry = row.getEntry();
- boolean isChildNotification = mGroupManager.isChildInGroup(entry);
-
- if (!onKeyguard) {
- // If mAlwaysExpandNonGroupedNotification is false, then only expand the
- // very first notification and if it's not a child of grouped notifications.
- row.setSystemExpanded(mAlwaysExpandNonGroupedNotification
- || (visibleNotifications == 0 && !isChildNotification
- && !row.isLowPriority()));
- }
-
- int userId = entry.getSbn().getUserId();
- boolean suppressedSummary = mGroupManager.isSummaryOfSuppressedGroup(
- entry.getSbn()) && !entry.isRowRemoved();
- boolean showOnKeyguard = mLockscreenUserManager.shouldShowOnKeyguard(entry);
- if (!showOnKeyguard) {
- // min priority notifications should show if their summary is showing
- if (mGroupManager.isChildInGroup(entry)) {
- NotificationEntry summary = mGroupManager.getLogicalGroupSummary(entry);
- if (summary != null && mLockscreenUserManager.shouldShowOnKeyguard(summary)) {
- showOnKeyguard = true;
- }
- }
- }
- if (suppressedSummary
- || mLockscreenUserManager.shouldHideNotifications(userId)
- || (onKeyguard && !showOnKeyguard)) {
- entry.getRow().setVisibility(View.GONE);
- } else {
- boolean wasGone = entry.getRow().getVisibility() == View.GONE;
- if (wasGone) {
- entry.getRow().setVisibility(View.VISIBLE);
- }
- if (!isChildNotification && !entry.getRow().isRemoved()) {
- if (wasGone) {
- // notify the scroller of a child addition
- mListContainer.generateAddAnimation(entry.getRow(),
- !showOnKeyguard /* fromMoreCard */);
- }
- visibleNotifications++;
- }
- }
- if (row.isSummaryWithChildren()) {
- List<ExpandableNotificationRow> notificationChildren =
- row.getAttachedChildren();
- int size = notificationChildren.size();
- for (int i = size - 1; i >= 0; i--) {
- stack.push(notificationChildren.get(i));
- }
- }
- row.setFeedbackIcon(mAssistantFeedbackController.getFeedbackIcon(entry));
- row.setLastAudiblyAlertedMs(entry.getLastAudiblyAlertedMs());
- }
-
- Trace.beginSection("NotificationPresenter#onUpdateRowStates");
- mPresenter.onUpdateRowStates();
- Trace.endSection();
- Trace.endSection();
- }
-
- @Override
- public void onDynamicPrivacyChanged() {
- mNotifPipelineFlags.assertLegacyPipelineEnabled();
- if (mPerformingUpdate) {
- Log.w(TAG, "onDynamicPrivacyChanged made a re-entrant call");
- }
- // This listener can be called from updateNotificationViews() via a convoluted listener
- // chain, so we post here to prevent a re-entrant call. See b/136186188
- // TODO: Refactor away the need for this
- if (!mIsHandleDynamicPrivacyChangeScheduled) {
- mIsHandleDynamicPrivacyChangeScheduled = true;
- mHandler.post(this::onHandleDynamicPrivacyChanged);
- }
- }
-
- private void onHandleDynamicPrivacyChanged() {
- mIsHandleDynamicPrivacyChangeScheduled = false;
- updateNotificationViews();
- }
-
- private void beginUpdate() {
- if (mPerformingUpdate) {
- Log.wtf(TAG, "Re-entrant code during update", new Exception());
- }
- mPerformingUpdate = true;
- }
-
- private void endUpdate() {
- if (!mPerformingUpdate) {
- Log.wtf(TAG, "Manager state has become desynced", new Exception());
- }
- mPerformingUpdate = false;
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
index 9e77dbc..48e3450 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
@@ -23,13 +23,11 @@
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.statusbar.IStatusBarService;
-import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.media.MediaDataManager;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -42,23 +40,16 @@
import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.NotificationShadeWindowController;
-import com.android.systemui.statusbar.NotificationViewHierarchyManager;
import com.android.systemui.statusbar.RemoteInputNotificationRebuilder;
import com.android.systemui.statusbar.SmartReplyController;
import com.android.systemui.statusbar.StatusBarStateControllerImpl;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.commandline.CommandRegistry;
import com.android.systemui.statusbar.gesture.SwipeStatusBarAwayGestureHandler;
-import com.android.systemui.statusbar.notification.AssistantFeedbackController;
-import com.android.systemui.statusbar.notification.DynamicChildBindController;
-import com.android.systemui.statusbar.notification.DynamicPrivacyController;
import com.android.systemui.statusbar.notification.NotifPipelineFlags;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.collection.NotifCollection;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
-import com.android.systemui.statusbar.notification.collection.legacy.LowPriorityInflationHelper;
-import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
-import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
import com.android.systemui.statusbar.phone.CentralSurfaces;
@@ -73,13 +64,11 @@
import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController;
import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallFlags;
import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallLogger;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.RemoteInputUriController;
import com.android.systemui.statusbar.window.StatusBarWindowController;
import com.android.systemui.tracing.ProtoTracer;
import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.time.SystemClock;
-import com.android.wm.shell.bubbles.Bubbles;
import java.util.Optional;
import java.util.concurrent.Executor;
@@ -182,47 +171,6 @@
NotificationRemoteInputManager.Callback provideNotificationRemoteInputManagerCallback(
StatusBarRemoteInputCallback callbackImpl);
- /** */
- @SysUISingleton
- @Provides
- static NotificationViewHierarchyManager provideNotificationViewHierarchyManager(
- Context context,
- @Main Handler mainHandler,
- FeatureFlags featureFlags,
- NotificationLockscreenUserManager notificationLockscreenUserManager,
- NotificationGroupManagerLegacy groupManager,
- VisualStabilityManager visualStabilityManager,
- StatusBarStateController statusBarStateController,
- NotificationEntryManager notificationEntryManager,
- KeyguardBypassController bypassController,
- Optional<Bubbles> bubblesOptional,
- DynamicPrivacyController privacyController,
- DynamicChildBindController dynamicChildBindController,
- LowPriorityInflationHelper lowPriorityInflationHelper,
- AssistantFeedbackController assistantFeedbackController,
- NotifPipelineFlags notifPipelineFlags,
- KeyguardUpdateMonitor keyguardUpdateMonitor,
- KeyguardStateController keyguardStateController) {
- return new NotificationViewHierarchyManager(
- context,
- mainHandler,
- featureFlags,
- notificationLockscreenUserManager,
- groupManager,
- visualStabilityManager,
- statusBarStateController,
- notificationEntryManager,
- bypassController,
- bubblesOptional,
- privacyController,
- dynamicChildBindController,
- lowPriorityInflationHelper,
- assistantFeedbackController,
- notifPipelineFlags,
- keyguardUpdateMonitor,
- keyguardStateController);
- }
-
/**
* Provides our instance of CommandQueue which is considered optional.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
index 0c6a91f..7fbdd35 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
@@ -29,10 +29,6 @@
val featureFlags: FeatureFlags
) {
fun checkLegacyPipelineEnabled(): Boolean {
- if (!isNewPipelineEnabled()) {
- return true
- }
-
if (Compile.IS_DEBUG) {
Toast.makeText(context, "Old pipeline code running!", Toast.LENGTH_SHORT).show()
}
@@ -45,11 +41,6 @@
return false
}
- fun assertLegacyPipelineEnabled(): Unit =
- check(!isNewPipelineEnabled()) { "Old pipeline code running w/ new pipeline enabled" }
-
- fun isNewPipelineEnabled(): Boolean = true
-
fun isDevLoggingEnabled(): Boolean =
featureFlags.isEnabled(Flags.NOTIFICATION_PIPELINE_DEVELOPER_LOGGING)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
index 852d5f7..7583a98 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
@@ -46,11 +46,9 @@
import com.android.systemui.statusbar.NotificationLifetimeExtender;
import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.NotificationListener.NotificationHandler;
-import com.android.systemui.statusbar.NotificationPresenter;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.NotificationRemoveInterceptor;
import com.android.systemui.statusbar.NotificationUiAdjustment;
-import com.android.systemui.statusbar.notification.collection.NotifLiveDataStoreImpl;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder;
import com.android.systemui.statusbar.notification.collection.legacy.LegacyNotificationRanker;
@@ -113,7 +111,6 @@
private final Lazy<NotificationRemoteInputManager> mRemoteInputManagerLazy;
private final LeakDetector mLeakDetector;
private final IStatusBarService mStatusBarService;
- private final NotifLiveDataStoreImpl mNotifLiveDataStore;
private final DumpManager mDumpManager;
private final Executor mBgExecutor;
@@ -141,7 +138,6 @@
private final List<NotifCollectionListener> mNotifCollectionListeners = new ArrayList<>();
private LegacyNotificationRanker mRanker = new LegacyNotificationRankerStub();
- private NotificationPresenter mPresenter;
private RankingMap mLatestRankingMap;
@VisibleForTesting
@@ -161,7 +157,6 @@
Lazy<NotificationRemoteInputManager> notificationRemoteInputManagerLazy,
LeakDetector leakDetector,
IStatusBarService statusBarService,
- NotifLiveDataStoreImpl notifLiveDataStore,
DumpManager dumpManager,
@Background Executor bgExecutor
) {
@@ -172,7 +167,6 @@
mRemoteInputManagerLazy = notificationRemoteInputManagerLazy;
mLeakDetector = leakDetector;
mStatusBarService = statusBarService;
- mNotifLiveDataStore = notifLiveDataStore;
mDumpManager = dumpManager;
mBgExecutor = bgExecutor;
}
@@ -250,10 +244,6 @@
mRemoveInterceptors.remove(interceptor);
}
- public void setUpWithPresenter(NotificationPresenter presenter) {
- mPresenter = presenter;
- }
-
/** Adds multiple {@link NotificationLifetimeExtender}s. */
public void addNotificationLifetimeExtenders(List<NotificationLifetimeExtender> extenders) {
for (NotificationLifetimeExtender extender : extenders) {
@@ -663,11 +653,6 @@
listener.onEntryBind(entry, notification);
}
- // Construct the expanded view.
- if (!mNotifPipelineFlags.isNewPipelineEnabled()) {
- mNotificationRowBinderLazy.get().inflateViews(entry, null, mInflationCallback);
- }
-
mPendingNotifications.put(key, entry);
mLogger.logNotifAdded(entry.getKey());
for (NotificationEntryListener listener : mNotificationEntryListeners) {
@@ -724,10 +709,6 @@
listener.onEntryUpdated(entry, fromSystem);
}
- if (!mNotifPipelineFlags.isNewPipelineEnabled()) {
- mNotificationRowBinderLazy.get().inflateViews(entry, null, mInflationCallback);
- }
-
updateNotifications("updateNotificationInternal");
for (NotificationEntryListener listener : mNotificationEntryListeners) {
@@ -752,17 +733,7 @@
* @param reason why the notifications are updating
*/
public void updateNotifications(String reason) {
- if (mNotifPipelineFlags.isNewPipelineEnabled()) {
- mLogger.logUseWhileNewPipelineActive("updateNotifications", reason);
- return;
- }
- Trace.beginSection("NotificationEntryManager.updateNotifications");
- reapplyFilterAndSort(reason);
- if (mPresenter != null) {
- mPresenter.updateNotificationViews(reason);
- }
- mNotifLiveDataStore.setActiveNotifList(getVisibleNotifications());
- Trace.endSection();
+ mLogger.logUseWhileNewPipelineActive("updateNotifications", reason);
}
public void updateNotificationRanking(RankingMap rankingMap) {
@@ -939,26 +910,12 @@
/** Resorts / filters the current notification set with the current RankingMap */
public void reapplyFilterAndSort(String reason) {
- if (mNotifPipelineFlags.isNewPipelineEnabled()) {
- mLogger.logUseWhileNewPipelineActive("reapplyFilterAndSort", reason);
- return;
- }
- Trace.beginSection("NotificationEntryManager.reapplyFilterAndSort");
- updateRankingAndSort(mRanker.getRankingMap(), reason);
- Trace.endSection();
+ mLogger.logUseWhileNewPipelineActive("reapplyFilterAndSort", reason);
}
/** Calls to NotificationRankingManager and updates mSortedAndFiltered */
private void updateRankingAndSort(RankingMap rankingMap, String reason) {
- if (mNotifPipelineFlags.isNewPipelineEnabled()) {
- mLogger.logUseWhileNewPipelineActive("updateRankingAndSort", reason);
- return;
- }
- Trace.beginSection("NotificationEntryManager.updateRankingAndSort");
- mSortedAndFiltered.clear();
- mSortedAndFiltered.addAll(mRanker.updateRanking(
- rankingMap, mActiveNotifications.values(), reason));
- Trace.endSection();
+ mLogger.logUseWhileNewPipelineActive("updateRankingAndSort", reason);
}
/** dump the current active notification list. Called from CentralSurfaces */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListAttachState.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListAttachState.kt
index 945bc72..9e5dab1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListAttachState.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListAttachState.kt
@@ -68,9 +68,6 @@
*/
var stableIndex: Int = -1
- /** Access the index of the [section] or -1 if the entry does not have one */
- val sectionIndex: Int get() = section?.index ?: -1
-
/** Copies the state of another instance. */
fun clone(other: ListAttachState) {
parent = other.parent
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
index 697054a..420f21d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
@@ -1039,25 +1039,22 @@
NotifSection currentSection = requireNonNull(notifList.get(0).getSection());
int sectionMemberIndex = 0;
for (int i = 0; i < notifList.size(); i++) {
- final ListEntry entry = notifList.get(i);
+ ListEntry entry = notifList.get(i);
NotifSection section = requireNonNull(entry.getSection());
if (section.getIndex() != currentSection.getIndex()) {
sectionMemberIndex = 0;
currentSection = section;
}
- entry.getAttachState().setStableIndex(sectionMemberIndex++);
+ entry.getAttachState().setStableIndex(sectionMemberIndex);
if (entry instanceof GroupEntry) {
- final GroupEntry parent = (GroupEntry) entry;
- final NotificationEntry summary = parent.getSummary();
- if (summary != null) {
- summary.getAttachState().setStableIndex(sectionMemberIndex++);
- }
- final List<NotificationEntry> children = parent.getChildren();
- for (int j = 0; j < children.size(); j++) {
- final NotificationEntry child = children.get(j);
- child.getAttachState().setStableIndex(sectionMemberIndex++);
+ GroupEntry parent = (GroupEntry) entry;
+ for (int j = 0; j < parent.getChildren().size(); j++) {
+ entry = parent.getChildren().get(j);
+ entry.getAttachState().setStableIndex(sectionMemberIndex);
+ sectionMemberIndex++;
}
}
+ sectionMemberIndex++;
}
}
@@ -1197,9 +1194,9 @@
o2.getSectionIndex());
if (cmp != 0) return cmp;
- cmp = Integer.compare(
- getStableOrderIndex(o1),
- getStableOrderIndex(o2));
+ int index1 = canReorder(o1) ? -1 : o1.getPreviousAttachState().getStableIndex();
+ int index2 = canReorder(o2) ? -1 : o2.getPreviousAttachState().getStableIndex();
+ cmp = Integer.compare(index1, index2);
if (cmp != 0) return cmp;
NotifComparator sectionComparator = getSectionComparator(o1, o2);
@@ -1213,32 +1210,31 @@
if (cmp != 0) return cmp;
}
- cmp = Integer.compare(
- o1.getRepresentativeEntry().getRanking().getRank(),
- o2.getRepresentativeEntry().getRanking().getRank());
+ final NotificationEntry rep1 = o1.getRepresentativeEntry();
+ final NotificationEntry rep2 = o2.getRepresentativeEntry();
+ cmp = rep1.getRanking().getRank() - rep2.getRanking().getRank();
if (cmp != 0) return cmp;
- cmp = -1 * Long.compare(
- o1.getRepresentativeEntry().getSbn().getNotification().when,
- o2.getRepresentativeEntry().getSbn().getNotification().when);
+ cmp = Long.compare(
+ rep2.getSbn().getNotification().when,
+ rep1.getSbn().getNotification().when);
return cmp;
};
private final Comparator<NotificationEntry> mGroupChildrenComparator = (o1, o2) -> {
- int cmp = Integer.compare(
- getStableOrderIndex(o1),
- getStableOrderIndex(o2));
+ int index1 = canReorder(o1) ? -1 : o1.getPreviousAttachState().getStableIndex();
+ int index2 = canReorder(o2) ? -1 : o2.getPreviousAttachState().getStableIndex();
+ int cmp = Integer.compare(index1, index2);
if (cmp != 0) return cmp;
- cmp = Integer.compare(
- o1.getRepresentativeEntry().getRanking().getRank(),
- o2.getRepresentativeEntry().getRanking().getRank());
+ cmp = o1.getRepresentativeEntry().getRanking().getRank()
+ - o2.getRepresentativeEntry().getRanking().getRank();
if (cmp != 0) return cmp;
- cmp = -1 * Long.compare(
- o1.getRepresentativeEntry().getSbn().getNotification().when,
- o2.getRepresentativeEntry().getSbn().getNotification().when);
+ cmp = Long.compare(
+ o2.getRepresentativeEntry().getSbn().getNotification().when,
+ o1.getRepresentativeEntry().getSbn().getNotification().when);
return cmp;
};
@@ -1248,21 +1244,8 @@
*/
private boolean mForceReorderable = false;
- private int getStableOrderIndex(ListEntry entry) {
- if (mForceReorderable) {
- // this is used to determine if the list is correctly sorted
- return -1;
- }
- if (getStabilityManager().isEntryReorderingAllowed(entry)) {
- // let the stability manager constrain or allow reordering
- return -1;
- }
- if (entry.getAttachState().getSectionIndex()
- != entry.getPreviousAttachState().getSectionIndex()) {
- // stable index is only valid within the same section; otherwise we allow reordering
- return -1;
- }
- return entry.getPreviousAttachState().getStableIndex();
+ private boolean canReorder(ListEntry entry) {
+ return mForceReorderable || getStabilityManager().isEntryReorderingAllowed(entry);
}
private boolean applyFilters(NotificationEntry entry, long now, List<NotifFilter> filters) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt
index 4c406e3..d8dae5d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt
@@ -336,7 +336,7 @@
}
fun logPipelineRunSuppressed() =
- buffer.log(TAG, INFO, {}) { "Suppressing pipeline run during animation." }
+ buffer.log(TAG, INFO, {}, { "Suppressing pipeline run during animation." })
}
private const val TAG = "ShadeListBuilder"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
index fcf35bf..fac234c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
@@ -127,7 +127,6 @@
Lazy<NotificationRemoteInputManager> notificationRemoteInputManagerLazy,
LeakDetector leakDetector,
IStatusBarService statusBarService,
- NotifLiveDataStoreImpl notifLiveDataStore,
DumpManager dumpManager,
@Background Executor bgExecutor) {
return new NotificationEntryManager(
@@ -138,7 +137,6 @@
notificationRemoteInputManagerLazy,
leakDetector,
statusBarService,
- notifLiveDataStore,
dumpManager,
bgExecutor);
}
@@ -174,7 +172,6 @@
accessibilityManager,
highPriorityProvider,
notificationManager,
- notificationEntryManager,
peopleSpaceWidgetManager,
launcherApps,
shortcutManager,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
index a72b381..558fd62 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
@@ -37,7 +37,6 @@
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.notification.NotifPipelineFlags;
-import com.android.systemui.statusbar.notification.NotificationFilter;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
@@ -60,13 +59,11 @@
private final List<NotificationInterruptSuppressor> mSuppressors = new ArrayList<>();
private final StatusBarStateController mStatusBarStateController;
private final KeyguardStateController mKeyguardStateController;
- private final NotificationFilter mNotificationFilter;
private final ContentResolver mContentResolver;
private final PowerManager mPowerManager;
private final IDreamManager mDreamManager;
private final AmbientDisplayConfiguration mAmbientDisplayConfiguration;
private final BatteryController mBatteryController;
- private final ContentObserver mHeadsUpObserver;
private final HeadsUpManager mHeadsUpManager;
private final NotificationInterruptLogger mLogger;
private final NotifPipelineFlags mFlags;
@@ -81,7 +78,6 @@
PowerManager powerManager,
IDreamManager dreamManager,
AmbientDisplayConfiguration ambientDisplayConfiguration,
- NotificationFilter notificationFilter,
BatteryController batteryController,
StatusBarStateController statusBarStateController,
KeyguardStateController keyguardStateController,
@@ -95,14 +91,13 @@
mDreamManager = dreamManager;
mBatteryController = batteryController;
mAmbientDisplayConfiguration = ambientDisplayConfiguration;
- mNotificationFilter = notificationFilter;
mStatusBarStateController = statusBarStateController;
mKeyguardStateController = keyguardStateController;
mHeadsUpManager = headsUpManager;
mLogger = logger;
mFlags = flags;
mKeyguardNotificationVisibilityProvider = keyguardNotificationVisibilityProvider;
- mHeadsUpObserver = new ContentObserver(mainHandler) {
+ ContentObserver headsUpObserver = new ContentObserver(mainHandler) {
@Override
public void onChange(boolean selfChange) {
boolean wasUsing = mUseHeadsUp;
@@ -125,12 +120,12 @@
mContentResolver.registerContentObserver(
Settings.Global.getUriFor(Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED),
true,
- mHeadsUpObserver);
+ headsUpObserver);
mContentResolver.registerContentObserver(
Settings.Global.getUriFor(SETTING_HEADS_UP_TICKER), true,
- mHeadsUpObserver);
+ headsUpObserver);
}
- mHeadsUpObserver.onChange(true); // set up
+ headsUpObserver.onChange(true); // set up
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
index c228af4..c4ff259 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
@@ -21,7 +21,6 @@
import android.app.INotificationManager;
import android.app.NotificationChannel;
-import android.appwidget.AppWidgetManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.LauncherApps;
@@ -63,13 +62,11 @@
import com.android.systemui.statusbar.StatusBarStateControllerImpl;
import com.android.systemui.statusbar.notification.AssistantFeedbackController;
import com.android.systemui.statusbar.notification.NotificationActivityStarter;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
import com.android.systemui.statusbar.notification.collection.render.NotifGutsViewListener;
import com.android.systemui.statusbar.notification.collection.render.NotifGutsViewManager;
import com.android.systemui.statusbar.notification.dagger.NotificationsModule;
-import com.android.systemui.statusbar.notification.row.NotificationInfo.CheckSaveListener;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
@@ -114,7 +111,6 @@
private NotificationPresenter mPresenter;
private NotificationActivityStarter mNotificationActivityStarter;
private NotificationListContainer mListContainer;
- private CheckSaveListener mCheckSaveListener;
private OnSettingsClickListener mOnSettingsClickListener;
@VisibleForTesting
protected String mKeyToRemoveOnGutsClosed;
@@ -131,7 +127,6 @@
private final UserContextProvider mContextTracker;
private final UiEventLogger mUiEventLogger;
private final ShadeController mShadeController;
- private final AppWidgetManager mAppWidgetManager;
private NotifGutsViewListener mGutsListener;
/**
@@ -144,7 +139,6 @@
AccessibilityManager accessibilityManager,
HighPriorityProvider highPriorityProvider,
INotificationManager notificationManager,
- NotificationEntryManager notificationEntryManager,
PeopleSpaceWidgetManager peopleSpaceWidgetManager,
LauncherApps launcherApps,
ShortcutManager shortcutManager,
@@ -173,17 +167,15 @@
mUiEventLogger = uiEventLogger;
mOnUserInteractionCallback = onUserInteractionCallback;
mShadeController = shadeController;
- mAppWidgetManager = AppWidgetManager.getInstance(context);
dumpManager.registerDumpable(this);
}
public void setUpWithPresenter(NotificationPresenter presenter,
NotificationListContainer listContainer,
- CheckSaveListener checkSave, OnSettingsClickListener onSettingsClick) {
+ OnSettingsClickListener onSettingsClick) {
mPresenter = presenter;
mListContainer = listContainer;
- mCheckSaveListener = checkSave;
mOnSettingsClickListener = onSettingsClick;
}
@@ -218,14 +210,11 @@
}
private void startAppDetailsSettingsActivity(String packageName, final int appUid,
- final NotificationChannel channel, ExpandableNotificationRow row) {
+ ExpandableNotificationRow row) {
final Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
intent.setData(Uri.fromParts("package", packageName, null));
intent.putExtra(Settings.EXTRA_APP_PACKAGE, packageName);
intent.putExtra(Settings.EXTRA_APP_UID, appUid);
- if (channel != null) {
- intent.putExtra(EXTRA_FRAGMENT_ARG_KEY, channel.getId());
- }
mNotificationActivityStarter.startNotificationGutsIntent(intent, appUid, row);
}
@@ -233,7 +222,7 @@
ExpandableNotificationRow row) {
if (ops.contains(OP_SYSTEM_ALERT_WINDOW)) {
if (ops.contains(OP_CAMERA) || ops.contains(OP_RECORD_AUDIO)) {
- startAppDetailsSettingsActivity(pkg, uid, null, row);
+ startAppDetailsSettingsActivity(pkg, uid, row);
} else {
Intent intent = new Intent(Settings.ACTION_MANAGE_APP_OVERLAY_PERMISSION);
intent.setData(Uri.fromParts("package", pkg, null));
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 d7ebce5..a2ea0ba 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -195,14 +195,12 @@
import com.android.systemui.statusbar.NotificationShadeDepthController;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.NotificationShelfController;
-import com.android.systemui.statusbar.NotificationViewHierarchyManager;
import com.android.systemui.statusbar.PowerButtonReveal;
import com.android.systemui.statusbar.PulseExpansionHandler;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.core.StatusBarInitializer;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
-import com.android.systemui.statusbar.notification.NotifPipelineFlags;
import com.android.systemui.statusbar.notification.NotificationActivityStarter;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorControllerProvider;
@@ -499,7 +497,6 @@
protected final NotificationEntryManager mEntryManager;
private final NotificationGutsManager mGutsManager;
private final NotificationLogger mNotificationLogger;
- private final NotificationViewHierarchyManager mViewHierarchyManager;
private final PanelExpansionStateManager mPanelExpansionStateManager;
private final KeyguardViewMediator mKeyguardViewMediator;
protected final NotificationInterruptStateProvider mNotificationInterruptStateProvider;
@@ -635,7 +632,6 @@
private final Optional<Bubbles> mBubblesOptional;
private final Bubbles.BubbleExpandListener mBubbleExpandListener;
private final Optional<StartingSurface> mStartingSurfaceOptional;
- private final NotifPipelineFlags mNotifPipelineFlags;
private final ActivityIntentHelper mActivityIntentHelper;
private NotificationStackScrollLayoutController mStackScrollerController;
@@ -677,7 +673,6 @@
NotificationGutsManager notificationGutsManager,
NotificationLogger notificationLogger,
NotificationInterruptStateProvider notificationInterruptStateProvider,
- NotificationViewHierarchyManager notificationViewHierarchyManager,
PanelExpansionStateManager panelExpansionStateManager,
KeyguardViewMediator keyguardViewMediator,
DisplayMetrics displayMetrics,
@@ -740,7 +735,6 @@
WallpaperManager wallpaperManager,
Optional<StartingSurface> startingSurfaceOptional,
ActivityLaunchAnimator activityLaunchAnimator,
- NotifPipelineFlags notifPipelineFlags,
InteractionJankMonitor jankMonitor,
DeviceStateManager deviceStateManager,
WiredChargingRippleController wiredChargingRippleController,
@@ -767,7 +761,6 @@
mGutsManager = notificationGutsManager;
mNotificationLogger = notificationLogger;
mNotificationInterruptStateProvider = notificationInterruptStateProvider;
- mViewHierarchyManager = notificationViewHierarchyManager;
mPanelExpansionStateManager = panelExpansionStateManager;
mKeyguardViewMediator = keyguardViewMediator;
mDisplayMetrics = displayMetrics;
@@ -828,7 +821,6 @@
mLockscreenShadeTransitionController = lockscreenShadeTransitionController;
mStartingSurfaceOptional = startingSurfaceOptional;
- mNotifPipelineFlags = notifPipelineFlags;
mDreamManager = dreamManager;
lockscreenShadeTransitionController.setCentralSurfaces(this);
statusBarWindowStateController.addListener(this::onStatusBarWindowStateChanged);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
index ff3d122..cc451d9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.phone;
+import static com.android.internal.util.Preconditions.checkNotNull;
import static com.android.systemui.controls.dagger.ControlsComponent.Visibility.AVAILABLE;
import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInOffset;
import static com.android.systemui.wallet.controller.QuickAccessWalletController.WalletChangeEvent.DEFAULT_PAYMENT_APP_CHANGE;
@@ -57,6 +58,8 @@
import com.android.systemui.controls.management.ControlsListingController;
import com.android.systemui.controls.ui.ControlsActivity;
import com.android.systemui.controls.ui.ControlsUiController;
+import com.android.systemui.keyguard.ui.binder.KeyguardBottomAreaViewBinder;
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.qrcodescanner.controller.QRCodeScannerController;
@@ -125,6 +128,9 @@
}
};
+ @Nullable private KeyguardBottomAreaViewBinder.Binding mBinding;
+ private boolean mUsesBinder;
+
public KeyguardBottomAreaView(Context context) {
this(context, null);
}
@@ -142,13 +148,36 @@
super(context, attrs, defStyleAttr, defStyleRes);
}
- /** Initializes the {@link KeyguardBottomAreaView} with the given dependencies */
+ /**
+ * Initializes the view.
+ */
+ public void init(
+ final KeyguardBottomAreaViewModel viewModel,
+ final FalsingManager falsingManager) {
+ Log.i(TAG, System.identityHashCode(this) + " initialized with a binder");
+ mUsesBinder = true;
+ mBinding = KeyguardBottomAreaViewBinder.bind(this, viewModel, falsingManager);
+ }
+
+ /**
+ * Initializes the {@link KeyguardBottomAreaView} with the given dependencies
+ *
+ * @deprecated Use
+ * {@link #init(KeyguardBottomAreaViewModel, FalsingManager)} instead
+ */
+ @Deprecated
public void init(
FalsingManager falsingManager,
QuickAccessWalletController controller,
ControlsComponent controlsComponent,
QRCodeScannerController qrCodeScannerController) {
+ if (mUsesBinder) {
+ return;
+ }
+
+ Log.i(TAG, "initialized without a binder");
mFalsingManager = falsingManager;
+
mQuickAccessWalletController = controller;
mQuickAccessWalletController.setupWalletChangeObservers(
mCardRetriever, WALLET_PREFERENCE_CHANGE, DEFAULT_PAYMENT_APP_CHANGE);
@@ -174,6 +203,10 @@
* another {@link KeyguardBottomAreaView}
*/
public void initFrom(KeyguardBottomAreaView oldBottomArea) {
+ if (mUsesBinder) {
+ return;
+ }
+
// if it exists, continue to use the original ambient indication container
// instead of the newly inflated one
if (mAmbientIndicationArea != null) {
@@ -201,6 +234,10 @@
@Override
protected void onFinishInflate() {
super.onFinishInflate();
+ if (mUsesBinder) {
+ return;
+ }
+
mOverlayContainer = findViewById(R.id.overlay_container);
mWalletButton = findViewById(R.id.wallet_button);
mQRCodeScannerButton = findViewById(R.id.qr_code_scanner_button);
@@ -229,6 +266,10 @@
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
+ if (mUsesBinder) {
+ return;
+ }
+
final IntentFilter filter = new IntentFilter();
filter.addAction(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED);
mKeyguardStateController.addCallback(mKeyguardStateCallback);
@@ -237,6 +278,10 @@
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
+ if (mUsesBinder) {
+ return;
+ }
+
mKeyguardStateController.removeCallback(mKeyguardStateCallback);
if (mQuickAccessWalletController != null) {
@@ -259,6 +304,13 @@
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
+ if (mUsesBinder) {
+ if (mBinding != null) {
+ mBinding.onConfigurationChanged();
+ }
+ return;
+ }
+
mIndicationBottomMargin = getResources().getDimensionPixelSize(
R.dimen.keyguard_indication_margin_bottom);
mBurnInYOffset = getResources().getDimensionPixelSize(
@@ -301,6 +353,10 @@
}
private void updateWalletVisibility() {
+ if (mUsesBinder) {
+ return;
+ }
+
if (mDozing
|| mQuickAccessWalletController == null
|| !mQuickAccessWalletController.isWalletEnabled()
@@ -318,6 +374,10 @@
}
private void updateControlsVisibility() {
+ if (mUsesBinder) {
+ return;
+ }
+
if (mControlsComponent == null) return;
mControlsButton.setImageResource(mControlsComponent.getTileImageId());
@@ -344,6 +404,10 @@
}
public void setDarkAmount(float darkAmount) {
+ if (mUsesBinder) {
+ return;
+ }
+
if (darkAmount == mDarkAmount) {
return;
}
@@ -355,6 +419,10 @@
* Returns a list of animators to use to animate the indication areas.
*/
public List<ViewPropertyAnimator> getIndicationAreaAnimators() {
+ if (mUsesBinder) {
+ return checkNotNull(mBinding).getIndicationAreaAnimators();
+ }
+
List<ViewPropertyAnimator> animators =
new ArrayList<>(mAmbientIndicationArea != null ? 2 : 1);
animators.add(mIndicationArea.animate());
@@ -394,6 +462,10 @@
}
public void setDozing(boolean dozing, boolean animate) {
+ if (mUsesBinder) {
+ return;
+ }
+
mDozing = dozing;
updateWalletVisibility();
@@ -411,6 +483,10 @@
}
public void dozeTimeTick() {
+ if (mUsesBinder) {
+ return;
+ }
+
int burnInYOffset = getBurnInOffset(mBurnInYOffset * 2, false /* xAxis */)
- mBurnInYOffset;
mIndicationArea.setTranslationY(burnInYOffset * mDarkAmount);
@@ -420,6 +496,10 @@
}
public void setAntiBurnInOffsetX(int burnInXOffset) {
+ if (mUsesBinder) {
+ return;
+ }
+
if (mBurnInXOffset == burnInXOffset) {
return;
}
@@ -435,6 +515,10 @@
* action buttons. Does not set the alpha of the lock icon.
*/
public void setComponentAlphas(float alpha) {
+ if (mUsesBinder) {
+ return;
+ }
+
setImportantForAccessibility(
alpha == 0f
? View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
@@ -461,6 +545,10 @@
}
private void updateQRCodeButtonVisibility() {
+ if (mUsesBinder) {
+ return;
+ }
+
if (mQuickAccessWalletController != null
&& mQuickAccessWalletController.isWalletEnabled()) {
// Don't enable if quick access wallet is enabled
@@ -481,6 +569,10 @@
}
private void onQRCodeScannerClicked(View view) {
+ if (mUsesBinder) {
+ return;
+ }
+
Intent intent = mQRCodeScannerController.getIntent();
if (intent != null) {
try {
@@ -500,6 +592,10 @@
}
private void updateAffordanceColors() {
+ if (mUsesBinder) {
+ return;
+ }
+
int iconColor = Utils.getColorAttrDefaultColor(
mContext,
com.android.internal.R.attr.textColorPrimary);
@@ -516,6 +612,10 @@
}
private void onWalletClick(View v) {
+ if (mUsesBinder) {
+ return;
+ }
+
// More coming here; need to inform the user about how to proceed
if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
return;
@@ -531,6 +631,10 @@
}
private void onControlsClick(View v) {
+ if (mUsesBinder) {
+ return;
+ }
+
if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
return;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculator.kt
index 70ec13b14..4496607 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculator.kt
@@ -17,7 +17,6 @@
package com.android.systemui.statusbar.phone
import android.annotation.ColorInt
-import android.graphics.Color
import android.graphics.Rect
import android.view.InsetsFlags
import android.view.ViewDebug
@@ -39,7 +38,13 @@
class LetterboxAppearance(
@Appearance val appearance: Int,
val appearanceRegions: Array<AppearanceRegion>
-)
+) {
+ override fun toString(): String {
+ val appearanceString =
+ ViewDebug.flagsToString(InsetsFlags::class.java, "appearance", appearance)
+ return "LetterboxAppearance{$appearanceString, ${appearanceRegions.contentToString()}}"
+ }
+}
/**
* Responsible for calculating the [Appearance] and [AppearanceRegion] for the status bar when apps
@@ -51,6 +56,7 @@
constructor(
private val lightBarController: LightBarController,
private val dumpManager: DumpManager,
+ private val letterboxBackgroundProvider: LetterboxBackgroundProvider,
) : OnStatusBarViewInitializedListener, CentralSurfacesComponent.Startable {
private var statusBarBoundsProvider: StatusBarBoundsProvider? = null
@@ -184,13 +190,11 @@
@ColorInt
private fun outerLetterboxBackgroundColor(): Int {
- // TODO(b/238607453): retrieve this information from WindowManager.
- return Color.BLACK
+ return letterboxBackgroundProvider.letterboxBackgroundColor
}
private fun isOuterLetterboxMultiColored(): Boolean {
- // TODO(b/238607453): retrieve this information from WindowManager.
- return false
+ return letterboxBackgroundProvider.isLetterboxBackgroundMultiColored
}
private fun getEndSideIconsBounds(): Rect {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxBackgroundProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxBackgroundProvider.kt
new file mode 100644
index 0000000..96b9aca
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxBackgroundProvider.kt
@@ -0,0 +1,74 @@
+/*
+ * 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.statusbar.phone
+
+import android.annotation.ColorInt
+import android.graphics.Color
+import android.os.RemoteException
+import android.view.IWindowManager
+import com.android.systemui.Dumpable
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent
+import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent.CentralSurfacesScope
+import java.io.PrintWriter
+import java.util.concurrent.Executor
+import javax.inject.Inject
+
+/** Responsible for providing information about the background of letterboxed apps. */
+@CentralSurfacesScope
+class LetterboxBackgroundProvider
+@Inject
+constructor(
+ private val windowManager: IWindowManager,
+ @Background private val backgroundExecutor: Executor,
+ private val dumpManager: DumpManager,
+) : CentralSurfacesComponent.Startable, Dumpable {
+
+ @ColorInt
+ var letterboxBackgroundColor: Int = Color.BLACK
+ private set
+
+ var isLetterboxBackgroundMultiColored: Boolean = false
+ private set
+
+ override fun start() {
+ dumpManager.registerDumpable(javaClass.simpleName, this)
+
+ // Using a background executor, as binder calls to IWindowManager are blocking
+ backgroundExecutor.execute {
+ try {
+ isLetterboxBackgroundMultiColored = windowManager.isLetterboxBackgroundMultiColored
+ letterboxBackgroundColor = windowManager.letterboxBackgroundColorInArgb
+ } catch (e: RemoteException) {
+ e.rethrowFromSystemServer()
+ }
+ }
+ }
+
+ override fun stop() {
+ dumpManager.unregisterDumpable(javaClass.simpleName)
+ }
+
+ override fun dump(pw: PrintWriter, args: Array<out String>) {
+ pw.println(
+ """
+ letterboxBackgroundColor: ${Color.valueOf(letterboxBackgroundColor)}
+ isLetterboxBackgroundMultiColored: $isLetterboxBackgroundMultiColored
+ """.trimIndent())
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
index ab640d2..b1104ae 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
@@ -41,7 +41,6 @@
import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
import com.android.systemui.shade.NotificationPanelViewController;
import com.android.systemui.shade.NotificationShadeWindowView;
-import com.android.systemui.shade.ShadeController;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.KeyguardIndicationController;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
@@ -92,10 +91,8 @@
private final HeadsUpManagerPhone mHeadsUpManager;
private final AboveShelfObserver mAboveShelfObserver;
private final DozeScrimController mDozeScrimController;
- private final ScrimController mScrimController;
private final KeyguardIndicationController mKeyguardIndicationController;
private final CentralSurfaces mCentralSurfaces;
- private final com.android.systemui.shade.ShadeController mShadeController;
private final LockscreenShadeTransitionController mShadeTransitionController;
private final CommandQueue mCommandQueue;
@@ -110,20 +107,19 @@
protected boolean mVrMode;
@Inject
- StatusBarNotificationPresenter(Context context,
+ StatusBarNotificationPresenter(
+ Context context,
NotificationPanelViewController panel,
HeadsUpManagerPhone headsUp,
NotificationShadeWindowView statusBarWindow,
ActivityStarter activityStarter,
NotificationStackScrollLayoutController stackScrollerController,
DozeScrimController dozeScrimController,
- ScrimController scrimController,
NotificationShadeWindowController notificationShadeWindowController,
DynamicPrivacyController dynamicPrivacyController,
KeyguardStateController keyguardStateController,
KeyguardIndicationController keyguardIndicationController,
CentralSurfaces centralSurfaces,
- ShadeController shadeController,
LockscreenShadeTransitionController shadeTransitionController,
CommandQueue commandQueue,
NotificationLockscreenUserManager lockscreenUserManager,
@@ -146,7 +142,6 @@
mKeyguardIndicationController = keyguardIndicationController;
// TODO: use KeyguardStateController#isOccluded to remove this dependency
mCentralSurfaces = centralSurfaces;
- mShadeController = shadeController;
mShadeTransitionController = shadeTransitionController;
mCommandQueue = commandQueue;
mLockscreenUserManager = lockscreenUserManager;
@@ -162,7 +157,6 @@
R.id.notification_container_parent));
mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
mDozeScrimController = dozeScrimController;
- mScrimController = scrimController;
mKeyguardManager = context.getSystemService(KeyguardManager.class);
mBarService = IStatusBarService.Stub.asInterface(
ServiceManager.getService(Context.STATUS_BAR_SERVICE));
@@ -188,7 +182,7 @@
notificationInterruptStateProvider.addSuppressor(mInterruptSuppressor);
mLockscreenUserManager.setUpWithPresenter(this);
mGutsManager.setUpWithPresenter(
- this, mNotifListContainer, mCheckSaveListener, mOnSettingsClickListener);
+ this, mNotifListContainer, mOnSettingsClickListener);
// ForegroundServiceNotificationListener adds its listener in its constructor
// but we need to request it here in order for it to be instantiated.
// TODO: figure out how to do this correctly once Dependency.get() is gone.
@@ -225,23 +219,6 @@
}
@Override
- public void updateNotificationViews(final String reason) {
- if (!mNotifPipelineFlags.checkLegacyPipelineEnabled()) {
- return;
- }
- // The function updateRowStates depends on both of these being non-null, so check them here.
- // We may be called before they are set from DeviceProvisionedController's callback.
- if (mScrimController == null) return;
-
- // Do not modify the notifications during collapse.
- if (isCollapsing()) {
- mShadeController.addPostCollapseAction(() -> updateNotificationViews(reason));
- return;
- }
- mNotificationPanel.updateNotificationViews(reason);
- }
-
- @Override
public void onUserSwitched(int newUserId) {
// Begin old BaseStatusBar.userSwitched
mHeadsUpManager.setUser(newUserId);
@@ -295,11 +272,6 @@
}
@Override
- public void onUpdateRowStates() {
- mNotificationPanel.onUpdateRowStates();
- }
-
- @Override
public void onExpandClicked(NotificationEntry clickedEntry, View clickedView,
boolean nowExpanded) {
mHeadsUpManager.setExpanded(clickedEntry, nowExpanded);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesStartableModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesStartableModule.java
index d57e6a7..b0532d7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesStartableModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesStartableModule.java
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.phone.dagger;
import com.android.systemui.statusbar.phone.LetterboxAppearanceCalculator;
+import com.android.systemui.statusbar.phone.LetterboxBackgroundProvider;
import com.android.systemui.statusbar.phone.SystemBarAttributesListener;
import java.util.Set;
@@ -40,4 +41,9 @@
@IntoSet
CentralSurfacesComponent.Startable sysBarAttrsListener(
SystemBarAttributesListener systemBarAttributesListener);
+
+ @Binds
+ @IntoSet
+ CentralSurfacesComponent.Startable letterboxBgProvider(
+ LetterboxBackgroundProvider letterboxBackgroundProvider);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessor.kt
index bd6cf9a..a841914 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessor.kt
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.pipeline
+import android.content.Context
+import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import javax.inject.Inject
@@ -27,4 +29,18 @@
* displayed in the RHS of the status bar.
*/
@SysUISingleton
-class ConnectivityInfoProcessor @Inject constructor()
+class ConnectivityInfoProcessor @Inject constructor(
+ context: Context,
+ private val statusBarPipelineFlags: StatusBarPipelineFlags,
+) : CoreStartable(context) {
+ override fun start() {
+ if (statusBarPipelineFlags.isNewPipelineEnabled()) {
+ init()
+ }
+ }
+
+ /** Initializes this processor and everything it depends on. */
+ private fun init() {
+ // TODO(b/238425913): Register all the connectivity callbacks here.
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityPipelineLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityPipelineLogger.kt
new file mode 100644
index 0000000..f88e9d6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityPipelineLogger.kt
@@ -0,0 +1,59 @@
+/*
+ * 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.statusbar.pipeline
+
+import android.net.Network
+import android.net.NetworkCapabilities
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
+import com.android.systemui.log.dagger.StatusBarConnectivityLog
+import javax.inject.Inject
+
+@SysUISingleton
+class ConnectivityPipelineLogger @Inject constructor(
+ @StatusBarConnectivityLog private val buffer: LogBuffer,
+) {
+ fun logOnCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {
+ buffer.log(
+ TAG,
+ LogLevel.INFO,
+ {
+ int1 = network.getNetId()
+ str1 = networkCapabilities.toString()
+ },
+ {
+ "onCapabilitiesChanged: net=$int1 capabilities=$str1"
+ }
+ )
+ }
+
+ fun logOnLost(network: Network) {
+ buffer.log(
+ TAG,
+ LogLevel.INFO,
+ {
+ int1 = network.getNetId()
+ },
+ {
+ "onLost: net=$int1"
+ }
+ )
+ }
+}
+
+private const val TAG = "SbConnectivityPipeline"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt
new file mode 100644
index 0000000..589cdb8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt
@@ -0,0 +1,37 @@
+/*
+ * 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.statusbar.pipeline
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import javax.inject.Inject
+
+/** All flagging methods related to the new status bar pipeline (see b/238425913). */
+@SysUISingleton
+class StatusBarPipelineFlags @Inject constructor(private val featureFlags: FeatureFlags) {
+ /**
+ * Returns true if we should run the new pipeline.
+ *
+ * TODO(b/238425913): We may want to split this out into:
+ * (1) isNewPipelineLoggingEnabled(), where the new pipeline runs and logs its decisions but
+ * doesn't change the UI at all.
+ * (2) isNewPipelineEnabled(), where the new pipeline runs and does change the UI (and the old
+ * pipeline doesn't change the UI).
+ */
+ fun isNewPipelineEnabled(): Boolean = featureFlags.isEnabled(Flags.NEW_STATUS_BAR_PIPELINE)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
index 734bd2d..771bb0c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
@@ -16,27 +16,18 @@
package com.android.systemui.statusbar.pipeline.dagger
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
+import com.android.systemui.CoreStartable
import com.android.systemui.statusbar.pipeline.ConnectivityInfoProcessor
-import dagger.Lazy
+import dagger.Binds
import dagger.Module
-import dagger.Provides
-import java.util.Optional
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
@Module
-class StatusBarPipelineModule {
- @Provides
- @SysUISingleton
- fun provideConnectivityInfoProcessor(
- featureFlags: FeatureFlags,
- processorLazy: Lazy<ConnectivityInfoProcessor>
- ): Optional<ConnectivityInfoProcessor> {
- return if (featureFlags.isEnabled(Flags.NEW_STATUS_BAR_PIPELINE)) {
- Optional.of(processorLazy.get())
- } else {
- Optional.empty()
- }
- }
+abstract class StatusBarPipelineModule {
+ /** Inject into ConnectivityInfoProcessor. */
+ @Binds
+ @IntoMap
+ @ClassKey(ConnectivityInfoProcessor::class)
+ abstract fun bindConnectivityInfoProcessor(cip: ConnectivityInfoProcessor): CoreStartable
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/repository/NetworkCapabilitiesRepo.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/repository/NetworkCapabilitiesRepo.kt
new file mode 100644
index 0000000..e5980c3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/repository/NetworkCapabilitiesRepo.kt
@@ -0,0 +1,92 @@
+/*
+ * 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.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.statusbar.pipeline.repository
+
+import android.annotation.SuppressLint
+import android.net.ConnectivityManager
+import android.net.Network
+import android.net.NetworkCapabilities
+import android.net.NetworkRequest
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.statusbar.pipeline.ConnectivityPipelineLogger
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.SharingStarted.Companion.Lazily
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.stateIn
+
+/** Repository that contains all relevant [NetworkCapabilites] for the current networks */
+@SysUISingleton
+class NetworkCapabilitiesRepo @Inject constructor(
+ connectivityManager: ConnectivityManager,
+ @Application scope: CoroutineScope,
+ logger: ConnectivityPipelineLogger,
+) {
+ @SuppressLint("MissingPermission")
+ val dataStream: StateFlow<Map<Int, NetworkCapabilityInfo>> = run {
+ var state = emptyMap<Int, NetworkCapabilityInfo>()
+ callbackFlow {
+ val networkRequest: NetworkRequest =
+ NetworkRequest.Builder()
+ .clearCapabilities()
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
+ .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+ .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+ .build()
+ val callback =
+ // TODO (b/240569788): log these using [LogBuffer]
+ object : ConnectivityManager.NetworkCallback(FLAG_INCLUDE_LOCATION_INFO) {
+ override fun onCapabilitiesChanged(
+ network: Network,
+ networkCapabilities: NetworkCapabilities
+ ) {
+ logger.logOnCapabilitiesChanged(network, networkCapabilities)
+ state =
+ state.toMutableMap().also {
+ it[network.getNetId()] =
+ NetworkCapabilityInfo(network, networkCapabilities)
+ }
+ trySend(state)
+ }
+
+ override fun onLost(network: Network) {
+ logger.logOnLost(network)
+ state = state.toMutableMap().also { it.remove(network.getNetId()) }
+ trySend(state)
+ }
+ }
+ connectivityManager.registerNetworkCallback(networkRequest, callback)
+
+ awaitClose { connectivityManager.unregisterNetworkCallback(callback) }
+ }
+ .stateIn(scope, started = Lazily, initialValue = state)
+ }
+}
+
+/** contains info about network capabilities. */
+data class NetworkCapabilityInfo(
+ val network: Network,
+ val capabilities: NetworkCapabilities,
+)
+
+private const val TAG = "ConnectivityRepository"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerExt.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerExt.kt
new file mode 100644
index 0000000..b3f1eeb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerExt.kt
@@ -0,0 +1,46 @@
+/*
+ * 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.statusbar.policy
+
+import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+
+object KeyguardStateControllerExt {
+ /**
+ * Returns an observable for whether the keyguard is currently shown or not.
+ */
+ fun KeyguardStateController.isKeyguardShowing(loggingTag: String): Flow<Boolean> {
+ return ConflatedCallbackFlow.conflatedCallbackFlow {
+ val callback =
+ object : KeyguardStateController.Callback {
+ override fun onKeyguardShowingChanged() {
+ trySendWithFailureLogging(
+ isShowing, loggingTag, "updated isKeyguardShowing")
+ }
+ }
+
+ addCallback(callback)
+ // Adding the callback does not send an initial update.
+ trySendWithFailureLogging(isShowing, loggingTag, "initial isKeyguardShowing")
+
+ awaitClose { removeCallback(callback) }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt b/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt
index d6dfcea..8f127fd 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt
@@ -22,11 +22,12 @@
import android.os.PowerManager
import android.provider.Settings
import androidx.core.view.OneShotPreDrawListener
+import com.android.internal.util.LatencyTracker
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.statusbar.LightRevealScrim
-import com.android.systemui.statusbar.phone.ScreenOffAnimation
import com.android.systemui.statusbar.phone.CentralSurfaces
+import com.android.systemui.statusbar.phone.ScreenOffAnimation
import com.android.systemui.statusbar.policy.CallbackController
import com.android.systemui.unfold.FoldAodAnimationController.FoldAodAnimationStatus
import com.android.systemui.util.settings.GlobalSettings
@@ -47,7 +48,8 @@
private val context: Context,
private val deviceStateManager: DeviceStateManager,
private val wakefulnessLifecycle: WakefulnessLifecycle,
- private val globalSettings: GlobalSettings
+ private val globalSettings: GlobalSettings,
+ private val latencyTracker: LatencyTracker,
) : CallbackController<FoldAodAnimationStatus>, ScreenOffAnimation, WakefulnessLifecycle.Observer {
private lateinit var mCentralSurfaces: CentralSurfaces
@@ -64,12 +66,14 @@
private var isAnimationPlaying = false
private val statusListeners = arrayListOf<FoldAodAnimationStatus>()
+ private val foldToAodLatencyTracker = FoldToAodLatencyTracker()
private val startAnimationRunnable = Runnable {
- mCentralSurfaces.notificationPanelViewController.startFoldToAodAnimation {
- // End action
- setAnimationState(playing = false)
- }
+ mCentralSurfaces.notificationPanelViewController.startFoldToAodAnimation(
+ /* startAction= */ { foldToAodLatencyTracker.onAnimationStarted() },
+ /* endAction= */ { setAnimationState(playing = false) },
+ /* cancelAction= */ { setAnimationState(playing = false) },
+ )
}
override fun initialize(centralSurfaces: CentralSurfaces, lightRevealScrim: LightRevealScrim) {
@@ -82,11 +86,13 @@
/** Returns true if we should run fold to AOD animation */
override fun shouldPlayAnimation(): Boolean = shouldPlayAnimation
- override fun startAnimation(): Boolean =
- if (alwaysOnEnabled &&
+ private fun shouldStartAnimation(): Boolean =
+ alwaysOnEnabled &&
wakefulnessLifecycle.lastSleepReason == PowerManager.GO_TO_SLEEP_REASON_DEVICE_FOLD &&
globalSettings.getString(Settings.Global.ANIMATOR_DURATION_SCALE) != "0"
- ) {
+
+ override fun startAnimation(): Boolean =
+ if (shouldStartAnimation()) {
setAnimationState(playing = true)
mCentralSurfaces.notificationPanelViewController.prepareFoldToAodAnimation()
true
@@ -97,6 +103,7 @@
override fun onStartedWakingUp() {
if (isAnimationPlaying) {
+ foldToAodLatencyTracker.cancel()
handler.removeCallbacks(startAnimationRunnable)
mCentralSurfaces.notificationPanelViewController.cancelFoldToAodAnimation()
}
@@ -137,7 +144,8 @@
// but we should wait for the initial animation preparations to be drawn
// (setting initial alpha/translation)
OneShotPreDrawListener.add(
- mCentralSurfaces.notificationPanelViewController.view, onReady
+ mCentralSurfaces.notificationPanelViewController.view,
+ onReady
)
} else {
// No animation, call ready callback immediately
@@ -209,5 +217,41 @@
isFoldHandled = false
}
this.isFolded = isFolded
- })
+ if (isFolded) {
+ foldToAodLatencyTracker.onFolded()
+ }
+ }
+ )
+
+ /**
+ * Tracks the latency of fold to AOD using [LatencyTracker].
+ *
+ * Events that trigger start and end are:
+ *
+ * - Start: Once [DeviceStateManager] sends the folded signal [FoldToAodLatencyTracker.onFolded]
+ * is called and latency tracking starts.
+ * - End: Once the fold -> AOD animation starts, [FoldToAodLatencyTracker.onAnimationStarted] is
+ * called, and latency tracking stops.
+ */
+ private inner class FoldToAodLatencyTracker {
+
+ /** Triggers the latency logging, if needed. */
+ fun onFolded() {
+ if (shouldStartAnimation()) {
+ latencyTracker.onActionStart(LatencyTracker.ACTION_FOLD_TO_AOD)
+ }
+ }
+ /**
+ * Called once the Fold -> AOD animation is started.
+ *
+ * For latency tracking, this determines the end of the fold to aod action.
+ */
+ fun onAnimationStarted() {
+ latencyTracker.onActionEnd(LatencyTracker.ACTION_FOLD_TO_AOD)
+ }
+
+ fun cancel() {
+ latencyTracker.onActionCancel(LatencyTracker.ACTION_FOLD_TO_AOD)
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java b/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java
index 345fc99..4dc78f9 100644
--- a/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java
+++ b/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java
@@ -21,6 +21,7 @@
import android.app.Notification.Action;
import android.app.NotificationManager;
import android.app.PendingIntent;
+import android.app.ActivityManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -215,9 +216,11 @@
} else {
// Boo, annoy the user to reinsert the private volume
- final CharSequence title = mContext.getString(R.string.ext_media_missing_title,
+ final CharSequence title =
+ mContext.getString(R.string.ext_media_missing_title,
rec.getNickname());
- final CharSequence text = mContext.getString(R.string.ext_media_missing_message);
+ final CharSequence text =
+ mContext.getString(R.string.ext_media_missing_message);
Notification.Builder builder =
new Notification.Builder(mContext, NotificationChannels.STORAGE)
@@ -381,8 +384,8 @@
if (rec.isSnoozed() && disk.isAdoptable()) {
return null;
}
-
- if (disk.isAdoptable() && !rec.isInited()) {
+ if (disk.isAdoptable() && !rec.isInited() && rec.getType() != VolumeInfo.TYPE_PUBLIC
+ && rec.getType() != VolumeInfo.TYPE_PRIVATE) {
final CharSequence title = disk.getDescription();
final CharSequence text = mContext.getString(
R.string.ext_media_new_notification_message, disk.getDescription());
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index c94a915..13c3df3 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -378,11 +378,13 @@
// The ringer and rows container has extra height at the top to fit the expanded ringer
// drawer. This area should not be touchable unless the ringer drawer is open.
+ // In landscape the ringer expands to the left and it has to be ensured that if there
+ // are multiple rows they are touchable.
if (view == mTopContainer && !mIsRingerDrawerOpen) {
if (!isLandscape()) {
y += getRingerDrawerOpenExtraSize();
- } else {
- x += getRingerDrawerOpenExtraSize();
+ } else if (getRingerDrawerOpenExtraSize() > getVisibleRowsExtraSize()) {
+ x += (getRingerDrawerOpenExtraSize() - getVisibleRowsExtraSize());
}
}
@@ -1968,6 +1970,21 @@
return (mRingerCount - 1) * mRingerDrawerItemSize;
}
+ /**
+ * Return the size of the additionally visible rows next to the default stream.
+ * An additional row is visible for example while receiving a voice call.
+ */
+ private int getVisibleRowsExtraSize() {
+ VolumeRow activeRow = getActiveRow();
+ int visibleRows = 0;
+ for (final VolumeRow row : mRows) {
+ if (shouldBeVisibleH(row, activeRow)) {
+ visibleRows++;
+ }
+ }
+ return (visibleRows - 1) * (mDialogWidth + mRingerRowsPadding);
+ }
+
private void updateBackgroundForDrawerClosedAmount() {
if (mRingerAndDrawerContainerBackground == null) {
return;
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
index 3e07144..e22a896 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
@@ -40,7 +40,6 @@
import android.provider.Settings;
import android.service.notification.NotificationListenerService.RankingMap;
import android.service.notification.ZenModeConfig;
-import android.util.ArraySet;
import android.util.Log;
import android.util.Pair;
import android.util.SparseArray;
@@ -83,6 +82,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Optional;
+import java.util.Set;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
import java.util.function.IntConsumer;
@@ -262,7 +262,7 @@
}
@Override
- public void getShouldRestoredEntries(ArraySet<String> savedBubbleKeys,
+ public void getShouldRestoredEntries(Set<String> savedBubbleKeys,
Consumer<List<BubbleEntry>> callback) {
sysuiMainExecutor.execute(() -> {
List<BubbleEntry> result = new ArrayList<>();
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
index 68e49c0..dc87a6a 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
@@ -318,7 +318,7 @@
@Test
public void onBouncerVisibilityChanged_withoutSidedSecurity_sideFpsHintHidden() {
setupConditionsToEnableSideFpsHint();
- setSidedSecurityMode(false);
+ setSideFpsHintEnabledFromResources(false);
reset(mSidefpsController);
mKeyguardSecurityContainerController.onBouncerVisibilityChanged(View.VISIBLE);
@@ -383,7 +383,7 @@
private void setupConditionsToEnableSideFpsHint() {
attachView();
- setSidedSecurityMode(true);
+ setSideFpsHintEnabledFromResources(true);
setFingerprintDetectionRunning(true);
setNeedsStrongAuth(false);
}
@@ -399,8 +399,9 @@
BiometricSourceType.FINGERPRINT);
}
- private void setSidedSecurityMode(boolean sided) {
- when(mView.isSidedSecurityMode()).thenReturn(sided);
+ private void setSideFpsHintEnabledFromResources(boolean enabled) {
+ when(mResources.getBoolean(R.bool.config_show_sidefps_hint_on_bouncer)).thenReturn(
+ enabled);
}
private void setNeedsStrongAuth(boolean needed) {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 84903d1..85ecfd3 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -86,6 +86,7 @@
import com.android.internal.widget.ILockSettings;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardUpdateMonitor.BiometricAuthenticated;
+import com.android.keyguard.logging.KeyguardUpdateMonitorLogger;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.broadcast.BroadcastDispatcher;
@@ -179,6 +180,8 @@
private KeyguardUpdateMonitorCallback mTestCallback;
@Mock
private ActiveUnlockConfig mActiveUnlockConfig;
+ @Mock
+ private KeyguardUpdateMonitorLogger mKeyguardUpdateMonitorLogger;
// Direct executor
private Executor mBackgroundExecutor = Runnable::run;
private Executor mMainExecutor = Runnable::run;
@@ -1189,7 +1192,8 @@
mBackgroundExecutor, mMainExecutor,
mStatusBarStateController, mLockPatternUtils,
mAuthController, mTelephonyListenerManager,
- mInteractionJankMonitor, mLatencyTracker, mActiveUnlockConfig);
+ mInteractionJankMonitor, mLatencyTracker, mActiveUnlockConfig,
+ mKeyguardUpdateMonitorLogger);
setStrongAuthTracker(KeyguardUpdateMonitorTest.this.mStrongAuthTracker);
}
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 6157ccb..8d969d0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt
@@ -45,6 +45,8 @@
import android.view.ViewPropertyAnimator
import android.view.WindowInsets
import android.view.WindowManager
+import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION
+import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY
import android.view.WindowMetrics
import androidx.test.filters.SmallTest
import com.airbnb.lottie.LottieAnimationView
@@ -438,6 +440,44 @@
assertThat(fingerprintManager.hasSideFpsSensor()).isFalse()
}
+
+ @Test
+ fun testLayoutParams_hasNoMoveAnimationWindowFlag() = testWithDisplay(
+ deviceConfig = DeviceConfig.Y_ALIGNED_UNFOLDED
+ ) {
+ sideFpsController.overlayOffsets = sensorLocation
+ sideFpsController.updateOverlayParams(
+ windowManager.defaultDisplay,
+ indicatorBounds
+ )
+ overlayController.show(SENSOR_ID, REASON_UNKNOWN)
+ executor.runAllReady()
+
+ verify(windowManager).updateViewLayout(any(), overlayViewParamsCaptor.capture())
+
+ val lpFlags = overlayViewParamsCaptor.value.privateFlags
+
+ assertThat((lpFlags and PRIVATE_FLAG_NO_MOVE_ANIMATION) != 0).isTrue()
+ }
+
+ @Test
+ fun testLayoutParams_hasTrustedOverlayWindowFlag() = testWithDisplay(
+ deviceConfig = DeviceConfig.Y_ALIGNED_UNFOLDED
+ ) {
+ sideFpsController.overlayOffsets = sensorLocation
+ sideFpsController.updateOverlayParams(
+ windowManager.defaultDisplay,
+ indicatorBounds
+ )
+ overlayController.show(SENSOR_ID, REASON_UNKNOWN)
+ executor.runAllReady()
+
+ verify(windowManager).updateViewLayout(any(), overlayViewParamsCaptor.capture())
+
+ val lpFlags = overlayViewParamsCaptor.value.privateFlags
+
+ assertThat((lpFlags and PRIVATE_FLAG_TRUSTED_OVERLAY) != 0).isTrue()
+ }
}
private fun insetsForSmallNavbar() = insetsWithBottom(60)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamClockDateComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamClockDateComplicationTest.java
deleted file mode 100644
index 86aa14d..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamClockDateComplicationTest.java
+++ /dev/null
@@ -1,128 +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.systemui.dreams.complication;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.junit.Assert.assertEquals;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.content.Context;
-import android.testing.AndroidTestingRunner;
-import android.view.View;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.dreams.DreamOverlayStateController;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import javax.inject.Provider;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-public class DreamClockDateComplicationTest extends SysuiTestCase {
- @SuppressWarnings("HidingField")
- @Mock
- private Context mContext;
-
- @Mock
- private DreamOverlayStateController mDreamOverlayStateController;
-
- @Mock
- private DreamClockDateComplication mComplication;
-
- @Mock
- private Provider<DreamClockDateComplication.DreamClockDateViewHolder>
- mDreamClockDateViewHolderProvider;
-
- @Mock
- private DreamClockDateComplication.DreamClockDateViewHolder
- mDreamClockDateViewHolder;
-
- @Mock
- private ComplicationViewModel mComplicationViewModel;
-
- @Mock
- private View mView;
-
- @Mock
- private ComplicationLayoutParams mLayoutParams;
-
- @Before
- public void setup() {
- MockitoAnnotations.initMocks(this);
- when(mDreamClockDateViewHolderProvider.get()).thenReturn(mDreamClockDateViewHolder);
-
- }
-
- /**
- * Ensures {@link DreamClockDateComplication} is registered.
- */
- @Test
- public void testComplicationAdded() {
- final DreamClockDateComplication.Registrant registrant =
- new DreamClockDateComplication.Registrant(
- mContext,
- mDreamOverlayStateController,
- mComplication);
- registrant.start();
- verify(mDreamOverlayStateController).addComplication(eq(mComplication));
- }
-
- /**
- * Verifies {@link DreamClockDateComplication} has the required type.
- */
- @Test
- public void testComplicationRequiredTypeAvailability() {
- final DreamClockDateComplication complication =
- new DreamClockDateComplication(mDreamClockDateViewHolderProvider);
- assertEquals(Complication.COMPLICATION_TYPE_DATE,
- complication.getRequiredTypeAvailability());
- }
-
- /**
- * Verifies {@link DreamClockDateComplication.DreamClockDateViewHolder} is obtainable from its
- * provider when the complication creates view.
- */
- @Test
- public void testComplicationViewHolderProviderOnCreateView() {
- final DreamClockDateComplication complication =
- new DreamClockDateComplication(mDreamClockDateViewHolderProvider);
- final Complication.ViewHolder viewHolder = complication.createView(mComplicationViewModel);
- verify(mDreamClockDateViewHolderProvider).get();
- assertThat(viewHolder).isEqualTo(mDreamClockDateViewHolder);
- }
-
- /**
- * Verifies {@link DreamClockDateComplication.DreamClockDateViewHolder} has the intended view
- * and layout parameters from constructor.
- */
- @Test
- public void testComplicationViewHolderContentAccessors() {
- final DreamClockDateComplication.DreamClockDateViewHolder viewHolder =
- new DreamClockDateComplication.DreamClockDateViewHolder(mView, mLayoutParams);
- assertThat(viewHolder.getView()).isEqualTo(mView);
- assertThat(viewHolder.getLayoutParams()).isEqualTo(mLayoutParams);
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/SmartSpaceComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/SmartSpaceComplicationTest.java
similarity index 94%
rename from packages/SystemUI/tests/src/com/android/systemui/dreams/SmartSpaceComplicationTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/dreams/complication/SmartSpaceComplicationTest.java
index 964e6d7..7d54758 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/SmartSpaceComplicationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/SmartSpaceComplicationTest.java
@@ -13,11 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.systemui.dreams;
+package com.android.systemui.dreams.complication;
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -30,8 +31,7 @@
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.dreams.complication.Complication;
-import com.android.systemui.dreams.complication.ComplicationViewModel;
+import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.dreams.smartspace.DreamSmartspaceController;
import com.android.systemui.plugins.BcSmartspaceDataPlugin;
@@ -183,9 +183,9 @@
@Test
public void testGetView_reusesSameView() {
- final SmartSpaceComplication complication = new SmartSpaceComplication(getContext(),
- mSmartspaceController);
- final Complication.ViewHolder viewHolder = complication.createView(mComplicationViewModel);
+ final Complication.ViewHolder viewHolder =
+ new SmartSpaceComplication.SmartSpaceComplicationViewHolder(getContext(),
+ mSmartspaceController, mock(ComplicationLayoutParams.class));
when(mSmartspaceController.buildAndConnectView(any())).thenReturn(mBcSmartspaceView);
assertEquals(viewHolder.getView(), viewHolder.getView());
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FakeFeatureFlags.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FakeFeatureFlags.kt
new file mode 100644
index 0000000..b2a4e33
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FakeFeatureFlags.kt
@@ -0,0 +1,93 @@
+/*
+ * 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.flags
+
+import android.util.SparseArray
+import android.util.SparseBooleanArray
+import androidx.core.util.containsKey
+import java.lang.IllegalStateException
+
+class FakeFeatureFlags : FeatureFlags {
+ private val booleanFlags = SparseBooleanArray()
+ private val stringFlags = SparseArray<String>()
+ private val knownFlagNames = mutableMapOf<Int, String>()
+
+ init {
+ Flags.getFlagFields().forEach { field ->
+ val flag: Flag<*> = field.get(null) as Flag<*>
+ knownFlagNames[flag.id] = field.name
+ }
+ }
+
+ fun set(flag: BooleanFlag, value: Boolean) {
+ booleanFlags.put(flag.id, value)
+ }
+
+ fun set(flag: DeviceConfigBooleanFlag, value: Boolean) {
+ booleanFlags.put(flag.id, value)
+ }
+
+ fun set(flag: ResourceBooleanFlag, value: Boolean) {
+ booleanFlags.put(flag.id, value)
+ }
+
+ fun set(flag: SysPropBooleanFlag, value: Boolean) {
+ booleanFlags.put(flag.id, value)
+ }
+
+ fun set(flag: StringFlag, value: String) {
+ stringFlags.put(flag.id, value)
+ }
+
+ fun set(flag: ResourceStringFlag, value: String) {
+ stringFlags.put(flag.id, value)
+ }
+
+ override fun isEnabled(flag: BooleanFlag): Boolean = requireBooleanValue(flag.id)
+
+ override fun isEnabled(flag: ResourceBooleanFlag): Boolean = requireBooleanValue(flag.id)
+
+ override fun isEnabled(flag: DeviceConfigBooleanFlag): Boolean = requireBooleanValue(flag.id)
+
+ override fun isEnabled(flag: SysPropBooleanFlag): Boolean = requireBooleanValue(flag.id)
+
+ override fun getString(flag: StringFlag): String = requireStringValue(flag.id)
+
+ override fun getString(flag: ResourceStringFlag): String = requireStringValue(flag.id)
+
+ override fun addListener(flag: Flag<*>, listener: FlagListenable.Listener) {}
+
+ override fun removeListener(listener: FlagListenable.Listener) {}
+
+ private fun flagName(flagId: Int): String {
+ return knownFlagNames[flagId] ?: "UNKNOWN(id=$flagId)"
+ }
+
+ private fun requireBooleanValue(flagId: Int): Boolean {
+ if (!booleanFlags.containsKey(flagId)) {
+ throw IllegalStateException("Flag ${flagName(flagId)} was accessed but not specified.")
+ }
+ return booleanFlags[flagId]
+ }
+
+ private fun requireStringValue(flagId: Int): String {
+ if (!stringFlags.containsKey(flagId)) {
+ throw IllegalStateException("Flag ${flagName(flagId)} was accessed but not specified.")
+ }
+ return stringFlags[flagId]
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FakeFeatureFlagsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FakeFeatureFlagsTest.kt
new file mode 100644
index 0000000..7d4b4f5
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FakeFeatureFlagsTest.kt
@@ -0,0 +1,106 @@
+/*
+ * 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.flags
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import java.lang.IllegalStateException
+import org.junit.Assert.fail
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class FakeFeatureFlagsTest : SysuiTestCase() {
+
+ private val booleanFlag = BooleanFlag(-1000)
+ private val stringFlag = StringFlag(-1001)
+ private val resourceBooleanFlag = ResourceBooleanFlag(-1002, resourceId = -1)
+ private val resourceStringFlag = ResourceStringFlag(-1003, resourceId = -1)
+ private val sysPropBooleanFlag = SysPropBooleanFlag(-1004, name = "test")
+
+ /**
+ * FakeFeatureFlags does not honor any default values. All flags which are accessed must be
+ * specified. If not, an exception is thrown.
+ */
+ @Test
+ fun throwsIfUnspecifiedFlagIsAccessed() {
+ val flags: FeatureFlags = FakeFeatureFlags()
+ try {
+ assertThat(flags.isEnabled(Flags.TEAMFOOD)).isFalse()
+ fail("Expected an exception when accessing an unspecified flag.")
+ } catch (ex: IllegalStateException) {
+ assertThat(ex.message).contains("TEAMFOOD")
+ }
+ try {
+ assertThat(flags.isEnabled(booleanFlag)).isFalse()
+ fail("Expected an exception when accessing an unspecified flag.")
+ } catch (ex: IllegalStateException) {
+ assertThat(ex.message).contains("UNKNOWN(id=-1000)")
+ }
+ try {
+ assertThat(flags.isEnabled(resourceBooleanFlag)).isFalse()
+ fail("Expected an exception when accessing an unspecified flag.")
+ } catch (ex: IllegalStateException) {
+ assertThat(ex.message).contains("UNKNOWN(id=-1002)")
+ }
+ try {
+ assertThat(flags.isEnabled(sysPropBooleanFlag)).isFalse()
+ fail("Expected an exception when accessing an unspecified flag.")
+ } catch (ex: IllegalStateException) {
+ assertThat(ex.message).contains("UNKNOWN(id=-1004)")
+ }
+ try {
+ assertThat(flags.getString(stringFlag)).isEmpty()
+ fail("Expected an exception when accessing an unspecified flag.")
+ } catch (ex: IllegalStateException) {
+ assertThat(ex.message).contains("UNKNOWN(id=-1001)")
+ }
+ try {
+ assertThat(flags.getString(resourceStringFlag)).isEmpty()
+ fail("Expected an exception when accessing an unspecified flag.")
+ } catch (ex: IllegalStateException) {
+ assertThat(ex.message).contains("UNKNOWN(id=-1003)")
+ }
+ }
+
+ @Test
+ fun specifiedFlagsReturnCorrectValues() {
+ val flags = FakeFeatureFlags()
+ flags.set(booleanFlag, false)
+ flags.set(resourceBooleanFlag, false)
+ flags.set(sysPropBooleanFlag, false)
+ flags.set(resourceStringFlag, "")
+
+ assertThat(flags.isEnabled(booleanFlag)).isFalse()
+ assertThat(flags.isEnabled(resourceBooleanFlag)).isFalse()
+ assertThat(flags.isEnabled(sysPropBooleanFlag)).isFalse()
+ assertThat(flags.getString(resourceStringFlag)).isEmpty()
+
+ flags.set(booleanFlag, true)
+ flags.set(resourceBooleanFlag, true)
+ flags.set(sysPropBooleanFlag, true)
+ flags.set(resourceStringFlag, "Android")
+
+ assertThat(flags.isEnabled(booleanFlag)).isTrue()
+ assertThat(flags.isEnabled(resourceBooleanFlag)).isTrue()
+ assertThat(flags.isEnabled(sysPropBooleanFlag)).isTrue()
+ assertThat(flags.getString(resourceStringFlag)).isEqualTo("Android")
+ }
+}
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 9b66555..21c018a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -186,6 +186,31 @@
}
@Test
+ @TestableLooper.RunWithLooper(setAsMainLooper = true)
+ public void restoreBouncerWhenSimLockedAndKeyguardIsGoingAway_initiallyNotShowing() {
+ // When showing and provisioned
+ mViewMediator.onSystemReady();
+ when(mUpdateMonitor.isDeviceProvisioned()).thenReturn(true);
+ mViewMediator.setShowingLocked(false);
+
+ // and a SIM becomes locked and requires a PIN
+ mViewMediator.mUpdateCallback.onSimStateChanged(
+ 1 /* subId */,
+ 0 /* slotId */,
+ TelephonyManager.SIM_STATE_PIN_REQUIRED);
+
+ // and the keyguard goes away
+ mViewMediator.setShowingLocked(false);
+ when(mStatusBarKeyguardViewManager.isShowing()).thenReturn(false);
+ mViewMediator.mUpdateCallback.onKeyguardVisibilityChanged(false);
+
+ TestableLooper.get(this).processAllMessages();
+
+ // then make sure it comes back
+ verify(mStatusBarKeyguardViewManager, atLeast(1)).show(null);
+ }
+
+ @Test
public void testBouncerPrompt_deviceLockedByAdmin() {
// GIVEN no trust agents enabled and biometrics aren't enrolled
when(mUpdateMonitor.isTrustUsuallyManaged(anyInt())).thenReturn(false);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/FakeKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/FakeKeyguardQuickAffordanceConfig.kt
new file mode 100644
index 0000000..6fff440
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/FakeKeyguardQuickAffordanceConfig.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.data.repository
+
+import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.OnClickedResult
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.yield
+
+/**
+ * Fake implementation of a quick affordance data source.
+ *
+ * This class is abstract to force tests to provide extensions of it as the system that references
+ * these configs uses each implementation's class type to refer to them.
+ */
+abstract class FakeKeyguardQuickAffordanceConfig : KeyguardQuickAffordanceConfig {
+
+ private val _onClickedInvocations = mutableListOf<ActivityLaunchAnimator.Controller?>()
+ val onClickedInvocations: List<ActivityLaunchAnimator.Controller?> = _onClickedInvocations
+
+ var onClickedResult: OnClickedResult = OnClickedResult.Handled
+
+ private val _state =
+ MutableStateFlow<KeyguardQuickAffordanceConfig.State>(
+ KeyguardQuickAffordanceConfig.State.Hidden
+ )
+ override val state: Flow<KeyguardQuickAffordanceConfig.State> = _state
+
+ override fun onQuickAffordanceClicked(
+ animationController: ActivityLaunchAnimator.Controller?,
+ ): OnClickedResult {
+ _onClickedInvocations.add(animationController)
+ return onClickedResult
+ }
+
+ suspend fun setState(state: KeyguardQuickAffordanceConfig.State) {
+ _state.value = state
+ // Yield to allow the test's collection coroutine to "catch up" and collect this value
+ // before the test continues to the next line.
+ // TODO(b/239834928): once coroutines.test is updated, switch to the approach described in
+ // https://developer.android.com/kotlin/flow/test#continuous-collection and remove this.
+ yield()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/FakeKeyguardQuickAffordanceConfigs.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/FakeKeyguardQuickAffordanceConfigs.kt
new file mode 100644
index 0000000..a24fc93
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/FakeKeyguardQuickAffordanceConfigs.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.data.repository
+
+import com.android.systemui.keyguard.data.config.KeyguardQuickAffordanceConfigs
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePosition
+import kotlin.reflect.KClass
+
+/** Fake implementation of [KeyguardQuickAffordanceConfigs], for tests. */
+class FakeKeyguardQuickAffordanceConfigs(
+ private val configsByPosition:
+ Map<KeyguardQuickAffordancePosition, List<KeyguardQuickAffordanceConfig>>,
+) : KeyguardQuickAffordanceConfigs {
+
+ override fun getAll(
+ position: KeyguardQuickAffordancePosition
+ ): List<KeyguardQuickAffordanceConfig> {
+ return configsByPosition.getValue(position)
+ }
+
+ override fun get(
+ configClass: KClass<out KeyguardQuickAffordanceConfig>
+ ): KeyguardQuickAffordanceConfig {
+ return configsByPosition.values
+ .flatten()
+ .associateBy { config -> config::class }
+ .getValue(configClass)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/FakeKeyguardQuickAffordanceRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/FakeKeyguardQuickAffordanceRepository.kt
new file mode 100644
index 0000000..7d1cccb
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/FakeKeyguardQuickAffordanceRepository.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.data.repository
+
+import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordanceModel
+import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePosition
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.yield
+
+/** Fake implementation of [KeyguardQuickAffordanceRepository], for tests. */
+class FakeKeyguardQuickAffordanceRepository : KeyguardQuickAffordanceRepository {
+
+ private val modelByPosition =
+ mutableMapOf<
+ KeyguardQuickAffordancePosition, MutableStateFlow<KeyguardQuickAffordanceModel>>()
+
+ init {
+ KeyguardQuickAffordancePosition.values().forEach { value ->
+ modelByPosition[value] = MutableStateFlow(KeyguardQuickAffordanceModel.Hidden)
+ }
+ }
+
+ override fun affordance(
+ position: KeyguardQuickAffordancePosition
+ ): Flow<KeyguardQuickAffordanceModel> {
+ return modelByPosition.getValue(position)
+ }
+
+ suspend fun setModel(
+ position: KeyguardQuickAffordancePosition,
+ model: KeyguardQuickAffordanceModel
+ ) {
+ modelByPosition.getValue(position).value = model
+ // Yield to allow the test's collection coroutine to "catch up" and collect this value
+ // before the test continues to the next line.
+ // TODO(b/239834928): once coroutines.test is updated, switch to the approach described in
+ // https://developer.android.com/kotlin/flow/test#continuous-collection and remove this.
+ yield()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
new file mode 100644
index 0000000..c82803a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.data.repository
+
+import com.android.systemui.common.data.model.Position
+import kotlinx.coroutines.channels.BufferOverflow
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+
+/** Fake implementation of [KeyguardRepository] */
+class FakeKeyguardRepository : KeyguardRepository {
+
+ private val _animateBottomAreaDozingTransitions = MutableStateFlow(false)
+ override val animateBottomAreaDozingTransitions: StateFlow<Boolean> =
+ _animateBottomAreaDozingTransitions
+
+ private val _bottomAreaAlpha = MutableStateFlow(1f)
+ override val bottomAreaAlpha: StateFlow<Float> = _bottomAreaAlpha
+
+ private val _clockPosition = MutableStateFlow(Position(0, 0))
+ override val clockPosition: StateFlow<Position> = _clockPosition
+
+ private val _isDozing =
+ MutableSharedFlow<Boolean>(
+ replay = 1,
+ onBufferOverflow = BufferOverflow.DROP_OLDEST,
+ )
+ override val isDozing: Flow<Boolean> = _isDozing
+
+ private val _dozeAmount =
+ MutableSharedFlow<Float>(
+ replay = 1,
+ onBufferOverflow = BufferOverflow.DROP_OLDEST,
+ )
+ override val dozeAmount: Flow<Float> = _dozeAmount
+
+ init {
+ setDozeAmount(0f)
+ setDozing(false)
+ }
+
+ override fun setAnimateDozingTransitions(animate: Boolean) {
+ _animateBottomAreaDozingTransitions.tryEmit(animate)
+ }
+
+ override fun setBottomAreaAlpha(alpha: Float) {
+ _bottomAreaAlpha.value = alpha
+ }
+
+ override fun setClockPosition(x: Int, y: Int) {
+ _clockPosition.value = Position(x, y)
+ }
+
+ fun setDozing(isDozing: Boolean) {
+ _isDozing.tryEmit(isDozing)
+ }
+
+ fun setDozeAmount(dozeAmount: Float) {
+ _dozeAmount.tryEmit(dozeAmount)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt
new file mode 100644
index 0000000..bcc76ab
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.data.repository
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.controls.controller.ControlsController
+import com.android.systemui.controls.dagger.ControlsComponent
+import com.android.systemui.controls.management.ControlsListingController
+import com.android.systemui.keyguard.data.quickaffordance.HomeControlsKeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import java.util.Optional
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.test.runBlockingTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import org.junit.runners.Parameterized.Parameter
+import org.junit.runners.Parameterized.Parameters
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(Parameterized::class)
+class HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest : SysuiTestCase() {
+
+ companion object {
+ @Parameters(
+ name =
+ "feature enabled = {0}, has favorites = {1}, has service infos = {2} - expected" +
+ " visible = {3}"
+ )
+ @JvmStatic
+ fun data() =
+ (0 until 8)
+ .map { combination ->
+ arrayOf(
+ /* isFeatureEnabled= */ combination and 0b100 != 0,
+ /* hasFavorites= */ combination and 0b010 != 0,
+ /* hasServiceInfos= */ combination and 0b001 != 0,
+ /* isVisible= */ combination == 0b111,
+ )
+ }
+ .toList()
+ }
+
+ @Mock private lateinit var component: ControlsComponent
+ @Mock private lateinit var controlsController: ControlsController
+ @Mock private lateinit var controlsListingController: ControlsListingController
+ @Captor
+ private lateinit var callbackCaptor:
+ ArgumentCaptor<ControlsListingController.ControlsListingCallback>
+
+ private lateinit var underTest: HomeControlsKeyguardQuickAffordanceConfig
+
+ @JvmField @Parameter(0) var isFeatureEnabled: Boolean = false
+ @JvmField @Parameter(1) var hasFavorites: Boolean = false
+ @JvmField @Parameter(2) var hasServiceInfos: Boolean = false
+ @JvmField @Parameter(3) var isVisible: Boolean = false
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ whenever(component.getTileImageId()).thenReturn(R.drawable.controls_icon)
+ whenever(component.getTileTitleId()).thenReturn(R.string.quick_controls_title)
+ whenever(component.getControlsController()).thenReturn(Optional.of(controlsController))
+ whenever(component.getControlsListingController())
+ .thenReturn(Optional.of(controlsListingController))
+
+ underTest =
+ HomeControlsKeyguardQuickAffordanceConfig(
+ context = context,
+ component = component,
+ )
+ }
+
+ @Test
+ fun state() = runBlockingTest {
+ whenever(component.isEnabled()).thenReturn(isFeatureEnabled)
+ whenever(controlsController.getFavorites())
+ .thenReturn(
+ if (hasFavorites) {
+ listOf(mock())
+ } else {
+ emptyList()
+ }
+ )
+ val values = mutableListOf<KeyguardQuickAffordanceConfig.State>()
+ val job = underTest.state.onEach(values::add).launchIn(this)
+
+ verify(controlsListingController).addCallback(callbackCaptor.capture())
+ callbackCaptor.value.onServicesUpdated(
+ if (hasServiceInfos) {
+ listOf(mock())
+ } else {
+ emptyList()
+ }
+ )
+
+ assertThat(values.last())
+ .isInstanceOf(
+ if (isVisible) {
+ KeyguardQuickAffordanceConfig.State.Visible::class.java
+ } else {
+ KeyguardQuickAffordanceConfig.State.Hidden::class.java
+ }
+ )
+ job.cancel()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/HomeControlsKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/HomeControlsKeyguardQuickAffordanceConfigTest.kt
new file mode 100644
index 0000000..9ce5724
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/HomeControlsKeyguardQuickAffordanceConfigTest.kt
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.data.repository
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.controls.controller.ControlsController
+import com.android.systemui.controls.dagger.ControlsComponent
+import com.android.systemui.keyguard.data.quickaffordance.HomeControlsKeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.OnClickedResult
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import java.util.Optional
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.test.runBlockingTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mock
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(JUnit4::class)
+class HomeControlsKeyguardQuickAffordanceConfigTest : SysuiTestCase() {
+
+ @Mock private lateinit var component: ControlsComponent
+ @Mock private lateinit var animationController: ActivityLaunchAnimator.Controller
+
+ private lateinit var underTest: HomeControlsKeyguardQuickAffordanceConfig
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ underTest =
+ HomeControlsKeyguardQuickAffordanceConfig(
+ context = context,
+ component = component,
+ )
+ }
+
+ @Test
+ fun `state - when listing controller is missing - returns None`() = runBlockingTest {
+ whenever(component.isEnabled()).thenReturn(true)
+ whenever(component.getTileImageId()).thenReturn(R.drawable.controls_icon)
+ whenever(component.getTileTitleId()).thenReturn(R.string.quick_controls_title)
+ val controlsController = mock<ControlsController>()
+ whenever(component.getControlsController()).thenReturn(Optional.of(controlsController))
+ whenever(component.getControlsListingController()).thenReturn(Optional.empty())
+ whenever(controlsController.getFavorites()).thenReturn(listOf(mock()))
+
+ val values = mutableListOf<KeyguardQuickAffordanceConfig.State>()
+ val job = underTest.state.onEach(values::add).launchIn(this)
+
+ assertThat(values.last())
+ .isInstanceOf(KeyguardQuickAffordanceConfig.State.Hidden::class.java)
+ job.cancel()
+ }
+
+ @Test
+ fun `onQuickAffordanceClicked - canShowWhileLockedSetting is true`() = runBlockingTest {
+ whenever(component.canShowWhileLockedSetting).thenReturn(MutableStateFlow(true))
+
+ val onClickedResult = underTest.onQuickAffordanceClicked(animationController)
+
+ assertThat(onClickedResult).isInstanceOf(OnClickedResult.StartActivity::class.java)
+ assertThat((onClickedResult as OnClickedResult.StartActivity).canShowWhileLocked).isTrue()
+ }
+
+ @Test
+ fun `onQuickAffordanceClicked - canShowWhileLockedSetting is false`() = runBlockingTest {
+ whenever(component.canShowWhileLockedSetting).thenReturn(MutableStateFlow(false))
+
+ val onClickedResult = underTest.onQuickAffordanceClicked(animationController)
+
+ assertThat(onClickedResult).isInstanceOf(OnClickedResult.StartActivity::class.java)
+ assertThat((onClickedResult as OnClickedResult.StartActivity).canShowWhileLocked).isFalse()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryImplTest.kt
new file mode 100644
index 0000000..dc0e6f7
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryImplTest.kt
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.data.repository
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordanceModel
+import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePosition
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import kotlin.reflect.KClass
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.test.runBlockingTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(JUnit4::class)
+class KeyguardQuickAffordanceRepositoryImplTest : SysuiTestCase() {
+
+ private lateinit var underTest: KeyguardQuickAffordanceRepository
+
+ private lateinit var homeControls: FakeKeyguardQuickAffordanceConfig
+ private lateinit var quickAccessWallet: FakeKeyguardQuickAffordanceConfig
+ private lateinit var qrCodeScanner: FakeKeyguardQuickAffordanceConfig
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ homeControls = object : FakeKeyguardQuickAffordanceConfig() {}
+ quickAccessWallet = object : FakeKeyguardQuickAffordanceConfig() {}
+ qrCodeScanner = object : FakeKeyguardQuickAffordanceConfig() {}
+
+ underTest =
+ KeyguardQuickAffordanceRepositoryImpl(
+ configs =
+ FakeKeyguardQuickAffordanceConfigs(
+ mapOf(
+ KeyguardQuickAffordancePosition.BOTTOM_START to
+ listOf(
+ homeControls,
+ ),
+ KeyguardQuickAffordancePosition.BOTTOM_END to
+ listOf(
+ quickAccessWallet,
+ qrCodeScanner,
+ ),
+ ),
+ ),
+ )
+ }
+
+ @Test
+ fun `bottom start affordance - none`() = runBlockingTest {
+ // TODO(b/239834928): once coroutines.test is updated, switch to the approach described in
+ // https://developer.android.com/kotlin/flow/test#continuous-collection
+ var latest: KeyguardQuickAffordanceModel? = null
+ val job =
+ underTest
+ .affordance(KeyguardQuickAffordancePosition.BOTTOM_START)
+ .onEach { latest = it }
+ .launchIn(this)
+
+ assertThat(latest).isEqualTo(KeyguardQuickAffordanceModel.Hidden)
+ job.cancel()
+ }
+
+ @Test
+ fun `bottom start affordance - home controls`() = runBlockingTest {
+ // TODO(b/239834928): once coroutines.test is updated, switch to the approach described in
+ // https://developer.android.com/kotlin/flow/test#continuous-collection
+ var latest: KeyguardQuickAffordanceModel? = null
+ val job =
+ underTest
+ .affordance(KeyguardQuickAffordancePosition.BOTTOM_START)
+ .onEach { latest = it }
+ .launchIn(this)
+
+ val state =
+ KeyguardQuickAffordanceConfig.State.Visible(
+ icon = mock(),
+ contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
+ )
+ homeControls.setState(state)
+
+ assertThat(latest).isEqualTo(state.toModel(homeControls::class))
+ job.cancel()
+ }
+
+ @Test
+ fun `bottom end affordance - none`() = runBlockingTest {
+ // TODO(b/239834928): once coroutines.test is updated, switch to the approach described in
+ // https://developer.android.com/kotlin/flow/test#continuous-collection
+ var latest: KeyguardQuickAffordanceModel? = null
+ val job =
+ underTest
+ .affordance(KeyguardQuickAffordancePosition.BOTTOM_END)
+ .onEach { latest = it }
+ .launchIn(this)
+
+ assertThat(latest).isEqualTo(KeyguardQuickAffordanceModel.Hidden)
+ job.cancel()
+ }
+
+ @Test
+ fun `bottom end affordance - quick access wallet`() = runBlockingTest {
+ // TODO(b/239834928): once coroutines.test is updated, switch to the approach described in
+ // https://developer.android.com/kotlin/flow/test#continuous-collection
+ var latest: KeyguardQuickAffordanceModel? = null
+ val job =
+ underTest
+ .affordance(KeyguardQuickAffordancePosition.BOTTOM_END)
+ .onEach { latest = it }
+ .launchIn(this)
+
+ val quickAccessWalletState =
+ KeyguardQuickAffordanceConfig.State.Visible(
+ icon = mock(),
+ contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
+ )
+ quickAccessWallet.setState(quickAccessWalletState)
+ val qrCodeScannerState =
+ KeyguardQuickAffordanceConfig.State.Visible(
+ icon = mock(),
+ contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
+ )
+ qrCodeScanner.setState(qrCodeScannerState)
+
+ assertThat(latest).isEqualTo(quickAccessWalletState.toModel(quickAccessWallet::class))
+ job.cancel()
+ }
+
+ @Test
+ fun `bottom end affordance - qr code scanner`() = runBlockingTest {
+ // TODO(b/239834928): once coroutines.test is updated, switch to the approach described in
+ // https://developer.android.com/kotlin/flow/test#continuous-collection
+ var latest: KeyguardQuickAffordanceModel? = null
+ val job =
+ underTest
+ .affordance(KeyguardQuickAffordancePosition.BOTTOM_END)
+ .onEach { latest = it }
+ .launchIn(this)
+
+ val state =
+ KeyguardQuickAffordanceConfig.State.Visible(
+ icon = mock(),
+ contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
+ )
+ qrCodeScanner.setState(state)
+
+ assertThat(latest).isEqualTo(state.toModel(qrCodeScanner::class))
+ job.cancel()
+ }
+
+ private fun KeyguardQuickAffordanceConfig.State?.toModel(
+ configKey: KClass<out KeyguardQuickAffordanceConfig>,
+ ): KeyguardQuickAffordanceModel? {
+ return when (this) {
+ is KeyguardQuickAffordanceConfig.State.Visible ->
+ KeyguardQuickAffordanceModel.Visible(
+ configKey = configKey,
+ icon = icon,
+ contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
+ )
+ is KeyguardQuickAffordanceConfig.State.Hidden -> KeyguardQuickAffordanceModel.Hidden
+ null -> null
+ }
+ }
+
+ companion object {
+ private const val CONTENT_DESCRIPTION_RESOURCE_ID = 1337
+ }
+}
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
new file mode 100644
index 0000000..2ee8034
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.data.repository
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.data.model.Position
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.util.mockito.argumentCaptor
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.test.runBlockingTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(JUnit4::class)
+class KeyguardRepositoryImplTest : SysuiTestCase() {
+
+ @Mock private lateinit var statusBarStateController: StatusBarStateController
+
+ private lateinit var underTest: KeyguardRepositoryImpl
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ underTest = KeyguardRepositoryImpl(statusBarStateController)
+ }
+
+ @Test
+ fun animateBottomAreaDozingTransitions() = runBlockingTest {
+ assertThat(underTest.animateBottomAreaDozingTransitions.value).isEqualTo(false)
+
+ underTest.setAnimateDozingTransitions(true)
+ assertThat(underTest.animateBottomAreaDozingTransitions.value).isTrue()
+
+ underTest.setAnimateDozingTransitions(false)
+ assertThat(underTest.animateBottomAreaDozingTransitions.value).isFalse()
+
+ underTest.setAnimateDozingTransitions(true)
+ assertThat(underTest.animateBottomAreaDozingTransitions.value).isTrue()
+ }
+
+ @Test
+ fun bottomAreaAlpha() = runBlockingTest {
+ assertThat(underTest.bottomAreaAlpha.value).isEqualTo(1f)
+
+ underTest.setBottomAreaAlpha(0.1f)
+ assertThat(underTest.bottomAreaAlpha.value).isEqualTo(0.1f)
+
+ underTest.setBottomAreaAlpha(0.2f)
+ assertThat(underTest.bottomAreaAlpha.value).isEqualTo(0.2f)
+
+ underTest.setBottomAreaAlpha(0.3f)
+ assertThat(underTest.bottomAreaAlpha.value).isEqualTo(0.3f)
+
+ underTest.setBottomAreaAlpha(0.5f)
+ assertThat(underTest.bottomAreaAlpha.value).isEqualTo(0.5f)
+
+ underTest.setBottomAreaAlpha(1.0f)
+ assertThat(underTest.bottomAreaAlpha.value).isEqualTo(1f)
+ }
+
+ @Test
+ fun clockPosition() = runBlockingTest {
+ assertThat(underTest.clockPosition.value).isEqualTo(Position(0, 0))
+
+ underTest.setClockPosition(0, 1)
+ assertThat(underTest.clockPosition.value).isEqualTo(Position(0, 1))
+
+ underTest.setClockPosition(1, 9)
+ assertThat(underTest.clockPosition.value).isEqualTo(Position(1, 9))
+
+ underTest.setClockPosition(1, 0)
+ assertThat(underTest.clockPosition.value).isEqualTo(Position(1, 0))
+
+ underTest.setClockPosition(3, 1)
+ assertThat(underTest.clockPosition.value).isEqualTo(Position(3, 1))
+ }
+
+ @Test
+ fun isDozing() = runBlockingTest {
+ var latest: Boolean? = null
+ val job = underTest.isDozing.onEach { latest = it }.launchIn(this)
+
+ val captor = argumentCaptor<StatusBarStateController.StateListener>()
+ verify(statusBarStateController).addCallback(captor.capture())
+
+ captor.value.onDozingChanged(true)
+ assertThat(latest).isTrue()
+
+ captor.value.onDozingChanged(false)
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ verify(statusBarStateController).removeCallback(captor.value)
+ }
+
+ @Test
+ fun dozeAmount() = runBlockingTest {
+ val values = mutableListOf<Float>()
+ val job = underTest.dozeAmount.onEach(values::add).launchIn(this)
+
+ val captor = argumentCaptor<StatusBarStateController.StateListener>()
+ verify(statusBarStateController).addCallback(captor.capture())
+
+ captor.value.onDozeAmountChanged(0.433f, 0.4f)
+ captor.value.onDozeAmountChanged(0.498f, 0.5f)
+ captor.value.onDozeAmountChanged(0.661f, 0.65f)
+
+ assertThat(values).isEqualTo(listOf(0f, 0.4f, 0.5f, 0.65f))
+
+ job.cancel()
+ verify(statusBarStateController).removeCallback(captor.value)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
new file mode 100644
index 0000000..4bef00a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.data.repository
+
+import android.content.Intent
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.OnClickedResult
+import com.android.systemui.keyguard.data.quickaffordance.QrCodeScannerKeyguardQuickAffordanceConfig
+import com.android.systemui.qrcodescanner.controller.QRCodeScannerController
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.test.runBlockingTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(JUnit4::class)
+class QrCodeScannerKeyguardQuickAffordanceConfigTest : SysuiTestCase() {
+
+ @Mock private lateinit var controller: QRCodeScannerController
+
+ private lateinit var underTest: QrCodeScannerKeyguardQuickAffordanceConfig
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ whenever(controller.intent).thenReturn(INTENT_1)
+
+ underTest = QrCodeScannerKeyguardQuickAffordanceConfig(context, controller)
+ }
+
+ @Test
+ fun `affordance - sets up registration and delivers initial model`() = runBlockingTest {
+ whenever(controller.isEnabledForLockScreenButton).thenReturn(true)
+ var latest: KeyguardQuickAffordanceConfig.State? = null
+
+ val job = underTest.state.onEach { latest = it }.launchIn(this)
+
+ val callbackCaptor = argumentCaptor<QRCodeScannerController.Callback>()
+ verify(controller).addCallback(callbackCaptor.capture())
+ verify(controller)
+ .registerQRCodeScannerChangeObservers(
+ QRCodeScannerController.DEFAULT_QR_CODE_SCANNER_CHANGE,
+ QRCodeScannerController.QR_CODE_SCANNER_PREFERENCE_CHANGE
+ )
+ assertVisibleState(latest)
+
+ job.cancel()
+ verify(controller).removeCallback(callbackCaptor.value)
+ }
+
+ @Test
+ fun `affordance - scanner activity changed - delivers model with updated intent`() =
+ runBlockingTest {
+ whenever(controller.isEnabledForLockScreenButton).thenReturn(true)
+ var latest: KeyguardQuickAffordanceConfig.State? = null
+ val job = underTest.state.onEach { latest = it }.launchIn(this)
+ val callbackCaptor = argumentCaptor<QRCodeScannerController.Callback>()
+ verify(controller).addCallback(callbackCaptor.capture())
+
+ whenever(controller.intent).thenReturn(INTENT_2)
+ callbackCaptor.value.onQRCodeScannerActivityChanged()
+
+ assertVisibleState(latest)
+
+ job.cancel()
+ verify(controller).removeCallback(callbackCaptor.value)
+ }
+
+ @Test
+ fun `affordance - scanner preference changed - delivers visible model`() = runBlockingTest {
+ var latest: KeyguardQuickAffordanceConfig.State? = null
+ val job = underTest.state.onEach { latest = it }.launchIn(this)
+ val callbackCaptor = argumentCaptor<QRCodeScannerController.Callback>()
+ verify(controller).addCallback(callbackCaptor.capture())
+
+ whenever(controller.isEnabledForLockScreenButton).thenReturn(true)
+ callbackCaptor.value.onQRCodeScannerPreferenceChanged()
+
+ assertVisibleState(latest)
+
+ job.cancel()
+ verify(controller).removeCallback(callbackCaptor.value)
+ }
+
+ @Test
+ fun `affordance - scanner preference changed - delivers none`() = runBlockingTest {
+ var latest: KeyguardQuickAffordanceConfig.State? = null
+ val job = underTest.state.onEach { latest = it }.launchIn(this)
+ val callbackCaptor = argumentCaptor<QRCodeScannerController.Callback>()
+ verify(controller).addCallback(callbackCaptor.capture())
+
+ whenever(controller.isEnabledForLockScreenButton).thenReturn(false)
+ callbackCaptor.value.onQRCodeScannerPreferenceChanged()
+
+ assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.State.Hidden)
+
+ job.cancel()
+ verify(controller).removeCallback(callbackCaptor.value)
+ }
+
+ @Test
+ fun onQuickAffordanceClicked() {
+ assertThat(underTest.onQuickAffordanceClicked(mock()))
+ .isEqualTo(
+ OnClickedResult.StartActivity(
+ intent = INTENT_1,
+ canShowWhileLocked = true,
+ )
+ )
+ }
+
+ private fun assertVisibleState(latest: KeyguardQuickAffordanceConfig.State?) {
+ assertThat(latest).isInstanceOf(KeyguardQuickAffordanceConfig.State.Visible::class.java)
+ val visibleState = latest as KeyguardQuickAffordanceConfig.State.Visible
+ assertThat(visibleState.icon).isNotNull()
+ assertThat(visibleState.contentDescriptionResourceId).isNotNull()
+ }
+
+ companion object {
+ private val INTENT_1 = Intent("intent1")
+ private val INTENT_2 = Intent("intent2")
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
new file mode 100644
index 0000000..ee1d4d8
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
@@ -0,0 +1,213 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.data.repository
+
+import android.graphics.drawable.Drawable
+import android.service.quickaccesswallet.GetWalletCardsResponse
+import android.service.quickaccesswallet.QuickAccessWalletClient
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.containeddrawable.ContainedDrawable
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.QuickAccessWalletKeyguardQuickAffordanceConfig
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.wallet.controller.QuickAccessWalletController
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.test.runBlockingTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(JUnit4::class)
+class QuickAccessWalletKeyguardQuickAffordanceConfigTest : SysuiTestCase() {
+
+ @Mock private lateinit var walletController: QuickAccessWalletController
+ @Mock private lateinit var keyguardStateController: KeyguardStateController
+ @Mock private lateinit var activityStarter: ActivityStarter
+
+ private lateinit var underTest: QuickAccessWalletKeyguardQuickAffordanceConfig
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ underTest =
+ QuickAccessWalletKeyguardQuickAffordanceConfig(
+ keyguardStateController,
+ walletController,
+ activityStarter,
+ )
+ }
+
+ @Test
+ fun `affordance - keyguard showing - has wallet card - visible model`() = runBlockingTest {
+ val callback = setUpState()
+ var latest: KeyguardQuickAffordanceConfig.State? = null
+
+ val job = underTest.state.onEach { latest = it }.launchIn(this)
+
+ val visibleModel = latest as KeyguardQuickAffordanceConfig.State.Visible
+ assertThat(visibleModel.icon).isEqualTo(ContainedDrawable.WithDrawable(ICON))
+ assertThat(visibleModel.contentDescriptionResourceId).isNotNull()
+ job.cancel()
+ callback?.let { verify(keyguardStateController).removeCallback(it) }
+ }
+
+ @Test
+ fun `affordance - keyguard not showing - model is none`() = runBlockingTest {
+ val callback = setUpState(isKeyguardShowing = false)
+ var latest: KeyguardQuickAffordanceConfig.State? = null
+
+ val job = underTest.state.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.State.Hidden)
+
+ job.cancel()
+ callback?.let { verify(keyguardStateController).removeCallback(it) }
+ }
+
+ @Test
+ fun `affordance - wallet not enabled - model is none`() = runBlockingTest {
+ val callback = setUpState(isWalletEnabled = false)
+ var latest: KeyguardQuickAffordanceConfig.State? = null
+
+ val job = underTest.state.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.State.Hidden)
+
+ job.cancel()
+ callback?.let { verify(keyguardStateController).removeCallback(it) }
+ }
+
+ @Test
+ fun `affordance - query not successful - model is none`() = runBlockingTest {
+ val callback = setUpState(isWalletQuerySuccessful = false)
+ var latest: KeyguardQuickAffordanceConfig.State? = null
+
+ val job = underTest.state.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.State.Hidden)
+
+ job.cancel()
+ callback?.let { verify(keyguardStateController).removeCallback(it) }
+ }
+
+ @Test
+ fun `affordance - missing icon - model is none`() = runBlockingTest {
+ val callback = setUpState(hasWalletIcon = false)
+ var latest: KeyguardQuickAffordanceConfig.State? = null
+
+ val job = underTest.state.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.State.Hidden)
+
+ job.cancel()
+ callback?.let { verify(keyguardStateController).removeCallback(it) }
+ }
+
+ @Test
+ fun `affordance - no selected card - model is none`() = runBlockingTest {
+ val callback = setUpState(hasWalletIcon = false)
+ var latest: KeyguardQuickAffordanceConfig.State? = null
+
+ val job = underTest.state.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.State.Hidden)
+
+ job.cancel()
+ callback?.let { verify(keyguardStateController).removeCallback(it) }
+ }
+
+ @Test
+ fun onQuickAffordanceClicked() {
+ val animationController: ActivityLaunchAnimator.Controller = mock()
+
+ assertThat(underTest.onQuickAffordanceClicked(animationController))
+ .isEqualTo(KeyguardQuickAffordanceConfig.OnClickedResult.Handled)
+ verify(walletController)
+ .startQuickAccessUiIntent(
+ activityStarter,
+ animationController,
+ /* hasCard= */ true,
+ )
+ }
+
+ private fun setUpState(
+ isKeyguardShowing: Boolean = true,
+ isWalletEnabled: Boolean = true,
+ isWalletQuerySuccessful: Boolean = true,
+ hasWalletIcon: Boolean = true,
+ hasSelectedCard: Boolean = true,
+ ): KeyguardStateController.Callback? {
+ var returnedCallback: KeyguardStateController.Callback? = null
+ whenever(keyguardStateController.isShowing).thenReturn(isKeyguardShowing)
+ whenever(keyguardStateController.addCallback(any())).thenAnswer { invocation ->
+ with(invocation.arguments[0] as KeyguardStateController.Callback) {
+ returnedCallback = this
+ onKeyguardShowingChanged()
+ }
+ }
+
+ whenever(walletController.isWalletEnabled).thenReturn(isWalletEnabled)
+
+ val walletClient: QuickAccessWalletClient = mock()
+ val icon: Drawable? =
+ if (hasWalletIcon) {
+ ICON
+ } else {
+ null
+ }
+ whenever(walletClient.tileIcon).thenReturn(icon)
+ whenever(walletController.walletClient).thenReturn(walletClient)
+
+ whenever(walletController.queryWalletCards(any())).thenAnswer { invocation ->
+ with(
+ invocation.arguments[0] as QuickAccessWalletClient.OnWalletCardsRetrievedCallback
+ ) {
+ if (isWalletQuerySuccessful) {
+ onWalletCardsRetrieved(
+ if (hasSelectedCard) {
+ GetWalletCardsResponse(listOf(mock()), 0)
+ } else {
+ GetWalletCardsResponse(emptyList(), 0)
+ }
+ )
+ } else {
+ onWalletCardRetrievalError(mock())
+ }
+ }
+ }
+
+ return returnedCallback
+ }
+
+ companion object {
+ private val ICON: Drawable = mock()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/FakeLaunchKeyguardQuickAffordanceUseCase.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/FakeLaunchKeyguardQuickAffordanceUseCase.kt
new file mode 100644
index 0000000..ba0c31f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/FakeLaunchKeyguardQuickAffordanceUseCase.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.usecase
+
+import android.content.Intent
+import com.android.systemui.animation.ActivityLaunchAnimator
+
+/** Fake implementation of [LaunchKeyguardQuickAffordanceUseCase], for tests. */
+class FakeLaunchKeyguardQuickAffordanceUseCase : LaunchKeyguardQuickAffordanceUseCase {
+
+ data class Invocation(
+ val intent: Intent,
+ val canShowWhileLocked: Boolean,
+ val animationController: ActivityLaunchAnimator.Controller?
+ )
+
+ private val _invocations = mutableListOf<Invocation>()
+ val invocations: List<Invocation> = _invocations
+
+ override fun invoke(
+ intent: Intent,
+ canShowWhileLocked: Boolean,
+ animationController: ActivityLaunchAnimator.Controller?
+ ) {
+ _invocations.add(
+ Invocation(
+ intent = intent,
+ canShowWhileLocked = canShowWhileLocked,
+ animationController = animationController,
+ )
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/LaunchKeyguardQuickAffordanceUseCaseImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/LaunchKeyguardQuickAffordanceUseCaseImplTest.kt
new file mode 100644
index 0000000..b3c1ae0
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/LaunchKeyguardQuickAffordanceUseCaseImplTest.kt
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.usecase
+
+import android.content.Intent
+import androidx.test.filters.SmallTest
+import com.android.internal.widget.LockPatternUtils
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import org.junit.runners.Parameterized.Parameter
+import org.junit.runners.Parameterized.Parameters
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(Parameterized::class)
+class LaunchKeyguardQuickAffordanceUseCaseImplTest : SysuiTestCase() {
+
+ companion object {
+ private val INTENT = Intent("some.intent.action")
+
+ @Parameters(
+ name =
+ "needStrongAuthAfterBoot={0}, canShowWhileLocked={1}," +
+ " keyguardIsUnlocked={2}, needsToUnlockFirst={3}"
+ )
+ @JvmStatic
+ fun data() =
+ listOf(
+ arrayOf(
+ /* needStrongAuthAfterBoot= */ false,
+ /* canShowWhileLocked= */ false,
+ /* keyguardIsUnlocked= */ false,
+ /* needsToUnlockFirst= */ true,
+ ),
+ arrayOf(
+ /* needStrongAuthAfterBoot= */ false,
+ /* canShowWhileLocked= */ false,
+ /* keyguardIsUnlocked= */ true,
+ /* needsToUnlockFirst= */ false,
+ ),
+ arrayOf(
+ /* needStrongAuthAfterBoot= */ false,
+ /* canShowWhileLocked= */ true,
+ /* keyguardIsUnlocked= */ false,
+ /* needsToUnlockFirst= */ false,
+ ),
+ arrayOf(
+ /* needStrongAuthAfterBoot= */ false,
+ /* canShowWhileLocked= */ true,
+ /* keyguardIsUnlocked= */ true,
+ /* needsToUnlockFirst= */ false,
+ ),
+ arrayOf(
+ /* needStrongAuthAfterBoot= */ true,
+ /* canShowWhileLocked= */ false,
+ /* keyguardIsUnlocked= */ false,
+ /* needsToUnlockFirst= */ true,
+ ),
+ arrayOf(
+ /* needStrongAuthAfterBoot= */ true,
+ /* canShowWhileLocked= */ false,
+ /* keyguardIsUnlocked= */ true,
+ /* needsToUnlockFirst= */ true,
+ ),
+ arrayOf(
+ /* needStrongAuthAfterBoot= */ true,
+ /* canShowWhileLocked= */ true,
+ /* keyguardIsUnlocked= */ false,
+ /* needsToUnlockFirst= */ true,
+ ),
+ arrayOf(
+ /* needStrongAuthAfterBoot= */ true,
+ /* canShowWhileLocked= */ true,
+ /* keyguardIsUnlocked= */ true,
+ /* needsToUnlockFirst= */ true,
+ ),
+ )
+ }
+
+ @Mock private lateinit var lockPatternUtils: LockPatternUtils
+ @Mock private lateinit var keyguardStateController: KeyguardStateController
+ @Mock private lateinit var userTracker: UserTracker
+ @Mock private lateinit var activityStarter: ActivityStarter
+ @Mock private lateinit var animationController: ActivityLaunchAnimator.Controller
+
+ private lateinit var underTest: LaunchKeyguardQuickAffordanceUseCase
+
+ @JvmField @Parameter(0) var needStrongAuthAfterBoot: Boolean = false
+ @JvmField @Parameter(1) var canShowWhileLocked: Boolean = false
+ @JvmField @Parameter(2) var keyguardIsUnlocked: Boolean = false
+ @JvmField @Parameter(3) var needsToUnlockFirst: Boolean = false
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ underTest =
+ LaunchKeyguardQuickAffordanceUseCaseImpl(
+ lockPatternUtils = lockPatternUtils,
+ keyguardStateController = keyguardStateController,
+ userTracker = userTracker,
+ activityStarter = activityStarter,
+ )
+ }
+
+ @Test
+ fun invoke() {
+ setUpMocks(
+ needStrongAuthAfterBoot = needStrongAuthAfterBoot,
+ keyguardIsUnlocked = keyguardIsUnlocked,
+ )
+
+ underTest(
+ intent = INTENT,
+ canShowWhileLocked = canShowWhileLocked,
+ animationController = animationController,
+ )
+
+ if (needsToUnlockFirst) {
+ verify(activityStarter)
+ .postStartActivityDismissingKeyguard(
+ INTENT,
+ /* delay= */ 0,
+ animationController,
+ )
+ } else {
+ verify(activityStarter)
+ .startActivity(
+ INTENT,
+ /* dismissShade= */ true,
+ animationController,
+ /* showOverLockscreenWhenLocked= */ true,
+ )
+ }
+ }
+
+ private fun setUpMocks(
+ needStrongAuthAfterBoot: Boolean = true,
+ keyguardIsUnlocked: Boolean = false,
+ ) {
+ whenever(userTracker.userHandle).thenReturn(mock())
+ whenever(lockPatternUtils.getStrongAuthForUser(any()))
+ .thenReturn(
+ if (needStrongAuthAfterBoot) {
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT
+ } else {
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED
+ }
+ )
+ whenever(keyguardStateController.isUnlocked).thenReturn(keyguardIsUnlocked)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/ObserveKeyguardQuickAffordanceUseCaseTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/ObserveKeyguardQuickAffordanceUseCaseTest.kt
new file mode 100644
index 0000000..a60657c5
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/ObserveKeyguardQuickAffordanceUseCaseTest.kt
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.usecase
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.containeddrawable.ContainedDrawable
+import com.android.systemui.keyguard.data.quickaffordance.HomeControlsKeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.repository.FakeKeyguardQuickAffordanceRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordanceModel
+import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePosition
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.test.runBlockingTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@SmallTest
+@RunWith(JUnit4::class)
+class ObserveKeyguardQuickAffordanceUseCaseTest : SysuiTestCase() {
+
+ private lateinit var underTest: ObserveKeyguardQuickAffordanceUseCase
+
+ private lateinit var repository: FakeKeyguardRepository
+ private lateinit var quickAffordanceRepository: FakeKeyguardQuickAffordanceRepository
+ private lateinit var isDozingUseCase: ObserveIsDozingUseCase
+ private lateinit var dozeAmountUseCase: ObserveDozeAmountUseCase
+
+ @Before
+ fun setUp() {
+ repository = FakeKeyguardRepository()
+ isDozingUseCase = ObserveIsDozingUseCase(repository)
+ dozeAmountUseCase = ObserveDozeAmountUseCase(repository)
+ quickAffordanceRepository = FakeKeyguardQuickAffordanceRepository()
+
+ underTest =
+ ObserveKeyguardQuickAffordanceUseCase(
+ repository = quickAffordanceRepository,
+ isDozingUseCase = isDozingUseCase,
+ dozeAmountUseCase = dozeAmountUseCase,
+ )
+ }
+
+ @Test
+ fun `invoke - affordance is visible`() = runBlockingTest {
+ val configKey = HomeControlsKeyguardQuickAffordanceConfig::class
+ val model =
+ KeyguardQuickAffordanceModel.Visible(
+ configKey = configKey,
+ icon = ICON,
+ contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
+ )
+ quickAffordanceRepository.setModel(
+ KeyguardQuickAffordancePosition.BOTTOM_END,
+ model,
+ )
+
+ var latest: KeyguardQuickAffordanceModel? = null
+ val job = underTest(KeyguardQuickAffordancePosition.BOTTOM_END)
+ .onEach { latest = it }
+ .launchIn(this)
+
+ assertThat(latest).isInstanceOf(KeyguardQuickAffordanceModel.Visible::class.java)
+ val visibleModel = latest as KeyguardQuickAffordanceModel.Visible
+ assertThat(visibleModel.configKey).isEqualTo(configKey)
+ assertThat(visibleModel.icon).isEqualTo(ICON)
+ assertThat(visibleModel.contentDescriptionResourceId)
+ .isEqualTo(CONTENT_DESCRIPTION_RESOURCE_ID)
+ job.cancel()
+ }
+
+ @Test
+ fun `invoke - affordance not visible while dozing`() = runBlockingTest {
+ repository.setDozing(true)
+ val configKey = HomeControlsKeyguardQuickAffordanceConfig::class
+ val model =
+ KeyguardQuickAffordanceModel.Visible(
+ configKey = configKey,
+ icon = ICON,
+ contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
+ )
+ quickAffordanceRepository.setModel(
+ KeyguardQuickAffordancePosition.BOTTOM_END,
+ model,
+ )
+
+ var latest: KeyguardQuickAffordanceModel? = null
+ val job = underTest(KeyguardQuickAffordancePosition.BOTTOM_END)
+ .onEach { latest = it }
+ .launchIn(this)
+ assertThat(latest).isEqualTo(KeyguardQuickAffordanceModel.Hidden)
+ job.cancel()
+ }
+
+ @Test
+ fun `invoke - affordance not visible doze amount is not 0`() = runBlockingTest {
+ repository.setDozeAmount(0.3f)
+ val configKey = HomeControlsKeyguardQuickAffordanceConfig::class
+ val model =
+ KeyguardQuickAffordanceModel.Visible(
+ configKey = configKey,
+ icon = ICON,
+ contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
+ )
+ quickAffordanceRepository.setModel(
+ KeyguardQuickAffordancePosition.BOTTOM_END,
+ model,
+ )
+
+ var latest: KeyguardQuickAffordanceModel? = null
+ val job = underTest(KeyguardQuickAffordancePosition.BOTTOM_END)
+ .onEach { latest = it }
+ .launchIn(this)
+ assertThat(latest).isEqualTo(KeyguardQuickAffordanceModel.Hidden)
+ job.cancel()
+ }
+
+ @Test
+ fun `invoke - affordance is none`() = runBlockingTest {
+ quickAffordanceRepository.setModel(
+ KeyguardQuickAffordancePosition.BOTTOM_START,
+ KeyguardQuickAffordanceModel.Hidden,
+ )
+
+ var latest: KeyguardQuickAffordanceModel? = null
+ val job = underTest(KeyguardQuickAffordancePosition.BOTTOM_START)
+ .onEach { latest = it }
+ .launchIn(this)
+ assertThat(latest).isEqualTo(KeyguardQuickAffordanceModel.Hidden)
+ job.cancel()
+ }
+
+ companion object {
+ private val ICON: ContainedDrawable = mock()
+ private const val CONTENT_DESCRIPTION_RESOURCE_ID = 1337
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
new file mode 100644
index 0000000..cb9cbba
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
@@ -0,0 +1,467 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import android.content.Intent
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.containeddrawable.ContainedDrawable
+import com.android.systemui.doze.util.BurnInHelperWrapper
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.repository.FakeKeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.repository.FakeKeyguardQuickAffordanceConfigs
+import com.android.systemui.keyguard.data.repository.FakeKeyguardQuickAffordanceRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.domain.usecase.FakeLaunchKeyguardQuickAffordanceUseCase
+import com.android.systemui.keyguard.domain.usecase.ObserveAnimateBottomAreaTransitionsUseCase
+import com.android.systemui.keyguard.domain.usecase.ObserveBottomAreaAlphaUseCase
+import com.android.systemui.keyguard.domain.usecase.ObserveClockPositionUseCase
+import com.android.systemui.keyguard.domain.usecase.ObserveDozeAmountUseCase
+import com.android.systemui.keyguard.domain.usecase.ObserveIsDozingUseCase
+import com.android.systemui.keyguard.domain.usecase.ObserveKeyguardQuickAffordanceUseCase
+import com.android.systemui.keyguard.domain.usecase.OnKeyguardQuickAffordanceClickedUseCase
+import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordanceModel
+import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePosition
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import kotlin.reflect.KClass
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.test.runBlockingTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mock
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(JUnit4::class)
+class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
+
+ @Mock private lateinit var animationController: ActivityLaunchAnimator.Controller
+ @Mock private lateinit var burnInHelperWrapper: BurnInHelperWrapper
+
+ private lateinit var underTest: KeyguardBottomAreaViewModel
+
+ private lateinit var affordanceRepository: FakeKeyguardQuickAffordanceRepository
+ private lateinit var repository: FakeKeyguardRepository
+ private lateinit var isDozingUseCase: ObserveIsDozingUseCase
+ private lateinit var dozeAmountUseCase: ObserveDozeAmountUseCase
+ private lateinit var launchQuickAffordanceUseCase: FakeLaunchKeyguardQuickAffordanceUseCase
+ private lateinit var homeControlsQuickAffordanceConfig: FakeKeyguardQuickAffordanceConfig
+ private lateinit var quickAccessWalletAffordanceConfig: FakeKeyguardQuickAffordanceConfig
+ private lateinit var qrCodeScannerAffordanceConfig: FakeKeyguardQuickAffordanceConfig
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ whenever(burnInHelperWrapper.burnInOffset(anyInt(), any()))
+ .thenReturn(RETURNED_BURN_IN_OFFSET)
+
+ affordanceRepository = FakeKeyguardQuickAffordanceRepository()
+ repository = FakeKeyguardRepository()
+ isDozingUseCase =
+ ObserveIsDozingUseCase(
+ repository = repository,
+ )
+ dozeAmountUseCase =
+ ObserveDozeAmountUseCase(
+ repository = repository,
+ )
+ launchQuickAffordanceUseCase = FakeLaunchKeyguardQuickAffordanceUseCase()
+ homeControlsQuickAffordanceConfig = object : FakeKeyguardQuickAffordanceConfig() {}
+ quickAccessWalletAffordanceConfig = object : FakeKeyguardQuickAffordanceConfig() {}
+ qrCodeScannerAffordanceConfig = object : FakeKeyguardQuickAffordanceConfig() {}
+
+ underTest =
+ KeyguardBottomAreaViewModel(
+ observeQuickAffordanceUseCase =
+ ObserveKeyguardQuickAffordanceUseCase(
+ repository = affordanceRepository,
+ isDozingUseCase = isDozingUseCase,
+ dozeAmountUseCase = dozeAmountUseCase,
+ ),
+ onQuickAffordanceClickedUseCase =
+ OnKeyguardQuickAffordanceClickedUseCase(
+ configs =
+ FakeKeyguardQuickAffordanceConfigs(
+ mapOf(
+ KeyguardQuickAffordancePosition.BOTTOM_START to
+ listOf(
+ homeControlsQuickAffordanceConfig,
+ ),
+ KeyguardQuickAffordancePosition.BOTTOM_END to
+ listOf(
+ quickAccessWalletAffordanceConfig,
+ qrCodeScannerAffordanceConfig,
+ ),
+ ),
+ ),
+ launchAffordanceUseCase = launchQuickAffordanceUseCase,
+ ),
+ observeBottomAreaAlphaUseCase =
+ ObserveBottomAreaAlphaUseCase(
+ repository = repository,
+ ),
+ observeIsDozingUseCase = isDozingUseCase,
+ observeAnimateBottomAreaTransitionsUseCase =
+ ObserveAnimateBottomAreaTransitionsUseCase(
+ repository = repository,
+ ),
+ observeDozeAmountUseCase =
+ ObserveDozeAmountUseCase(
+ repository = repository,
+ ),
+ observeClockPositionUseCase =
+ ObserveClockPositionUseCase(
+ repository = repository,
+ ),
+ burnInHelperWrapper = burnInHelperWrapper,
+ )
+ }
+
+ @Test
+ fun `startButton - present and not dozing - visible model - starts activity on click`() =
+ runBlockingTest {
+ var latest: KeyguardQuickAffordanceViewModel? = null
+ val job = underTest.startButton.onEach { latest = it }.launchIn(this)
+
+ repository.setDozing(false)
+ val testConfig =
+ TestConfig(
+ isVisible = true,
+ icon = mock(),
+ canShowWhileLocked = false,
+ intent = Intent("action"),
+ )
+ val configKey =
+ setUpQuickAffordanceModel(
+ position = KeyguardQuickAffordancePosition.BOTTOM_START,
+ testConfig = testConfig,
+ )
+
+ assertQuickAffordanceViewModel(
+ viewModel = latest,
+ testConfig = testConfig,
+ configKey = configKey,
+ )
+ job.cancel()
+ }
+
+ @Test
+ fun `endButton - present and not dozing - visible model - do nothing on click`() =
+ runBlockingTest {
+ var latest: KeyguardQuickAffordanceViewModel? = null
+ val job = underTest.endButton.onEach { latest = it }.launchIn(this)
+
+ repository.setDozing(false)
+ val config =
+ TestConfig(
+ isVisible = true,
+ icon = mock(),
+ canShowWhileLocked = false,
+ intent =
+ null, // This will cause it to tell the system that the click was handled.
+ )
+ val configKey =
+ setUpQuickAffordanceModel(
+ position = KeyguardQuickAffordancePosition.BOTTOM_END,
+ testConfig = config,
+ )
+
+ assertQuickAffordanceViewModel(
+ viewModel = latest,
+ testConfig = config,
+ configKey = configKey,
+ )
+ job.cancel()
+ }
+
+ @Test
+ fun `startButton - not present and not dozing - model is none`() = runBlockingTest {
+ var latest: KeyguardQuickAffordanceViewModel? = null
+ val job = underTest.startButton.onEach { latest = it }.launchIn(this)
+
+ repository.setDozing(false)
+ val config =
+ TestConfig(
+ isVisible = false,
+ )
+ val configKey =
+ setUpQuickAffordanceModel(
+ position = KeyguardQuickAffordancePosition.BOTTOM_START,
+ testConfig = config,
+ )
+
+ assertQuickAffordanceViewModel(
+ viewModel = latest,
+ testConfig = config,
+ configKey = configKey,
+ )
+ job.cancel()
+ }
+
+ @Test
+ fun `startButton - present but dozing - model is none`() = runBlockingTest {
+ var latest: KeyguardQuickAffordanceViewModel? = null
+ val job = underTest.startButton.onEach { latest = it }.launchIn(this)
+
+ repository.setDozing(true)
+ val config =
+ TestConfig(
+ isVisible = true,
+ icon = mock(),
+ canShowWhileLocked = false,
+ intent = Intent("action"),
+ )
+ val configKey =
+ setUpQuickAffordanceModel(
+ position = KeyguardQuickAffordancePosition.BOTTOM_START,
+ testConfig = config,
+ )
+
+ assertQuickAffordanceViewModel(
+ viewModel = latest,
+ testConfig = TestConfig(isVisible = false),
+ configKey = configKey,
+ )
+ job.cancel()
+ }
+
+ @Test
+ fun animateButtonReveal() = runBlockingTest {
+ val values = mutableListOf<Boolean>()
+ val job = underTest.animateButtonReveal.onEach(values::add).launchIn(this)
+
+ repository.setAnimateDozingTransitions(true)
+ repository.setAnimateDozingTransitions(false)
+
+ assertThat(values).isEqualTo(listOf(false, true, false))
+ job.cancel()
+ }
+
+ @Test
+ fun isOverlayContainerVisible() = runBlockingTest {
+ val values = mutableListOf<Boolean>()
+ val job = underTest.isOverlayContainerVisible.onEach(values::add).launchIn(this)
+
+ repository.setDozing(true)
+ repository.setDozing(false)
+
+ assertThat(values).isEqualTo(listOf(true, false, true))
+ job.cancel()
+ }
+
+ @Test
+ fun alpha() = runBlockingTest {
+ val values = mutableListOf<Float>()
+ val job = underTest.alpha.onEach(values::add).launchIn(this)
+
+ repository.setBottomAreaAlpha(0.1f)
+ repository.setBottomAreaAlpha(0.5f)
+ repository.setBottomAreaAlpha(0.2f)
+ repository.setBottomAreaAlpha(0f)
+
+ assertThat(values).isEqualTo(listOf(1f, 0.1f, 0.5f, 0.2f, 0f))
+ job.cancel()
+ }
+
+ @Test
+ fun isIndicationAreaPadded() = runBlockingTest {
+ val values = mutableListOf<Boolean>()
+ val job = underTest.isIndicationAreaPadded.onEach(values::add).launchIn(this)
+
+ setUpQuickAffordanceModel(
+ position = KeyguardQuickAffordancePosition.BOTTOM_START,
+ testConfig =
+ TestConfig(
+ isVisible = true,
+ icon = mock(),
+ canShowWhileLocked = true,
+ )
+ )
+ setUpQuickAffordanceModel(
+ position = KeyguardQuickAffordancePosition.BOTTOM_END,
+ testConfig =
+ TestConfig(
+ isVisible = true,
+ icon = mock(),
+ canShowWhileLocked = false,
+ )
+ )
+ setUpQuickAffordanceModel(
+ position = KeyguardQuickAffordancePosition.BOTTOM_START,
+ testConfig =
+ TestConfig(
+ isVisible = false,
+ )
+ )
+ setUpQuickAffordanceModel(
+ position = KeyguardQuickAffordancePosition.BOTTOM_END,
+ testConfig =
+ TestConfig(
+ isVisible = false,
+ )
+ )
+
+ assertThat(values)
+ .isEqualTo(
+ listOf(
+ // Initially, no button is visible so the indication area is not padded.
+ false,
+ // Once we add the first visible button, the indication area becomes padded.
+ // This
+ // continues to be true after we add the second visible button and even after we
+ // make the first button not visible anymore.
+ true,
+ // Once both buttons are not visible, the indication area is, again, not padded.
+ false,
+ )
+ )
+ job.cancel()
+ }
+
+ @Test
+ fun indicationAreaTranslationX() = runBlockingTest {
+ val values = mutableListOf<Float>()
+ val job = underTest.indicationAreaTranslationX.onEach(values::add).launchIn(this)
+
+ repository.setClockPosition(100, 100)
+ repository.setClockPosition(200, 100)
+ repository.setClockPosition(200, 200)
+ repository.setClockPosition(300, 100)
+
+ assertThat(values).isEqualTo(listOf(0f, 100f, 200f, 300f))
+ job.cancel()
+ }
+
+ @Test
+ fun indicationAreaTranslationY() = runBlockingTest {
+ val values = mutableListOf<Float>()
+ val job =
+ underTest
+ .indicationAreaTranslationY(DEFAULT_BURN_IN_OFFSET)
+ .onEach(values::add)
+ .launchIn(this)
+
+ val expectedTranslationValues =
+ listOf(
+ -0f, // Negative 0 - apparently there's a difference in floating point arithmetic -
+ // FML
+ setDozeAmountAndCalculateExpectedTranslationY(0.1f),
+ setDozeAmountAndCalculateExpectedTranslationY(0.2f),
+ setDozeAmountAndCalculateExpectedTranslationY(0.5f),
+ setDozeAmountAndCalculateExpectedTranslationY(1f),
+ )
+
+ assertThat(values).isEqualTo(expectedTranslationValues)
+ job.cancel()
+ }
+
+ private fun setDozeAmountAndCalculateExpectedTranslationY(dozeAmount: Float): Float {
+ repository.setDozeAmount(dozeAmount)
+ return dozeAmount * (RETURNED_BURN_IN_OFFSET - DEFAULT_BURN_IN_OFFSET)
+ }
+
+ private suspend fun setUpQuickAffordanceModel(
+ position: KeyguardQuickAffordancePosition,
+ testConfig: TestConfig,
+ ): KClass<*> {
+ val config =
+ when (position) {
+ KeyguardQuickAffordancePosition.BOTTOM_START -> homeControlsQuickAffordanceConfig
+ KeyguardQuickAffordancePosition.BOTTOM_END -> quickAccessWalletAffordanceConfig
+ }
+
+ affordanceRepository.setModel(
+ position = position,
+ model =
+ if (testConfig.isVisible) {
+ if (testConfig.intent != null) {
+ config.onClickedResult =
+ KeyguardQuickAffordanceConfig.OnClickedResult.StartActivity(
+ intent = testConfig.intent,
+ canShowWhileLocked = testConfig.canShowWhileLocked,
+ )
+ }
+ KeyguardQuickAffordanceModel.Visible(
+ configKey = config::class,
+ icon = testConfig.icon ?: error("Icon is unexpectedly null!"),
+ contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
+ )
+ } else {
+ KeyguardQuickAffordanceModel.Hidden
+ }
+ )
+ return config::class
+ }
+
+ private fun assertQuickAffordanceViewModel(
+ viewModel: KeyguardQuickAffordanceViewModel?,
+ testConfig: TestConfig,
+ configKey: KClass<*>,
+ ) {
+ checkNotNull(viewModel)
+ assertThat(viewModel.isVisible).isEqualTo(testConfig.isVisible)
+ if (testConfig.isVisible) {
+ assertThat(viewModel.icon).isEqualTo(testConfig.icon)
+ viewModel.onClicked.invoke(
+ KeyguardQuickAffordanceViewModel.OnClickedParameters(
+ configKey = configKey,
+ animationController = animationController,
+ )
+ )
+ testConfig.intent?.let { intent ->
+ assertThat(launchQuickAffordanceUseCase.invocations)
+ .isEqualTo(
+ listOf(
+ FakeLaunchKeyguardQuickAffordanceUseCase.Invocation(
+ intent = intent,
+ canShowWhileLocked = testConfig.canShowWhileLocked,
+ animationController = animationController,
+ )
+ )
+ )
+ }
+ ?: run { assertThat(launchQuickAffordanceUseCase.invocations).isEmpty() }
+ } else {
+ assertThat(viewModel.isVisible).isFalse()
+ }
+ }
+
+ private data class TestConfig(
+ val isVisible: Boolean,
+ val icon: ContainedDrawable? = null,
+ val canShowWhileLocked: Boolean = false,
+ val intent: Intent? = null,
+ ) {
+ init {
+ check(!isVisible || icon != null) { "Must supply non-null icon if visible!" }
+ }
+ }
+
+ companion object {
+ private const val DEFAULT_BURN_IN_OFFSET = 5
+ private const val RETURNED_BURN_IN_OFFSET = 3
+ private const val CONTENT_DESCRIPTION_RESOURCE_ID = 1337
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/lifecycle/RepeatWhenAttachedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/lifecycle/RepeatWhenAttachedTest.kt
new file mode 100644
index 0000000..80f3e46
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/lifecycle/RepeatWhenAttachedTest.kt
@@ -0,0 +1,319 @@
+/*
+ * 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.lifecycle
+
+import android.testing.TestableLooper.RunWithLooper
+import android.view.View
+import android.view.ViewTreeObserver
+import androidx.arch.core.executor.ArchTaskExecutor
+import androidx.arch.core.executor.TaskExecutor
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleOwner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.Assert
+import com.android.systemui.util.mockito.argumentCaptor
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.DisposableHandle
+import kotlinx.coroutines.test.runBlockingTest
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.TestWatcher
+import org.junit.runner.Description
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mock
+import org.mockito.Mockito.any
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.junit.MockitoJUnit
+
+@SmallTest
+@RunWith(JUnit4::class)
+@RunWithLooper
+class RepeatWhenAttachedTest : SysuiTestCase() {
+
+ @JvmField @Rule val mockito = MockitoJUnit.rule()
+ @JvmField @Rule val instantTaskExecutor = InstantTaskExecutorRule()
+
+ @Mock private lateinit var view: View
+ @Mock private lateinit var viewTreeObserver: ViewTreeObserver
+
+ private lateinit var block: Block
+ private lateinit var attachListeners: MutableList<View.OnAttachStateChangeListener>
+
+ @Before
+ fun setUp() {
+ Assert.setTestThread(Thread.currentThread())
+ whenever(view.viewTreeObserver).thenReturn(viewTreeObserver)
+ whenever(view.windowVisibility).thenReturn(View.GONE)
+ whenever(view.hasWindowFocus()).thenReturn(false)
+ attachListeners = mutableListOf()
+ whenever(view.addOnAttachStateChangeListener(any())).then {
+ attachListeners.add(it.arguments[0] as View.OnAttachStateChangeListener)
+ }
+ whenever(view.removeOnAttachStateChangeListener(any())).then {
+ attachListeners.remove(it.arguments[0] as View.OnAttachStateChangeListener)
+ }
+ block = Block()
+ }
+
+ @Test(expected = IllegalStateException::class)
+ fun `repeatWhenAttached - enforces main thread`() = runBlockingTest {
+ Assert.setTestThread(null)
+
+ repeatWhenAttached()
+ }
+
+ @Test(expected = IllegalStateException::class)
+ fun `repeatWhenAttached - dispose enforces main thread`() = runBlockingTest {
+ val disposableHandle = repeatWhenAttached()
+ Assert.setTestThread(null)
+
+ disposableHandle.dispose()
+ }
+
+ @Test
+ fun `repeatWhenAttached - view starts detached - runs block when attached`() = runBlockingTest {
+ whenever(view.isAttachedToWindow).thenReturn(false)
+ repeatWhenAttached()
+ assertThat(block.invocationCount).isEqualTo(0)
+
+ whenever(view.isAttachedToWindow).thenReturn(true)
+ attachListeners.last().onViewAttachedToWindow(view)
+
+ assertThat(block.invocationCount).isEqualTo(1)
+ assertThat(block.latestLifecycleState).isEqualTo(Lifecycle.State.CREATED)
+ }
+
+ @Test
+ fun `repeatWhenAttached - view already attached - immediately runs block`() = runBlockingTest {
+ whenever(view.isAttachedToWindow).thenReturn(true)
+
+ repeatWhenAttached()
+
+ assertThat(block.invocationCount).isEqualTo(1)
+ assertThat(block.latestLifecycleState).isEqualTo(Lifecycle.State.CREATED)
+ }
+
+ @Test
+ fun `repeatWhenAttached - starts visible without focus - STARTED`() = runBlockingTest {
+ whenever(view.isAttachedToWindow).thenReturn(true)
+ whenever(view.windowVisibility).thenReturn(View.VISIBLE)
+
+ repeatWhenAttached()
+
+ assertThat(block.invocationCount).isEqualTo(1)
+ assertThat(block.latestLifecycleState).isEqualTo(Lifecycle.State.STARTED)
+ }
+
+ @Test
+ fun `repeatWhenAttached - starts with focus but invisible - CREATED`() = runBlockingTest {
+ whenever(view.isAttachedToWindow).thenReturn(true)
+ whenever(view.hasWindowFocus()).thenReturn(true)
+
+ repeatWhenAttached()
+
+ assertThat(block.invocationCount).isEqualTo(1)
+ assertThat(block.latestLifecycleState).isEqualTo(Lifecycle.State.CREATED)
+ }
+
+ @Test
+ fun `repeatWhenAttached - starts visible and with focus - RESUMED`() = runBlockingTest {
+ whenever(view.isAttachedToWindow).thenReturn(true)
+ whenever(view.windowVisibility).thenReturn(View.VISIBLE)
+ whenever(view.hasWindowFocus()).thenReturn(true)
+
+ repeatWhenAttached()
+
+ assertThat(block.invocationCount).isEqualTo(1)
+ assertThat(block.latestLifecycleState).isEqualTo(Lifecycle.State.RESUMED)
+ }
+
+ @Test
+ fun `repeatWhenAttached - becomes visible without focus - STARTED`() = runBlockingTest {
+ whenever(view.isAttachedToWindow).thenReturn(true)
+ repeatWhenAttached()
+ val listenerCaptor = argumentCaptor<ViewTreeObserver.OnWindowVisibilityChangeListener>()
+ verify(viewTreeObserver).addOnWindowVisibilityChangeListener(listenerCaptor.capture())
+
+ whenever(view.windowVisibility).thenReturn(View.VISIBLE)
+ listenerCaptor.value.onWindowVisibilityChanged(View.VISIBLE)
+
+ assertThat(block.invocationCount).isEqualTo(1)
+ assertThat(block.latestLifecycleState).isEqualTo(Lifecycle.State.STARTED)
+ }
+
+ @Test
+ fun `repeatWhenAttached - gains focus but invisible - CREATED`() = runBlockingTest {
+ whenever(view.isAttachedToWindow).thenReturn(true)
+ repeatWhenAttached()
+ val listenerCaptor = argumentCaptor<ViewTreeObserver.OnWindowFocusChangeListener>()
+ verify(viewTreeObserver).addOnWindowFocusChangeListener(listenerCaptor.capture())
+
+ whenever(view.hasWindowFocus()).thenReturn(true)
+ listenerCaptor.value.onWindowFocusChanged(true)
+
+ assertThat(block.invocationCount).isEqualTo(1)
+ assertThat(block.latestLifecycleState).isEqualTo(Lifecycle.State.CREATED)
+ }
+
+ @Test
+ fun `repeatWhenAttached - becomes visible and gains focus - RESUMED`() = runBlockingTest {
+ whenever(view.isAttachedToWindow).thenReturn(true)
+ repeatWhenAttached()
+ val visibleCaptor = argumentCaptor<ViewTreeObserver.OnWindowVisibilityChangeListener>()
+ verify(viewTreeObserver).addOnWindowVisibilityChangeListener(visibleCaptor.capture())
+ val focusCaptor = argumentCaptor<ViewTreeObserver.OnWindowFocusChangeListener>()
+ verify(viewTreeObserver).addOnWindowFocusChangeListener(focusCaptor.capture())
+
+ whenever(view.windowVisibility).thenReturn(View.VISIBLE)
+ visibleCaptor.value.onWindowVisibilityChanged(View.VISIBLE)
+ whenever(view.hasWindowFocus()).thenReturn(true)
+ focusCaptor.value.onWindowFocusChanged(true)
+
+ assertThat(block.invocationCount).isEqualTo(1)
+ assertThat(block.latestLifecycleState).isEqualTo(Lifecycle.State.RESUMED)
+ }
+
+ @Test
+ fun `repeatWhenAttached - view gets detached - destroys the lifecycle`() = runBlockingTest {
+ whenever(view.isAttachedToWindow).thenReturn(true)
+ repeatWhenAttached()
+
+ whenever(view.isAttachedToWindow).thenReturn(false)
+ attachListeners.last().onViewDetachedFromWindow(view)
+
+ assertThat(block.invocationCount).isEqualTo(1)
+ assertThat(block.latestLifecycleState).isEqualTo(Lifecycle.State.DESTROYED)
+ }
+
+ @Test
+ fun `repeatWhenAttached - view gets reattached - recreates a lifecycle`() = runBlockingTest {
+ whenever(view.isAttachedToWindow).thenReturn(true)
+ repeatWhenAttached()
+ whenever(view.isAttachedToWindow).thenReturn(false)
+ attachListeners.last().onViewDetachedFromWindow(view)
+
+ whenever(view.isAttachedToWindow).thenReturn(true)
+ attachListeners.last().onViewAttachedToWindow(view)
+
+ assertThat(block.invocationCount).isEqualTo(2)
+ assertThat(block.invocations[0].lifecycleState).isEqualTo(Lifecycle.State.DESTROYED)
+ assertThat(block.invocations[1].lifecycleState).isEqualTo(Lifecycle.State.CREATED)
+ }
+
+ @Test
+ fun `repeatWhenAttached - dispose attached`() = runBlockingTest {
+ whenever(view.isAttachedToWindow).thenReturn(true)
+ val handle = repeatWhenAttached()
+
+ handle.dispose()
+
+ assertThat(attachListeners).isEmpty()
+ assertThat(block.invocationCount).isEqualTo(1)
+ assertThat(block.latestLifecycleState).isEqualTo(Lifecycle.State.DESTROYED)
+ }
+
+ @Test
+ fun `repeatWhenAttached - dispose never attached`() = runBlockingTest {
+ whenever(view.isAttachedToWindow).thenReturn(false)
+ val handle = repeatWhenAttached()
+
+ handle.dispose()
+
+ assertThat(attachListeners).isEmpty()
+ assertThat(block.invocationCount).isEqualTo(0)
+ }
+
+ @Test
+ fun `repeatWhenAttached - dispose previously attached now detached`() = runBlockingTest {
+ whenever(view.isAttachedToWindow).thenReturn(true)
+ val handle = repeatWhenAttached()
+ attachListeners.last().onViewDetachedFromWindow(view)
+
+ handle.dispose()
+
+ assertThat(attachListeners).isEmpty()
+ assertThat(block.invocationCount).isEqualTo(1)
+ assertThat(block.latestLifecycleState).isEqualTo(Lifecycle.State.DESTROYED)
+ }
+
+ private fun CoroutineScope.repeatWhenAttached(): DisposableHandle {
+ return view.repeatWhenAttached(
+ coroutineContext = coroutineContext,
+ block = block,
+ )
+ }
+
+ private class Block : suspend LifecycleOwner.(View) -> Unit {
+ data class Invocation(
+ val lifecycleOwner: LifecycleOwner,
+ ) {
+ val lifecycleState: Lifecycle.State
+ get() = lifecycleOwner.lifecycle.currentState
+ }
+
+ private val _invocations = mutableListOf<Invocation>()
+ val invocations: List<Invocation> = _invocations
+ val invocationCount: Int
+ get() = _invocations.size
+ val latestLifecycleState: Lifecycle.State
+ get() = _invocations.last().lifecycleState
+
+ override suspend fun invoke(lifecycleOwner: LifecycleOwner, view: View) {
+ _invocations.add(Invocation(lifecycleOwner))
+ }
+ }
+
+ /**
+ * Test rule that makes ArchTaskExecutor main thread assertions pass. There is one such assert
+ * in LifecycleRegistry.
+ */
+ class InstantTaskExecutorRule : TestWatcher() {
+ // TODO(b/240620122): This is a copy of
+ // androidx/arch/core/executor/testing/InstantTaskExecutorRule which should be replaced
+ // with a dependency on the real library once b/ is cleared.
+ override fun starting(description: Description) {
+ super.starting(description)
+ ArchTaskExecutor.getInstance()
+ .setDelegate(
+ object : TaskExecutor() {
+ override fun executeOnDiskIO(runnable: Runnable) {
+ runnable.run()
+ }
+
+ override fun postToMainThread(runnable: Runnable) {
+ runnable.run()
+ }
+
+ override fun isMainThread(): Boolean {
+ return true
+ }
+ }
+ )
+ }
+
+ override fun finished(description: Description) {
+ super.finished(description)
+ ArchTaskExecutor.getInstance().setDelegate(null)
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/lifecycle/WindowAddedViewLifecycleOwnerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/lifecycle/WindowAddedViewLifecycleOwnerTest.kt
deleted file mode 100644
index 4f5c570..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/lifecycle/WindowAddedViewLifecycleOwnerTest.kt
+++ /dev/null
@@ -1,150 +0,0 @@
-package com.android.systemui.lifecycle
-
-import android.view.View
-import android.view.ViewTreeObserver
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.LifecycleRegistry
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.argumentCaptor
-import com.android.systemui.util.mockito.capture
-import com.google.common.truth.Truth.assertThat
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import org.mockito.Mock
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
-import org.mockito.MockitoAnnotations
-
-@SmallTest
-@RunWith(JUnit4::class)
-class WindowAddedViewLifecycleOwnerTest : SysuiTestCase() {
-
- @Mock lateinit var view: View
- @Mock lateinit var viewTreeObserver: ViewTreeObserver
-
- private lateinit var underTest: WindowAddedViewLifecycleOwner
-
- @Before
- fun setUp() {
- MockitoAnnotations.initMocks(this)
- whenever(view.viewTreeObserver).thenReturn(viewTreeObserver)
- whenever(view.isAttachedToWindow).thenReturn(false)
- whenever(view.windowVisibility).thenReturn(View.INVISIBLE)
- whenever(view.hasWindowFocus()).thenReturn(false)
-
- underTest = WindowAddedViewLifecycleOwner(view) { LifecycleRegistry.createUnsafe(it) }
- }
-
- @Test
- fun `detached - invisible - does not have focus -- INITIALIZED`() {
- assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.INITIALIZED)
- }
-
- @Test
- fun `detached - invisible - has focus -- INITIALIZED`() {
- whenever(view.hasWindowFocus()).thenReturn(true)
- val captor = argumentCaptor<ViewTreeObserver.OnWindowFocusChangeListener>()
- verify(viewTreeObserver).addOnWindowFocusChangeListener(capture(captor))
- captor.value.onWindowFocusChanged(true)
-
- assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.INITIALIZED)
- }
-
- @Test
- fun `detached - visible - does not have focus -- INITIALIZED`() {
- whenever(view.windowVisibility).thenReturn(View.VISIBLE)
- val captor = argumentCaptor<ViewTreeObserver.OnWindowVisibilityChangeListener>()
- verify(viewTreeObserver).addOnWindowVisibilityChangeListener(capture(captor))
- captor.value.onWindowVisibilityChanged(View.VISIBLE)
-
- assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.INITIALIZED)
- }
-
- @Test
- fun `detached - visible - has focus -- INITIALIZED`() {
- whenever(view.hasWindowFocus()).thenReturn(true)
- val focusCaptor = argumentCaptor<ViewTreeObserver.OnWindowFocusChangeListener>()
- verify(viewTreeObserver).addOnWindowFocusChangeListener(capture(focusCaptor))
- focusCaptor.value.onWindowFocusChanged(true)
-
- whenever(view.windowVisibility).thenReturn(View.VISIBLE)
- val visibilityCaptor = argumentCaptor<ViewTreeObserver.OnWindowVisibilityChangeListener>()
- verify(viewTreeObserver).addOnWindowVisibilityChangeListener(capture(visibilityCaptor))
- visibilityCaptor.value.onWindowVisibilityChanged(View.VISIBLE)
-
- assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.INITIALIZED)
- }
-
- @Test
- fun `attached - invisible - does not have focus -- CREATED`() {
- whenever(view.isAttachedToWindow).thenReturn(true)
- val captor = argumentCaptor<ViewTreeObserver.OnWindowAttachListener>()
- verify(viewTreeObserver).addOnWindowAttachListener(capture(captor))
- captor.value.onWindowAttached()
-
- assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.CREATED)
- }
-
- @Test
- fun `attached - invisible - has focus -- CREATED`() {
- whenever(view.isAttachedToWindow).thenReturn(true)
- val attachCaptor = argumentCaptor<ViewTreeObserver.OnWindowAttachListener>()
- verify(viewTreeObserver).addOnWindowAttachListener(capture(attachCaptor))
- attachCaptor.value.onWindowAttached()
-
- whenever(view.hasWindowFocus()).thenReturn(true)
- val focusCaptor = argumentCaptor<ViewTreeObserver.OnWindowFocusChangeListener>()
- verify(viewTreeObserver).addOnWindowFocusChangeListener(capture(focusCaptor))
- focusCaptor.value.onWindowFocusChanged(true)
-
- assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.CREATED)
- }
-
- @Test
- fun `attached - visible - does not have focus -- STARTED`() {
- whenever(view.isAttachedToWindow).thenReturn(true)
- val attachCaptor = argumentCaptor<ViewTreeObserver.OnWindowAttachListener>()
- verify(viewTreeObserver).addOnWindowAttachListener(capture(attachCaptor))
- attachCaptor.value.onWindowAttached()
-
- whenever(view.windowVisibility).thenReturn(View.VISIBLE)
- val visibilityCaptor = argumentCaptor<ViewTreeObserver.OnWindowVisibilityChangeListener>()
- verify(viewTreeObserver).addOnWindowVisibilityChangeListener(capture(visibilityCaptor))
- visibilityCaptor.value.onWindowVisibilityChanged(View.VISIBLE)
-
- assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.STARTED)
- }
-
- @Test
- fun `attached - visible - has focus -- RESUMED`() {
- whenever(view.isAttachedToWindow).thenReturn(true)
- val attachCaptor = argumentCaptor<ViewTreeObserver.OnWindowAttachListener>()
- verify(viewTreeObserver).addOnWindowAttachListener(capture(attachCaptor))
- attachCaptor.value.onWindowAttached()
-
- whenever(view.hasWindowFocus()).thenReturn(true)
- val focusCaptor = argumentCaptor<ViewTreeObserver.OnWindowFocusChangeListener>()
- verify(viewTreeObserver).addOnWindowFocusChangeListener(capture(focusCaptor))
- focusCaptor.value.onWindowFocusChanged(true)
-
- whenever(view.windowVisibility).thenReturn(View.VISIBLE)
- val visibilityCaptor = argumentCaptor<ViewTreeObserver.OnWindowVisibilityChangeListener>()
- verify(viewTreeObserver).addOnWindowVisibilityChangeListener(capture(visibilityCaptor))
- visibilityCaptor.value.onWindowVisibilityChanged(View.VISIBLE)
-
- assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.RESUMED)
- }
-
- @Test
- fun dispose() {
- underTest.dispose()
-
- verify(viewTreeObserver).removeOnWindowAttachListener(any())
- verify(viewTreeObserver).removeOnWindowVisibilityChangeListener(any())
- verify(viewTreeObserver).removeOnWindowFocusChangeListener(any())
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/LogBufferTest.kt b/packages/SystemUI/tests/src/com/android/systemui/log/LogBufferTest.kt
new file mode 100644
index 0000000..4abb973
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/log/LogBufferTest.kt
@@ -0,0 +1,167 @@
+package com.android.systemui.log
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import java.io.PrintWriter
+import java.io.StringWriter
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.junit.MockitoJUnitRunner
+
+@SmallTest
+@RunWith(MockitoJUnitRunner::class)
+class LogBufferTest : SysuiTestCase() {
+ private lateinit var buffer: LogBuffer
+
+ private lateinit var outputWriter: StringWriter
+
+ @Mock
+ private lateinit var logcatEchoTracker: LogcatEchoTracker
+
+ @Before
+ fun setup() {
+ outputWriter = StringWriter()
+ buffer = createBuffer(UNBOUNDED_STACK_TRACE, NESTED_TRACE_DEPTH)
+ }
+
+ private fun createBuffer(rootTraceDepth: Int, nestedTraceDepth: Int): LogBuffer {
+ return LogBuffer("TestBuffer",
+ 1,
+ logcatEchoTracker,
+ false,
+ rootStackTraceDepth = rootTraceDepth,
+ nestedStackTraceDepth = nestedTraceDepth)
+ }
+
+ @Test
+ fun log_shouldSaveLogToBuffer() {
+ buffer.log("Test", LogLevel.INFO, "Some test message")
+
+ val dumpedString = dumpBuffer()
+
+ assertThat(dumpedString).contains("Some test message")
+ }
+
+ @Test
+ fun log_shouldRotateIfLogBufferIsFull() {
+ buffer.log("Test", LogLevel.INFO, "This should be rotated")
+ buffer.log("Test", LogLevel.INFO, "New test message")
+
+ val dumpedString = dumpBuffer()
+
+ assertThat(dumpedString).contains("New test message")
+ }
+
+ @Test
+ fun dump_writesExceptionAndStacktraceLimitedToGivenDepth() {
+ buffer = createBuffer(rootTraceDepth = 2, nestedTraceDepth = -1)
+ // stack trace depth of 5
+ val exception = createTestException("Exception message", "TestClass", 5)
+ buffer.log("Tag", LogLevel.ERROR, { str1 = "Extra message" }, { str1!! }, exception)
+
+ val dumpedString = dumpBuffer()
+
+ // logs are limited to depth 2
+ assertThat(dumpedString).contains("E Tag: Extra message")
+ assertThat(dumpedString).contains("E Tag: java.lang.RuntimeException: Exception message")
+ assertThat(dumpedString).contains("E Tag: \tat TestClass.TestMethod(TestClass.java:1)")
+ assertThat(dumpedString).contains("E Tag: \tat TestClass.TestMethod(TestClass.java:2)")
+ assertThat(dumpedString)
+ .doesNotContain("E Tag: \tat TestClass.TestMethod(TestClass.java:3)")
+ }
+
+ @Test
+ fun dump_writesCauseAndStacktraceLimitedToGivenDepth() {
+ buffer = createBuffer(rootTraceDepth = 0, nestedTraceDepth = 2)
+ val exception = createTestException("Exception message",
+ "TestClass",
+ 1,
+ cause = createTestException("The real cause!", "TestClass", 5))
+ buffer.log("Tag", LogLevel.ERROR, { str1 = "Extra message" }, { str1!! }, exception)
+
+ val dumpedString = dumpBuffer()
+
+ // logs are limited to depth 2
+ assertThat(dumpedString)
+ .contains("E Tag: Caused by: java.lang.RuntimeException: The real cause!")
+ assertThat(dumpedString).contains("E Tag: \tat TestClass.TestMethod(TestClass.java:1)")
+ assertThat(dumpedString).contains("E Tag: \tat TestClass.TestMethod(TestClass.java:2)")
+ assertThat(dumpedString)
+ .doesNotContain("E Tag: \tat TestClass.TestMethod(TestClass.java:3)")
+ }
+
+ @Test
+ fun dump_writesSuppressedExceptionAndStacktraceLimitedToGivenDepth() {
+ buffer = createBuffer(rootTraceDepth = 0, nestedTraceDepth = 2)
+ val exception = RuntimeException("Root exception message")
+ exception.addSuppressed(
+ createTestException(
+ "First suppressed exception",
+ "FirstClass",
+ 5,
+ createTestException("Cause of suppressed exp", "ThirdClass", 5)
+ ))
+ exception.addSuppressed(
+ createTestException("Second suppressed exception", "SecondClass", 5))
+ buffer.log("Tag", LogLevel.ERROR, { str1 = "Extra message" }, { str1!! }, exception)
+
+ val dumpedStr = dumpBuffer()
+
+ // logs are limited to depth 2
+ // first suppressed exception
+ assertThat(dumpedStr)
+ .contains("E Tag: Suppressed: " +
+ "java.lang.RuntimeException: First suppressed exception")
+ assertThat(dumpedStr).contains("E Tag: \tat FirstClass.TestMethod(FirstClass.java:1)")
+ assertThat(dumpedStr).contains("E Tag: \tat FirstClass.TestMethod(FirstClass.java:2)")
+ assertThat(dumpedStr)
+ .doesNotContain("E Tag: \tat FirstClass.TestMethod(FirstClass.java:3)")
+
+ assertThat(dumpedStr)
+ .contains("E Tag: Caused by: java.lang.RuntimeException: Cause of suppressed exp")
+ assertThat(dumpedStr).contains("E Tag: \tat ThirdClass.TestMethod(ThirdClass.java:1)")
+ assertThat(dumpedStr).contains("E Tag: \tat ThirdClass.TestMethod(ThirdClass.java:2)")
+ assertThat(dumpedStr)
+ .doesNotContain("E Tag: \tat ThirdClass.TestMethod(ThirdClass.java:3)")
+
+ // second suppressed exception
+ assertThat(dumpedStr)
+ .contains("E Tag: Suppressed: " +
+ "java.lang.RuntimeException: Second suppressed exception")
+ assertThat(dumpedStr).contains("E Tag: \tat SecondClass.TestMethod(SecondClass.java:1)")
+ assertThat(dumpedStr).contains("E Tag: \tat SecondClass.TestMethod(SecondClass.java:2)")
+ assertThat(dumpedStr)
+ .doesNotContain("E Tag: \tat SecondClass.TestMethod(SecondClass.java:3)")
+ }
+
+ private fun createTestException(
+ message: String,
+ errorClass: String,
+ stackTraceLength: Int,
+ cause: Throwable? = null
+ ): Exception {
+ val exception = RuntimeException(message, cause)
+ exception.stackTrace = createStackTraceElements(errorClass, stackTraceLength)
+ return exception
+ }
+
+ private fun dumpBuffer(): String {
+ buffer.dump(PrintWriter(outputWriter), tailLength = 100)
+ return outputWriter.toString()
+ }
+
+ private fun createStackTraceElements(
+ errorClass: String,
+ stackTraceLength: Int
+ ): Array<StackTraceElement> {
+ return (1..stackTraceLength).map { lineNumber ->
+ StackTraceElement(errorClass,
+ "TestMethod",
+ "$errorClass.java",
+ lineNumber)
+ }.toTypedArray()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt
index 2eb4783..5539786 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt
@@ -32,6 +32,8 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.statusbar.gesture.TapGestureDetector
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
@@ -53,7 +55,7 @@
@SmallTest
class MediaTttChipControllerCommonTest : SysuiTestCase() {
- private lateinit var controllerCommon: MediaTttChipControllerCommon<ChipInfo>
+ private lateinit var controllerCommon: TestControllerCommon
private lateinit var fakeClock: FakeSystemClock
private lateinit var fakeExecutor: FakeExecutor
@@ -68,6 +70,8 @@
@Mock
private lateinit var accessibilityManager: AccessibilityManager
@Mock
+ private lateinit var configurationController: ConfigurationController
+ @Mock
private lateinit var windowManager: WindowManager
@Mock
private lateinit var viewUtil: ViewUtil
@@ -98,14 +102,15 @@
fakeExecutor = FakeExecutor(fakeClock)
controllerCommon = TestControllerCommon(
- context,
- logger,
- windowManager,
- viewUtil,
- fakeExecutor,
- accessibilityManager,
- tapGestureDetector,
- powerManager
+ context,
+ logger,
+ windowManager,
+ viewUtil,
+ fakeExecutor,
+ accessibilityManager,
+ configurationController,
+ tapGestureDetector,
+ powerManager,
)
}
@@ -186,6 +191,19 @@
}
@Test
+ fun displayScaleChange_chipReinflatedWithMostRecentState() {
+ controllerCommon.displayChip(getState(name = "First name"))
+ controllerCommon.displayChip(getState(name = "Second name"))
+ reset(windowManager)
+
+ getConfigurationListener().onDensityOrFontScaleChanged()
+
+ verify(windowManager).removeView(any())
+ verify(windowManager).addView(any(), any())
+ assertThat(controllerCommon.mostRecentChipInfo?.name).isEqualTo("Second name")
+ }
+
+ @Test
fun removeChip_chipRemovedAndGestureDetectionStoppedAndRemovalLogged() {
// First, add the chip
controllerCommon.displayChip(getState())
@@ -341,7 +359,7 @@
verify(windowManager, never()).removeView(any())
}
- private fun getState() = ChipInfo()
+ private fun getState(name: String = "name") = ChipInfo(name)
private fun getChipView(): ViewGroup {
val viewCaptor = ArgumentCaptor.forClass(View::class.java)
@@ -351,6 +369,12 @@
private fun ViewGroup.getAppIconView() = this.requireViewById<ImageView>(R.id.app_icon)
+ private fun getConfigurationListener(): ConfigurationListener {
+ val callbackCaptor = argumentCaptor<ConfigurationListener>()
+ verify(configurationController).addCallback(capture(callbackCaptor))
+ return callbackCaptor.value
+ }
+
inner class TestControllerCommon(
context: Context,
logger: MediaTttLogger,
@@ -358,8 +382,9 @@
viewUtil: ViewUtil,
@Main mainExecutor: DelayableExecutor,
accessibilityManager: AccessibilityManager,
+ configurationController: ConfigurationController,
tapGestureDetector: TapGestureDetector,
- powerManager: PowerManager
+ powerManager: PowerManager,
) : MediaTttChipControllerCommon<ChipInfo>(
context,
logger,
@@ -367,16 +392,22 @@
viewUtil,
mainExecutor,
accessibilityManager,
+ configurationController,
tapGestureDetector,
powerManager,
- R.layout.media_ttt_chip
+ R.layout.media_ttt_chip,
) {
+ var mostRecentChipInfo: ChipInfo? = null
+
override val windowLayoutParams = commonWindowLayoutParams
- override fun updateChipView(chipInfo: ChipInfo, currentChipView: ViewGroup) {}
+ override fun updateChipView(newChipInfo: ChipInfo, currentChipView: ViewGroup) {
+ super.updateChipView(newChipInfo, currentChipView)
+ mostRecentChipInfo = newChipInfo
+ }
override fun getIconSize(isAppIcon: Boolean): Int = ICON_SIZE
}
- inner class ChipInfo : ChipInfoCommon {
+ inner class ChipInfo(val name: String) : ChipInfoCommon {
override fun getTimeoutMs() = 1L
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
index bbc5641..7c5d077 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
@@ -37,6 +37,7 @@
import com.android.systemui.media.taptotransfer.common.MediaTttLogger
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.gesture.TapGestureDetector
+import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
@@ -68,6 +69,8 @@
@Mock
private lateinit var accessibilityManager: AccessibilityManager
@Mock
+ private lateinit var configurationController: ConfigurationController
+ @Mock
private lateinit var powerManager: PowerManager
@Mock
private lateinit var windowManager: WindowManager
@@ -103,6 +106,7 @@
viewUtil,
FakeExecutor(FakeSystemClock()),
accessibilityManager,
+ configurationController,
TapGestureDetector(context),
powerManager,
Handler.getMain(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt
index 7ca0cd3..e06a27d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt
@@ -38,12 +38,12 @@
import com.android.systemui.media.taptotransfer.common.MediaTttLogger
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.gesture.TapGestureDetector
+import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.time.FakeSystemClock
import com.android.systemui.util.view.ViewUtil
-
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
@@ -52,8 +52,8 @@
import org.mockito.Mock
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
+import org.mockito.Mockito.`when` as whenever
@SmallTest
@RunWith(AndroidTestingRunner::class)
@@ -70,6 +70,8 @@
@Mock
private lateinit var accessibilityManager: AccessibilityManager
@Mock
+ private lateinit var configurationController: ConfigurationController
+ @Mock
private lateinit var powerManager: PowerManager
@Mock
private lateinit var windowManager: WindowManager
@@ -112,6 +114,7 @@
viewUtil,
fakeExecutor,
accessibilityManager,
+ configurationController,
TapGestureDetector(context),
powerManager,
senderUiEventLogger
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java
index 4e3bdea..b0cf061 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java
@@ -41,6 +41,7 @@
import com.android.systemui.Dependency;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.model.SysUiState;
import com.android.systemui.recents.OverviewProxyService;
import com.android.systemui.statusbar.CommandQueue;
@@ -96,7 +97,8 @@
mock(AutoHideController.class),
mock(LightBarController.class),
Optional.of(mock(Pip.class)),
- Optional.of(mock(BackAnimation.class))));
+ Optional.of(mock(BackAnimation.class)),
+ mock(FeatureFlags.class)));
initializeNavigationBars();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierGroupControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierGroupControllerTest.java
index bd794d6..09ce37b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierGroupControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierGroupControllerTest.java
@@ -16,19 +16,26 @@
package com.android.systemui.qs.carrier;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
+import android.content.Context;
+import android.content.Intent;
import android.os.Handler;
+import android.provider.Settings;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.View;
@@ -50,6 +57,7 @@
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;
@@ -85,6 +93,7 @@
private QSCarrierGroupController.OnSingleCarrierChangedListener mOnSingleCarrierChangedListener;
private FakeSlotIndexResolver mSlotIndexResolver;
+ private ClickListenerTextView mNoCarrierTextView;
@Before
public void setup() throws Exception {
@@ -108,7 +117,8 @@
.when(mCarrierTextManager)
.setListening(any(CarrierTextManager.CarrierTextCallback.class));
- when(mQSCarrierGroup.getNoSimTextView()).thenReturn(new TextView(mContext));
+ mNoCarrierTextView = new ClickListenerTextView(mContext);
+ when(mQSCarrierGroup.getNoSimTextView()).thenReturn(mNoCarrierTextView);
when(mQSCarrierGroup.getCarrier1View()).thenReturn(mQSCarrier1);
when(mQSCarrierGroup.getCarrier2View()).thenReturn(mQSCarrier2);
when(mQSCarrierGroup.getCarrier3View()).thenReturn(mQSCarrier3);
@@ -376,6 +386,47 @@
verify(mOnSingleCarrierChangedListener, never()).onSingleCarrierChanged(anyBoolean());
}
+ @Test
+ public void testOnlyInternalViewsHaveClickableListener() {
+ ArgumentCaptor<View.OnClickListener> captor =
+ ArgumentCaptor.forClass(View.OnClickListener.class);
+
+ verify(mQSCarrier1).setOnClickListener(captor.capture());
+ verify(mQSCarrier2).setOnClickListener(captor.getValue());
+ verify(mQSCarrier3).setOnClickListener(captor.getValue());
+
+ assertThat(mNoCarrierTextView.getOnClickListener()).isSameInstanceAs(captor.getValue());
+ verify(mQSCarrierGroup, never()).setOnClickListener(any());
+ }
+
+ @Test
+ public void testOnClickListenerDoesntStartActivityIfViewNotVisible() {
+ ArgumentCaptor<View.OnClickListener> captor =
+ ArgumentCaptor.forClass(View.OnClickListener.class);
+
+ verify(mQSCarrier1).setOnClickListener(captor.capture());
+ when(mQSCarrier1.isVisibleToUser()).thenReturn(false);
+
+ captor.getValue().onClick(mQSCarrier1);
+ verifyZeroInteractions(mActivityStarter);
+ }
+
+ @Test
+ public void testOnClickListenerLaunchesActivityIfViewVisible() {
+ ArgumentCaptor<View.OnClickListener> listenerCaptor =
+ ArgumentCaptor.forClass(View.OnClickListener.class);
+ ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+
+ verify(mQSCarrier1).setOnClickListener(listenerCaptor.capture());
+ when(mQSCarrier1.isVisibleToUser()).thenReturn(true);
+
+ listenerCaptor.getValue().onClick(mQSCarrier1);
+ verify(mActivityStarter)
+ .postStartActivityDismissingKeyguard(intentCaptor.capture(), anyInt());
+ assertThat(intentCaptor.getValue().getAction())
+ .isEqualTo(Settings.ACTION_WIRELESS_SETTINGS);
+ }
+
private class FakeSlotIndexResolver implements QSCarrierGroupController.SlotIndexResolver {
public boolean overrideInvalid;
@@ -384,4 +435,22 @@
return overrideInvalid ? -1 : subscriptionId;
}
}
+
+ private class ClickListenerTextView extends TextView {
+ View.OnClickListener mListener = null;
+
+ ClickListenerTextView(Context context) {
+ super(context);
+ }
+
+ @Override
+ public void setOnClickListener(OnClickListener l) {
+ super.setOnClickListener(l);
+ mListener = l;
+ }
+
+ View.OnClickListener getOnClickListener() {
+ return mListener;
+ }
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
index e2673bb..c9405c8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -97,6 +97,10 @@
import com.android.systemui.fragments.FragmentHostManager;
import com.android.systemui.fragments.FragmentService;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
+import com.android.systemui.keyguard.domain.usecase.SetClockPositionUseCase;
+import com.android.systemui.keyguard.domain.usecase.SetKeyguardBottomAreaAlphaUseCase;
+import com.android.systemui.keyguard.domain.usecase.SetKeyguardBottomAreaAnimateDozingTransitionsUseCase;
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel;
import com.android.systemui.media.KeyguardMediaController;
import com.android.systemui.media.MediaDataManager;
import com.android.systemui.media.MediaHierarchyManager;
@@ -373,6 +377,11 @@
private ViewParent mViewParent;
@Mock
private ViewTreeObserver mViewTreeObserver;
+ @Mock private KeyguardBottomAreaViewModel mKeyguardBottomAreaViewModel;
+ @Mock private SetClockPositionUseCase mSetClockPositionUseCase;
+ @Mock private SetKeyguardBottomAreaAlphaUseCase mSetKeyguardBottomAreaAlphaUseCase;
+ @Mock private SetKeyguardBottomAreaAnimateDozingTransitionsUseCase
+ mSetKeyguardBottomAreaAnimateDozingTransitionsUseCase;
private NotificationPanelViewController.PanelEventsEmitter mPanelEventsEmitter;
private Optional<SysUIUnfoldComponent> mSysUIUnfoldComponent = Optional.empty();
private SysuiStatusBarStateController mStatusBarStateController;
@@ -564,7 +573,11 @@
mUnlockedScreenOffAnimationController,
mShadeTransitionController,
mSystemClock,
- mock(CameraGestureHelper.class));
+ mock(CameraGestureHelper.class),
+ () -> mKeyguardBottomAreaViewModel,
+ () -> mSetClockPositionUseCase,
+ () -> mSetKeyguardBottomAreaAlphaUseCase,
+ () -> mSetKeyguardBottomAreaAnimateDozingTransitionsUseCase);
mNotificationPanelViewController.initDependencies(
mCentralSurfaces,
() -> {},
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
index ec1fa48..ad3d3d2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
@@ -28,7 +28,6 @@
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy;
@@ -44,8 +43,6 @@
import android.view.View;
import android.view.WindowManager;
-import androidx.lifecycle.LifecycleOwner;
-import androidx.lifecycle.ViewTreeLifecycleOwner;
import androidx.test.filters.SmallTest;
import com.android.internal.colorextraction.ColorExtractor;
@@ -188,24 +185,6 @@
}
@Test
- public void attach_setsUpLifecycleOwner() {
- mNotificationShadeWindowController.attach();
-
- assertThat(ViewTreeLifecycleOwner.get(mNotificationShadeWindowView)).isNotNull();
- }
-
- @Test
- public void attach_doesNotSetUpLifecycleOwnerIfAlreadySet() {
- final LifecycleOwner previouslySet = mock(LifecycleOwner.class);
- ViewTreeLifecycleOwner.set(mNotificationShadeWindowView, previouslySet);
-
- mNotificationShadeWindowController.attach();
-
- assertThat(ViewTreeLifecycleOwner.get(mNotificationShadeWindowView))
- .isEqualTo(previouslySet);
- }
-
- @Test
public void setScrimsVisibility_earlyReturn() {
clearInvocations(mWindowManager);
mNotificationShadeWindowController.setScrimsVisibility(ScrimController.TRANSPARENT);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java
index b2ef7b3..79b1bb0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java
@@ -27,16 +27,13 @@
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.Dependency;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.shade.ShadeController;
import com.android.systemui.statusbar.notification.NotificationEntryListener;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
-import com.android.systemui.statusbar.notification.collection.render.NotifStackController;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager.OnSettingsClickListener;
import com.android.systemui.statusbar.notification.row.NotificationInfo.CheckSaveListener;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
import org.junit.Before;
import org.junit.Ignore;
@@ -55,11 +52,8 @@
@TestableLooper.RunWithLooper(setAsMainLooper = true)
public class NonPhoneDependencyTest extends SysuiTestCase {
@Mock private NotificationPresenter mPresenter;
- @Mock private NotifStackController mStackController;
@Mock private NotificationListContainer mListContainer;
- @Mock
- private NotificationEntryListener mEntryListener;
- @Mock private HeadsUpManager mHeadsUpManager;
+ @Mock private NotificationEntryListener mEntryListener;
@Mock private RemoteInputController.Delegate mDelegate;
@Mock private NotificationRemoteInputManager.Callback mRemoteInputManagerCallback;
@Mock private CheckSaveListener mCheckSaveListener;
@@ -76,28 +70,22 @@
@Ignore("Causes binder calls which fail")
@Test
public void testNotificationManagementCodeHasNoDependencyOnStatusBarWindowManager() {
- mDependency.injectMockDependency(ShadeController.class);
NotificationEntryManager entryManager = Dependency.get(NotificationEntryManager.class);
NotificationGutsManager gutsManager = Dependency.get(NotificationGutsManager.class);
- NotificationListener notificationListener = Dependency.get(NotificationListener.class);
NotificationLogger notificationLogger = Dependency.get(NotificationLogger.class);
NotificationMediaManager mediaManager = Dependency.get(NotificationMediaManager.class);
NotificationRemoteInputManager remoteInputManager =
Dependency.get(NotificationRemoteInputManager.class);
NotificationLockscreenUserManager lockscreenUserManager =
Dependency.get(NotificationLockscreenUserManager.class);
- NotificationViewHierarchyManager viewHierarchyManager =
- Dependency.get(NotificationViewHierarchyManager.class);
- entryManager.setUpWithPresenter(mPresenter);
entryManager.addNotificationEntryListener(mEntryListener);
gutsManager.setUpWithPresenter(mPresenter, mListContainer,
- mCheckSaveListener, mOnSettingsClickListener);
+ mOnSettingsClickListener);
notificationLogger.setUpWithContainer(mListContainer);
mediaManager.setUpWithPresenter(mPresenter);
remoteInputManager.setUpWithCallback(mRemoteInputManagerCallback,
mDelegate);
lockscreenUserManager.setUpWithPresenter(mPresenter);
- viewHierarchyManager.setUpWithPresenter(mPresenter, mStackController, mListContainer);
TestableLooper.get(this).processAllMessages();
assertFalse(mDependency.hasInstantiatedDependency(NotificationShadeWindowController.class));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
deleted file mode 100644
index 407044b..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
+++ /dev/null
@@ -1,375 +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.statusbar;
-
-import static junit.framework.Assert.assertTrue;
-
-import static org.junit.Assert.assertEquals;
-import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.os.Handler;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.LinearLayout;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
-import com.android.systemui.statusbar.notification.AssistantFeedbackController;
-import com.android.systemui.statusbar.notification.DynamicChildBindController;
-import com.android.systemui.statusbar.notification.DynamicPrivacyController;
-import com.android.systemui.statusbar.notification.NotifPipelineFlags;
-import com.android.systemui.statusbar.notification.NotificationActivityStarter;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.legacy.LowPriorityInflationHelper;
-import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
-import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
-import com.android.systemui.statusbar.notification.collection.render.NotifStackController;
-import com.android.systemui.statusbar.notification.logging.NotificationLogger;
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-import com.android.systemui.statusbar.notification.row.ExpandableView;
-import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
-import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
-import com.android.systemui.statusbar.phone.KeyguardBypassController;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.wm.shell.bubbles.Bubbles;
-
-import com.google.android.collect.Lists;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.mockito.Spy;
-
-import java.util.List;
-import java.util.Optional;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper
-public class NotificationViewHierarchyManagerTest extends SysuiTestCase {
- @Mock private NotificationPresenter mPresenter;
- @Mock private NotifStackController mStackController;
- @Spy private FakeListContainer mListContainer = new FakeListContainer();
-
- // Dependency mocks:
- @Mock private FeatureFlags mFeatureFlags;
- @Mock private NotifPipelineFlags mNotifPipelineFlags;
- @Mock private NotificationEntryManager mEntryManager;
- @Mock private NotificationLockscreenUserManager mLockscreenUserManager;
- @Mock private NotificationGroupManagerLegacy mGroupManager;
- @Mock private VisualStabilityManager mVisualStabilityManager;
-
- private TestableLooper mTestableLooper;
- private Handler mHandler;
- private NotificationViewHierarchyManager mViewHierarchyManager;
- private NotificationTestHelper mHelper;
- private boolean mMadeReentrantCall = false;
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
- mTestableLooper = TestableLooper.get(this);
- allowTestableLooperAsMainThread();
- mHandler = Handler.createAsync(mTestableLooper.getLooper());
-
- mDependency.injectTestDependency(NotificationEntryManager.class, mEntryManager);
- mDependency.injectTestDependency(NotificationLockscreenUserManager.class,
- mLockscreenUserManager);
- mDependency.injectTestDependency(NotificationGroupManagerLegacy.class, mGroupManager);
- mDependency.injectTestDependency(VisualStabilityManager.class, mVisualStabilityManager);
- when(mVisualStabilityManager.areGroupChangesAllowed()).thenReturn(true);
- when(mVisualStabilityManager.isReorderingAllowed()).thenReturn(true);
-
- when(mNotifPipelineFlags.checkLegacyPipelineEnabled()).thenReturn(true);
- when(mNotifPipelineFlags.isNewPipelineEnabled()).thenReturn(false);
-
- mHelper = new NotificationTestHelper(mContext, mDependency, TestableLooper.get(this));
-
- mViewHierarchyManager = new NotificationViewHierarchyManager(mContext,
- mHandler, mFeatureFlags, mLockscreenUserManager, mGroupManager,
- mVisualStabilityManager,
- mock(StatusBarStateControllerImpl.class), mEntryManager,
- mock(KeyguardBypassController.class),
- Optional.of(mock(Bubbles.class)),
- mock(DynamicPrivacyController.class),
- mock(DynamicChildBindController.class),
- mock(LowPriorityInflationHelper.class),
- mock(AssistantFeedbackController.class),
- mNotifPipelineFlags,
- mock(KeyguardUpdateMonitor.class),
- mock(KeyguardStateController.class));
- mViewHierarchyManager.setUpWithPresenter(mPresenter, mStackController, mListContainer);
- }
-
- private NotificationEntry createEntry() throws Exception {
- ExpandableNotificationRow row = mHelper.createRow();
- return row.getEntry();
- }
-
- @Test
- public void testNotificationsBecomingBundled() throws Exception {
- // Tests 3 top level notifications becoming a single bundled notification with |entry0| as
- // the summary.
- NotificationEntry entry0 = createEntry();
- NotificationEntry entry1 = createEntry();
- NotificationEntry entry2 = createEntry();
-
- // Set up the prior state to look like three top level notifications.
- mListContainer.addContainerView(entry0.getRow());
- mListContainer.addContainerView(entry1.getRow());
- mListContainer.addContainerView(entry2.getRow());
- when(mEntryManager.getVisibleNotifications()).thenReturn(
- Lists.newArrayList(entry0, entry1, entry2));
-
- // Set up group manager to report that they should be bundled now.
- when(mGroupManager.isChildInGroup(entry0)).thenReturn(false);
- when(mGroupManager.isChildInGroup(entry1)).thenReturn(true);
- when(mGroupManager.isChildInGroup(entry2)).thenReturn(true);
- when(mGroupManager.getGroupSummary(entry1)).thenReturn(entry0);
- when(mGroupManager.getGroupSummary(entry2)).thenReturn(entry0);
-
- // Run updateNotifications - the view hierarchy should be reorganized.
- mViewHierarchyManager.updateNotificationViews();
-
- verify(mListContainer).notifyGroupChildAdded(entry1.getRow());
- verify(mListContainer).notifyGroupChildAdded(entry2.getRow());
- assertTrue(Lists.newArrayList(entry0.getRow()).equals(mListContainer.mRows));
- }
-
- @Test
- public void testNotificationsBecomingUnbundled() throws Exception {
- // Tests a bundled notification becoming three top level notifications.
- NotificationEntry entry0 = createEntry();
- NotificationEntry entry1 = createEntry();
- NotificationEntry entry2 = createEntry();
- entry0.getRow().addChildNotification(entry1.getRow());
- entry0.getRow().addChildNotification(entry2.getRow());
-
- // Set up the prior state to look like one top level notification.
- mListContainer.addContainerView(entry0.getRow());
- when(mEntryManager.getVisibleNotifications()).thenReturn(
- Lists.newArrayList(entry0, entry1, entry2));
-
- // Set up group manager to report that they should not be bundled now.
- when(mGroupManager.isChildInGroup(entry0)).thenReturn(false);
- when(mGroupManager.isChildInGroup(entry1)).thenReturn(false);
- when(mGroupManager.isChildInGroup(entry2)).thenReturn(false);
-
- // Run updateNotifications - the view hierarchy should be reorganized.
- mViewHierarchyManager.updateNotificationViews();
-
- verify(mListContainer).notifyGroupChildRemoved(
- entry1.getRow(), entry0.getRow().getChildrenContainer());
- verify(mListContainer).notifyGroupChildRemoved(
- entry2.getRow(), entry0.getRow().getChildrenContainer());
- assertTrue(
- Lists.newArrayList(entry0.getRow(), entry1.getRow(), entry2.getRow())
- .equals(mListContainer.mRows));
- }
-
- @Test
- public void testNotificationsBecomingSuppressed() throws Exception {
- // Tests two top level notifications becoming a suppressed summary and a child.
- NotificationEntry entry0 = createEntry();
- NotificationEntry entry1 = createEntry();
- entry0.getRow().addChildNotification(entry1.getRow());
-
- // Set up the prior state to look like a top level notification.
- mListContainer.addContainerView(entry0.getRow());
- when(mEntryManager.getVisibleNotifications()).thenReturn(
- Lists.newArrayList(entry0, entry1));
-
- // Set up group manager to report a suppressed summary now.
- when(mGroupManager.isChildInGroup(entry0)).thenReturn(false);
- when(mGroupManager.isChildInGroup(entry1)).thenReturn(false);
- when(mGroupManager.isSummaryOfSuppressedGroup(entry0.getSbn())).thenReturn(true);
-
- // Run updateNotifications - the view hierarchy should be reorganized.
- mViewHierarchyManager.updateNotificationViews();
-
- verify(mListContainer).notifyGroupChildRemoved(
- entry1.getRow(), entry0.getRow().getChildrenContainer());
- assertTrue(Lists.newArrayList(entry0.getRow(), entry1.getRow()).equals(mListContainer.mRows));
- assertEquals(View.GONE, entry0.getRow().getVisibility());
- assertEquals(View.VISIBLE, entry1.getRow().getVisibility());
- }
-
- @Test
- public void testReentrantCallsToOnDynamicPrivacyChangedPostForLater() {
- // GIVEN a ListContainer that will make a re-entrant call to updateNotificationViews()
- mMadeReentrantCall = false;
- doAnswer((invocation) -> {
- if (!mMadeReentrantCall) {
- mMadeReentrantCall = true;
- mViewHierarchyManager.onDynamicPrivacyChanged();
- }
- return null;
- }).when(mListContainer).onNotificationViewUpdateFinished();
-
- // WHEN we call updateNotificationViews()
- mViewHierarchyManager.updateNotificationViews();
-
- // THEN onNotificationViewUpdateFinished() is only called once
- verify(mListContainer).onNotificationViewUpdateFinished();
-
- // WHEN we drain the looper
- mTestableLooper.processAllMessages();
-
- // THEN updateNotificationViews() is called a second time (for the reentrant call)
- verify(mListContainer, times(2)).onNotificationViewUpdateFinished();
- }
-
- @Test
- public void testMultipleReentrantCallsToOnDynamicPrivacyChangedOnlyPostOnce() {
- // GIVEN a ListContainer that will make many re-entrant calls to updateNotificationViews()
- mMadeReentrantCall = false;
- doAnswer((invocation) -> {
- if (!mMadeReentrantCall) {
- mMadeReentrantCall = true;
- mViewHierarchyManager.onDynamicPrivacyChanged();
- mViewHierarchyManager.onDynamicPrivacyChanged();
- mViewHierarchyManager.onDynamicPrivacyChanged();
- mViewHierarchyManager.onDynamicPrivacyChanged();
- }
- return null;
- }).when(mListContainer).onNotificationViewUpdateFinished();
-
- // WHEN we call updateNotificationViews() and drain the looper
- mViewHierarchyManager.updateNotificationViews();
- verify(mListContainer).onNotificationViewUpdateFinished();
- clearInvocations(mListContainer);
- mTestableLooper.processAllMessages();
-
- // THEN updateNotificationViews() is called only one more time
- verify(mListContainer).onNotificationViewUpdateFinished();
- }
-
- private class FakeListContainer implements NotificationListContainer {
- final LinearLayout mLayout = new LinearLayout(mContext);
- final List<View> mRows = Lists.newArrayList();
-
- @Override
- public void setChildTransferInProgress(boolean childTransferInProgress) {}
-
- @Override
- public void changeViewPosition(ExpandableView child, int newIndex) {
- mRows.remove(child);
- mRows.add(newIndex, child);
- }
-
- @Override
- public void notifyGroupChildAdded(ExpandableView row) {}
-
- @Override
- public void notifyGroupChildRemoved(ExpandableView row, ViewGroup childrenContainer) {}
-
- @Override
- public void generateAddAnimation(ExpandableView child, boolean fromMoreCard) {}
-
- @Override
- public void generateChildOrderChangedEvent() {}
-
- @Override
- public void onReset(ExpandableView view) {}
-
- @Override
- public int getContainerChildCount() {
- return mRows.size();
- }
-
- @Override
- public View getContainerChildAt(int i) {
- return mRows.get(i);
- }
-
- @Override
- public void removeContainerView(View v) {
- mLayout.removeView(v);
- mRows.remove(v);
- }
-
- @Override
- public void setNotificationActivityStarter(
- NotificationActivityStarter notificationActivityStarter) {}
-
- @Override
- public void addContainerView(View v) {
- mLayout.addView(v);
- mRows.add(v);
- }
-
- @Override
- public void addContainerViewAt(View v, int index) {
- mLayout.addView(v, index);
- mRows.add(index, v);
- }
-
- @Override
- public void setMaxDisplayedNotifications(int maxNotifications) {
- }
-
- @Override
- public ViewGroup getViewParentForNotification(NotificationEntry entry) {
- return null;
- }
-
- @Override
- public void onHeightChanged(ExpandableView view, boolean animate) {}
-
- @Override
- public void resetExposedMenuView(boolean animate, boolean force) {}
-
- @Override
- public NotificationSwipeActionHelper getSwipeActionHelper() {
- return null;
- }
-
- @Override
- public void cleanUpViewStateForEntry(NotificationEntry entry) { }
-
- @Override
- public boolean isInVisibleLocation(NotificationEntry entry) {
- return true;
- }
-
- @Override
- public void setChildLocationsChangedListener(
- NotificationLogger.OnChildLocationsChangedListener listener) {}
-
- @Override
- public boolean hasPulsingNotifications() {
- return false;
- }
-
- @Override
- public void onNotificationViewUpdateFinished() { }
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
index aeef6b0..842f057 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
@@ -77,10 +77,8 @@
import com.android.systemui.statusbar.NotificationPresenter;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.NotificationRemoveInterceptor;
-import com.android.systemui.statusbar.RankingBuilder;
import com.android.systemui.statusbar.SmartReplyController;
import com.android.systemui.statusbar.notification.NotificationEntryManager.KeyguardEnvironment;
-import com.android.systemui.statusbar.notification.collection.NotifLiveDataStoreMocksKt;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.collection.NotificationRankingManager;
@@ -91,7 +89,6 @@
import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-import com.android.systemui.statusbar.notification.row.NotificationEntryManagerInflationTest;
import com.android.systemui.statusbar.notification.row.RowInflaterTask;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
@@ -115,9 +112,7 @@
import java.util.Set;
/**
- * Unit tests for {@link NotificationEntryManager}. This test will not test any interactions with
- * inflation. Instead, for functional inflation tests, see
- * {@link NotificationEntryManagerInflationTest}.
+ * Unit tests for {@link NotificationEntryManager}.
*/
@SmallTest
@RunWith(AndroidTestingRunner.class)
@@ -205,7 +200,6 @@
mStats = defaultStats(mEntry);
mSbn = mEntry.getSbn();
- when(mNotifPipelineFlags.isNewPipelineEnabled()).thenReturn(false);
mEntryManager = new NotificationEntryManager(
mLogger,
mGroupManager,
@@ -214,7 +208,6 @@
() -> mRemoteInputManager,
mLeakDetector,
mStatusBarService,
- NotifLiveDataStoreMocksKt.createNotifLiveDataStoreImplMock(),
mock(DumpManager.class),
mBgExecutor
);
@@ -230,7 +223,6 @@
mock(PeopleNotificationIdentifier.class),
mock(HighPriorityProvider.class),
mEnvironment));
- mEntryManager.setUpWithPresenter(mPresenter);
mEntryManager.addNotificationEntryListener(mEntryListener);
mEntryManager.addCollectionListener(mNotifCollectionListener);
mEntryManager.addNotificationRemoveInterceptor(mRemoveInterceptor);
@@ -273,17 +265,6 @@
}
@Test
- public void testUpdateNotification_updatesUserSentiment() {
- mEntryManager.addActiveNotificationForTest(mEntry);
- setUserSentiment(
- mEntry.getKey(), Ranking.USER_SENTIMENT_NEGATIVE);
-
- mEntryManager.updateNotification(mSbn, mRankingMap);
-
- assertEquals(Ranking.USER_SENTIMENT_NEGATIVE, mEntry.getUserSentiment());
- }
-
- @Test
public void testUpdateNotification_prePostEntryOrder() throws Exception {
TestableLooper.get(this).processAllMessages();
@@ -294,7 +275,6 @@
// Ensure that update callbacks happen in correct order
InOrder order = inOrder(mEntryListener, mPresenter, mEntryListener);
order.verify(mEntryListener).onPreEntryUpdated(mEntry);
- order.verify(mPresenter).updateNotificationViews(any());
order.verify(mEntryListener).onPostEntryUpdated(mEntry);
}
@@ -305,7 +285,6 @@
mEntryManager.removeNotification(mSbn.getKey(), mRankingMap, UNDEFINED_DISMISS_REASON);
- verify(mPresenter).updateNotificationViews(any());
verify(mEntryListener).onEntryRemoved(
argThat(matchEntryOnKey()), any(),
eq(false) /* removedByUser */, eq(UNDEFINED_DISMISS_REASON));
@@ -379,23 +358,6 @@
}
@Test
- public void testUpdateNotificationRanking() {
- when(mDeviceProvisionedController.isDeviceProvisioned()).thenReturn(true);
- when(mEnvironment.isDeviceProvisioned()).thenReturn(true);
- when(mEnvironment.isNotificationForCurrentProfiles(any())).thenReturn(true);
-
- mEntry.setRow(mRow);
- mEntry.setInflationTask(mAsyncInflationTask);
- mEntryManager.addActiveNotificationForTest(mEntry);
- setSmartActions(mEntry.getKey(), new ArrayList<>(Arrays.asList(createAction())));
-
- mEntryManager.updateNotificationRanking(mRankingMap);
- assertEquals(1, mEntry.getSmartActions().size());
- assertEquals("action", mEntry.getSmartActions().get(0).title);
- verify(mEntryListener).onNotificationRankingUpdated(mRankingMap);
- }
-
- @Test
public void testUpdateNotificationRanking_noChange() {
when(mDeviceProvisionedController.isDeviceProvisioned()).thenReturn(true);
when(mEnvironment.isNotificationForCurrentProfiles(any())).thenReturn(true);
@@ -409,20 +371,6 @@
}
@Test
- public void testUpdateNotificationRanking_rowNotInflatedYet() {
- when(mDeviceProvisionedController.isDeviceProvisioned()).thenReturn(true);
- when(mEnvironment.isNotificationForCurrentProfiles(any())).thenReturn(true);
-
- mEntry.setRow(null);
- mEntryManager.addActiveNotificationForTest(mEntry);
- setSmartActions(mEntry.getKey(), new ArrayList<>(Arrays.asList(createAction())));
-
- mEntryManager.updateNotificationRanking(mRankingMap);
- assertEquals(1, mEntry.getSmartActions().size());
- assertEquals("action", mEntry.getSmartActions().get(0).title);
- }
-
- @Test
public void testUpdateNotificationRanking_pendingNotification() {
when(mDeviceProvisionedController.isDeviceProvisioned()).thenReturn(true);
when(mEnvironment.isNotificationForCurrentProfiles(any())).thenReturn(true);
@@ -614,31 +562,6 @@
/* Tests annexed from NotificationDataTest go here */
@Test
- public void testChannelIsSetWhenAdded() {
- NotificationChannel nc = new NotificationChannel(
- "testId",
- "testName",
- IMPORTANCE_DEFAULT);
-
- Ranking r = new RankingBuilder()
- .setKey(mEntry.getKey())
- .setChannel(nc)
- .build();
-
- RankingMap rm = new RankingMap(new Ranking[] { r });
-
- // GIVEN: a notification is added, and the ranking updated
- mEntryManager.addActiveNotificationForTest(mEntry);
- mEntryManager.updateRanking(rm, "testReason");
-
- // THEN the notification entry better have a channel on it
- assertEquals(
- "Channel must be set when adding a notification",
- nc.getName(),
- mEntry.getChannel().getName());
- }
-
- @Test
public void testGetNotificationsForCurrentUser_shouldFilterNonCurrentUserNotifications() {
Notification.Builder n = new Notification.Builder(mContext, "di")
.setSmallIcon(R.drawable.ic_person)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
index c283cec..dfa38ab 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
@@ -33,7 +33,6 @@
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.inOrder;
-import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
@@ -1846,103 +1845,6 @@
}
@Test
- public void stableOrderingDisregardedWithSectionChange() {
- // GIVEN the first sectioner's packages can be changed from run-to-run
- List<String> mutableSectionerPackages = new ArrayList<>();
- mutableSectionerPackages.add(PACKAGE_1);
- mListBuilder.setSectioners(asList(
- new PackageSectioner(mutableSectionerPackages, null),
- new PackageSectioner(List.of(PACKAGE_1, PACKAGE_2, PACKAGE_3), null)));
- mStabilityManager.setAllowEntryReordering(false);
-
- // WHEN the list is originally built with reordering disabled (and section changes allowed)
- addNotif(0, PACKAGE_1).setRank(1);
- addNotif(1, PACKAGE_1).setRank(5);
- addNotif(2, PACKAGE_2).setRank(2);
- addNotif(3, PACKAGE_2).setRank(3);
- addNotif(4, PACKAGE_3).setRank(4);
- dispatchBuild();
-
- // VERIFY the order and that entry reordering has not been suppressed
- verifyBuiltList(
- notif(0),
- notif(1),
- notif(2),
- notif(3),
- notif(4)
- );
- verify(mStabilityManager, never()).onEntryReorderSuppressed();
-
- // WHEN the first section now claims PACKAGE_3 notifications
- mutableSectionerPackages.add(PACKAGE_3);
- dispatchBuild();
-
- // VERIFY the re-sectioned notification is inserted at the top of the first section, because
- // it's effectively "new" and "new" things are inserted at the top of their section.
- verifyBuiltList(
- notif(4),
- notif(0),
- notif(1),
- notif(2),
- notif(3)
- );
- verify(mStabilityManager).onEntryReorderSuppressed();
- clearInvocations(mStabilityManager);
-
- // WHEN reordering is now allowed again
- mStabilityManager.setAllowEntryReordering(true);
- dispatchBuild();
-
- // VERIFY that list order changes to put the re-sectioned notification in the middle where
- // it is ranked.
- verifyBuiltList(
- notif(0),
- notif(4),
- notif(1),
- notif(2),
- notif(3)
- );
- verify(mStabilityManager, never()).onEntryReorderSuppressed();
- }
-
- @Test
- public void groupRevertingToSummaryRetainsStablePosition() {
- // GIVEN a notification group is on screen
- mStabilityManager.setAllowEntryReordering(false);
-
- // WHEN the list is originally built with reordering disabled (and section changes allowed)
- addNotif(0, PACKAGE_1).setRank(2);
- addNotif(1, PACKAGE_1).setRank(3);
- addGroupSummary(2, PACKAGE_1, "group").setRank(4);
- addGroupChild(3, PACKAGE_1, "group").setRank(5);
- addGroupChild(4, PACKAGE_1, "group").setRank(6);
- dispatchBuild();
-
- verifyBuiltList(
- notif(0),
- notif(1),
- group(
- summary(2),
- child(3),
- child(4)
- )
- );
-
- // WHEN the notification summary rank increases and children removed
- setNewRank(notif(2).entry, 1);
- mEntrySet.remove(4);
- mEntrySet.remove(3);
- dispatchBuild();
-
- // VERIFY the summary stays in the same location on rebuild
- verifyBuiltList(
- notif(0),
- notif(1),
- notif(2)
- );
- }
-
- @Test
public void testStableChildOrdering() {
// WHEN the list is originally built with reordering disabled
mStabilityManager.setAllowEntryReordering(false);
@@ -2138,7 +2040,6 @@
private void assertOrder(String visible, String active, String expected) {
StringBuilder differenceSb = new StringBuilder();
- NotifSection section = new NotifSection(mock(NotifSectioner.class), 0);
for (char c : active.toCharArray()) {
if (visible.indexOf(c) < 0) differenceSb.append(c);
}
@@ -2147,7 +2048,6 @@
for (int i = 0; i < visible.length(); i++) {
addNotif(i, String.valueOf(visible.charAt(i)))
.setRank(active.indexOf(visible.charAt(i)))
- .setSection(section)
.setStableIndex(i);
}
@@ -2155,7 +2055,6 @@
for (int i = 0; i < difference.length(); i++) {
addNotif(i + visible.length(), String.valueOf(difference.charAt(i)))
.setRank(active.indexOf(difference.charAt(i)))
- .setSection(section)
.setStableIndex(-1);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
index 5386171..e00e20f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
@@ -118,7 +118,6 @@
mPowerManager,
mDreamManager,
mAmbientDisplayConfiguration,
- mNotificationFilter,
mBatteryController,
mStatusBarStateController,
mKeyguardStateController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java
deleted file mode 100644
index bf7549a..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java
+++ /dev/null
@@ -1,467 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.row;
-
-import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
-
-import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP;
-
-import static junit.framework.Assert.assertNotNull;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.app.Notification;
-import android.content.Context;
-import android.content.pm.LauncherApps;
-import android.os.Handler;
-import android.service.notification.NotificationListenerService;
-import android.service.notification.NotificationListenerService.Ranking;
-import android.service.notification.StatusBarNotification;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-
-import androidx.asynclayoutinflater.view.AsyncLayoutInflater;
-import androidx.test.filters.SmallTest;
-
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.statusbar.IStatusBarService;
-import com.android.internal.util.NotificationMessagingUtil;
-import com.android.systemui.R;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.classifier.FalsingCollectorFake;
-import com.android.systemui.classifier.FalsingManagerFake;
-import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.media.MediaFeatureFlag;
-import com.android.systemui.media.dialog.MediaOutputDialogFactory;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.shared.plugins.PluginManager;
-import com.android.systemui.statusbar.NotificationListener;
-import com.android.systemui.statusbar.NotificationLockscreenUserManager;
-import com.android.systemui.statusbar.NotificationMediaManager;
-import com.android.systemui.statusbar.NotificationPresenter;
-import com.android.systemui.statusbar.NotificationRemoteInputManager;
-import com.android.systemui.statusbar.SbnBuilder;
-import com.android.systemui.statusbar.SmartReplyController;
-import com.android.systemui.statusbar.notification.ConversationNotificationProcessor;
-import com.android.systemui.statusbar.notification.NotifPipelineFlags;
-import com.android.systemui.statusbar.notification.NotificationClicker;
-import com.android.systemui.statusbar.notification.NotificationEntryListener;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
-import com.android.systemui.statusbar.notification.NotificationEntryManagerLogger;
-import com.android.systemui.statusbar.notification.NotificationFilter;
-import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager;
-import com.android.systemui.statusbar.notification.collection.NotifLiveDataStoreMocksKt;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.NotificationRankingManager;
-import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl;
-import com.android.systemui.statusbar.notification.collection.legacy.LowPriorityInflationHelper;
-import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
-import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
-import com.android.systemui.statusbar.notification.icon.IconBuilder;
-import com.android.systemui.statusbar.notification.icon.IconManager;
-import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
-import com.android.systemui.statusbar.notification.logging.NotificationLogger;
-import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
-import com.android.systemui.statusbar.notification.row.dagger.ExpandableNotificationRowComponent;
-import com.android.systemui.statusbar.notification.row.dagger.NotificationRowComponent;
-import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
-import com.android.systemui.statusbar.phone.KeyguardBypassController;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
-import com.android.systemui.statusbar.policy.InflatedSmartReplyState;
-import com.android.systemui.statusbar.policy.InflatedSmartReplyViewHolder;
-import com.android.systemui.statusbar.policy.SmartReplyConstants;
-import com.android.systemui.statusbar.policy.SmartReplyStateInflater;
-import com.android.systemui.statusbar.policy.dagger.RemoteInputViewSubcomponent;
-import com.android.systemui.util.concurrency.FakeExecutor;
-import com.android.systemui.util.leak.LeakDetector;
-import com.android.systemui.util.time.FakeSystemClock;
-import com.android.systemui.wmshell.BubblesManager;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Answers;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
-import org.mockito.stubbing.Answer;
-
-import java.util.Optional;
-import java.util.concurrent.CountDownLatch;
-
-/**
- * Functional tests for notification inflation from {@link NotificationEntryManager}.
- */
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper(setAsMainLooper = true)
-public class NotificationEntryManagerInflationTest extends SysuiTestCase {
-
- private static final String TEST_TITLE = "Title";
- private static final String TEST_TEXT = "Text";
- private static final long TIMEOUT_TIME = 10000;
- private static final Runnable TIMEOUT_RUNNABLE = () -> {
- throw new RuntimeException("Timed out waiting to inflate");
- };
-
- @Mock private NotificationListener mNotificationListener;
- @Mock private NotificationPresenter mPresenter;
- @Mock private NotificationEntryManager.KeyguardEnvironment mEnvironment;
- @Mock private NotificationListContainer mListContainer;
- @Mock private NotificationEntryListener mEntryListener;
- @Mock private NotificationRowBinderImpl.BindRowCallback mBindCallback;
- @Mock private HeadsUpManager mHeadsUpManager;
- @Mock private NotificationInterruptStateProvider mNotificationInterruptionStateProvider;
- @Mock private NotificationLockscreenUserManager mLockscreenUserManager;
- @Mock private NotificationGutsManager mGutsManager;
- @Mock private NotificationRemoteInputManager mRemoteInputManager;
- @Mock private NotificationMediaManager mNotificationMediaManager;
- @Mock(answer = Answers.RETURNS_SELF)
- private ExpandableNotificationRowComponent.Builder mExpandableNotificationRowComponentBuilder;
- @Mock private ExpandableNotificationRowComponent mExpandableNotificationRowComponent;
- @Mock private KeyguardBypassController mKeyguardBypassController;
- @Mock private StatusBarStateController mStatusBarStateController;
-
- @Mock private NotificationGroupManagerLegacy mGroupMembershipManager;
- @Mock private NotificationGroupManagerLegacy mGroupExpansionManager;
- @Mock private NotifPipelineFlags mNotifPipelineFlags;
- @Mock private LeakDetector mLeakDetector;
-
- @Mock private ActivatableNotificationViewController mActivatableNotificationViewController;
- @Mock private NotificationRowComponent.Builder mNotificationRowComponentBuilder;
- @Mock private PeopleNotificationIdentifier mPeopleNotificationIdentifier;
- @Mock private InflatedSmartReplyState mInflatedSmartReplyState;
- @Mock private InflatedSmartReplyViewHolder mInflatedSmartReplies;
-
- private StatusBarNotification mSbn;
- private NotificationListenerService.RankingMap mRankingMap;
- private NotificationEntryManager mEntryManager;
- private NotificationRowBinderImpl mRowBinder;
- private Handler mHandler;
- private FakeExecutor mBgExecutor;
- private RowContentBindStage mRowContentBindStage;
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
- mDependency.injectMockDependency(SmartReplyController.class);
- mDependency.injectMockDependency(MediaOutputDialogFactory.class);
-
- mHandler = Handler.createAsync(TestableLooper.get(this).getLooper());
-
- // Add an action so heads up content views are made
- Notification.Action action = new Notification.Action.Builder(null, null, null).build();
- Notification notification = new Notification.Builder(mContext)
- .setSmallIcon(R.drawable.ic_person)
- .setContentTitle(TEST_TITLE)
- .setContentText(TEST_TEXT)
- .setActions(action)
- .build();
- mSbn = new SbnBuilder()
- .setNotification(notification)
- .build();
-
- when(mNotifPipelineFlags.checkLegacyPipelineEnabled()).thenReturn(true);
- when(mNotifPipelineFlags.isNewPipelineEnabled()).thenReturn(false);
-
- mEntryManager = new NotificationEntryManager(
- mock(NotificationEntryManagerLogger.class),
- mGroupMembershipManager,
- mNotifPipelineFlags,
- () -> mRowBinder,
- () -> mRemoteInputManager,
- mLeakDetector,
- mock(IStatusBarService.class),
- NotifLiveDataStoreMocksKt.createNotifLiveDataStoreImplMock(),
- mock(DumpManager.class),
- mBgExecutor
- );
- mEntryManager.initialize(
- mNotificationListener,
- new NotificationRankingManager(
- () -> mock(NotificationMediaManager.class),
- mGroupMembershipManager,
- mHeadsUpManager,
- mock(NotificationFilter.class),
- mock(NotificationEntryManagerLogger.class),
- mock(NotificationSectionsFeatureManager.class),
- mock(PeopleNotificationIdentifier.class),
- mock(HighPriorityProvider.class),
- mEnvironment));
-
- NotifRemoteViewCache cache = new NotifRemoteViewCacheImpl(mEntryManager);
- NotifBindPipeline pipeline = new NotifBindPipeline(
- mEntryManager,
- mock(NotifBindPipelineLogger.class),
- TestableLooper.get(this).getLooper());
- mBgExecutor = new FakeExecutor(new FakeSystemClock());
- NotificationContentInflater binder = new NotificationContentInflater(
- cache,
- mRemoteInputManager,
- mock(ConversationNotificationProcessor.class),
- mock(MediaFeatureFlag.class),
- mBgExecutor,
- new SmartReplyStateInflater() {
- @Override
- public InflatedSmartReplyState inflateSmartReplyState(NotificationEntry entry) {
- return mInflatedSmartReplyState;
- }
-
- @Override
- public InflatedSmartReplyViewHolder inflateSmartReplyViewHolder(
- Context sysuiContext, Context notifPackageContext,
- NotificationEntry entry,
- InflatedSmartReplyState existingSmartReplyState,
- InflatedSmartReplyState newSmartReplyState) {
- return mInflatedSmartReplies;
- }
- });
- mRowContentBindStage = new RowContentBindStage(
- binder,
- mock(NotifInflationErrorManager.class),
- mock(RowContentBindStageLogger.class));
- pipeline.setStage(mRowContentBindStage);
-
- ArgumentCaptor<ExpandableNotificationRow> viewCaptor =
- ArgumentCaptor.forClass(ExpandableNotificationRow.class);
- when(mExpandableNotificationRowComponentBuilder
- .expandableNotificationRow(viewCaptor.capture()))
- .thenReturn(mExpandableNotificationRowComponentBuilder);
- when(mExpandableNotificationRowComponentBuilder.build())
- .thenReturn(mExpandableNotificationRowComponent);
-
- when(mExpandableNotificationRowComponent.getExpandableNotificationRowController())
- .thenAnswer((Answer<ExpandableNotificationRowController>) invocation ->
- new ExpandableNotificationRowController(
- viewCaptor.getValue(),
- mock(ActivatableNotificationViewController.class),
- mock(RemoteInputViewSubcomponent.Factory.class),
- mock(MetricsLogger.class),
- mListContainer,
- mNotificationMediaManager,
- mock(SmartReplyConstants.class),
- mock(SmartReplyController.class),
- mock(PluginManager.class),
- new FakeSystemClock(),
- "FOOBAR",
- "FOOBAR",
- mKeyguardBypassController,
- mGroupMembershipManager,
- mGroupExpansionManager,
- mRowContentBindStage,
- mock(NotificationLogger.class),
- mHeadsUpManager,
- mPresenter,
- mStatusBarStateController,
- mGutsManager,
- true,
- null,
- new FalsingManagerFake(),
- new FalsingCollectorFake(),
- mock(FeatureFlags.class),
- mPeopleNotificationIdentifier,
- Optional.of(mock(BubblesManager.class)),
- mock(ExpandableNotificationRowDragController.class)));
-
- when(mNotificationRowComponentBuilder.activatableNotificationView(any()))
- .thenReturn(mNotificationRowComponentBuilder);
- when(mNotificationRowComponentBuilder.build()).thenReturn(
- () -> mActivatableNotificationViewController);
-
- mRowBinder = new NotificationRowBinderImpl(
- mContext,
- new NotificationMessagingUtil(mContext),
- mRemoteInputManager,
- mLockscreenUserManager,
- pipeline,
- mRowContentBindStage,
- RowInflaterTask::new,
- mExpandableNotificationRowComponentBuilder,
- new IconManager(
- mEntryManager,
- mock(LauncherApps.class),
- new IconBuilder(mContext)),
- mock(LowPriorityInflationHelper.class),
- mNotifPipelineFlags);
-
- mEntryManager.setUpWithPresenter(mPresenter);
- mEntryManager.addNotificationEntryListener(mEntryListener);
-
- mRowBinder.setUpWithPresenter(mPresenter, mListContainer, mBindCallback);
- mRowBinder.setNotificationClicker(mock(NotificationClicker.class));
-
- Ranking ranking = new Ranking();
- ranking.populate(
- mSbn.getKey(),
- 0,
- false,
- 0,
- 0,
- IMPORTANCE_DEFAULT,
- null,
- null,
- null,
- null,
- null,
- true,
- Ranking.USER_SENTIMENT_NEUTRAL,
- false,
- -1,
- false,
- null,
- null,
- false,
- false,
- false,
- null,
- 0,
- false
- );
- mRankingMap = new NotificationListenerService.RankingMap(new Ranking[] {ranking});
-
- TestableLooper.get(this).processAllMessages();
- }
-
- @After
- public void cleanUp() {
- // Don't leave anything on main thread
- TestableLooper.get(this).processAllMessages();
- }
-
- @Test
- public void testAddNotification() {
- // WHEN a notification is added
- mEntryManager.addNotification(mSbn, mRankingMap);
- ArgumentCaptor<NotificationEntry> entryCaptor = ArgumentCaptor.forClass(
- NotificationEntry.class);
- verify(mEntryListener).onPendingEntryAdded(entryCaptor.capture());
- NotificationEntry entry = entryCaptor.getValue();
-
- waitForInflation();
-
- // THEN the notification has its row inflated
- assertNotNull(entry.getRow());
- assertNotNull(entry.getRow().getPrivateLayout().getContractedChild());
-
- // THEN inflation callbacks are called
- verify(mBindCallback).onBindRow(entry.getRow());
- verify(mEntryListener, never()).onInflationError(any(), any());
- verify(mEntryListener).onEntryInflated(entry);
- verify(mEntryListener).onNotificationAdded(entry);
-
- // THEN the notification is active
- assertNotNull(mEntryManager.getActiveNotificationUnfiltered(mSbn.getKey()));
-
- // THEN we update the presenter
- verify(mPresenter).updateNotificationViews(any());
- }
-
- @Test
- public void testUpdateNotification() {
- // GIVEN a notification already added
- mEntryManager.addNotification(mSbn, mRankingMap);
- ArgumentCaptor<NotificationEntry> entryCaptor = ArgumentCaptor.forClass(
- NotificationEntry.class);
- verify(mEntryListener).onPendingEntryAdded(entryCaptor.capture());
- NotificationEntry entry = entryCaptor.getValue();
- waitForInflation();
-
- Mockito.reset(mEntryListener);
- Mockito.reset(mPresenter);
-
- // WHEN the notification is updated
- mEntryManager.updateNotification(mSbn, mRankingMap);
-
- waitForInflation();
-
- // THEN the notification has its row and inflated
- assertNotNull(entry.getRow());
-
- // THEN inflation callbacks are called
- verify(mEntryListener, never()).onInflationError(any(), any());
- verify(mEntryListener).onEntryReinflated(entry);
-
- // THEN we update the presenter
- verify(mPresenter).updateNotificationViews(any());
- }
-
- @Test
- public void testContentViewInflationDuringRowInflationInflatesCorrectViews() {
- // GIVEN a notification is added and the row is inflating
- mEntryManager.addNotification(mSbn, mRankingMap);
- ArgumentCaptor<NotificationEntry> entryCaptor = ArgumentCaptor.forClass(
- NotificationEntry.class);
- verify(mEntryListener).onPendingEntryAdded(entryCaptor.capture());
- NotificationEntry entry = entryCaptor.getValue();
-
- // WHEN we try to bind a content view
- mRowContentBindStage.getStageParams(entry).requireContentViews(FLAG_CONTENT_VIEW_HEADS_UP);
- mRowContentBindStage.requestRebind(entry, null);
-
- waitForInflation();
-
- // THEN the notification has its row and all relevant content views inflated
- assertNotNull(entry.getRow());
- assertNotNull(entry.getRow().getPrivateLayout().getContractedChild());
- assertNotNull(entry.getRow().getPrivateLayout().getHeadsUpChild());
- }
-
- /**
- * Wait for inflation to finish.
- *
- * A few things to note
- * 1) Row inflation is done via {@link AsyncLayoutInflater} on its own background thread that
- * calls back to main thread which is why we wait on main thread.
- * 2) Row *content* inflation is done on the {@link FakeExecutor} we pass in in this test class
- * so we control when that work is done. The callback is still always on the main thread.
- */
- private void waitForInflation() {
- mHandler.postDelayed(TIMEOUT_RUNNABLE, TIMEOUT_TIME);
- final CountDownLatch latch = new CountDownLatch(1);
- NotificationEntryListener inflationListener = new NotificationEntryListener() {
- @Override
- public void onEntryInflated(NotificationEntry entry) {
- latch.countDown();
- }
-
- @Override
- public void onEntryReinflated(NotificationEntry entry) {
- latch.countDown();
- }
-
- @Override
- public void onInflationError(StatusBarNotification notification, Exception exception) {
- latch.countDown();
- }
- };
- mEntryManager.addNotificationEntryListener(inflationListener);
- while (latch.getCount() != 0) {
- mBgExecutor.runAllReady();
- TestableLooper.get(this).processMessages(1);
- }
- mHandler.removeCallbacks(TIMEOUT_RUNNABLE);
- mEntryManager.removeNotificationEntryListener(inflationListener);
- }
-
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
index e26c583..f57c409 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
@@ -156,13 +156,13 @@
mGutsManager = new NotificationGutsManager(mContext,
() -> Optional.of(mCentralSurfaces), mHandler, mHandler, mAccessibilityManager,
- mHighPriorityProvider, mINotificationManager, mNotificationEntryManager,
+ mHighPriorityProvider, mINotificationManager,
mPeopleSpaceWidgetManager, mLauncherApps, mShortcutManager,
mChannelEditorDialogController, mContextTracker, mAssistantFeedbackController,
Optional.of(mBubblesManager), new UiEventLoggerFake(), mOnUserInteractionCallback,
mShadeController, mock(DumpManager.class));
mGutsManager.setUpWithPresenter(mPresenter, mNotificationListContainer,
- mCheckSaveListener, mOnSettingsClickListener);
+ mOnSettingsClickListener);
mGutsManager.setNotificationActivityStarter(mNotificationActivityStarter);
}
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 30c40b9..2a3509c 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
@@ -121,7 +121,6 @@
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.NotificationShadeDepthController;
import com.android.systemui.statusbar.NotificationShadeWindowController;
-import com.android.systemui.statusbar.NotificationViewHierarchyManager;
import com.android.systemui.statusbar.OperatorNameViewController;
import com.android.systemui.statusbar.PulseExpansionHandler;
import com.android.systemui.statusbar.StatusBarState;
@@ -129,7 +128,6 @@
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
import com.android.systemui.statusbar.notification.NotifPipelineFlags;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
-import com.android.systemui.statusbar.notification.NotificationFilter;
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
import com.android.systemui.statusbar.notification.collection.NotifLiveDataStore;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
@@ -220,7 +218,6 @@
@Mock private BatteryController mBatteryController;
@Mock private DeviceProvisionedController mDeviceProvisionedController;
@Mock private StatusBarNotificationPresenter mNotificationPresenter;
- @Mock private NotificationFilter mNotificationFilter;
@Mock private AmbientDisplayConfiguration mAmbientDisplayConfiguration;
@Mock private NotificationLogger.ExpansionStateLogger mExpansionStateLogger;
@Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@@ -242,7 +239,6 @@
@Mock private AutoHideController mAutoHideController;
@Mock private StatusBarWindowController mStatusBarWindowController;
@Mock private StatusBarWindowStateController mStatusBarWindowStateController;
- @Mock private NotificationViewHierarchyManager mNotificationViewHierarchyManager;
@Mock private UserSwitcherController mUserSwitcherController;
@Mock private Bubbles mBubbles;
@Mock private NotificationShadeWindowController mNotificationShadeWindowController;
@@ -283,7 +279,6 @@
@Mock private OperatorNameViewController mOperatorNameViewController;
@Mock private OperatorNameViewController.Factory mOperatorNameViewControllerFactory;
@Mock private ActivityLaunchAnimator mActivityLaunchAnimator;
- @Mock private NotifPipelineFlags mNotifPipelineFlags;
@Mock private NotifLiveDataStore mNotifLiveDataStore;
@Mock private InteractionJankMonitor mJankMonitor;
@Mock private DeviceStateManager mDeviceStateManager;
@@ -298,7 +293,6 @@
@Before
public void setup() throws Exception {
MockitoAnnotations.initMocks(this);
- mDependency.injectTestDependency(NotificationFilter.class, mNotificationFilter);
IPowerManager powerManagerService = mock(IPowerManager.class);
IThermalService thermalService = mock(IThermalService.class);
@@ -310,7 +304,6 @@
mPowerManager,
mDreamManager,
mAmbientDisplayConfiguration,
- mNotificationFilter,
mStatusBarStateController,
mKeyguardStateController,
mBatteryController,
@@ -410,7 +403,6 @@
mNotificationGutsManager,
notificationLogger,
mNotificationInterruptStateProvider,
- mNotificationViewHierarchyManager,
new PanelExpansionStateManager(),
mKeyguardViewMediator,
new DisplayMetrics(),
@@ -472,7 +464,6 @@
mWallpaperManager,
Optional.of(mStartingSurface),
mActivityLaunchAnimator,
- mNotifPipelineFlags,
mJankMonitor,
mDeviceStateManager,
mWiredChargingRippleController, mDreamManager);
@@ -659,7 +650,6 @@
public void testShouldHeadsUp_nonSuppressedGroupSummary() throws Exception {
when(mPowerManager.isScreenOn()).thenReturn(true);
when(mHeadsUpManager.isSnoozed(anyString())).thenReturn(false);
- when(mNotificationFilter.shouldFilterOut(any())).thenReturn(false);
when(mDreamManager.isDreaming()).thenReturn(false);
Notification n = new Notification.Builder(getContext(), "a")
@@ -683,7 +673,6 @@
public void testShouldHeadsUp_suppressedGroupSummary() throws Exception {
when(mPowerManager.isScreenOn()).thenReturn(true);
when(mHeadsUpManager.isSnoozed(anyString())).thenReturn(false);
- when(mNotificationFilter.shouldFilterOut(any())).thenReturn(false);
when(mDreamManager.isDreaming()).thenReturn(false);
Notification n = new Notification.Builder(getContext(), "a")
@@ -707,7 +696,6 @@
public void testShouldHeadsUp_suppressedHeadsUp() throws Exception {
when(mPowerManager.isScreenOn()).thenReturn(true);
when(mHeadsUpManager.isSnoozed(anyString())).thenReturn(false);
- when(mNotificationFilter.shouldFilterOut(any())).thenReturn(false);
when(mDreamManager.isDreaming()).thenReturn(false);
Notification n = new Notification.Builder(getContext(), "a").build();
@@ -729,7 +717,6 @@
public void testShouldHeadsUp_noSuppressedHeadsUp() throws Exception {
when(mPowerManager.isScreenOn()).thenReturn(true);
when(mHeadsUpManager.isSnoozed(anyString())).thenReturn(false);
- when(mNotificationFilter.shouldFilterOut(any())).thenReturn(false);
when(mDreamManager.isDreaming()).thenReturn(false);
Notification n = new Notification.Builder(getContext(), "a").build();
@@ -1041,7 +1028,6 @@
PowerManager powerManager,
IDreamManager dreamManager,
AmbientDisplayConfiguration ambientDisplayConfiguration,
- NotificationFilter filter,
StatusBarStateController controller,
KeyguardStateController keyguardStateController,
BatteryController batteryController,
@@ -1055,7 +1041,6 @@
powerManager,
dreamManager,
ambientDisplayConfiguration,
- filter,
batteryController,
controller,
keyguardStateController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculatorTest.kt
index 0531bc7..c0243dc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculatorTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.phone
+import android.graphics.Color
import android.graphics.Rect
import android.testing.AndroidTestingRunner
import android.view.WindowInsetsController
@@ -56,6 +57,7 @@
@Mock private lateinit var statusBarBoundsProvider: StatusBarBoundsProvider
@Mock private lateinit var statusBarFragmentComponent: StatusBarFragmentComponent
@Mock private lateinit var dumpManager: DumpManager
+ @Mock private lateinit var letterboxBackgroundProvider: LetterboxBackgroundProvider
private lateinit var calculator: LetterboxAppearanceCalculator
@@ -63,8 +65,12 @@
fun setUp() {
MockitoAnnotations.initMocks(this)
whenever(statusBarFragmentComponent.boundsProvider).thenReturn(statusBarBoundsProvider)
- calculator = LetterboxAppearanceCalculator(lightBarController, dumpManager)
+ calculator =
+ LetterboxAppearanceCalculator(
+ lightBarController, dumpManager, letterboxBackgroundProvider)
calculator.onStatusBarViewInitialized(statusBarFragmentComponent)
+ whenever(letterboxBackgroundProvider.letterboxBackgroundColor).thenReturn(Color.BLACK)
+ whenever(letterboxBackgroundProvider.isLetterboxBackgroundMultiColored).thenReturn(false)
}
@Test
@@ -100,6 +106,23 @@
}
@Test
+ fun getLetterboxAppearance_noOverlap_BackgroundMultiColor_returnsAppearanceWithScrim() {
+ whenever(letterboxBackgroundProvider.isLetterboxBackgroundMultiColored).thenReturn(true)
+ whenever(statusBarBoundsProvider.visibleStartSideBounds).thenReturn(Rect(0, 0, 100, 100))
+ whenever(statusBarBoundsProvider.visibleEndSideBounds).thenReturn(Rect(200, 0, 300, 100))
+ val letterbox = letterboxWithInnerBounds(Rect(101, 0, 199, 100))
+
+ val letterboxAppearance =
+ calculator.getLetterboxAppearance(
+ TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, arrayOf(letterbox))
+
+ expect
+ .that(letterboxAppearance.appearance)
+ .isEqualTo(TEST_APPEARANCE or APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS)
+ expect.that(letterboxAppearance.appearanceRegions).isEqualTo(TEST_APPEARANCE_REGIONS)
+ }
+
+ @Test
fun getLetterboxAppearance_noOverlap_returnsAppearanceWithoutScrim() {
whenever(statusBarBoundsProvider.visibleStartSideBounds).thenReturn(Rect(0, 0, 100, 100))
whenever(statusBarBoundsProvider.visibleEndSideBounds).thenReturn(Rect(200, 0, 300, 100))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxBackgroundProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxBackgroundProviderTest.kt
new file mode 100644
index 0000000..44325dd
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxBackgroundProviderTest.kt
@@ -0,0 +1,100 @@
+/*
+ * 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.statusbar.phone
+
+import android.graphics.Color
+import android.testing.AndroidTestingRunner
+import android.view.IWindowManager
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class LetterboxBackgroundProviderTest : SysuiTestCase() {
+
+ private val fakeSystemClock = FakeSystemClock()
+ private val fakeExecutor = FakeExecutor(fakeSystemClock)
+
+ @Mock private lateinit var windowManager: IWindowManager
+ @Mock private lateinit var dumpManager: DumpManager
+
+ private lateinit var provider: LetterboxBackgroundProvider
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ provider = LetterboxBackgroundProvider(windowManager, fakeExecutor, dumpManager)
+ }
+
+ @Test
+ fun letterboxBackgroundColor_defaultValue_returnsBlack() {
+ assertThat(provider.letterboxBackgroundColor).isEqualTo(Color.BLACK)
+ }
+
+ @Test
+ fun letterboxBackgroundColor_afterOnStart_executorNotDone_returnsDefaultValue() {
+ whenever(windowManager.letterboxBackgroundColorInArgb).thenReturn(Color.RED)
+
+ provider.start()
+
+ assertThat(provider.letterboxBackgroundColor).isEqualTo(Color.BLACK)
+ }
+
+ @Test
+ fun letterboxBackgroundColor_afterOnStart_executorDone_returnsValueFromWindowManager() {
+ whenever(windowManager.letterboxBackgroundColorInArgb).thenReturn(Color.RED)
+
+ provider.start()
+ fakeExecutor.runAllReady()
+
+ assertThat(provider.letterboxBackgroundColor).isEqualTo(Color.RED)
+ }
+
+ @Test
+ fun isLetterboxBackgroundMultiColored_defaultValue_returnsFalse() {
+ assertThat(provider.isLetterboxBackgroundMultiColored).isEqualTo(false)
+ }
+ @Test
+ fun isLetterboxBackgroundMultiColored_afterOnStart_executorNotDone_returnsDefaultValue() {
+ whenever(windowManager.isLetterboxBackgroundMultiColored).thenReturn(true)
+
+ provider.start()
+
+ assertThat(provider.isLetterboxBackgroundMultiColored).isFalse()
+ }
+
+ @Test
+ fun isBackgroundMultiColored_afterOnStart_executorDone_returnsValueFromWindowManager() {
+ whenever(windowManager.isLetterboxBackgroundMultiColored).thenReturn(true)
+
+ provider.start()
+ fakeExecutor.runAllReady()
+
+ assertThat(provider.isLetterboxBackgroundMultiColored).isTrue()
+ }
+}
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 cf1eb35..eef43bd 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
@@ -43,7 +43,6 @@
import com.android.systemui.shade.NotificationPanelViewController;
import com.android.systemui.shade.NotificationShadeWindowView;
import com.android.systemui.shade.ShadeController;
-import com.android.systemui.shade.ShadeControllerImpl;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.KeyguardIndicationController;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
@@ -76,16 +75,17 @@
@RunWithLooper()
public class StatusBarNotificationPresenterTest extends SysuiTestCase {
private StatusBarNotificationPresenter mStatusBarNotificationPresenter;
- private NotificationInterruptStateProvider mNotificationInterruptStateProvider =
+ private final NotificationInterruptStateProvider mNotificationInterruptStateProvider =
mock(NotificationInterruptStateProvider.class);
private NotificationInterruptSuppressor mInterruptSuppressor;
private CommandQueue mCommandQueue;
private FakeMetricsLogger mMetricsLogger;
- private ShadeController mShadeController = mock(ShadeController.class);
- private CentralSurfaces mCentralSurfaces = mock(CentralSurfaces.class);
- private KeyguardStateController mKeyguardStateController = mock(KeyguardStateController.class);
- private NotifPipelineFlags mNotifPipelineFlags = mock(NotifPipelineFlags.class);
- private InitController mInitController = new InitController();
+ private final ShadeController mShadeController = mock(ShadeController.class);
+ private final CentralSurfaces mCentralSurfaces = mock(CentralSurfaces.class);
+ private final KeyguardStateController mKeyguardStateController =
+ mock(KeyguardStateController.class);
+ private final NotifPipelineFlags mNotifPipelineFlags = mock(NotifPipelineFlags.class);
+ private final InitController mInitController = new InitController();
@Before
public void setup() {
@@ -116,13 +116,11 @@
mock(ActivityStarter.class),
stackScrollLayoutController,
mock(DozeScrimController.class),
- mock(ScrimController.class),
mock(NotificationShadeWindowController.class),
mock(DynamicPrivacyController.class),
mKeyguardStateController,
mock(KeyguardIndicationController.class),
mCentralSurfaces,
- mock(ShadeControllerImpl.class),
mock(LockscreenShadeTransitionController.class),
mCommandQueue,
mock(NotificationLockscreenUserManager.class),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/ConnectivityPipelineLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/ConnectivityPipelineLoggerTest.kt
new file mode 100644
index 0000000..2915ae8
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/ConnectivityPipelineLoggerTest.kt
@@ -0,0 +1,75 @@
+/*
+ * 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.statusbar.pipeline
+
+import android.net.Network
+import android.net.NetworkCapabilities
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.log.LogBufferFactory
+import com.android.systemui.log.LogcatEchoTracker
+import com.google.common.truth.Truth.assertThat
+import java.io.PrintWriter
+import java.io.StringWriter
+import org.junit.Test
+import org.mockito.Mockito
+import org.mockito.Mockito.mock
+
+@SmallTest
+class ConnectivityPipelineLoggerTest : SysuiTestCase() {
+ private val buffer = LogBufferFactory(DumpManager(), mock(LogcatEchoTracker::class.java))
+ .create("buffer", 10)
+ private val logger = ConnectivityPipelineLogger(buffer)
+
+ @Test
+ fun testLogNetworkCapsChange_bufferHasInfo() {
+ logger.logOnCapabilitiesChanged(NET_1, NET_1_CAPS)
+
+ val stringWriter = StringWriter()
+ buffer.dump(PrintWriter(stringWriter), tailLength = 0)
+ val actualString = stringWriter.toString()
+
+ val expectedNetId = NET_1_ID.toString()
+ val expectedCaps = NET_1_CAPS.toString()
+
+ assertThat(actualString).contains(expectedNetId)
+ assertThat(actualString).contains(expectedCaps)
+ }
+
+ @Test
+ fun testLogOnLost_bufferHasNetIdOfLostNetwork() {
+ logger.logOnLost(NET_1)
+
+ val stringWriter = StringWriter()
+ buffer.dump(PrintWriter(stringWriter), tailLength = 0)
+ val actualString = stringWriter.toString()
+
+ val expectedNetId = NET_1_ID.toString()
+
+ assertThat(actualString).contains(expectedNetId)
+ }
+
+ private val NET_1_ID = 100
+ private val NET_1 = com.android.systemui.util.mockito.mock<Network>().also {
+ Mockito.`when`(it.getNetId()).thenReturn(NET_1_ID)
+ }
+ private val NET_1_CAPS = NetworkCapabilities.Builder()
+ .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
+ .build()
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/repository/NetworkCapabilitiesRepoTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/repository/NetworkCapabilitiesRepoTest.kt
new file mode 100644
index 0000000..40f8fbf
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/repository/NetworkCapabilitiesRepoTest.kt
@@ -0,0 +1,252 @@
+/*
+ * 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.statusbar.pipeline.repository
+
+import android.net.ConnectivityManager
+import android.net.ConnectivityManager.NetworkCallback
+import android.net.Network
+import android.net.NetworkCapabilities
+import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED
+import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED
+import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
+import android.net.NetworkCapabilities.TRANSPORT_WIFI
+import android.net.NetworkRequest
+import android.test.suitebuilder.annotation.SmallTest
+import android.testing.AndroidTestingRunner
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.pipeline.ConnectivityPipelineLogger
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.withArgCaptor
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.CoroutineStart
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.any
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+// TODO(b/240619365): Update this test to use `runTest` when we update the testing library
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class NetworkCapabilitiesRepoTest : SysuiTestCase() {
+ @Mock private lateinit var connectivityManager: ConnectivityManager
+ @Mock private lateinit var logger: ConnectivityPipelineLogger
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ }
+
+ @Test
+ fun testOnCapabilitiesChanged_oneNewNetwork_networkStored() = runBlocking {
+ // GIVEN a repo hooked up to [ConnectivityManager]
+ val scope = CoroutineScope(Dispatchers.Unconfined)
+ val repo = NetworkCapabilitiesRepo(
+ connectivityManager = connectivityManager,
+ scope = scope,
+ logger = logger,
+ )
+
+ val job = launch(start = CoroutineStart.UNDISPATCHED) {
+ repo.dataStream.collect {
+ }
+ }
+
+ val callback: NetworkCallback = withArgCaptor {
+ verify(connectivityManager)
+ .registerNetworkCallback(any(NetworkRequest::class.java), capture())
+ }
+
+ // WHEN a new network is added
+ callback.onCapabilitiesChanged(NET_1, NET_1_CAPS)
+
+ val currentMap = repo.dataStream.value
+
+ // THEN it is emitted from the flow
+ assertThat(currentMap[NET_1_ID]?.network).isEqualTo(NET_1)
+ assertThat(currentMap[NET_1_ID]?.capabilities).isEqualTo(NET_1_CAPS)
+
+ job.cancel()
+ scope.cancel()
+ }
+
+ @Test
+ fun testOnCapabilitiesChanged_twoNewNetworks_bothStored() = runBlocking {
+ // GIVEN a repo hooked up to [ConnectivityManager]
+ val scope = CoroutineScope(Dispatchers.Unconfined)
+ val repo = NetworkCapabilitiesRepo(
+ connectivityManager = connectivityManager,
+ scope = scope,
+ logger = logger,
+ )
+
+ val job = launch(start = CoroutineStart.UNDISPATCHED) {
+ repo.dataStream.collect {
+ }
+ }
+
+ val callback: NetworkCallback = withArgCaptor {
+ verify(connectivityManager)
+ .registerNetworkCallback(any(NetworkRequest::class.java), capture())
+ }
+
+ // WHEN two new networks are added
+ callback.onCapabilitiesChanged(NET_1, NET_1_CAPS)
+ callback.onCapabilitiesChanged(NET_2, NET_2_CAPS)
+
+ val currentMap = repo.dataStream.value
+
+ // THEN the current state of the flow reflects 2 networks
+ assertThat(currentMap[NET_1_ID]?.network).isEqualTo(NET_1)
+ assertThat(currentMap[NET_1_ID]?.capabilities).isEqualTo(NET_1_CAPS)
+ assertThat(currentMap[NET_2_ID]?.network).isEqualTo(NET_2)
+ assertThat(currentMap[NET_2_ID]?.capabilities).isEqualTo(NET_2_CAPS)
+
+ job.cancel()
+ scope.cancel()
+ }
+
+ @Test
+ fun testOnCapabilitesChanged_newCapabilitiesForExistingNetwork_areCaptured() = runBlocking {
+ // GIVEN a repo hooked up to [ConnectivityManager]
+ val scope = CoroutineScope(Dispatchers.Unconfined)
+ val repo = NetworkCapabilitiesRepo(
+ connectivityManager = connectivityManager,
+ scope = scope,
+ logger = logger,
+ )
+
+ val job = launch(start = CoroutineStart.UNDISPATCHED) {
+ repo.dataStream.collect {
+ }
+ }
+
+ val callback: NetworkCallback = withArgCaptor {
+ verify(connectivityManager)
+ .registerNetworkCallback(any(NetworkRequest::class.java), capture())
+ }
+
+ // WHEN a network is added, and then its capabilities are changed
+ callback.onCapabilitiesChanged(NET_1, NET_1_CAPS)
+ callback.onCapabilitiesChanged(NET_1, NET_2_CAPS)
+
+ val currentMap = repo.dataStream.value
+
+ // THEN the current state of the flow reflects the new capabilities
+ assertThat(currentMap[NET_1_ID]?.capabilities).isEqualTo(NET_2_CAPS)
+
+ job.cancel()
+ scope.cancel()
+ }
+
+ @Test
+ fun testOnLost_networkIsRemoved() = runBlocking {
+ // GIVEN a repo hooked up to [ConnectivityManager]
+ val scope = CoroutineScope(Dispatchers.Unconfined)
+ val repo = NetworkCapabilitiesRepo(
+ connectivityManager = connectivityManager,
+ scope = scope,
+ logger = logger,
+ )
+
+ val job = launch(start = CoroutineStart.UNDISPATCHED) {
+ repo.dataStream.collect {
+ }
+ }
+
+ val callback: NetworkCallback = withArgCaptor {
+ verify(connectivityManager)
+ .registerNetworkCallback(any(NetworkRequest::class.java), capture())
+ }
+
+ // WHEN two new networks are added, and one is removed
+ callback.onCapabilitiesChanged(NET_1, NET_1_CAPS)
+ callback.onCapabilitiesChanged(NET_2, NET_2_CAPS)
+ callback.onLost(NET_1)
+
+ val currentMap = repo.dataStream.value
+
+ // THEN the current state of the flow reflects only the remaining network
+ assertThat(currentMap[NET_1_ID]).isNull()
+ assertThat(currentMap[NET_2_ID]?.network).isEqualTo(NET_2)
+ assertThat(currentMap[NET_2_ID]?.capabilities).isEqualTo(NET_2_CAPS)
+
+ job.cancel()
+ scope.cancel()
+ }
+
+ @Test
+ fun testOnLost_noNetworks_doesNotCrash() = runBlocking {
+ // GIVEN a repo hooked up to [ConnectivityManager]
+ val scope = CoroutineScope(Dispatchers.Unconfined)
+ val repo = NetworkCapabilitiesRepo(
+ connectivityManager = connectivityManager,
+ scope = scope,
+ logger = logger,
+ )
+
+ val job = launch(start = CoroutineStart.UNDISPATCHED) {
+ repo.dataStream.collect {
+ }
+ }
+
+ val callback: NetworkCallback = withArgCaptor {
+ verify(connectivityManager)
+ .registerNetworkCallback(any(NetworkRequest::class.java), capture())
+ }
+
+ // WHEN no networks are added, and one is removed
+ callback.onLost(NET_1)
+
+ val currentMap = repo.dataStream.value
+
+ // THEN the current state of the flow shows no networks
+ assertThat(currentMap).isEmpty()
+
+ job.cancel()
+ scope.cancel()
+ }
+
+ private val NET_1_ID = 100
+ private val NET_1 = mock<Network>().also {
+ whenever(it.getNetId()).thenReturn(NET_1_ID)
+ }
+ private val NET_2_ID = 200
+ private val NET_2 = mock<Network>().also {
+ whenever(it.getNetId()).thenReturn(NET_2_ID)
+ }
+
+ private val NET_1_CAPS = NetworkCapabilities.Builder()
+ .addTransportType(TRANSPORT_CELLULAR)
+ .addCapability(NET_CAPABILITY_VALIDATED)
+ .build()
+
+ private val NET_2_CAPS = NetworkCapabilities.Builder()
+ .addTransportType(TRANSPORT_WIFI)
+ .addCapability(NET_CAPABILITY_NOT_METERED)
+ .addCapability(NET_CAPABILITY_VALIDATED)
+ .build()
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt
new file mode 100644
index 0000000..f51f783
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt
@@ -0,0 +1,166 @@
+/*
+ * 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.unfold
+
+import android.hardware.devicestate.DeviceStateManager
+import android.hardware.devicestate.DeviceStateManager.FoldStateListener
+import android.os.Handler
+import android.os.PowerManager
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.testing.TestableLooper.RunWithLooper
+import android.view.ViewGroup
+import android.view.ViewTreeObserver
+import androidx.test.filters.SmallTest
+import com.android.internal.util.LatencyTracker
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.shade.NotificationPanelViewController
+import com.android.systemui.statusbar.LightRevealScrim
+import com.android.systemui.statusbar.phone.CentralSurfaces
+import com.android.systemui.unfold.util.FoldableDeviceStates
+import com.android.systemui.unfold.util.FoldableTestUtils
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.settings.GlobalSettings
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+@RunWithLooper
+class FoldAodAnimationControllerTest : SysuiTestCase() {
+
+ @Mock lateinit var deviceStateManager: DeviceStateManager
+
+ @Mock lateinit var wakefulnessLifecycle: WakefulnessLifecycle
+
+ @Mock lateinit var globalSettings: GlobalSettings
+
+ @Mock lateinit var latencyTracker: LatencyTracker
+
+ @Mock lateinit var centralSurfaces: CentralSurfaces
+
+ @Mock lateinit var lightRevealScrim: LightRevealScrim
+
+ @Mock lateinit var notificationPanelViewController: NotificationPanelViewController
+
+ @Mock lateinit var viewGroup: ViewGroup
+
+ @Mock lateinit var viewTreeObserver: ViewTreeObserver
+
+ @Captor private lateinit var foldStateListenerCaptor: ArgumentCaptor<FoldStateListener>
+
+ private lateinit var deviceStates: FoldableDeviceStates
+
+ private lateinit var testableLooper: TestableLooper
+
+ lateinit var foldAodAnimationController: FoldAodAnimationController
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ testableLooper = TestableLooper.get(this)
+
+ foldAodAnimationController =
+ FoldAodAnimationController(
+ Handler(testableLooper.looper),
+ context.mainExecutor,
+ context,
+ deviceStateManager,
+ wakefulnessLifecycle,
+ globalSettings,
+ latencyTracker,
+ )
+ .apply { initialize(centralSurfaces, lightRevealScrim) }
+ deviceStates = FoldableTestUtils.findDeviceStates(context)
+
+ whenever(notificationPanelViewController.view).thenReturn(viewGroup)
+ whenever(viewGroup.viewTreeObserver).thenReturn(viewTreeObserver)
+ whenever(wakefulnessLifecycle.lastSleepReason)
+ .thenReturn(PowerManager.GO_TO_SLEEP_REASON_DEVICE_FOLD)
+ whenever(centralSurfaces.notificationPanelViewController)
+ .thenReturn(notificationPanelViewController)
+ whenever(notificationPanelViewController.startFoldToAodAnimation(any(), any(), any()))
+ .then {
+ val onActionStarted = it.arguments[0] as Runnable
+ onActionStarted.run()
+ }
+ verify(deviceStateManager).registerCallback(any(), foldStateListenerCaptor.capture())
+
+ foldAodAnimationController.setIsDozing(dozing = true)
+ setAodEnabled(enabled = true)
+ sendFoldEvent(folded = false)
+ }
+
+ @Test
+ fun onFolded_aodDisabled_doesNotLogLatency() {
+ setAodEnabled(enabled = false)
+
+ fold()
+ simulateScreenTurningOn()
+
+ verifyNoMoreInteractions(latencyTracker)
+ }
+
+ @Test
+ fun onFolded_aodEnabled_logsLatency() {
+ setAodEnabled(enabled = true)
+
+ fold()
+ simulateScreenTurningOn()
+
+ verify(latencyTracker).onActionStart(any())
+ verify(latencyTracker).onActionEnd(any())
+ }
+
+ @Test
+ fun onFolded_animationCancelled_doesNotLogLatency() {
+ setAodEnabled(enabled = true)
+
+ fold()
+ foldAodAnimationController.onScreenTurningOn({})
+ foldAodAnimationController.onStartedWakingUp()
+ testableLooper.processAllMessages()
+
+ verify(latencyTracker).onActionStart(any())
+ verify(latencyTracker).onActionCancel(any())
+ }
+
+ private fun simulateScreenTurningOn() {
+ foldAodAnimationController.onScreenTurningOn({})
+ foldAodAnimationController.onScreenTurnedOn()
+ testableLooper.processAllMessages()
+ }
+
+ private fun fold() = sendFoldEvent(folded = true)
+
+ private fun setAodEnabled(enabled: Boolean) =
+ foldAodAnimationController.onAlwaysOnChanged(alwaysOn = enabled)
+
+ private fun sendFoldEvent(folded: Boolean) {
+ val state = if (folded) deviceStates.folded else deviceStates.unfolded
+ foldStateListenerCaptor.value.onStateChanged(state)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index 59a9a3c..d009280 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -43,6 +43,7 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.app.ActivityManager;
import android.app.IActivityManager;
import android.app.INotificationManager;
import android.app.Notification;
@@ -93,7 +94,6 @@
import com.android.systemui.statusbar.RankingBuilder;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.notification.NotifPipelineFlags;
-import com.android.systemui.statusbar.notification.NotificationFilter;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
@@ -143,7 +143,10 @@
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import org.mockito.stubbing.Answer;
+import java.util.Arrays;
+import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Optional;
@@ -252,6 +255,8 @@
private TaskViewTransitions mTaskViewTransitions;
@Mock
private Optional<OneHandedController> mOneHandedOptional;
+ @Mock
+ private UserManager mUserManager;
private TestableBubblePositioner mPositioner;
@@ -314,12 +319,14 @@
mPositioner.setMaxBubbles(5);
mBubbleData = new BubbleData(mContext, mBubbleLogger, mPositioner, syncExecutor);
+ when(mUserManager.getProfiles(ActivityManager.getCurrentUser())).thenReturn(
+ Collections.singletonList(mock(UserInfo.class)));
+
TestableNotificationInterruptStateProviderImpl interruptionStateProvider =
new TestableNotificationInterruptStateProviderImpl(mContext.getContentResolver(),
mock(PowerManager.class),
mock(IDreamManager.class),
mock(AmbientDisplayConfiguration.class),
- mock(NotificationFilter.class),
mock(StatusBarStateController.class),
mock(KeyguardStateController.class),
mock(BatteryController.class),
@@ -339,7 +346,7 @@
mStatusBarService,
mWindowManager,
mWindowManagerShellWrapper,
- mock(UserManager.class),
+ mUserManager,
mLauncherApps,
mBubbleLogger,
mTaskStackListener,
@@ -1025,7 +1032,7 @@
assertThat(mBubbleData.getOverflowBubbleWithKey(mBubbleEntry2.getKey())).isNotNull();
// Switch users
- mBubbleController.onUserChanged(secondUserId);
+ switchUser(secondUserId);
assertThat(mBubbleData.getOverflowBubbles()).isEmpty();
// Give this user some bubbles
@@ -1042,6 +1049,41 @@
verify(mDataRepository, times(2)).loadBubbles(anyInt(), any());
}
+ @Test
+ public void testOnUserChanged_bubblesRestored() {
+ int firstUserId = mBubbleEntry.getStatusBarNotification().getUser().getIdentifier();
+ int secondUserId = mBubbleEntryUser11.getStatusBarNotification().getUser().getIdentifier();
+ // Mock current profile
+ when(mLockscreenUserManager.isCurrentProfile(firstUserId)).thenReturn(true);
+ when(mLockscreenUserManager.isCurrentProfile(secondUserId)).thenReturn(false);
+
+ mBubbleController.updateBubble(mBubbleEntry);
+ assertThat(mBubbleController.hasBubbles()).isTrue();
+ // We start with 1 bubble
+ assertThat(mBubbleData.getBubbles()).hasSize(1);
+
+ // Switch to second user
+ switchUser(secondUserId);
+
+ // Second user has no bubbles
+ assertThat(mBubbleController.hasBubbles()).isFalse();
+
+ // Send bubble update for first user, ensure it does not show up
+ mBubbleController.updateBubble(mBubbleEntry2);
+ assertThat(mBubbleController.hasBubbles()).isFalse();
+
+ // Start returning notif for first user again
+ when(mCommonNotifCollection.getAllNotifs()).thenReturn(Arrays.asList(mRow, mRow2));
+
+ // Switch back to first user
+ switchUser(firstUserId);
+
+ // Check we now have two bubbles, one previous and one new that came in
+ assertThat(mBubbleController.hasBubbles()).isTrue();
+ // Now there are 2 bubbles
+ assertThat(mBubbleData.getBubbles()).hasSize(2);
+ }
+
/**
* Verifies we only load the overflow data once.
*/
@@ -1443,6 +1485,14 @@
.build();
}
+ private void switchUser(int userId) {
+ when(mLockscreenUserManager.isCurrentProfile(anyInt())).thenAnswer(
+ (Answer<Boolean>) invocation -> invocation.<Integer>getArgument(0) == userId);
+ SparseArray<UserInfo> userInfos = new SparseArray<>(1);
+ userInfos.put(userId, mock(UserInfo.class));
+ mBubbleController.onCurrentProfilesChanged(userInfos);
+ mBubbleController.onUserChanged(userId);
+ }
/**
* Asserts that the bubble stack is expanded and also validates the cached state is updated.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java
index d80ea15..9635faf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java
@@ -24,7 +24,6 @@
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.notification.NotifPipelineFlags;
-import com.android.systemui.statusbar.notification.NotificationFilter;
import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider;
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptLogger;
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl;
@@ -40,7 +39,6 @@
PowerManager powerManager,
IDreamManager dreamManager,
AmbientDisplayConfiguration ambientDisplayConfiguration,
- NotificationFilter filter,
StatusBarStateController statusBarStateController,
KeyguardStateController keyguardStateController,
BatteryController batteryController,
@@ -53,7 +51,6 @@
powerManager,
dreamManager,
ambientDisplayConfiguration,
- filter,
batteryController,
statusBarStateController,
keyguardStateController,
diff --git a/packages/SystemUI/tests/utils/AndroidManifest.xml b/packages/SystemUI/tests/utils/AndroidManifest.xml
new file mode 100644
index 0000000..cbef5f6
--- /dev/null
+++ b/packages/SystemUI/tests/utils/AndroidManifest.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.systemui.tests.utils">
+
+
+</manifest>
+
diff --git a/packages/SystemUI/tests/src/com/android/systemui/SysuiBaseFragmentTest.java b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiBaseFragmentTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/SysuiBaseFragmentTest.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/SysuiBaseFragmentTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/SysuiTestableContext.java b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestableContext.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/SysuiTestableContext.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestableContext.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/TestableDependency.java b/packages/SystemUI/tests/utils/src/com/android/systemui/TestableDependency.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/TestableDependency.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/TestableDependency.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingManagerFake.java b/packages/SystemUI/tests/utils/src/com/android/systemui/classifier/FalsingManagerFake.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingManagerFake.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/classifier/FalsingManagerFake.java
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/people/FakePeopleTileRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/people/FakePeopleTileRepository.kt
new file mode 100644
index 0000000..0bde5d2
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/people/FakePeopleTileRepository.kt
@@ -0,0 +1,30 @@
+/*
+ * 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.people
+
+import com.android.systemui.people.data.model.PeopleTileModel
+import com.android.systemui.people.data.repository.PeopleTileRepository
+
+/** A fake [PeopleTileRepository] to be used in tests. */
+class FakePeopleTileRepository(
+ private val priorityTiles: List<PeopleTileModel>,
+ private val recentTiles: List<PeopleTileModel>,
+) : PeopleTileRepository {
+ override fun priorityTiles(): List<PeopleTileModel> = priorityTiles
+
+ override fun recentTiles(): List<PeopleTileModel> = recentTiles
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/people/FakePeopleWidgetRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/people/FakePeopleWidgetRepository.kt
new file mode 100644
index 0000000..2f81409
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/people/FakePeopleWidgetRepository.kt
@@ -0,0 +1,29 @@
+/*
+ * 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.people
+
+import com.android.systemui.people.data.repository.PeopleWidgetRepository
+import com.android.systemui.people.widget.PeopleTileKey
+
+/** A fake [PeopleWidgetRepository] to be used in tests. */
+class FakePeopleWidgetRepository(
+ private val onSetWidgetTile: (widgetId: Int, tileKey: PeopleTileKey) -> Unit = { _, _ -> },
+) : PeopleWidgetRepository {
+ override fun setWidgetTile(widgetId: Int, tileKey: PeopleTileKey) {
+ onSetWidgetTile(widgetId, tileKey)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/FakeScrollCaptureConnection.java b/packages/SystemUI/tests/utils/src/com/android/systemui/screenshot/FakeScrollCaptureConnection.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/screenshot/FakeScrollCaptureConnection.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/screenshot/FakeScrollCaptureConnection.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/FakeSession.java b/packages/SystemUI/tests/utils/src/com/android/systemui/screenshot/FakeSession.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/screenshot/FakeSession.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/screenshot/FakeSession.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/RankingBuilder.java b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/RankingBuilder.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/RankingBuilder.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/RankingBuilder.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/SbnBuilder.java b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/SbnBuilder.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/SbnBuilder.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/SbnBuilder.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilder.java b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilder.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilder.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilder.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/FakeConfigurationController.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeConfigurationController.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/FakeConfigurationController.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeConfigurationController.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/DeviceConfigProxyFake.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/DeviceConfigProxyFake.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/util/DeviceConfigProxyFake.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/util/DeviceConfigProxyFake.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeExecutor.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/concurrency/FakeExecutor.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeExecutor.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/util/concurrency/FakeExecutor.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeRepeatableExecutor.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/concurrency/FakeRepeatableExecutor.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeRepeatableExecutor.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/util/concurrency/FakeRepeatableExecutor.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeThreadFactory.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/concurrency/FakeThreadFactory.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeThreadFactory.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/util/concurrency/FakeThreadFactory.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/condition/FakeCondition.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/condition/FakeCondition.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/util/condition/FakeCondition.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/util/condition/FakeCondition.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/io/FakeBasicFileAttributes.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/io/FakeBasicFileAttributes.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/util/io/FakeBasicFileAttributes.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/util/io/FakeBasicFileAttributes.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeProximitySensor.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/sensors/FakeProximitySensor.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeProximitySensor.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/util/sensors/FakeProximitySensor.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeSensorManager.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/sensors/FakeSensorManager.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeSensorManager.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/util/sensors/FakeSensorManager.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeThresholdSensor.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/sensors/FakeThresholdSensor.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeThresholdSensor.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/util/sensors/FakeThresholdSensor.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettings.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettings.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettings.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettings.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/time/FakeSystemClock.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/time/FakeSystemClock.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/util/time/FakeSystemClock.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/util/time/FakeSystemClock.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/wakelock/WakeLockFake.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/wakelock/WakeLockFake.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/util/wakelock/WakeLockFake.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/util/wakelock/WakeLockFake.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/BaseLeakChecker.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/BaseLeakChecker.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/utils/leaks/BaseLeakChecker.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/BaseLeakChecker.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBatteryController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeBatteryController.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBatteryController.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeBatteryController.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBluetoothController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeBluetoothController.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBluetoothController.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeBluetoothController.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeCastController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeCastController.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeCastController.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeCastController.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeConfigurationController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeConfigurationController.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeConfigurationController.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeConfigurationController.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeDataSaverController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeDataSaverController.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeDataSaverController.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeDataSaverController.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeExtensionController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeExtensionController.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeExtensionController.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeExtensionController.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeFlashlightController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeFlashlightController.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeFlashlightController.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeFlashlightController.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeHotspotController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeHotspotController.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeHotspotController.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeHotspotController.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeKeyguardStateController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeKeyguardStateController.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeKeyguardStateController.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeKeyguardStateController.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeLocationController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeLocationController.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeLocationController.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeLocationController.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeManagedProfileController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeManagedProfileController.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeManagedProfileController.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeManagedProfileController.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeNetworkController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeNetworkController.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeNetworkController.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeNetworkController.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeNextAlarmController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeNextAlarmController.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeNextAlarmController.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeNextAlarmController.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakePluginManager.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakePluginManager.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakePluginManager.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakePluginManager.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeRotationLockController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeRotationLockController.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeRotationLockController.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeRotationLockController.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeSecurityController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeSecurityController.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeSecurityController.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeSecurityController.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeTunerService.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeTunerService.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeTunerService.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeTunerService.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeUserInfoController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeUserInfoController.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeUserInfoController.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeUserInfoController.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeZenModeController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeZenModeController.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeZenModeController.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeZenModeController.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/LeakCheckedTest.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/LeakCheckedTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/utils/leaks/LeakCheckedTest.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/LeakCheckedTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/os/FakeHandler.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/os/FakeHandler.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/utils/os/FakeHandler.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/utils/os/FakeHandler.java
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
index 676bde7..d33e7b2 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
@@ -146,11 +146,8 @@
if (getWindowTokenForUserAndWindowIdLocked(resolvedUserId, windowId) == null) {
return;
}
- final AccessibilityWindowAttributes currentAttrs = mWindowAttributes.get(windowId);
- if (currentAttrs == null || !currentAttrs.equals(attributes)) {
- mWindowAttributes.put(windowId, attributes);
- shouldComputeWindows = findWindowInfoByIdLocked(windowId) != null;
- }
+ mWindowAttributes.put(windowId, attributes);
+ shouldComputeWindows = findWindowInfoByIdLocked(windowId) != null;
}
if (shouldComputeWindows) {
mWindowManagerInternal.computeWindowsForAccessibility(displayId);
diff --git a/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java b/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java
index 803177b..3fa0ab6 100644
--- a/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java
+++ b/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java
@@ -16,8 +16,6 @@
package com.android.server.accessibility;
-import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_ACCESSIBILITY_ACTIONS;
-
import android.accessibilityservice.AccessibilityService;
import android.app.PendingIntent;
import android.app.RemoteAction;
@@ -34,6 +32,7 @@
import android.view.InputDevice;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
+import android.view.WindowManager;
import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
import com.android.internal.R;
@@ -392,8 +391,8 @@
private boolean takeScreenshot() {
ScreenshotHelper screenshotHelper = (mScreenshotHelperSupplier != null)
? mScreenshotHelperSupplier.get() : new ScreenshotHelper(mContext);
- screenshotHelper.takeScreenshot(android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN,
- true, true, SCREENSHOT_ACCESSIBILITY_ACTIONS,
+ screenshotHelper.takeScreenshot(WindowManager.TAKE_SCREENSHOT_FULLSCREEN,
+ WindowManager.ScreenshotSource.SCREENSHOT_ACCESSIBILITY_ACTIONS,
new Handler(Looper.getMainLooper()), null);
return true;
}
diff --git a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
index 3ab873d..e07f412 100644
--- a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
+++ b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
@@ -439,6 +439,7 @@
log(MetricsEvent.TYPE_DISMISS);
hideFillDialogUiThread(callback);
callback.requestShowSoftInput(focusedId);
+ callback.requestFallbackFromFillDialog();
}
@Override
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index a238705..768fdfd 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -755,6 +755,7 @@
case FrameworkStatsLog.BATTERY_USAGE_STATS_SINCE_RESET:
final BatteryUsageStatsQuery querySinceReset =
new BatteryUsageStatsQuery.Builder()
+ .setMaxStatsAgeMs(0)
.includeProcessStateData()
.includeVirtualUids()
.build();
@@ -763,6 +764,7 @@
case FrameworkStatsLog.BATTERY_USAGE_STATS_SINCE_RESET_USING_POWER_PROFILE_MODEL:
final BatteryUsageStatsQuery queryPowerProfile =
new BatteryUsageStatsQuery.Builder()
+ .setMaxStatsAgeMs(0)
.includeProcessStateData()
.includeVirtualUids()
.powerProfileModeledOnly()
@@ -779,6 +781,7 @@
final long sessionEnd = mStats.getStartClockTime();
final BatteryUsageStatsQuery queryBeforeReset =
new BatteryUsageStatsQuery.Builder()
+ .setMaxStatsAgeMs(0)
.includeProcessStateData()
.includeVirtualUids()
.aggregateSnapshots(sessionStart, sessionEnd)
diff --git a/services/core/java/com/android/server/am/UidObserverController.java b/services/core/java/com/android/server/am/UidObserverController.java
index e8c1b54..51cb987 100644
--- a/services/core/java/com/android/server/am/UidObserverController.java
+++ b/services/core/java/com/android/server/am/UidObserverController.java
@@ -15,6 +15,7 @@
*/
package com.android.server.am;
+import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_UID_OBSERVERS;
@@ -25,6 +26,7 @@
import android.app.ActivityManager;
import android.app.ActivityManagerProto;
import android.app.IUidObserver;
+import android.content.pm.PackageManager;
import android.os.Handler;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
@@ -81,7 +83,9 @@
@NonNull String callingPackage, int callingUid) {
synchronized (mLock) {
mUidObservers.register(observer, new UidObserverRegistration(callingUid,
- callingPackage, which, cutpoint));
+ callingPackage, which, cutpoint,
+ ActivityManager.checkUidPermission(INTERACT_ACROSS_USERS_FULL, callingUid)
+ == PackageManager.PERMISSION_GRANTED));
}
}
@@ -252,6 +256,11 @@
final ChangeRecord item = mActiveUidChanges[j];
final long start = SystemClock.uptimeMillis();
final int change = item.change;
+ // Does the user have permission? Don't send a non user UID change otherwise
+ if (UserHandle.getUserId(item.uid) != UserHandle.getUserId(reg.mUid)
+ && !reg.mCanInteractAcrossUsers) {
+ continue;
+ }
if (change == UidRecord.CHANGE_PROCSTATE
&& (reg.mWhich & ActivityManager.UID_OBSERVER_PROCSTATE) == 0) {
// No-op common case: no significant change, the observer is not
@@ -437,6 +446,7 @@
private final String mPkg;
private final int mWhich;
private final int mCutpoint;
+ private final boolean mCanInteractAcrossUsers;
/**
* Total # of callback calls that took more than {@link #SLOW_UID_OBSERVER_THRESHOLD_MS}.
@@ -467,11 +477,13 @@
ActivityManagerProto.UID_OBSERVER_FLAG_PROC_OOM_ADJ,
};
- UidObserverRegistration(int uid, @NonNull String pkg, int which, int cutpoint) {
+ UidObserverRegistration(int uid, @NonNull String pkg, int which, int cutpoint,
+ boolean canInteractAcrossUsers) {
this.mUid = uid;
this.mPkg = pkg;
this.mWhich = which;
this.mCutpoint = cutpoint;
+ this.mCanInteractAcrossUsers = canInteractAcrossUsers;
mLastProcStates = cutpoint >= ActivityManager.MIN_PROCESS_STATE
? new SparseIntArray() : null;
}
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 774fe5b..16a060a 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -222,6 +222,11 @@
*/
private static final int VPN_DEFAULT_SCORE = 101;
+ /**
+ * The initial token value of IKE session.
+ */
+ private static final int STARTING_TOKEN = -1;
+
// TODO: create separate trackers for each unique VPN to support
// automated reconnection
@@ -514,12 +519,8 @@
@NonNull NetworkScore score,
@NonNull NetworkAgentConfig config,
@Nullable NetworkProvider provider) {
- return new NetworkAgent(context, looper, logTag, nc, lp, score, config, provider) {
- @Override
- public void onNetworkUnwanted() {
- // We are user controlled, not driven by NetworkRequest.
- }
- };
+ return new VpnNetworkAgentWrapper(
+ context, looper, logTag, nc, lp, score, config, provider);
}
}
@@ -785,7 +786,7 @@
}
}
- private boolean isVpnApp(String packageName) {
+ private static boolean isVpnApp(String packageName) {
return packageName != null && !VpnConfig.LEGACY_VPN.equals(packageName);
}
@@ -1813,7 +1814,7 @@
Log.wtf(TAG, "Failed to add restricted user to owner", e);
}
if (mNetworkAgent != null) {
- mNetworkAgent.sendNetworkCapabilities(mNetworkCapabilities);
+ doSendNetworkCapabilities(mNetworkAgent, mNetworkCapabilities);
}
}
setVpnForcedLocked(mLockdown);
@@ -1843,7 +1844,7 @@
Log.wtf(TAG, "Failed to remove restricted user to owner", e);
}
if (mNetworkAgent != null) {
- mNetworkAgent.sendNetworkCapabilities(mNetworkCapabilities);
+ doSendNetworkCapabilities(mNetworkAgent, mNetworkCapabilities);
}
}
setVpnForcedLocked(mLockdown);
@@ -2077,7 +2078,7 @@
return false;
}
boolean success = jniAddAddress(mInterface, address, prefixLength);
- mNetworkAgent.sendLinkProperties(makeLinkProperties());
+ doSendLinkProperties(mNetworkAgent, makeLinkProperties());
return success;
}
@@ -2086,7 +2087,7 @@
return false;
}
boolean success = jniDelAddress(mInterface, address, prefixLength);
- mNetworkAgent.sendLinkProperties(makeLinkProperties());
+ doSendLinkProperties(mNetworkAgent, makeLinkProperties());
return success;
}
@@ -2100,8 +2101,11 @@
// Make defensive copy since the content of array might be altered by the caller.
mConfig.underlyingNetworks =
(networks != null) ? Arrays.copyOf(networks, networks.length) : null;
- mNetworkAgent.setUnderlyingNetworks((mConfig.underlyingNetworks != null)
- ? Arrays.asList(mConfig.underlyingNetworks) : null);
+ doSetUnderlyingNetworks(
+ mNetworkAgent,
+ (mConfig.underlyingNetworks != null)
+ ? Arrays.asList(mConfig.underlyingNetworks)
+ : null);
return true;
}
@@ -2589,7 +2593,7 @@
}
@Nullable
- protected synchronized NetworkCapabilities getRedactedNetworkCapabilitiesOfUnderlyingNetwork(
+ private synchronized NetworkCapabilities getRedactedNetworkCapabilities(
NetworkCapabilities nc) {
if (nc == null) return null;
return mConnectivityManager.getRedactedNetworkCapabilitiesForPackage(
@@ -2597,8 +2601,7 @@
}
@Nullable
- protected synchronized LinkProperties getRedactedLinkPropertiesOfUnderlyingNetwork(
- LinkProperties lp) {
+ private synchronized LinkProperties getRedactedLinkProperties(LinkProperties lp) {
if (lp == null) return null;
return mConnectivityManager.getRedactedLinkPropertiesForPackage(lp, mOwnerUID, mPackage);
}
@@ -2712,11 +2715,13 @@
private boolean mIsRunning = true;
/**
- * The token used by the primary/current/active IKE session.
+ * The token that identifies the most recently created IKE session.
*
- * <p>This token MUST be updated when the VPN switches to use a new IKE session.
+ * <p>This token is monotonically increasing and will never be reset in the lifetime of this
+ * Ikev2VpnRunner, but it does get reset across runs. It also MUST be accessed on the
+ * executor thread and updated when a new IKE session is created.
*/
- private int mCurrentToken = -1;
+ private int mCurrentToken = STARTING_TOKEN;
@Nullable private IpSecTunnelInterface mTunnelIface;
@Nullable private Network mActiveNetwork;
@@ -2910,7 +2915,7 @@
return; // Link properties are already sent.
} else {
// Underlying networks also set in agentConnect()
- networkAgent.setUnderlyingNetworks(Collections.singletonList(network));
+ doSetUnderlyingNetworks(networkAgent, Collections.singletonList(network));
mNetworkCapabilities =
new NetworkCapabilities.Builder(mNetworkCapabilities)
.setUnderlyingNetworks(Collections.singletonList(network))
@@ -2920,7 +2925,7 @@
lp = makeLinkProperties(); // Accesses VPN instance fields; must be locked
}
- networkAgent.sendLinkProperties(lp);
+ doSendLinkProperties(networkAgent, lp);
} catch (Exception e) {
Log.d(TAG, "Error in ChildOpened for token " + token, e);
onSessionLost(token, e);
@@ -2987,7 +2992,7 @@
new NetworkCapabilities.Builder(mNetworkCapabilities)
.setUnderlyingNetworks(Collections.singletonList(network))
.build();
- mNetworkAgent.setUnderlyingNetworks(Collections.singletonList(network));
+ doSetUnderlyingNetworks(mNetworkAgent, Collections.singletonList(network));
}
mTunnelIface.setUnderlyingNetwork(network);
@@ -3208,7 +3213,7 @@
mExecutor.schedule(
() -> {
if (isActiveToken(token)) {
- handleSessionLost(null, network);
+ handleSessionLost(null /* exception */, network);
} else {
Log.d(
TAG,
@@ -3225,7 +3230,7 @@
TimeUnit.MILLISECONDS);
} else {
Log.d(TAG, "Call handleSessionLost for losing network " + network);
- handleSessionLost(null, network);
+ handleSessionLost(null /* exception */, network);
}
}
@@ -3293,70 +3298,69 @@
// already terminated due to other failures.
cancelHandleNetworkLostTimeout();
- synchronized (Vpn.this) {
- String category = null;
- int errorClass = -1;
- int errorCode = -1;
- if (exception instanceof IkeProtocolException) {
- final IkeProtocolException ikeException = (IkeProtocolException) exception;
- category = VpnManager.CATEGORY_EVENT_IKE_ERROR;
- errorCode = ikeException.getErrorType();
+ String category = null;
+ int errorClass = -1;
+ int errorCode = -1;
+ if (exception instanceof IllegalArgumentException) {
+ // Failed to build IKE/ChildSessionParams; fatal profile configuration error
+ markFailedAndDisconnect(exception);
+ return;
+ }
- switch (ikeException.getErrorType()) {
- case IkeProtocolException.ERROR_TYPE_NO_PROPOSAL_CHOSEN: // Fallthrough
- case IkeProtocolException.ERROR_TYPE_INVALID_KE_PAYLOAD: // Fallthrough
- case IkeProtocolException.ERROR_TYPE_AUTHENTICATION_FAILED: // Fallthrough
- case IkeProtocolException.ERROR_TYPE_SINGLE_PAIR_REQUIRED: // Fallthrough
- case IkeProtocolException.ERROR_TYPE_FAILED_CP_REQUIRED: // Fallthrough
- case IkeProtocolException.ERROR_TYPE_TS_UNACCEPTABLE:
- // All the above failures are configuration errors, and are terminal
- errorClass = VpnManager.ERROR_CLASS_NOT_RECOVERABLE;
- break;
- // All other cases possibly recoverable.
- default:
- // All the above failures are configuration errors, and are terminal
- errorClass = VpnManager.ERROR_CLASS_RECOVERABLE;
- }
- } else if (exception instanceof IllegalArgumentException) {
- // Failed to build IKE/ChildSessionParams; fatal profile configuration error
- markFailedAndDisconnect(exception);
- return;
- } else if (exception instanceof IkeNetworkLostException) {
- category = VpnManager.CATEGORY_EVENT_NETWORK_ERROR;
- errorClass = VpnManager.ERROR_CLASS_RECOVERABLE;
- errorCode = VpnManager.ERROR_CODE_NETWORK_LOST;
- } else if (exception instanceof IkeNonProtocolException) {
- category = VpnManager.CATEGORY_EVENT_NETWORK_ERROR;
- errorClass = VpnManager.ERROR_CLASS_RECOVERABLE;
- if (exception.getCause() instanceof UnknownHostException) {
- errorCode = VpnManager.ERROR_CODE_NETWORK_UNKNOWN_HOST;
- } else if (exception.getCause() instanceof IkeTimeoutException) {
- errorCode = VpnManager.ERROR_CODE_NETWORK_PROTOCOL_TIMEOUT;
- } else if (exception.getCause() instanceof IOException) {
- errorCode = VpnManager.ERROR_CODE_NETWORK_IO;
- }
- } else if (exception != null) {
- Log.wtf(TAG, "onSessionLost: exception = " + exception);
+ if (exception instanceof IkeProtocolException) {
+ final IkeProtocolException ikeException = (IkeProtocolException) exception;
+ category = VpnManager.CATEGORY_EVENT_IKE_ERROR;
+ errorCode = ikeException.getErrorType();
+
+ switch (ikeException.getErrorType()) {
+ case IkeProtocolException.ERROR_TYPE_NO_PROPOSAL_CHOSEN: // Fallthrough
+ case IkeProtocolException.ERROR_TYPE_INVALID_KE_PAYLOAD: // Fallthrough
+ case IkeProtocolException.ERROR_TYPE_AUTHENTICATION_FAILED: // Fallthrough
+ case IkeProtocolException.ERROR_TYPE_SINGLE_PAIR_REQUIRED: // Fallthrough
+ case IkeProtocolException.ERROR_TYPE_FAILED_CP_REQUIRED: // Fallthrough
+ case IkeProtocolException.ERROR_TYPE_TS_UNACCEPTABLE:
+ // All the above failures are configuration errors, and are terminal
+ errorClass = VpnManager.ERROR_CLASS_NOT_RECOVERABLE;
+ break;
+ // All other cases possibly recoverable.
+ default:
+ errorClass = VpnManager.ERROR_CLASS_RECOVERABLE;
}
+ } else if (exception instanceof IkeNetworkLostException) {
+ category = VpnManager.CATEGORY_EVENT_NETWORK_ERROR;
+ errorClass = VpnManager.ERROR_CLASS_RECOVERABLE;
+ errorCode = VpnManager.ERROR_CODE_NETWORK_LOST;
+ } else if (exception instanceof IkeNonProtocolException) {
+ category = VpnManager.CATEGORY_EVENT_NETWORK_ERROR;
+ errorClass = VpnManager.ERROR_CLASS_RECOVERABLE;
+ if (exception.getCause() instanceof UnknownHostException) {
+ errorCode = VpnManager.ERROR_CODE_NETWORK_UNKNOWN_HOST;
+ } else if (exception.getCause() instanceof IkeTimeoutException) {
+ errorCode = VpnManager.ERROR_CODE_NETWORK_PROTOCOL_TIMEOUT;
+ } else if (exception.getCause() instanceof IOException) {
+ errorCode = VpnManager.ERROR_CODE_NETWORK_IO;
+ }
+ } else if (exception != null) {
+ Log.wtf(TAG, "onSessionLost: exception = " + exception);
+ }
+ synchronized (Vpn.this) {
// TODO(b/230548427): Remove SDK check once VPN related stuff are
// decoupled from ConnectivityServiceTest.
if (SdkLevel.isAtLeastT() && category != null && isVpnApp(mPackage)) {
sendEventToVpnManagerApp(category, errorClass, errorCode,
getPackage(), mSessionKey, makeVpnProfileStateLocked(),
mActiveNetwork,
- getRedactedNetworkCapabilitiesOfUnderlyingNetwork(
- mUnderlyingNetworkCapabilities),
- getRedactedLinkPropertiesOfUnderlyingNetwork(
- mUnderlyingLinkProperties));
+ getRedactedNetworkCapabilities(mUnderlyingNetworkCapabilities),
+ getRedactedLinkProperties(mUnderlyingLinkProperties));
}
+ }
- if (errorClass == VpnManager.ERROR_CLASS_NOT_RECOVERABLE) {
- markFailedAndDisconnect(exception);
- return;
- } else {
- scheduleRetryNewIkeSession();
- }
+ if (errorClass == VpnManager.ERROR_CLASS_NOT_RECOVERABLE) {
+ markFailedAndDisconnect(exception);
+ return;
+ } else {
+ scheduleRetryNewIkeSession();
}
mUnderlyingNetworkCapabilities = null;
@@ -3384,7 +3388,7 @@
null /*gateway*/, null /*iface*/, RTN_UNREACHABLE));
}
if (mNetworkAgent != null) {
- mNetworkAgent.sendLinkProperties(makeLinkProperties());
+ doSendLinkProperties(mNetworkAgent, makeLinkProperties());
}
}
}
@@ -4121,7 +4125,7 @@
.setUids(createUserAndRestrictedProfilesRanges(
mUserId, null /* allowedApplications */, excludedApps))
.build();
- mNetworkAgent.sendNetworkCapabilities(mNetworkCapabilities);
+ doSendNetworkCapabilities(mNetworkAgent, mNetworkCapabilities);
}
}
}
@@ -4198,6 +4202,85 @@
return isCurrentIkev2VpnLocked(packageName) ? makeVpnProfileStateLocked() : null;
}
+ /** Proxy to allow different testing setups */
+ // TODO: b/240492694 Remove VpnNetworkAgentWrapper and this method when
+ // NetworkAgent#sendLinkProperties can be un-finalized.
+ private static void doSendLinkProperties(
+ @NonNull NetworkAgent agent, @NonNull LinkProperties lp) {
+ if (agent instanceof VpnNetworkAgentWrapper) {
+ ((VpnNetworkAgentWrapper) agent).doSendLinkProperties(lp);
+ } else {
+ agent.sendLinkProperties(lp);
+ }
+ }
+
+ /** Proxy to allow different testing setups */
+ // TODO: b/240492694 Remove VpnNetworkAgentWrapper and this method when
+ // NetworkAgent#sendNetworkCapabilities can be un-finalized.
+ private static void doSendNetworkCapabilities(
+ @NonNull NetworkAgent agent, @NonNull NetworkCapabilities nc) {
+ if (agent instanceof VpnNetworkAgentWrapper) {
+ ((VpnNetworkAgentWrapper) agent).doSendNetworkCapabilities(nc);
+ } else {
+ agent.sendNetworkCapabilities(nc);
+ }
+ }
+
+ /** Proxy to allow different testing setups */
+ // TODO: b/240492694 Remove VpnNetworkAgentWrapper and this method when
+ // NetworkAgent#setUnderlyingNetworks can be un-finalized.
+ private static void doSetUnderlyingNetworks(
+ @NonNull NetworkAgent agent, @NonNull List<Network> networks) {
+ if (agent instanceof VpnNetworkAgentWrapper) {
+ ((VpnNetworkAgentWrapper) agent).doSetUnderlyingNetworks(networks);
+ } else {
+ agent.setUnderlyingNetworks(networks);
+ }
+ }
+
+ /**
+ * Proxy to allow testing
+ *
+ * @hide
+ */
+ // TODO: b/240492694 Remove VpnNetworkAgentWrapper when NetworkAgent's methods can be
+ // un-finalized.
+ @VisibleForTesting
+ public static class VpnNetworkAgentWrapper extends NetworkAgent {
+ /** Create an VpnNetworkAgentWrapper */
+ public VpnNetworkAgentWrapper(
+ @NonNull Context context,
+ @NonNull Looper looper,
+ @NonNull String logTag,
+ @NonNull NetworkCapabilities nc,
+ @NonNull LinkProperties lp,
+ @NonNull NetworkScore score,
+ @NonNull NetworkAgentConfig config,
+ @Nullable NetworkProvider provider) {
+ super(context, looper, logTag, nc, lp, score, config, provider);
+ }
+
+ /** Update the LinkProperties */
+ public void doSendLinkProperties(@NonNull LinkProperties lp) {
+ sendLinkProperties(lp);
+ }
+
+ /** Update the NetworkCapabilities */
+ public void doSendNetworkCapabilities(@NonNull NetworkCapabilities nc) {
+ sendNetworkCapabilities(nc);
+ }
+
+ /** Set the underlying networks */
+ public void doSetUnderlyingNetworks(@NonNull List<Network> networks) {
+ setUnderlyingNetworks(networks);
+ }
+
+ @Override
+ public void onNetworkUnwanted() {
+ // We are user controlled, not driven by NetworkRequest.
+ }
+ }
+
/**
* Proxy to allow testing
*
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 4e33fd0..c01a228 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -1501,6 +1501,8 @@
// Animate the screen brightness when the screen is on or dozing.
// Skip the animation when the screen is off or suspended or transition to/from VR.
boolean brightnessAdjusted = false;
+ final boolean brightnessIsTemporary =
+ mAppliedTemporaryBrightness || mAppliedTemporaryAutoBrightnessAdjustment;
if (!mPendingScreenOff) {
if (mSkipScreenOnBrightnessRamp) {
if (state == Display.STATE_ON) {
@@ -1533,8 +1535,6 @@
// level without it being a noticeable jump since any actual content isn't yet visible.
final boolean isDisplayContentVisible =
mColorFadeEnabled && mPowerState.getColorFadeLevel() == 1.0f;
- final boolean brightnessIsTemporary =
- mAppliedTemporaryBrightness || mAppliedTemporaryAutoBrightnessAdjustment;
// We only want to animate the brightness if it is between 0.0f and 1.0f.
// brightnessState can contain the values -1.0f and NaN, which we do not want to
// animate to. To avoid this, we check the value first.
@@ -1607,7 +1607,8 @@
brightnessAdjusted = saveBrightnessInfo(getScreenBrightnessSetting());
}
- if (brightnessAdjusted) {
+ // Only notify if the brightness adjustment is not temporary (i.e. slider has been released)
+ if (brightnessAdjusted && !brightnessIsTemporary) {
postBrightnessChangeRunnable();
}
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 804315c..b86fa7a 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -35,8 +35,6 @@
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PIN;
import static com.android.internal.widget.LockPatternUtils.CURRENT_LSKF_BASED_PROTECTOR_ID_KEY;
import static com.android.internal.widget.LockPatternUtils.EscrowTokenStateChangeCallback;
-import static com.android.internal.widget.LockPatternUtils.PROFILE_KEY_NAME_DECRYPT;
-import static com.android.internal.widget.LockPatternUtils.PROFILE_KEY_NAME_ENCRYPT;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE;
import static com.android.internal.widget.LockPatternUtils.USER_FRP;
@@ -122,6 +120,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.internal.notification.SystemNotificationChannels;
+import com.android.internal.util.ArrayUtils;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.Preconditions;
@@ -147,7 +146,6 @@
import libcore.util.HexEncoding;
-import java.io.ByteArrayOutputStream;
import java.io.FileDescriptor;
import java.io.FileNotFoundException;
import java.io.IOException;
@@ -211,6 +209,9 @@
// user's credential must be presented again, e.g. via ConfirmLockPattern/ConfirmLockPassword.
private static final int GK_PW_HANDLE_STORE_DURATION_MS = 10 * 60 * 1000; // 10 minutes
+ private static final String PROFILE_KEY_NAME_ENCRYPT = "profile_key_name_encrypt_";
+ private static final String PROFILE_KEY_NAME_DECRYPT = "profile_key_name_decrypt_";
+
// Order of holding lock: mSeparateChallengeLock -> mSpManager -> this
// Do not call into ActivityManager while holding mSpManager lock.
private final Object mSeparateChallengeLock = new Object();
@@ -234,7 +235,8 @@
protected final UserManager mUserManager;
private final IStorageManager mStorageManager;
private final IActivityManager mActivityManager;
- private final SyntheticPasswordManager mSpManager;
+ @VisibleForTesting
+ protected final SyntheticPasswordManager mSpManager;
private final KeyStore mKeyStore;
private final java.security.KeyStore mJavaKeyStore;
@@ -1625,7 +1627,7 @@
}
onSyntheticPasswordKnown(userId, sp);
- setLockCredentialWithSpLocked(credential, sp, userId);
+ setLockCredentialWithSpLocked(credential, sp, userId, isLockTiedToParent);
sendCredentialsOnChangeIfRequired(credential, userId, isLockTiedToParent);
return true;
}
@@ -1639,15 +1641,15 @@
if (newCredential.isPattern()) {
setBoolean(LockPatternUtils.PATTERN_EVER_CHOSEN_KEY, true, userHandle);
}
- updatePasswordHistory(newCredential, userHandle);
mContext.getSystemService(TrustManager.class).reportEnabledTrustAgentsChanged(userHandle);
}
/**
- * Store the hash of the *current* password in the password history list, if device policy
- * enforces password history requirement.
+ * Store the hash of the new password in the password history list, if device policy enforces
+ * a password history requirement.
*/
- private void updatePasswordHistory(LockscreenCredential password, int userHandle) {
+ private void updatePasswordHistory(SyntheticPassword sp, LockscreenCredential password,
+ int userHandle, boolean isLockTiedToParent) {
if (password.isNone()) {
return;
}
@@ -1655,8 +1657,11 @@
// Do not keep track of historical patterns
return;
}
- // Add the password to the password history. We assume all
- // password hashes have the same length for simplicity of implementation.
+ if (isLockTiedToParent) {
+ // Do not keep track of historical auto-generated profile passwords
+ return;
+ }
+ // Add the password to the password history.
String passwordHistory = getString(
LockPatternUtils.PASSWORD_HISTORY_KEY, /* defaultValue= */ null, userHandle);
if (passwordHistory == null) {
@@ -1666,13 +1671,9 @@
if (passwordHistoryLength == 0) {
passwordHistory = "";
} else {
- final byte[] hashFactor = getHashFactor(password, userHandle);
+ final byte[] hashFactor = sp.derivePasswordHashFactor();
final byte[] salt = getSalt(userHandle).getBytes();
String hash = password.passwordToHistoryHash(salt, hashFactor);
- if (hash == null) {
- Slog.e(TAG, "Compute new style password hash failed, fallback to legacy style");
- hash = password.legacyPasswordToHash(salt);
- }
if (TextUtils.isEmpty(passwordHistory)) {
passwordHistory = hash;
} else {
@@ -1847,8 +1848,8 @@
@VisibleForTesting /** Note: this method is overridden in unit tests */
protected void tieProfileLockToParent(int userId, LockscreenCredential password) {
if (DEBUG) Slog.v(TAG, "tieProfileLockToParent for user: " + userId);
- byte[] encryptionResult;
- byte[] iv;
+ final byte[] iv;
+ final byte[] ciphertext;
try {
KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES);
keyGenerator.init(new SecureRandom());
@@ -1877,7 +1878,7 @@
KeyProperties.KEY_ALGORITHM_AES + "/" + KeyProperties.BLOCK_MODE_GCM + "/"
+ KeyProperties.ENCRYPTION_PADDING_NONE);
cipher.init(Cipher.ENCRYPT_MODE, keyStoreEncryptionKey);
- encryptionResult = cipher.doFinal(password.getCredential());
+ ciphertext = cipher.doFinal(password.getCredential());
iv = cipher.getIV();
} finally {
// The original key can now be discarded.
@@ -1888,17 +1889,10 @@
| NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeyException e) {
throw new IllegalStateException("Failed to encrypt key", e);
}
- ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
- try {
- if (iv.length != PROFILE_KEY_IV_SIZE) {
- throw new IllegalArgumentException("Invalid iv length: " + iv.length);
- }
- outputStream.write(iv);
- outputStream.write(encryptionResult);
- } catch (IOException e) {
- throw new IllegalStateException("Failed to concatenate byte arrays", e);
+ if (iv.length != PROFILE_KEY_IV_SIZE) {
+ throw new IllegalArgumentException("Invalid iv length: " + iv.length);
}
- mStorage.writeChildProfileLock(userId, outputStream.toByteArray());
+ mStorage.writeChildProfileLock(userId, ArrayUtils.concat(iv, ciphertext));
}
private void setUserKeyProtection(int userId, byte[] key) {
@@ -2650,7 +2644,7 @@
*/
@GuardedBy("mSpManager")
private long setLockCredentialWithSpLocked(LockscreenCredential credential,
- SyntheticPassword sp, int userId) {
+ SyntheticPassword sp, int userId, boolean isLockTiedToParent) {
if (DEBUG) Slog.d(TAG, "setLockCredentialWithSpLocked: user=" + userId);
final int savedCredentialType = getCredentialTypeInternal(userId);
final long oldProtectorId = getCurrentLskfBasedProtectorId(userId);
@@ -2688,6 +2682,7 @@
LockPatternUtils.invalidateCredentialTypeCache();
synchronizeUnifiedWorkChallengeForProfiles(userId, profilePasswords);
+ updatePasswordHistory(sp, credential, userId, isLockTiedToParent);
setUserPasswordMetrics(credential, userId);
mManagedProfilePasswordCache.removePassword(userId);
if (savedCredentialType != CREDENTIAL_TYPE_NONE) {
@@ -2933,7 +2928,8 @@
return false;
}
onSyntheticPasswordKnown(userId, result.syntheticPassword);
- setLockCredentialWithSpLocked(credential, result.syntheticPassword, userId);
+ setLockCredentialWithSpLocked(credential, result.syntheticPassword, userId,
+ /* isLockTiedToParent= */ false);
return true;
}
diff --git a/services/core/java/com/android/server/locksettings/ManagedProfilePasswordCache.java b/services/core/java/com/android/server/locksettings/ManagedProfilePasswordCache.java
index 672c3f7..e43d4e8 100644
--- a/services/core/java/com/android/server/locksettings/ManagedProfilePasswordCache.java
+++ b/services/core/java/com/android/server/locksettings/ManagedProfilePasswordCache.java
@@ -26,6 +26,7 @@
import android.util.Slog;
import android.util.SparseArray;
+import com.android.internal.util.ArrayUtils;
import com.android.internal.widget.LockscreenCredential;
import java.security.GeneralSecurityException;
@@ -117,8 +118,7 @@
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] ciphertext = cipher.doFinal(password.getCredential());
byte[] iv = cipher.getIV();
- byte[] block = Arrays.copyOf(iv, ciphertext.length + iv.length);
- System.arraycopy(ciphertext, 0, block, iv.length, ciphertext.length);
+ byte[] block = ArrayUtils.concat(iv, ciphertext);
mEncryptedPasswords.put(userId, block);
} catch (GeneralSecurityException e) {
Slog.d(TAG, "Cannot encrypt", e);
diff --git a/services/core/java/com/android/server/locksettings/SyntheticPasswordCrypto.java b/services/core/java/com/android/server/locksettings/SyntheticPasswordCrypto.java
index b06af8e..a931844 100644
--- a/services/core/java/com/android/server/locksettings/SyntheticPasswordCrypto.java
+++ b/services/core/java/com/android/server/locksettings/SyntheticPasswordCrypto.java
@@ -25,7 +25,8 @@
import android.system.keystore2.KeyDescriptor;
import android.util.Slog;
-import java.io.ByteArrayOutputStream;
+import com.android.internal.util.ArrayUtils;
+
import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
@@ -50,9 +51,9 @@
public class SyntheticPasswordCrypto {
private static final String TAG = "SyntheticPasswordCrypto";
- private static final int PROFILE_KEY_IV_SIZE = 12;
- private static final int DEFAULT_TAG_LENGTH_BITS = 128;
- private static final int AES_KEY_LENGTH = 32; // 256-bit AES key
+ private static final int AES_GCM_KEY_SIZE = 32; // AES-256-GCM
+ private static final int AES_GCM_IV_SIZE = 12;
+ private static final int AES_GCM_TAG_SIZE = 16;
private static final byte[] PROTECTOR_SECRET_PERSONALIZATION = "application-id".getBytes();
// Time between the user credential is verified with GK and the decryption of synthetic password
// under the auth-bound key. This should always happen one after the other, but give it 15
@@ -65,11 +66,11 @@
if (blob == null) {
return null;
}
- byte[] iv = Arrays.copyOfRange(blob, 0, PROFILE_KEY_IV_SIZE);
- byte[] ciphertext = Arrays.copyOfRange(blob, PROFILE_KEY_IV_SIZE, blob.length);
+ byte[] iv = Arrays.copyOfRange(blob, 0, AES_GCM_IV_SIZE);
+ byte[] ciphertext = Arrays.copyOfRange(blob, AES_GCM_IV_SIZE, blob.length);
Cipher cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/"
+ KeyProperties.BLOCK_MODE_GCM + "/" + KeyProperties.ENCRYPTION_PADDING_NONE);
- cipher.init(Cipher.DECRYPT_MODE, key, new GCMParameterSpec(DEFAULT_TAG_LENGTH_BITS, iv));
+ cipher.init(Cipher.DECRYPT_MODE, key, new GCMParameterSpec(AES_GCM_TAG_SIZE * 8, iv));
return cipher.doFinal(ciphertext);
}
@@ -86,23 +87,20 @@
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] ciphertext = cipher.doFinal(blob);
byte[] iv = cipher.getIV();
- if (iv.length != PROFILE_KEY_IV_SIZE) {
- throw new IllegalArgumentException("Invalid iv length: " + iv.length);
+ if (iv.length != AES_GCM_IV_SIZE) {
+ throw new IllegalArgumentException("Invalid iv length: " + iv.length + " bytes");
}
final GCMParameterSpec spec = cipher.getParameters().getParameterSpec(
GCMParameterSpec.class);
- if (spec.getTLen() != DEFAULT_TAG_LENGTH_BITS) {
- throw new IllegalArgumentException("Invalid tag length: " + spec.getTLen());
+ if (spec.getTLen() != AES_GCM_TAG_SIZE * 8) {
+ throw new IllegalArgumentException("Invalid tag length: " + spec.getTLen() + " bits");
}
- ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
- outputStream.write(iv);
- outputStream.write(ciphertext);
- return outputStream.toByteArray();
+ return ArrayUtils.concat(iv, ciphertext);
}
public static byte[] encrypt(byte[] keyBytes, byte[] personalization, byte[] message) {
byte[] keyHash = personalizedHash(personalization, keyBytes);
- SecretKeySpec key = new SecretKeySpec(Arrays.copyOf(keyHash, AES_KEY_LENGTH),
+ SecretKeySpec key = new SecretKeySpec(Arrays.copyOf(keyHash, AES_GCM_KEY_SIZE),
KeyProperties.KEY_ALGORITHM_AES);
try {
return encrypt(key, message);
@@ -116,7 +114,7 @@
public static byte[] decrypt(byte[] keyBytes, byte[] personalization, byte[] ciphertext) {
byte[] keyHash = personalizedHash(personalization, keyBytes);
- SecretKeySpec key = new SecretKeySpec(Arrays.copyOf(keyHash, AES_KEY_LENGTH),
+ SecretKeySpec key = new SecretKeySpec(Arrays.copyOf(keyHash, AES_GCM_KEY_SIZE),
KeyProperties.KEY_ALGORITHM_AES);
try {
return decrypt(key, ciphertext);
@@ -129,18 +127,20 @@
}
/**
- * Decrypt a legacy SP blob which did the Keystore and software encryption layers in the wrong
+ * Decrypts a legacy SP blob which did the Keystore and software encryption layers in the wrong
* order.
*/
- public static byte[] decryptBlobV1(String keyAlias, byte[] blob, byte[] protectorSecret) {
+ public static byte[] decryptBlobV1(String protectorKeyAlias, byte[] blob,
+ byte[] protectorSecret) {
try {
KeyStore keyStore = getKeyStore();
- SecretKey keyStoreKey = (SecretKey) keyStore.getKey(keyAlias, null);
- if (keyStoreKey == null) {
- throw new IllegalStateException("SP key is missing: " + keyAlias);
+ SecretKey protectorKey = (SecretKey) keyStore.getKey(protectorKeyAlias, null);
+ if (protectorKey == null) {
+ throw new IllegalStateException("SP protector key is missing: "
+ + protectorKeyAlias);
}
byte[] intermediate = decrypt(protectorSecret, PROTECTOR_SECRET_PERSONALIZATION, blob);
- return decrypt(keyStoreKey, intermediate);
+ return decrypt(protectorKey, intermediate);
} catch (Exception e) {
Slog.e(TAG, "Failed to decrypt V1 blob", e);
throw new IllegalStateException("Failed to decrypt blob", e);
@@ -165,15 +165,17 @@
/**
* Decrypts an SP blob that was created by {@link #createBlob}.
*/
- public static byte[] decryptBlob(String keyAlias, byte[] blob, byte[] protectorSecret) {
+ public static byte[] decryptBlob(String protectorKeyAlias, byte[] blob,
+ byte[] protectorSecret) {
try {
final KeyStore keyStore = getKeyStore();
- SecretKey keyStoreKey = (SecretKey) keyStore.getKey(keyAlias, null);
- if (keyStoreKey == null) {
- throw new IllegalStateException("SP key is missing: " + keyAlias);
+ SecretKey protectorKey = (SecretKey) keyStore.getKey(protectorKeyAlias, null);
+ if (protectorKey == null) {
+ throw new IllegalStateException("SP protector key is missing: "
+ + protectorKeyAlias);
}
- byte[] intermediate = decrypt(keyStoreKey, blob);
+ byte[] intermediate = decrypt(protectorKey, blob);
return decrypt(protectorSecret, PROTECTOR_SECRET_PERSONALIZATION, intermediate);
} catch (CertificateException | IOException | BadPaddingException
| IllegalBlockSizeException
@@ -187,20 +189,21 @@
/**
* Creates a new SP blob by encrypting the given data. Two encryption layers are applied: an
- * inner layer using a hash of protectorSecret as the key, and an outer layer using a new
- * Keystore key with the given alias and optionally bound to a SID.
+ * inner layer using a hash of protectorSecret as the key, and an outer layer using the
+ * protector key, which is a Keystore key that is optionally bound to a SID. This method
+ * creates the protector key and stores it under protectorKeyAlias.
*
* The reason we use a layer of software encryption, instead of using protectorSecret as the
* applicationId of the Keystore key, is to work around buggy KeyMint implementations that don't
* cryptographically bind the applicationId to the key. The Keystore layer has to be the outer
* layer, so that LSKF verification is ratelimited by Gatekeeper when Weaver is unavailable.
*/
- public static byte[] createBlob(String keyAlias, byte[] data, byte[] protectorSecret,
+ public static byte[] createBlob(String protectorKeyAlias, byte[] data, byte[] protectorSecret,
long sid) {
try {
KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES);
- keyGenerator.init(AES_KEY_LENGTH * 8, new SecureRandom());
- SecretKey keyStoreKey = keyGenerator.generateKey();
+ keyGenerator.init(AES_GCM_KEY_SIZE * 8, new SecureRandom());
+ SecretKey protectorKey = keyGenerator.generateKey();
final KeyStore keyStore = getKeyStore();
KeyProtection.Builder builder = new KeyProtection.Builder(KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
@@ -214,9 +217,9 @@
final KeyProtection protNonRollbackResistant = builder.build();
builder.setRollbackResistant(true);
final KeyProtection protRollbackResistant = builder.build();
- final KeyStore.SecretKeyEntry entry = new KeyStore.SecretKeyEntry(keyStoreKey);
+ final KeyStore.SecretKeyEntry entry = new KeyStore.SecretKeyEntry(protectorKey);
try {
- keyStore.setEntry(keyAlias, entry, protRollbackResistant);
+ keyStore.setEntry(protectorKeyAlias, entry, protRollbackResistant);
Slog.i(TAG, "Using rollback-resistant key");
} catch (KeyStoreException e) {
if (!(e.getCause() instanceof android.security.KeyStoreException)) {
@@ -228,11 +231,11 @@
}
Slog.w(TAG, "Rollback-resistant keys unavailable. Falling back to "
+ "non-rollback-resistant key");
- keyStore.setEntry(keyAlias, entry, protNonRollbackResistant);
+ keyStore.setEntry(protectorKeyAlias, entry, protNonRollbackResistant);
}
byte[] intermediate = encrypt(protectorSecret, PROTECTOR_SECRET_PERSONALIZATION, data);
- return encrypt(keyStoreKey, intermediate);
+ return encrypt(protectorKey, intermediate);
} catch (CertificateException | IOException | BadPaddingException
| IllegalBlockSizeException
| KeyStoreException | NoSuchPaddingException | NoSuchAlgorithmException
@@ -243,15 +246,15 @@
}
}
- public static void destroyBlobKey(String keyAlias) {
+ public static void destroyProtectorKey(String keyAlias) {
KeyStore keyStore;
try {
keyStore = getKeyStore();
keyStore.deleteEntry(keyAlias);
- Slog.i(TAG, "SP key deleted: " + keyAlias);
+ Slog.i(TAG, "Deleted SP protector key " + keyAlias);
} catch (KeyStoreException | NoSuchAlgorithmException | CertificateException
| IOException e) {
- Slog.e(TAG, "Failed to destroy blob", e);
+ Slog.e(TAG, "Failed to delete SP protector key " + keyAlias, e);
}
}
diff --git a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
index 7ace03d..d9cf353 100644
--- a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
+++ b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
@@ -135,8 +135,10 @@
private static final byte PROTECTOR_TYPE_STRONG_TOKEN_BASED = 1;
private static final byte PROTECTOR_TYPE_WEAK_TOKEN_BASED = 2;
- // 256-bit synthetic password
- private static final byte SYNTHETIC_PASSWORD_LENGTH = 256 / 8;
+ private static final String PROTECTOR_KEY_ALIAS_PREFIX = "synthetic_password_";
+
+ // The security strength of the synthetic password, in bytes
+ private static final int SYNTHETIC_PASSWORD_SECURITY_STRENGTH = 256 / 8;
private static final int PASSWORD_SCRYPT_LOG_N = 11;
private static final int PASSWORD_SCRYPT_LOG_R = 3;
@@ -275,8 +277,8 @@
*/
static SyntheticPassword create() {
SyntheticPassword result = new SyntheticPassword(SYNTHETIC_PASSWORD_VERSION_V3);
- byte[] escrowSplit0 = secureRandom(SYNTHETIC_PASSWORD_LENGTH);
- byte[] escrowSplit1 = secureRandom(SYNTHETIC_PASSWORD_LENGTH);
+ byte[] escrowSplit0 = secureRandom(SYNTHETIC_PASSWORD_SECURITY_STRENGTH);
+ byte[] escrowSplit1 = secureRandom(SYNTHETIC_PASSWORD_SECURITY_STRENGTH);
result.recreate(escrowSplit0, escrowSplit1);
byte[] encrypteEscrowSplit0 = SyntheticPasswordCrypto.encrypt(result.mSyntheticPassword,
PERSONALIZATION_E0, escrowSplit0);
@@ -583,7 +585,7 @@
for (long protectorId : mStorage.listSyntheticPasswordProtectorsForUser(SP_BLOB_NAME,
userId)) {
destroyWeaverSlot(protectorId, userId);
- destroySPBlobKey(getKeyName(protectorId));
+ destroyProtectorKey(getProtectorKeyAlias(protectorId));
}
// Remove potential persistent state (in RPMB), to prevent them from accumulating and
// causing problems.
@@ -999,7 +1001,8 @@
} else {
spSecret = sp.getSyntheticPassword();
}
- byte[] content = createSPBlob(getKeyName(protectorId), spSecret, protectorSecret, sid);
+ byte[] content = createSpBlob(getProtectorKeyAlias(protectorId), spSecret, protectorSecret,
+ sid);
/*
* We can upgrade from v1 to v2 because that's just a change in the way that
* the SP is stored. However, we can't upgrade to v3 because that is a change
@@ -1210,10 +1213,11 @@
}
final byte[] spSecret;
if (blob.mVersion == SYNTHETIC_PASSWORD_VERSION_V1) {
- spSecret = SyntheticPasswordCrypto.decryptBlobV1(getKeyName(protectorId), blob.mContent,
- protectorSecret);
+ spSecret = SyntheticPasswordCrypto.decryptBlobV1(getProtectorKeyAlias(protectorId),
+ blob.mContent, protectorSecret);
} else {
- spSecret = decryptSPBlob(getKeyName(protectorId), blob.mContent, protectorSecret);
+ spSecret = decryptSpBlob(getProtectorKeyAlias(protectorId), blob.mContent,
+ protectorSecret);
}
if (spSecret == null) {
Slog.e(TAG, "Fail to decrypt SP for user " + userId);
@@ -1337,7 +1341,7 @@
private void destroyProtectorCommon(long protectorId, int userId) {
destroyState(SP_BLOB_NAME, protectorId, userId);
- destroySPBlobKey(getKeyName(protectorId));
+ destroyProtectorKey(getProtectorKeyAlias(protectorId));
destroyState(SECDISCARDABLE_NAME, protectorId, userId);
if (hasState(WEAVER_SLOT_NAME, protectorId, userId)) {
destroyWeaverSlot(protectorId, userId);
@@ -1347,19 +1351,13 @@
private byte[] transformUnderWeaverSecret(byte[] data, byte[] secret) {
byte[] weaverSecret = SyntheticPasswordCrypto.personalizedHash(
PERSONALIZATION_WEAVER_PASSWORD, secret);
- byte[] result = new byte[data.length + weaverSecret.length];
- System.arraycopy(data, 0, result, 0, data.length);
- System.arraycopy(weaverSecret, 0, result, data.length, weaverSecret.length);
- return result;
+ return ArrayUtils.concat(data, weaverSecret);
}
private byte[] transformUnderSecdiscardable(byte[] data, byte[] rawSecdiscardable) {
byte[] secdiscardable = SyntheticPasswordCrypto.personalizedHash(
PERSONALIZATION_SECDISCARDABLE, rawSecdiscardable);
- byte[] result = new byte[data.length + secdiscardable.length];
- System.arraycopy(data, 0, result, 0, data.length);
- System.arraycopy(secdiscardable, 0, result, data.length, secdiscardable.length);
- return result;
+ return ArrayUtils.concat(data, secdiscardable);
}
private byte[] createSecdiscardable(long protectorId, int userId) {
@@ -1428,17 +1426,20 @@
mStorage.deleteSyntheticPasswordState(userId, protectorId, stateName);
}
- protected byte[] decryptSPBlob(String blobKeyName, byte[] blob, byte[] protectorSecret) {
- return SyntheticPasswordCrypto.decryptBlob(blobKeyName, blob, protectorSecret);
+ @VisibleForTesting
+ protected byte[] decryptSpBlob(String protectorKeyAlias, byte[] blob, byte[] protectorSecret) {
+ return SyntheticPasswordCrypto.decryptBlob(protectorKeyAlias, blob, protectorSecret);
}
- protected byte[] createSPBlob(String blobKeyName, byte[] data, byte[] protectorSecret,
+ @VisibleForTesting
+ protected byte[] createSpBlob(String protectorKeyAlias, byte[] data, byte[] protectorSecret,
long sid) {
- return SyntheticPasswordCrypto.createBlob(blobKeyName, data, protectorSecret, sid);
+ return SyntheticPasswordCrypto.createBlob(protectorKeyAlias, data, protectorSecret, sid);
}
- protected void destroySPBlobKey(String keyAlias) {
- SyntheticPasswordCrypto.destroyBlobKey(keyAlias);
+ @VisibleForTesting
+ protected void destroyProtectorKey(String keyAlias) {
+ SyntheticPasswordCrypto.destroyProtectorKey(keyAlias);
}
public static long generateProtectorId() {
@@ -1463,8 +1464,8 @@
}
}
- private String getKeyName(long protectorId) {
- return String.format("%s%x", LockPatternUtils.SYNTHETIC_PASSWORD_KEY_PREFIX, protectorId);
+ private String getProtectorKeyAlias(long protectorId) {
+ return String.format("%s%x", PROTECTOR_KEY_ALIAS_PREFIX, protectorId);
}
private byte[] computePasswordToken(LockscreenCredential credential, PasswordData data) {
@@ -1517,7 +1518,7 @@
}
/**
- * Migrate all existing SP keystore keys from uid 1000 app domain to LSS selinux domain
+ * Migrates all existing SP protector keys from uid 1000 app domain to LSS selinux domain.
*/
public boolean migrateKeyNamespace() {
boolean success = true;
@@ -1525,7 +1526,8 @@
mStorage.listSyntheticPasswordProtectorsForAllUsers(SP_BLOB_NAME);
for (List<Long> userProtectors : allProtectors.values()) {
for (long protectorId : userProtectors) {
- success &= SyntheticPasswordCrypto.migrateLockSettingsKey(getKeyName(protectorId));
+ success &= SyntheticPasswordCrypto.migrateLockSettingsKey(
+ getProtectorKeyAlias(protectorId));
}
}
return success;
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncUtils.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncUtils.java
index 24d575e..7921619 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncUtils.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncUtils.java
@@ -20,6 +20,7 @@
import android.util.Pair;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
@@ -88,7 +89,7 @@
) throws NoSuchAlgorithmException, InvalidKeyException {
byte[] encryptedRecoveryKey = locallyEncryptRecoveryKey(lockScreenHash, recoveryKey);
byte[] thmKfHash = calculateThmKfHash(lockScreenHash);
- byte[] header = concat(THM_ENCRYPTED_RECOVERY_KEY_HEADER, vaultParams);
+ byte[] header = ArrayUtils.concat(THM_ENCRYPTED_RECOVERY_KEY_HEADER, vaultParams);
return SecureBox.encrypt(
/*theirPublicKey=*/ publicKey,
/*sharedSecret=*/ thmKfHash,
@@ -171,7 +172,7 @@
// Note that Android P devices do not have the API to provide the optional metadata,
// so all the keys with non-empty metadata stored on Android Q+ devices cannot be
// recovered on Android P devices.
- header = concat(ENCRYPTED_APPLICATION_KEY_HEADER, metadata);
+ header = ArrayUtils.concat(ENCRYPTED_APPLICATION_KEY_HEADER, metadata);
}
byte[] encryptedKey = SecureBox.encrypt(
/*theirPublicKey=*/ null,
@@ -218,8 +219,8 @@
return SecureBox.encrypt(
publicKey,
/*sharedSecret=*/ null,
- /*header=*/ concat(RECOVERY_CLAIM_HEADER, vaultParams, challenge),
- /*payload=*/ concat(thmKfHash, keyClaimant));
+ /*header=*/ ArrayUtils.concat(RECOVERY_CLAIM_HEADER, vaultParams, challenge),
+ /*payload=*/ ArrayUtils.concat(thmKfHash, keyClaimant));
}
/**
@@ -240,7 +241,7 @@
return SecureBox.decrypt(
/*ourPrivateKey=*/ null,
/*sharedSecret=*/ keyClaimant,
- /*header=*/ concat(RECOVERY_RESPONSE_HEADER, vaultParams),
+ /*header=*/ ArrayUtils.concat(RECOVERY_RESPONSE_HEADER, vaultParams),
/*encryptedPayload=*/ encryptedResponse);
}
@@ -280,7 +281,7 @@
if (applicationKeyMetadata == null) {
header = ENCRYPTED_APPLICATION_KEY_HEADER;
} else {
- header = concat(ENCRYPTED_APPLICATION_KEY_HEADER, applicationKeyMetadata);
+ header = ArrayUtils.concat(ENCRYPTED_APPLICATION_KEY_HEADER, applicationKeyMetadata);
}
return SecureBox.decrypt(
/*ourPrivateKey=*/ null,
@@ -333,26 +334,6 @@
.array();
}
- /**
- * Returns the concatenation of all the given {@code arrays}.
- */
- @VisibleForTesting
- static byte[] concat(byte[]... arrays) {
- int length = 0;
- for (byte[] array : arrays) {
- length += array.length;
- }
-
- byte[] concatenated = new byte[length];
- int pos = 0;
- for (byte[] array : arrays) {
- System.arraycopy(array, /*srcPos=*/ 0, concatenated, pos, array.length);
- pos += array.length;
- }
-
- return concatenated;
- }
-
// Statics only
private KeySyncUtils() {}
}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/SecureBox.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/SecureBox.java
index 807ee03..51a37b3 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/SecureBox.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/SecureBox.java
@@ -18,6 +18,7 @@
import android.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
import java.math.BigInteger;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
@@ -69,7 +70,7 @@
private static final byte[] VERSION = new byte[] {(byte) 0x02, 0}; // LITTLE_ENDIAN_TWO_BYTES(2)
private static final byte[] HKDF_SALT =
- concat("SECUREBOX".getBytes(StandardCharsets.UTF_8), VERSION);
+ ArrayUtils.concat("SECUREBOX".getBytes(StandardCharsets.UTF_8), VERSION);
private static final byte[] HKDF_INFO_WITH_PUBLIC_KEY =
"P256 HKDF-SHA-256 AES-128-GCM".getBytes(StandardCharsets.UTF_8);
private static final byte[] HKDF_INFO_WITHOUT_PUBLIC_KEY =
@@ -199,13 +200,13 @@
}
byte[] randNonce = genRandomNonce();
- byte[] keyingMaterial = concat(dhSecret, sharedSecret);
+ byte[] keyingMaterial = ArrayUtils.concat(dhSecret, sharedSecret);
SecretKey encryptionKey = hkdfDeriveKey(keyingMaterial, HKDF_SALT, hkdfInfo);
byte[] ciphertext = aesGcmEncrypt(encryptionKey, randNonce, payload, header);
if (senderKeyPair == null) {
- return concat(VERSION, randNonce, ciphertext);
+ return ArrayUtils.concat(VERSION, randNonce, ciphertext);
} else {
- return concat(
+ return ArrayUtils.concat(
VERSION, encodePublicKey(senderKeyPair.getPublic()), randNonce, ciphertext);
}
}
@@ -268,7 +269,7 @@
byte[] randNonce = readEncryptedPayload(ciphertextBuffer, GCM_NONCE_LEN_BYTES);
byte[] ciphertext = readEncryptedPayload(ciphertextBuffer, ciphertextBuffer.remaining());
- byte[] keyingMaterial = concat(dhSecret, sharedSecret);
+ byte[] keyingMaterial = ArrayUtils.concat(dhSecret, sharedSecret);
SecretKey decryptionKey = hkdfDeriveKey(keyingMaterial, HKDF_SALT, hkdfInfo);
return aesGcmDecrypt(decryptionKey, randNonce, ciphertext, header);
}
@@ -446,25 +447,6 @@
return nonce;
}
- @VisibleForTesting
- static byte[] concat(byte[]... inputs) {
- int length = 0;
- for (int i = 0; i < inputs.length; i++) {
- if (inputs[i] == null) {
- inputs[i] = EMPTY_BYTE_ARRAY;
- }
- length += inputs[i].length;
- }
-
- byte[] output = new byte[length];
- int outputPos = 0;
- for (byte[] input : inputs) {
- System.arraycopy(input, /*srcPos=*/ 0, output, outputPos, input.length);
- outputPos += input.length;
- }
- return output;
- }
-
private static byte[] emptyByteArrayIfNull(@Nullable byte[] input) {
return input == null ? EMPTY_BYTE_ARRAY : input;
}
diff --git a/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java b/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java
index 9a19031..a5c762a 100644
--- a/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java
+++ b/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java
@@ -32,6 +32,7 @@
import android.os.PowerWhitelistManager;
import android.os.UserHandle;
import android.text.TextUtils;
+import android.util.EventLog;
import android.util.Log;
import android.view.KeyEvent;
@@ -117,6 +118,12 @@
int componentType = getComponentType(pendingIntent);
ComponentName componentName = getComponentName(pendingIntent, componentType);
if (componentName != null) {
+ if (!TextUtils.equals(componentName.getPackageName(), sessionPackageName)) {
+ EventLog.writeEvent(0x534e4554, "238177121", -1, "");
+ throw new IllegalArgumentException("ComponentName does not belong to "
+ + "sessionPackageName. sessionPackageName = " + sessionPackageName
+ + ", ComponentName pkg = " + componentName.getPackageName());
+ }
return new MediaButtonReceiverHolder(userId, pendingIntent, componentName,
componentType);
}
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index 34cd6a0..1ee9a87 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -50,6 +50,8 @@
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.SystemClock;
+import android.text.TextUtils;
+import android.util.EventLog;
import android.util.Log;
import android.view.KeyEvent;
@@ -914,6 +916,14 @@
@Override
public void setMediaButtonReceiver(PendingIntent pi, String sessionPackageName)
throws RemoteException {
+ //mPackageName has been verified in MediaSessionService.enforcePackageName().
+ if (!TextUtils.equals(sessionPackageName, mPackageName)) {
+ EventLog.writeEvent(0x534e4554, "238177121", -1, "");
+ throw new IllegalArgumentException("sessionPackageName name does not match "
+ + "package name provided to MediaSessionRecord. sessionPackageName = "
+ + sessionPackageName + ", pkg = "
+ + mPackageName);
+ }
final long token = Binder.clearCallingIdentity();
try {
if ((mPolicies & MediaSessionPolicyProvider.SESSION_POLICY_IGNORE_BUTTON_RECEIVER)
@@ -932,6 +942,15 @@
public void setMediaButtonBroadcastReceiver(ComponentName receiver) throws RemoteException {
final long token = Binder.clearCallingIdentity();
try {
+ //mPackageName has been verified in MediaSessionService.enforcePackageName().
+ if (receiver != null && !TextUtils.equals(
+ mPackageName, receiver.getPackageName())) {
+ EventLog.writeEvent(0x534e4554, "238177121", -1, "");
+ throw new IllegalArgumentException("receiver does not belong to "
+ + "package name provided to MediaSessionRecord. Pkg = " + mPackageName
+ + ", Receiver Pkg = " + receiver.getPackageName());
+ }
+
if ((mPolicies & MediaSessionPolicyProvider.SESSION_POLICY_IGNORE_BUTTON_RECEIVER)
!= 0) {
return;
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
index 7d4cfdf..164445c 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
@@ -1698,7 +1698,8 @@
mDefaultPermissionCallback.onInstallPermissionGranted();
}
- public void onPermissionRevoked(int uid, int userId, String reason) {
+ public void onPermissionRevoked(int uid, int userId, String reason,
+ boolean overrideKill, @Nullable String permissionName) {
revokedPermissions.add(IntPair.of(uid, userId));
syncUpdatedUsers.add(userId);
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 2b3a685..111d8a0 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -2611,7 +2611,7 @@
// either way, abort and reset the sequence.
if (parcelable == null
|| mTransferringSplashScreenState != TRANSFER_SPLASH_SCREEN_COPYING
- || mStartingWindow == null
+ || mStartingWindow == null || mStartingWindow.mRemoved
|| finishing) {
if (parcelable != null) {
parcelable.clearIfNeeded();
@@ -9744,6 +9744,7 @@
record.mThumbnailAdapter != null ? record.mThumbnailAdapter.mCapturedLeash : null,
record.mStartBounds, task.getTaskInfo(), checkEnterPictureInPictureAppOpsState());
target.setShowBackdrop(record.mShowBackdrop);
+ target.setWillShowImeOnTarget(mStartingData != null && mStartingData.hasImeSurface());
target.hasAnimatingParent = record.hasAnimatingParent();
return target;
}
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 8e4de31..e062c38 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1659,9 +1659,6 @@
&& transitionController.getTransitionPlayer() != null)
? transitionController.createTransition(TRANSIT_OPEN) : null;
RemoteTransition remoteTransition = r.takeRemoteTransition();
- if (newTransition != null && remoteTransition != null) {
- newTransition.setRemoteTransition(remoteTransition);
- }
transitionController.collect(r);
try {
mService.deferWindowLayout();
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 6fd28ca..471424b 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -5324,7 +5324,13 @@
}
@Override
- public void setRunningRemoteTransitionDelegate(IApplicationThread caller) {
+ public void setRunningRemoteTransitionDelegate(IApplicationThread delegate) {
+ final TransitionController controller = getTransitionController();
+ // A quick path without entering WM lock.
+ if (delegate != null && controller.mRemotePlayer.reportRunning(delegate)) {
+ // The delegate was known as running remote transition.
+ return;
+ }
mAmInternal.enforceCallingPermission(CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS,
"setRunningRemoteTransition");
final int callingPid = Binder.getCallingPid();
@@ -5341,13 +5347,12 @@
Slog.e(TAG, msg);
throw new SecurityException(msg);
}
- final WindowProcessController wpc = getProcessController(caller);
+ final WindowProcessController wpc = getProcessController(delegate);
if (wpc == null) {
- Slog.w(TAG, "Unable to find process for application " + caller);
+ Slog.w(TAG, "setRunningRemoteTransition: no process for " + delegate);
return;
}
- wpc.setRunningRemoteAnimation(true /* running */);
- callingProc.addRemoteAnimationDelegate(wpc);
+ controller.mRemotePlayer.update(wpc, true /* running */, false /* predict */);
}
}
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index f967cf9..d9ab971 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -131,7 +131,6 @@
"Focused window found using getFocusedWindowToken");
}
- OnBackInvokedCallbackInfo overrideCallbackInfo = null;
if (window != null) {
// This is needed to bridge the old and new back behavior with recents. While in
// Overview with live tile enabled, the previous app is technically focused but we
@@ -140,15 +139,18 @@
// the right window to consume back while in overview, so we need to route it to
// launcher and use the legacy behavior of injecting KEYCODE_BACK since the existing
// compat callback in VRI only works when the window is focused.
+ // This symptom also happen while shell transition enabled, we can check that by
+ // isTransientLaunch to know whether the focus window is point to live tile.
final RecentsAnimationController recentsAnimationController =
wmService.getRecentsAnimationController();
- if (recentsAnimationController != null
- && recentsAnimationController.shouldApplyInputConsumer(
- window.getActivityRecord())) {
- window = recentsAnimationController.getTargetAppMainWindow();
- overrideCallbackInfo = recentsAnimationController.getBackInvokedInfo();
+ final ActivityRecord ar = window.mActivityRecord;
+ if ((ar != null && ar.isActivityTypeHomeOrRecents()
+ && ar.mTransitionController.isTransientLaunch(ar))
+ || (recentsAnimationController != null
+ && recentsAnimationController.shouldApplyInputConsumer(ar))) {
ProtoLog.d(WM_DEBUG_BACK_PREVIEW, "Current focused window being animated by "
+ "recents. Overriding back callback to recents controller callback.");
+ return null;
}
}
@@ -166,9 +168,7 @@
if (window != null) {
currentActivity = window.mActivityRecord;
currentTask = window.getTask();
- callbackInfo = overrideCallbackInfo != null
- ? overrideCallbackInfo
- : window.getOnBackInvokedCallbackInfo();
+ callbackInfo = window.getOnBackInvokedCallbackInfo();
if (callbackInfo == null) {
Slog.e(TAG, "No callback registered, returning null.");
return null;
@@ -213,8 +213,10 @@
Task finalTask = currentTask;
prevActivity = currentTask.getActivity(
(r) -> !r.finishing && r.getTask() == finalTask && !r.isTopRunningActivity());
- if (window.getParent().getChildCount() > 1 && window.getParent().getChildAt(0)
- != window) {
+ // TODO Dialog window does not need to attach on activity, check
+ // window.mAttrs.type != TYPE_BASE_APPLICATION
+ if ((window.getParent().getChildCount() > 1
+ && window.getParent().getChildAt(0) != window)) {
// Are we the top window of our parent? If not, we are a window on top of the
// activity, we won't close the activity.
backType = BackNavigationInfo.TYPE_DIALOG_CLOSE;
@@ -379,7 +381,8 @@
private void onBackNavigationDone(
Bundle result, WindowState focusedWindow, WindowContainer<?> windowContainer,
- int backType, Task task, ActivityRecord prevActivity, boolean prepareAnimation) {
+ int backType, @Nullable Task task, @Nullable ActivityRecord prevActivity,
+ boolean prepareAnimation) {
SurfaceControl surfaceControl = windowContainer.getSurfaceControl();
boolean triggerBack = result != null && result.getBoolean(
BackNavigationInfo.KEY_TRIGGER_BACK);
@@ -404,7 +407,7 @@
"Setting Activity.mLauncherTaskBehind to false. Activity=%s",
prevActivity);
}
- } else {
+ } else if (task != null) {
task.mBackGestureStarted = false;
}
resetSurfaces(windowContainer);
diff --git a/services/core/java/com/android/server/wm/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java
index 863782a..0422906 100644
--- a/services/core/java/com/android/server/wm/DisplayArea.java
+++ b/services/core/java/com/android/server/wm/DisplayArea.java
@@ -207,6 +207,23 @@
return false;
}
+ @Override
+ public void setAlwaysOnTop(boolean alwaysOnTop) {
+ if (isAlwaysOnTop() == alwaysOnTop) {
+ return;
+ }
+ super.setAlwaysOnTop(alwaysOnTop);
+ // positionChildAtTop() must be called even when always on top gets turned off because
+ // we need to make sure that the display area is moved from among always on top containers
+ // to below other always on top containers. Since the position the display area should be
+ // inserted into is calculated properly in {@link DisplayContent#getTopInsertPosition()}
+ // in both cases, we can just request that the root task is put at top here.
+ if (getParent().asDisplayArea() != null) {
+ getParent().asDisplayArea().positionChildAt(POSITION_TOP, this,
+ false /* includingParents */);
+ }
+ }
+
boolean getIgnoreOrientationRequest() {
// Adding an exception for when ignoreOrientationRequest is overridden at runtime for all
// DisplayArea-s. For example, this is needed for the Kids Mode since many Kids apps aren't
@@ -234,6 +251,18 @@
// The min possible position we can insert the child at.
int minPosition = findMinPositionForChildDisplayArea(child);
+ // Place all non-always-on-top containers below always-on-top ones.
+ int alwaysOnTopCount = 0;
+ for (int i = minPosition; i <= maxPosition; i++) {
+ if (mChildren.get(i).isAlwaysOnTop()) {
+ alwaysOnTopCount++;
+ }
+ }
+ if (child.isAlwaysOnTop()) {
+ minPosition = maxPosition - alwaysOnTopCount + 1;
+ } else {
+ maxPosition -= alwaysOnTopCount;
+ }
return Math.max(Math.min(requestPosition, maxPosition), minPosition);
}
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 14e2241..7aa0541 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -318,12 +318,19 @@
*/
private final ArrayList<WindowState> mStatusBarBackgroundWindows = new ArrayList<>();
+ /**
+ * A collection of {@link LetterboxDetails} of all visible activities to be sent to SysUI in
+ * order to determine status bar appearance
+ */
+ private final ArrayList<LetterboxDetails> mLetterboxDetails = new ArrayList<>();
+
private String mFocusedApp;
private int mLastDisableFlags;
private int mLastAppearance;
private int mLastBehavior;
private InsetsVisibilities mRequestedVisibilities = new InsetsVisibilities();
private AppearanceRegion[] mLastStatusBarAppearanceRegions;
+ private LetterboxDetails[] mLastLetterboxDetails;
/** The union of checked bounds while building {@link #mStatusBarAppearanceRegionList}. */
private final Rect mStatusBarColorCheckedBounds = new Rect();
@@ -1638,6 +1645,7 @@
mNavBarColorWindowCandidate = null;
mNavBarBackgroundWindow = null;
mStatusBarAppearanceRegionList.clear();
+ mLetterboxDetails.clear();
mStatusBarBackgroundWindows.clear();
mStatusBarColorCheckedBounds.setEmpty();
mStatusBarBackgroundCheckedBounds.setEmpty();
@@ -1717,6 +1725,16 @@
win.mAttrs.insetsFlags.appearance & APPEARANCE_LIGHT_STATUS_BARS,
new Rect(win.getFrame())));
mStatusBarColorCheckedBounds.union(sTmpRect);
+ // Check if current activity is letterboxed in order create a LetterboxDetails
+ // component to be passed to SysUI for status bar treatment
+ final ActivityRecord currentActivity = win.getActivityRecord();
+ if (currentActivity != null) {
+ final LetterboxDetails currentLetterboxDetails = currentActivity
+ .mLetterboxUiController.getLetterboxDetails();
+ if (currentLetterboxDetails != null) {
+ mLetterboxDetails.add(currentLetterboxDetails);
+ }
+ }
}
}
@@ -2404,12 +2422,15 @@
callStatusBarSafely(statusBar -> statusBar.setDisableFlags(displayId, disableFlags,
cause));
}
+ final LetterboxDetails[] letterboxDetails = new LetterboxDetails[mLetterboxDetails.size()];
+ mLetterboxDetails.toArray(letterboxDetails);
if (mLastAppearance == appearance
&& mLastBehavior == behavior
&& mRequestedVisibilities.equals(win.getRequestedVisibilities())
&& Objects.equals(mFocusedApp, focusedApp)
&& mLastFocusIsFullscreen == isFullscreen
- && Arrays.equals(mLastStatusBarAppearanceRegions, statusBarAppearanceRegions)) {
+ && Arrays.equals(mLastStatusBarAppearanceRegions, statusBarAppearanceRegions)
+ && Arrays.equals(mLastLetterboxDetails, letterboxDetails)) {
return;
}
if (mDisplayContent.isDefaultDisplay && mLastFocusIsFullscreen != isFullscreen
@@ -2425,9 +2446,10 @@
mFocusedApp = focusedApp;
mLastFocusIsFullscreen = isFullscreen;
mLastStatusBarAppearanceRegions = statusBarAppearanceRegions;
+ mLastLetterboxDetails = letterboxDetails;
callStatusBarSafely(statusBar -> statusBar.onSystemBarAttributesChanged(displayId,
appearance, statusBarAppearanceRegions, isNavbarColorManagedByIme, behavior,
- requestedVisibilities, focusedApp, new LetterboxDetails[]{}));
+ requestedVisibilities, focusedApp, letterboxDetails));
}
private void callStatusBarSafely(Consumer<StatusBarManagerInternal> consumer) {
@@ -2748,8 +2770,6 @@
public void takeScreenshot(int screenshotType, int source) {
if (mScreenshotHelper != null) {
mScreenshotHelper.takeScreenshot(screenshotType,
- getStatusBar() != null && getStatusBar().isVisible(),
- getNavigationBar() != null && getNavigationBar().isVisible(),
source, mHandler, null /* completionConsumer */);
}
}
@@ -2843,6 +2863,12 @@
pw.print(prefixInner); pw.println(mLastStatusBarAppearanceRegions[i]);
}
}
+ if (mLastLetterboxDetails != null) {
+ pw.print(prefix); pw.println("mLastLetterboxDetails=");
+ for (int i = mLastLetterboxDetails.length - 1; i >= 0; i--) {
+ pw.print(prefixInner); pw.println(mLastLetterboxDetails[i]);
+ }
+ }
if (!mStatusBarBackgroundWindows.isEmpty()) {
pw.print(prefix); pw.println("mStatusBarBackgroundWindows=");
for (int i = mStatusBarBackgroundWindows.size() - 1; i >= 0; i--) {
diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java
index d76f6be..1567fa7 100644
--- a/services/core/java/com/android/server/wm/KeyguardController.java
+++ b/services/core/java/com/android/server/wm/KeyguardController.java
@@ -209,8 +209,6 @@
if (keyguardChanged) {
// Irrelevant to AOD.
- dismissMultiWindowModeForTaskIfNeeded(displayId,
- null /* currentTaskControllsingOcclusion */);
state.mKeyguardGoingAway = false;
if (keyguardShowing) {
state.mDismissalRequested = false;
diff --git a/services/core/java/com/android/server/wm/Letterbox.java b/services/core/java/com/android/server/wm/Letterbox.java
index b5eff41..df3109a 100644
--- a/services/core/java/com/android/server/wm/Letterbox.java
+++ b/services/core/java/com/android/server/wm/Letterbox.java
@@ -138,6 +138,11 @@
return mInner;
}
+ /** @return The frame that contains the inner frame and the insets. */
+ Rect getOuterFrame() {
+ return mOuter;
+ }
+
/**
* Returns {@code true} if the letterbox does not overlap with the bar, or the letterbox can
* fully cover the window frame.
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index d652767..ec9ee29 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -68,6 +68,7 @@
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.statusbar.LetterboxDetails;
import com.android.server.wm.LetterboxConfiguration.LetterboxBackgroundType;
import java.io.PrintWriter;
@@ -141,6 +142,15 @@
}
}
+ /** Gets the outer bounds of letterbox. The bounds will be empty if there is no letterbox. */
+ private void getLetterboxOuterBounds(Rect outBounds) {
+ if (mLetterbox != null) {
+ outBounds.set(mLetterbox.getOuterFrame());
+ } else {
+ outBounds.setEmpty();
+ }
+ }
+
/**
* @return {@code true} if bar shown within a given rectangle is allowed to be fully transparent
* when the current activity is displayed.
@@ -683,4 +693,26 @@
mActivityRecord.mTaskSupervisor.getActivityMetricsLogger()
.logLetterboxPositionChange(mActivityRecord, letterboxPositionChange);
}
+
+ @Nullable
+ LetterboxDetails getLetterboxDetails() {
+ final WindowState w = mActivityRecord.findMainWindow();
+ if (mLetterbox == null || w == null || w.isLetterboxedForDisplayCutout()) {
+ return null;
+ }
+ Rect letterboxInnerBounds = new Rect();
+ Rect letterboxOuterBounds = new Rect();
+ getLetterboxInnerBounds(letterboxInnerBounds);
+ getLetterboxOuterBounds(letterboxOuterBounds);
+
+ if (letterboxInnerBounds.isEmpty() || letterboxOuterBounds.isEmpty()) {
+ return null;
+ }
+
+ return new LetterboxDetails(
+ letterboxInnerBounds,
+ letterboxOuterBounds,
+ w.mAttrs.insetsFlags.appearance
+ );
+ }
}
diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
index 53f1fe6..5b702ea 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java
@@ -19,12 +19,10 @@
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-import static android.hardware.input.InputManager.INJECT_INPUT_EVENT_MODE_ASYNC;
import static android.view.RemoteAnimationTarget.MODE_CLOSING;
import static android.view.RemoteAnimationTarget.MODE_OPENING;
import static android.view.WindowManager.INPUT_CONSUMER_RECENTS_ANIMATION;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
-import static android.window.OnBackInvokedDispatcher.PRIORITY_DEFAULT;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_RECENTS_ANIMATIONS;
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
@@ -41,7 +39,6 @@
import android.graphics.Point;
import android.graphics.Rect;
import android.hardware.HardwareBuffer;
-import android.hardware.input.InputManager;
import android.os.Binder;
import android.os.IBinder.DeathRecipient;
import android.os.RemoteException;
@@ -54,18 +51,12 @@
import android.util.proto.ProtoOutputStream;
import android.view.IRecentsAnimationController;
import android.view.IRecentsAnimationRunner;
-import android.view.InputDevice;
import android.view.InputWindowHandle;
-import android.view.KeyCharacterMap;
-import android.view.KeyEvent;
import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
import android.view.SurfaceControl.Transaction;
import android.view.SurfaceSession;
import android.view.WindowInsets.Type;
-import android.window.BackEvent;
-import android.window.IOnBackInvokedCallback;
-import android.window.OnBackInvokedCallbackInfo;
import android.window.PictureInPictureSurfaceTransaction;
import android.window.TaskSnapshot;
@@ -195,46 +186,6 @@
}
};
- /**
- * Back invoked callback for legacy recents transition with the new back dispatch system.
- */
- final IOnBackInvokedCallback mBackCallback = new IOnBackInvokedCallback.Stub() {
- @Override
- public void onBackStarted() {
- // Do nothing
- }
-
- @Override
- public void onBackProgressed(BackEvent backEvent) {
- // Do nothing
- }
-
- @Override
- public void onBackCancelled() {
- // Do nothing
- }
-
- @Override
- public void onBackInvoked() {
- sendBackEvent(KeyEvent.ACTION_DOWN);
- sendBackEvent(KeyEvent.ACTION_UP);
- }
-
- private void sendBackEvent(int action) {
- if (mTargetActivityRecord == null) {
- return;
- }
- long when = SystemClock.uptimeMillis();
- final KeyEvent ev = new KeyEvent(when, when, action,
- KeyEvent.KEYCODE_BACK, 0 /* repeat */, 0 /* metaState */,
- KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /* scancode */,
- KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY,
- InputDevice.SOURCE_KEYBOARD);
- ev.setDisplayId(mTargetActivityRecord.getDisplayId());
- InputManager.getInstance().injectInputEvent(ev, INJECT_INPUT_EVENT_MODE_ASYNC);
- }
- };
-
public interface RecentsAnimationCallbacks {
/** Callback when recents animation is finished. */
void onAnimationFinished(@ReorderMode int reorderMode, boolean sendUserLeaveHint);
@@ -1112,10 +1063,6 @@
return mTargetActivityRecord.findMainWindow();
}
- OnBackInvokedCallbackInfo getBackInvokedInfo() {
- return new OnBackInvokedCallbackInfo(mBackCallback, PRIORITY_DEFAULT);
- }
-
DisplayArea getTargetAppDisplayArea() {
if (mTargetActivityRecord == null) {
return null;
@@ -1236,6 +1183,12 @@
mLocalBounds, mBounds, mTask.getWindowConfiguration(),
mIsRecentTaskInvisible, null, null, mTask.getTaskInfo(),
topApp.checkEnterPictureInPictureAppOpsState());
+
+ final ActivityRecord topActivity = mTask.getTopNonFinishingActivity();
+ if (topActivity != null && topActivity.mStartingData != null
+ && topActivity.mStartingData.hasImeSurface()) {
+ mTarget.setWillShowImeOnTarget(true);
+ }
return mTarget;
}
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 4df0a49..589618e 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -401,6 +401,10 @@
mTaskFragmentOrganizerProcessName = processName;
}
+ void onTaskFragmentOrganizerRemoved() {
+ mTaskFragmentOrganizer = null;
+ }
+
/** Whether this TaskFragment is organized by the given {@code organizer}. */
boolean hasTaskFragmentOrganizer(ITaskFragmentOrganizer organizer) {
return organizer != null && mTaskFragmentOrganizer != null
diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
index d4551be..602579f 100644
--- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
@@ -136,6 +136,9 @@
void dispose() {
while (!mOrganizedTaskFragments.isEmpty()) {
final TaskFragment taskFragment = mOrganizedTaskFragments.get(0);
+ // Cleanup before remove to prevent it from sending any additional event, such as
+ // #onTaskFragmentVanished, to the removed organizer.
+ taskFragment.onTaskFragmentOrganizerRemoved();
taskFragment.removeImmediately();
mOrganizedTaskFragments.remove(taskFragment);
}
@@ -512,10 +515,21 @@
mPendingTaskFragmentEvents.add(pendingEvent);
}
+ boolean isOrganizerRegistered(ITaskFragmentOrganizer organizer) {
+ return mTaskFragmentOrganizerState.containsKey(organizer.asBinder());
+ }
+
private void removeOrganizer(ITaskFragmentOrganizer organizer) {
final TaskFragmentOrganizerState state = validateAndGetState(organizer);
// remove all of the children of the organized TaskFragment
state.dispose();
+ // Remove any pending event of this organizer.
+ for (int i = mPendingTaskFragmentEvents.size() - 1; i >= 0; i--) {
+ final PendingTaskFragmentEvent event = mPendingTaskFragmentEvents.get(i);
+ if (event.mTaskFragmentOrg.asBinder().equals(organizer.asBinder())) {
+ mPendingTaskFragmentEvents.remove(i);
+ }
+ }
mTaskFragmentOrganizerState.remove(organizer.asBinder());
}
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index 8b71a34..0a2e877 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -632,6 +632,11 @@
final Rect mainFrame = window.getRelativeFrame();
final StartingWindowAnimationAdaptor adaptor = new StartingWindowAnimationAdaptor();
window.startAnimation(t, adaptor, false, ANIMATION_TYPE_STARTING_REVEAL);
+ if (adaptor.mAnimationLeash == null) {
+ Slog.e(TAG, "Cannot start starting window animation, the window " + window
+ + " was removed");
+ return null;
+ }
t.setPosition(adaptor.mAnimationLeash, mainFrame.left, mainFrame.top);
return adaptor.mAnimationLeash;
}
@@ -679,13 +684,15 @@
if (topActivity != null) {
removalInfo.deferRemoveForIme = topActivity.mDisplayContent
.mayImeShowOnLaunchingActivity(topActivity);
- if (removalInfo.playRevealAnimation && playShiftUpAnimation) {
- final WindowState mainWindow =
- topActivity.findMainWindow(false/* includeStartingApp */);
- if (mainWindow != null) {
- removalInfo.windowAnimationLeash = applyStartingWindowAnimation(mainWindow);
- removalInfo.mainFrame = mainWindow.getRelativeFrame();
- }
+ final WindowState mainWindow =
+ topActivity.findMainWindow(false/* includeStartingApp */);
+ // No app window for this activity, app might be crashed.
+ // Remove starting window immediately without playing reveal animation.
+ if (mainWindow == null || mainWindow.mRemoved) {
+ removalInfo.playRevealAnimation = false;
+ } else if (removalInfo.playRevealAnimation && playShiftUpAnimation) {
+ removalInfo.windowAnimationLeash = applyStartingWindowAnimation(mainWindow);
+ removalInfo.mainFrame = mainWindow.getRelativeFrame();
}
}
try {
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 31d8eb8..bbc95a1 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -54,6 +54,7 @@
import static android.window.TransitionInfo.FLAG_SHOW_WALLPAPER;
import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
+import static android.window.TransitionInfo.FLAG_WILL_IME_SHOWN;
import static com.android.server.wm.ActivityRecord.State.RESUMED;
import static com.android.server.wm.ActivityTaskManagerInternal.APP_TRANSITION_RECENTS_ANIM;
@@ -1728,6 +1729,13 @@
if (task != null && task.voiceSession != null) {
flags |= FLAG_IS_VOICE_INTERACTION;
}
+ if (task != null) {
+ final ActivityRecord topActivity = task.getTopNonFinishingActivity();
+ if (topActivity != null && topActivity.mStartingData != null
+ && topActivity.mStartingData.hasImeSurface()) {
+ flags |= FLAG_WILL_IME_SHOWN;
+ }
+ }
final ActivityRecord record = wc.asActivityRecord();
if (record != null) {
if (record.mUseTransferredAnimation) {
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index a02be25..4f324f2 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -45,6 +45,7 @@
import android.window.TransitionInfo;
import android.window.TransitionRequestInfo;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.protolog.ProtoLogGroup;
import com.android.internal.protolog.common.ProtoLog;
import com.android.server.LocalServices;
@@ -77,9 +78,10 @@
final TransitionMetricsReporter mTransitionMetricsReporter = new TransitionMetricsReporter();
final TransitionTracer mTransitionTracer;
- private IApplicationThread mTransitionPlayerThread;
+ private WindowProcessController mTransitionPlayerProc;
final ActivityTaskManagerService mAtm;
final TaskSnapshotController mTaskSnapshotController;
+ final RemotePlayer mRemotePlayer;
private final ArrayList<WindowManagerInternal.AppTransitionListener> mLegacyListeners =
new ArrayList<>();
@@ -112,6 +114,7 @@
TaskSnapshotController taskSnapshotController,
TransitionTracer transitionTracer) {
mAtm = atm;
+ mRemotePlayer = new RemotePlayer(atm);
mStatusBar = LocalServices.getService(StatusBarManagerInternal.class);
mTaskSnapshotController = taskSnapshotController;
mTransitionTracer = transitionTracer;
@@ -123,6 +126,8 @@
}
mPlayingTransitions.clear();
mTransitionPlayer = null;
+ mTransitionPlayerProc = null;
+ mRemotePlayer.clear();
mRunningLock.doNotifyLocked();
}
};
@@ -169,7 +174,7 @@
}
void registerTransitionPlayer(@Nullable ITransitionPlayer player,
- @Nullable IApplicationThread appThread) {
+ @Nullable WindowProcessController playerProc) {
try {
// Note: asBinder() can be null if player is same process (likely in a test).
if (mTransitionPlayer != null) {
@@ -182,7 +187,7 @@
player.asBinder().linkToDeath(mTransitionPlayerDeath, 0);
}
mTransitionPlayer = player;
- mTransitionPlayerThread = appThread;
+ mTransitionPlayerProc = playerProc;
} catch (RemoteException e) {
throw new RuntimeException("Unable to set transition player");
}
@@ -421,6 +426,7 @@
}
mTransitionPlayer.requestStartTransition(transition, new TransitionRequestInfo(
transition.mType, info, remoteTransition, displayChange));
+ transition.setRemoteTransition(remoteTransition);
} catch (RemoteException e) {
Slog.e(TAG, "Error requesting transition", e);
transition.start();
@@ -473,9 +479,10 @@
// Collect all visible non-app windows which need to be drawn before the animation starts.
final DisplayContent dc = wc.asDisplayContent();
if (dc != null) {
+ final boolean noAsyncRotation = dc.getAsyncRotationController() == null;
wc.forAllWindows(w -> {
if (w.mActivityRecord == null && w.isVisible() && !isCollecting(w.mToken)
- && dc.shouldSyncRotationChange(w)) {
+ && (noAsyncRotation || !AsyncRotationController.canBeAsync(w.mToken))) {
transition.collect(w.mToken);
}
}, true /* traverseTopToBottom */);
@@ -537,9 +544,7 @@
}
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Finish Transition: %s", record);
mPlayingTransitions.remove(record);
- if (mPlayingTransitions.isEmpty()) {
- setAnimationRunning(false /* running */);
- }
+ updateRunningRemoteAnimation(record, false /* isPlaying */);
record.finishTransition();
mRunningLock.doNotifyLocked();
}
@@ -549,21 +554,27 @@
throw new IllegalStateException("Trying to move non-collecting transition to playing");
}
mCollectingTransition = null;
- if (mPlayingTransitions.isEmpty()) {
- setAnimationRunning(true /* running */);
- }
mPlayingTransitions.add(transition);
+ updateRunningRemoteAnimation(transition, true /* isPlaying */);
mTransitionTracer.logState(transition);
}
- private void setAnimationRunning(boolean running) {
- if (mTransitionPlayerThread == null) return;
- final WindowProcessController wpc = mAtm.getProcessController(mTransitionPlayerThread);
- if (wpc == null) {
- Slog.w(TAG, "Unable to find process for player thread=" + mTransitionPlayerThread);
+ /** Updates the process state of animation player. */
+ private void updateRunningRemoteAnimation(Transition transition, boolean isPlaying) {
+ if (mTransitionPlayerProc == null) return;
+ if (isPlaying) {
+ mTransitionPlayerProc.setRunningRemoteAnimation(true);
+ } else if (mPlayingTransitions.isEmpty()) {
+ mTransitionPlayerProc.setRunningRemoteAnimation(false);
+ mRemotePlayer.clear();
return;
}
- wpc.setRunningRemoteAnimation(running);
+ final RemoteTransition remote = transition.getRemoteTransition();
+ if (remote == null) return;
+ final IApplicationThread appThread = remote.getAppThread();
+ final WindowProcessController delegate = mAtm.getProcessController(appThread);
+ if (delegate == null) return;
+ mRemotePlayer.update(delegate, isPlaying, true /* predict */);
}
void abort(Transition transition) {
@@ -666,6 +677,95 @@
proto.end(token);
}
+ /**
+ * This manages the animating state of processes that are running remote animations for
+ * {@link #mTransitionPlayerProc}.
+ */
+ static class RemotePlayer {
+ private static final long REPORT_RUNNING_GRACE_PERIOD_MS = 100;
+ @GuardedBy("itself")
+ private final ArrayMap<IBinder, DelegateProcess> mDelegateProcesses = new ArrayMap<>();
+ private final ActivityTaskManagerService mAtm;
+
+ private class DelegateProcess implements Runnable {
+ final WindowProcessController mProc;
+ /** Requires {@link RemotePlayer#reportRunning} to confirm it is really running. */
+ boolean mNeedReport;
+
+ DelegateProcess(WindowProcessController proc) {
+ mProc = proc;
+ }
+
+ /** This runs when the remote player doesn't report running in time. */
+ @Override
+ public void run() {
+ synchronized (mAtm.mGlobalLockWithoutBoost) {
+ update(mProc, false /* running */, false /* predict */);
+ }
+ }
+ }
+
+ RemotePlayer(ActivityTaskManagerService atm) {
+ mAtm = atm;
+ }
+
+ void update(@NonNull WindowProcessController delegate, boolean running, boolean predict) {
+ if (!running) {
+ synchronized (mDelegateProcesses) {
+ boolean removed = false;
+ for (int i = mDelegateProcesses.size() - 1; i >= 0; i--) {
+ if (mDelegateProcesses.valueAt(i).mProc == delegate) {
+ mDelegateProcesses.removeAt(i);
+ removed = true;
+ break;
+ }
+ }
+ if (!removed) return;
+ }
+ delegate.setRunningRemoteAnimation(false);
+ return;
+ }
+ if (delegate.isRunningRemoteTransition() || !delegate.hasThread()) return;
+ delegate.setRunningRemoteAnimation(true);
+ final DelegateProcess delegateProc = new DelegateProcess(delegate);
+ // If "predict" is true, that means the remote animation is set from
+ // ActivityOptions#makeRemoteAnimation(). But it is still up to shell side to decide
+ // whether to use the remote animation, so there is a timeout to cancel the prediction
+ // if the remote animation doesn't happen.
+ if (predict) {
+ delegateProc.mNeedReport = true;
+ mAtm.mH.postDelayed(delegateProc, REPORT_RUNNING_GRACE_PERIOD_MS);
+ }
+ synchronized (mDelegateProcesses) {
+ mDelegateProcesses.put(delegate.getThread().asBinder(), delegateProc);
+ }
+ }
+
+ void clear() {
+ synchronized (mDelegateProcesses) {
+ for (int i = mDelegateProcesses.size() - 1; i >= 0; i--) {
+ mDelegateProcesses.valueAt(i).mProc.setRunningRemoteAnimation(false);
+ }
+ mDelegateProcesses.clear();
+ }
+ }
+
+ /** Returns {@code true} if the app is known to be running remote transition. */
+ boolean reportRunning(@NonNull IApplicationThread appThread) {
+ final DelegateProcess delegate;
+ synchronized (mDelegateProcesses) {
+ delegate = mDelegateProcesses.get(appThread.asBinder());
+ if (delegate != null && delegate.mNeedReport) {
+ // It was predicted to run remote transition. Now it is really requesting so
+ // remove the timeout of restoration.
+ delegate.mNeedReport = false;
+ mAtm.mH.removeCallbacks(delegate);
+ }
+ }
+ return delegate != null;
+ }
+ }
+
static class TransitionMetricsReporter extends ITransitionMetricsReporter.Stub {
private final ArrayMap<IBinder, LongConsumer> mMetricConsumers = new ArrayMap<>();
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index bce131b..adb8bf6 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -3769,9 +3769,9 @@
// Check if this is changing displays. If so, mark the old display as "ready" for
// transitions. This is to work around the problem where setting readiness against this
// container will only set the new display as ready and leave the old display as unready.
- if (mSyncState != SYNC_STATE_NONE && oldParent != null
- && oldParent.getDisplayContent() != null && (newParent == null
- || oldParent.getDisplayContent() != newParent.getDisplayContent())) {
+ if (mSyncState != SYNC_STATE_NONE && oldParent != null && newParent != null
+ && oldParent.getDisplayContent() != null && newParent.getDisplayContent() != null
+ && oldParent.getDisplayContent() != newParent.getDisplayContent()) {
mTransitionController.setReady(oldParent.getDisplayContent());
}
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 0f5655c..95db746 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -37,6 +37,7 @@
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_RESTORE_TRANSIENT_ORDER;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_ADJACENT_ROOTS;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS;
+import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_ALWAYS_ON_TOP;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_LAUNCH_ADJACENT_FLAG_ROOT;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_LAUNCH_ROOT;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT;
@@ -55,7 +56,6 @@
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityOptions;
-import android.app.IApplicationThread;
import android.app.WindowConfiguration;
import android.content.ActivityNotFoundException;
import android.content.Intent;
@@ -386,6 +386,12 @@
private void applyTransaction(@NonNull WindowContainerTransaction t, int syncId,
@Nullable Transition transition, @NonNull CallerInfo caller,
@Nullable Transition finishTransition) {
+ if (t.getTaskFragmentOrganizer() != null && !mTaskFragmentOrganizerController
+ .isOrganizerRegistered(t.getTaskFragmentOrganizer())) {
+ Slog.e(TAG, "Caller organizer=" + t.getTaskFragmentOrganizer()
+ + " is no longer registered");
+ return;
+ }
int effects = 0;
ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "Apply window transaction, syncId=%d", syncId);
mService.deferWindowLayout();
@@ -1068,6 +1074,18 @@
WindowContainer.fromBinder(hop.getContainer())
.removeLocalInsetsSourceProvider(hop.getInsetsTypes());
break;
+ case HIERARCHY_OP_TYPE_SET_ALWAYS_ON_TOP:
+ final WindowContainer container = WindowContainer.fromBinder(hop.getContainer());
+ if (container == null || container.asDisplayArea() == null
+ || !container.isAttached()) {
+ Slog.e(TAG, "Attempt to operate on unknown or detached display area: "
+ + container);
+ break;
+ }
+ container.setAlwaysOnTop(hop.isAlwaysOnTop());
+ effects |= TRANSACT_EFFECTS_LIFECYCLE;
+ break;
+
}
return effects;
}
@@ -1449,11 +1467,7 @@
synchronized (mGlobalLock) {
final WindowProcessController wpc =
mService.getProcessController(callerPid, callerUid);
- IApplicationThread appThread = null;
- if (wpc != null) {
- appThread = wpc.getThread();
- }
- mTransitionController.registerTransitionPlayer(player, appThread);
+ mTransitionController.registerTransitionPlayer(player, wpc);
}
} finally {
Binder.restoreCallingIdentity(ident);
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index b39b3e4b..95b0645 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -79,7 +79,6 @@
import java.io.IOException;
import java.io.PrintWriter;
-import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
@@ -229,10 +228,6 @@
/** Whether our process is currently running a {@link IRemoteAnimationRunner} */
private boolean mRunningRemoteAnimation;
- /** List of "chained" processes that are running remote animations for this process */
- private final ArrayList<WeakReference<WindowProcessController>> mRemoteAnimationDelegates =
- new ArrayList<>();
-
// The bits used for mActivityStateFlags.
private static final int ACTIVITY_STATE_FLAG_IS_VISIBLE = 1 << 16;
private static final int ACTIVITY_STATE_FLAG_IS_PAUSING_OR_PAUSED = 1 << 17;
@@ -1677,30 +1672,7 @@
updateRunningRemoteOrRecentsAnimation();
}
- /**
- * Marks another process as a "delegate" animator. This means that process is doing some part
- * of a remote animation on behalf of this process.
- */
- void addRemoteAnimationDelegate(WindowProcessController delegate) {
- if (!isRunningRemoteTransition()) {
- throw new IllegalStateException("Can't add a delegate to a process which isn't itself"
- + " running a remote animation");
- }
- mRemoteAnimationDelegates.add(new WeakReference<>(delegate));
- }
-
void updateRunningRemoteOrRecentsAnimation() {
- if (!isRunningRemoteTransition()) {
- // Clean-up any delegates
- for (int i = 0; i < mRemoteAnimationDelegates.size(); ++i) {
- final WindowProcessController delegate = mRemoteAnimationDelegates.get(i).get();
- if (delegate == null) continue;
- delegate.setRunningRemoteAnimation(false);
- delegate.setRunningRecentsAnimation(false);
- }
- mRemoteAnimationDelegates.clear();
- }
-
// Posting on handler so WM lock isn't held when we call into AM.
mAtm.mH.sendMessage(PooledLambda.obtainMessage(
WindowProcessListener::setRunningRemoteAnimation, mListener,
diff --git a/services/tests/PackageManagerServiceTests/appenumeration/Android.bp b/services/tests/PackageManagerServiceTests/appenumeration/Android.bp
index e44ce3c..31c49b3 100644
--- a/services/tests/PackageManagerServiceTests/appenumeration/Android.bp
+++ b/services/tests/PackageManagerServiceTests/appenumeration/Android.bp
@@ -34,5 +34,10 @@
],
platform_apis: true,
test_suites: ["device-tests"],
- data: [":AppEnumerationSyncProviderTestApp"],
+ data: [
+ ":AppEnumerationCrossUserPackageVisibilityTestApp",
+ ":AppEnumerationHasAppOpPermissionTestApp",
+ ":AppEnumerationSharedUserTestApp",
+ ":AppEnumerationSyncProviderTestApp",
+ ],
}
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index 5d0554a..a9ee84f 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -109,7 +109,11 @@
data: [
":BstatsTestApp",
":JobTestApp",
+ ":SimpleServiceTestApp1",
+ ":SimpleServiceTestApp2",
+ ":SimpleServiceTestApp3",
":StubTestApp",
+ ":SuspendTestApp",
],
java_resources: [
diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml
index 0afb182..0483a60 100644
--- a/services/tests/servicestests/AndroidManifest.xml
+++ b/services/tests/servicestests/AndroidManifest.xml
@@ -103,6 +103,7 @@
<uses-permission android:name="android.permission.MODIFY_AUDIO_ROUTING" />
<uses-permission android:name="android.permission.PACKAGE_VERIFICATION_AGENT" />
<uses-permission android:name="android.permission.OBSERVE_ROLE_HOLDERS" />
+ <uses-permission android:name="android.permission.BATTERY_STATS" />
<queries>
<package android:name="com.android.servicestests.apps.suspendtestapp" />
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/SystemActionPerformerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/SystemActionPerformerTest.java
index 1d6ed03..c15f6a9 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/SystemActionPerformerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/SystemActionPerformerTest.java
@@ -17,6 +17,7 @@
package com.android.server.accessibility;
import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_ACCESSIBILITY_ACTIONS;
+import static android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN;
import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.Matchers.is;
@@ -27,7 +28,6 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
@@ -301,8 +301,9 @@
mSystemActionPerformer.performSystemAction(
AccessibilityService.GLOBAL_ACTION_TAKE_SCREENSHOT);
verify(mMockScreenshotHelper).takeScreenshot(
- eq(android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN), anyBoolean(),
- anyBoolean(), eq(SCREENSHOT_ACCESSIBILITY_ACTIONS), any(Handler.class), any());
+ eq(TAKE_SCREENSHOT_FULLSCREEN),
+ eq(SCREENSHOT_ACCESSIBILITY_ACTIONS),
+ any(Handler.class), any());
}
// PendingIntent is a final class and cannot be mocked. So we are using this
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
index 20cc42c..a4cccb3 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
@@ -33,15 +33,18 @@
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import android.app.PropertyInvalidatedCache;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
import android.service.gatekeeper.GateKeeperResponse;
+import android.text.TextUtils;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.LockscreenCredential;
import com.android.internal.widget.VerifyCredentialResponse;
@@ -450,6 +453,45 @@
PRIMARY_USER_ID));
}
+ @Test
+ public void testPasswordHistoryDisabledByDefault() throws Exception {
+ final int userId = PRIMARY_USER_ID;
+ checkPasswordHistoryLength(userId, 0);
+ initializeStorageWithCredential(userId, nonePassword());
+ checkPasswordHistoryLength(userId, 0);
+
+ assertTrue(mService.setLockCredential(newPassword("1234"), nonePassword(), userId));
+ checkPasswordHistoryLength(userId, 0);
+ }
+
+ @Test
+ public void testPasswordHistoryLengthHonored() throws Exception {
+ final int userId = PRIMARY_USER_ID;
+ when(mDevicePolicyManager.getPasswordHistoryLength(any(), eq(userId))).thenReturn(3);
+ checkPasswordHistoryLength(userId, 0);
+ initializeStorageWithCredential(userId, nonePassword());
+ checkPasswordHistoryLength(userId, 0);
+
+ assertTrue(mService.setLockCredential(newPassword("pass1"), nonePassword(), userId));
+ checkPasswordHistoryLength(userId, 1);
+
+ assertTrue(mService.setLockCredential(newPassword("pass2"), newPassword("pass1"), userId));
+ checkPasswordHistoryLength(userId, 2);
+
+ assertTrue(mService.setLockCredential(newPassword("pass3"), newPassword("pass2"), userId));
+ checkPasswordHistoryLength(userId, 3);
+
+ // maximum length should have been reached
+ assertTrue(mService.setLockCredential(newPassword("pass4"), newPassword("pass3"), userId));
+ checkPasswordHistoryLength(userId, 3);
+ }
+
+ private void checkPasswordHistoryLength(int userId, int expectedLen) {
+ String history = mService.getString(LockPatternUtils.PASSWORD_HISTORY_KEY, "", userId);
+ String[] hashes = TextUtils.split(history, LockPatternUtils.PASSWORD_HISTORY_DELIMITER);
+ assertEquals(expectedLen, hashes.length);
+ }
+
private void testCreateCredential(int userId, LockscreenCredential credential)
throws RemoteException {
assertTrue(mService.setLockCredential(credential, nonePassword(), userId));
@@ -504,11 +546,16 @@
badCredential, userId, 0 /* flags */).getResponseCode());
}
- @SuppressWarnings("GuardedBy") // for initializeSyntheticPasswordLocked
private void initializeStorageWithCredential(int userId, LockscreenCredential credential)
throws RemoteException {
assertEquals(0, mGateKeeperService.getSecureUserId(userId));
- mService.initializeSyntheticPasswordLocked(credential, userId);
- assertNotEquals(0, mGateKeeperService.getSecureUserId(userId));
+ synchronized (mService.mSpManager) {
+ mService.initializeSyntheticPasswordLocked(credential, userId);
+ }
+ if (credential.isNone()) {
+ assertEquals(0, mGateKeeperService.getSecureUserId(userId));
+ } else {
+ assertNotEquals(0, mGateKeeperService.getSecureUserId(userId));
+ }
}
}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/MockSyntheticPasswordManager.java b/services/tests/servicestests/src/com/android/server/locksettings/MockSyntheticPasswordManager.java
index 0bb2021..186a04f 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/MockSyntheticPasswordManager.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/MockSyntheticPasswordManager.java
@@ -44,12 +44,14 @@
mGateKeeper = gatekeeper;
}
- private ArrayMap<String, byte[]> mBlobs = new ArrayMap<>();
+ private final ArrayMap<String, byte[]> mBlobs = new ArrayMap<>();
@Override
- protected byte[] decryptSPBlob(String blobKeyName, byte[] blob, byte[] protectorSecret) {
- if (mBlobs.containsKey(blobKeyName) && !Arrays.equals(mBlobs.get(blobKeyName), blob)) {
- throw new AssertionFailedError("blobKeyName content is overwritten: " + blobKeyName);
+ protected byte[] decryptSpBlob(String protectorKeyAlias, byte[] blob, byte[] protectorSecret) {
+ if (mBlobs.containsKey(protectorKeyAlias) &&
+ !Arrays.equals(mBlobs.get(protectorKeyAlias), blob)) {
+ throw new AssertionFailedError("Blob was overwritten; protectorKeyAlias="
+ + protectorKeyAlias);
}
ByteBuffer buffer = ByteBuffer.allocate(blob.length);
buffer.put(blob, 0, blob.length);
@@ -72,7 +74,7 @@
}
@Override
- protected byte[] createSPBlob(String blobKeyName, byte[] data, byte[] protectorSecret,
+ protected byte[] createSpBlob(String protectorKeyAlias, byte[] data, byte[] protectorSecret,
long sid) {
ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES + data.length + Integer.BYTES
+ protectorSecret.length + Long.BYTES);
@@ -82,12 +84,12 @@
buffer.put(protectorSecret);
buffer.putLong(sid);
byte[] result = buffer.array();
- mBlobs.put(blobKeyName, result);
+ mBlobs.put(protectorKeyAlias, result);
return result;
}
@Override
- protected void destroySPBlobKey(String keyAlias) {
+ protected void destroyProtectorKey(String keyAlias) {
}
@Override
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java
index d9af51f..c2e83f2 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java
@@ -54,6 +54,7 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.internal.util.ArrayUtils;
import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb;
import com.android.server.locksettings.recoverablekeystore.storage.RecoverySnapshotStorage;
@@ -835,7 +836,7 @@
byte[] locallyEncryptedKey = SecureBox.decrypt(
TestData.getInsecurePrivateKeyForEndpoint1(),
/*sharedSecret=*/ KeySyncUtils.calculateThmKfHash(lockScreenHash),
- /*header=*/ KeySyncUtils.concat(THM_ENCRYPTED_RECOVERY_KEY_HEADER, vaultParams),
+ /*header=*/ ArrayUtils.concat(THM_ENCRYPTED_RECOVERY_KEY_HEADER, vaultParams),
encryptedKey
);
return KeySyncUtils.decryptRecoveryKey(lockScreenHash, locallyEncryptedKey);
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncUtilsTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncUtilsTest.java
index 178fd10..19a606e 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncUtilsTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncUtilsTest.java
@@ -27,6 +27,7 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.internal.util.ArrayUtils;
import com.google.common.collect.ImmutableMap;
import org.junit.Test;
@@ -117,17 +118,6 @@
}
@Test
- public void concat_concatenatesArrays() {
- assertArrayEquals(
- utf8Bytes("hello, world!"),
- KeySyncUtils.concat(
- utf8Bytes("hello"),
- utf8Bytes(", "),
- utf8Bytes("world"),
- utf8Bytes("!")));
- }
-
- @Test
public void decryptApplicationKey_decryptsAnApplicationKey_nullMetadata() throws Exception {
String alias = "phoebe";
SecretKey recoveryKey = KeySyncUtils.generateRecoveryKey();
@@ -253,7 +243,7 @@
byte[] encryptedPayload = SecureBox.encrypt(
/*theirPublicKey=*/ null,
/*sharedSecret=*/ keyClaimant,
- /*header=*/ KeySyncUtils.concat(RECOVERY_RESPONSE_HEADER, vaultParams),
+ /*header=*/ ArrayUtils.concat(RECOVERY_RESPONSE_HEADER, vaultParams),
/*payload=*/ recoveryKey);
byte[] decrypted = KeySyncUtils.decryptRecoveryClaimResponse(
@@ -269,7 +259,7 @@
byte[] encryptedPayload = SecureBox.encrypt(
/*theirPublicKey=*/ null,
/*sharedSecret=*/ KeySyncUtils.generateKeyClaimant(),
- /*header=*/ KeySyncUtils.concat(RECOVERY_RESPONSE_HEADER, vaultParams),
+ /*header=*/ ArrayUtils.concat(RECOVERY_RESPONSE_HEADER, vaultParams),
/*payload=*/ recoveryKey);
try {
@@ -298,9 +288,9 @@
byte[] decrypted = SecureBox.decrypt(
keyPair.getPrivate(),
/*sharedSecret=*/ null,
- /*header=*/ KeySyncUtils.concat(RECOVERY_CLAIM_HEADER, vaultParams, challenge),
+ /*header=*/ ArrayUtils.concat(RECOVERY_CLAIM_HEADER, vaultParams, challenge),
encryptedRecoveryClaim);
- assertArrayEquals(KeySyncUtils.concat(LOCK_SCREEN_HASH_1, keyClaimant), decrypted);
+ assertArrayEquals(ArrayUtils.concat(LOCK_SCREEN_HASH_1, keyClaimant), decrypted);
}
@Test
@@ -320,7 +310,7 @@
SecureBox.decrypt(
keyPair.getPrivate(),
/*sharedSecret=*/ null,
- /*header=*/ KeySyncUtils.concat(
+ /*header=*/ ArrayUtils.concat(
RECOVERY_CLAIM_HEADER, vaultParams, randomBytes(32)),
encryptedRecoveryClaim);
fail("Should throw if challenge is incorrect.");
@@ -346,7 +336,7 @@
SecureBox.decrypt(
SecureBox.genKeyPair().getPrivate(),
/*sharedSecret=*/ null,
- /*header=*/ KeySyncUtils.concat(
+ /*header=*/ ArrayUtils.concat(
RECOVERY_CLAIM_HEADER, vaultParams, challenge),
encryptedRecoveryClaim);
fail("Should throw if secret key is incorrect.");
@@ -372,7 +362,7 @@
SecureBox.decrypt(
keyPair.getPrivate(),
/*sharedSecret=*/ null,
- /*header=*/ KeySyncUtils.concat(
+ /*header=*/ ArrayUtils.concat(
RECOVERY_CLAIM_HEADER, randomBytes(100), challenge),
encryptedRecoveryClaim);
fail("Should throw if vault params is incorrect.");
@@ -399,7 +389,7 @@
SecureBox.decrypt(
keyPair.getPrivate(),
/*sharedSecret=*/ null,
- /*header=*/ KeySyncUtils.concat(randomBytes(10), vaultParams, challenge),
+ /*header=*/ ArrayUtils.concat(randomBytes(10), vaultParams, challenge),
encryptedRecoveryClaim);
fail("Should throw if header is incorrect.");
} catch (AEADBadTagException e) {
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
index 035249e..aceae61 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
@@ -58,6 +58,7 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.internal.util.ArrayUtils;
import com.android.internal.widget.LockPatternUtils;
import com.android.server.locksettings.recoverablekeystore.storage.ApplicationKeyStorage;
import com.android.server.locksettings.recoverablekeystore.storage.CleanupManager;
@@ -1287,7 +1288,7 @@
return SecureBox.encrypt(
/*theirPublicKey=*/ null,
/*sharedSecret=*/ keyClaimant,
- /*header=*/ KeySyncUtils.concat(RECOVERY_RESPONSE_HEADER, vaultParams),
+ /*header=*/ ArrayUtils.concat(RECOVERY_RESPONSE_HEADER, vaultParams),
/*payload=*/ locallyEncryptedRecoveryKey);
}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/SecureBoxTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/SecureBoxTest.java
index 15b0708..34235bd 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/SecureBoxTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/SecureBoxTest.java
@@ -27,6 +27,8 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import com.android.internal.util.ArrayUtils;
+
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
@@ -176,9 +178,9 @@
SecureBox.decrypt(
THM_PRIVATE_KEY,
/*sharedSecret=*/ null,
- SecureBox.concat(getBytes("V1 KF_claim"), VAULT_PARAMS, VAULT_CHALLENGE),
+ ArrayUtils.concat(getBytes("V1 KF_claim"), VAULT_PARAMS, VAULT_CHALLENGE),
RECOVERY_CLAIM);
- assertThat(claimContent).isEqualTo(SecureBox.concat(THM_KF_HASH, KEY_CLAIMANT));
+ assertThat(claimContent).isEqualTo(ArrayUtils.concat(THM_KF_HASH, KEY_CLAIMANT));
}
@Test
@@ -186,7 +188,7 @@
SecureBox.decrypt(
THM_PRIVATE_KEY,
THM_KF_HASH,
- SecureBox.concat(getBytes("V1 THM_encrypted_recovery_key"), VAULT_PARAMS),
+ ArrayUtils.concat(getBytes("V1 THM_encrypted_recovery_key"), VAULT_PARAMS),
ENCRYPTED_RECOVERY_KEY);
}
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsManagerTest.java b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsManagerTest.java
new file mode 100644
index 0000000..7ae1117
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsManagerTest.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.power.stats;
+
+import static androidx.test.InstrumentationRegistry.getContext;
+
+import static org.junit.Assert.fail;
+
+import android.os.BatteryConsumer;
+import android.os.BatteryStatsManager;
+import android.os.BatteryUsageStats;
+import android.os.BatteryUsageStatsQuery;
+import android.os.UidBatteryConsumer;
+
+import org.junit.Test;
+
+/**
+ * Test BatteryStatsManager and CellularBatteryStats to ensure that valid data is being reported
+ * and that invalid data is not reported.
+ */
+public class BatteryStatsManagerTest {
+
+ @Test
+ public void testBatteryUsageStatsDataConsistency() {
+ BatteryStatsManager bsm = getContext().getSystemService(BatteryStatsManager.class);
+ BatteryUsageStats stats = bsm.getBatteryUsageStats(
+ new BatteryUsageStatsQuery.Builder().setMaxStatsAgeMs(
+ 0).includeProcessStateData().build());
+ final int[] components =
+ {BatteryConsumer.POWER_COMPONENT_CPU,
+ BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
+ BatteryConsumer.POWER_COMPONENT_WIFI,
+ BatteryConsumer.POWER_COMPONENT_BLUETOOTH};
+ final int[] states =
+ {BatteryConsumer.PROCESS_STATE_FOREGROUND,
+ BatteryConsumer.PROCESS_STATE_BACKGROUND,
+ BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE,
+ BatteryConsumer.PROCESS_STATE_CACHED};
+ for (UidBatteryConsumer ubc : stats.getUidBatteryConsumers()) {
+ for (int component : components) {
+ double consumedPower = ubc.getConsumedPower(ubc.getKey(component));
+ double sumStates = 0;
+ for (int state : states) {
+ sumStates += ubc.getConsumedPower(ubc.getKey(component, state));
+ }
+ if (sumStates > consumedPower + 0.1) {
+ fail("Sum of states exceeds total. UID = " + ubc.getUid() + " "
+ + BatteryConsumer.powerComponentIdToString(component)
+ + " total = " + consumedPower + " states = " + sumStates);
+ }
+ }
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsTests.java b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsTests.java
index 1b724d0..807df47 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsTests.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsTests.java
@@ -34,6 +34,7 @@
BatteryStatsHistoryIteratorTest.class,
BatteryStatsHistoryTest.class,
BatteryStatsImplTest.class,
+ BatteryStatsManagerTest.class,
BatteryStatsNoteTest.class,
BatteryStatsSamplingTimerTest.class,
BatteryStatsSensorTest.class,
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 7c46fd6..5146616 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -2415,7 +2415,7 @@
activity.removeImmediately();
}
- @UseTestDisplay(addWindows = W_ACTIVITY)
+ @SetupWindows(addWindows = W_ACTIVITY)
@Test
public void testLandscapeSeascapeRotationByApp() {
final Task task = new TaskBuilder(mSupervisor)
@@ -2448,14 +2448,14 @@
appWindow.removeImmediately();
}
- @UseTestDisplay(addWindows = W_ACTIVITY)
+ @SetupWindows(addWindows = W_ACTIVITY)
@Test
public void testLandscapeSeascapeRotationByPolicy() {
final Task task = new TaskBuilder(mSupervisor)
.setDisplay(mDisplayContent).setCreateActivity(true).build();
final ActivityRecord activity = task.getTopNonFinishingActivity();
- // This instance has been spied in {@link TestDisplayContent}.
final DisplayRotation displayRotation = mDisplayContent.getDisplayRotation();
+ spyOn(displayRotation);
final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(
TYPE_BASE_APPLICATION);
@@ -2572,7 +2572,6 @@
mWm.mDisplayFrozen = false;
}
- @UseTestDisplay
@Test
public void testRespectTopFullscreenOrientation() {
final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build();
@@ -2595,7 +2594,6 @@
assertEquals(Configuration.ORIENTATION_LANDSCAPE, activityConfig.orientation);
}
- @UseTestDisplay
@Test
public void testReportOrientationChange() {
final Task task = new TaskBuilder(mSupervisor)
@@ -3119,7 +3117,7 @@
assertFalse(app.mActivityRecord.mImeInsetsFrozenUntilStartInput);
}
- @UseTestDisplay(addWindows = W_INPUT_METHOD)
+ @SetupWindows(addWindows = W_INPUT_METHOD)
@Test
public void testImeInsetsFrozenFlag_resetWhenResized() {
final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
@@ -3137,7 +3135,7 @@
assertFalse(app.mActivityRecord.mImeInsetsFrozenUntilStartInput);
}
- @UseTestDisplay(addWindows = W_INPUT_METHOD)
+ @SetupWindows(addWindows = W_INPUT_METHOD)
@Test
public void testImeInsetsFrozenFlag_resetWhenNoImeFocusableInActivity() {
final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
@@ -3165,7 +3163,7 @@
assertFalse(app.mActivityRecord.mImeInsetsFrozenUntilStartInput);
}
- @UseTestDisplay(addWindows = W_INPUT_METHOD)
+ @SetupWindows(addWindows = W_INPUT_METHOD)
@Test
public void testImeInsetsFrozenFlag_resetWhenReportedToBeImeInputTarget() {
final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
@@ -3202,7 +3200,7 @@
assertEquals(state.getSource(ITYPE_IME).getFrame(), imeSource.getFrame());
}
- @UseTestDisplay(addWindows = {W_ACTIVITY, W_INPUT_METHOD})
+ @SetupWindows(addWindows = { W_ACTIVITY, W_INPUT_METHOD })
@Test
public void testImeInsetsFrozenFlag_noDispatchVisibleInsetsWhenAppNotRequest()
throws RemoteException {
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java
index d94e6c9..b87c5a3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
@@ -399,9 +400,9 @@
parentBounds.right / 2, parentBounds.bottom);
final Rect childBounds2 = new Rect(parentBounds.right / 2, parentBounds.top,
parentBounds.right, parentBounds.bottom);
- TestDisplayArea parentDa = new TestDisplayArea(mWm, parentBounds);
- TestDisplayArea childDa1 = new TestDisplayArea(mWm, childBounds1);
- TestDisplayArea childDa2 = new TestDisplayArea(mWm, childBounds2);
+ TestDisplayArea parentDa = new TestDisplayArea(mWm, parentBounds, "Parent");
+ TestDisplayArea childDa1 = new TestDisplayArea(mWm, childBounds1, "Child1");
+ TestDisplayArea childDa2 = new TestDisplayArea(mWm, childBounds2, "Child2");
parentDa.addChild(childDa1, 0);
parentDa.addChild(childDa2, 1);
@@ -619,9 +620,67 @@
controller.registerOrganizer(mockDisplayAreaOrganizer, FEATURE_VENDOR_FIRST);
}
+ @Test
+ public void testSetAlwaysOnTop_movesDisplayAreaToTop() {
+ final Rect bounds = new Rect(0, 0, 100, 100);
+ DisplayArea<WindowContainer> parent = new TestDisplayArea(mWm, bounds, "Parent");
+ parent.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+ DisplayArea<WindowContainer> child1 = new TestDisplayArea(mWm, bounds, "Child1");
+ child1.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+ DisplayArea<WindowContainer> child2 = new TestDisplayArea(mWm, bounds, "Child2");
+ child2.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+ parent.addChild(child2, 0);
+ parent.addChild(child1, 1);
+
+ child2.setAlwaysOnTop(true);
+
+ assertEquals(parent.getChildAt(1), child2);
+ assertThat(child2.isAlwaysOnTop()).isTrue();
+ }
+
+ @Test
+ public void testDisplayAreaRequestsTopPosition_alwaysOnTopSiblingExists_doesNotMoveToTop() {
+ final Rect bounds = new Rect(0, 0, 100, 100);
+ DisplayArea<WindowContainer> parent = new TestDisplayArea(mWm, bounds, "Parent");
+ parent.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+ DisplayArea<WindowContainer> alwaysOnTopChild = new TestDisplayArea(mWm, bounds,
+ "AlwaysOnTopChild");
+ alwaysOnTopChild.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+ DisplayArea<WindowContainer> child = new TestDisplayArea(mWm, bounds, "Child");
+ child.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+ parent.addChild(alwaysOnTopChild, 0);
+ parent.addChild(child, 1);
+ alwaysOnTopChild.setAlwaysOnTop(true);
+
+ parent.positionChildAt(POSITION_TOP, child, false /* includingParents */);
+
+ assertEquals(parent.getChildAt(1), alwaysOnTopChild);
+ assertEquals(parent.getChildAt(0), child);
+ }
+
+ @Test
+ public void testAlwaysOnTopDisplayArea_requestsNonTopLocation_doesNotMove() {
+ final Rect bounds = new Rect(0, 0, 100, 100);
+ DisplayArea<WindowContainer> parent = new TestDisplayArea(mWm, bounds, "Parent");
+ parent.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+ DisplayArea<WindowContainer> alwaysOnTopChild = new TestDisplayArea(mWm, bounds,
+ "AlwaysOnTopChild");
+ alwaysOnTopChild.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+ DisplayArea<WindowContainer> child = new TestDisplayArea(mWm, bounds, "Child");
+ child.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+ parent.addChild(alwaysOnTopChild, 0);
+ parent.addChild(child, 1);
+ alwaysOnTopChild.setAlwaysOnTop(true);
+
+ parent.positionChildAt(POSITION_BOTTOM, alwaysOnTopChild, false /* includingParents */);
+
+ assertEquals(parent.getChildAt(1), alwaysOnTopChild);
+ assertEquals(parent.getChildAt(0), child);
+ }
+
private static class TestDisplayArea<T extends WindowContainer> extends DisplayArea<T> {
- private TestDisplayArea(WindowManagerService wms, Rect bounds) {
- super(wms, ANY, "half display area");
+ private TestDisplayArea(WindowManagerService wms, Rect bounds, String name) {
+ super(wms, ANY, name);
setBounds(bounds);
}
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 d6b807f..641a3ad 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -170,7 +170,7 @@
@RunWith(WindowTestRunner.class)
public class DisplayContentTests extends WindowTestsBase {
- @UseTestDisplay(addAllCommonWindows = true)
+ @SetupWindows(addAllCommonWindows = true)
@Test
public void testForAllWindows() {
final WindowState exitingAppWindow = createWindow(null, TYPE_BASE_APPLICATION,
@@ -203,7 +203,7 @@
assertForAllWindowsOrder(windows);
}
- @UseTestDisplay(addAllCommonWindows = true)
+ @SetupWindows(addAllCommonWindows = true)
@Test
public void testForAllWindows_WithAppImeTarget() {
final WindowState imeAppTarget =
@@ -225,7 +225,7 @@
mNavBarWindow));
}
- @UseTestDisplay(addAllCommonWindows = true)
+ @SetupWindows(addAllCommonWindows = true)
@Test
public void testForAllWindows_WithChildWindowImeTarget() throws Exception {
mDisplayContent.setImeLayeringTarget(mChildAppWindowAbove);
@@ -243,7 +243,7 @@
mNavBarWindow));
}
- @UseTestDisplay(addAllCommonWindows = true)
+ @SetupWindows(addAllCommonWindows = true)
@Test
public void testForAllWindows_WithStatusBarImeTarget() throws Exception {
mDisplayContent.setImeLayeringTarget(mStatusBarWindow);
@@ -261,7 +261,7 @@
mNavBarWindow));
}
- @UseTestDisplay(addAllCommonWindows = true)
+ @SetupWindows(addAllCommonWindows = true)
@Test
public void testForAllWindows_WithNotificationShadeImeTarget() throws Exception {
mDisplayContent.setImeLayeringTarget(mNotificationShadeWindow);
@@ -279,7 +279,7 @@
mNavBarWindow));
}
- @UseTestDisplay(addAllCommonWindows = true)
+ @SetupWindows(addAllCommonWindows = true)
@Test
public void testForAllWindows_WithInBetweenWindowToken() {
// This window is set-up to be z-ordered between some windows that go in the same token like
@@ -301,7 +301,7 @@
mNavBarWindow));
}
- @UseTestDisplay(addAllCommonWindows = true)
+ @SetupWindows(addAllCommonWindows = true)
@Test
public void testComputeImeTarget() {
// Verify that an app window can be an ime target.
@@ -321,7 +321,7 @@
assertEquals(childWin, imeTarget);
}
- @UseTestDisplay(addAllCommonWindows = true)
+ @SetupWindows(addAllCommonWindows = true)
@Test
public void testComputeImeTarget_startingWindow() {
ActivityRecord activity = createActivityRecord(mDisplayContent);
@@ -985,7 +985,7 @@
assertFalse(isOptionsPanelAtRight(landscapeDisplay.getDisplayId()));
}
- @UseTestDisplay(addWindows = W_INPUT_METHOD)
+ @SetupWindows(addWindows = W_INPUT_METHOD)
@Test
public void testInputMethodTargetUpdateWhenSwitchingOnDisplays() {
final DisplayContent newDisplay = createNewDisplay();
@@ -1020,7 +1020,7 @@
mDisplayContent.getImeTarget(IME_TARGET_LAYERING));
}
- @UseTestDisplay(addWindows = W_INPUT_METHOD)
+ @SetupWindows(addWindows = W_INPUT_METHOD)
@Test
public void testInputMethodSet_listenOnDisplayAreaConfigurationChanged() {
spyOn(mAtm);
@@ -1159,7 +1159,7 @@
assertEquals(dc.getImeContainer().getParentSurfaceControl(), dc.computeImeParent());
}
- @UseTestDisplay(addWindows = W_ACTIVITY)
+ @SetupWindows(addWindows = W_ACTIVITY)
@Test
public void testComputeImeParent_app_notMatchParentBounds() {
spyOn(mAppWindow.mActivityRecord);
@@ -1178,7 +1178,7 @@
assertEquals(dc.getImeContainer().getParentSurfaceControl(), dc.computeImeParent());
}
- @UseTestDisplay(addWindows = W_ACTIVITY)
+ @SetupWindows(addWindows = W_ACTIVITY)
@Test
public void testComputeImeParent_inputTargetNotUpdate() throws Exception {
WindowState app1 = createWindow(null, TYPE_BASE_APPLICATION, "app1");
@@ -1193,7 +1193,7 @@
assertNull(mDisplayContent.computeImeParent());
}
- @UseTestDisplay(addWindows = W_ACTIVITY)
+ @SetupWindows(addWindows = W_ACTIVITY)
@Test
public void testComputeImeParent_updateParentWhenTargetNotUseIme() throws Exception {
WindowState overlay = createWindow(null, TYPE_APPLICATION_OVERLAY, "overlay");
@@ -1268,7 +1268,7 @@
dc.computeImeControlTarget());
}
- @UseTestDisplay(addWindows = W_INPUT_METHOD)
+ @SetupWindows(addWindows = W_INPUT_METHOD)
@Test
public void testImeSecureFlagGetUpdatedAfterImeInputTarget() {
// Verify IME window can get up-to-date secure flag update when the IME input target
@@ -1282,7 +1282,7 @@
verify(t).setSecure(eq(mDisplayContent.mInputMethodWindow.mSurfaceControl), eq(true));
}
- @UseTestDisplay(addWindows = W_ACTIVITY)
+ @SetupWindows(addWindows = W_ACTIVITY)
@Test
public void testComputeImeControlTarget_notMatchParentBounds() throws Exception {
spyOn(mAppWindow.mActivityRecord);
@@ -1426,7 +1426,7 @@
win.setHasSurface(false);
}
- @UseTestDisplay(addWindows = { W_ABOVE_ACTIVITY, W_ACTIVITY})
+ @SetupWindows(addWindows = { W_ABOVE_ACTIVITY, W_ACTIVITY })
@Test
public void testRequestResizeForEmptyFrames() {
final WindowState win = mChildAppWindowAbove;
@@ -1501,11 +1501,12 @@
assertNull(displayContent.getAsyncRotationController());
}
- @UseTestDisplay(addWindows = { W_ACTIVITY, W_WALLPAPER, W_STATUS_BAR, W_NAVIGATION_BAR,
+ @SetupWindows(addWindows = { W_ACTIVITY, W_WALLPAPER, W_STATUS_BAR, W_NAVIGATION_BAR,
W_INPUT_METHOD, W_NOTIFICATION_SHADE })
@Test
public void testApplyTopFixedRotationTransform() {
final DisplayPolicy displayPolicy = mDisplayContent.getDisplayPolicy();
+ spyOn(displayPolicy);
// Only non-movable (gesture) navigation bar will be animated by fixed rotation animation.
doReturn(false).when(displayPolicy).navigationBarCanMove();
displayPolicy.addWindowLw(mStatusBarWindow, mStatusBarWindow.mAttrs);
@@ -1624,6 +1625,8 @@
// The display should be rotated after the launch is finished.
doReturn(false).when(app).isAnimating(anyInt(), anyInt());
mDisplayContent.mAppTransition.notifyAppTransitionFinishedLocked(app.token);
+ mStatusBarWindow.finishSeamlessRotation(t);
+ mNavBarWindow.finishSeamlessRotation(t);
// The fixed rotation should be cleared and the new rotation is applied to display.
assertFalse(app.hasFixedRotationTransform());
@@ -1655,7 +1658,7 @@
assertFalse(mDisplayContent.hasTopFixedRotationLaunchingApp());
}
- @UseTestDisplay(addWindows = W_ACTIVITY)
+ @SetupWindows(addWindows = W_ACTIVITY)
@Test
public void testRotateSeamlesslyWithFixedRotation() {
final DisplayRotation displayRotation = mDisplayContent.getDisplayRotation();
@@ -2081,7 +2084,7 @@
verifySizes(dc, forcedWidth, forcedHeight, forcedDensity);
}
- @UseTestDisplay(addWindows = { W_ACTIVITY, W_INPUT_METHOD })
+ @SetupWindows(addWindows = { W_ACTIVITY, W_INPUT_METHOD })
@Test
public void testComputeImeTarget_shouldNotCheckOutdatedImeTargetLayerWhenRemoved() {
final WindowState child1 = createWindow(mAppWindow, FIRST_SUB_WINDOW, "child1");
@@ -2104,7 +2107,7 @@
verify(child1, never()).needsRelativeLayeringToIme();
}
- @UseTestDisplay(addWindows = {W_INPUT_METHOD}, addAllCommonWindows = true)
+ @SetupWindows(addWindows = W_INPUT_METHOD)
@Test
public void testAttachAndShowImeScreenshotOnTarget() {
// Preparation: Simulate screen state is on.
@@ -2155,7 +2158,7 @@
assertNotNull(mDisplayContent.mImeScreenshot);
}
- @UseTestDisplay(addWindows = {W_INPUT_METHOD}, addAllCommonWindows = true)
+ @SetupWindows(addWindows = W_INPUT_METHOD)
@Test
public void testShowImeScreenshot() {
final Task rootTask = createTask(mDisplayContent);
@@ -2181,7 +2184,7 @@
verify(mDisplayContent, never()).showImeScreenshot();
}
- @UseTestDisplay(addWindows = {W_INPUT_METHOD})
+ @SetupWindows(addWindows = W_INPUT_METHOD)
@Test
public void testShowImeScreenshot_removeCurSnapshotBeforeCreateNext() {
final Task rootTask = createTask(mDisplayContent);
@@ -2471,7 +2474,7 @@
ACTIVITY_TYPE_STANDARD));
}
- @UseTestDisplay(addWindows = W_INPUT_METHOD)
+ @SetupWindows(addWindows = W_INPUT_METHOD)
@Test
public void testImeChildWindowFocusWhenImeLayeringTargetChanges() {
final WindowState imeChildWindow =
@@ -2496,7 +2499,7 @@
assertNotEquals(imeChildWindow, mDisplayContent.findFocusedWindow());
}
- @UseTestDisplay(addWindows = W_INPUT_METHOD)
+ @SetupWindows(addWindows = W_INPUT_METHOD)
@Test
public void testImeMenuDialogFocusWhenImeLayeringTargetChanges() {
final WindowState imeMenuDialog =
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyInsetsTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyInsetsTests.java
index f41fee7..2158caf 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyInsetsTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyInsetsTests.java
@@ -36,7 +36,7 @@
@SmallTest
@Presubmit
-@WindowTestsBase.UseTestDisplay(
+@WindowTestsBase.SetupWindows(
addWindows = { WindowTestsBase.W_STATUS_BAR, WindowTestsBase.W_NAVIGATION_BAR })
@RunWith(WindowTestRunner.class)
public class DisplayPolicyInsetsTests extends DisplayPolicyTestsBase {
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
index 34575ae..6bdc2e3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
@@ -67,7 +67,7 @@
*/
@SmallTest
@Presubmit
-@WindowTestsBase.UseTestDisplay(
+@WindowTestsBase.SetupWindows(
addWindows = { WindowTestsBase.W_STATUS_BAR, WindowTestsBase.W_NAVIGATION_BAR })
@RunWith(WindowTestRunner.class)
public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase {
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
index 8f2e9b4..9cc665b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
@@ -167,7 +167,7 @@
dimmingNonImTarget, imeNonDrawNavBar, NAV_BAR_BOTTOM));
}
- @UseTestDisplay(addWindows = { W_NAVIGATION_BAR })
+ @SetupWindows(addWindows = W_NAVIGATION_BAR)
@Test
public void testUpdateLightNavigationBarLw() {
DisplayPolicy displayPolicy = mDisplayContent.getDisplayPolicy();
@@ -204,7 +204,7 @@
APPEARANCE_LIGHT_NAVIGATION_BARS, opaqueLightNavBar));
}
- @UseTestDisplay(addWindows = {W_ACTIVITY, W_STATUS_BAR})
+ @SetupWindows(addWindows = { W_ACTIVITY, W_STATUS_BAR })
@Test
public void testComputeTopFullscreenOpaqueWindow() {
final WindowManager.LayoutParams attrs = mAppWindow.mAttrs;
@@ -292,7 +292,7 @@
return win;
}
- @UseTestDisplay(addWindows = { W_NAVIGATION_BAR, W_INPUT_METHOD })
+ @SetupWindows(addWindows = { W_NAVIGATION_BAR, W_INPUT_METHOD })
@Test
public void testImeMinimalSourceFrame() {
final DisplayPolicy displayPolicy = mDisplayContent.getDisplayPolicy();
@@ -328,7 +328,7 @@
assertTrue(imeSource.getFrame().contains(navBarSource.getFrame()));
}
- @UseTestDisplay(addWindows = { W_NAVIGATION_BAR })
+ @SetupWindows(addWindows = W_NAVIGATION_BAR)
@Test
public void testInsetsGivenContentFrame() {
final DisplayPolicy displayPolicy = mDisplayContent.getDisplayPolicy();
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
index d3282b9..041f298 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
@@ -253,7 +253,7 @@
assertEquals(ITYPE_NAVIGATION_BAR, panelControls[0].getType());
}
- @UseTestDisplay(addWindows = W_ACTIVITY)
+ @SetupWindows(addWindows = W_ACTIVITY)
@Test
public void testShowTransientBars_bothCanBeTransient_appGetsBothFakeControls() {
final WindowState statusBar = addNonFocusableWindow(TYPE_STATUS_BAR, "statusBar");
@@ -295,7 +295,7 @@
.getSource(ITYPE_NAVIGATION_BAR).isVisible());
}
- @UseTestDisplay(addWindows = W_ACTIVITY)
+ @SetupWindows(addWindows = W_ACTIVITY)
@Test
public void testShowTransientBars_statusBarCanBeTransient_appGetsStatusBarFakeControl() {
addNonFocusableWindow(TYPE_STATUS_BAR, "statusBar")
@@ -325,7 +325,7 @@
}
}
- @UseTestDisplay(addWindows = W_ACTIVITY)
+ @SetupWindows(addWindows = W_ACTIVITY)
@Test
public void testAbortTransientBars_bothCanBeAborted_appGetsBothRealControls() {
final InsetsSource statusBarSource = addNonFocusableWindow(TYPE_STATUS_BAR, "statusBar")
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
index 6c161cf..fe14d8e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
@@ -131,7 +131,7 @@
assertNull(app.getInsetsState().peekSource(ITYPE_NAVIGATION_BAR));
}
- @UseTestDisplay(addWindows = W_INPUT_METHOD)
+ @SetupWindows(addWindows = W_INPUT_METHOD)
@Test
public void testStripForDispatch_independentSources() {
getController().getSourceProvider(ITYPE_IME).setWindowContainer(mImeWindow, null, null);
@@ -148,7 +148,7 @@
.isVisible());
}
- @UseTestDisplay(addWindows = W_INPUT_METHOD)
+ @SetupWindows(addWindows = W_INPUT_METHOD)
@Test
public void testStripForDispatch_belowIme() {
getController().getSourceProvider(ITYPE_IME).setWindowContainer(mImeWindow, null, null);
@@ -161,7 +161,7 @@
assertTrue(app.getInsetsState().getSource(ITYPE_IME).isVisible());
}
- @UseTestDisplay(addWindows = W_INPUT_METHOD)
+ @SetupWindows(addWindows = W_INPUT_METHOD)
@Test
public void testStripForDispatch_aboveIme() {
getController().getSourceProvider(ITYPE_IME).setWindowContainer(mImeWindow, null, null);
@@ -173,7 +173,7 @@
.isVisible());
}
- @UseTestDisplay(addWindows = W_INPUT_METHOD)
+ @SetupWindows(addWindows = W_INPUT_METHOD)
@Test
public void testStripForDispatch_imeOrderChanged() {
// This can be the IME z-order target while app cannot be the IME z-order target.
@@ -228,7 +228,7 @@
assertTrue(app.getInsetsState().getSource(ITYPE_IME).isVisible());
}
- @UseTestDisplay(addWindows = W_INPUT_METHOD)
+ @SetupWindows(addWindows = W_INPUT_METHOD)
@Test
public void testStripForDispatch_childWindow_altFocusable() {
getController().getSourceProvider(ITYPE_IME).setWindowContainer(mImeWindow, null, null);
@@ -249,7 +249,7 @@
.isVisible());
}
- @UseTestDisplay(addWindows = W_INPUT_METHOD)
+ @SetupWindows(addWindows = W_INPUT_METHOD)
@Test
public void testStripForDispatch_childWindow_splitScreen() {
getController().getSourceProvider(ITYPE_IME).setWindowContainer(mImeWindow, null, null);
@@ -456,7 +456,7 @@
assertNotNull(app.getInsetsState().peekSource(ITYPE_NAVIGATION_BAR));
}
- @UseTestDisplay(addWindows = W_INPUT_METHOD)
+ @SetupWindows(addWindows = W_INPUT_METHOD)
@Test
public void testGetInsetsHintForNewControl() {
final WindowState app1 = createTestWindow("app1");
diff --git a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
index 027f521..c548dc3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
@@ -749,7 +749,7 @@
}
}
- @UseTestDisplay(addWindows = W_INPUT_METHOD)
+ @SetupWindows(addWindows = W_INPUT_METHOD)
@Test
public void testLaunchRemoteAnimationWithoutImeBehind() {
final WindowState win1 = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin1");
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index 324e244..21839aa 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -61,8 +61,10 @@
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.ArgumentMatchers.same;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.clearInvocations;
@@ -82,11 +84,14 @@
import android.platform.test.annotations.Presubmit;
import android.provider.DeviceConfig;
import android.provider.DeviceConfig.Properties;
+import android.view.InsetsVisibilities;
import android.view.WindowManager;
import androidx.test.filters.MediumTest;
import com.android.internal.policy.SystemBarUtils;
+import com.android.internal.statusbar.LetterboxDetails;
+import com.android.server.statusbar.StatusBarManagerInternal;
import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
@@ -97,6 +102,7 @@
import org.junit.Test;
import org.junit.rules.TestRule;
import org.junit.runner.RunWith;
+import org.mockito.Mockito;
/**
* Tests for Size Compatibility mode.
@@ -2113,6 +2119,104 @@
assertLetterboxSurfacesDrawnBetweenActivityAndParentBounds(organizer.mPrimary.getBounds());
}
+ @Test
+ public void testLetterboxDetailsForStatusBar_noLetterbox() {
+ setUpDisplaySizeWithApp(2800, 1000);
+ addStatusBar(mActivity.mDisplayContent);
+ addWindowToActivity(mActivity); // Add a window to the activity so that we can get an
+ // appearance inside letterboxDetails
+
+ DisplayPolicy displayPolicy = mActivity.getDisplayContent().getDisplayPolicy();
+ StatusBarManagerInternal statusBar = displayPolicy.getStatusBarManagerInternal();
+ // We should get a null LetterboxDetails object as there is no letterboxed activity, so
+ // nothing will get passed to SysUI
+ verify(statusBar, never()).onSystemBarAttributesChanged(anyInt(), anyInt(),
+ any(), anyBoolean(), anyInt(),
+ any(InsetsVisibilities.class), isNull(), isNull());
+
+ }
+
+ @Test
+ public void testLetterboxDetailsForStatusBar_letterboxedForMaxAspectRatio() {
+ setUpDisplaySizeWithApp(2800, 1000);
+ addStatusBar(mActivity.mDisplayContent);
+ addWindowToActivity(mActivity); // Add a window to the activity so that we can get an
+ // appearance inside letterboxDetails
+ // Prepare unresizable activity with max aspect ratio
+ prepareUnresizable(mActivity, /* maxAspect */ 1.1f, SCREEN_ORIENTATION_UNSPECIFIED);
+ // Refresh the letterbox
+ mActivity.mRootWindowContainer.performSurfacePlacement();
+
+ Rect mBounds = new Rect(mActivity.getWindowConfiguration().getBounds());
+ assertEquals(mBounds, new Rect(850, 0, 1950, 1000));
+
+ DisplayPolicy displayPolicy = mActivity.getDisplayContent().getDisplayPolicy();
+ LetterboxDetails[] expectedLetterboxDetails = {new LetterboxDetails(
+ mBounds,
+ mActivity.getDisplayContent().getBounds(),
+ mActivity.findMainWindow().mAttrs.insetsFlags.appearance
+ )};
+
+ // Check that letterboxDetails actually gets passed to SysUI
+ StatusBarManagerInternal statusBar = displayPolicy.getStatusBarManagerInternal();
+ verify(statusBar).onSystemBarAttributesChanged(anyInt(), anyInt(),
+ any(), anyBoolean(), anyInt(),
+ any(InsetsVisibilities.class), isNull(), eq(expectedLetterboxDetails));
+ }
+
+ @Test
+ public void testSplitScreenLetterboxDetailsForStatusBar_twoLetterboxedApps() {
+ mAtm.mDevEnableNonResizableMultiWindow = true;
+ setUpDisplaySizeWithApp(2800, 1000);
+ addStatusBar(mActivity.mDisplayContent);
+ // Create another task for the second activity
+ final Task newTask = new TaskBuilder(mSupervisor).setDisplay(mActivity.getDisplayContent())
+ .setCreateActivity(true).build();
+ ActivityRecord newActivity = newTask.getTopNonFinishingActivity();
+
+ final TestSplitOrganizer organizer =
+ new TestSplitOrganizer(mAtm, mActivity.getDisplayContent());
+
+ // Move first activity to split screen which takes half of the screen.
+ organizer.mPrimary.setBounds(0, 0, 1400, 1000);
+ organizer.putTaskToPrimary(mTask, true);
+ // Move second activity to split screen which takes half of the screen.
+ organizer.mSecondary.setBounds(1400, 0, 2800, 1000);
+ organizer.putTaskToSecondary(newTask, true);
+
+ addWindowToActivity(mActivity); // Add a window to the activity so that we can get an
+ // appearance inside letterboxDetails
+ // Prepare unresizable activity with max aspect ratio
+ prepareUnresizable(mActivity, /* maxAspect */ 1.1f, SCREEN_ORIENTATION_UNSPECIFIED);
+ addWindowToActivity(newActivity);
+ prepareUnresizable(newActivity, /* maxAspect */ 1.1f, SCREEN_ORIENTATION_UNSPECIFIED);
+
+ // Refresh the letterboxes
+ newActivity.mRootWindowContainer.performSurfacePlacement();
+
+ Rect mBounds = new Rect(mActivity.getWindowConfiguration().getBounds());
+ assertEquals(mBounds, new Rect(150, 0, 1250, 1000));
+ final Rect newBounds = new Rect(newActivity.getWindowConfiguration().getBounds());
+ assertEquals(newBounds, new Rect(1550, 0, 2650, 1000));
+
+ DisplayPolicy displayPolicy = mActivity.getDisplayContent().getDisplayPolicy();
+ LetterboxDetails[] expectedLetterboxDetails = { new LetterboxDetails(
+ mBounds,
+ organizer.mPrimary.getBounds(),
+ mActivity.findMainWindow().mAttrs.insetsFlags.appearance
+ ), new LetterboxDetails(
+ newBounds,
+ organizer.mSecondary.getBounds(),
+ newActivity.findMainWindow().mAttrs.insetsFlags.appearance
+ )};
+
+ // Check that letterboxDetails actually gets passed to SysUI
+ StatusBarManagerInternal statusBar = displayPolicy.getStatusBarManagerInternal();
+ verify(statusBar).onSystemBarAttributesChanged(anyInt(), anyInt(),
+ any(), anyBoolean(), anyInt(),
+ any(InsetsVisibilities.class), isNull(), eq(expectedLetterboxDetails));
+ }
+
private void recomputeNaturalConfigurationOfUnresizableActivity() {
// Recompute the natural configuration of the non-resizable activity and the split screen.
mActivity.clearSizeCompatMode();
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
index 97f0918..eba2755 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
@@ -420,6 +420,7 @@
@Test
public void testApplyTransaction_enforceHierarchyChange_setAdjacentRoots()
throws RemoteException {
+ mAtm.mTaskFragmentOrganizerController.registerOrganizer(mIOrganizer);
final TaskFragment taskFragment2 =
new TaskFragment(mAtm, new Binder(), true /* createdByOrganizer */);
final WindowContainerToken token2 = taskFragment2.mRemoteToken.toWindowContainerToken();
@@ -595,6 +596,25 @@
}
@Test
+ public void testApplyTransaction_skipTransactionForUnregisterOrganizer() {
+ final ActivityRecord ownerActivity = createActivityRecord(mDisplayContent);
+ final IBinder fragmentToken = new Binder();
+
+ // Allow organizer to create TaskFragment and start/reparent activity to TaskFragment.
+ createTaskFragmentFromOrganizer(mTransaction, ownerActivity, fragmentToken);
+ mAtm.mWindowOrganizerController.applyTransaction(mTransaction);
+
+ // Nothing should happen as the organizer is not registered.
+ assertNull(mAtm.mWindowOrganizerController.getTaskFragment(fragmentToken));
+
+ mController.registerOrganizer(mIOrganizer);
+ mAtm.mWindowOrganizerController.applyTransaction(mTransaction);
+
+ // Successfully created when the organizer is registered.
+ assertNotNull(mAtm.mWindowOrganizerController.getTaskFragment(fragmentToken));
+ }
+
+ @Test
public void testTaskFragmentInPip_startActivityInTaskFragment() {
setupTaskFragmentInPip();
final ActivityRecord activity = mTaskFragment.getTopMostActivity();
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java
index e433684..06a176f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java
@@ -190,7 +190,7 @@
}
}
- @UseTestDisplay(addWindows = {W_ACTIVITY, W_INPUT_METHOD})
+ @SetupWindows(addWindows = { W_ACTIVITY, W_INPUT_METHOD })
@Test
public void testCreateTaskSnapshotWithExcludingIme() {
Task task = mAppWindow.mActivityRecord.getTask();
@@ -209,7 +209,7 @@
}
}
- @UseTestDisplay(addWindows = {W_ACTIVITY, W_INPUT_METHOD})
+ @SetupWindows(addWindows = { W_ACTIVITY, W_INPUT_METHOD })
@Test
public void testCreateTaskSnapshotWithIncludingIme() {
Task task = mAppWindow.mActivityRecord.getTask();
@@ -237,7 +237,7 @@
}
}
- @UseTestDisplay(addWindows = W_ACTIVITY)
+ @SetupWindows(addWindows = W_ACTIVITY)
@Test
public void testPrepareTaskSnapshot() {
mAppWindow.mWinAnimator.mLastAlpha = 1f;
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 4227939..5a2d456 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -63,9 +63,11 @@
import android.util.ArraySet;
import android.view.SurfaceControl;
import android.window.IDisplayAreaOrganizer;
+import android.window.IRemoteTransition;
import android.window.ITaskFragmentOrganizer;
import android.window.ITaskOrganizer;
import android.window.ITransitionPlayer;
+import android.window.RemoteTransition;
import android.window.TaskFragmentOrganizer;
import android.window.TransitionInfo;
@@ -416,6 +418,38 @@
}
@Test
+ public void testRunningRemoteTransition() {
+ final TestTransitionPlayer testPlayer = new TestTransitionPlayer(
+ mAtm.getTransitionController(), mAtm.mWindowOrganizerController);
+ final WindowProcessController playerProc = mSystemServicesTestRule.addProcess(
+ "pkg.player", "proc.player", 5000 /* pid */, 5000 /* uid */);
+ testPlayer.mController.registerTransitionPlayer(testPlayer, playerProc);
+ doReturn(mock(IBinder.class)).when(playerProc.getThread()).asBinder();
+ final WindowProcessController delegateProc = mSystemServicesTestRule.addProcess(
+ "pkg.delegate", "proc.delegate", 6000 /* pid */, 6000 /* uid */);
+ doReturn(mock(IBinder.class)).when(delegateProc.getThread()).asBinder();
+ final ActivityRecord app = new ActivityBuilder(mAtm).setCreateTask(true).build();
+ final TransitionController controller = app.mTransitionController;
+ final Transition transition = controller.createTransition(TRANSIT_OPEN);
+ final RemoteTransition remoteTransition = new RemoteTransition(
+ mock(IRemoteTransition.class));
+ remoteTransition.setAppThread(delegateProc.getThread());
+ transition.collectExistenceChange(app.getTask());
+ controller.requestStartTransition(transition, app.getTask(), remoteTransition,
+ null /* displayChange */);
+ testPlayer.startTransition();
+ testPlayer.onTransactionReady(app.getSyncTransaction());
+ assertTrue(playerProc.isRunningRemoteTransition());
+ assertTrue(delegateProc.isRunningRemoteTransition());
+ assertTrue(controller.mRemotePlayer.reportRunning(delegateProc.getThread()));
+
+ testPlayer.finish();
+ assertFalse(playerProc.isRunningRemoteTransition());
+ assertFalse(delegateProc.isRunningRemoteTransition());
+ assertFalse(controller.mRemotePlayer.reportRunning(delegateProc.getThread()));
+ }
+
+ @Test
public void testOpenActivityInTheSameTaskWithDisplayChange() {
final ActivityRecord closing = createActivityRecord(mDisplayContent);
closing.mVisibleRequested = true;
@@ -864,7 +898,7 @@
final TransitionController controller = new TransitionController(mAtm, snapshotController,
mock(TransitionTracer.class));
final ITransitionPlayer player = new ITransitionPlayer.Default();
- controller.registerTransitionPlayer(player, null /* appThread */);
+ controller.registerTransitionPlayer(player, null /* playerProc */);
final Transition openTransition = controller.createTransition(TRANSIT_OPEN);
// Start out with task2 visible and set up a transition that closes task2 and opens task1
@@ -929,7 +963,7 @@
final TransitionController controller = new TransitionController(mAtm, snapshotController,
mock(TransitionTracer.class));
final ITransitionPlayer player = new ITransitionPlayer.Default();
- controller.registerTransitionPlayer(player, null /* appThread */);
+ controller.registerTransitionPlayer(player, null /* playerProc */);
final Transition openTransition = controller.createTransition(TRANSIT_OPEN);
// Start out with task2 visible and set up a transition that closes task2 and opens task1
@@ -993,7 +1027,7 @@
final TransitionController controller = new TransitionController(mAtm, snapshotController,
mock(TransitionTracer.class));
final ITransitionPlayer player = new ITransitionPlayer.Default();
- controller.registerTransitionPlayer(player, null /* appThread */);
+ controller.registerTransitionPlayer(player, null /* playerProc */);
final Transition openTransition = controller.createTransition(TRANSIT_OPEN);
// Start out with task2 visible and set up a transition that closes task2 and opens task1
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTraversalTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTraversalTests.java
index 64959f2..593e983 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTraversalTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTraversalTests.java
@@ -45,7 +45,7 @@
@RunWith(WindowTestRunner.class)
public class WindowContainerTraversalTests extends WindowTestsBase {
- @UseTestDisplay(addWindows = { W_DOCK_DIVIDER, W_INPUT_METHOD })
+ @SetupWindows(addWindows = { W_DOCK_DIVIDER, W_INPUT_METHOD })
@Test
public void testDockedDividerPosition() {
final WindowState splitScreenWindow = createWindow(null,
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index 84c2c55..9693532 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -810,7 +810,6 @@
assertThat(navigationBarInsetsReceiverTask.mLocalInsetsSourceProviders.size()).isEqualTo(0);
}
- @UseTestDisplay
@Test
public void testTaskInfoCallback() {
final ArrayList<RunningTaskInfo> lastReportedTiles = new ArrayList<>();
@@ -842,8 +841,7 @@
lastReportedTiles.clear();
called[0] = false;
- final Task rootTask2 = createTask(
- mDisplayContent, WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME);
+ final Task rootTask2 = mDisplayContent.getDefaultTaskDisplayArea().getRootHomeTask();
wct = new WindowContainerTransaction();
wct.reparent(rootTask2.mRemoteToken.toWindowContainerToken(),
info1.token, true /* onTop */);
@@ -869,7 +867,6 @@
assertEquals(ACTIVITY_TYPE_UNDEFINED, lastReportedTiles.get(0).topActivityType);
}
- @UseTestDisplay
@Test
public void testHierarchyTransaction() {
final ArrayMap<IBinder, RunningTaskInfo> lastReportedTiles = new ArrayMap<>();
@@ -890,23 +887,22 @@
// Ensure events dispatch to organizer.
mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
+ // 2 + 1 (home) = 3
final int initialRootTaskCount = mWm.mAtmService.mTaskOrganizerController.getRootTasks(
mDisplayContent.mDisplayId, null /* activityTypes */).size();
-
final Task rootTask = createTask(
mDisplayContent, WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD);
- final Task rootTask2 = createTask(
- mDisplayContent, WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME);
// Check getRootTasks works
List<RunningTaskInfo> roots = mWm.mAtmService.mTaskOrganizerController.getRootTasks(
mDisplayContent.mDisplayId, null /* activityTypes */);
- assertEquals(initialRootTaskCount + 2, roots.size());
+ assertEquals(initialRootTaskCount + 1, roots.size());
lastReportedTiles.clear();
WindowContainerTransaction wct = new WindowContainerTransaction();
wct.reparent(rootTask.mRemoteToken.toWindowContainerToken(),
info1.token, true /* onTop */);
+ final Task rootTask2 = mDisplayContent.getDefaultTaskDisplayArea().getRootHomeTask();
wct.reparent(rootTask2.mRemoteToken.toWindowContainerToken(),
info2.token, true /* onTop */);
mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct);
@@ -940,7 +936,8 @@
// Check that getRootTasks doesn't include children of tiles
roots = mWm.mAtmService.mTaskOrganizerController.getRootTasks(mDisplayContent.mDisplayId,
null /* activityTypes */);
- assertEquals(initialRootTaskCount, roots.size());
+ // Home (rootTask2) was moved into task1, so only remain 2 roots: task1 and task2.
+ assertEquals(initialRootTaskCount - 1, roots.size());
lastReportedTiles.clear();
wct = new WindowContainerTransaction();
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index 638ca05..5c7b882 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -418,7 +418,7 @@
assertFalse(app.canAffectSystemUiFlags());
}
- @UseTestDisplay(addWindows = {W_ACTIVITY, W_STATUS_BAR})
+ @SetupWindows(addWindows = { W_ACTIVITY, W_STATUS_BAR })
@Test
public void testVisibleWithInsetsProvider() {
final WindowState statusBar = mStatusBarWindow;
@@ -694,7 +694,7 @@
verify(t).setMatrix(child2.mSurfaceControl, w.mInvGlobalScale, 0, 0, w.mInvGlobalScale);
}
- @UseTestDisplay(addWindows = {W_ABOVE_ACTIVITY, W_NOTIFICATION_SHADE})
+ @SetupWindows(addWindows = { W_ABOVE_ACTIVITY, W_NOTIFICATION_SHADE })
@Test
public void testRequestDrawIfNeeded() {
final WindowState startingApp = createWindow(null /* parent */,
@@ -739,7 +739,7 @@
assertFalse(startingApp.getOrientationChanging());
}
- @UseTestDisplay(addWindows = W_ABOVE_ACTIVITY)
+ @SetupWindows(addWindows = W_ABOVE_ACTIVITY)
@Test
public void testReportResizedWithRemoteException() {
final WindowState win = mChildAppWindowAbove;
@@ -769,7 +769,7 @@
assertFalse(win.getOrientationChanging());
}
- @UseTestDisplay(addWindows = W_ABOVE_ACTIVITY)
+ @SetupWindows(addWindows = W_ABOVE_ACTIVITY)
@Test
public void testRequestResizeForBlastSync() {
final WindowState win = mChildAppWindowAbove;
@@ -907,7 +907,7 @@
assertTrue(mAtm.mActiveUids.hasNonAppVisibleWindow(uid));
}
- @UseTestDisplay(addWindows = {W_ACTIVITY, W_INPUT_METHOD})
+ @SetupWindows(addWindows = { W_ACTIVITY, W_INPUT_METHOD })
@Test
public void testNeedsRelativeLayeringToIme_notAttached() {
WindowState sameTokenWindow = createWindow(null, TYPE_BASE_APPLICATION, mAppWindow.mToken,
@@ -920,7 +920,7 @@
assertFalse(sameTokenWindow.needsRelativeLayeringToIme());
}
- @UseTestDisplay(addWindows = {W_ACTIVITY, W_INPUT_METHOD})
+ @SetupWindows(addWindows = { W_ACTIVITY, W_INPUT_METHOD })
@Test
public void testNeedsRelativeLayeringToIme_startingWindow() {
WindowState sameTokenWindow = createWindow(null, TYPE_APPLICATION_STARTING,
@@ -963,7 +963,7 @@
assertFalse(overlay.getWindowFrames().hasInsetsChanged());
}
- @UseTestDisplay(addWindows = {W_INPUT_METHOD, W_ACTIVITY})
+ @SetupWindows(addWindows = { W_INPUT_METHOD, W_ACTIVITY })
@Test
public void testImeAlwaysReceivesVisibleNavigationBarInsets() {
final InsetsSource navSource = new InsetsSource(ITYPE_NAVIGATION_BAR);
@@ -1053,7 +1053,7 @@
assertFalse(app2.getInsetsState().getSource(ITYPE_IME).isVisible());
}
- @UseTestDisplay(addWindows = {W_ACTIVITY})
+ @SetupWindows(addWindows = W_ACTIVITY)
@Test
public void testUpdateImeControlTargetWhenLeavingMultiWindow() {
WindowState app = createWindow(null, TYPE_BASE_APPLICATION,
@@ -1079,7 +1079,7 @@
assertEquals(mAppWindow, mDisplayContent.getImeTarget(IME_TARGET_CONTROL).getWindow());
}
- @UseTestDisplay(addWindows = {W_ACTIVITY, W_INPUT_METHOD, W_NOTIFICATION_SHADE})
+ @SetupWindows(addWindows = { W_ACTIVITY, W_INPUT_METHOD, W_NOTIFICATION_SHADE })
@Test
public void testNotificationShadeHasImeInsetsWhenMultiWindow() {
WindowState app = createWindow(null, TYPE_BASE_APPLICATION,
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index 0cbf1b2..564d3ca 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -110,6 +110,7 @@
import org.junit.runner.Description;
import org.mockito.Mockito;
+import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -151,7 +152,8 @@
*/
DisplayContent mDisplayContent;
- // The following fields are only available depending on the usage of annotation UseTestDisplay.
+ // The following fields are only available depending on the usage of annotation UseTestDisplay
+ // and UseCommonWindows.
WindowState mWallpaperWindow;
WindowState mImeWindow;
WindowState mImeDialogWindow;
@@ -214,14 +216,15 @@
// Only create an additional test display for annotated test class/method because it may
// significantly increase the execution time.
final Description description = mSystemServicesTestRule.getDescription();
- UseTestDisplay testDisplayAnnotation = description.getAnnotation(UseTestDisplay.class);
- if (testDisplayAnnotation == null) {
- testDisplayAnnotation = description.getTestClass().getAnnotation(UseTestDisplay.class);
- }
- if (testDisplayAnnotation != null) {
- createTestDisplay(testDisplayAnnotation);
+ final UseTestDisplay useTestDisplay = getAnnotation(description, UseTestDisplay.class);
+ if (useTestDisplay != null) {
+ createTestDisplay(useTestDisplay);
} else {
mDisplayContent = mDefaultDisplay;
+ final SetupWindows setupWindows = getAnnotation(description, SetupWindows.class);
+ if (setupWindows != null) {
+ addCommonWindows(setupWindows.addAllCommonWindows(), setupWindows.addWindows());
+ }
}
// Ensure letterbox aspect ratio is not overridden on any device target.
@@ -290,10 +293,15 @@
private void createTestDisplay(UseTestDisplay annotation) {
beforeCreateTestDisplay();
mDisplayContent = createNewDisplayWithImeSupport(DISPLAY_IME_POLICY_LOCAL);
+ addCommonWindows(annotation.addAllCommonWindows(), annotation.addWindows());
+ mDisplayContent.getInsetsPolicy().setRemoteInsetsControllerControlsSystemBars(false);
- final boolean addAll = annotation.addAllCommonWindows();
- final @CommonTypes int[] requestedWindows = annotation.addWindows();
+ // Adding a display will cause freezing the display. Make sure to wait until it's
+ // unfrozen to not run into race conditions with the tests.
+ waitUntilHandlersIdle();
+ }
+ private void addCommonWindows(boolean addAll, @CommonTypes int[] requestedWindows) {
if (addAll || ArrayUtils.contains(requestedWindows, W_WALLPAPER)) {
mWallpaperWindow = createCommonWindow(null, TYPE_WALLPAPER, "wallpaperWindow");
}
@@ -346,12 +354,6 @@
mChildAppWindowBelow = createCommonWindow(mAppWindow, TYPE_APPLICATION_MEDIA_OVERLAY,
"mChildAppWindowBelow");
}
-
- mDisplayContent.getInsetsPolicy().setRemoteInsetsControllerControlsSystemBars(false);
-
- // Adding a display will cause freezing the display. Make sure to wait until it's
- // unfrozen to not run into race conditions with the tests.
- waitUntilHandlersIdle();
}
private WindowManager.LayoutParams getNavBarLayoutParamsForRotation(int rotation) {
@@ -830,7 +832,7 @@
TestTransitionPlayer registerTestTransitionPlayer() {
final TestTransitionPlayer testPlayer = new TestTransitionPlayer(
mAtm.getTransitionController(), mAtm.mWindowOrganizerController);
- testPlayer.mController.registerTransitionPlayer(testPlayer, null /* appThread */);
+ testPlayer.mController.registerTransitionPlayer(testPlayer, null /* playerProc */);
return testPlayer;
}
@@ -877,11 +879,21 @@
}
/**
+ * The annotation to provide common windows on default display. This is mutually exclusive
+ * with {@link UseTestDisplay}.
+ */
+ @Target({ ElementType.METHOD, ElementType.TYPE })
+ @Retention(RetentionPolicy.RUNTIME)
+ @interface SetupWindows {
+ boolean addAllCommonWindows() default false;
+ @CommonTypes int[] addWindows() default {};
+ }
+
+ /**
* The annotation for class and method (higher priority) to create a non-default display that
* will be assigned to {@link #mDisplayContent}. It is used if the test needs
* <ul>
* <li>Pure empty display.</li>
- * <li>Configured common windows.</li>
* <li>Independent and customizable orientation.</li>
* <li>Cross display operation.</li>
* </ul>
@@ -896,6 +908,12 @@
@CommonTypes int[] addWindows() default {};
}
+ static <T extends Annotation> T getAnnotation(Description desc, Class<T> type) {
+ final T annotation = desc.getAnnotation(type);
+ if (annotation != null) return annotation;
+ return desc.getTestClass().getAnnotation(type);
+ }
+
/** Creates and adds a {@link TestDisplayContent} to supervisor at the given position. */
TestDisplayContent addNewDisplayContentAt(int position) {
return new TestDisplayContent.Builder(mAtm, 1000, 1500).setPosition(position).build();
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java
index 4b5f330a..3ff791b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java
@@ -258,7 +258,7 @@
* states for its children windows and by default it shouldn't let IME window setting
* the frozen insets state even the window of the window token is the IME layering target.
*/
- @UseTestDisplay(addWindows = W_INPUT_METHOD)
+ @SetupWindows(addWindows = W_INPUT_METHOD)
@Test
public void testSetInsetsFrozen_notAffectImeWindowState() {
// Pre-condition: make the IME window be controlled by IME insets provider.
diff --git a/startop/view_compiler/dex_builder_test/AndroidTest.xml b/startop/view_compiler/dex_builder_test/AndroidTest.xml
index 82509b9..59093c7 100644
--- a/startop/view_compiler/dex_builder_test/AndroidTest.xml
+++ b/startop/view_compiler/dex_builder_test/AndroidTest.xml
@@ -21,7 +21,7 @@
<option name="test-file-name" value="dex-builder-test.apk" />
</target_preparer>
- <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+ <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
<option name="cleanup" value="true" />
<option name="push" value="trivial.dex->/data/local/tmp/dex-builder-test/trivial.dex" />
<option name="push" value="simple.dex->/data/local/tmp/dex-builder-test/simple.dex" />
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 0bf4088..6deb6f8 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -39,7 +39,6 @@
import android.telecom.TelecomManager;
import android.telephony.AccessNetworkConstants.AccessNetworkType;
import android.telephony.data.ApnSetting;
-import android.telephony.data.DataCallResponse;
import android.telephony.gba.TlsParams;
import android.telephony.gba.UaSecurityProtocolIdentifier;
import android.telephony.ims.ImsReasonInfo;
@@ -1126,27 +1125,6 @@
public static final String KEY_DEFAULT_MTU_INT = "default_mtu_int";
/**
- * The data call retry configuration for different types of APN.
- * @hide
- */
- public static final String KEY_CARRIER_DATA_CALL_RETRY_CONFIG_STRINGS =
- "carrier_data_call_retry_config_strings";
-
- /**
- * Delay in milliseconds between trying APN from the pool
- * @hide
- */
- public static final String KEY_CARRIER_DATA_CALL_APN_DELAY_DEFAULT_LONG =
- "carrier_data_call_apn_delay_default_long";
-
- /**
- * Faster delay in milliseconds between trying APN from the pool
- * @hide
- */
- public static final String KEY_CARRIER_DATA_CALL_APN_DELAY_FASTER_LONG =
- "carrier_data_call_apn_delay_faster_long";
-
- /**
* Delay in milliseconds for retrying APN after disconnect
* @hide
*/
@@ -1154,21 +1132,6 @@
"carrier_data_call_apn_retry_after_disconnect_long";
/**
- * The maximum times for telephony to retry data setup on the same APN requested by
- * network through the data setup response retry timer
- * {@link DataCallResponse#getRetryDurationMillis()}. This is to prevent that network keeps
- * asking device to retry data setup forever and causes power consumption issue. For infinite
- * retring same APN, configure this as 2147483647 (i.e. {@link Integer#MAX_VALUE}).
- *
- * Note if network does not suggest any retry timer, frameworks uses the retry configuration
- * from {@link #KEY_CARRIER_DATA_CALL_RETRY_CONFIG_STRINGS}, and the maximum retry times could
- * be configured there.
- * @hide
- */
- public static final String KEY_CARRIER_DATA_CALL_RETRY_NETWORK_REQUESTED_MAX_COUNT_INT =
- "carrier_data_call_retry_network_requested_max_count_int";
-
- /**
* Data call setup permanent failure causes by the carrier
*/
public static final String KEY_CARRIER_DATA_CALL_PERMANENT_FAILURE_STRINGS =
@@ -1188,19 +1151,6 @@
"carrier_metered_roaming_apn_types_strings";
/**
- * APN types that are not allowed on cellular
- * @hide
- */
- public static final String KEY_CARRIER_WWAN_DISALLOWED_APN_TYPES_STRING_ARRAY =
- "carrier_wwan_disallowed_apn_types_string_array";
-
- /**
- * APN types that are not allowed on IWLAN
- * @hide
- */
- public static final String KEY_CARRIER_WLAN_DISALLOWED_APN_TYPES_STRING_ARRAY =
- "carrier_wlan_disallowed_apn_types_string_array";
- /**
* CDMA carrier ERI (Enhanced Roaming Indicator) file name
* @hide
*/
@@ -8419,7 +8369,6 @@
* "1800000, maximum_retries=20" means for those capabilities, retry happens in 2.5s, 3s, 5s,
* 10s, 15s, 20s, 40s, 1m, 2m, 4m, 10m, 20m, 30m, 30m, 30m, until reaching 20 retries.
*
- * // TODO: remove KEY_CARRIER_DATA_CALL_RETRY_CONFIG_STRINGS
* @hide
*/
public static final String KEY_TELEPHONY_DATA_SETUP_RETRY_RULES_STRING_ARRAY =
@@ -8814,27 +8763,13 @@
sDefaults.putBoolean(KEY_BROADCAST_EMERGENCY_CALL_STATE_CHANGES_BOOL, false);
sDefaults.putBoolean(KEY_ALWAYS_SHOW_EMERGENCY_ALERT_ONOFF_BOOL, false);
sDefaults.putInt(KEY_DEFAULT_MTU_INT, 1500);
- sDefaults.putStringArray(KEY_CARRIER_DATA_CALL_RETRY_CONFIG_STRINGS, new String[]{
- "default:default_randomization=2000,5000,10000,20000,40000,80000:5000,160000:5000,"
- + "320000:5000,640000:5000,1280000:5000,1800000:5000",
- "mms:default_randomization=2000,5000,10000,20000,40000,80000:5000,160000:5000,"
- + "320000:5000,640000:5000,1280000:5000,1800000:5000",
- "ims:max_retries=10, 5000, 5000, 5000",
- "others:max_retries=3, 5000, 5000, 5000"});
- sDefaults.putLong(KEY_CARRIER_DATA_CALL_APN_DELAY_DEFAULT_LONG, 20000);
- sDefaults.putLong(KEY_CARRIER_DATA_CALL_APN_DELAY_FASTER_LONG, 3000);
sDefaults.putLong(KEY_CARRIER_DATA_CALL_APN_RETRY_AFTER_DISCONNECT_LONG, 3000);
- sDefaults.putInt(KEY_CARRIER_DATA_CALL_RETRY_NETWORK_REQUESTED_MAX_COUNT_INT, 3);
sDefaults.putString(KEY_CARRIER_ERI_FILE_NAME_STRING, "eri.xml");
sDefaults.putInt(KEY_DURATION_BLOCKING_DISABLED_AFTER_EMERGENCY_INT, 7200);
sDefaults.putStringArray(KEY_CARRIER_METERED_APN_TYPES_STRINGS,
new String[]{"default", "mms", "dun", "supl"});
sDefaults.putStringArray(KEY_CARRIER_METERED_ROAMING_APN_TYPES_STRINGS,
new String[]{"default", "mms", "dun", "supl"});
- sDefaults.putStringArray(KEY_CARRIER_WWAN_DISALLOWED_APN_TYPES_STRING_ARRAY,
- new String[]{""});
- sDefaults.putStringArray(KEY_CARRIER_WLAN_DISALLOWED_APN_TYPES_STRING_ARRAY,
- new String[]{""});
sDefaults.putIntArray(KEY_ONLY_SINGLE_DC_ALLOWED_INT_ARRAY,
new int[] {TelephonyManager.NETWORK_TYPE_CDMA, TelephonyManager.NETWORK_TYPE_1xRTT,
TelephonyManager.NETWORK_TYPE_EVDO_0, TelephonyManager.NETWORK_TYPE_EVDO_A,
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 0ce6b14..da1ffcd 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -2510,9 +2510,6 @@
CellIdentity getLastKnownCellIdentity(int subId, String callingPackage,
String callingFeatureId);
- /** Check if telephony new data stack is enabled. */
- boolean isUsingNewDataStack();
-
/**
* @return true if the modem service is set successfully, false otherwise.
*/
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeEditorPopupDialogActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeEditorPopupDialogActivity.java
index a8613f5..95f933f 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeEditorPopupDialogActivity.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeEditorPopupDialogActivity.java
@@ -16,6 +16,8 @@
package com.android.server.wm.flicker.testapp;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN;
+
import android.app.Activity;
import android.app.AlertDialog;
import android.os.Bundle;
@@ -30,6 +32,7 @@
WindowManager.LayoutParams p = getWindow().getAttributes();
p.layoutInDisplayCutoutMode = WindowManager.LayoutParams
.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
+ p.softInputMode = SOFT_INPUT_STATE_ALWAYS_HIDDEN;
getWindow().setAttributes(p);
LinearLayout layout = new LinearLayout(this);
layout.setOrientation(LinearLayout.VERTICAL);