summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/app/Activity.java2
-rw-r--r--core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java6
-rw-r--r--core/java/android/view/contentcapture/MainContentCaptureSession.java7
-rw-r--r--core/java/com/android/internal/util/ScreenshotHelper.java200
-rw-r--r--core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotHelperTest.java11
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java77
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java10
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java15
-rw-r--r--packages/SettingsLib/ActivityEmbedding/AndroidManifest.xml2
-rw-r--r--packages/SettingsLib/CollapsingToolbarBaseActivity/AndroidManifest.xml2
-rw-r--r--packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/BasePreferencesFragment.java1
-rw-r--r--packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java1
-rw-r--r--packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarDelegate.java2
-rw-r--r--packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayout.java2
-rw-r--r--packages/SettingsLib/SettingsTransition/AndroidManifest.xml2
-rw-r--r--packages/SystemUI/Android.bp1
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt183
-rw-r--r--packages/SystemUI/src/com/android/systemui/lifecycle/WindowAddedViewLifecycleOwner.kt114
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt176
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/LogMessage.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/LogMessageImpl.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListAttachState.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java71
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java25
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/lifecycle/RepeatWhenAttachedTest.kt319
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/lifecycle/WindowAddedViewLifecycleOwnerTest.kt150
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/log/LogBufferTest.kt167
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java21
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java101
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java56
-rw-r--r--services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java7
-rw-r--r--services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java3
-rw-r--r--services/core/java/com/android/server/wm/ActivityStarter.java3
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskManagerService.java15
-rw-r--r--services/core/java/com/android/server/wm/DisplayPolicy.java2
-rw-r--r--services/core/java/com/android/server/wm/TransitionController.java129
-rw-r--r--services/core/java/com/android/server/wm/WindowOrganizerController.java7
-rw-r--r--services/core/java/com/android/server/wm/WindowProcessController.java28
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/SystemActionPerformerTest.java7
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TransitionTests.java40
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java2
50 files changed, 1306 insertions, 743 deletions
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 90c37d1999e1..4ea0c32d3b36 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -308,7 +308,7 @@ import java.util.function.Consumer;
* <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/hardware/camera2/impl/CameraExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
index 336ef7ac78db..41822e77f953 100644
--- a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
@@ -1693,8 +1693,10 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession {
((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/view/contentcapture/MainContentCaptureSession.java b/core/java/android/view/contentcapture/MainContentCaptureSession.java
index 98ef4e7ae6fa..90384b520315 100644
--- a/core/java/android/view/contentcapture/MainContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/MainContentCaptureSession.java
@@ -64,6 +64,7 @@ import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.NoSuchElementException;
import java.util.concurrent.atomic.AtomicBoolean;
/**
@@ -630,7 +631,11 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
mComponentName = null;
mEvents = null;
if (mDirectServiceInterface != null) {
- mDirectServiceInterface.asBinder().unlinkToDeath(mDirectServiceVulture, 0);
+ try {
+ mDirectServiceInterface.asBinder().unlinkToDeath(mDirectServiceVulture, 0);
+ } catch (NoSuchElementException e) {
+ Log.w(TAG, "IContentCaptureDirectManager does not exist");
+ }
}
mDirectServiceInterface = null;
mHandler.removeMessages(MSG_FLUSH);
diff --git a/core/java/com/android/internal/util/ScreenshotHelper.java b/core/java/com/android/internal/util/ScreenshotHelper.java
index f4f438b1f601..8af2450f29ef 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.RemoteException;
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 @@ public class ScreenshotHelper {
* 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;
-
- ScreenshotRequest(int source, boolean hasStatus, boolean hasNav) {
+ 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;
+
+ @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 @@ public class ScreenshotHelper {
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 @@ public class ScreenshotHelper {
return mSource;
}
- public boolean getHasStatusBar() {
- return mHasStatusBar;
- }
-
- public boolean getHasNavBar() {
- return mHasNavBar;
- }
-
public Bundle getBitmapBundle() {
return mBitmapBundle;
}
@@ -112,7 +116,6 @@ public class ScreenshotHelper {
return mTaskId;
}
-
public int getUserId() {
return mUserId;
}
@@ -129,8 +132,6 @@ public class ScreenshotHelper {
@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 class ScreenshotHelper {
}
}
- 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 @@ public class ScreenshotHelper {
/**
* 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,
- 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,
+ 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 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/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotHelperTest.java b/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotHelperTest.java
index 4b8173732b4d..fd4fb133ef12 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 @@ public final class ScreenshotHelperTest {
@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 @@ public final class ScreenshotHelperTest {
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/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index d7f1292cb717..1c2f0d8ca7b2 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.UserHandle;
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.ArrayList;
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 @@ public class BubbleController implements ConfigurationChangeListener {
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 @@ public class BubbleController implements ConfigurationChangeListener {
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 @@ public class BubbleController implements ConfigurationChangeListener {
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 @@ public class BubbleController implements ConfigurationChangeListener {
*/
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 @@ public class BubbleController implements ConfigurationChangeListener {
* @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 @@ public class BubbleController implements ConfigurationChangeListener {
*/
@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 @@ public class BubbleController implements ConfigurationChangeListener {
}
}
}
+
+ /**
+ * 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 f8ccf2364b4c..cf792cda91b5 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 static java.lang.annotation.RetentionPolicy.SOURCE;
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.Retention;
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 @@ public interface Bubbles {
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/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 3c7db33bccf2..66f5bb663fbe 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.NonNull;
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 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
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) {
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 ebaece2189aa..4e1fa290270d 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 @@ package com.android.wm.shell.transition;
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 @@ public class OneShotRemoteHandler implements Transitions.TransitionHandler {
});
}
};
+ 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 b15c48cb5889..cedb340816ae 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 @@ package com.android.wm.shell.transition;
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 @@ public class RemoteTransitionHandler implements Transitions.TransitionHandler {
});
}
};
+ 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 8e36154d10e7..881b7a1699f6 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 static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTas
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 @@ public class Transitions implements RemoteCallable<Transitions> {
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/packages/SettingsLib/ActivityEmbedding/AndroidManifest.xml b/packages/SettingsLib/ActivityEmbedding/AndroidManifest.xml
index 5ce08b7747a4..27425589c822 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 1c47f5ff862a..244b367423a4 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 8ebbac39d1d0..3582897014ff 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.fragment.app.FragmentActivity;
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 77d65834da37..8c8b47875bec 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.annotation.Nullable;
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 31e8cc709749..ec091bf4de67 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.NonNull;
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 1ead2f337314..a8c7a3f936ee 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.annotation.RequiresApi;
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 b6aff53e0290..244b367423a4 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/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index ffd6b522e394..1344144ee6fc 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -225,6 +225,7 @@ android_library {
"androidx.exifinterface_exifinterface",
"kotlinx-coroutines-android",
"kotlinx-coroutines-core",
+ "kotlinx_coroutines_test",
"iconloader_base",
"SystemUI-tags",
"SystemUI-proto",
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
index 448b99b6e5d0..a1288b531955 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 @@ public class SystemActions extends CoreStartable {
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/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
index ab30db297fce..ca65d12e87e6 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 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
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 e913d2b86306..dbf218a115cb 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 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable,
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();
}
}
@@ -2991,6 +2991,7 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable,
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/lifecycle/RepeatWhenAttached.kt b/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt
new file mode 100644
index 000000000000..e3649187b0a7
--- /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 55c7ac9fb0cc..000000000000
--- 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 db446c39767a..dc23684dd517 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.os.Trace
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 @@ import kotlin.math.max
* @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 @@ class LogBuffer @JvmOverloads constructor(
* 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 @@ class LogBuffer @JvmOverloads constructor(
* 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 @@ class LogBuffer @JvmOverloads constructor(
*/
@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 @@ class LogBuffer @JvmOverloads constructor(
}
}
- 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 @@ class LogBuffer @JvmOverloads constructor(
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 2a0a2aa6fb38..987aea8bff08 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 @@ package com.android.systemui.log
*
* 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 @@ interface LogMessage {
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 @@ interface LogMessage {
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 d33ac4b4a80b..4dd6f652d1c7 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 @@ data class LogMessageImpl(
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 @@ data class LogMessageImpl(
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 @@ data class LogMessageImpl(
LogLevel.DEBUG,
DEFAULT_TAG,
0,
- DEFAULT_RENDERER,
+ DEFAULT_PRINTER,
+ null,
null,
null,
null,
@@ -86,4 +90,4 @@ data class LogMessageImpl(
}
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/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
index 4b71b2c94f5f..15e1129e43b5 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;
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.colorextraction.SysuiColorExtractor;
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 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
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/notification/collection/ListAttachState.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListAttachState.kt
index 945bc72e3e81..9e5dab1152ec 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 @@ data class ListAttachState private constructor(
*/
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 5198d82a86c7..075a0dc7555e 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 @@ public class ShadeListBuilder implements Dumpable {
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 @@ public class ShadeListBuilder implements Dumpable {
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 @@ public class ShadeListBuilder implements Dumpable {
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 @@ public class ShadeListBuilder implements Dumpable {
*/
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 4c406e3ba0b4..d8dae5d23f42 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 @@ class ShadeListBuilderLogger @Inject constructor(
}
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/wmshell/BubblesManager.java b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
index 3e07144dce74..e22a896227ef 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.os.UserHandle;
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.Collection;
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 @@ public class BubblesManager implements Dumpable {
}
@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/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index 9b665555562a..21c018a0419d 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 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
}
@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/lifecycle/RepeatWhenAttachedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/lifecycle/RepeatWhenAttachedTest.kt
new file mode 100644
index 000000000000..80f3e46b848f
--- /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 4f5c570ee812..000000000000
--- 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 000000000000..4abb973817b1
--- /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/shade/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
index ec1fa48662b4..ad3d3d2958cb 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.anyInt;
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.testing.TestableLooper.RunWithLooper;
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 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase {
}
@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/notification/collection/ShadeListBuilderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
index c283cec5c15c..dfa38abc1ff8 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.atLeast;
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 @@ public class ShadeListBuilderTest extends SysuiTestCase {
}
@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 @@ public class ShadeListBuilderTest extends SysuiTestCase {
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 @@ public class ShadeListBuilderTest extends SysuiTestCase {
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 @@ public class ShadeListBuilderTest extends SysuiTestCase {
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/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index 59a9a3c74718..4fa45076a47c 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.times;
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;
@@ -143,7 +144,10 @@ import org.mockito.ArgumentCaptor;
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 +256,8 @@ public class BubblesTest extends SysuiTestCase {
private TaskViewTransitions mTaskViewTransitions;
@Mock
private Optional<OneHandedController> mOneHandedOptional;
+ @Mock
+ private UserManager mUserManager;
private TestableBubblePositioner mPositioner;
@@ -314,6 +320,9 @@ public class BubblesTest extends SysuiTestCase {
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),
@@ -339,7 +348,7 @@ public class BubblesTest extends SysuiTestCase {
mStatusBarService,
mWindowManager,
mWindowManagerShellWrapper,
- mock(UserManager.class),
+ mUserManager,
mLauncherApps,
mBubbleLogger,
mTaskStackListener,
@@ -1025,7 +1034,7 @@ public class BubblesTest extends SysuiTestCase {
assertThat(mBubbleData.getOverflowBubbleWithKey(mBubbleEntry2.getKey())).isNotNull();
// Switch users
- mBubbleController.onUserChanged(secondUserId);
+ switchUser(secondUserId);
assertThat(mBubbleData.getOverflowBubbles()).isEmpty();
// Give this user some bubbles
@@ -1042,6 +1051,41 @@ public class BubblesTest extends SysuiTestCase {
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 +1487,14 @@ public class BubblesTest extends SysuiTestCase {
.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/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java b/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java
index 803177b6c010..3fa0ab69d67c 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.util.Slog;
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 @@ public class SystemActionPerformer {
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/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
index d34682df3413..13816142a633 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 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt
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/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index fe3f37dd4a27..99b34c78597c 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1661,9 +1661,6 @@ class ActivityStarter {
&& 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 0260b78f49e4..c8fcee67a383 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 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
}
@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 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
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/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 14e224111286..693f9c460ee6 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -2748,8 +2748,6 @@ public class DisplayPolicy {
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 */);
}
}
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index a02be25bc8d2..5c20258538ee 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.RemoteTransition;
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 @@ class TransitionController {
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 @@ class TransitionController {
TaskSnapshotController taskSnapshotController,
TransitionTracer transitionTracer) {
mAtm = atm;
+ mRemotePlayer = new RemotePlayer(atm);
mStatusBar = LocalServices.getService(StatusBarManagerInternal.class);
mTaskSnapshotController = taskSnapshotController;
mTransitionTracer = transitionTracer;
@@ -123,6 +126,8 @@ class TransitionController {
}
mPlayingTransitions.clear();
mTransitionPlayer = null;
+ mTransitionPlayerProc = null;
+ mRemotePlayer.clear();
mRunningLock.doNotifyLocked();
}
};
@@ -169,7 +174,7 @@ class TransitionController {
}
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 @@ class TransitionController {
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 @@ class TransitionController {
}
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();
@@ -537,9 +543,7 @@ class TransitionController {
}
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 +553,27 @@ class TransitionController {
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 +676,95 @@ class TransitionController {
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/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 0f5655c74f31..48740a3c2f50 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -55,7 +55,6 @@ import android.annotation.NonNull;
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;
@@ -1449,11 +1448,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
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 3ff912cca091..87b0c8b77904 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -77,7 +77,6 @@ import com.android.server.wm.ActivityTaskManagerService.HotPath;
import java.io.IOException;
import java.io.PrintWriter;
-import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
@@ -227,10 +226,6 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
/** 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;
@@ -1663,30 +1658,7 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
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/servicestests/src/com/android/server/accessibility/SystemActionPerformerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/SystemActionPerformerTest.java
index 1d6ed038b86d..c15f6a9c3d66 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.assertThat;
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 @@ public class SystemActionPerformerTest {
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/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index c323e02ad149..a1ad07adbafb 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -62,9 +62,11 @@ import android.util.ArrayMap;
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;
@@ -413,6 +415,38 @@ public class TransitionTests extends WindowTestsBase {
}
@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;
@@ -861,7 +895,7 @@ public class TransitionTests extends WindowTestsBase {
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
@@ -926,7 +960,7 @@ public class TransitionTests extends WindowTestsBase {
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
@@ -990,7 +1024,7 @@ public class TransitionTests extends WindowTestsBase {
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/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index 0cbf1b2c7cc8..ee5f36412df2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -830,7 +830,7 @@ class WindowTestsBase extends SystemServiceTestsBase {
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;
}