diff options
155 files changed, 2265 insertions, 1298 deletions
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index f320b742a430..e8379205d55f 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -9591,21 +9591,16 @@ public class Notification implements Parcelable @NonNull public ArrayList<Action> getActionsListWithSystemActions() { // Define the system actions we expect to see - final Action negativeAction = makeNegativeAction(); - final Action answerAction = makeAnswerAction(); - // Sort the expected actions into the correct order: - // * If there's no answer action, put the hang up / decline action at the end - // * Otherwise put the answer action at the end, and put the decline action at start. - final Action firstAction = answerAction == null ? null : negativeAction; - final Action lastAction = answerAction == null ? negativeAction : answerAction; + final Action firstAction = makeNegativeAction(); + final Action lastAction = makeAnswerAction(); // Start creating the result list. int nonContextualActionSlotsRemaining = MAX_ACTION_BUTTONS; ArrayList<Action> resultActions = new ArrayList<>(MAX_ACTION_BUTTONS); - if (firstAction != null) { - resultActions.add(firstAction); - --nonContextualActionSlotsRemaining; - } + + // Always have a first action. + resultActions.add(firstAction); + --nonContextualActionSlotsRemaining; // Copy actions into the new list, correcting system actions. if (mBuilder.mActions != null) { @@ -9621,14 +9616,14 @@ public class Notification implements Parcelable --nonContextualActionSlotsRemaining; } // If there's exactly one action slot left, fill it with the lastAction. - if (nonContextualActionSlotsRemaining == 1) { + if (lastAction != null && nonContextualActionSlotsRemaining == 1) { resultActions.add(lastAction); --nonContextualActionSlotsRemaining; } } } // If there are any action slots left, the lastAction still needs to be added. - if (nonContextualActionSlotsRemaining >= 1) { + if (lastAction != null && nonContextualActionSlotsRemaining >= 1) { resultActions.add(lastAction); } return resultActions; diff --git a/core/java/android/app/servertransaction/PendingTransactionActions.java b/core/java/android/app/servertransaction/PendingTransactionActions.java index a47fe821cd01..81747782cab2 100644 --- a/core/java/android/app/servertransaction/PendingTransactionActions.java +++ b/core/java/android/app/servertransaction/PendingTransactionActions.java @@ -25,11 +25,12 @@ import android.os.Bundle; import android.os.PersistableBundle; import android.os.TransactionTooLargeException; import android.util.Log; -import android.util.LogWriter; import android.util.Slog; import com.android.internal.util.IndentingPrintWriter; +import java.io.StringWriter; + /** * Container that has data pending to be used at later stages of * {@link android.app.servertransaction.ClientTransaction}. @@ -134,6 +135,16 @@ public class PendingTransactionActions { mDescription = description; } + private String collectBundleStates() { + final StringWriter writer = new StringWriter(); + final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " "); + pw.println("Bundle stats:"); + Bundle.dumpStats(pw, mState); + pw.println("PersistableBundle stats:"); + Bundle.dumpStats(pw, mPersistentState); + return writer.toString().stripTrailing(); + } + @Override public void run() { // Tell activity manager we have been stopped. @@ -142,19 +153,24 @@ public class PendingTransactionActions { // TODO(lifecycler): Use interface callback instead of AMS. ActivityClient.getInstance().activityStopped( mActivity.token, mState, mPersistentState, mDescription); - } catch (RuntimeException ex) { - // Dump statistics about bundle to help developers debug - final LogWriter writer = new LogWriter(Log.WARN, TAG); - final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " "); - pw.println("Bundle stats:"); - Bundle.dumpStats(pw, mState); - pw.println("PersistableBundle stats:"); - Bundle.dumpStats(pw, mPersistentState); - - if (ex.getCause() instanceof TransactionTooLargeException - && mActivity.packageInfo.getTargetSdkVersion() < Build.VERSION_CODES.N) { - Log.e(TAG, "App sent too much data in instance state, so it was ignored", ex); - return; + } catch (RuntimeException runtimeException) { + // Collect the statistics about bundle + final String bundleStats = collectBundleStates(); + + RuntimeException ex = runtimeException; + if (ex.getCause() instanceof TransactionTooLargeException) { + // Embed the stats into exception message to help developers debug if the + // transaction size is too large. + final String message = ex.getMessage() + "\n" + bundleStats; + ex = new RuntimeException(message, ex.getCause()); + if (mActivity.packageInfo.getTargetSdkVersion() < Build.VERSION_CODES.N) { + Log.e(TAG, "App sent too much data in instance state, so it was ignored", + ex); + return; + } + } else { + // Otherwise, dump the stats anyway. + Log.w(TAG, bundleStats); } throw ex; } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 4522c0dcf719..8f4a836b6861 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -284,7 +284,7 @@ public final class ViewRootImpl implements ViewParent, * @hide */ public static final boolean LOCAL_LAYOUT = - SystemProperties.getBoolean("persist.debug.local_layout", false); + SystemProperties.getBoolean("persist.debug.local_layout", true); /** * Set this system property to true to force the view hierarchy to render diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 84280605862f..a9799f243cb7 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -701,7 +701,7 @@ <!-- Indicates the time needed to time out the fold animation if the device stops in half folded mode. --> - <integer name="config_unfoldTransitionHalfFoldedTimeout">600</integer> + <integer name="config_unfoldTransitionHalfFoldedTimeout">1000</integer> <!-- Indicates that the device supports having more than one internal display on at the same time. Only applicable to devices with more than one internal display. If this option is diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java index f9f3b4c8ead1..0b8b29b9dda9 100644 --- a/core/tests/coretests/src/android/app/NotificationTest.java +++ b/core/tests/coretests/src/android/app/NotificationTest.java @@ -59,6 +59,7 @@ import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.spy; import android.annotation.Nullable; +import android.app.Notification.CallStyle; import android.content.Context; import android.content.Intent; import android.content.LocusId; @@ -92,6 +93,7 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import java.util.List; import java.util.function.Consumer; @RunWith(AndroidJUnit4.class) @@ -531,6 +533,108 @@ public class NotificationTest { } @Test + public void testCallStyle_getSystemActions_forIncomingCall() { + PendingIntent answerIntent = createPendingIntent("answer"); + PendingIntent declineIntent = createPendingIntent("decline"); + Notification.CallStyle style = Notification.CallStyle.forIncomingCall( + new Person.Builder().setName("A Caller").build(), + declineIntent, + answerIntent + ); + style.setBuilder(new Notification.Builder(mContext, "Channel")); + + List<Notification.Action> actions = style.getActionsListWithSystemActions(); + + assertEquals(2, actions.size()); + assertEquals(declineIntent, actions.get(0).actionIntent); + assertEquals(answerIntent, actions.get(1).actionIntent); + } + + @Test + public void testCallStyle_getSystemActions_forOngoingCall() { + PendingIntent hangUpIntent = createPendingIntent("hangUp"); + Notification.CallStyle style = Notification.CallStyle.forOngoingCall( + new Person.Builder().setName("A Caller").build(), + hangUpIntent + ); + style.setBuilder(new Notification.Builder(mContext, "Channel")); + + List<Notification.Action> actions = style.getActionsListWithSystemActions(); + + assertEquals(1, actions.size()); + assertEquals(hangUpIntent, actions.get(0).actionIntent); + } + + @Test + public void testCallStyle_getSystemActions_forIncomingCallWithOtherActions() { + PendingIntent answerIntent = createPendingIntent("answer"); + PendingIntent declineIntent = createPendingIntent("decline"); + Notification.CallStyle style = Notification.CallStyle.forIncomingCall( + new Person.Builder().setName("A Caller").build(), + declineIntent, + answerIntent + ); + Notification.Action actionToKeep = makeNotificationAction(null); + Notification.Action actionToDrop = makeNotificationAction(null); + Notification.Builder notifBuilder = new Notification.Builder(mContext, "Channel") + .addAction(actionToKeep) + .addAction(actionToDrop); //expect to move this action to the end + style.setBuilder(notifBuilder); //add a builder with actions + + List<Notification.Action> actions = style.getActionsListWithSystemActions(); + + assertEquals(4, actions.size()); + assertEquals(declineIntent, actions.get(0).actionIntent); + assertEquals(actionToKeep, actions.get(1)); + assertEquals(answerIntent, actions.get(2).actionIntent); + assertEquals(actionToDrop, actions.get(3)); + } + + @Test + public void testCallStyle_getSystemActions_forOngoingCallWithOtherActions() { + PendingIntent hangUpIntent = createPendingIntent("hangUp"); + Notification.CallStyle style = Notification.CallStyle.forOngoingCall( + new Person.Builder().setName("A Caller").build(), + hangUpIntent + ); + Notification.Action firstAction = makeNotificationAction(null); + Notification.Action secondAction = makeNotificationAction(null); + Notification.Builder notifBuilder = new Notification.Builder(mContext, "Channel") + .addAction(firstAction) + .addAction(secondAction); + style.setBuilder(notifBuilder); //add a builder with actions + + List<Notification.Action> actions = style.getActionsListWithSystemActions(); + + assertEquals(3, actions.size()); + assertEquals(hangUpIntent, actions.get(0).actionIntent); + assertEquals(firstAction, actions.get(1)); + assertEquals(secondAction, actions.get(2)); + } + + @Test + public void testCallStyle_getSystemActions_dropsOldSystemActions() { + PendingIntent hangUpIntent = createPendingIntent("decline"); + Notification.CallStyle style = Notification.CallStyle.forOngoingCall( + new Person.Builder().setName("A Caller").build(), + hangUpIntent + ); + Bundle actionExtras = new Bundle(); + actionExtras.putBoolean("key_action_priority", true); + Notification.Action oldSystemAction = makeNotificationAction( + builder -> builder.addExtras(actionExtras) + ); + Notification.Builder notifBuilder = new Notification.Builder(mContext, "Channel") + .addAction(oldSystemAction); + style.setBuilder(notifBuilder); //add a builder with actions + + List<Notification.Action> actions = style.getActionsListWithSystemActions(); + + assertFalse("Old versions of system actions should be dropped.", + actions.contains(oldSystemAction)); + } + + @Test public void testBuild_ensureSmallIconIsNotTooBig_resizesIcon() { Icon hugeIcon = Icon.createWithBitmap( Bitmap.createBitmap(3000, 3000, Bitmap.Config.ARGB_8888)); @@ -788,7 +892,7 @@ public class NotificationTest { @Test public void testRestoreFromExtras_Call_invalidExtra_noCrash() { - Notification.Style style = new Notification.CallStyle(); + Notification.Style style = new CallStyle(); Bundle fakeTypes = new Bundle(); fakeTypes.putParcelable(EXTRA_CALL_PERSON, new Bundle()); fakeTypes.putParcelable(EXTRA_ANSWER_INTENT, new Bundle()); @@ -962,4 +1066,12 @@ public class NotificationTest { } return actionBuilder.build(); } + + /** + * Creates a PendingIntent with the given action. + */ + private PendingIntent createPendingIntent(String action) { + return PendingIntent.getActivity(mContext, 0, new Intent(action), + PendingIntent.FLAG_MUTABLE); + } } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java index fb0a9db6a20b..7e9c4189dabb 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java @@ -41,7 +41,7 @@ public class WindowExtensionsImpl implements WindowExtensions { // TODO(b/241126279) Introduce constants to better version functionality @Override public int getVendorApiLevel() { - return 1; + return 2; } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java index 591e3476ecd9..215308d9e96e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java @@ -130,6 +130,10 @@ class ActivityEmbeddingAnimationAdapter { if (!cropRect.intersect(mWholeAnimationBounds)) { // Hide the surface when it is outside of the animation area. t.setAlpha(mLeash, 0); + } else if (mAnimation.hasExtension()) { + // Allow the surface to be shown in its original bounds in case we want to use edge + // extensions. + cropRect.union(mChange.getEndAbsBounds()); } // cropRect is in absolute coordinate, so we need to translate it to surface top left. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java index 756d80204833..490975cce956 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java @@ -21,6 +21,7 @@ import static android.view.WindowManagerPolicyConstants.TYPE_LAYER_OFFSET; import static android.window.TransitionInfo.FLAG_IS_BEHIND_STARTING_WINDOW; import static com.android.wm.shell.transition.TransitionAnimationHelper.addBackgroundToTransition; +import static com.android.wm.shell.transition.TransitionAnimationHelper.edgeExtendWindow; import static com.android.wm.shell.transition.TransitionAnimationHelper.getTransitionBackgroundColorIfSet; import android.animation.Animator; @@ -45,6 +46,7 @@ import com.android.wm.shell.transition.Transitions; import java.util.ArrayList; import java.util.List; import java.util.Set; +import java.util.function.Consumer; /** To run the ActivityEmbedding animations. */ class ActivityEmbeddingAnimationRunner { @@ -65,10 +67,31 @@ class ActivityEmbeddingAnimationRunner { void startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction) { + // There may be some surface change that we want to apply after the start transaction is + // applied to make sure the surface is ready. + final List<Consumer<SurfaceControl.Transaction>> postStartTransactionCallbacks = + new ArrayList<>(); final Animator animator = createAnimator(info, startTransaction, finishTransaction, - () -> mController.onAnimationFinished(transition)); - startTransaction.apply(); - animator.start(); + () -> mController.onAnimationFinished(transition), postStartTransactionCallbacks); + + // Start the animation. + if (!postStartTransactionCallbacks.isEmpty()) { + // postStartTransactionCallbacks require that the start transaction is already + // applied to run otherwise they may result in flickers and UI inconsistencies. + startTransaction.apply(true /* sync */); + + // Run tasks that require startTransaction to already be applied + final SurfaceControl.Transaction t = new SurfaceControl.Transaction(); + for (Consumer<SurfaceControl.Transaction> postStartTransactionCallback : + postStartTransactionCallbacks) { + postStartTransactionCallback.accept(t); + } + t.apply(); + animator.start(); + } else { + startTransaction.apply(); + animator.start(); + } } /** @@ -85,9 +108,13 @@ class ActivityEmbeddingAnimationRunner { Animator createAnimator(@NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, - @NonNull Runnable animationFinishCallback) { - final List<ActivityEmbeddingAnimationAdapter> adapters = - createAnimationAdapters(info, startTransaction, finishTransaction); + @NonNull Runnable animationFinishCallback, + @NonNull List<Consumer<SurfaceControl.Transaction>> postStartTransactionCallbacks) { + final List<ActivityEmbeddingAnimationAdapter> adapters = createAnimationAdapters(info, + startTransaction); + addEdgeExtensionIfNeeded(startTransaction, finishTransaction, postStartTransactionCallbacks, + adapters); + addBackgroundColorIfNeeded(info, startTransaction, finishTransaction, adapters); long duration = 0; for (ActivityEmbeddingAnimationAdapter adapter : adapters) { duration = Math.max(duration, adapter.getDurationHint()); @@ -131,8 +158,7 @@ class ActivityEmbeddingAnimationRunner { */ @NonNull private List<ActivityEmbeddingAnimationAdapter> createAnimationAdapters( - @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, - @NonNull SurfaceControl.Transaction finishTransaction) { + @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction) { boolean isChangeTransition = false; for (TransitionInfo.Change change : info.getChanges()) { if (change.hasFlags(FLAG_IS_BEHIND_STARTING_WINDOW)) { @@ -148,25 +174,23 @@ class ActivityEmbeddingAnimationRunner { return createChangeAnimationAdapters(info, startTransaction); } if (Transitions.isClosingType(info.getType())) { - return createCloseAnimationAdapters(info, startTransaction, finishTransaction); + return createCloseAnimationAdapters(info); } - return createOpenAnimationAdapters(info, startTransaction, finishTransaction); + return createOpenAnimationAdapters(info); } @NonNull private List<ActivityEmbeddingAnimationAdapter> createOpenAnimationAdapters( - @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, - @NonNull SurfaceControl.Transaction finishTransaction) { - return createOpenCloseAnimationAdapters(info, startTransaction, finishTransaction, - true /* isOpening */, mAnimationSpec::loadOpenAnimation); + @NonNull TransitionInfo info) { + return createOpenCloseAnimationAdapters(info, true /* isOpening */, + mAnimationSpec::loadOpenAnimation); } @NonNull private List<ActivityEmbeddingAnimationAdapter> createCloseAnimationAdapters( - @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, - @NonNull SurfaceControl.Transaction finishTransaction) { - return createOpenCloseAnimationAdapters(info, startTransaction, finishTransaction, - false /* isOpening */, mAnimationSpec::loadCloseAnimation); + @NonNull TransitionInfo info) { + return createOpenCloseAnimationAdapters(info, false /* isOpening */, + mAnimationSpec::loadCloseAnimation); } /** @@ -175,8 +199,7 @@ class ActivityEmbeddingAnimationRunner { */ @NonNull private List<ActivityEmbeddingAnimationAdapter> createOpenCloseAnimationAdapters( - @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, - @NonNull SurfaceControl.Transaction finishTransaction, boolean isOpening, + @NonNull TransitionInfo info, boolean isOpening, @NonNull AnimationProvider animationProvider) { // We need to know if the change window is only a partial of the whole animation screen. // If so, we will need to adjust it to make the whole animation screen looks like one. @@ -200,8 +223,7 @@ class ActivityEmbeddingAnimationRunner { final List<ActivityEmbeddingAnimationAdapter> adapters = new ArrayList<>(); for (TransitionInfo.Change change : openingChanges) { final ActivityEmbeddingAnimationAdapter adapter = createOpenCloseAnimationAdapter( - info, change, startTransaction, finishTransaction, animationProvider, - openingWholeScreenBounds); + info, change, animationProvider, openingWholeScreenBounds); if (isOpening) { adapter.overrideLayer(offsetLayer++); } @@ -209,8 +231,7 @@ class ActivityEmbeddingAnimationRunner { } for (TransitionInfo.Change change : closingChanges) { final ActivityEmbeddingAnimationAdapter adapter = createOpenCloseAnimationAdapter( - info, change, startTransaction, finishTransaction, animationProvider, - closingWholeScreenBounds); + info, change, animationProvider, closingWholeScreenBounds); if (!isOpening) { adapter.overrideLayer(offsetLayer++); } @@ -219,20 +240,51 @@ class ActivityEmbeddingAnimationRunner { return adapters; } + /** Adds edge extension to the surfaces that have such an animation property. */ + private void addEdgeExtensionIfNeeded(@NonNull SurfaceControl.Transaction startTransaction, + @NonNull SurfaceControl.Transaction finishTransaction, + @NonNull List<Consumer<SurfaceControl.Transaction>> postStartTransactionCallbacks, + @NonNull List<ActivityEmbeddingAnimationAdapter> adapters) { + for (ActivityEmbeddingAnimationAdapter adapter : adapters) { + final Animation animation = adapter.mAnimation; + if (!animation.hasExtension()) { + continue; + } + final TransitionInfo.Change change = adapter.mChange; + if (Transitions.isOpeningType(adapter.mChange.getMode())) { + // Need to screenshot after startTransaction is applied otherwise activity + // may not be visible or ready yet. + postStartTransactionCallbacks.add( + t -> edgeExtendWindow(change, animation, t, finishTransaction)); + } else { + // Can screenshot now (before startTransaction is applied) + edgeExtendWindow(change, animation, startTransaction, finishTransaction); + } + } + } + + /** Adds background color to the transition if any animation has such a property. */ + private void addBackgroundColorIfNeeded(@NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction startTransaction, + @NonNull SurfaceControl.Transaction finishTransaction, + @NonNull List<ActivityEmbeddingAnimationAdapter> adapters) { + for (ActivityEmbeddingAnimationAdapter adapter : adapters) { + final int backgroundColor = getTransitionBackgroundColorIfSet(info, adapter.mChange, + adapter.mAnimation, 0 /* defaultColor */); + if (backgroundColor != 0) { + // We only need to show one color. + addBackgroundToTransition(info.getRootLeash(), backgroundColor, startTransaction, + finishTransaction); + return; + } + } + } + @NonNull private ActivityEmbeddingAnimationAdapter createOpenCloseAnimationAdapter( @NonNull TransitionInfo info, @NonNull TransitionInfo.Change change, - @NonNull SurfaceControl.Transaction startTransaction, - @NonNull SurfaceControl.Transaction finishTransaction, @NonNull AnimationProvider animationProvider, @NonNull Rect wholeAnimationBounds) { final Animation animation = animationProvider.get(info, change, wholeAnimationBounds); - // We may want to show a background color for open/close transition. - final int backgroundColor = getTransitionBackgroundColorIfSet(info, change, animation, - 0 /* defaultColor */); - if (backgroundColor != 0) { - addBackgroundToTransition(info.getRootLeash(), backgroundColor, startTransaction, - finishTransaction); - } return new ActivityEmbeddingAnimationAdapter(animation, change, change.getLeash(), wholeAnimationBounds); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java index eb6ac7615266..58b23667dc18 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java @@ -181,15 +181,15 @@ class ActivityEmbeddingAnimationSpec { @NonNull TransitionInfo.Change change, @NonNull Rect wholeAnimationBounds) { final boolean isEnter = Transitions.isOpeningType(change.getMode()); final Animation animation; - // TODO(b/207070762): Implement edgeExtension version if (shouldShowBackdrop(info, change)) { animation = mTransitionAnimation.loadDefaultAnimationRes(isEnter ? com.android.internal.R.anim.task_fragment_clear_top_open_enter : com.android.internal.R.anim.task_fragment_clear_top_open_exit); } else { + // Use the same edge extension animation as regular activity open. animation = mTransitionAnimation.loadDefaultAnimationRes(isEnter - ? com.android.internal.R.anim.task_fragment_open_enter - : com.android.internal.R.anim.task_fragment_open_exit); + ? com.android.internal.R.anim.activity_open_enter + : com.android.internal.R.anim.activity_open_exit); } // Use the whole animation bounds instead of the change bounds, so that when multiple change // targets are opening at the same time, the animation applied to each will be the same. @@ -205,15 +205,15 @@ class ActivityEmbeddingAnimationSpec { @NonNull TransitionInfo.Change change, @NonNull Rect wholeAnimationBounds) { final boolean isEnter = Transitions.isOpeningType(change.getMode()); final Animation animation; - // TODO(b/207070762): Implement edgeExtension version if (shouldShowBackdrop(info, change)) { animation = mTransitionAnimation.loadDefaultAnimationRes(isEnter ? com.android.internal.R.anim.task_fragment_clear_top_close_enter : com.android.internal.R.anim.task_fragment_clear_top_close_exit); } else { + // Use the same edge extension animation as regular activity close. animation = mTransitionAnimation.loadDefaultAnimationRes(isEnter - ? com.android.internal.R.anim.task_fragment_close_enter - : com.android.internal.R.anim.task_fragment_close_exit); + ? com.android.internal.R.anim.activity_close_enter + : com.android.internal.R.anim.activity_close_exit); } // Use the whole animation bounds instead of the change bounds, so that when multiple change // targets are closing at the same time, the animation applied to each will be the same. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java index dbb2948de5db..9c2c2fa8598a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java @@ -59,6 +59,7 @@ import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITI import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_NONE; import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_OPEN; import static com.android.wm.shell.transition.TransitionAnimationHelper.addBackgroundToTransition; +import static com.android.wm.shell.transition.TransitionAnimationHelper.edgeExtendWindow; import static com.android.wm.shell.transition.TransitionAnimationHelper.getTransitionBackgroundColorIfSet; import static com.android.wm.shell.transition.TransitionAnimationHelper.loadAttributeAnimation; import static com.android.wm.shell.transition.TransitionAnimationHelper.sDisableCustomTaskAnimationProperty; @@ -76,10 +77,7 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.graphics.Canvas; import android.graphics.Insets; -import android.graphics.Paint; -import android.graphics.PixelFormat; import android.graphics.Point; import android.graphics.Rect; import android.graphics.drawable.Drawable; @@ -89,7 +87,6 @@ import android.os.IBinder; import android.os.UserHandle; import android.util.ArrayMap; import android.view.Choreographer; -import android.view.Surface; import android.view.SurfaceControl; import android.view.SurfaceSession; import android.view.WindowManager; @@ -525,123 +522,6 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { } } - private void edgeExtendWindow(TransitionInfo.Change change, - Animation a, SurfaceControl.Transaction startTransaction, - SurfaceControl.Transaction finishTransaction) { - // Do not create edge extension surface for transfer starting window change. - // The app surface could be empty thus nothing can draw on the hardware renderer, which will - // block this thread when calling Surface#unlockCanvasAndPost. - if ((change.getFlags() & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) != 0) { - return; - } - final Transformation transformationAtStart = new Transformation(); - a.getTransformationAt(0, transformationAtStart); - final Transformation transformationAtEnd = new Transformation(); - a.getTransformationAt(1, transformationAtEnd); - - // We want to create an extension surface that is the maximal size and the animation will - // take care of cropping any part that overflows. - final Insets maxExtensionInsets = Insets.min( - transformationAtStart.getInsets(), transformationAtEnd.getInsets()); - - final int targetSurfaceHeight = Math.max(change.getStartAbsBounds().height(), - change.getEndAbsBounds().height()); - final int targetSurfaceWidth = Math.max(change.getStartAbsBounds().width(), - change.getEndAbsBounds().width()); - if (maxExtensionInsets.left < 0) { - final Rect edgeBounds = new Rect(0, 0, 1, targetSurfaceHeight); - final Rect extensionRect = new Rect(0, 0, - -maxExtensionInsets.left, targetSurfaceHeight); - final int xPos = maxExtensionInsets.left; - final int yPos = 0; - createExtensionSurface(change.getLeash(), edgeBounds, extensionRect, xPos, yPos, - "Left Edge Extension", startTransaction, finishTransaction); - } - - if (maxExtensionInsets.top < 0) { - final Rect edgeBounds = new Rect(0, 0, targetSurfaceWidth, 1); - final Rect extensionRect = new Rect(0, 0, - targetSurfaceWidth, -maxExtensionInsets.top); - final int xPos = 0; - final int yPos = maxExtensionInsets.top; - createExtensionSurface(change.getLeash(), edgeBounds, extensionRect, xPos, yPos, - "Top Edge Extension", startTransaction, finishTransaction); - } - - if (maxExtensionInsets.right < 0) { - final Rect edgeBounds = new Rect(targetSurfaceWidth - 1, 0, - targetSurfaceWidth, targetSurfaceHeight); - final Rect extensionRect = new Rect(0, 0, - -maxExtensionInsets.right, targetSurfaceHeight); - final int xPos = targetSurfaceWidth; - final int yPos = 0; - createExtensionSurface(change.getLeash(), edgeBounds, extensionRect, xPos, yPos, - "Right Edge Extension", startTransaction, finishTransaction); - } - - if (maxExtensionInsets.bottom < 0) { - final Rect edgeBounds = new Rect(0, targetSurfaceHeight - 1, - targetSurfaceWidth, targetSurfaceHeight); - final Rect extensionRect = new Rect(0, 0, - targetSurfaceWidth, -maxExtensionInsets.bottom); - final int xPos = maxExtensionInsets.left; - final int yPos = targetSurfaceHeight; - createExtensionSurface(change.getLeash(), edgeBounds, extensionRect, xPos, yPos, - "Bottom Edge Extension", startTransaction, finishTransaction); - } - } - - private SurfaceControl createExtensionSurface(SurfaceControl surfaceToExtend, Rect edgeBounds, - Rect extensionRect, int xPos, int yPos, String layerName, - SurfaceControl.Transaction startTransaction, - SurfaceControl.Transaction finishTransaction) { - final SurfaceControl edgeExtensionLayer = new SurfaceControl.Builder() - .setName(layerName) - .setParent(surfaceToExtend) - .setHidden(true) - .setCallsite("DefaultTransitionHandler#startAnimation") - .setOpaque(true) - .setBufferSize(extensionRect.width(), extensionRect.height()) - .build(); - - SurfaceControl.LayerCaptureArgs captureArgs = - new SurfaceControl.LayerCaptureArgs.Builder(surfaceToExtend) - .setSourceCrop(edgeBounds) - .setFrameScale(1) - .setPixelFormat(PixelFormat.RGBA_8888) - .setChildrenOnly(true) - .setAllowProtected(true) - .build(); - final SurfaceControl.ScreenshotHardwareBuffer edgeBuffer = - SurfaceControl.captureLayers(captureArgs); - - if (edgeBuffer == null) { - ProtoLog.e(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, - "Failed to capture edge of window."); - return null; - } - - android.graphics.BitmapShader shader = - new android.graphics.BitmapShader(edgeBuffer.asBitmap(), - android.graphics.Shader.TileMode.CLAMP, - android.graphics.Shader.TileMode.CLAMP); - final Paint paint = new Paint(); - paint.setShader(shader); - - final Surface surface = new Surface(edgeExtensionLayer); - Canvas c = surface.lockHardwareCanvas(); - c.drawRect(extensionRect, paint); - surface.unlockCanvasAndPost(c); - surface.release(); - - startTransaction.setLayer(edgeExtensionLayer, Integer.MIN_VALUE); - startTransaction.setPosition(edgeExtensionLayer, xPos, yPos); - startTransaction.setVisibility(edgeExtensionLayer, true); - finishTransaction.remove(edgeExtensionLayer); - - return edgeExtensionLayer; - } - @Nullable @Override public WindowContainerTransaction handleRequest(@NonNull IBinder transition, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java index efee6f40b53e..b75c55274cff 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java @@ -24,6 +24,7 @@ import static android.view.WindowManager.TRANSIT_OPEN; import static android.view.WindowManager.TRANSIT_TO_BACK; import static android.view.WindowManager.TRANSIT_TO_FRONT; import static android.view.WindowManager.transitTypeToString; +import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT; import static android.window.TransitionInfo.FLAG_TRANSLUCENT; import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_CLOSE; @@ -34,10 +35,19 @@ import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITI import android.annotation.ColorInt; import android.annotation.NonNull; import android.annotation.Nullable; +import android.graphics.BitmapShader; +import android.graphics.Canvas; import android.graphics.Color; +import android.graphics.Insets; +import android.graphics.Paint; +import android.graphics.PixelFormat; +import android.graphics.Rect; +import android.graphics.Shader; import android.os.SystemProperties; +import android.view.Surface; import android.view.SurfaceControl; import android.view.animation.Animation; +import android.view.animation.Transformation; import android.window.TransitionInfo; import com.android.internal.R; @@ -217,4 +227,126 @@ public class TransitionAnimationHelper { .show(animationBackgroundSurface); finishTransaction.remove(animationBackgroundSurface); } + + /** + * Adds edge extension surface to the given {@code change} for edge extension animation. + */ + public static void edgeExtendWindow(@NonNull TransitionInfo.Change change, + @NonNull Animation a, @NonNull SurfaceControl.Transaction startTransaction, + @NonNull SurfaceControl.Transaction finishTransaction) { + // Do not create edge extension surface for transfer starting window change. + // The app surface could be empty thus nothing can draw on the hardware renderer, which will + // block this thread when calling Surface#unlockCanvasAndPost. + if ((change.getFlags() & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) != 0) { + return; + } + final Transformation transformationAtStart = new Transformation(); + a.getTransformationAt(0, transformationAtStart); + final Transformation transformationAtEnd = new Transformation(); + a.getTransformationAt(1, transformationAtEnd); + + // We want to create an extension surface that is the maximal size and the animation will + // take care of cropping any part that overflows. + final Insets maxExtensionInsets = Insets.min( + transformationAtStart.getInsets(), transformationAtEnd.getInsets()); + + final int targetSurfaceHeight = Math.max(change.getStartAbsBounds().height(), + change.getEndAbsBounds().height()); + final int targetSurfaceWidth = Math.max(change.getStartAbsBounds().width(), + change.getEndAbsBounds().width()); + if (maxExtensionInsets.left < 0) { + final Rect edgeBounds = new Rect(0, 0, 1, targetSurfaceHeight); + final Rect extensionRect = new Rect(0, 0, + -maxExtensionInsets.left, targetSurfaceHeight); + final int xPos = maxExtensionInsets.left; + final int yPos = 0; + createExtensionSurface(change.getLeash(), edgeBounds, extensionRect, xPos, yPos, + "Left Edge Extension", startTransaction, finishTransaction); + } + + if (maxExtensionInsets.top < 0) { + final Rect edgeBounds = new Rect(0, 0, targetSurfaceWidth, 1); + final Rect extensionRect = new Rect(0, 0, + targetSurfaceWidth, -maxExtensionInsets.top); + final int xPos = 0; + final int yPos = maxExtensionInsets.top; + createExtensionSurface(change.getLeash(), edgeBounds, extensionRect, xPos, yPos, + "Top Edge Extension", startTransaction, finishTransaction); + } + + if (maxExtensionInsets.right < 0) { + final Rect edgeBounds = new Rect(targetSurfaceWidth - 1, 0, + targetSurfaceWidth, targetSurfaceHeight); + final Rect extensionRect = new Rect(0, 0, + -maxExtensionInsets.right, targetSurfaceHeight); + final int xPos = targetSurfaceWidth; + final int yPos = 0; + createExtensionSurface(change.getLeash(), edgeBounds, extensionRect, xPos, yPos, + "Right Edge Extension", startTransaction, finishTransaction); + } + + if (maxExtensionInsets.bottom < 0) { + final Rect edgeBounds = new Rect(0, targetSurfaceHeight - 1, + targetSurfaceWidth, targetSurfaceHeight); + final Rect extensionRect = new Rect(0, 0, + targetSurfaceWidth, -maxExtensionInsets.bottom); + final int xPos = maxExtensionInsets.left; + final int yPos = targetSurfaceHeight; + createExtensionSurface(change.getLeash(), edgeBounds, extensionRect, xPos, yPos, + "Bottom Edge Extension", startTransaction, finishTransaction); + } + } + + /** + * Takes a screenshot of {@code surfaceToExtend}'s edge and extends it for edge extension + * animation. + */ + private static SurfaceControl createExtensionSurface(@NonNull SurfaceControl surfaceToExtend, + @NonNull Rect edgeBounds, @NonNull Rect extensionRect, int xPos, int yPos, + @NonNull String layerName, @NonNull SurfaceControl.Transaction startTransaction, + @NonNull SurfaceControl.Transaction finishTransaction) { + final SurfaceControl edgeExtensionLayer = new SurfaceControl.Builder() + .setName(layerName) + .setParent(surfaceToExtend) + .setHidden(true) + .setCallsite("TransitionAnimationHelper#createExtensionSurface") + .setOpaque(true) + .setBufferSize(extensionRect.width(), extensionRect.height()) + .build(); + + final SurfaceControl.LayerCaptureArgs captureArgs = + new SurfaceControl.LayerCaptureArgs.Builder(surfaceToExtend) + .setSourceCrop(edgeBounds) + .setFrameScale(1) + .setPixelFormat(PixelFormat.RGBA_8888) + .setChildrenOnly(true) + .setAllowProtected(true) + .build(); + final SurfaceControl.ScreenshotHardwareBuffer edgeBuffer = + SurfaceControl.captureLayers(captureArgs); + + if (edgeBuffer == null) { + ProtoLog.e(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, + "Failed to capture edge of window."); + return null; + } + + final BitmapShader shader = new BitmapShader(edgeBuffer.asBitmap(), + Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); + final Paint paint = new Paint(); + paint.setShader(shader); + + final Surface surface = new Surface(edgeExtensionLayer); + final Canvas c = surface.lockHardwareCanvas(); + c.drawRect(extensionRect, paint); + surface.unlockCanvasAndPost(c); + surface.release(); + + startTransaction.setLayer(edgeExtensionLayer, Integer.MIN_VALUE); + startTransaction.setPosition(edgeExtensionLayer, xPos, yPos); + startTransaction.setVisibility(edgeExtensionLayer, true); + finishTransaction.remove(edgeExtensionLayer); + + return edgeExtensionLayer; + } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java index 98b59126227c..79070b1469be 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java @@ -40,6 +40,8 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; +import java.util.ArrayList; + /** * Tests for {@link ActivityEmbeddingAnimationRunner}. * @@ -62,13 +64,13 @@ public class ActivityEmbeddingAnimationRunnerTests extends ActivityEmbeddingAnim final TransitionInfo.Change embeddingChange = createChange(); embeddingChange.setFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY); info.addChange(embeddingChange); - doReturn(mAnimator).when(mAnimRunner).createAnimator(any(), any(), any(), any()); + doReturn(mAnimator).when(mAnimRunner).createAnimator(any(), any(), any(), any(), any()); mAnimRunner.startAnimation(mTransition, info, mStartTransaction, mFinishTransaction); final ArgumentCaptor<Runnable> finishCallback = ArgumentCaptor.forClass(Runnable.class); verify(mAnimRunner).createAnimator(eq(info), eq(mStartTransaction), eq(mFinishTransaction), - finishCallback.capture()); + finishCallback.capture(), any()); verify(mStartTransaction).apply(); verify(mAnimator).start(); verifyNoMoreInteractions(mFinishTransaction); @@ -88,7 +90,8 @@ public class ActivityEmbeddingAnimationRunnerTests extends ActivityEmbeddingAnim info.addChange(embeddingChange); final Animator animator = mAnimRunner.createAnimator( info, mStartTransaction, mFinishTransaction, - () -> mFinishCallback.onTransitionFinished(null /* wct */, null /* wctCB */)); + () -> mFinishCallback.onTransitionFinished(null /* wct */, null /* wctCB */), + new ArrayList()); // The animation should be empty when it is behind starting window. assertEquals(0, animator.getDuration()); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationTestBase.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationTestBase.java index 3792e8361284..54a12ab999c5 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationTestBase.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationTestBase.java @@ -56,13 +56,12 @@ abstract class ActivityEmbeddingAnimationTestBase extends ShellTestCase { @Mock SurfaceControl.Transaction mFinishTransaction; @Mock - Transitions.TransitionFinishCallback mFinishCallback; - @Mock Animator mAnimator; ActivityEmbeddingController mController; ActivityEmbeddingAnimationRunner mAnimRunner; ActivityEmbeddingAnimationSpec mAnimSpec; + Transitions.TransitionFinishCallback mFinishCallback; @CallSuper @Before @@ -75,9 +74,11 @@ abstract class ActivityEmbeddingAnimationTestBase extends ShellTestCase { assertNotNull(mAnimRunner); mAnimSpec = mAnimRunner.mAnimationSpec; assertNotNull(mAnimSpec); + mFinishCallback = (wct, wctCB) -> {}; spyOn(mController); spyOn(mAnimRunner); spyOn(mAnimSpec); + spyOn(mFinishCallback); } /** Creates a mock {@link TransitionInfo.Change}. */ diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java index baecf6fe6673..4d98b6ba4f7a 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java @@ -55,7 +55,7 @@ public class ActivityEmbeddingControllerTests extends ActivityEmbeddingAnimation @Before public void setup() { super.setUp(); - doReturn(mAnimator).when(mAnimRunner).createAnimator(any(), any(), any(), any()); + doReturn(mAnimator).when(mAnimRunner).createAnimator(any(), any(), any(), any(), any()); } @Test diff --git a/packages/CompanionDeviceManager/res/layout/list_item_device.xml b/packages/CompanionDeviceManager/res/layout/list_item_device.xml index db54ae3aeee9..d4439f9e7e64 100644 --- a/packages/CompanionDeviceManager/res/layout/list_item_device.xml +++ b/packages/CompanionDeviceManager/res/layout/list_item_device.xml @@ -39,7 +39,6 @@ android:layout_height="wrap_content" android:paddingStart="24dp" android:paddingEnd="24dp" - android:singleLine="true" android:textAppearance="?android:attr/textAppearanceListItemSmall"/> -</LinearLayout>
\ No newline at end of file +</LinearLayout> diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 2737ecf5ffa6..b5145f926abd 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -402,6 +402,9 @@ android:permission="com.android.systemui.permission.SELF" android:exported="false" /> + <service android:name=".screenshot.ScreenshotCrossProfileService" + android:permission="com.android.systemui.permission.SELF" + android:exported="false" /> <service android:name=".screenrecord.RecordingService" /> diff --git a/packages/SystemUI/OWNERS b/packages/SystemUI/OWNERS index a65f9be3e83f..6d61fd86e39d 100644 --- a/packages/SystemUI/OWNERS +++ b/packages/SystemUI/OWNERS @@ -5,23 +5,32 @@ set noparent dsandler@android.com aaliomer@google.com +aaronjli@google.com +acul@google.com adamcohen@google.com +aioana@google.com alexflo@google.com +andonian@google.com +aroederer@google.com arteiro@google.com asc@google.com awickham@google.com +ayepin@google.com +bbade@google.com beverlyt@google.com -brockman@google.com -brzezinski@google.com +bhinegardner@google.com +bhnm@google.com brycelee@google.com +brzezinski@google.com caitlinshk@google.com +chandruis@google.com chrisgollner@google.com cinek@google.com -cwren@google.com dupin@google.com ethibodeau@google.com evanlaird@google.com florenceyang@google.com +gallmann@google.com gwasserman@google.com hwwang@google.com hyunyoungs@google.com @@ -37,35 +46,42 @@ jonmiranda@google.com joshtrask@google.com juliacr@google.com juliatuttle@google.com -kchyn@google.com +justinkoh@google.com +justinweir@google.com kozynski@google.com kprevas@google.com +lusilva@google.com lynhan@google.com madym@google.com mankoff@google.com -mett@google.com +mateuszc@google.com +michaelmikhil@google.com +michschn@google.com mkephart@google.com mpietal@google.com mrcasey@google.com mrenouf@google.com -nesciosquid@google.com nickchameyev@google.com nicomazz@google.com +nijamkin@google.com ogunwale@google.com +omarmt@google.com +patmanning@google.com peanutbutter@google.com peskal@google.com pinyaoting@google.com pixel@google.com pomini@google.com rahulbanerjee@google.com +rasheedlewis@google.com roosa@google.com +saff@google.com santie@google.com shanh@google.com snoeberger@google.com -sreyasr@google.com steell@google.com -sfufa@google.com stwu@google.com +syeonlee@google.com sunnygoyal@google.com susikp@google.com thiruram@google.com @@ -75,13 +91,14 @@ twickham@google.com vadimt@google.com victortulias@google.com winsonc@google.com +wleshner@google.com +xilei@google.com xuqiu@google.com +yeinj@google.com yuandizhou@google.com yurilin@google.com zakcohen@google.com - -#Android Auto -hseog@google.com +zoepage@google.com #Android TV rgl@google.com diff --git a/packages/SystemUI/res-keyguard/values/strings.xml b/packages/SystemUI/res-keyguard/values/strings.xml index d90156d451c7..8135aaa6faea 100644 --- a/packages/SystemUI/res-keyguard/values/strings.xml +++ b/packages/SystemUI/res-keyguard/values/strings.xml @@ -241,4 +241,6 @@ <string name="clock_title_bubble">Bubble</string> <!-- Name of the "Analog" clock face [CHAR LIMIT=15]--> <string name="clock_title_analog">Analog</string> + <!-- Title of bouncer when we want to authenticate before continuing with action. [CHAR LIMIT=NONE] --> + <string name="keyguard_unlock_to_continue">Unlock your device to continue</string> </resources> diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardBiometricLockoutLogger.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardBiometricLockoutLogger.kt index 692fe83ee2b8..e6a2bfa1af12 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardBiometricLockoutLogger.kt +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardBiometricLockoutLogger.kt @@ -17,7 +17,6 @@ package com.android.keyguard import android.app.StatusBarManager.SESSION_KEYGUARD -import android.content.Context import android.hardware.biometrics.BiometricSourceType import com.android.internal.annotations.VisibleForTesting import com.android.internal.logging.UiEvent @@ -41,11 +40,10 @@ import javax.inject.Inject */ @SysUISingleton class KeyguardBiometricLockoutLogger @Inject constructor( - context: Context?, private val uiEventLogger: UiEventLogger, private val keyguardUpdateMonitor: KeyguardUpdateMonitor, private val sessionTracker: SessionTracker -) : CoreStartable(context) { +) : CoreStartable { private var fingerprintLockedOut = false private var faceLockedOut = false private var encryptedOrLockdown = false @@ -169,4 +167,4 @@ class KeyguardBiometricLockoutLogger @Inject constructor( return strongAuthFlags and flagCheck != 0 } } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java index c34db1532d6c..93ee151f26c5 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java @@ -67,7 +67,6 @@ import android.view.ViewGroup; import android.view.WindowInsets; import android.view.WindowInsetsAnimation; import android.view.WindowManager; -import android.widget.AdapterView; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.TextView; @@ -318,7 +317,8 @@ public class KeyguardSecurityContainer extends ConstraintLayout { } void initMode(@Mode int mode, GlobalSettings globalSettings, FalsingManager falsingManager, - UserSwitcherController userSwitcherController) { + UserSwitcherController userSwitcherController, + UserSwitcherViewMode.UserSwitcherCallback userSwitcherCallback) { if (mCurrentMode == mode) return; Log.i(TAG, "Switching mode from " + modeToString(mCurrentMode) + " to " + modeToString(mode)); @@ -330,7 +330,7 @@ public class KeyguardSecurityContainer extends ConstraintLayout { mViewMode = new OneHandedViewMode(); break; case MODE_USER_SWITCHER: - mViewMode = new UserSwitcherViewMode(); + mViewMode = new UserSwitcherViewMode(userSwitcherCallback); break; default: mViewMode = new DefaultViewMode(); @@ -864,6 +864,12 @@ public class KeyguardSecurityContainer extends ConstraintLayout { private UserSwitcherController.UserSwitchCallback mUserSwitchCallback = this::setupUserSwitcher; + private UserSwitcherCallback mUserSwitcherCallback; + + UserSwitcherViewMode(UserSwitcherCallback userSwitcherCallback) { + mUserSwitcherCallback = userSwitcherCallback; + } + @Override public void init(@NonNull ConstraintLayout v, @NonNull GlobalSettings globalSettings, @NonNull KeyguardSecurityViewFlipper viewFlipper, @@ -1040,34 +1046,25 @@ public class KeyguardSecurityContainer extends ConstraintLayout { } }; - if (adapter.getCount() < 2) { - // The drop down arrow is at index 1 - ((LayerDrawable) mUserSwitcher.getBackground()).getDrawable(1).setAlpha(0); - anchor.setClickable(false); - return; - } else { - ((LayerDrawable) mUserSwitcher.getBackground()).getDrawable(1).setAlpha(255); - } - anchor.setOnClickListener((v) -> { if (mFalsingManager.isFalseTap(LOW_PENALTY)) return; mPopup = new KeyguardUserSwitcherPopupMenu(v.getContext(), mFalsingManager); mPopup.setAnchorView(anchor); mPopup.setAdapter(adapter); - mPopup.setOnItemClickListener(new AdapterView.OnItemClickListener() { - public void onItemClick(AdapterView parent, View view, int pos, long id) { - if (mFalsingManager.isFalseTap(LOW_PENALTY)) return; - if (!view.isEnabled()) return; - - // Subtract one for the header - UserRecord user = adapter.getItem(pos - 1); - if (!user.isCurrent) { - adapter.onUserListItemClicked(user); - } - mPopup.dismiss(); - mPopup = null; - } - }); + mPopup.setOnItemClickListener((parent, view, pos, id) -> { + if (mFalsingManager.isFalseTap(LOW_PENALTY)) return; + if (!view.isEnabled()) return; + // Subtract one for the header + UserRecord user = adapter.getItem(pos - 1); + if (user.isManageUsers || user.isAddSupervisedUser) { + mUserSwitcherCallback.showUnlockToContinueMessage(); + } + if (!user.isCurrent) { + adapter.onUserListItemClicked(user); + } + mPopup.dismiss(); + mPopup = null; + }); mPopup.show(); }); } @@ -1122,6 +1119,10 @@ public class KeyguardSecurityContainer extends ConstraintLayout { constraintSet.applyTo(mView); } } + + interface UserSwitcherCallback { + void showUnlockToContinueMessage(); + } } /** diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java index d448f40ed529..bcd1a1ee2696 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java @@ -620,7 +620,9 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard mode = KeyguardSecurityContainer.MODE_ONE_HANDED; } - mView.initMode(mode, mGlobalSettings, mFalsingManager, mUserSwitcherController); + mView.initMode(mode, mGlobalSettings, mFalsingManager, mUserSwitcherController, + () -> showMessage(getContext().getString(R.string.keyguard_unlock_to_continue), + null)); } public void reportFailedUnlockAttempt(int userId, int timeoutMs) { diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewTransition.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewTransition.kt index 9eb2c118abac..c9128e5881cc 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewTransition.kt +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewTransition.kt @@ -109,12 +109,13 @@ class KeyguardSecurityViewTransition : Transition() { object : AnimatorListenerAdapter() { override fun onAnimationEnd(animation: Animator) { runningSecurityShiftAnimator = null + if (shouldRestoreLayerType) { + v.setLayerType(View.LAYER_TYPE_NONE, /* paint= */ null) + } } } ) - var finishedFadingOutNonSecurityView = false - runningSecurityShiftAnimator.addUpdateListener { animation: ValueAnimator -> val switchPoint = SECURITY_SHIFT_ANIMATION_FADE_OUT_PROPORTION val isFadingOut = animation.animatedFraction < switchPoint @@ -153,6 +154,13 @@ class KeyguardSecurityViewTransition : Transition() { startRect.right + currentTranslation, startRect.bottom ) + } else { + v.setLeftTopRightBottom( + startRect.left, + startRect.top, + startRect.right, + startRect.bottom + ) } } else { // And in again over the remaining (100-X)%. @@ -175,32 +183,13 @@ class KeyguardSecurityViewTransition : Transition() { endRect.right - translationRemaining, endRect.bottom ) - } - } - if (animation.animatedFraction == 1.0f && shouldRestoreLayerType) { - v.setLayerType(View.LAYER_TYPE_NONE, /* paint= */ null) - } - - // For views that are not the security view flipper, we do not want to apply - // an x translation animation. Instead, we want to fade out, move to final position and - // then fade in. - if (v !is KeyguardSecurityViewFlipper) { - // Opacity goes close to 0 but does not fully get to 0. - if (opacity - 0.001f < 0f) { + } else { v.setLeftTopRightBottom( endRect.left, endRect.top, endRect.right, endRect.bottom ) - finishedFadingOutNonSecurityView = true - } else if (!finishedFadingOutNonSecurityView) { - v.setLeftTopRightBottom( - startRect.left, - startRect.top, - startRect.right, - startRect.bottom - ) } } } diff --git a/packages/SystemUI/src/com/android/systemui/ChooserSelector.kt b/packages/SystemUI/src/com/android/systemui/ChooserSelector.kt index 37829f25d179..a89cbf57f95b 100644 --- a/packages/SystemUI/src/com/android/systemui/ChooserSelector.kt +++ b/packages/SystemUI/src/com/android/systemui/ChooserSelector.kt @@ -19,11 +19,11 @@ import kotlinx.coroutines.withContext @SysUISingleton class ChooserSelector @Inject constructor( - context: Context, + private val context: Context, private val featureFlags: FeatureFlags, @Application private val coroutineScope: CoroutineScope, @Background private val bgDispatcher: CoroutineDispatcher -) : CoreStartable(context) { +) : CoreStartable { private val packageManager = context.packageManager private val chooserComponent = ComponentName.unflattenFromString( diff --git a/packages/SystemUI/src/com/android/systemui/CoreStartable.java b/packages/SystemUI/src/com/android/systemui/CoreStartable.java index 0201cdc25319..929ebea37eef 100644 --- a/packages/SystemUI/src/com/android/systemui/CoreStartable.java +++ b/packages/SystemUI/src/com/android/systemui/CoreStartable.java @@ -16,39 +16,41 @@ package com.android.systemui; -import android.content.Context; import android.content.res.Configuration; import androidx.annotation.NonNull; -import com.android.internal.annotations.VisibleForTesting; - import java.io.PrintWriter; /** - * A top-level module of system UI code (sometimes called "system UI services" elsewhere in code). - * Which CoreStartable modules are loaded can be controlled via a config resource. + * Code that needs to be run when SystemUI is started. + * + * Which CoreStartable modules are loaded is controlled via the dagger graph. Bind them into the + * CoreStartable map with code such as: + * + * <pre> + * @Binds + * @IntoMap + * @ClassKey(FoobarStartable::class) + * abstract fun bind(impl: FoobarStartable): CoreStartable + * </pre> * * @see SystemUIApplication#startServicesIfNeeded() */ -public abstract class CoreStartable implements Dumpable { - protected final Context mContext; - - public CoreStartable(Context context) { - mContext = context; - } +public interface CoreStartable extends Dumpable { /** Main entry point for implementations. Called shortly after app startup. */ - public abstract void start(); + void start(); - protected void onConfigurationChanged(Configuration newConfig) { + /** */ + default void onConfigurationChanged(Configuration newConfig) { } @Override - public void dump(@NonNull PrintWriter pw, @NonNull String[] args) { + default void dump(@NonNull PrintWriter pw, @NonNull String[] args) { } - @VisibleForTesting - protected void onBootCompleted() { + /** Called when the device reports BOOT_COMPLETED. */ + default void onBootCompleted() { } } diff --git a/packages/SystemUI/src/com/android/systemui/LatencyTester.java b/packages/SystemUI/src/com/android/systemui/LatencyTester.java index 9cdce6400e56..8f419560c78d 100644 --- a/packages/SystemUI/src/com/android/systemui/LatencyTester.java +++ b/packages/SystemUI/src/com/android/systemui/LatencyTester.java @@ -46,7 +46,7 @@ import javax.inject.Inject; * system that are used for testing the latency. */ @SysUISingleton -public class LatencyTester extends CoreStartable { +public class LatencyTester implements CoreStartable { private static final boolean DEFAULT_ENABLED = Build.IS_ENG; private static final String ACTION_FINGERPRINT_WAKE = @@ -62,13 +62,11 @@ public class LatencyTester extends CoreStartable { @Inject public LatencyTester( - Context context, BiometricUnlockController biometricUnlockController, BroadcastDispatcher broadcastDispatcher, DeviceConfigProxy deviceConfigProxy, @Main DelayableExecutor mainExecutor ) { - super(context); mBiometricUnlockController = biometricUnlockController; mBroadcastDispatcher = broadcastDispatcher; mDeviceConfigProxy = deviceConfigProxy; diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java index 2e13903814a5..b5f42a164495 100644 --- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java +++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java @@ -105,7 +105,7 @@ import kotlin.Pair; * for antialiasing and emulation purposes. */ @SysUISingleton -public class ScreenDecorations extends CoreStartable implements Tunable , Dumpable { +public class ScreenDecorations implements CoreStartable, Tunable , Dumpable { private static final boolean DEBUG = false; private static final String TAG = "ScreenDecorations"; @@ -130,6 +130,7 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab @VisibleForTesting protected boolean mIsRegistered; private final BroadcastDispatcher mBroadcastDispatcher; + private final Context mContext; private final Executor mMainExecutor; private final TunerService mTunerService; private final SecureSettings mSecureSettings; @@ -308,7 +309,7 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab ThreadFactory threadFactory, PrivacyDotDecorProviderFactory dotFactory, FaceScanningProviderFactory faceScanningFactory) { - super(context); + mContext = context; mMainExecutor = mainExecutor; mSecureSettings = secureSettings; mBroadcastDispatcher = broadcastDispatcher; @@ -973,7 +974,7 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab } @Override - protected void onConfigurationChanged(Configuration newConfig) { + public void onConfigurationChanged(Configuration newConfig) { if (DEBUG_DISABLE_SCREEN_DECORATIONS) { Log.i(TAG, "ScreenDecorations is disabled"); return; diff --git a/packages/SystemUI/src/com/android/systemui/SliceBroadcastRelayHandler.java b/packages/SystemUI/src/com/android/systemui/SliceBroadcastRelayHandler.java index 1f2de4cfc346..5bd85a72b06f 100644 --- a/packages/SystemUI/src/com/android/systemui/SliceBroadcastRelayHandler.java +++ b/packages/SystemUI/src/com/android/systemui/SliceBroadcastRelayHandler.java @@ -38,16 +38,17 @@ import javax.inject.Inject; * @see SliceBroadcastRelay */ @SysUISingleton -public class SliceBroadcastRelayHandler extends CoreStartable { +public class SliceBroadcastRelayHandler implements CoreStartable { private static final String TAG = "SliceBroadcastRelay"; private static final boolean DEBUG = false; private final ArrayMap<Uri, BroadcastRelay> mRelays = new ArrayMap<>(); + private final Context mContext; private final BroadcastDispatcher mBroadcastDispatcher; @Inject public SliceBroadcastRelayHandler(Context context, BroadcastDispatcher broadcastDispatcher) { - super(context); + mContext = context; mBroadcastDispatcher = broadcastDispatcher; } diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java index 9138b2346ab8..8415ef88f6a0 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java @@ -47,8 +47,6 @@ import com.android.systemui.dagger.SysUIComponent; import com.android.systemui.dump.DumpManager; import com.android.systemui.util.NotificationChannels; -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; import java.util.Comparator; import java.util.Map; import java.util.TreeMap; @@ -296,14 +294,10 @@ public class SystemUIApplication extends Application implements CoreStartable startable; if (DEBUG) Log.d(TAG, "loading: " + clsName); try { - Constructor<?> constructor = Class.forName(clsName).getConstructor( - Context.class); - startable = (CoreStartable) constructor.newInstance(this); + startable = (CoreStartable) Class.forName(clsName).newInstance(); } catch (ClassNotFoundException - | NoSuchMethodException | IllegalAccessException - | InstantiationException - | InvocationTargetException ex) { + | InstantiationException ex) { throw new RuntimeException(ex); } diff --git a/packages/SystemUI/src/com/android/systemui/VendorServices.java b/packages/SystemUI/src/com/android/systemui/VendorServices.java index 139448c0fab4..a3209396ac27 100644 --- a/packages/SystemUI/src/com/android/systemui/VendorServices.java +++ b/packages/SystemUI/src/com/android/systemui/VendorServices.java @@ -16,15 +16,12 @@ package com.android.systemui; -import android.content.Context; - /** * Placeholder for any vendor-specific services. */ -public class VendorServices extends CoreStartable { +public class VendorServices implements CoreStartable { - public VendorServices(Context context) { - super(context); + public VendorServices() { } @Override diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java index a1288b531955..9f1c9b45e6cd 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java @@ -69,7 +69,7 @@ import dagger.Lazy; * Class to register system actions with accessibility framework. */ @SysUISingleton -public class SystemActions extends CoreStartable { +public class SystemActions implements CoreStartable { private static final String TAG = "SystemActions"; /** @@ -177,6 +177,7 @@ public class SystemActions extends CoreStartable { private static final String PERMISSION_SELF = "com.android.systemui.permission.SELF"; private final SystemActionsBroadcastReceiver mReceiver; + private final Context mContext; private final Optional<Recents> mRecentsOptional; private Locale mLocale; private final AccessibilityManager mA11yManager; @@ -190,7 +191,7 @@ public class SystemActions extends CoreStartable { NotificationShadeWindowController notificationShadeController, Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy, Optional<Recents> recentsOptional) { - super(context); + mContext = context; mRecentsOptional = recentsOptional; mReceiver = new SystemActionsBroadcastReceiver(); mLocale = mContext.getResources().getConfiguration().getLocales().get(0); @@ -219,7 +220,6 @@ public class SystemActions extends CoreStartable { @Override public void onConfigurationChanged(Configuration newConfig) { - super.onConfigurationChanged(newConfig); final Locale locale = mContext.getResources().getConfiguration().getLocales().get(0); if (!locale.equals(mLocale)) { mLocale = locale; diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java index 6e14ddc671a3..ab11fcea7b28 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java @@ -52,11 +52,12 @@ import javax.inject.Inject; * when {@code IStatusBar#requestWindowMagnificationConnection(boolean)} is called. */ @SysUISingleton -public class WindowMagnification extends CoreStartable implements WindowMagnifierCallback, +public class WindowMagnification implements CoreStartable, WindowMagnifierCallback, CommandQueue.Callbacks { private static final String TAG = "WindowMagnification"; private final ModeSwitchesController mModeSwitchesController; + private final Context mContext; private final Handler mHandler; private final AccessibilityManager mAccessibilityManager; private final CommandQueue mCommandQueue; @@ -102,7 +103,7 @@ public class WindowMagnification extends CoreStartable implements WindowMagnifie public WindowMagnification(Context context, @Main Handler mainHandler, CommandQueue commandQueue, ModeSwitchesController modeSwitchesController, SysUiState sysUiState, OverviewProxyService overviewProxyService) { - super(context); + mContext = context; mHandler = mainHandler; mAccessibilityManager = mContext.getSystemService(AccessibilityManager.class); mCommandQueue = commandQueue; diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java index aae92adc5880..d43e5d9d09cc 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java @@ -102,7 +102,7 @@ import kotlin.Unit; * {@link com.android.keyguard.KeyguardUpdateMonitor} */ @SysUISingleton -public class AuthController extends CoreStartable implements CommandQueue.Callbacks, +public class AuthController implements CoreStartable, CommandQueue.Callbacks, AuthDialogCallback, DozeReceiver { private static final String TAG = "AuthController"; @@ -110,6 +110,7 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba private static final int SENSOR_PRIVACY_DELAY = 500; private final Handler mHandler; + private final Context mContext; private final Execution mExecution; private final CommandQueue mCommandQueue; private final StatusBarStateController mStatusBarStateController; @@ -669,7 +670,7 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba @NonNull InteractionJankMonitor jankMonitor, @Main Handler handler, @Background DelayableExecutor bgExecutor) { - super(context); + mContext = context; mExecution = execution; mUserManager = userManager; mLockPatternUtils = lockPatternUtils; @@ -1099,8 +1100,7 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba } @Override - protected void onConfigurationChanged(Configuration newConfig) { - super.onConfigurationChanged(newConfig); + public void onConfigurationChanged(Configuration newConfig) { updateSensorLocations(); // Save the state of the current dialog (buttons showing, etc) diff --git a/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcherStartable.kt b/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcherStartable.kt index d7b263a323ca..c536e81ef20a 100644 --- a/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcherStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcherStartable.kt @@ -16,16 +16,14 @@ package com.android.systemui.broadcast -import android.content.Context import com.android.systemui.CoreStartable import javax.inject.Inject class BroadcastDispatcherStartable @Inject constructor( - context: Context, val broadcastDispatcher: BroadcastDispatcher -) : CoreStartable(context) { +) : CoreStartable { override fun start() { broadcastDispatcher.initialize() } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java index f526277a0a37..05e3f1ce87a6 100644 --- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java +++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java @@ -39,8 +39,8 @@ import javax.inject.Inject; * ClipboardListener brings up a clipboard overlay when something is copied to the clipboard. */ @SysUISingleton -public class ClipboardListener extends CoreStartable - implements ClipboardManager.OnPrimaryClipChangedListener { +public class ClipboardListener implements + CoreStartable, ClipboardManager.OnPrimaryClipChangedListener { private static final String TAG = "ClipboardListener"; @VisibleForTesting @@ -49,6 +49,7 @@ public class ClipboardListener extends CoreStartable static final String EXTRA_SUPPRESS_OVERLAY = "com.android.systemui.SUPPRESS_CLIPBOARD_OVERLAY"; + private final Context mContext; private final DeviceConfigProxy mDeviceConfig; private final ClipboardOverlayControllerFactory mOverlayFactory; private final ClipboardManager mClipboardManager; @@ -59,7 +60,7 @@ public class ClipboardListener extends CoreStartable public ClipboardListener(Context context, DeviceConfigProxy deviceConfigProxy, ClipboardOverlayControllerFactory overlayFactory, ClipboardManager clipboardManager, UiEventLogger uiEventLogger) { - super(context); + mContext = context; mDeviceConfig = deviceConfigProxy; mOverlayFactory = overlayFactory; mClipboardManager = clipboardManager; diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayRegistrant.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayRegistrant.java index 99ca3c76cf8d..d145f5c14917 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayRegistrant.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayRegistrant.java @@ -40,11 +40,12 @@ import javax.inject.Inject; * {@link DreamOverlayRegistrant} is responsible for telling system server that SystemUI should be * the designated dream overlay component. */ -public class DreamOverlayRegistrant extends CoreStartable { +public class DreamOverlayRegistrant implements CoreStartable { private static final String TAG = "DreamOverlayRegistrant"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private final IDreamManager mDreamManager; private final ComponentName mOverlayServiceComponent; + private final Context mContext; private final Resources mResources; private boolean mCurrentRegisteredState = false; @@ -98,7 +99,7 @@ public class DreamOverlayRegistrant extends CoreStartable { @Inject public DreamOverlayRegistrant(Context context, @Main Resources resources) { - super(context); + mContext = context; mResources = resources; mDreamManager = IDreamManager.Stub.asInterface( ServiceManager.getService(DreamService.DREAM_SERVICE)); diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationTypesUpdater.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationTypesUpdater.java index bbcab60d7ba2..ee2f1af6a99b 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationTypesUpdater.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationTypesUpdater.java @@ -16,7 +16,6 @@ package com.android.systemui.dreams.complication; -import android.content.Context; import android.database.ContentObserver; import android.os.UserHandle; import android.provider.Settings; @@ -37,7 +36,7 @@ import javax.inject.Inject; * user, and pushes updates to {@link DreamOverlayStateController}. */ @SysUISingleton -public class ComplicationTypesUpdater extends CoreStartable { +public class ComplicationTypesUpdater implements CoreStartable { private final DreamBackend mDreamBackend; private final Executor mExecutor; private final SecureSettings mSecureSettings; @@ -45,13 +44,11 @@ public class ComplicationTypesUpdater extends CoreStartable { private final DreamOverlayStateController mDreamOverlayStateController; @Inject - ComplicationTypesUpdater(Context context, + ComplicationTypesUpdater( DreamBackend dreamBackend, @Main Executor executor, SecureSettings secureSettings, DreamOverlayStateController dreamOverlayStateController) { - super(context); - mDreamBackend = dreamBackend; mExecutor = executor; mSecureSettings = secureSettings; diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamClockTimeComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamClockTimeComplication.java index 675a2f46d310..77e1fc91e6ee 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamClockTimeComplication.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamClockTimeComplication.java @@ -19,7 +19,6 @@ package com.android.systemui.dreams.complication; import static com.android.systemui.dreams.complication.dagger.DreamClockTimeComplicationModule.DREAM_CLOCK_TIME_COMPLICATION_VIEW; import static com.android.systemui.dreams.complication.dagger.RegisteredComplicationsModule.DREAM_CLOCK_TIME_COMPLICATION_LAYOUT_PARAMS; -import android.content.Context; import android.view.View; import com.android.systemui.CoreStartable; @@ -61,7 +60,7 @@ public class DreamClockTimeComplication implements Complication { * {@link CoreStartable} responsible for registering {@link DreamClockTimeComplication} with * SystemUI. */ - public static class Registrant extends CoreStartable { + public static class Registrant implements CoreStartable { private final DreamOverlayStateController mDreamOverlayStateController; private final DreamClockTimeComplication mComplication; @@ -69,10 +68,9 @@ public class DreamClockTimeComplication implements Complication { * Default constructor to register {@link DreamClockTimeComplication}. */ @Inject - public Registrant(Context context, + public Registrant( DreamOverlayStateController dreamOverlayStateController, DreamClockTimeComplication dreamClockTimeComplication) { - super(context); mDreamOverlayStateController = dreamOverlayStateController; mComplication = dreamClockTimeComplication; } diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java index 821e13ef733c..0ccb222c8acc 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java @@ -71,7 +71,7 @@ public class DreamHomeControlsComplication implements Complication { /** * {@link CoreStartable} for registering the complication with SystemUI on startup. */ - public static class Registrant extends CoreStartable { + public static class Registrant implements CoreStartable { private final DreamHomeControlsComplication mComplication; private final DreamOverlayStateController mDreamOverlayStateController; private final ControlsComponent mControlsComponent; @@ -90,11 +90,9 @@ public class DreamHomeControlsComplication implements Complication { }; @Inject - public Registrant(Context context, DreamHomeControlsComplication complication, + public Registrant(DreamHomeControlsComplication complication, DreamOverlayStateController dreamOverlayStateController, ControlsComponent controlsComponent) { - super(context); - mComplication = complication; mControlsComponent = controlsComponent; mDreamOverlayStateController = dreamOverlayStateController; diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/SmartSpaceComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/SmartSpaceComplication.java index a981f255a873..c3aaf0cbf2d7 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/complication/SmartSpaceComplication.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/SmartSpaceComplication.java @@ -61,7 +61,7 @@ public class SmartSpaceComplication implements Complication { * {@link CoreStartable} responsbile for registering {@link SmartSpaceComplication} with * SystemUI. */ - public static class Registrant extends CoreStartable { + public static class Registrant implements CoreStartable { private final DreamSmartspaceController mSmartSpaceController; private final DreamOverlayStateController mDreamOverlayStateController; private final SmartSpaceComplication mComplication; @@ -78,11 +78,10 @@ public class SmartSpaceComplication implements Complication { * Default constructor for {@link SmartSpaceComplication}. */ @Inject - public Registrant(Context context, + public Registrant( DreamOverlayStateController dreamOverlayStateController, SmartSpaceComplication smartSpaceComplication, DreamSmartspaceController smartSpaceController) { - super(context); mDreamOverlayStateController = dreamOverlayStateController; mComplication = smartSpaceComplication; mSmartSpaceController = smartSpaceController; diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugStartable.kt b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugStartable.kt index c0e30211e018..560dcbd78c42 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugStartable.kt @@ -16,9 +16,7 @@ package com.android.systemui.flags -import android.content.Context import com.android.systemui.CoreStartable -import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dump.DumpManager import com.android.systemui.statusbar.commandline.CommandRegistry import dagger.Binds @@ -30,12 +28,11 @@ import javax.inject.Inject class FeatureFlagsDebugStartable @Inject constructor( - @Application context: Context, dumpManager: DumpManager, private val commandRegistry: CommandRegistry, private val flagCommand: FlagCommand, featureFlags: FeatureFlags -) : CoreStartable(context) { +) : CoreStartable { init { dumpManager.registerDumpable(FeatureFlagsDebug.TAG) { pw, args -> diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseStartable.kt b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseStartable.kt index f138f1e8aa79..e7d8cc362c56 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseStartable.kt @@ -16,9 +16,7 @@ package com.android.systemui.flags -import android.content.Context import com.android.systemui.CoreStartable -import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dump.DumpManager import dagger.Binds import dagger.Module @@ -28,8 +26,7 @@ import javax.inject.Inject class FeatureFlagsReleaseStartable @Inject -constructor(@Application context: Context, dumpManager: DumpManager, featureFlags: FeatureFlags) : - CoreStartable(context) { +constructor(dumpManager: DumpManager, featureFlags: FeatureFlags) : CoreStartable { init { dumpManager.registerDumpable(FeatureFlagsRelease.TAG) { pw, args -> diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java index 3868b6f063e8..72e0ec1dd144 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java @@ -105,10 +105,6 @@ public class Flags { */ public static final UnreleasedFlag MODERN_BOUNCER = new UnreleasedFlag(208); - /** Whether UserSwitcherActivity should use modern architecture. */ - public static final ReleasedFlag MODERN_USER_SWITCHER_ACTIVITY = - new ReleasedFlag(209, true); - /** * Whether the user interactor and repository should use `UserSwitcherController`. * diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsComponent.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsComponent.java index 74d5bd577cf4..9f321d83d292 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsComponent.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsComponent.java @@ -36,8 +36,7 @@ import javax.inject.Provider; * Manages power menu plugins and communicates power menu actions to the CentralSurfaces. */ @SysUISingleton -public class GlobalActionsComponent extends CoreStartable - implements Callbacks, GlobalActionsManager { +public class GlobalActionsComponent implements CoreStartable, Callbacks, GlobalActionsManager { private final CommandQueue mCommandQueue; private final ExtensionController mExtensionController; @@ -48,11 +47,10 @@ public class GlobalActionsComponent extends CoreStartable private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; @Inject - public GlobalActionsComponent(Context context, CommandQueue commandQueue, + public GlobalActionsComponent(CommandQueue commandQueue, ExtensionController extensionController, Provider<GlobalActions> globalActionsProvider, StatusBarKeyguardViewManager statusBarKeyguardViewManager) { - super(context); mCommandQueue = commandQueue; mExtensionController = extensionController; mGlobalActionsProvider = globalActionsProvider; diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java b/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java index 568143c8df71..4f1a2b34f07c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java +++ b/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java @@ -27,7 +27,6 @@ import android.bluetooth.le.ScanSettings; import android.content.ContentResolver; import android.content.Context; import android.content.DialogInterface; -import android.content.res.Configuration; import android.hardware.input.InputManager; import android.os.Handler; import android.os.HandlerThread; @@ -66,7 +65,7 @@ import javax.inject.Provider; /** */ @SysUISingleton -public class KeyboardUI extends CoreStartable implements InputManager.OnTabletModeChangedListener { +public class KeyboardUI implements CoreStartable, InputManager.OnTabletModeChangedListener { private static final String TAG = "KeyboardUI"; private static final boolean DEBUG = false; @@ -127,13 +126,12 @@ public class KeyboardUI extends CoreStartable implements InputManager.OnTabletMo @Inject public KeyboardUI(Context context, Provider<LocalBluetoothManager> bluetoothManagerProvider) { - super(context); + mContext = context; this.mBluetoothManagerProvider = bluetoothManagerProvider; } @Override public void start() { - mContext = super.mContext; HandlerThread thread = new HandlerThread("Keyboard", Process.THREAD_PRIORITY_BACKGROUND); thread.start(); mHandler = new KeyboardHandler(thread.getLooper()); @@ -141,10 +139,6 @@ public class KeyboardUI extends CoreStartable implements InputManager.OnTabletMo } @Override - protected void onConfigurationChanged(Configuration newConfig) { - } - - @Override public void dump(PrintWriter pw, String[] args) { pw.println("KeyboardUI:"); pw.println(" mEnabled=" + mEnabled); @@ -156,7 +150,7 @@ public class KeyboardUI extends CoreStartable implements InputManager.OnTabletMo } @Override - protected void onBootCompleted() { + public void onBootCompleted() { mHandler.sendEmptyMessage(MSG_ON_BOOT_COMPLETED); } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 9a118e018bfd..9b036c166418 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -186,7 +186,7 @@ import dagger.Lazy; * directly to the keyguard UI is posted to a {@link android.os.Handler} to ensure it is taken on the UI * thread of the keyguard. */ -public class KeyguardViewMediator extends CoreStartable implements Dumpable, +public class KeyguardViewMediator implements CoreStartable, Dumpable, StatusBarStateController.StateListener { private static final int KEYGUARD_DISPLAY_TIMEOUT_DELAY_DEFAULT = 30000; private static final long KEYGUARD_DONE_PENDING_TIMEOUT_MS = 3000; @@ -272,6 +272,7 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, private boolean mShuttingDown; private boolean mDozing; private boolean mAnimatingScreenOff; + private final Context mContext; private final FalsingCollector mFalsingCollector; /** High level access to the power manager for WakeLocks */ @@ -1128,7 +1129,7 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, DreamOverlayStateController dreamOverlayStateController, Lazy<NotificationShadeWindowController> notificationShadeWindowControllerLazy, Lazy<ActivityLaunchAnimator> activityLaunchAnimator) { - super(context); + mContext = context; mFalsingCollector = falsingCollector; mLockPatternUtils = lockPatternUtils; mBroadcastDispatcher = broadcastDispatcher; diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt index 01cd3e28472e..f663b0dd23cd 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt @@ -19,7 +19,7 @@ package com.android.systemui.keyguard.domain.interactor import android.content.Intent import com.android.internal.widget.LockPatternUtils -import com.android.systemui.animation.ActivityLaunchAnimator +import com.android.systemui.animation.Expandable import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordancePosition @@ -67,19 +67,19 @@ constructor( * * @param configKey The configuration key corresponding to the [KeyguardQuickAffordanceModel] of * the affordance that was clicked - * @param animationController An optional controller for the activity-launch animation + * @param expandable An optional [Expandable] for the activity- or dialog-launch animation */ fun onQuickAffordanceClicked( configKey: KClass<out KeyguardQuickAffordanceConfig>, - animationController: ActivityLaunchAnimator.Controller?, + expandable: Expandable?, ) { @Suppress("UNCHECKED_CAST") val config = registry.get(configKey as KClass<Nothing>) - when (val result = config.onQuickAffordanceClicked(animationController)) { + when (val result = config.onQuickAffordanceClicked(expandable)) { is KeyguardQuickAffordanceConfig.OnClickedResult.StartActivity -> launchQuickAffordance( intent = result.intent, canShowWhileLocked = result.canShowWhileLocked, - animationController = animationController + expandable = expandable, ) is KeyguardQuickAffordanceConfig.OnClickedResult.Handled -> Unit } @@ -104,6 +104,7 @@ constructor( KeyguardQuickAffordanceModel.Visible( configKey = configs[index]::class, icon = visibleState.icon, + toggle = visibleState.toggle, ) } else { KeyguardQuickAffordanceModel.Hidden @@ -114,7 +115,7 @@ constructor( private fun launchQuickAffordance( intent: Intent, canShowWhileLocked: Boolean, - animationController: ActivityLaunchAnimator.Controller?, + expandable: Expandable?, ) { @LockPatternUtils.StrongAuthTracker.StrongAuthFlags val strongAuthFlags = @@ -130,13 +131,13 @@ constructor( activityStarter.postStartActivityDismissingKeyguard( intent, 0 /* delay */, - animationController + expandable?.activityLaunchController(), ) } else { activityStarter.startActivity( intent, true /* dismissShade */, - animationController, + expandable?.activityLaunchController(), true /* showOverLockscreenWhenLocked */, ) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordanceModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordanceModel.kt index eb6bb92dd912..e56b25967910 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordanceModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordanceModel.kt @@ -19,6 +19,7 @@ package com.android.systemui.keyguard.domain.model import com.android.systemui.common.shared.model.Icon import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig +import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordanceToggleState import kotlin.reflect.KClass /** @@ -35,5 +36,7 @@ sealed class KeyguardQuickAffordanceModel { val configKey: KClass<out KeyguardQuickAffordanceConfig>, /** An icon for the affordance. */ val icon: Icon, + /** The toggle state for the affordance. */ + val toggle: KeyguardQuickAffordanceToggleState, ) : KeyguardQuickAffordanceModel() } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt index 89604f054f9b..83842602cbee 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt @@ -20,7 +20,7 @@ package com.android.systemui.keyguard.domain.quickaffordance import android.content.Context import android.content.Intent import androidx.annotation.DrawableRes -import com.android.systemui.animation.ActivityLaunchAnimator +import com.android.systemui.animation.Expandable import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow import com.android.systemui.common.shared.model.ContentDescription @@ -61,7 +61,7 @@ constructor( } override fun onQuickAffordanceClicked( - animationController: ActivityLaunchAnimator.Controller?, + expandable: Expandable?, ): KeyguardQuickAffordanceConfig.OnClickedResult { return KeyguardQuickAffordanceConfig.OnClickedResult.StartActivity( intent = diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceConfig.kt index 8e1c6b76079f..95027d00c46c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceConfig.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceConfig.kt @@ -18,8 +18,9 @@ package com.android.systemui.keyguard.domain.quickaffordance import android.content.Intent -import com.android.systemui.animation.ActivityLaunchAnimator +import com.android.systemui.animation.Expandable import com.android.systemui.common.shared.model.Icon +import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordanceToggleState import kotlinx.coroutines.flow.Flow /** Defines interface that can act as data source for a single quick affordance model. */ @@ -27,9 +28,7 @@ interface KeyguardQuickAffordanceConfig { val state: Flow<State> - fun onQuickAffordanceClicked( - animationController: ActivityLaunchAnimator.Controller? - ): OnClickedResult + fun onQuickAffordanceClicked(expandable: Expandable?): OnClickedResult /** * Encapsulates the state of a "quick affordance" in the keyguard bottom area (for example, a @@ -44,6 +43,9 @@ interface KeyguardQuickAffordanceConfig { data class Visible( /** An icon for the affordance. */ val icon: Icon, + /** The toggle state for the affordance. */ + val toggle: KeyguardQuickAffordanceToggleState = + KeyguardQuickAffordanceToggleState.NotSupported, ) : State() } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt index d97deaf3b69e..502a6070a422 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt @@ -18,7 +18,7 @@ package com.android.systemui.keyguard.domain.quickaffordance import com.android.systemui.R -import com.android.systemui.animation.ActivityLaunchAnimator +import com.android.systemui.animation.Expandable import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow import com.android.systemui.common.shared.model.ContentDescription @@ -66,7 +66,7 @@ constructor( } override fun onQuickAffordanceClicked( - animationController: ActivityLaunchAnimator.Controller?, + expandable: Expandable?, ): KeyguardQuickAffordanceConfig.OnClickedResult { return KeyguardQuickAffordanceConfig.OnClickedResult.StartActivity( intent = controller.intent, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt index 9196b0920bb4..a24a0d62465f 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt @@ -23,7 +23,7 @@ import android.service.quickaccesswallet.GetWalletCardsResponse import android.service.quickaccesswallet.QuickAccessWalletClient import android.util.Log import com.android.systemui.R -import com.android.systemui.animation.ActivityLaunchAnimator +import com.android.systemui.animation.Expandable import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow import com.android.systemui.common.shared.model.ContentDescription @@ -84,11 +84,11 @@ constructor( } override fun onQuickAffordanceClicked( - animationController: ActivityLaunchAnimator.Controller?, + expandable: Expandable?, ): KeyguardQuickAffordanceConfig.OnClickedResult { walletController.startQuickAccessUiIntent( activityStarter, - animationController, + expandable?.activityLaunchController(), /* hasCard= */ true, ) return KeyguardQuickAffordanceConfig.OnClickedResult.Handled diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/quickaffordance/KeyguardQuickAffordanceToggleState.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/quickaffordance/KeyguardQuickAffordanceToggleState.kt new file mode 100644 index 000000000000..55d38a41849d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/quickaffordance/KeyguardQuickAffordanceToggleState.kt @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.keyguard.shared.quickaffordance + +/** Enumerates all possible toggle states for a quick affordance on the lock-screen. */ +sealed class KeyguardQuickAffordanceToggleState { + /** Toggling is not supported. */ + object NotSupported : KeyguardQuickAffordanceToggleState() + /** The quick affordance is on. */ + object On : KeyguardQuickAffordanceToggleState() + /** The quick affordance is off. */ + object Off : KeyguardQuickAffordanceToggleState() +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt index 65b85c0355db..2c99ca59ba6b 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt @@ -29,7 +29,7 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle import com.android.settingslib.Utils import com.android.systemui.R -import com.android.systemui.animation.ActivityLaunchAnimator +import com.android.systemui.animation.Expandable import com.android.systemui.animation.Interpolators import com.android.systemui.common.ui.binder.IconViewBinder import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel @@ -238,14 +238,26 @@ object KeyguardBottomAreaViewBinder { IconViewBinder.bind(viewModel.icon, view) + view.isActivated = viewModel.isActivated view.drawable.setTint( Utils.getColorAttrDefaultColor( view.context, - com.android.internal.R.attr.textColorPrimary + if (viewModel.isActivated) { + com.android.internal.R.attr.textColorPrimaryInverse + } else { + com.android.internal.R.attr.textColorPrimary + }, ) ) view.backgroundTintList = - Utils.getColorAttr(view.context, com.android.internal.R.attr.colorSurface) + Utils.getColorAttr( + view.context, + if (viewModel.isActivated) { + com.android.internal.R.attr.colorAccentPrimary + } else { + com.android.internal.R.attr.colorSurface + } + ) view.isClickable = viewModel.isClickable if (viewModel.isClickable) { @@ -268,7 +280,7 @@ object KeyguardBottomAreaViewBinder { viewModel.onClicked( KeyguardQuickAffordanceViewModel.OnClickedParameters( configKey = viewModel.configKey, - animationController = ActivityLaunchAnimator.Controller.fromView(view), + expandable = Expandable.fromView(view), ) ) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt index 970ee4c541d7..535ca7210244 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt @@ -23,6 +23,7 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordancePosition +import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordanceToggleState import javax.inject.Inject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine @@ -119,10 +120,11 @@ constructor( onClicked = { parameters -> quickAffordanceInteractor.onQuickAffordanceClicked( configKey = parameters.configKey, - animationController = parameters.animationController, + expandable = parameters.expandable, ) }, isClickable = isClickable, + isActivated = toggle is KeyguardQuickAffordanceToggleState.On, ) is KeyguardQuickAffordanceModel.Hidden -> KeyguardQuickAffordanceViewModel() } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt index 0971f13e58dd..bf598ba85932 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt @@ -16,7 +16,7 @@ package com.android.systemui.keyguard.ui.viewmodel -import com.android.systemui.animation.ActivityLaunchAnimator +import com.android.systemui.animation.Expandable import com.android.systemui.common.shared.model.Icon import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig import kotlin.reflect.KClass @@ -30,9 +30,10 @@ data class KeyguardQuickAffordanceViewModel( val icon: Icon = Icon.Resource(res = 0, contentDescription = null), val onClicked: (OnClickedParameters) -> Unit = {}, val isClickable: Boolean = false, + val isActivated: Boolean = false, ) { data class OnClickedParameters( val configKey: KClass<out KeyguardQuickAffordanceConfig>, - val animationController: ActivityLaunchAnimator.Controller?, + val expandable: Expandable?, ) } diff --git a/packages/SystemUI/src/com/android/systemui/log/SessionTracker.java b/packages/SystemUI/src/com/android/systemui/log/SessionTracker.java index 8f9357abbba8..c7e4c5e93090 100644 --- a/packages/SystemUI/src/com/android/systemui/log/SessionTracker.java +++ b/packages/SystemUI/src/com/android/systemui/log/SessionTracker.java @@ -21,7 +21,6 @@ import static android.app.StatusBarManager.SESSION_BIOMETRIC_PROMPT; import static android.app.StatusBarManager.SESSION_KEYGUARD; import android.annotation.Nullable; -import android.content.Context; import android.os.RemoteException; import android.util.Log; @@ -48,7 +47,7 @@ import javax.inject.Inject; * session. Can be used across processes via StatusBarManagerService#registerSessionListener */ @SysUISingleton -public class SessionTracker extends CoreStartable { +public class SessionTracker implements CoreStartable { private static final String TAG = "SessionTracker"; private static final boolean DEBUG = false; @@ -65,13 +64,11 @@ public class SessionTracker extends CoreStartable { @Inject public SessionTracker( - Context context, IStatusBarService statusBarService, AuthController authController, KeyguardUpdateMonitor keyguardUpdateMonitor, KeyguardStateController keyguardStateController ) { - super(context); mStatusBarManagerService = statusBarService; mAuthController = authController; mKeyguardUpdateMonitor = keyguardUpdateMonitor; diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java index 529d0662028a..1b81e88e62ba 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java @@ -344,4 +344,14 @@ public class LogModule { public static LogBuffer provideKeyguardLogBuffer(LogBufferFactory factory) { return factory.create("KeyguardLog", 250); } + + /** + * Provides a {@link LogBuffer} for Udfps logs. + */ + @Provides + @SysUISingleton + @UdfpsLog + public static LogBuffer provideUdfpsLogBuffer(LogBufferFactory factory) { + return factory.create("UdfpsLog", 1000); + } } diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/UdfpsLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/UdfpsLog.java new file mode 100644 index 000000000000..14000e183c10 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/UdfpsLog.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.log.dagger; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; + +import javax.inject.Qualifier; + +@Qualifier +@Documented +@Retention(RUNTIME) +public @interface UdfpsLog { +} diff --git a/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java b/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java index 0b9b32b0d7d7..2a8168b0cb36 100644 --- a/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java +++ b/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java @@ -51,9 +51,10 @@ import javax.inject.Inject; * {@link android.Manifest.permission#READ_EXTERNAL_STORAGE}. */ @SysUISingleton -public class RingtonePlayer extends CoreStartable { +public class RingtonePlayer implements CoreStartable { private static final String TAG = "RingtonePlayer"; private static final boolean LOGD = false; + private final Context mContext; // TODO: support Uri switching under same IBinder @@ -64,7 +65,7 @@ public class RingtonePlayer extends CoreStartable { @Inject public RingtonePlayer(Context context) { - super(context); + mContext = context; } @Override diff --git a/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java b/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java index 53b4d434bfcb..91e7b4933096 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java +++ b/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java @@ -18,7 +18,6 @@ package com.android.systemui.media.dream; import static com.android.systemui.flags.Flags.DREAM_MEDIA_COMPLICATION; -import android.content.Context; import android.util.Log; import androidx.annotation.NonNull; @@ -38,7 +37,7 @@ import javax.inject.Inject; * {@link MediaDreamSentinel} is responsible for tracking media state and registering/unregistering * the media complication as appropriate */ -public class MediaDreamSentinel extends CoreStartable { +public class MediaDreamSentinel implements CoreStartable { private static final String TAG = "MediaDreamSentinel"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); @@ -113,11 +112,10 @@ public class MediaDreamSentinel extends CoreStartable { private final FeatureFlags mFeatureFlags; @Inject - public MediaDreamSentinel(Context context, MediaDataManager mediaDataManager, + public MediaDreamSentinel(MediaDataManager mediaDataManager, DreamOverlayStateController dreamOverlayStateController, DreamMediaEntryComplication mediaEntryComplication, FeatureFlags featureFlags) { - super(context); mMediaDataManager = mediaDataManager; mDreamOverlayStateController = dreamOverlayStateController; mMediaEntryComplication = mediaEntryComplication; diff --git a/packages/SystemUI/src/com/android/systemui/media/systemsounds/HomeSoundEffectController.java b/packages/SystemUI/src/com/android/systemui/media/systemsounds/HomeSoundEffectController.java index d60172a17988..0ba5f28c351f 100644 --- a/packages/SystemUI/src/com/android/systemui/media/systemsounds/HomeSoundEffectController.java +++ b/packages/SystemUI/src/com/android/systemui/media/systemsounds/HomeSoundEffectController.java @@ -40,7 +40,7 @@ import javax.inject.Inject; * documented at {@link #handleTaskStackChanged} apply. */ @SysUISingleton -public class HomeSoundEffectController extends CoreStartable { +public class HomeSoundEffectController implements CoreStartable { private static final String TAG = "HomeSoundEffectController"; private final AudioManager mAudioManager; @@ -65,7 +65,6 @@ public class HomeSoundEffectController extends CoreStartable { TaskStackChangeListeners taskStackChangeListeners, ActivityManagerWrapper activityManagerWrapper, PackageManager packageManager) { - super(context); mAudioManager = audioManager; mTaskStackChangeListeners = taskStackChangeListeners; mActivityManagerWrapper = activityManagerWrapper; diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt index f5caefbf4ced..a4a968067462 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt @@ -43,7 +43,7 @@ class MediaTttCommandLineHelper @Inject constructor( private val commandRegistry: CommandRegistry, private val context: Context, @Main private val mainExecutor: Executor -) : CoreStartable(context) { +) : CoreStartable { /** All commands for the sender device. */ inner class SenderCommand : Command { diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java index 50a10bc0b15a..6da8c69c013b 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java @@ -114,6 +114,7 @@ import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.DisplayId; import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.model.SysUiState; import com.android.systemui.navigationbar.NavigationBarComponent.NavigationBarScope; import com.android.systemui.navigationbar.NavigationModeController.ModeChangedListener; @@ -211,6 +212,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements private final NotificationShadeDepthController mNotificationShadeDepthController; private final OnComputeInternalInsetsListener mOnComputeInternalInsetsListener; private final UserContextProvider mUserContextProvider; + private final WakefulnessLifecycle mWakefulnessLifecycle; private final RegionSamplingHelper mRegionSamplingHelper; private final int mNavColorSampleMargin; private NavigationBarFrame mFrame; @@ -451,6 +453,28 @@ public class NavigationBar extends ViewController<NavigationBarView> implements } }; + private final WakefulnessLifecycle.Observer mWakefulnessObserver = + new WakefulnessLifecycle.Observer() { + private void notifyScreenStateChanged(boolean isScreenOn) { + notifyNavigationBarScreenOn(); + mView.onScreenStateChanged(isScreenOn); + } + + @Override + public void onStartedWakingUp() { + notifyScreenStateChanged(true); + if (isGesturalModeOnDefaultDisplay(getContext(), mNavBarMode)) { + mRegionSamplingHelper.start(mSamplingBounds); + } + } + + @Override + public void onFinishedGoingToSleep() { + notifyScreenStateChanged(false); + mRegionSamplingHelper.stop(); + } + }; + @Inject NavigationBar( NavigationBarView navigationBarView, @@ -491,7 +515,8 @@ public class NavigationBar extends ViewController<NavigationBarView> implements NavigationBarTransitions navigationBarTransitions, EdgeBackGestureHandler edgeBackGestureHandler, Optional<BackAnimation> backAnimation, - UserContextProvider userContextProvider) { + UserContextProvider userContextProvider, + WakefulnessLifecycle wakefulnessLifecycle) { super(navigationBarView); mFrame = navigationBarFrame; mContext = context; @@ -529,6 +554,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements mTelecomManagerOptional = telecomManagerOptional; mInputMethodManager = inputMethodManager; mUserContextProvider = userContextProvider; + mWakefulnessLifecycle = wakefulnessLifecycle; mNavColorSampleMargin = getResources() .getDimensionPixelSize(R.dimen.navigation_handle_sample_horizontal_margin); @@ -682,11 +708,10 @@ public class NavigationBar extends ViewController<NavigationBarView> implements prepareNavigationBarView(); checkNavBarModes(); - IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF); - filter.addAction(Intent.ACTION_SCREEN_ON); - filter.addAction(Intent.ACTION_USER_SWITCHED); + IntentFilter filter = new IntentFilter(Intent.ACTION_USER_SWITCHED); mBroadcastDispatcher.registerReceiverWithHandler(mBroadcastReceiver, filter, Handler.getMain(), UserHandle.ALL); + mWakefulnessLifecycle.addObserver(mWakefulnessObserver); notifyNavigationBarScreenOn(); mOverviewProxyService.addCallback(mOverviewProxyListener); @@ -737,6 +762,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements getBarTransitions().destroy(); mOverviewProxyService.removeCallback(mOverviewProxyListener); mBroadcastDispatcher.unregisterReceiver(mBroadcastReceiver); + mWakefulnessLifecycle.removeObserver(mWakefulnessObserver); if (mOrientationHandle != null) { resetSecondaryHandle(); getBarTransitions().removeDarkIntensityListener(mOrientationHandleIntensityListener); @@ -1619,19 +1645,6 @@ public class NavigationBar extends ViewController<NavigationBarView> implements return; } String action = intent.getAction(); - if (Intent.ACTION_SCREEN_OFF.equals(action) - || Intent.ACTION_SCREEN_ON.equals(action)) { - notifyNavigationBarScreenOn(); - boolean isScreenOn = Intent.ACTION_SCREEN_ON.equals(action); - mView.onScreenStateChanged(isScreenOn); - if (isScreenOn) { - if (isGesturalModeOnDefaultDisplay(getContext(), mNavBarMode)) { - mRegionSamplingHelper.start(mSamplingBounds); - } - } else { - mRegionSamplingHelper.stop(); - } - } if (Intent.ACTION_USER_SWITCHED.equals(action)) { // The accessibility settings may be different for the new user updateAccessibilityStateFlags(); diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java index 029cf68b243d..3fd1aa73c033 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java @@ -21,6 +21,7 @@ import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_GESTURE import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR; import static android.view.Display.DEFAULT_DISPLAY; +import static com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler.DEBUG_MISSING_GESTURE_TAG; import static com.android.systemui.shared.recents.utilities.Utilities.isTablet; import android.content.ContentResolver; @@ -141,7 +142,13 @@ public class NavigationBarController implements public void onConfigChanged(Configuration newConfig) { boolean isOldConfigTablet = mIsTablet; mIsTablet = isTablet(mContext); + boolean willApplyConfig = mConfigChanges.applyNewConfig(mContext.getResources()); boolean largeScreenChanged = mIsTablet != isOldConfigTablet; + // TODO(b/243765256): Disable this logging once b/243765256 is fixed. + Log.d(DEBUG_MISSING_GESTURE_TAG, "NavbarController: newConfig=" + newConfig + + " mTaskbarDelegate initialized=" + mTaskbarDelegate.isInitialized() + + " willApplyConfigToNavbars=" + willApplyConfig + + " navBarCount=" + mNavigationBars.size()); if (mTaskbarDelegate.isInitialized()) { mTaskbarDelegate.onConfigurationChanged(newConfig); } @@ -150,7 +157,7 @@ public class NavigationBarController implements return; } - if (mConfigChanges.applyNewConfig(mContext.getResources())) { + if (willApplyConfig) { for (int i = 0; i < mNavigationBars.size(); i++) { recreateNavigationBar(mNavigationBars.keyAt(i)); } diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java index a8799c744656..709467ffd3b5 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java @@ -113,7 +113,7 @@ public class EdgeBackGestureHandler extends CurrentUserTracker private static final int MAX_NUM_LOGGED_GESTURES = 10; static final boolean DEBUG_MISSING_GESTURE = false; - static final String DEBUG_MISSING_GESTURE_TAG = "NoBackGesture"; + public static final String DEBUG_MISSING_GESTURE_TAG = "NoBackGesture"; private ISystemGestureExclusionListener mGestureExclusionListener = new ISystemGestureExclusionListener.Stub() { diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java index 67dae9e7a0ea..1da866efc08d 100644 --- a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java +++ b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java @@ -46,6 +46,7 @@ import com.android.systemui.CoreStartable; import com.android.systemui.R; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.phone.CentralSurfaces; @@ -59,7 +60,7 @@ import javax.inject.Inject; import dagger.Lazy; @SysUISingleton -public class PowerUI extends CoreStartable implements CommandQueue.Callbacks { +public class PowerUI implements CoreStartable, CommandQueue.Callbacks { static final String TAG = "PowerUI"; static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); @@ -78,6 +79,7 @@ public class PowerUI extends CoreStartable implements CommandQueue.Callbacks { private final PowerManager mPowerManager; private final WarningsUI mWarnings; + private final WakefulnessLifecycle mWakefulnessLifecycle; private InattentiveSleepWarningView mOverlayView; private final Configuration mLastConfiguration = new Configuration(); private int mPlugType = 0; @@ -103,22 +105,37 @@ public class PowerUI extends CoreStartable implements CommandQueue.Callbacks { private IThermalEventListener mSkinThermalEventListener; private IThermalEventListener mUsbThermalEventListener; + private final Context mContext; private final BroadcastDispatcher mBroadcastDispatcher; private final CommandQueue mCommandQueue; private final Lazy<Optional<CentralSurfaces>> mCentralSurfacesOptionalLazy; + private final WakefulnessLifecycle.Observer mWakefulnessObserver = + new WakefulnessLifecycle.Observer() { + @Override + public void onStartedWakingUp() { + mScreenOffTime = -1; + } + + @Override + public void onFinishedGoingToSleep() { + mScreenOffTime = SystemClock.elapsedRealtime(); + } + }; @Inject public PowerUI(Context context, BroadcastDispatcher broadcastDispatcher, CommandQueue commandQueue, Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy, WarningsUI warningsUI, EnhancedEstimates enhancedEstimates, + WakefulnessLifecycle wakefulnessLifecycle, PowerManager powerManager) { - super(context); + mContext = context; mBroadcastDispatcher = broadcastDispatcher; mCommandQueue = commandQueue; mCentralSurfacesOptionalLazy = centralSurfacesOptionalLazy; mWarnings = warningsUI; mEnhancedEstimates = enhancedEstimates; mPowerManager = powerManager; + mWakefulnessLifecycle = wakefulnessLifecycle; } public void start() { @@ -137,6 +154,7 @@ public class PowerUI extends CoreStartable implements CommandQueue.Callbacks { false, obs, UserHandle.USER_ALL); updateBatteryWarningLevels(); mReceiver.init(); + mWakefulnessLifecycle.addObserver(mWakefulnessObserver); // Check to see if we need to let the user know that the phone previously shut down due // to the temperature being too high. @@ -169,7 +187,7 @@ public class PowerUI extends CoreStartable implements CommandQueue.Callbacks { } @Override - protected void onConfigurationChanged(Configuration newConfig) { + public void onConfigurationChanged(Configuration newConfig) { final int mask = ActivityInfo.CONFIG_MCC | ActivityInfo.CONFIG_MNC; // Safe to modify mLastConfiguration here as it's only updated by the main thread (here). @@ -232,8 +250,6 @@ public class PowerUI extends CoreStartable implements CommandQueue.Callbacks { IntentFilter filter = new IntentFilter(); filter.addAction(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED); filter.addAction(Intent.ACTION_BATTERY_CHANGED); - filter.addAction(Intent.ACTION_SCREEN_OFF); - filter.addAction(Intent.ACTION_SCREEN_ON); filter.addAction(Intent.ACTION_USER_SWITCHED); mBroadcastDispatcher.registerReceiverWithHandler(this, filter, mHandler); // Force get initial values. Relying on Sticky behavior until API for getting info. @@ -316,10 +332,6 @@ public class PowerUI extends CoreStartable implements CommandQueue.Callbacks { plugged, bucket); }); - } else if (Intent.ACTION_SCREEN_OFF.equals(action)) { - mScreenOffTime = SystemClock.elapsedRealtime(); - } else if (Intent.ACTION_SCREEN_ON.equals(action)) { - mScreenOffTime = -1; } else if (Intent.ACTION_USER_SWITCHED.equals(action)) { mWarnings.userSwitched(); } else { diff --git a/packages/SystemUI/src/com/android/systemui/privacy/television/TvOngoingPrivacyChip.java b/packages/SystemUI/src/com/android/systemui/privacy/television/TvOngoingPrivacyChip.java index 5510eb172cd7..cd32a10a432b 100644 --- a/packages/SystemUI/src/com/android/systemui/privacy/television/TvOngoingPrivacyChip.java +++ b/packages/SystemUI/src/com/android/systemui/privacy/television/TvOngoingPrivacyChip.java @@ -67,7 +67,7 @@ import javax.inject.Inject; * recording audio, accessing the camera or accessing the location. */ @SysUISingleton -public class TvOngoingPrivacyChip extends CoreStartable implements PrivacyItemController.Callback, +public class TvOngoingPrivacyChip implements CoreStartable, PrivacyItemController.Callback, PrivacyChipDrawable.PrivacyChipDrawableListener { private static final String TAG = "TvOngoingPrivacyChip"; private static final boolean DEBUG = false; @@ -134,7 +134,6 @@ public class TvOngoingPrivacyChip extends CoreStartable implements PrivacyItemCo @Inject public TvOngoingPrivacyChip(Context context, PrivacyItemController privacyItemController, IWindowManager iWindowManager) { - super(context); if (DEBUG) Log.d(TAG, "Privacy chip running"); mContext = context; mPrivacyItemController = privacyItemController; diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt index 11d955580983..d3c06f60bc90 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt @@ -23,7 +23,6 @@ import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleOwner import com.android.settingslib.Utils -import com.android.settingslib.drawable.UserIconDrawable import com.android.systemui.R import com.android.systemui.animation.Expandable import com.android.systemui.common.shared.model.ContentDescription @@ -250,22 +249,19 @@ class FooterActionsViewModel( status: UserSwitcherStatusModel.Enabled ): FooterActionsButtonViewModel { val icon = status.currentUserImage!! - val iconTint = - if (status.isGuestUser && icon !is UserIconDrawable) { - Utils.getColorAttrDefaultColor(context, android.R.attr.colorForeground) - } else { - null - } return FooterActionsButtonViewModel( id = R.id.multi_user_switch, - Icon.Loaded( - icon, - ContentDescription.Loaded(userSwitcherContentDescription(status.currentUserName)), - ), - iconTint, - R.drawable.qs_footer_action_circle, - this::onUserSwitcherClicked, + icon = + Icon.Loaded( + icon, + ContentDescription.Loaded( + userSwitcherContentDescription(status.currentUserName) + ), + ), + iconTint = null, + background = R.drawable.qs_footer_action_circle, + onClick = this::onUserSwitcherClicked, ) } diff --git a/packages/SystemUI/src/com/android/systemui/recents/Recents.java b/packages/SystemUI/src/com/android/systemui/recents/Recents.java index 9b3b843c9848..b041f957d771 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/Recents.java +++ b/packages/SystemUI/src/com/android/systemui/recents/Recents.java @@ -29,13 +29,14 @@ import java.io.PrintWriter; /** * A proxy to a Recents implementation. */ -public class Recents extends CoreStartable implements CommandQueue.Callbacks { +public class Recents implements CoreStartable, CommandQueue.Callbacks { + private final Context mContext; private final RecentsImplementation mImpl; private final CommandQueue mCommandQueue; public Recents(Context context, RecentsImplementation impl, CommandQueue commandQueue) { - super(context); + mContext = context; mImpl = impl; mCommandQueue = commandQueue; } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentCreator.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentCreator.kt new file mode 100644 index 000000000000..017e57fcaf62 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentCreator.kt @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.screenshot + +import android.content.ClipData +import android.content.ClipDescription +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.net.Uri +import com.android.systemui.R + +object ActionIntentCreator { + /** @return a chooser intent to share the given URI with the optional provided subject. */ + fun createShareIntent(uri: Uri, subject: String?): Intent { + // Create a share intent, this will always go through the chooser activity first + // which should not trigger auto-enter PiP + val sharingIntent = + Intent(Intent.ACTION_SEND).apply { + setDataAndType(uri, "image/png") + putExtra(Intent.EXTRA_STREAM, uri) + + // Include URI in ClipData also, so that grantPermission picks it up. + // We don't use setData here because some apps interpret this as "to:". + clipData = + ClipData( + ClipDescription("content", arrayOf(ClipDescription.MIMETYPE_TEXT_PLAIN)), + ClipData.Item(uri) + ) + + putExtra(Intent.EXTRA_SUBJECT, subject) + addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION) + } + + return Intent.createChooser(sharingIntent, null) + .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + } + + /** + * @return an ACTION_EDIT intent for the given URI, directed to config_screenshotEditor if + * available. + */ + fun createEditIntent(uri: Uri, context: Context): Intent { + val editIntent = Intent(Intent.ACTION_EDIT) + + context.getString(R.string.config_screenshotEditor)?.let { + if (it.isNotEmpty()) { + editIntent.component = ComponentName.unflattenFromString(it) + } + } + + return editIntent + .setDataAndType(uri, "image/png") + .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + .addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt new file mode 100644 index 000000000000..5961635a0dba --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt @@ -0,0 +1,159 @@ +/* + * 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.screenshot + +import android.content.Context +import android.content.Intent +import android.os.Bundle +import android.os.RemoteException +import android.os.UserHandle +import android.util.Log +import android.view.Display +import android.view.IRemoteAnimationFinishedCallback +import android.view.IRemoteAnimationRunner +import android.view.RemoteAnimationAdapter +import android.view.RemoteAnimationTarget +import android.view.WindowManager +import android.view.WindowManagerGlobal +import com.android.internal.infra.ServiceConnector +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dagger.qualifiers.Background +import javax.inject.Inject +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext + +@SysUISingleton +class ActionIntentExecutor +@Inject +constructor( + @Application private val applicationScope: CoroutineScope, + @Background private val bgDispatcher: CoroutineDispatcher, + private val context: Context, +) { + /** + * Execute the given intent with startActivity while performing operations for screenshot action + * launching. + * - Dismiss the keyguard first + * - If the userId is not the current user, proxy to a service running as that user to execute + * - After startActivity, optionally override the pending app transition. + */ + fun launchIntentAsync( + intent: Intent, + bundle: Bundle, + userId: Int, + overrideTransition: Boolean, + ) { + applicationScope.launch { launchIntent(intent, bundle, userId, overrideTransition) } + } + + suspend fun launchIntent( + intent: Intent, + bundle: Bundle, + userId: Int, + overrideTransition: Boolean, + ) { + withContext(bgDispatcher) { + dismissKeyguard() + + if (userId == UserHandle.myUserId()) { + context.startActivity(intent, bundle) + } else { + launchCrossProfileIntent(userId, intent, bundle) + } + + if (overrideTransition) { + val runner = RemoteAnimationAdapter(SCREENSHOT_REMOTE_RUNNER, 0, 0) + try { + WindowManagerGlobal.getWindowManagerService() + .overridePendingAppTransitionRemote(runner, Display.DEFAULT_DISPLAY) + } catch (e: Exception) { + Log.e(TAG, "Error overriding screenshot app transition", e) + } + } + } + } + + private val proxyConnector: ServiceConnector<IScreenshotProxy> = + ServiceConnector.Impl( + context, + Intent(context, ScreenshotProxyService::class.java), + Context.BIND_AUTO_CREATE or Context.BIND_WAIVE_PRIORITY or Context.BIND_NOT_VISIBLE, + context.userId, + IScreenshotProxy.Stub::asInterface, + ) + + private suspend fun dismissKeyguard() { + val completion = CompletableDeferred<Unit>() + val onDoneBinder = + object : IOnDoneCallback.Stub() { + override fun onDone(success: Boolean) { + completion.complete(Unit) + } + } + proxyConnector.post { it.dismissKeyguard(onDoneBinder) } + completion.await() + } + + private fun getCrossProfileConnector(userId: Int): ServiceConnector<ICrossProfileService> = + ServiceConnector.Impl<ICrossProfileService>( + context, + Intent(context, ScreenshotCrossProfileService::class.java), + Context.BIND_AUTO_CREATE or Context.BIND_WAIVE_PRIORITY or Context.BIND_NOT_VISIBLE, + userId, + ICrossProfileService.Stub::asInterface, + ) + + private suspend fun launchCrossProfileIntent(userId: Int, intent: Intent, bundle: Bundle) { + val connector = getCrossProfileConnector(userId) + val completion = CompletableDeferred<Unit>() + connector.post { + it.launchIntent(intent, bundle) + completion.complete(Unit) + } + completion.await() + } +} + +private const val TAG: String = "ActionIntentExecutor" +private const val SCREENSHOT_SHARE_SUBJECT_TEMPLATE = "Screenshot (%s)" + +/** + * This is effectively a no-op, but we need something non-null to pass in, in order to successfully + * override the pending activity entrance animation. + */ +private val SCREENSHOT_REMOTE_RUNNER: IRemoteAnimationRunner.Stub = + object : IRemoteAnimationRunner.Stub() { + override fun onAnimationStart( + @WindowManager.TransitionOldType transit: Int, + apps: Array<RemoteAnimationTarget>, + wallpapers: Array<RemoteAnimationTarget>, + nonApps: Array<RemoteAnimationTarget>, + finishedCallback: IRemoteAnimationFinishedCallback, + ) { + try { + finishedCallback.onAnimationFinished() + } catch (e: RemoteException) { + Log.e(TAG, "Error finishing screenshot remote animation", e) + } + } + + override fun onAnimationCancelled(isKeyguardOccluded: Boolean) {} + } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/DraggableConstraintLayout.java b/packages/SystemUI/src/com/android/systemui/screenshot/DraggableConstraintLayout.java index 950806d89422..ead3b7b1de53 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/DraggableConstraintLayout.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/DraggableConstraintLayout.java @@ -49,7 +49,6 @@ public class DraggableConstraintLayout extends ConstraintLayout private final SwipeDismissHandler mSwipeDismissHandler; private final GestureDetector mSwipeDetector; private View mActionsContainer; - private View mActionsContainerBackground; private SwipeDismissCallbacks mCallbacks; private final DisplayMetrics mDisplayMetrics; @@ -111,6 +110,9 @@ public class DraggableConstraintLayout extends ConstraintLayout } }); mSwipeDetector.setIsLongpressEnabled(false); + + mCallbacks = new SwipeDismissCallbacks() { + }; // default to unimplemented callbacks } public void setCallbacks(SwipeDismissCallbacks callbacks) { @@ -119,16 +121,13 @@ public class DraggableConstraintLayout extends ConstraintLayout @Override public boolean onInterceptHoverEvent(MotionEvent event) { - if (mCallbacks != null) { - mCallbacks.onInteraction(); - } + mCallbacks.onInteraction(); return super.onInterceptHoverEvent(event); } @Override // View protected void onFinishInflate() { mActionsContainer = findViewById(R.id.actions_container); - mActionsContainerBackground = findViewById(R.id.actions_container_background); } @Override @@ -186,6 +185,13 @@ public class DraggableConstraintLayout extends ConstraintLayout inoutInfo.touchableRegion.set(r); } + private int getBackgroundRight() { + // background expected to be null in testing. + // animation may have unexpected behavior if view is not present + View background = findViewById(R.id.actions_container_background); + return background == null ? 0 : background.getRight(); + } + /** * Allows a view to be swipe-dismissed, or returned to its location if distance threshold is not * met @@ -213,8 +219,6 @@ public class DraggableConstraintLayout extends ConstraintLayout mGestureDetector = new GestureDetector(context, gestureListener); mDisplayMetrics = new DisplayMetrics(); context.getDisplay().getRealMetrics(mDisplayMetrics); - mCallbacks = new SwipeDismissCallbacks() { - }; // default to unimplemented callbacks } @Override @@ -230,7 +234,9 @@ public class DraggableConstraintLayout extends ConstraintLayout return true; } if (isPastDismissThreshold()) { - dismiss(); + ValueAnimator anim = createSwipeDismissAnimation(); + mCallbacks.onSwipeDismissInitiated(anim); + dismiss(anim); } else { // if we've moved, but not past the threshold, start the return animation if (DEBUG_DISMISS) { @@ -295,10 +301,7 @@ public class DraggableConstraintLayout extends ConstraintLayout } void dismiss() { - float velocityPxPerMs = FloatingWindowUtil.dpToPx(mDisplayMetrics, VELOCITY_DP_PER_MS); - ValueAnimator anim = createSwipeDismissAnimation(velocityPxPerMs); - mCallbacks.onSwipeDismissInitiated(anim); - dismiss(anim); + dismiss(createSwipeDismissAnimation()); } private void dismiss(ValueAnimator animator) { @@ -323,6 +326,11 @@ public class DraggableConstraintLayout extends ConstraintLayout mDismissAnimation.start(); } + private ValueAnimator createSwipeDismissAnimation() { + float velocityPxPerMs = FloatingWindowUtil.dpToPx(mDisplayMetrics, VELOCITY_DP_PER_MS); + return createSwipeDismissAnimation(velocityPxPerMs); + } + private ValueAnimator createSwipeDismissAnimation(float velocity) { // velocity is measured in pixels per millisecond velocity = Math.min(3, Math.max(1, velocity)); @@ -337,7 +345,7 @@ public class DraggableConstraintLayout extends ConstraintLayout if (startX > 0 || (startX == 0 && layoutDir == LAYOUT_DIRECTION_RTL)) { finalX = mDisplayMetrics.widthPixels; } else { - finalX = -1 * mActionsContainerBackground.getRight(); + finalX = -1 * getBackgroundRight(); } float distance = Math.abs(finalX - startX); diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ICrossProfileService.aidl b/packages/SystemUI/src/com/android/systemui/screenshot/ICrossProfileService.aidl new file mode 100644 index 000000000000..da834729d319 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ICrossProfileService.aidl @@ -0,0 +1,27 @@ +/** + * Copyright (c) 2009, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.screenshot; + +import android.app.PendingIntent; +import android.content.Intent; +import android.os.Bundle; + +/** Interface implemented by ScreenshotCrossProfileService */ +interface ICrossProfileService { + + void launchIntent(in Intent intent, in Bundle bundle); +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/IOnDoneCallback.aidl b/packages/SystemUI/src/com/android/systemui/screenshot/IOnDoneCallback.aidl new file mode 100644 index 000000000000..e15030f78234 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/screenshot/IOnDoneCallback.aidl @@ -0,0 +1,21 @@ +/** + * 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.screenshot; + +interface IOnDoneCallback { + void onDone(boolean success); +} diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/IScreenshotProxy.aidl b/packages/SystemUI/src/com/android/systemui/screenshot/IScreenshotProxy.aidl index f7c4dadc6605..d2e3fbd65762 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/IScreenshotProxy.aidl +++ b/packages/SystemUI/src/com/android/systemui/screenshot/IScreenshotProxy.aidl @@ -16,9 +16,14 @@ package com.android.systemui.screenshot; +import com.android.systemui.screenshot.IOnDoneCallback; + /** Interface implemented by ScreenshotProxyService */ interface IScreenshotProxy { /** Is the notification shade currently exanded? */ boolean isNotificationShadeExpanded(); -}
\ No newline at end of file + + /** Attempts to dismiss the keyguard. */ + void dismissKeyguard(IOnDoneCallback callback); +} diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java index 077ad35fd63f..7143ba263570 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java @@ -173,6 +173,7 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { mImageData.deleteAction = createDeleteAction(mContext, mContext.getResources(), uri); mImageData.quickShareAction = createQuickShareAction(mContext, mQuickShareData.quickShareAction, uri); + mImageData.subject = getSubjectString(); mParams.mActionsReadyListener.onActionsReady(mImageData); if (DEBUG_CALLBACK) { @@ -237,8 +238,6 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { // Create a share intent, this will always go through the chooser activity first // which should not trigger auto-enter PiP - String subjectDate = DateFormat.getDateTimeInstance().format(new Date(mImageTime)); - String subject = String.format(SCREENSHOT_SHARE_SUBJECT_TEMPLATE, subjectDate); Intent sharingIntent = new Intent(Intent.ACTION_SEND); sharingIntent.setDataAndType(uri, "image/png"); sharingIntent.putExtra(Intent.EXTRA_STREAM, uri); @@ -248,7 +247,7 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { new String[]{ClipDescription.MIMETYPE_TEXT_PLAIN}), new ClipData.Item(uri)); sharingIntent.setClipData(clipdata); - sharingIntent.putExtra(Intent.EXTRA_SUBJECT, subject); + sharingIntent.putExtra(Intent.EXTRA_SUBJECT, getSubjectString()); sharingIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) .addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION); @@ -318,7 +317,7 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { // by setting the (otherwise unused) request code to the current user id. int requestCode = mContext.getUserId(); - // Create a edit action + // Create an edit action PendingIntent editAction = PendingIntent.getBroadcastAsUser(context, requestCode, new Intent(context, ActionProxyReceiver.class) .putExtra(ScreenshotController.EXTRA_ACTION_INTENT, pendingIntent) @@ -479,4 +478,9 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { mParams.mQuickShareActionsReadyListener.onActionsReady(mQuickShareData); } } + + private String getSubjectString() { + String subjectDate = DateFormat.getDateTimeInstance().format(new Date(mImageTime)); + return String.format(SCREENSHOT_SHARE_SUBJECT_TEMPLATE, subjectDate); + } } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java index 704e11512b37..231e415f17c6 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java @@ -28,6 +28,7 @@ import static com.android.systemui.screenshot.LogConfig.DEBUG_UI; import static com.android.systemui.screenshot.LogConfig.DEBUG_WINDOW; import static com.android.systemui.screenshot.LogConfig.logTag; import static com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_DISMISSED_OTHER; +import static com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_INTERACTION_TIMEOUT; import static java.util.Objects.requireNonNull; @@ -173,7 +174,7 @@ public class ScreenshotController { public List<Notification.Action> smartActions; public Notification.Action quickShareAction; public UserHandle owner; - + public String subject; // Title for sharing /** * POD for shared element transition. @@ -194,6 +195,7 @@ public class ScreenshotController { deleteAction = null; smartActions = null; quickShareAction = null; + subject = null; } } @@ -272,6 +274,7 @@ public class ScreenshotController { private final ScreenshotNotificationSmartActionsProvider mScreenshotNotificationSmartActionsProvider; private final TimeoutHandler mScreenshotHandler; + private final ActionIntentExecutor mActionExecutor; private ScreenshotView mScreenshotView; private Bitmap mScreenBitmap; @@ -309,7 +312,8 @@ public class ScreenshotController { ActivityManager activityManager, TimeoutHandler timeoutHandler, BroadcastSender broadcastSender, - ScreenshotNotificationSmartActionsProvider screenshotNotificationSmartActionsProvider + ScreenshotNotificationSmartActionsProvider screenshotNotificationSmartActionsProvider, + ActionIntentExecutor actionExecutor ) { mScreenshotSmartActions = screenshotSmartActions; mNotificationsController = screenshotNotificationsController; @@ -331,9 +335,7 @@ public class ScreenshotController { if (DEBUG_UI) { Log.d(TAG, "Corner timeout hit"); } - mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_INTERACTION_TIMEOUT, 0, - mPackageName); - ScreenshotController.this.dismissScreenshot(false); + dismissScreenshot(SCREENSHOT_INTERACTION_TIMEOUT); }); mDisplayManager = requireNonNull(context.getSystemService(DisplayManager.class)); @@ -341,6 +343,7 @@ public class ScreenshotController { mContext = (WindowContext) displayContext.createWindowContext(TYPE_SCREENSHOT, null); mWindowManager = mContext.getSystemService(WindowManager.class); mFlags = flags; + mActionExecutor = actionExecutor; mAccessibilityManager = AccessibilityManager.getInstance(mContext); @@ -361,8 +364,7 @@ public class ScreenshotController { @Override public void onReceive(Context context, Intent intent) { if (ClipboardOverlayController.COPY_OVERLAY_ACTION.equals(intent.getAction())) { - mUiEventLogger.log(SCREENSHOT_DISMISSED_OTHER); - dismissScreenshot(false); + dismissScreenshot(SCREENSHOT_DISMISSED_OTHER); } } }; @@ -410,24 +412,20 @@ public class ScreenshotController { /** * Clears current screenshot */ - void dismissScreenshot(boolean immediate) { + void dismissScreenshot(ScreenshotEvent event) { if (DEBUG_DISMISS) { - Log.d(TAG, "dismissScreenshot(immediate=" + immediate + ")"); + Log.d(TAG, "dismissScreenshot"); } // If we're already animating out, don't restart the animation - // (but do obey an immediate dismissal) - if (!immediate && mScreenshotView.isDismissing()) { + if (mScreenshotView.isDismissing()) { if (DEBUG_DISMISS) { Log.v(TAG, "Already dismissing, ignoring duplicate command"); } return; } + mUiEventLogger.log(event, 0, mPackageName); mScreenshotHandler.cancelTimeout(); - if (immediate) { - finishDismiss(); - } else { - mScreenshotView.animateDismissal(); - } + mScreenshotView.animateDismissal(); } boolean isPendingSharedTransition() { @@ -492,7 +490,7 @@ public class ScreenshotController { // TODO(159460485): Remove this when focus is handled properly in the system setWindowFocusable(false); } - }); + }, mActionExecutor, mFlags); mScreenshotView.setDefaultTimeoutMillis(mScreenshotHandler.getDefaultTimeoutMillis()); mScreenshotView.setOnKeyListener((v, keyCode, event) -> { @@ -500,7 +498,7 @@ public class ScreenshotController { if (DEBUG_INPUT) { Log.d(TAG, "onKeyEvent: KeyEvent.KEYCODE_BACK"); } - dismissScreenshot(false); + dismissScreenshot(SCREENSHOT_DISMISSED_OTHER); return true; } return false; diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotCrossProfileService.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotCrossProfileService.kt new file mode 100644 index 000000000000..2e6c7567259f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotCrossProfileService.kt @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.screenshot + +import android.app.Service +import android.content.Intent +import android.os.Bundle +import android.os.IBinder +import android.util.Log + +/** + * If a screenshot is saved to the work profile, any intents that grant access to the screenshot + * must come from a service running as the work profile user. This service is meant to be started as + * the desired user and just startActivity for the given intent. + */ +class ScreenshotCrossProfileService : Service() { + + private val mBinder: IBinder = + object : ICrossProfileService.Stub() { + override fun launchIntent(intent: Intent, bundle: Bundle) { + startActivity(intent, bundle) + } + } + + override fun onBind(intent: Intent): IBinder? { + Log.d(TAG, "onBind: $intent") + return mBinder + } + + companion object { + const val TAG = "ScreenshotProxyService" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt index 793085a60133..c41e2bc14afc 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt @@ -20,13 +20,16 @@ import android.content.Intent import android.os.IBinder import android.util.Log import com.android.systemui.shade.ShadeExpansionStateManager +import com.android.systemui.statusbar.phone.CentralSurfaces +import java.util.Optional import javax.inject.Inject /** * Provides state from the main SystemUI process on behalf of the Screenshot process. */ internal class ScreenshotProxyService @Inject constructor( - private val mExpansionMgr: ShadeExpansionStateManager + private val mExpansionMgr: ShadeExpansionStateManager, + private val mCentralSurfacesOptional: Optional<CentralSurfaces>, ) : Service() { private val mBinder: IBinder = object : IScreenshotProxy.Stub() { @@ -38,6 +41,20 @@ internal class ScreenshotProxyService @Inject constructor( Log.d(TAG, "isNotificationShadeExpanded(): $expanded") return expanded } + + override fun dismissKeyguard(callback: IOnDoneCallback) { + if (mCentralSurfacesOptional.isPresent) { + mCentralSurfacesOptional.get().executeRunnableDismissingKeyguard( + Runnable { + callback.onDone(true) + }, null, + true /* dismissShade */, true /* afterKeyguardGone */, + true /* deferred */ + ) + } else { + callback.onDone(false) + } + } } override fun onBind(intent: Intent): IBinder? { diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java index be41a6b0d376..26cbcbf5214f 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java @@ -87,6 +87,8 @@ import androidx.constraintlayout.widget.ConstraintLayout; import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.logging.UiEventLogger; import com.android.systemui.R; +import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.flags.Flags; import com.android.systemui.screenshot.ScreenshotController.SavedImageData.ActionTransition; import com.android.systemui.shared.system.InputChannelCompat; import com.android.systemui.shared.system.InputMonitorCompat; @@ -168,6 +170,8 @@ public class ScreenshotView extends FrameLayout implements private final InteractionJankMonitor mInteractionJankMonitor; private long mDefaultTimeoutOfTimeoutHandler; + private ActionIntentExecutor mActionExecutor; + private FeatureFlags mFlags; private enum PendingInteraction { PREVIEW, @@ -422,9 +426,12 @@ public class ScreenshotView extends FrameLayout implements * Note: must be called before any other (non-constructor) method or null pointer exceptions * may occur. */ - void init(UiEventLogger uiEventLogger, ScreenshotViewCallback callbacks) { + void init(UiEventLogger uiEventLogger, ScreenshotViewCallback callbacks, + ActionIntentExecutor actionExecutor, FeatureFlags flags) { mUiEventLogger = uiEventLogger; mCallbacks = callbacks; + mActionExecutor = actionExecutor; + mFlags = flags; } void setScreenshot(Bitmap bitmap, Insets screenInsets) { @@ -759,18 +766,37 @@ public class ScreenshotView extends FrameLayout implements void setChipIntents(ScreenshotController.SavedImageData imageData) { mShareChip.setOnClickListener(v -> { mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SHARE_TAPPED, 0, mPackageName); - startSharedTransition( - imageData.shareTransition.get()); + if (mFlags.isEnabled(Flags.SCREENSHOT_WORK_PROFILE_POLICY)) { + mActionExecutor.launchIntentAsync(ActionIntentCreator.INSTANCE.createShareIntent( + imageData.uri, imageData.subject), + imageData.shareTransition.get().bundle, + imageData.owner.getIdentifier(), false); + } else { + startSharedTransition(imageData.shareTransition.get()); + } }); mEditChip.setOnClickListener(v -> { mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_EDIT_TAPPED, 0, mPackageName); - startSharedTransition( - imageData.editTransition.get()); + if (mFlags.isEnabled(Flags.SCREENSHOT_WORK_PROFILE_POLICY)) { + mActionExecutor.launchIntentAsync( + ActionIntentCreator.INSTANCE.createEditIntent(imageData.uri, mContext), + imageData.editTransition.get().bundle, + imageData.owner.getIdentifier(), true); + } else { + startSharedTransition(imageData.editTransition.get()); + } }); mScreenshotPreview.setOnClickListener(v -> { mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_PREVIEW_TAPPED, 0, mPackageName); - startSharedTransition( - imageData.editTransition.get()); + if (mFlags.isEnabled(Flags.SCREENSHOT_WORK_PROFILE_POLICY)) { + mActionExecutor.launchIntentAsync( + ActionIntentCreator.INSTANCE.createEditIntent(imageData.uri, mContext), + imageData.editTransition.get().bundle, + imageData.owner.getIdentifier(), true); + } else { + startSharedTransition( + imageData.editTransition.get()); + } }); if (mQuickShareChip != null) { mQuickShareChip.setPendingIntent(imageData.quickShareAction.actionIntent, diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java index a4a59ce52c7a..2176825d8b38 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java @@ -89,8 +89,7 @@ public class TakeScreenshotService extends Service { Log.d(TAG, "Received ACTION_CLOSE_SYSTEM_DIALOGS"); } if (!mScreenshot.isPendingSharedTransition()) { - mUiEventLogger.log(SCREENSHOT_DISMISSED_OTHER); - mScreenshot.dismissScreenshot(false); + mScreenshot.dismissScreenshot(SCREENSHOT_DISMISSED_OTHER); } } } diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserFileManagerImpl.kt b/packages/SystemUI/src/com/android/systemui/settings/UserFileManagerImpl.kt index ad073c073ed8..d450afa59c7d 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/UserFileManagerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/settings/UserFileManagerImpl.kt @@ -42,11 +42,11 @@ import javax.inject.Inject @SysUISingleton class UserFileManagerImpl @Inject constructor( // Context of system process and system user. - val context: Context, + private val context: Context, val userManager: UserManager, val broadcastDispatcher: BroadcastDispatcher, @Background val backgroundExecutor: DelayableExecutor -) : UserFileManager, CoreStartable(context) { +) : UserFileManager, CoreStartable { companion object { private const val FILES = "files" @VisibleForTesting internal const val SHARED_PREFS = "shared_prefs" diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index e3318127af93..200579ad17ce 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -6051,6 +6051,10 @@ public final class NotificationPanelViewController { mShadeLog.logMotionEvent(event, "onTouch: PulseExpansionHandler handled event"); return true; } + if (mPulsing) { + mShadeLog.logMotionEvent(event, "onTouch: eat touch, device pulsing"); + return true; + } if (mListenForHeadsUp && !mHeadsUpTouchHelper.isTrackingHeadsUp() && !mNotificationStackScrollLayoutController.isLongPressInProgress() && mHeadsUpTouchHelper.onInterceptTouchEvent(event)) { @@ -6073,7 +6077,7 @@ public final class NotificationPanelViewController { } handled |= handleTouch(event); - return !mDozing || mPulsing || handled; + return !mDozing || handled; } private boolean handleTouch(MotionEvent event) { diff --git a/packages/SystemUI/src/com/android/systemui/shade/OWNERS b/packages/SystemUI/src/com/android/systemui/shade/OWNERS index 7dc9dc7efeb7..49709a8c7674 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/OWNERS +++ b/packages/SystemUI/src/com/android/systemui/shade/OWNERS @@ -1,3 +1,6 @@ +justinweir@google.com +syeonlee@google.com + per-file *Notification* = set noparent per-file *Notification* = file:../statusbar/notification/OWNERS diff --git a/packages/SystemUI/src/com/android/systemui/shortcut/ShortcutKeyDispatcher.java b/packages/SystemUI/src/com/android/systemui/shortcut/ShortcutKeyDispatcher.java index 6abf339685e4..ff26766fba66 100644 --- a/packages/SystemUI/src/com/android/systemui/shortcut/ShortcutKeyDispatcher.java +++ b/packages/SystemUI/src/com/android/systemui/shortcut/ShortcutKeyDispatcher.java @@ -32,10 +32,10 @@ import javax.inject.Inject; * Dispatches shortcut to System UI components */ @SysUISingleton -public class ShortcutKeyDispatcher extends CoreStartable - implements ShortcutKeyServiceProxy.Callbacks { +public class ShortcutKeyDispatcher implements CoreStartable, ShortcutKeyServiceProxy.Callbacks { private static final String TAG = "ShortcutKeyDispatcher"; + private final Context mContext; private ShortcutKeyServiceProxy mShortcutKeyServiceProxy = new ShortcutKeyServiceProxy(this); private IWindowManager mWindowManagerService = WindowManagerGlobal.getWindowManagerService(); @@ -50,7 +50,7 @@ public class ShortcutKeyDispatcher extends CoreStartable @Inject public ShortcutKeyDispatcher(Context context) { - super(context); + mContext = context; } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java index c290ce260cc6..184dc253bfc6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java @@ -189,7 +189,6 @@ public class NotificationLockscreenUserManagerImpl implements protected NotificationPresenter mPresenter; protected ContentObserver mLockscreenSettingsObserver; protected ContentObserver mSettingsObserver; - private boolean mHideSilentNotificationsOnLockscreen; @Inject public NotificationLockscreenUserManagerImpl(Context context, @@ -266,12 +265,6 @@ public class NotificationLockscreenUserManagerImpl implements UserHandle.USER_ALL); mContext.getContentResolver().registerContentObserver( - mSecureSettings.getUriFor(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS), - true, - mLockscreenSettingsObserver, - UserHandle.USER_ALL); - - mContext.getContentResolver().registerContentObserver( Settings.Global.getUriFor(Settings.Global.ZEN_MODE), false, mSettingsObserver); @@ -340,9 +333,6 @@ public class NotificationLockscreenUserManagerImpl implements final boolean allowedByDpm = (dpmFlags & DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS) == 0; - mHideSilentNotificationsOnLockscreen = mSecureSettings.getIntForUser( - Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, 1, mCurrentUserId) == 0; - setShowLockscreenNotifications(show && allowedByDpm); if (ENABLE_LOCK_SCREEN_ALLOW_REMOTE_INPUT) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt b/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt index bbff0466cf50..8222c9d9ba59 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt @@ -52,7 +52,10 @@ import javax.inject.Inject import kotlin.math.max /** - * A utility class to enable the downward swipe on when pulsing. + * A utility class that handles notification panel expansion when a user swipes downward on a + * notification from the pulsing state. + * If face-bypass is enabled, the user can swipe down anywhere on the screen (not just from a + * notification) to trigger the notification panel expansion. */ @SysUISingleton class PulseExpansionHandler @Inject @@ -62,7 +65,7 @@ constructor( private val bypassController: KeyguardBypassController, private val headsUpManager: HeadsUpManagerPhone, private val roundnessManager: NotificationRoundnessManager, - private val configurationController: ConfigurationController, + configurationController: ConfigurationController, private val statusBarStateController: StatusBarStateController, private val falsingManager: FalsingManager, private val lockscreenShadeTransitionController: LockscreenShadeTransitionController, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java index 59022c0ffbf2..822840dfd86b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java @@ -66,11 +66,12 @@ import javax.inject.Inject; * splitted screen. */ @SysUISingleton -public class InstantAppNotifier extends CoreStartable - implements CommandQueue.Callbacks, KeyguardStateController.Callback { +public class InstantAppNotifier + implements CoreStartable, CommandQueue.Callbacks, KeyguardStateController.Callback { private static final String TAG = "InstantAppNotifier"; public static final int NUM_TASKS_FOR_INSTANT_APP_INFO = 5; + private final Context mContext; private final Handler mHandler = new Handler(); private final Executor mUiBgExecutor; private final ArraySet<Pair<String, Integer>> mCurrentNotifs = new ArraySet<>(); @@ -83,7 +84,7 @@ public class InstantAppNotifier extends CoreStartable CommandQueue commandQueue, @UiBackground Executor uiBgExecutor, KeyguardStateController keyguardStateController) { - super(context); + mContext = context; mCommandQueue = commandQueue; mUiBgExecutor = uiBgExecutor; mKeyguardStateController = keyguardStateController; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinder.java index 6f41425b506d..9a7610ddd354 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinder.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinder.java @@ -114,7 +114,18 @@ public class HeadsUpViewBinder { */ public void unbindHeadsUpView(NotificationEntry entry) { abortBindCallback(entry); - mStage.getStageParams(entry).markContentViewsFreeable(FLAG_CONTENT_VIEW_HEADS_UP); + + // params may be null if the notification was already removed from the collection but we let + // it stick around during a launch animation. In this case, the heads up view has already + // been unbound, so we don't need to unbind it. + // TODO(b/253081345): Change this back to getStageParams and remove null check. + RowContentBindParams params = mStage.tryGetStageParams(entry); + if (params == null) { + mLogger.entryBindStageParamsNullOnUnbind(entry); + return; + } + + params.markContentViewsFreeable(FLAG_CONTENT_VIEW_HEADS_UP); mLogger.entryContentViewMarkedFreeable(entry); mStage.requestRebind(entry, e -> mLogger.entryUnbound(e)); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderLogger.kt index d1feaa05c653..5dbec8dcba20 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderLogger.kt @@ -47,6 +47,14 @@ class HeadsUpViewBinderLogger @Inject constructor(@NotificationHeadsUpLog val bu "start unbinding heads up entry $str1 " }) } + + fun entryBindStageParamsNullOnUnbind(entry: NotificationEntry) { + buffer.log(TAG, INFO, { + str1 = entry.logKey + }, { + "heads up entry bind stage params null on unbind $str1 " + }) + } } private const val TAG = "HeadsUpViewBinder" diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt index 3c10dd5636eb..e6dbcee10f60 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt @@ -72,7 +72,6 @@ private interface KeyguardNotificationVisibilityProviderImplModule { @SysUISingleton private class KeyguardNotificationVisibilityProviderImpl @Inject constructor( - context: Context, @Main private val handler: Handler, private val keyguardStateController: KeyguardStateController, private val lockscreenUserManager: NotificationLockscreenUserManager, @@ -82,7 +81,7 @@ private class KeyguardNotificationVisibilityProviderImpl @Inject constructor( private val broadcastDispatcher: BroadcastDispatcher, private val secureSettings: SecureSettings, private val globalSettings: GlobalSettings -) : CoreStartable(context), KeyguardNotificationVisibilityProvider { +) : CoreStartable, KeyguardNotificationVisibilityProvider { private val showSilentNotifsUri = secureSettings.getUriFor(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS) private val onStateChangedListeners = ListenerSet<Consumer<String>>() diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BindStage.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BindStage.java index 7c41800d880d..d626c18e46f5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BindStage.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BindStage.java @@ -21,6 +21,7 @@ import android.util.ArrayMap; import android.util.Log; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import com.android.systemui.statusbar.notification.collection.NotificationEntry; @@ -64,7 +65,7 @@ public abstract class BindStage<Params> extends BindRequester { * Get the stage parameters for the entry. Clients should use this to modify how the stage * handles the notification content. */ - public final Params getStageParams(@NonNull NotificationEntry entry) { + public final @NonNull Params getStageParams(@NonNull NotificationEntry entry) { Params params = mContentParams.get(entry); if (params == null) { // TODO: This should throw an exception but there are some cases of re-entrant calls @@ -79,6 +80,17 @@ public abstract class BindStage<Params> extends BindRequester { return params; } + // TODO(b/253081345): Remove this method. + /** + * Get the stage parameters for the entry, or null if there are no stage parameters for the + * entry. + * + * @see #getStageParams(NotificationEntry) + */ + public final @Nullable Params tryGetStageParams(@NonNull NotificationEntry entry) { + return mContentParams.get(entry); + } + /** * Create a params entry for the notification for this stage. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java index 9958ec7e87fb..34935db72467 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -91,7 +91,6 @@ import android.util.EventLog; import android.util.IndentingPrintWriter; import android.util.Log; import android.util.MathUtils; -import android.util.Slog; import android.view.Display; import android.view.IRemoteAnimationRunner; import android.view.IWindowManager; @@ -270,8 +269,7 @@ import dagger.Lazy; * </b> */ @SysUISingleton -public class CentralSurfacesImpl extends CoreStartable implements - CentralSurfaces { +public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { private static final String BANNER_ACTION_CANCEL = "com.android.systemui.statusbar.banner_action_cancel"; @@ -307,6 +305,7 @@ public class CentralSurfacesImpl extends CoreStartable implements ONLY_CORE_APPS = onlyCoreApps; } + private final Context mContext; private final LockscreenShadeTransitionController mLockscreenShadeTransitionController; private CentralSurfacesCommandQueueCallbacks mCommandQueueCallbacks; private float mTransitionToFullShadeProgress = 0f; @@ -764,7 +763,7 @@ public class CentralSurfacesImpl extends CoreStartable implements DeviceStateManager deviceStateManager, WiredChargingRippleController wiredChargingRippleController, IDreamManager dreamManager) { - super(context); + mContext = context; mNotificationsController = notificationsController; mFragmentService = fragmentService; mLightBarController = lightBarController; @@ -3550,7 +3549,7 @@ public class CentralSurfacesImpl extends CoreStartable implements public void setBouncerShowingOverDream(boolean bouncerShowingOverDream) { mBouncerShowingOverDream = bouncerShowingOverDream; } - + /** * Propagate the bouncer state to status bar components. * @@ -3812,8 +3811,7 @@ public class CentralSurfacesImpl extends CoreStartable implements if (mDevicePolicyManager.getCameraDisabled(null, mLockscreenUserManager.getCurrentUserId())) { return false; - } else if (mStatusBarKeyguardViewManager == null - || (isKeyguardShowing() && isKeyguardSecure())) { + } else if (isKeyguardShowing() && isKeyguardSecure()) { // Check if the admin has disabled the camera specifically for the keyguard return (mDevicePolicyManager.getKeyguardDisabledFeatures(null, mLockscreenUserManager.getCurrentUserId()) @@ -4221,14 +4219,6 @@ public class CentralSurfacesImpl extends CoreStartable implements @Override public boolean isKeyguardSecure() { - if (mStatusBarKeyguardViewManager == null) { - // startKeyguard() hasn't been called yet, so we don't know. - // Make sure anything that needs to know isKeyguardSecure() checks and re-checks this - // value onVisibilityChanged(). - Slog.w(TAG, "isKeyguardSecure() called before startKeyguard(), returning false", - new Throwable()); - return false; - } return mStatusBarKeyguardViewManager.isSecure(); } @Override @@ -4288,11 +4278,6 @@ public class CentralSurfacesImpl extends CoreStartable implements .Callback() { @Override public void onFinished() { - if (mStatusBarKeyguardViewManager == null) { - Log.w(TAG, "Tried to notify keyguard visibility when " - + "mStatusBarKeyguardViewManager was null"); - return; - } if (mKeyguardStateController.isKeyguardFadingAway()) { mStatusBarKeyguardViewManager.onKeyguardFadedAway(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt index e3e85728ff83..5e26cf062b58 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt @@ -47,7 +47,7 @@ class KeyguardLiftController @Inject constructor( private val asyncSensorManager: AsyncSensorManager, private val keyguardUpdateMonitor: KeyguardUpdateMonitor, private val dumpManager: DumpManager -) : Dumpable, CoreStartable(context) { +) : Dumpable, CoreStartable { private val pickupSensor = asyncSensorManager.getDefaultSensor(Sensor.TYPE_PICK_UP_GESTURE) private var isListening = false diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt index 5b2d69564585..2f0ebf752a23 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt @@ -35,8 +35,8 @@ protected constructor( protected val controller: UserSwitcherController, ) : BaseAdapter() { - protected open val users: ArrayList<UserRecord> - get() = controller.users + protected open val users: List<UserRecord> + get() = controller.users.filter { !controller.isKeyguardShowing || !it.isRestricted } init { controller.addAdapter(WeakReference(this)) @@ -112,6 +112,7 @@ protected constructor( item.isGuest, item.isAddSupervisedUser, isTablet, + item.isManageUsers, ) return checkNotNull(context.getDrawable(iconRes)) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java index 494a4bbbdf9f..c1506541229d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java @@ -53,6 +53,7 @@ import com.android.systemui.user.data.source.UserRecord; import com.android.systemui.util.ViewController; import java.util.ArrayList; +import java.util.List; import javax.inject.Inject; @@ -456,7 +457,7 @@ public class KeyguardUserSwitcherController extends ViewController<KeyguardUserS } void refreshUserOrder() { - ArrayList<UserRecord> users = super.getUsers(); + List<UserRecord> users = super.getUsers(); mUsersOrdered = new ArrayList<>(users.size()); for (int i = 0; i < users.size(); i++) { UserRecord record = users.get(i); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerImpl.kt index af39eeed26b0..935fc7f10198 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerImpl.kt @@ -249,7 +249,7 @@ constructor( override fun startActivity(intent: Intent) { if (useInteractor) { - activityStarter.startActivity(intent, /* dismissShade= */ false) + activityStarter.startActivity(intent, /* dismissShade= */ true) } else { _oldImpl.startActivity(intent) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImpl.java index 46d2f3ac9ce4..c294c370a601 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImpl.java @@ -49,6 +49,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.logging.UiEventLogger; import com.android.internal.util.LatencyTracker; +import com.android.keyguard.KeyguardUpdateMonitor; import com.android.settingslib.users.UserCreatingDialog; import com.android.systemui.GuestResetOrExitSessionReceiver; import com.android.systemui.GuestResumeSessionReceiver; @@ -399,6 +400,16 @@ public class UserSwitcherControllerOldImpl implements UserSwitcherController { records.add(userRecord); } + if (canManageUsers()) { + records.add(LegacyUserDataHelper.createRecord( + mContext, + KeyguardUpdateMonitor.getCurrentUser(), + UserActionModel.NAVIGATE_TO_USER_MANAGEMENT, + /* isRestricted= */ false, + /* isSwitchToEnabled= */ true + )); + } + mUiExecutor.execute(() -> { if (records != null) { mUsers = records; @@ -438,6 +449,14 @@ public class UserSwitcherControllerOldImpl implements UserSwitcherController { && mUserManager.canAddMoreUsers(UserManager.USER_TYPE_FULL_SECONDARY); } + @VisibleForTesting + boolean canManageUsers() { + UserInfo currentUser = mUserTracker.getUserInfo(); + return mUserSwitcherEnabled + && ((currentUser != null && currentUser.isAdmin()) + || mAddUsersFromLockScreen.getValue()); + } + private boolean createIsRestricted() { return !mAddUsersFromLockScreen.getValue(); } @@ -525,6 +544,8 @@ public class UserSwitcherControllerOldImpl implements UserSwitcherController { showAddUserDialog(dialogShower); } else if (record.isAddSupervisedUser) { startSupervisedUserActivity(); + } else if (record.isManageUsers) { + startActivity(new Intent(Settings.ACTION_USER_SETTINGS)); } else { onUserListItemClicked(record.info.id, record, dialogShower); } @@ -984,7 +1005,7 @@ public class UserSwitcherControllerOldImpl implements UserSwitcherController { @Override public void startActivity(Intent intent) { - mActivityStarter.startActivity(intent, true); + mActivityStarter.startActivity(intent, /* dismissShade= */ true); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java index 09298b60ff51..b1b8341d9584 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java @@ -37,19 +37,20 @@ import dagger.Lazy; * Serves as a collection of UI components, rather than showing its own UI. */ @SysUISingleton -public class TvStatusBar extends CoreStartable implements CommandQueue.Callbacks { +public class TvStatusBar implements CoreStartable, CommandQueue.Callbacks { private static final String ACTION_SHOW_PIP_MENU = "com.android.wm.shell.pip.tv.notification.action.SHOW_PIP_MENU"; private static final String SYSTEMUI_PERMISSION = "com.android.systemui.permission.SELF"; + private final Context mContext; private final CommandQueue mCommandQueue; private final Lazy<AssistManager> mAssistManagerLazy; @Inject public TvStatusBar(Context context, CommandQueue commandQueue, Lazy<AssistManager> assistManagerLazy) { - super(context); + mContext = context; mCommandQueue = commandQueue; mAssistManagerLazy = assistManagerLazy; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/VpnStatusObserver.kt b/packages/SystemUI/src/com/android/systemui/statusbar/tv/VpnStatusObserver.kt index c1997446c126..b938c9002d90 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/VpnStatusObserver.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/VpnStatusObserver.kt @@ -35,9 +35,9 @@ import javax.inject.Inject */ @SysUISingleton class VpnStatusObserver @Inject constructor( - context: Context, + private val context: Context, private val securityController: SecurityController -) : CoreStartable(context), +) : CoreStartable, SecurityController.SecurityControllerCallback { private var vpnConnected = false @@ -102,7 +102,7 @@ class VpnStatusObserver @Inject constructor( .apply { vpnName?.let { setContentText( - mContext.getString( + context.getString( R.string.notification_disclosure_vpn_text, it ) ) @@ -111,23 +111,23 @@ class VpnStatusObserver @Inject constructor( .build() private fun createVpnConnectedNotificationBuilder() = - Notification.Builder(mContext, NOTIFICATION_CHANNEL_TV_VPN) + Notification.Builder(context, NOTIFICATION_CHANNEL_TV_VPN) .setSmallIcon(vpnIconId) .setVisibility(Notification.VISIBILITY_PUBLIC) .setCategory(Notification.CATEGORY_SYSTEM) .extend(Notification.TvExtender()) .setOngoing(true) - .setContentTitle(mContext.getString(R.string.notification_vpn_connected)) - .setContentIntent(VpnConfig.getIntentForStatusPanel(mContext)) + .setContentTitle(context.getString(R.string.notification_vpn_connected)) + .setContentIntent(VpnConfig.getIntentForStatusPanel(context)) private fun createVpnDisconnectedNotification() = - Notification.Builder(mContext, NOTIFICATION_CHANNEL_TV_VPN) + Notification.Builder(context, NOTIFICATION_CHANNEL_TV_VPN) .setSmallIcon(vpnIconId) .setVisibility(Notification.VISIBILITY_PUBLIC) .setCategory(Notification.CATEGORY_SYSTEM) .extend(Notification.TvExtender()) .setTimeoutAfter(VPN_DISCONNECTED_NOTIFICATION_TIMEOUT_MS) - .setContentTitle(mContext.getString(R.string.notification_vpn_disconnected)) + .setContentTitle(context.getString(R.string.notification_vpn_disconnected)) .build() companion object { @@ -137,4 +137,4 @@ class VpnStatusObserver @Inject constructor( private const val TAG = "TvVpnNotification" private const val VPN_DISCONNECTED_NOTIFICATION_TIMEOUT_MS = 5_000L } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/notifications/TvNotificationHandler.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/notifications/TvNotificationHandler.java index 8026ba517820..b92725bd7cf7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/notifications/TvNotificationHandler.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/notifications/TvNotificationHandler.java @@ -18,13 +18,13 @@ package com.android.systemui.statusbar.tv.notifications; import android.annotation.Nullable; import android.app.Notification; -import android.content.Context; import android.service.notification.NotificationListenerService; import android.service.notification.StatusBarNotification; import android.util.Log; import android.util.SparseArray; import com.android.systemui.CoreStartable; +import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.statusbar.NotificationListener; import javax.inject.Inject; @@ -32,7 +32,8 @@ import javax.inject.Inject; /** * Keeps track of the notifications on TV. */ -public class TvNotificationHandler extends CoreStartable implements +@SysUISingleton +public class TvNotificationHandler implements CoreStartable, NotificationListener.NotificationHandler { private static final String TAG = "TvNotificationHandler"; private final NotificationListener mNotificationListener; @@ -41,8 +42,7 @@ public class TvNotificationHandler extends CoreStartable implements private Listener mUpdateListener; @Inject - public TvNotificationHandler(Context context, NotificationListener notificationListener) { - super(context); + public TvNotificationHandler(NotificationListener notificationListener) { mNotificationListener = notificationListener; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/notifications/TvNotificationPanel.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/notifications/TvNotificationPanel.java index 892fedcc8ce2..dbbd0b8613de 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/notifications/TvNotificationPanel.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/notifications/TvNotificationPanel.java @@ -35,14 +35,15 @@ import javax.inject.Inject; * Offers control methods for the notification panel handler on TV devices. */ @SysUISingleton -public class TvNotificationPanel extends CoreStartable implements CommandQueue.Callbacks { +public class TvNotificationPanel implements CoreStartable, CommandQueue.Callbacks { private static final String TAG = "TvNotificationPanel"; + private final Context mContext; private final CommandQueue mCommandQueue; private final String mNotificationHandlerPackage; @Inject public TvNotificationPanel(Context context, CommandQueue commandQueue) { - super(context); + mContext = context; mCommandQueue = commandQueue; mNotificationHandlerPackage = mContext.getResources().getString( com.android.internal.R.string.config_notificationHandlerPackage); diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt index 4450b76a878c..5cbdf7c43a12 100644 --- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt +++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt @@ -62,7 +62,7 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora @LayoutRes private val viewLayoutRes: Int, private val windowTitle: String, private val wakeReason: String, -) : CoreStartable(context) { +) : CoreStartable { /** * Window layout params that will be used as a starting point for the [windowLayoutParams] of * all subclasses. diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java index a345d99b47c6..3d56f2317660 100644 --- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java +++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java @@ -100,7 +100,7 @@ import javax.inject.Inject; * associated work profiles */ @SysUISingleton -public class ThemeOverlayController extends CoreStartable implements Dumpable { +public class ThemeOverlayController implements CoreStartable, Dumpable { protected static final String TAG = "ThemeOverlayController"; private static final boolean DEBUG = true; @@ -114,6 +114,7 @@ public class ThemeOverlayController extends CoreStartable implements Dumpable { private final SecureSettings mSecureSettings; private final Executor mMainExecutor; private final Handler mBgHandler; + private final Context mContext; private final boolean mIsMonetEnabled; private final UserTracker mUserTracker; private final DeviceProvisionedController mDeviceProvisionedController; @@ -361,8 +362,7 @@ public class ThemeOverlayController extends CoreStartable implements Dumpable { UserManager userManager, DeviceProvisionedController deviceProvisionedController, UserTracker userTracker, DumpManager dumpManager, FeatureFlags featureFlags, @Main Resources resources, WakefulnessLifecycle wakefulnessLifecycle) { - super(context); - + mContext = context; mIsMonetEnabled = featureFlags.isEnabled(Flags.MONET); mDeviceProvisionedController = deviceProvisionedController; mBroadcastDispatcher = broadcastDispatcher; diff --git a/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java b/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java index 9eb34a42a0a9..ed14c8ad150c 100644 --- a/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java +++ b/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java @@ -50,13 +50,14 @@ import javax.inject.Inject; * Controls display of text toasts. */ @SysUISingleton -public class ToastUI extends CoreStartable implements CommandQueue.Callbacks { +public class ToastUI implements CoreStartable, CommandQueue.Callbacks { // values from NotificationManagerService#LONG_DELAY and NotificationManagerService#SHORT_DELAY private static final int TOAST_LONG_TIME = 3500; // 3.5 seconds private static final int TOAST_SHORT_TIME = 2000; // 2 seconds private static final String TAG = "ToastUI"; + private final Context mContext; private final CommandQueue mCommandQueue; private final INotificationManager mNotificationManager; private final IAccessibilityManager mIAccessibilityManager; @@ -90,7 +91,7 @@ public class ToastUI extends CoreStartable implements CommandQueue.Callbacks { @Nullable IAccessibilityManager accessibilityManager, ToastFactory toastFactory, ToastLogger toastLogger ) { - super(context); + mContext = context; mCommandQueue = commandQueue; mNotificationManager = notificationManager; mIAccessibilityManager = accessibilityManager; @@ -179,7 +180,7 @@ public class ToastUI extends CoreStartable implements CommandQueue.Callbacks { } @Override - protected void onConfigurationChanged(Configuration newConfig) { + public void onConfigurationChanged(Configuration newConfig) { if (newConfig.orientation != mOrientation) { mOrientation = newConfig.orientation; if (mToast != null) { diff --git a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java index 3ce5ca3d0301..10a09dd169e8 100644 --- a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java @@ -203,9 +203,9 @@ public abstract class TvSystemUIModule { @Provides @SysUISingleton - static TvNotificationHandler provideTvNotificationHandler(Context context, + static TvNotificationHandler provideTvNotificationHandler( NotificationListener notificationListener) { - return new TvNotificationHandler(context, notificationListener); + return new TvNotificationHandler(notificationListener); } /** diff --git a/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java b/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java index 4dc78f9ec8a6..bf706735d531 100644 --- a/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java +++ b/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java @@ -21,7 +21,6 @@ import android.app.Notification; import android.app.Notification.Action; import android.app.NotificationManager; import android.app.PendingIntent; -import android.app.ActivityManager; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -56,11 +55,12 @@ import javax.inject.Inject; /** */ @SysUISingleton -public class StorageNotification extends CoreStartable { +public class StorageNotification implements CoreStartable { private static final String TAG = "StorageNotification"; private static final String ACTION_SNOOZE_VOLUME = "com.android.systemui.action.SNOOZE_VOLUME"; private static final String ACTION_FINISH_WIZARD = "com.android.systemui.action.FINISH_WIZARD"; + private final Context mContext; // TODO: delay some notifications to avoid bumpy fast operations @@ -69,7 +69,7 @@ public class StorageNotification extends CoreStartable { @Inject public StorageNotification(Context context) { - super(context); + mContext = context; } private static class MoveInfo { diff --git a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt index 108ab43977e9..7f1195b78c77 100644 --- a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt @@ -16,426 +16,41 @@ package com.android.systemui.user -import android.content.BroadcastReceiver -import android.content.Context -import android.content.Intent -import android.content.IntentFilter -import android.graphics.drawable.BitmapDrawable -import android.graphics.drawable.Drawable -import android.graphics.drawable.GradientDrawable -import android.graphics.drawable.LayerDrawable import android.os.Bundle -import android.os.UserManager -import android.provider.Settings -import android.util.Log -import android.view.LayoutInflater -import android.view.MotionEvent import android.view.View -import android.view.ViewGroup -import android.widget.AdapterView -import android.widget.ArrayAdapter -import android.widget.ImageView -import android.widget.TextView -import android.window.OnBackInvokedCallback -import android.window.OnBackInvokedDispatcher import androidx.activity.ComponentActivity -import androidx.constraintlayout.helper.widget.Flow import androidx.lifecycle.ViewModelProvider -import com.android.internal.annotations.VisibleForTesting -import com.android.internal.util.UserIcons -import com.android.settingslib.Utils -import com.android.systemui.Gefingerpoken import com.android.systemui.R -import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.classifier.FalsingCollector -import com.android.systemui.flags.FeatureFlags -import com.android.systemui.flags.Flags -import com.android.systemui.plugins.FalsingManager -import com.android.systemui.plugins.FalsingManager.LOW_PENALTY -import com.android.systemui.settings.UserTracker -import com.android.systemui.statusbar.policy.BaseUserSwitcherAdapter -import com.android.systemui.statusbar.policy.UserSwitcherController -import com.android.systemui.user.data.source.UserRecord import com.android.systemui.user.ui.binder.UserSwitcherViewBinder import com.android.systemui.user.ui.viewmodel.UserSwitcherViewModel import dagger.Lazy import javax.inject.Inject -import kotlin.math.ceil - -private const val USER_VIEW = "user_view" /** Support a fullscreen user switcher */ open class UserSwitcherActivity @Inject constructor( - private val userSwitcherController: UserSwitcherController, - private val broadcastDispatcher: BroadcastDispatcher, private val falsingCollector: FalsingCollector, - private val falsingManager: FalsingManager, - private val userManager: UserManager, - private val userTracker: UserTracker, - private val flags: FeatureFlags, private val viewModelFactory: Lazy<UserSwitcherViewModel.Factory>, ) : ComponentActivity() { - private lateinit var parent: UserSwitcherRootView - private lateinit var broadcastReceiver: BroadcastReceiver - private var popupMenu: UserSwitcherPopupMenu? = null - private lateinit var addButton: View - private var addUserRecords = mutableListOf<UserRecord>() - private val onBackCallback = OnBackInvokedCallback { finish() } - private val userSwitchedCallback: UserTracker.Callback = - object : UserTracker.Callback { - override fun onUserChanged(newUser: Int, userContext: Context) { - finish() - } - } - // When the add users options become available, insert another option to manage users - private val manageUserRecord = - UserRecord( - null /* info */, - null /* picture */, - false /* isGuest */, - false /* isCurrent */, - false /* isAddUser */, - false /* isRestricted */, - false /* isSwitchToEnabled */, - false /* isAddSupervisedUser */ - ) - - private val adapter: UserAdapter by lazy { UserAdapter() } - override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - createActivity() - } - - @VisibleForTesting - fun createActivity() { setContentView(R.layout.user_switcher_fullscreen) window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) - if (isUsingModernArchitecture()) { - Log.d(TAG, "Using modern architecture.") - val viewModel = - ViewModelProvider(this, viewModelFactory.get())[UserSwitcherViewModel::class.java] - UserSwitcherViewBinder.bind( - view = requireViewById(R.id.user_switcher_root), - viewModel = viewModel, - lifecycleOwner = this, - layoutInflater = layoutInflater, - falsingCollector = falsingCollector, - onFinish = this::finish, - ) - return - } else { - Log.d(TAG, "Not using modern architecture.") - } - - parent = requireViewById<UserSwitcherRootView>(R.id.user_switcher_root) - - parent.touchHandler = - object : Gefingerpoken { - override fun onTouchEvent(ev: MotionEvent?): Boolean { - falsingCollector.onTouchEvent(ev) - return false - } - } - - requireViewById<View>(R.id.cancel).apply { setOnClickListener { _ -> finish() } } - - addButton = - requireViewById<View>(R.id.add).apply { setOnClickListener { _ -> showPopupMenu() } } - - onBackInvokedDispatcher.registerOnBackInvokedCallback( - OnBackInvokedDispatcher.PRIORITY_DEFAULT, - onBackCallback + val viewModel = + ViewModelProvider(this, viewModelFactory.get())[UserSwitcherViewModel::class.java] + UserSwitcherViewBinder.bind( + view = requireViewById(R.id.user_switcher_root), + viewModel = viewModel, + lifecycleOwner = this, + layoutInflater = layoutInflater, + falsingCollector = falsingCollector, + onFinish = this::finish, ) - - userSwitcherController.init(parent) - initBroadcastReceiver() - - parent.post { buildUserViews() } - userTracker.addCallback(userSwitchedCallback, mainExecutor) - } - - private fun showPopupMenu() { - val items = mutableListOf<UserRecord>() - addUserRecords.forEach { items.add(it) } - - var popupMenuAdapter = - ItemAdapter( - this, - R.layout.user_switcher_fullscreen_popup_item, - layoutInflater, - { item: UserRecord -> adapter.getName(this@UserSwitcherActivity, item, true) }, - { item: UserRecord -> - adapter.findUserIcon(item, true).mutate().apply { - setTint( - resources.getColor( - R.color.user_switcher_fullscreen_popup_item_tint, - getTheme() - ) - ) - } - } - ) - popupMenuAdapter.addAll(items) - - popupMenu = - UserSwitcherPopupMenu(this).apply { - setAnchorView(addButton) - setAdapter(popupMenuAdapter) - setOnItemClickListener { parent: AdapterView<*>, view: View, pos: Int, id: Long -> - if (falsingManager.isFalseTap(LOW_PENALTY) || !view.isEnabled()) { - return@setOnItemClickListener - } - // -1 for the header - val item = popupMenuAdapter.getItem(pos - 1) - if (item == manageUserRecord) { - val i = Intent().setAction(Settings.ACTION_USER_SETTINGS) - this@UserSwitcherActivity.startActivity(i) - } else { - adapter.onUserListItemClicked(item) - } - - dismiss() - popupMenu = null - - if (!item.isAddUser) { - this@UserSwitcherActivity.finish() - } - } - - show() - } - } - - private fun buildUserViews() { - var count = 0 - var start = 0 - for (i in 0 until parent.getChildCount()) { - if (parent.getChildAt(i).getTag() == USER_VIEW) { - if (count == 0) start = i - count++ - } - } - parent.removeViews(start, count) - addUserRecords.clear() - val flow = requireViewById<Flow>(R.id.flow) - val totalWidth = parent.width - val userViewCount = adapter.getTotalUserViews() - val maxColumns = getMaxColumns(userViewCount) - val horizontalGap = - resources.getDimensionPixelSize(R.dimen.user_switcher_fullscreen_horizontal_gap) - val totalWidthOfHorizontalGap = (maxColumns - 1) * horizontalGap - val maxWidgetDiameter = (totalWidth - totalWidthOfHorizontalGap) / maxColumns - - flow.setMaxElementsWrap(maxColumns) - - for (i in 0 until adapter.getCount()) { - val item = adapter.getItem(i) - if (adapter.doNotRenderUserView(item)) { - addUserRecords.add(item) - } else { - val userView = adapter.getView(i, null, parent) - userView.requireViewById<ImageView>(R.id.user_switcher_icon).apply { - val lp = layoutParams - if (maxWidgetDiameter < lp.width) { - lp.width = maxWidgetDiameter - lp.height = maxWidgetDiameter - layoutParams = lp - } - } - - userView.setId(View.generateViewId()) - parent.addView(userView) - - // Views must have an id and a parent in order for Flow to lay them out - flow.addView(userView) - - userView.setOnClickListener { v -> - if (falsingManager.isFalseTap(LOW_PENALTY) || !v.isEnabled()) { - return@setOnClickListener - } - - if (!item.isCurrent || item.isGuest) { - adapter.onUserListItemClicked(item) - } - } - } - } - - if (!addUserRecords.isEmpty()) { - addUserRecords.add(manageUserRecord) - addButton.visibility = View.VISIBLE - } else { - addButton.visibility = View.GONE - } - } - - override fun onBackPressed() { - if (isUsingModernArchitecture()) { - return super.onBackPressed() - } - - finish() - } - - override fun onDestroy() { - super.onDestroy() - if (isUsingModernArchitecture()) { - return - } - destroyActivity() - } - - @VisibleForTesting - fun destroyActivity() { - onBackInvokedDispatcher.unregisterOnBackInvokedCallback(onBackCallback) - broadcastDispatcher.unregisterReceiver(broadcastReceiver) - userTracker.removeCallback(userSwitchedCallback) - } - - private fun initBroadcastReceiver() { - broadcastReceiver = - object : BroadcastReceiver() { - override fun onReceive(context: Context, intent: Intent) { - val action = intent.getAction() - if (Intent.ACTION_SCREEN_OFF.equals(action)) { - finish() - } - } - } - - val filter = IntentFilter() - filter.addAction(Intent.ACTION_SCREEN_OFF) - broadcastDispatcher.registerReceiver(broadcastReceiver, filter) - } - - @VisibleForTesting - fun getMaxColumns(userCount: Int): Int { - return if (userCount < 5) 4 else ceil(userCount / 2.0).toInt() - } - - private fun isUsingModernArchitecture(): Boolean { - return flags.isEnabled(Flags.MODERN_USER_SWITCHER_ACTIVITY) - } - - /** Provides views to populate the option menu. */ - private class ItemAdapter( - val parentContext: Context, - val resource: Int, - val layoutInflater: LayoutInflater, - val textGetter: (UserRecord) -> String, - val iconGetter: (UserRecord) -> Drawable - ) : ArrayAdapter<UserRecord>(parentContext, resource) { - - override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { - val item = getItem(position) - val view = convertView ?: layoutInflater.inflate(resource, parent, false) - - view.requireViewById<ImageView>(R.id.icon).apply { setImageDrawable(iconGetter(item)) } - view.requireViewById<TextView>(R.id.text).apply { setText(textGetter(item)) } - - return view - } - } - - private inner class UserAdapter : BaseUserSwitcherAdapter(userSwitcherController) { - override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { - val item = getItem(position) - var view = convertView as ViewGroup? - if (view == null) { - view = - layoutInflater.inflate(R.layout.user_switcher_fullscreen_item, parent, false) - as ViewGroup - } - (view.getChildAt(0) as ImageView).apply { setImageDrawable(getDrawable(item)) } - (view.getChildAt(1) as TextView).apply { setText(getName(getContext(), item)) } - - view.setEnabled(item.isSwitchToEnabled) - UserSwitcherController.setSelectableAlpha(view) - view.setTag(USER_VIEW) - return view - } - - override fun getName(context: Context, item: UserRecord, isTablet: Boolean): String { - return if (item == manageUserRecord) { - getString(R.string.manage_users) - } else { - super.getName(context, item, isTablet) - } - } - - fun findUserIcon(item: UserRecord, isTablet: Boolean = false): Drawable { - if (item == manageUserRecord) { - return getDrawable(R.drawable.ic_manage_users) - } - if (item.info == null) { - return getIconDrawable(this@UserSwitcherActivity, item, isTablet) - } - val userIcon = userManager.getUserIcon(item.info.id) - if (userIcon != null) { - return BitmapDrawable(userIcon) - } - return UserIcons.getDefaultUserIcon(resources, item.info.id, false) - } - - fun getTotalUserViews(): Int { - return users.count { item -> !doNotRenderUserView(item) } - } - - fun doNotRenderUserView(item: UserRecord): Boolean { - return item.isAddUser || item.isAddSupervisedUser || item.isGuest && item.info == null - } - - private fun getDrawable(item: UserRecord): Drawable { - var drawable = - if (item.isGuest) { - getDrawable(R.drawable.ic_account_circle) - } else { - findUserIcon(item) - } - drawable.mutate() - - if (!item.isCurrent && !item.isSwitchToEnabled) { - drawable.setTint( - resources.getColor( - R.color.kg_user_switcher_restricted_avatar_icon_color, - getTheme() - ) - ) - } - - val ld = getDrawable(R.drawable.user_switcher_icon_large).mutate() as LayerDrawable - if (item == userSwitcherController.currentUserRecord) { - (ld.findDrawableByLayerId(R.id.ring) as GradientDrawable).apply { - val stroke = - resources.getDimensionPixelSize(R.dimen.user_switcher_icon_selected_width) - val color = - Utils.getColorAttrDefaultColor( - this@UserSwitcherActivity, - com.android.internal.R.attr.colorAccentPrimary - ) - - setStroke(stroke, color) - } - } - - ld.setDrawableByLayerId(R.id.user_avatar, drawable) - return ld - } - - override fun notifyDataSetChanged() { - super.notifyDataSetChanged() - buildUserViews() - } - } - - companion object { - private const val TAG = "UserSwitcherActivity" } } diff --git a/packages/SystemUI/src/com/android/systemui/user/data/source/UserRecord.kt b/packages/SystemUI/src/com/android/systemui/user/data/source/UserRecord.kt index 9370286d7ee7..d4fb5634bd1d 100644 --- a/packages/SystemUI/src/com/android/systemui/user/data/source/UserRecord.kt +++ b/packages/SystemUI/src/com/android/systemui/user/data/source/UserRecord.kt @@ -47,6 +47,9 @@ data class UserRecord( * If not disabled, this is `null`. */ @JvmField val enforcedAdmin: RestrictedLockUtils.EnforcedAdmin? = null, + + /** Whether this record is to go to the Settings page to manage users. */ + @JvmField val isManageUsers: Boolean = false ) { /** Returns a new instance of [UserRecord] with its [isCurrent] set to the given value. */ fun copyWithIsCurrent(isCurrent: Boolean): UserRecord { diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserActionsUtil.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserActionsUtil.kt index 1b4746a99f8f..dc004f3603a0 100644 --- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserActionsUtil.kt +++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserActionsUtil.kt @@ -82,6 +82,15 @@ object UserActionsUtil { ) } + fun canManageUsers( + repository: UserRepository, + isUserSwitcherEnabled: Boolean, + isAddUsersFromLockScreenEnabled: Boolean, + ): Boolean { + return isUserSwitcherEnabled && + (repository.getSelectedUserInfo().isAdmin || isAddUsersFromLockScreenEnabled) + } + /** * Returns `true` if the current user is allowed to add users to the device; `false` otherwise. */ diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt index 142a328b2bc4..ba5a82a42d94 100644 --- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt @@ -102,6 +102,7 @@ constructor( interface UserCallback { /** Returns `true` if this callback can be cleaned-up. */ fun isEvictable(): Boolean = false + /** Notifies that the state of users on the device has changed. */ fun onUserStateChanged() } @@ -164,10 +165,11 @@ constructor( get() = if (isNewImpl) { combine( + repository.selectedUserInfo, repository.userInfos, repository.userSwitcherSettings, keyguardInteractor.isKeyguardShowing, - ) { userInfos, settings, isDeviceLocked -> + ) { _, userInfos, settings, isDeviceLocked -> buildList { val hasGuestUser = userInfos.any { it.isGuest } if ( @@ -183,35 +185,45 @@ constructor( add(UserActionModel.ENTER_GUEST_MODE) } - if (isDeviceLocked && !settings.isAddUsersFromLockscreen) { + if (!isDeviceLocked || settings.isAddUsersFromLockscreen) { // The device is locked and our setting to allow actions that add users // from the lock-screen is not enabled. The guest action from above is // always allowed, even when the device is locked, but the various "add // user" actions below are not. We can finish building the list here. - return@buildList - } - if ( - UserActionsUtil.canCreateUser( - manager, - repository, - settings.isUserSwitcherEnabled, - settings.isAddUsersFromLockscreen, - ) - ) { - add(UserActionModel.ADD_USER) + val canCreateUsers = + UserActionsUtil.canCreateUser( + manager, + repository, + settings.isUserSwitcherEnabled, + settings.isAddUsersFromLockscreen, + ) + + if (canCreateUsers) { + add(UserActionModel.ADD_USER) + } + + if ( + UserActionsUtil.canCreateSupervisedUser( + manager, + repository, + settings.isUserSwitcherEnabled, + settings.isAddUsersFromLockscreen, + supervisedUserPackageName, + ) + ) { + add(UserActionModel.ADD_SUPERVISED_USER) + } } if ( - UserActionsUtil.canCreateSupervisedUser( - manager, + UserActionsUtil.canManageUsers( repository, settings.isUserSwitcherEnabled, settings.isAddUsersFromLockscreen, - supervisedUserPackageName, ) ) { - add(UserActionModel.ADD_SUPERVISED_USER) + add(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT) } } } @@ -264,7 +276,10 @@ constructor( toRecord( action = it, selectedUserId = selectedUserInfo.id, - isAddFromLockscreenEnabled = settings.isAddUsersFromLockscreen, + isRestricted = + it != UserActionModel.ENTER_GUEST_MODE && + it != UserActionModel.NAVIGATE_TO_USER_MANAGEMENT && + !settings.isAddUsersFromLockscreen, ) } ) @@ -482,12 +497,12 @@ constructor( .setAction(UserManager.ACTION_CREATE_SUPERVISED_USER) .setPackage(supervisedUserPackageName) .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK), - /* dismissShade= */ false, + /* dismissShade= */ true, ) UserActionModel.NAVIGATE_TO_USER_MANAGEMENT -> activityStarter.startActivity( Intent(Settings.ACTION_USER_SETTINGS), - /* dismissShade= */ false, + /* dismissShade= */ true, ) } } else { @@ -575,20 +590,13 @@ constructor( private suspend fun toRecord( action: UserActionModel, selectedUserId: Int, - isAddFromLockscreenEnabled: Boolean, + isRestricted: Boolean, ): UserRecord { return LegacyUserDataHelper.createRecord( context = applicationContext, selectedUserId = selectedUserId, actionType = action, - isRestricted = - if (action == UserActionModel.ENTER_GUEST_MODE) { - // Entering guest mode is never restricted, so it's allowed to happen from the - // lockscreen even if the "add from lockscreen" system setting is off. - false - } else { - !isAddFromLockscreenEnabled - }, + isRestricted = isRestricted, isSwitchToEnabled = canSwitchUsers(selectedUserId) && // If the user is auto-created is must not be currently resetting. diff --git a/packages/SystemUI/src/com/android/systemui/user/legacyhelper/data/LegacyUserDataHelper.kt b/packages/SystemUI/src/com/android/systemui/user/legacyhelper/data/LegacyUserDataHelper.kt index 137de1544b2d..03a7470a3fe6 100644 --- a/packages/SystemUI/src/com/android/systemui/user/legacyhelper/data/LegacyUserDataHelper.kt +++ b/packages/SystemUI/src/com/android/systemui/user/legacyhelper/data/LegacyUserDataHelper.kt @@ -80,6 +80,7 @@ object LegacyUserDataHelper { context = context, selectedUserId = selectedUserId, ), + isManageUsers = actionType == UserActionModel.NAVIGATE_TO_USER_MANAGEMENT, ) } @@ -90,6 +91,7 @@ object LegacyUserDataHelper { record.isAddUser -> UserActionModel.ADD_USER record.isAddSupervisedUser -> UserActionModel.ADD_SUPERVISED_USER record.isGuest -> UserActionModel.ENTER_GUEST_MODE + record.isManageUsers -> UserActionModel.NAVIGATE_TO_USER_MANAGEMENT else -> error("Not a known action: $record") } } diff --git a/packages/SystemUI/src/com/android/systemui/user/legacyhelper/ui/LegacyUserUiHelper.kt b/packages/SystemUI/src/com/android/systemui/user/legacyhelper/ui/LegacyUserUiHelper.kt index 15fdc352d864..e74232df3ac3 100644 --- a/packages/SystemUI/src/com/android/systemui/user/legacyhelper/ui/LegacyUserUiHelper.kt +++ b/packages/SystemUI/src/com/android/systemui/user/legacyhelper/ui/LegacyUserUiHelper.kt @@ -22,7 +22,6 @@ import androidx.annotation.DrawableRes import androidx.annotation.StringRes import com.android.systemui.R import com.android.systemui.user.data.source.UserRecord -import kotlin.math.ceil /** * Defines utility functions for helping with legacy UI code for users. @@ -33,16 +32,6 @@ import kotlin.math.ceil */ object LegacyUserUiHelper { - /** Returns the maximum number of columns for user items in the user switcher. */ - fun getMaxUserSwitcherItemColumns(userCount: Int): Int { - // TODO(b/243844097): remove this once we remove the old user switcher implementation. - return if (userCount < 5) { - 4 - } else { - ceil(userCount / 2.0).toInt() - } - } - @JvmStatic @DrawableRes fun getUserSwitcherActionIconResourceId( @@ -50,6 +39,7 @@ object LegacyUserUiHelper { isGuest: Boolean, isAddSupervisedUser: Boolean, isTablet: Boolean = false, + isManageUsers: Boolean, ): Int { return if (isAddUser && isTablet) { R.drawable.ic_account_circle_filled @@ -59,6 +49,8 @@ object LegacyUserUiHelper { R.drawable.ic_account_circle } else if (isAddSupervisedUser) { R.drawable.ic_add_supervised_user + } else if (isManageUsers) { + R.drawable.ic_manage_users } else { R.drawable.ic_avatar_user } @@ -85,6 +77,7 @@ object LegacyUserUiHelper { isAddUser = record.isAddUser, isAddSupervisedUser = record.isAddSupervisedUser, isTablet = isTablet, + isManageUsers = record.isManageUsers, ) ) } @@ -114,8 +107,9 @@ object LegacyUserUiHelper { isAddUser: Boolean, isAddSupervisedUser: Boolean, isTablet: Boolean = false, + isManageUsers: Boolean, ): Int { - check(isGuest || isAddUser || isAddSupervisedUser) + check(isGuest || isAddUser || isAddSupervisedUser || isManageUsers) return when { isGuest && isGuestUserAutoCreated && isGuestUserResetting -> @@ -125,6 +119,7 @@ object LegacyUserUiHelper { isGuest -> com.android.internal.R.string.guest_name isAddUser -> com.android.settingslib.R.string.user_add_user isAddSupervisedUser -> R.string.add_user_supervised + isManageUsers -> R.string.manage_users else -> error("This should never happen!") } } diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt index 6e7b5232d818..91c592177d19 100644 --- a/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt @@ -48,7 +48,7 @@ constructor( private val dialogLaunchAnimator: DialogLaunchAnimator, private val interactor: UserInteractor, private val featureFlags: FeatureFlags, -) : CoreStartable(context) { +) : CoreStartable { private var currentDialog: Dialog? = null diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt b/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt index 5b83df7b4a36..219dae29117f 100644 --- a/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt @@ -19,7 +19,6 @@ package com.android.systemui.user.ui.viewmodel import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider -import com.android.systemui.R import com.android.systemui.common.ui.drawable.CircularDrawable import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags @@ -30,6 +29,7 @@ import com.android.systemui.user.legacyhelper.ui.LegacyUserUiHelper import com.android.systemui.user.shared.model.UserActionModel import com.android.systemui.user.shared.model.UserModel import javax.inject.Inject +import kotlin.math.ceil import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.combine @@ -52,8 +52,7 @@ private constructor( userInteractor.users.map { models -> models.map { user -> toViewModel(user) } } /** The maximum number of columns that the user selection grid should use. */ - val maximumUserColumns: Flow<Int> = - users.map { LegacyUserUiHelper.getMaxUserSwitcherItemColumns(it.size) } + val maximumUserColumns: Flow<Int> = users.map { getMaxUserSwitcherItemColumns(it.size) } private val _isMenuVisible = MutableStateFlow(false) /** @@ -118,6 +117,15 @@ private constructor( _isMenuVisible.value = false } + /** Returns the maximum number of columns for user items in the user switcher. */ + private fun getMaxUserSwitcherItemColumns(userCount: Int): Int { + return if (userCount < 5) { + 4 + } else { + ceil(userCount / 2.0).toInt() + } + } + private fun createFinishRequestedFlow(): Flow<Boolean> { var mostRecentSelectedUserId: Int? = null var mostRecentIsInteractive: Boolean? = null @@ -171,27 +179,23 @@ private constructor( return UserActionViewModel( viewKey = model.ordinal.toLong(), iconResourceId = - if (model == UserActionModel.NAVIGATE_TO_USER_MANAGEMENT) { - R.drawable.ic_manage_users - } else { - LegacyUserUiHelper.getUserSwitcherActionIconResourceId( - isAddSupervisedUser = model == UserActionModel.ADD_SUPERVISED_USER, - isAddUser = model == UserActionModel.ADD_USER, - isGuest = model == UserActionModel.ENTER_GUEST_MODE, - ) - }, + LegacyUserUiHelper.getUserSwitcherActionIconResourceId( + isAddSupervisedUser = model == UserActionModel.ADD_SUPERVISED_USER, + isAddUser = model == UserActionModel.ADD_USER, + isGuest = model == UserActionModel.ENTER_GUEST_MODE, + isManageUsers = model == UserActionModel.NAVIGATE_TO_USER_MANAGEMENT, + isTablet = true, + ), textResourceId = - if (model == UserActionModel.NAVIGATE_TO_USER_MANAGEMENT) { - R.string.manage_users - } else { - LegacyUserUiHelper.getUserSwitcherActionTextResourceId( - isGuest = model == UserActionModel.ENTER_GUEST_MODE, - isGuestUserAutoCreated = guestUserInteractor.isGuestUserAutoCreated, - isGuestUserResetting = guestUserInteractor.isGuestUserResetting, - isAddSupervisedUser = model == UserActionModel.ADD_SUPERVISED_USER, - isAddUser = model == UserActionModel.ADD_USER, - ) - }, + LegacyUserUiHelper.getUserSwitcherActionTextResourceId( + isGuest = model == UserActionModel.ENTER_GUEST_MODE, + isGuestUserAutoCreated = guestUserInteractor.isGuestUserAutoCreated, + isGuestUserResetting = guestUserInteractor.isGuestUserResetting, + isAddSupervisedUser = model == UserActionModel.ADD_SUPERVISED_USER, + isAddUser = model == UserActionModel.ADD_USER, + isManageUsers = model == UserActionModel.NAVIGATE_TO_USER_MANAGEMENT, + isTablet = true, + ), onClicked = { userInteractor.executeAction(action = model) // We don't finish because we want to show a dialog over the full-screen UI and diff --git a/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java b/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java index 53da213eb38e..2efeda932ff3 100644 --- a/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java +++ b/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java @@ -32,7 +32,8 @@ import java.util.Arrays; import javax.inject.Inject; // NOT Singleton. Started per-user. -public class NotificationChannels extends CoreStartable { +/** */ +public class NotificationChannels implements CoreStartable { public static String ALERTS = "ALR"; public static String SCREENSHOTS_HEADSUP = "SCN_HEADSUP"; // Deprecated. Please use or create a more specific channel that users will better understand @@ -45,9 +46,11 @@ public class NotificationChannels extends CoreStartable { public static String INSTANT = "INS"; public static String SETUP = "STP"; + private final Context mContext; + @Inject public NotificationChannels(Context context) { - super(context); + mContext = context; } public static void createAll(Context context) { diff --git a/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java b/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java index 619e50b47f13..a0a0372426ec 100644 --- a/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java +++ b/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java @@ -564,12 +564,13 @@ public class GarbageMonitor implements Dumpable { /** */ @SysUISingleton - public static class Service extends CoreStartable implements Dumpable { + public static class Service implements CoreStartable, Dumpable { + private final Context mContext; private final GarbageMonitor mGarbageMonitor; @Inject public Service(Context context, GarbageMonitor garbageMonitor) { - super(context); + mContext = context; mGarbageMonitor = garbageMonitor; } diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java index 87fb2a692682..0b3521b048c4 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java @@ -31,18 +31,19 @@ import java.io.PrintWriter; import javax.inject.Inject; @SysUISingleton -public class VolumeUI extends CoreStartable { +public class VolumeUI implements CoreStartable { private static final String TAG = "VolumeUI"; private static boolean LOGD = Log.isLoggable(TAG, Log.DEBUG); private final Handler mHandler = new Handler(); private boolean mEnabled; + private final Context mContext; private VolumeDialogComponent mVolumeComponent; @Inject public VolumeUI(Context context, VolumeDialogComponent volumeDialogComponent) { - super(context); + mContext = context; mVolumeComponent = volumeDialogComponent; } @@ -59,8 +60,7 @@ public class VolumeUI extends CoreStartable { } @Override - protected void onConfigurationChanged(Configuration newConfig) { - super.onConfigurationChanged(newConfig); + public void onConfigurationChanged(Configuration newConfig) { if (!mEnabled) return; mVolumeComponent.onConfigurationChanged(newConfig); } diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java index 3472cb1c2a7d..fbc6a582da2e 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java @@ -89,8 +89,10 @@ import javax.inject.Inject; * -> WMShell starts and binds SysUI with Shell components via exported Shell interfaces */ @SysUISingleton -public final class WMShell extends CoreStartable - implements CommandQueue.Callbacks, ProtoTraceable<SystemUiTraceProto> { +public final class WMShell implements + CoreStartable, + CommandQueue.Callbacks, + ProtoTraceable<SystemUiTraceProto> { private static final String TAG = WMShell.class.getName(); private static final int INVALID_SYSUI_STATE_MASK = SYSUI_STATE_DIALOG_SHOWING @@ -102,6 +104,7 @@ public final class WMShell extends CoreStartable | SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED | SYSUI_STATE_QUICK_SETTINGS_EXPANDED; + private final Context mContext; // Shell interfaces private final ShellInterface mShell; private final Optional<Pip> mPipOptional; @@ -163,7 +166,8 @@ public final class WMShell extends CoreStartable private WakefulnessLifecycle.Observer mWakefulnessObserver; @Inject - public WMShell(Context context, + public WMShell( + Context context, ShellInterface shell, Optional<Pip> pipOptional, Optional<SplitScreen> splitScreenOptional, @@ -179,7 +183,7 @@ public final class WMShell extends CoreStartable WakefulnessLifecycle wakefulnessLifecycle, UserTracker userTracker, @Main Executor sysUiMainExecutor) { - super(context); + mContext = context; mShell = shell; mCommandQueue = commandQueue; mConfigurationController = configurationController; diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardBiometricLockoutLoggerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardBiometricLockoutLoggerTest.kt index aa671d1e3790..91b544b8265c 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardBiometricLockoutLoggerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardBiometricLockoutLoggerTest.kt @@ -17,7 +17,6 @@ package com.android.keyguard import android.hardware.biometrics.BiometricSourceType -import org.mockito.Mockito.verify import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest import com.android.internal.logging.InstanceId @@ -30,9 +29,10 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentCaptor -import org.mockito.Captor import org.mockito.ArgumentMatchers.anyInt +import org.mockito.Captor import org.mockito.Mock +import org.mockito.Mockito.verify import org.mockito.Mockito.verifyNoMoreInteractions import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations @@ -63,7 +63,6 @@ class KeyguardBiometricLockoutLoggerTest : SysuiTestCase() { whenever(keyguardUpdateMonitor.strongAuthTracker).thenReturn(strongAuthTracker) whenever(sessionTracker.getSessionId(anyInt())).thenReturn(sessionId) keyguardBiometricLockoutLogger = KeyguardBiometricLockoutLogger( - mContext, uiEventLogger, keyguardUpdateMonitor, sessionTracker) @@ -195,4 +194,4 @@ class KeyguardBiometricLockoutLoggerTest : SysuiTestCase() { verify(keyguardUpdateMonitor).registerCallback(updateMonitorCallbackCaptor.capture()) updateMonitorCallback = updateMonitorCallbackCaptor.value } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java index c6ebaa8bb46c..48e82397e826 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java @@ -221,15 +221,17 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase { public void onResourcesUpdate_callsThroughOnRotationChange() { // Rotation is the same, shouldn't cause an update mKeyguardSecurityContainerController.updateResources(); - verify(mView, never()).initMode(MODE_DEFAULT, mGlobalSettings, mFalsingManager, - mUserSwitcherController); + verify(mView, never()).initMode(eq(MODE_DEFAULT), eq(mGlobalSettings), eq(mFalsingManager), + eq(mUserSwitcherController), + any(KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class)); // Update rotation. Should trigger update mConfiguration.orientation = Configuration.ORIENTATION_LANDSCAPE; mKeyguardSecurityContainerController.updateResources(); - verify(mView).initMode(MODE_DEFAULT, mGlobalSettings, mFalsingManager, - mUserSwitcherController); + verify(mView).initMode(eq(MODE_DEFAULT), eq(mGlobalSettings), eq(mFalsingManager), + eq(mUserSwitcherController), + any(KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class)); } private void touchDown() { @@ -263,8 +265,9 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase { .thenReturn((KeyguardInputViewController) mKeyguardPasswordViewController); mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.Pattern); - verify(mView).initMode(MODE_DEFAULT, mGlobalSettings, mFalsingManager, - mUserSwitcherController); + verify(mView).initMode(eq(MODE_DEFAULT), eq(mGlobalSettings), eq(mFalsingManager), + eq(mUserSwitcherController), + any(KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class)); } @Test @@ -275,8 +278,9 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase { .thenReturn((KeyguardInputViewController) mKeyguardPasswordViewController); mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.Pattern); - verify(mView).initMode(MODE_ONE_HANDED, mGlobalSettings, mFalsingManager, - mUserSwitcherController); + verify(mView).initMode(eq(MODE_ONE_HANDED), eq(mGlobalSettings), eq(mFalsingManager), + eq(mUserSwitcherController), + any(KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class)); } @Test @@ -285,8 +289,26 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase { setupGetSecurityView(); mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.Password); - verify(mView).initMode(MODE_DEFAULT, mGlobalSettings, mFalsingManager, - mUserSwitcherController); + verify(mView).initMode(eq(MODE_DEFAULT), eq(mGlobalSettings), eq(mFalsingManager), + eq(mUserSwitcherController), + any(KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class)); + } + + @Test + public void addUserSwitcherCallback() { + ArgumentCaptor<KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback> + captor = ArgumentCaptor.forClass( + KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class); + + setupGetSecurityView(); + + mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.Password); + verify(mView).initMode(anyInt(), any(GlobalSettings.class), any(FalsingManager.class), + any(UserSwitcherController.class), + captor.capture()); + captor.getValue().showUnlockToContinueMessage(); + verify(mKeyguardPasswordViewControllerMock).showMessage( + getContext().getString(R.string.keyguard_unlock_to_continue), null); } @Test diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java index 52f8825c724b..82d3ca785161 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java @@ -119,7 +119,7 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase { int systemBarInsetAmount = 0; mKeyguardSecurityContainer.initMode(MODE_DEFAULT, mGlobalSettings, mFalsingManager, - mUserSwitcherController); + mUserSwitcherController, () -> {}); Insets imeInset = Insets.of(0, 0, 0, imeInsetAmount); Insets systemBarInset = Insets.of(0, 0, 0, systemBarInsetAmount); @@ -141,7 +141,7 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase { int systemBarInsetAmount = paddingBottom + 1; mKeyguardSecurityContainer.initMode(MODE_DEFAULT, mGlobalSettings, mFalsingManager, - mUserSwitcherController); + mUserSwitcherController, () -> {}); Insets imeInset = Insets.of(0, 0, 0, imeInsetAmount); Insets systemBarInset = Insets.of(0, 0, 0, systemBarInsetAmount); @@ -158,9 +158,10 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase { @Test public void testDefaultViewMode() { mKeyguardSecurityContainer.initMode(MODE_ONE_HANDED, mGlobalSettings, mFalsingManager, - mUserSwitcherController); + mUserSwitcherController, () -> { + }); mKeyguardSecurityContainer.initMode(MODE_DEFAULT, mGlobalSettings, mFalsingManager, - mUserSwitcherController); + mUserSwitcherController, () -> {}); ConstraintSet.Constraint viewFlipperConstraint = getViewConstraint(mSecurityViewFlipper.getId()); assertThat(viewFlipperConstraint.layout.topToTop).isEqualTo(PARENT_ID); @@ -377,7 +378,7 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase { private void setupUserSwitcher() { when(mGlobalSettings.getInt(any(), anyInt())).thenReturn(ONE_HANDED_KEYGUARD_SIDE_RIGHT); mKeyguardSecurityContainer.initMode(KeyguardSecurityContainer.MODE_USER_SWITCHER, - mGlobalSettings, mFalsingManager, mUserSwitcherController); + mGlobalSettings, mFalsingManager, mUserSwitcherController, () -> {}); } private ArrayList<UserRecord> buildUserRecords(int count) { @@ -387,7 +388,8 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase { 0 /* flags */); users.add(new UserRecord(info, null, false /* isGuest */, false /* isCurrent */, false /* isAddUser */, false /* isRestricted */, true /* isSwitchToEnabled */, - false /* isAddSupervisedUser */, null /* enforcedAdmin */)); + false /* isAddSupervisedUser */, null /* enforcedAdmin */, + false /* isManageUsers */)); } return users; } @@ -395,7 +397,7 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase { private void setupForUpdateKeyguardPosition(boolean oneHandedMode) { int mode = oneHandedMode ? MODE_ONE_HANDED : MODE_DEFAULT; mKeyguardSecurityContainer.initMode(mode, mGlobalSettings, mFalsingManager, - mUserSwitcherController); + mUserSwitcherController, () -> {}); } /** Get the ConstraintLayout constraint of the view. */ diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java index df10dfe9f160..2319f4386798 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java @@ -231,7 +231,7 @@ public class ScreenDecorationsTest extends SysuiTestCase { } @Override - protected void onConfigurationChanged(Configuration newConfig) { + public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); mExecutor.runAllReady(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationTypesUpdaterTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationTypesUpdaterTest.java index 571dd3d1faf3..9f4a7c820efc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationTypesUpdaterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationTypesUpdaterTest.java @@ -71,7 +71,7 @@ public class ComplicationTypesUpdaterTest extends SysuiTestCase { MockitoAnnotations.initMocks(this); when(mDreamBackend.getEnabledComplications()).thenReturn(new HashSet<>()); - mController = new ComplicationTypesUpdater(mContext, mDreamBackend, mExecutor, + mController = new ComplicationTypesUpdater(mDreamBackend, mExecutor, mSecureSettings, mDreamOverlayStateController); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamClockTimeComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamClockTimeComplicationTest.java index 314a30b2d14a..ec448f94ba83 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamClockTimeComplicationTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamClockTimeComplicationTest.java @@ -82,7 +82,6 @@ public class DreamClockTimeComplicationTest extends SysuiTestCase { public void testComplicationAdded() { final DreamClockTimeComplication.Registrant registrant = new DreamClockTimeComplication.Registrant( - mContext, mDreamOverlayStateController, mComplication); registrant.start(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java index db6082d52501..aa8c93edce68 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java @@ -115,7 +115,7 @@ public class DreamHomeControlsComplicationTest extends SysuiTestCase { @Test public void complicationAvailability_serviceNotAvailable_noFavorites_doNotAddComplication() { final DreamHomeControlsComplication.Registrant registrant = - new DreamHomeControlsComplication.Registrant(mContext, mComplication, + new DreamHomeControlsComplication.Registrant(mComplication, mDreamOverlayStateController, mControlsComponent); registrant.start(); @@ -128,7 +128,7 @@ public class DreamHomeControlsComplicationTest extends SysuiTestCase { @Test public void complicationAvailability_serviceAvailable_noFavorites_doNotAddComplication() { final DreamHomeControlsComplication.Registrant registrant = - new DreamHomeControlsComplication.Registrant(mContext, mComplication, + new DreamHomeControlsComplication.Registrant(mComplication, mDreamOverlayStateController, mControlsComponent); registrant.start(); @@ -141,7 +141,7 @@ public class DreamHomeControlsComplicationTest extends SysuiTestCase { @Test public void complicationAvailability_serviceNotAvailable_haveFavorites_doNotAddComplication() { final DreamHomeControlsComplication.Registrant registrant = - new DreamHomeControlsComplication.Registrant(mContext, mComplication, + new DreamHomeControlsComplication.Registrant(mComplication, mDreamOverlayStateController, mControlsComponent); registrant.start(); @@ -154,7 +154,7 @@ public class DreamHomeControlsComplicationTest extends SysuiTestCase { @Test public void complicationAvailability_serviceAvailable_haveFavorites_addComplication() { final DreamHomeControlsComplication.Registrant registrant = - new DreamHomeControlsComplication.Registrant(mContext, mComplication, + new DreamHomeControlsComplication.Registrant(mComplication, mDreamOverlayStateController, mControlsComponent); registrant.start(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/SmartSpaceComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/SmartSpaceComplicationTest.java index fa8f88a08368..c8b2b2556828 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/SmartSpaceComplicationTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/SmartSpaceComplicationTest.java @@ -24,7 +24,6 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.smartspace.SmartspaceTarget; -import android.content.Context; import android.testing.AndroidTestingRunner; import android.view.View; @@ -48,8 +47,6 @@ import java.util.Collections; @SmallTest @RunWith(AndroidTestingRunner.class) public class SmartSpaceComplicationTest extends SysuiTestCase { - @Mock - private Context mContext; @Mock private DreamSmartspaceController mSmartspaceController; @@ -80,7 +77,6 @@ public class SmartSpaceComplicationTest extends SysuiTestCase { private SmartSpaceComplication.Registrant getRegistrant() { return new SmartSpaceComplication.Registrant( - mContext, mDreamOverlayStateController, mComplication, mSmartspaceController); diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/KeyguardQuickAffordanceInteractorParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt index b6d7559dbcbb..b4d5464d1177 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/KeyguardQuickAffordanceInteractorParameterizedTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt @@ -12,20 +12,20 @@ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. + * */ -package com.android.systemui.keyguard.domain.usecase +package com.android.systemui.keyguard.domain.interactor import android.content.Intent import androidx.test.filters.SmallTest import com.android.internal.widget.LockPatternUtils import com.android.systemui.SysuiTestCase import com.android.systemui.animation.ActivityLaunchAnimator +import com.android.systemui.animation.Expandable import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.Icon import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository -import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor -import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordancePosition import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceConfig import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceRegistry @@ -195,6 +195,7 @@ class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() { @Mock private lateinit var userTracker: UserTracker @Mock private lateinit var activityStarter: ActivityStarter @Mock private lateinit var animationController: ActivityLaunchAnimator.Controller + @Mock private lateinit var expandable: Expandable private lateinit var underTest: KeyguardQuickAffordanceInteractor @@ -208,6 +209,7 @@ class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() { @Before fun setUp() { MockitoAnnotations.initMocks(this) + whenever(expandable.activityLaunchController()).thenReturn(animationController) homeControls = object : FakeKeyguardQuickAffordanceConfig() {} underTest = @@ -259,7 +261,7 @@ class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() { underTest.onQuickAffordanceClicked( configKey = homeControls::class, - animationController = animationController, + expandable = expandable, ) if (startActivity) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/KeyguardQuickAffordanceInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt index 1dd919aba88d..65fd6e576650 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/KeyguardQuickAffordanceInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt @@ -12,9 +12,10 @@ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. + * */ -package com.android.systemui.keyguard.domain.usecase +package com.android.systemui.keyguard.domain.interactor import androidx.test.filters.SmallTest import com.android.internal.widget.LockPatternUtils @@ -22,13 +23,12 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.Icon import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository -import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor -import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordancePosition import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceConfig import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceRegistry import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig +import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordanceToggleState import com.android.systemui.plugins.ActivityStarter import com.android.systemui.settings.UserTracker import com.android.systemui.statusbar.policy.KeyguardStateController @@ -103,6 +103,7 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() { homeControls.setState( KeyguardQuickAffordanceConfig.State.Visible( icon = ICON, + toggle = KeyguardQuickAffordanceToggleState.On, ) ) @@ -123,6 +124,7 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() { assertThat(visibleModel.icon).isEqualTo(ICON) assertThat(visibleModel.icon.contentDescription) .isEqualTo(ContentDescription.Resource(res = CONTENT_DESCRIPTION_RESOURCE_ID)) + assertThat(visibleModel.toggle).isEqualTo(KeyguardQuickAffordanceToggleState.On) job.cancel() } @@ -152,6 +154,7 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() { assertThat(visibleModel.icon).isEqualTo(ICON) assertThat(visibleModel.icon.contentDescription) .isEqualTo(ContentDescription.Resource(res = CONTENT_DESCRIPTION_RESOURCE_ID)) + assertThat(visibleModel.toggle).isEqualTo(KeyguardQuickAffordanceToggleState.NotSupported) job.cancel() } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt index 6ea1daa7704f..e99c139e9e7e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt @@ -17,7 +17,7 @@ package com.android.systemui.keyguard.domain.quickaffordance -import com.android.systemui.animation.ActivityLaunchAnimator +import com.android.systemui.animation.Expandable import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig.OnClickedResult import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow @@ -40,7 +40,7 @@ abstract class FakeKeyguardQuickAffordanceConfig : KeyguardQuickAffordanceConfig override val state: Flow<KeyguardQuickAffordanceConfig.State> = _state override fun onQuickAffordanceClicked( - animationController: ActivityLaunchAnimator.Controller?, + expandable: Expandable?, ): OnClickedResult { return onClickedResult } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt index dede4ec0210c..a809f0547ee6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt @@ -20,7 +20,7 @@ package com.android.systemui.keyguard.domain.quickaffordance import androidx.test.filters.SmallTest import com.android.systemui.R import com.android.systemui.SysuiTestCase -import com.android.systemui.animation.ActivityLaunchAnimator +import com.android.systemui.animation.Expandable import com.android.systemui.controls.controller.ControlsController import com.android.systemui.controls.dagger.ControlsComponent import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig.OnClickedResult @@ -44,7 +44,7 @@ import org.mockito.MockitoAnnotations class HomeControlsKeyguardQuickAffordanceConfigTest : SysuiTestCase() { @Mock private lateinit var component: ControlsComponent - @Mock private lateinit var animationController: ActivityLaunchAnimator.Controller + @Mock private lateinit var expandable: Expandable private lateinit var underTest: HomeControlsKeyguardQuickAffordanceConfig @@ -103,7 +103,7 @@ class HomeControlsKeyguardQuickAffordanceConfigTest : SysuiTestCase() { fun `onQuickAffordanceClicked - canShowWhileLockedSetting is true`() = runBlockingTest { whenever(component.canShowWhileLockedSetting).thenReturn(MutableStateFlow(true)) - val onClickedResult = underTest.onQuickAffordanceClicked(animationController) + val onClickedResult = underTest.onQuickAffordanceClicked(expandable) assertThat(onClickedResult).isInstanceOf(OnClickedResult.StartActivity::class.java) assertThat((onClickedResult as OnClickedResult.StartActivity).canShowWhileLocked).isTrue() @@ -113,7 +113,7 @@ class HomeControlsKeyguardQuickAffordanceConfigTest : SysuiTestCase() { fun `onQuickAffordanceClicked - canShowWhileLockedSetting is false`() = runBlockingTest { whenever(component.canShowWhileLockedSetting).thenReturn(MutableStateFlow(false)) - val onClickedResult = underTest.onQuickAffordanceClicked(animationController) + val onClickedResult = underTest.onQuickAffordanceClicked(expandable) assertThat(onClickedResult).isInstanceOf(OnClickedResult.StartActivity::class.java) assertThat((onClickedResult as OnClickedResult.StartActivity).canShowWhileLocked).isFalse() diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt index 0a4478f27448..98dc4c4f6f76 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt @@ -24,11 +24,13 @@ import androidx.test.filters.SmallTest import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.animation.ActivityLaunchAnimator +import com.android.systemui.animation.Expandable import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.Icon import com.android.systemui.plugins.ActivityStarter import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.whenever import com.android.systemui.wallet.controller.QuickAccessWalletController import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.flow.launchIn @@ -40,7 +42,6 @@ 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 @@ -135,8 +136,11 @@ class QuickAccessWalletKeyguardQuickAffordanceConfigTest : SysuiTestCase() { @Test fun onQuickAffordanceClicked() { val animationController: ActivityLaunchAnimator.Controller = mock() + val expandable: Expandable = mock { + whenever(this.activityLaunchController()).thenReturn(animationController) + } - assertThat(underTest.onQuickAffordanceClicked(animationController)) + assertThat(underTest.onQuickAffordanceClicked(expandable)) .isEqualTo(KeyguardQuickAffordanceConfig.OnClickedResult.Handled) verify(walletController) .startQuickAccessUiIntent( diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt index 96544e7b7da6..d674c89c0e14 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt @@ -20,7 +20,7 @@ import android.content.Intent import androidx.test.filters.SmallTest import com.android.internal.widget.LockPatternUtils import com.android.systemui.SysuiTestCase -import com.android.systemui.animation.ActivityLaunchAnimator +import com.android.systemui.animation.Expandable import com.android.systemui.common.shared.model.Icon import com.android.systemui.doze.util.BurnInHelperWrapper import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository @@ -31,6 +31,7 @@ import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordancePositio import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceConfig import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceRegistry import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig +import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordanceToggleState import com.android.systemui.plugins.ActivityStarter import com.android.systemui.settings.UserTracker import com.android.systemui.statusbar.policy.KeyguardStateController @@ -59,7 +60,7 @@ import org.mockito.MockitoAnnotations @RunWith(JUnit4::class) class KeyguardBottomAreaViewModelTest : SysuiTestCase() { - @Mock private lateinit var animationController: ActivityLaunchAnimator.Controller + @Mock private lateinit var expandable: Expandable @Mock private lateinit var burnInHelperWrapper: BurnInHelperWrapper @Mock private lateinit var lockPatternUtils: LockPatternUtils @Mock private lateinit var keyguardStateController: KeyguardStateController @@ -130,6 +131,7 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() { TestConfig( isVisible = true, isClickable = true, + isActivated = true, icon = mock(), canShowWhileLocked = false, intent = Intent("action"), @@ -505,6 +507,12 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() { } KeyguardQuickAffordanceConfig.State.Visible( icon = testConfig.icon ?: error("Icon is unexpectedly null!"), + toggle = + when (testConfig.isActivated) { + true -> KeyguardQuickAffordanceToggleState.On + false -> KeyguardQuickAffordanceToggleState.Off + null -> KeyguardQuickAffordanceToggleState.NotSupported + } ) } else { KeyguardQuickAffordanceConfig.State.Hidden @@ -521,12 +529,13 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() { checkNotNull(viewModel) assertThat(viewModel.isVisible).isEqualTo(testConfig.isVisible) assertThat(viewModel.isClickable).isEqualTo(testConfig.isClickable) + assertThat(viewModel.isActivated).isEqualTo(testConfig.isActivated) if (testConfig.isVisible) { assertThat(viewModel.icon).isEqualTo(testConfig.icon) viewModel.onClicked.invoke( KeyguardQuickAffordanceViewModel.OnClickedParameters( configKey = configKey, - animationController = animationController, + expandable = expandable, ) ) if (testConfig.intent != null) { @@ -542,6 +551,7 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() { private data class TestConfig( val isVisible: Boolean, val isClickable: Boolean = false, + val isActivated: Boolean = false, val icon: Icon? = null, val canShowWhileLocked: Boolean = false, val intent: Intent? = null, diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/SessionTrackerTest.java b/packages/SystemUI/tests/src/com/android/systemui/log/SessionTrackerTest.java index b8e9cf48f3e2..dc5522efe406 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/log/SessionTrackerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/log/SessionTrackerTest.java @@ -82,7 +82,6 @@ public class SessionTrackerTest extends SysuiTestCase { MockitoAnnotations.initMocks(this); mSessionTracker = new SessionTracker( - mContext, mStatusBarService, mAuthController, mKeyguardUpdateMonitor, diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java index 2f52950a9ee4..af530163e289 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java @@ -73,7 +73,7 @@ public class MediaDreamSentinelTest extends SysuiTestCase { @Test public void testOnMediaDataLoaded_complicationAddition() { - final MediaDreamSentinel sentinel = new MediaDreamSentinel(mContext, mMediaDataManager, + final MediaDreamSentinel sentinel = new MediaDreamSentinel(mMediaDataManager, mDreamOverlayStateController, mMediaEntryComplication, mFeatureFlags); sentinel.start(); @@ -94,7 +94,7 @@ public class MediaDreamSentinelTest extends SysuiTestCase { @Test public void testOnMediaDataRemoved_complicationRemoval() { - final MediaDreamSentinel sentinel = new MediaDreamSentinel(mContext, mMediaDataManager, + final MediaDreamSentinel sentinel = new MediaDreamSentinel(mMediaDataManager, mDreamOverlayStateController, mMediaEntryComplication, mFeatureFlags); sentinel.start(); @@ -114,7 +114,7 @@ public class MediaDreamSentinelTest extends SysuiTestCase { @Test public void testOnMediaDataLoaded_complicationRemoval() { - final MediaDreamSentinel sentinel = new MediaDreamSentinel(mContext, mMediaDataManager, + final MediaDreamSentinel sentinel = new MediaDreamSentinel(mMediaDataManager, mDreamOverlayStateController, mMediaEntryComplication, mFeatureFlags); sentinel.start(); @@ -139,7 +139,7 @@ public class MediaDreamSentinelTest extends SysuiTestCase { public void testOnMediaDataLoaded_mediaComplicationDisabled_doesNotAddComplication() { when(mFeatureFlags.isEnabled(DREAM_MEDIA_COMPLICATION)).thenReturn(false); - final MediaDreamSentinel sentinel = new MediaDreamSentinel(mContext, mMediaDataManager, + final MediaDreamSentinel sentinel = new MediaDreamSentinel(mMediaDataManager, mDreamOverlayStateController, mMediaEntryComplication, mFeatureFlags); sentinel.start(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java index 0e9d2799dddb..6adce7a827b6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java @@ -80,6 +80,7 @@ import com.android.systemui.accessibility.SystemActions; import com.android.systemui.assist.AssistManager; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dump.DumpManager; +import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.model.SysUiState; import com.android.systemui.navigationbar.buttons.ButtonDispatcher; import com.android.systemui.navigationbar.buttons.DeadZone; @@ -197,6 +198,8 @@ public class NavigationBarTest extends SysuiTestCase { @Mock private UserContextProvider mUserContextProvider; @Mock + private WakefulnessLifecycle mWakefulnessLifecycle; + @Mock private Resources mResources; private FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock()); private DeviceConfigProxyFake mDeviceConfigProxyFake = new DeviceConfigProxyFake(); @@ -474,7 +477,8 @@ public class NavigationBarTest extends SysuiTestCase { mNavigationBarTransitions, mEdgeBackGestureHandler, Optional.of(mock(BackAnimation.class)), - mUserContextProvider)); + mUserContextProvider, + mWakefulnessLifecycle)); } private void processAllMessages() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java index 4e9b2325b899..c377c374148f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java @@ -46,6 +46,7 @@ import com.android.settingslib.fuelgauge.Estimate; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.broadcast.BroadcastDispatcher; +import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.power.PowerUI.WarningsUI; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.phone.CentralSurfaces; @@ -84,6 +85,7 @@ public class PowerUITest extends SysuiTestCase { private PowerUI mPowerUI; @Mock private EnhancedEstimates mEnhancedEstimates; @Mock private PowerManager mPowerManager; + @Mock private WakefulnessLifecycle mWakefulnessLifecycle; @Mock private IThermalService mThermalServiceMock; private IThermalEventListener mUsbThermalEventListener; private IThermalEventListener mSkinThermalEventListener; @@ -680,7 +682,7 @@ public class PowerUITest extends SysuiTestCase { private void createPowerUi() { mPowerUI = new PowerUI( mContext, mBroadcastDispatcher, mCommandQueue, mCentralSurfacesOptionalLazy, - mMockWarnings, mEnhancedEstimates, mPowerManager); + mMockWarnings, mEnhancedEstimates, mWakefulnessLifecycle, mPowerManager); mPowerUI.mThermalService = mThermalServiceMock; } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt index 2a4996f259dc..760bb9bec559 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt @@ -192,16 +192,6 @@ class FooterActionsViewModelTest : SysuiTestCase() { // UserManager change. assertThat(iconTint()).isNull() - // Trigger a user info change: there should now be a tint. - userInfoController.updateInfo { userAccount = "doe" } - assertThat(iconTint()) - .isEqualTo( - Utils.getColorAttrDefaultColor( - context, - android.R.attr.colorForeground, - ) - ) - // Make sure we don't tint the icon if it is a user image (and not the default image), even // in guest mode. userInfoController.updateInfo { this.picture = mock<UserIconDrawable>() } diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentCreatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentCreatorTest.kt new file mode 100644 index 000000000000..b6a595b0077a --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentCreatorTest.kt @@ -0,0 +1,103 @@ +/* + * 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.screenshot + +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.net.Uri +import androidx.test.filters.SmallTest +import com.android.systemui.R +import com.android.systemui.SysuiTestCase +import com.android.systemui.util.mockito.eq +import com.android.systemui.util.mockito.mock +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.mockito.Mockito.`when` as whenever + +@SmallTest +class ActionIntentCreatorTest : SysuiTestCase() { + + @Test + fun testCreateShareIntent() { + val uri = Uri.parse("content://fake") + val subject = "Example subject" + + val output = ActionIntentCreator.createShareIntent(uri, subject) + + assertThat(output.action).isEqualTo(Intent.ACTION_CHOOSER) + assertFlagsSet( + Intent.FLAG_ACTIVITY_NEW_TASK or + Intent.FLAG_ACTIVITY_CLEAR_TASK or + Intent.FLAG_GRANT_READ_URI_PERMISSION, + output.flags + ) + + val wrappedIntent = output.getParcelableExtra(Intent.EXTRA_INTENT, Intent::class.java) + assertThat(wrappedIntent?.action).isEqualTo(Intent.ACTION_SEND) + assertThat(wrappedIntent?.data).isEqualTo(uri) + assertThat(wrappedIntent?.type).isEqualTo("image/png") + assertThat(wrappedIntent?.getStringExtra(Intent.EXTRA_SUBJECT)).isEqualTo(subject) + assertThat(wrappedIntent?.getParcelableExtra(Intent.EXTRA_STREAM, Uri::class.java)) + .isEqualTo(uri) + } + + @Test + fun testCreateShareIntent_noSubject() { + val uri = Uri.parse("content://fake") + val output = ActionIntentCreator.createShareIntent(uri, null) + val wrappedIntent = output.getParcelableExtra(Intent.EXTRA_INTENT, Intent::class.java) + assertThat(wrappedIntent?.getStringExtra(Intent.EXTRA_SUBJECT)).isNull() + } + + @Test + fun testCreateEditIntent() { + val uri = Uri.parse("content://fake") + val context = mock<Context>() + + val output = ActionIntentCreator.createEditIntent(uri, context) + + assertThat(output.action).isEqualTo(Intent.ACTION_EDIT) + assertThat(output.data).isEqualTo(uri) + assertThat(output.type).isEqualTo("image/png") + assertThat(output.component).isNull() + val expectedFlags = + Intent.FLAG_GRANT_READ_URI_PERMISSION or + Intent.FLAG_GRANT_WRITE_URI_PERMISSION or + Intent.FLAG_ACTIVITY_NEW_TASK or + Intent.FLAG_ACTIVITY_CLEAR_TASK + assertFlagsSet(expectedFlags, output.flags) + } + + @Test + fun testCreateEditIntent_withEditor() { + val uri = Uri.parse("content://fake") + val context = mock<Context>() + var component = ComponentName("com.android.foo", "com.android.foo.Something") + + whenever(context.getString(eq(R.string.config_screenshotEditor))) + .thenReturn(component.flattenToString()) + + val output = ActionIntentCreator.createEditIntent(uri, context) + + assertThat(output.component).isEqualTo(component) + } + + private fun assertFlagsSet(expected: Int, observed: Int) { + assertThat(observed and expected).isEqualTo(expected) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/DraggableConstraintLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/DraggableConstraintLayoutTest.java new file mode 100644 index 000000000000..c6ce51a28dd3 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/DraggableConstraintLayoutTest.java @@ -0,0 +1,81 @@ +/* + * 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.screenshot; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; +import android.view.MotionEvent; + +import androidx.test.filters.SmallTest; + +import com.android.systemui.SysuiTestCase; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper(setAsMainLooper = true) +public class DraggableConstraintLayoutTest extends SysuiTestCase { + + @Mock + DraggableConstraintLayout.SwipeDismissCallbacks mCallbacks; + + private DraggableConstraintLayout mDraggableConstraintLayout; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + mDraggableConstraintLayout = new DraggableConstraintLayout(mContext, null, 0); + } + + @Test + public void test_dismissDoesNotCallSwipeInitiated() { + mDraggableConstraintLayout.setCallbacks(mCallbacks); + + mDraggableConstraintLayout.dismiss(); + + verify(mCallbacks, never()).onSwipeDismissInitiated(any()); + } + + @Test + public void test_onTouchCallsOnInteraction() { + mDraggableConstraintLayout.setCallbacks(mCallbacks); + + mDraggableConstraintLayout.onInterceptTouchEvent( + MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0)); + + verify(mCallbacks).onInteraction(); + } + + @Test + public void test_callbacksNotSet() { + // just test that it doesn't throw an NPE + mDraggableConstraintLayout.onInterceptTouchEvent( + MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0)); + mDraggableConstraintLayout.onInterceptHoverEvent( + MotionEvent.obtain(0, 0, MotionEvent.ACTION_HOVER_ENTER, 0, 0, 0)); + mDraggableConstraintLayout.dismiss(); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java index 37be3439fdc8..c0dae03023c5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java @@ -716,6 +716,40 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { } @Test + public void test_pulsing_onTouchEvent_noTracking() { + // GIVEN device is pulsing + mNotificationPanelViewController.setPulsing(true); + + // WHEN touch DOWN & MOVE events received + onTouchEvent(MotionEvent.obtain(0L /* downTime */, + 0L /* eventTime */, MotionEvent.ACTION_DOWN, 0f /* x */, 0f /* y */, + 0 /* metaState */)); + onTouchEvent(MotionEvent.obtain(0L /* downTime */, + 0L /* eventTime */, MotionEvent.ACTION_MOVE, 0f /* x */, 200f /* y */, + 0 /* metaState */)); + + // THEN touch is NOT tracked (since the device is pulsing) + assertThat(mNotificationPanelViewController.isTracking()).isFalse(); + } + + @Test + public void test_onTouchEvent_startTracking() { + // GIVEN device is NOT pulsing + mNotificationPanelViewController.setPulsing(false); + + // WHEN touch DOWN & MOVE events received + onTouchEvent(MotionEvent.obtain(0L /* downTime */, + 0L /* eventTime */, MotionEvent.ACTION_DOWN, 0f /* x */, 0f /* y */, + 0 /* metaState */)); + onTouchEvent(MotionEvent.obtain(0L /* downTime */, + 0L /* eventTime */, MotionEvent.ACTION_MOVE, 0f /* x */, 200f /* y */, + 0 /* metaState */)); + + // THEN touch is tracked + assertThat(mNotificationPanelViewController.isTracking()).isTrue(); + } + + @Test public void handleTouchEventFromStatusBar_panelsNotEnabled_returnsFalseAndNoViewEvent() { when(mCommandQueue.panelsEnabled()).thenReturn(false); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderTest.java index 3f641df376ed..ca6598726a85 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderTest.java @@ -91,6 +91,8 @@ public class HeadsUpViewBinderTest extends SysuiTestCase { verifyNoMoreInteractions(mLogger); clearInvocations(mLogger); + when(mBindStage.tryGetStageParams(eq(mEntry))).thenReturn(new RowContentBindParams()); + mViewBinder.unbindHeadsUpView(mEntry); verify(mLogger).entryContentViewMarkedFreeable(eq(mEntry)); verifyNoMoreInteractions(mLogger); @@ -139,6 +141,8 @@ public class HeadsUpViewBinderTest extends SysuiTestCase { verifyNoMoreInteractions(mLogger); clearInvocations(mLogger); + when(mBindStage.tryGetStageParams(eq(mEntry))).thenReturn(new RowContentBindParams()); + mViewBinder.unbindHeadsUpView(mEntry); verify(mLogger).currentOngoingBindingAborted(eq(mEntry)); verify(mLogger).entryContentViewMarkedFreeable(eq(mEntry)); @@ -150,4 +154,30 @@ public class HeadsUpViewBinderTest extends SysuiTestCase { verifyNoMoreInteractions(mLogger); clearInvocations(mLogger); } + + @Test + public void testLoggingForLateUnbindFlow() { + AtomicReference<NotifBindPipeline.BindCallback> callback = new AtomicReference<>(); + when(mBindStage.requestRebind(any(), any())).then(i -> { + callback.set(i.getArgument(1)); + return new CancellationSignal(); + }); + + mViewBinder.bindHeadsUpView(mEntry, null); + verify(mLogger).startBindingHun(eq(mEntry)); + verifyNoMoreInteractions(mLogger); + clearInvocations(mLogger); + + callback.get().onBindFinished(mEntry); + verify(mLogger).entryBoundSuccessfully(eq(mEntry)); + verifyNoMoreInteractions(mLogger); + clearInvocations(mLogger); + + when(mBindStage.tryGetStageParams(eq(mEntry))).thenReturn(null); + + mViewBinder.unbindHeadsUpView(mEntry); + verify(mLogger).entryBindStageParamsNullOnUnbind(eq(mEntry)); + verifyNoMoreInteractions(mLogger); + clearInvocations(mLogger); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java index ad3bd711c23f..7c99568ee75f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java @@ -21,6 +21,10 @@ import static com.android.systemui.statusbar.notification.row.NotificationRowCon import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_EXPANDED; import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP; +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertNotNull; +import static junit.framework.Assert.assertNotSame; +import static junit.framework.Assert.assertNull; import static junit.framework.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; @@ -31,6 +35,7 @@ import static org.mockito.Mockito.verify; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; +import android.util.Log; import androidx.test.filters.SmallTest; @@ -100,6 +105,67 @@ public class RowContentBindStageTest extends SysuiTestCase { verify(mBinder).unbindContent(eq(mEntry), any(), eq(flags)); } + class CountingWtfHandler implements Log.TerribleFailureHandler { + private Log.TerribleFailureHandler mOldHandler = null; + private int mWtfCount = 0; + + public void register() { + mOldHandler = Log.setWtfHandler(this); + } + + public void unregister() { + Log.setWtfHandler(mOldHandler); + mOldHandler = null; + } + + @Override + public void onTerribleFailure(String tag, Log.TerribleFailure what, boolean system) { + mWtfCount++; + } + + public int getWtfCount() { + return mWtfCount; + } + } + + @Test + public void testGetStageParamsAfterCleanUp() { + // GIVEN an entry whose params have already been deleted. + RowContentBindParams originalParams = mRowContentBindStage.getStageParams(mEntry); + mRowContentBindStage.deleteStageParams(mEntry); + + // WHEN a caller calls getStageParams. + CountingWtfHandler countingWtfHandler = new CountingWtfHandler(); + countingWtfHandler.register(); + + RowContentBindParams blankParams = mRowContentBindStage.getStageParams(mEntry); + + countingWtfHandler.unregister(); + + // THEN getStageParams logs a WTF and returns blank params created to avoid a crash. + assertEquals(1, countingWtfHandler.getWtfCount()); + assertNotNull(blankParams); + assertNotSame(originalParams, blankParams); + } + + @Test + public void testTryGetStageParamsAfterCleanUp() { + // GIVEN an entry whose params have already been deleted. + mRowContentBindStage.deleteStageParams(mEntry); + + // WHEN a caller calls getStageParams. + CountingWtfHandler countingWtfHandler = new CountingWtfHandler(); + countingWtfHandler.register(); + + RowContentBindParams nullParams = mRowContentBindStage.tryGetStageParams(mEntry); + + countingWtfHandler.unregister(); + + // THEN getStageParams does NOT log a WTF and returns null to indicate missing params. + assertEquals(0, countingWtfHandler.getWtfCount()); + assertNull(nullParams); + } + @Test public void testRebindAllContentViews() { // GIVEN a view with content bound. diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImplTest.kt index 76ecc1c7f36d..169f4fb2715b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImplTest.kt @@ -57,14 +57,18 @@ import com.android.systemui.settings.UserTracker import com.android.systemui.shade.NotificationShadeWindowView import com.android.systemui.telephony.TelephonyListenerManager import com.android.systemui.user.data.source.UserRecord +import com.android.systemui.user.legacyhelper.data.LegacyUserDataHelper +import com.android.systemui.user.shared.model.UserActionModel import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.capture +import com.android.systemui.util.mockito.kotlinArgumentCaptor import com.android.systemui.util.mockito.nullable import com.android.systemui.util.settings.GlobalSettings import com.android.systemui.util.settings.SecureSettings import com.android.systemui.util.time.FakeSystemClock +import com.google.common.truth.Truth import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse import org.junit.Assert.assertNotNull @@ -123,7 +127,7 @@ class UserSwitcherControllerOldImplTest : SysuiTestCase() { private val ownerId = UserHandle.USER_SYSTEM private val ownerInfo = UserInfo(ownerId, "Owner", null, UserInfo.FLAG_ADMIN or UserInfo.FLAG_FULL or UserInfo.FLAG_INITIALIZED or - UserInfo.FLAG_PRIMARY or UserInfo.FLAG_SYSTEM, + UserInfo.FLAG_PRIMARY or UserInfo.FLAG_SYSTEM or UserInfo.FLAG_ADMIN, UserManager.USER_TYPE_FULL_SYSTEM) private val guestId = 1234 private val guestInfo = UserInfo(guestId, "Guest", null, @@ -597,6 +601,76 @@ class UserSwitcherControllerOldImplTest : SysuiTestCase() { } @Test + fun testCanManageUser_userSwitcherEnabled_addUserWhenLocked() { + `when`( + globalSettings.getIntForUser( + eq(Settings.Global.USER_SWITCHER_ENABLED), + anyInt(), + eq(UserHandle.USER_SYSTEM) + ) + ).thenReturn(1) + + `when`( + globalSettings.getIntForUser( + eq(Settings.Global.ADD_USERS_WHEN_LOCKED), + anyInt(), + eq(UserHandle.USER_SYSTEM) + ) + ).thenReturn(1) + setupController() + assertTrue(userSwitcherController.canManageUsers()) + } + + @Test + fun testCanManageUser_userSwitcherDisabled_addUserWhenLocked() { + `when`( + globalSettings.getIntForUser( + eq(Settings.Global.USER_SWITCHER_ENABLED), + anyInt(), + eq(UserHandle.USER_SYSTEM) + ) + ).thenReturn(0) + + `when`( + globalSettings.getIntForUser( + eq(Settings.Global.ADD_USERS_WHEN_LOCKED), + anyInt(), + eq(UserHandle.USER_SYSTEM) + ) + ).thenReturn(1) + setupController() + assertFalse(userSwitcherController.canManageUsers()) + } + + @Test + fun testCanManageUser_userSwitcherEnabled_isAdmin() { + `when`( + globalSettings.getIntForUser( + eq(Settings.Global.USER_SWITCHER_ENABLED), + anyInt(), + eq(UserHandle.USER_SYSTEM) + ) + ).thenReturn(1) + + setupController() + assertTrue(userSwitcherController.canManageUsers()) + } + + @Test + fun testCanManageUser_userSwitcherDisabled_isAdmin() { + `when`( + globalSettings.getIntForUser( + eq(Settings.Global.USER_SWITCHER_ENABLED), + anyInt(), + eq(UserHandle.USER_SYSTEM) + ) + ).thenReturn(0) + + setupController() + assertFalse(userSwitcherController.canManageUsers()) + } + + @Test fun addUserSwitchCallback() { val broadcastReceiverCaptor = argumentCaptor<BroadcastReceiver>() verify(broadcastDispatcher).registerReceiver( @@ -632,4 +706,22 @@ class UserSwitcherControllerOldImplTest : SysuiTestCase() { bgExecutor.runAllReady() verify(userManager).createGuest(context) } + + @Test + fun onUserItemClicked_manageUsers() { + val manageUserRecord = LegacyUserDataHelper.createRecord( + mContext, + ownerId, + UserActionModel.NAVIGATE_TO_USER_MANAGEMENT, + isRestricted = false, + isSwitchToEnabled = true + ) + + userSwitcherController.onUserListItemClicked(manageUserRecord, null) + val intentCaptor = kotlinArgumentCaptor<Intent>() + verify(activityStarter).startActivity(intentCaptor.capture(), + eq(true) + ) + Truth.assertThat(intentCaptor.value.action).isEqualTo(Settings.ACTION_USER_SETTINGS) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/UserSwitcherActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/UserSwitcherActivityTest.kt deleted file mode 100644 index 3968bb798bb7..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/user/UserSwitcherActivityTest.kt +++ /dev/null @@ -1,152 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.user - -import android.app.Application -import android.os.UserManager -import android.testing.AndroidTestingRunner -import android.testing.TestableLooper.RunWithLooper -import android.view.LayoutInflater -import android.view.View -import android.view.Window -import android.window.OnBackInvokedCallback -import android.window.OnBackInvokedDispatcher -import androidx.test.filters.SmallTest -import com.android.systemui.R -import com.android.systemui.SysuiTestCase -import com.android.systemui.broadcast.BroadcastDispatcher -import com.android.systemui.classifier.FalsingCollector -import com.android.systemui.flags.FeatureFlags -import com.android.systemui.plugins.FalsingManager -import com.android.systemui.settings.UserTracker -import com.android.systemui.statusbar.policy.UserSwitcherController -import com.android.systemui.user.ui.viewmodel.UserSwitcherViewModel -import com.android.systemui.util.concurrency.FakeExecutor -import com.android.systemui.util.time.FakeSystemClock -import com.google.common.truth.Truth.assertThat -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.mockito.ArgumentCaptor -import org.mockito.Captor -import org.mockito.Mock -import org.mockito.Mockito.`when` -import org.mockito.Mockito.any -import org.mockito.Mockito.anyInt -import org.mockito.Mockito.doNothing -import org.mockito.Mockito.eq -import org.mockito.Mockito.mock -import org.mockito.Mockito.spy -import org.mockito.Mockito.verify -import org.mockito.MockitoAnnotations -import java.util.concurrent.Executor - -@SmallTest -@RunWith(AndroidTestingRunner::class) -@RunWithLooper(setAsMainLooper = true) -class UserSwitcherActivityTest : SysuiTestCase() { - @Mock - private lateinit var activity: UserSwitcherActivity - @Mock - private lateinit var userSwitcherController: UserSwitcherController - @Mock - private lateinit var broadcastDispatcher: BroadcastDispatcher - @Mock - private lateinit var layoutInflater: LayoutInflater - @Mock - private lateinit var falsingCollector: FalsingCollector - @Mock - private lateinit var falsingManager: FalsingManager - @Mock - private lateinit var userManager: UserManager - @Mock - private lateinit var userTracker: UserTracker - @Mock - private lateinit var flags: FeatureFlags - @Mock - private lateinit var viewModelFactoryLazy: dagger.Lazy<UserSwitcherViewModel.Factory> - @Mock - private lateinit var onBackDispatcher: OnBackInvokedDispatcher - @Mock - private lateinit var decorView: View - @Mock - private lateinit var window: Window - @Mock - private lateinit var userSwitcherRootView: UserSwitcherRootView - @Captor - private lateinit var onBackInvokedCallback: ArgumentCaptor<OnBackInvokedCallback> - var isFinished = false - - @Before - fun setUp() { - MockitoAnnotations.initMocks(this) - activity = spy(object : UserSwitcherActivity( - userSwitcherController, - broadcastDispatcher, - falsingCollector, - falsingManager, - userManager, - userTracker, - flags, - viewModelFactoryLazy, - ) { - override fun getOnBackInvokedDispatcher() = onBackDispatcher - override fun getMainExecutor(): Executor = FakeExecutor(FakeSystemClock()) - override fun finish() { - isFinished = true - } - }) - `when`(activity.window).thenReturn(window) - `when`(window.decorView).thenReturn(decorView) - `when`(activity.findViewById<UserSwitcherRootView>(R.id.user_switcher_root)) - .thenReturn(userSwitcherRootView) - `when`(activity.findViewById<View>(R.id.cancel)).thenReturn(mock(View::class.java)) - `when`(activity.findViewById<View>(R.id.add)).thenReturn(mock(View::class.java)) - `when`(activity.application).thenReturn(mock(Application::class.java)) - doNothing().`when`(activity).setContentView(anyInt()) - } - - @Test - fun testMaxColumns() { - assertThat(activity.getMaxColumns(3)).isEqualTo(4) - assertThat(activity.getMaxColumns(4)).isEqualTo(4) - assertThat(activity.getMaxColumns(5)).isEqualTo(3) - assertThat(activity.getMaxColumns(6)).isEqualTo(3) - assertThat(activity.getMaxColumns(7)).isEqualTo(4) - assertThat(activity.getMaxColumns(9)).isEqualTo(5) - } - - @Test - fun onCreate_callbackRegistration() { - activity.createActivity() - verify(onBackDispatcher).registerOnBackInvokedCallback( - eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT), any()) - - activity.destroyActivity() - verify(onBackDispatcher).unregisterOnBackInvokedCallback(any()) - } - - @Test - fun onBackInvokedCallback_finishesActivity() { - activity.createActivity() - verify(onBackDispatcher).registerOnBackInvokedCallback( - eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT), onBackInvokedCallback.capture()) - - onBackInvokedCallback.value.onBackInvoked() - assertThat(isFinished).isTrue() - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorRefactoredTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorRefactoredTest.kt index 37c378c9a530..1540f8552002 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorRefactoredTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorRefactoredTest.kt @@ -202,6 +202,7 @@ class UserInteractorRefactoredTest : UserInteractorTest() { fun `actions - device unlocked`() = runBlocking(IMMEDIATE) { val userInfos = createUserInfos(count = 2, includeGuest = false) + userRepository.setUserInfos(userInfos) userRepository.setSelectedUserInfo(userInfos[0]) userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true)) @@ -215,6 +216,7 @@ class UserInteractorRefactoredTest : UserInteractorTest() { UserActionModel.ENTER_GUEST_MODE, UserActionModel.ADD_USER, UserActionModel.ADD_SUPERVISED_USER, + UserActionModel.NAVIGATE_TO_USER_MANAGEMENT, ) ) @@ -276,6 +278,7 @@ class UserInteractorRefactoredTest : UserInteractorTest() { UserActionModel.ENTER_GUEST_MODE, UserActionModel.ADD_USER, UserActionModel.ADD_SUPERVISED_USER, + UserActionModel.NAVIGATE_TO_USER_MANAGEMENT, ) ) @@ -283,7 +286,7 @@ class UserInteractorRefactoredTest : UserInteractorTest() { } @Test - fun `actions - device locked - only guest action is shown`() = + fun `actions - device locked - only guest action and manage user is shown`() = runBlocking(IMMEDIATE) { val userInfos = createUserInfos(count = 2, includeGuest = false) userRepository.setUserInfos(userInfos) @@ -293,7 +296,13 @@ class UserInteractorRefactoredTest : UserInteractorTest() { var value: List<UserActionModel>? = null val job = underTest.actions.onEach { value = it }.launchIn(this) - assertThat(value).isEqualTo(listOf(UserActionModel.ENTER_GUEST_MODE)) + assertThat(value) + .isEqualTo( + listOf( + UserActionModel.ENTER_GUEST_MODE, + UserActionModel.NAVIGATE_TO_USER_MANAGEMENT + ) + ) job.cancel() } @@ -330,7 +339,7 @@ class UserInteractorRefactoredTest : UserInteractorTest() { underTest.executeAction(UserActionModel.ADD_SUPERVISED_USER) val intentCaptor = kotlinArgumentCaptor<Intent>() - verify(activityStarter).startActivity(intentCaptor.capture(), eq(false)) + verify(activityStarter).startActivity(intentCaptor.capture(), eq(true)) assertThat(intentCaptor.value.action) .isEqualTo(UserManager.ACTION_CREATE_SUPERVISED_USER) assertThat(intentCaptor.value.`package`).isEqualTo(SUPERVISED_USER_CREATION_APP_PACKAGE) @@ -342,7 +351,7 @@ class UserInteractorRefactoredTest : UserInteractorTest() { underTest.executeAction(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT) val intentCaptor = kotlinArgumentCaptor<Intent>() - verify(activityStarter).startActivity(intentCaptor.capture(), eq(false)) + verify(activityStarter).startActivity(intentCaptor.capture(), eq(true)) assertThat(intentCaptor.value.action).isEqualTo(Settings.ACTION_USER_SETTINGS) } @@ -561,6 +570,7 @@ class UserInteractorRefactoredTest : UserInteractorTest() { UserActionModel.ENTER_GUEST_MODE, UserActionModel.ADD_USER, UserActionModel.ADD_SUPERVISED_USER, + UserActionModel.NAVIGATE_TO_USER_MANAGEMENT, ), ) } @@ -705,7 +715,7 @@ class UserInteractorRefactoredTest : UserInteractorTest() { name, /* iconPath= */ "", /* flags= */ if (isPrimary) { - UserInfo.FLAG_PRIMARY + UserInfo.FLAG_PRIMARY or UserInfo.FLAG_ADMIN } else { 0 }, diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java index ca7fe0c571d1..14cfce7cc679 100644 --- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java +++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java @@ -3722,21 +3722,34 @@ public class UserBackupManagerService { Slog.w(TAG, "agentDisconnected: the backup agent for " + packageName + " died: cancel current operations"); - // handleCancel() causes the PerformFullTransportBackupTask to go on to - // tearDownAgentAndKill: that will unbindBackupAgent in the Activity Manager, so - // that the package being backed up doesn't get stuck in restricted mode until the - // backup time-out elapses. - for (int token : mOperationStorage.operationTokensForPackage(packageName)) { - if (MORE_DEBUG) { - Slog.d(TAG, "agentDisconnected: will handleCancel(all) for token:" - + Integer.toHexString(token)); + // Offload operation cancellation off the main thread as the cancellation callbacks + // might call out to BackupTransport. Other operations started on the same package + // before the cancellation callback has executed will also be cancelled by the callback. + Runnable cancellationRunnable = () -> { + // handleCancel() causes the PerformFullTransportBackupTask to go on to + // tearDownAgentAndKill: that will unbindBackupAgent in the Activity Manager, so + // that the package being backed up doesn't get stuck in restricted mode until the + // backup time-out elapses. + for (int token : mOperationStorage.operationTokensForPackage(packageName)) { + if (MORE_DEBUG) { + Slog.d(TAG, "agentDisconnected: will handleCancel(all) for token:" + + Integer.toHexString(token)); + } + handleCancel(token, true /* cancelAll */); } - handleCancel(token, true /* cancelAll */); - } + }; + getThreadForAsyncOperation(/* operationName */ "agent-disconnected", + cancellationRunnable).start(); + mAgentConnectLock.notifyAll(); } } + @VisibleForTesting + Thread getThreadForAsyncOperation(String operationName, Runnable operation) { + return new Thread(operation, operationName); + } + /** * An application being installed will need a restore pass, then the {@link PackageManager} will * need to be told when the restore is finished. diff --git a/services/core/java/com/android/server/biometrics/log/ALSProbe.java b/services/core/java/com/android/server/biometrics/log/ALSProbe.java index 62f94ed05e0a..1a5f31c8ac90 100644 --- a/services/core/java/com/android/server/biometrics/log/ALSProbe.java +++ b/services/core/java/com/android/server/biometrics/log/ALSProbe.java @@ -30,7 +30,10 @@ import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; import com.android.server.biometrics.sensors.BaseClientMonitor; +import java.util.ArrayList; +import java.util.List; import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; /** Probe for ambient light. */ final class ALSProbe implements Probe { @@ -47,12 +50,18 @@ final class ALSProbe implements Probe { private boolean mEnabled = false; private boolean mDestroyed = false; + private boolean mDestroyRequested = false; + private boolean mDisableRequested = false; + private volatile NextConsumer mNextConsumer = null; private volatile float mLastAmbientLux = -1; private final SensorEventListener mLightSensorListener = new SensorEventListener() { @Override public void onSensorChanged(SensorEvent event) { mLastAmbientLux = event.values[0]; + if (mNextConsumer != null) { + completeNextConsumer(mLastAmbientLux); + } } @Override @@ -102,29 +111,84 @@ final class ALSProbe implements Probe { @Override public synchronized void enable() { - if (!mDestroyed) { + if (!mDestroyed && !mDestroyRequested) { + mDisableRequested = false; enableLightSensorLoggingLocked(); } } @Override public synchronized void disable() { - if (!mDestroyed) { + mDisableRequested = true; + + // if a final consumer is set it will call destroy/disable on the next value if requested + if (!mDestroyed && mNextConsumer == null) { disableLightSensorLoggingLocked(); } } @Override public synchronized void destroy() { - disable(); - mDestroyed = true; + mDestroyRequested = true; + + // if a final consumer is set it will call destroy/disable on the next value if requested + if (!mDestroyed && mNextConsumer == null) { + disable(); + mDestroyed = true; + } } /** The most recent lux reading. */ - public float getCurrentLux() { + public float getMostRecentLux() { return mLastAmbientLux; } + /** + * Register a listener for the next available ALS reading, which will be reported to the given + * consumer even if this probe is {@link #disable()}'ed or {@link #destroy()}'ed before a value + * is available. + * + * This method is intended to be used for event logs that occur when the screen may be + * off and sampling may have been {@link #disable()}'ed. In these cases, this method will turn + * on the sensor (if needed), fetch & report the first value, and then destroy or disable this + * probe (if needed). + * + * @param consumer consumer to notify when the data is available + * @param handler handler for notifying the consumer, or null + */ + public synchronized void awaitNextLux(@NonNull Consumer<Float> consumer, + @Nullable Handler handler) { + final NextConsumer nextConsumer = new NextConsumer(consumer, handler); + final float current = mLastAmbientLux; + if (current > 0) { + nextConsumer.consume(current); + } else if (mDestroyed) { + nextConsumer.consume(-1f); + } else if (mNextConsumer != null) { + mNextConsumer.add(nextConsumer); + } else { + mNextConsumer = nextConsumer; + enableLightSensorLoggingLocked(); + } + } + + private synchronized void completeNextConsumer(float value) { + Slog.v(TAG, "Finishing next consumer"); + + final NextConsumer consumer = mNextConsumer; + mNextConsumer = null; + + if (mDestroyRequested) { + destroy(); + } else if (mDisableRequested) { + disable(); + } + + if (consumer != null) { + consumer.consume(value); + } + } + private void enableLightSensorLoggingLocked() { if (!mEnabled) { mEnabled = true; @@ -160,4 +224,30 @@ final class ALSProbe implements Probe { + mLightSensorListener.hashCode()); disable(); } + + private static class NextConsumer { + @NonNull private final Consumer<Float> mConsumer; + @Nullable private final Handler mHandler; + @NonNull private final List<NextConsumer> mOthers = new ArrayList<>(); + + private NextConsumer(@NonNull Consumer<Float> consumer, @Nullable Handler handler) { + mConsumer = consumer; + mHandler = handler; + } + + public void consume(float value) { + if (mHandler != null) { + mHandler.post(() -> mConsumer.accept(value)); + } else { + mConsumer.accept(value); + } + for (NextConsumer c : mOthers) { + c.consume(value); + } + } + + public void add(NextConsumer consumer) { + mOthers.add(consumer); + } + } } diff --git a/services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java b/services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java index d6ca8a68145e..27a70c51f667 100644 --- a/services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java +++ b/services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java @@ -62,8 +62,7 @@ public class BiometricFrameworkStatsLogger { /** {@see FrameworkStatsLog.BIOMETRIC_AUTHENTICATED}. */ public void authenticate(OperationContext operationContext, int statsModality, int statsAction, int statsClient, boolean isDebug, long latency, - int authState, boolean requireConfirmation, - int targetUserId, float ambientLightLux) { + int authState, boolean requireConfirmation, int targetUserId, float ambientLightLux) { FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_AUTHENTICATED, statsModality, targetUserId, @@ -80,6 +79,16 @@ public class BiometricFrameworkStatsLogger { operationContext.isAod); } + /** {@see FrameworkStatsLog.BIOMETRIC_AUTHENTICATED}. */ + public void authenticate(OperationContext operationContext, + int statsModality, int statsAction, int statsClient, boolean isDebug, long latency, + int authState, boolean requireConfirmation, int targetUserId, ALSProbe alsProbe) { + alsProbe.awaitNextLux((ambientLightLux) -> { + authenticate(operationContext, statsModality, statsAction, statsClient, isDebug, + latency, authState, requireConfirmation, targetUserId, ambientLightLux); + }, null /* handler */); + } + /** {@see FrameworkStatsLog.BIOMETRIC_ENROLLED}. */ public void enroll(int statsModality, int statsAction, int statsClient, int targetUserId, long latency, boolean enrollSuccessful, float ambientLightLux) { diff --git a/services/core/java/com/android/server/biometrics/log/BiometricLogger.java b/services/core/java/com/android/server/biometrics/log/BiometricLogger.java index 02b350e97ef8..55fe854e1404 100644 --- a/services/core/java/com/android/server/biometrics/log/BiometricLogger.java +++ b/services/core/java/com/android/server/biometrics/log/BiometricLogger.java @@ -220,7 +220,7 @@ public class BiometricLogger { + ", RequireConfirmation: " + requireConfirmation + ", State: " + authState + ", Latency: " + latency - + ", Lux: " + mALSProbe.getCurrentLux()); + + ", Lux: " + mALSProbe.getMostRecentLux()); } else { Slog.v(TAG, "Authentication latency: " + latency); } @@ -231,7 +231,7 @@ public class BiometricLogger { mSink.authenticate(operationContext, mStatsModality, mStatsAction, mStatsClient, Utils.isDebugEnabled(context, targetUserId), - latency, authState, requireConfirmation, targetUserId, mALSProbe.getCurrentLux()); + latency, authState, requireConfirmation, targetUserId, mALSProbe); } /** Log enrollment outcome. */ @@ -245,7 +245,7 @@ public class BiometricLogger { + ", User: " + targetUserId + ", Client: " + mStatsClient + ", Latency: " + latency - + ", Lux: " + mALSProbe.getCurrentLux() + + ", Lux: " + mALSProbe.getMostRecentLux() + ", Success: " + enrollSuccessful); } else { Slog.v(TAG, "Enroll latency: " + latency); @@ -256,7 +256,7 @@ public class BiometricLogger { } mSink.enroll(mStatsModality, mStatsAction, mStatsClient, - targetUserId, latency, enrollSuccessful, mALSProbe.getCurrentLux()); + targetUserId, latency, enrollSuccessful, mALSProbe.getMostRecentLux()); } /** Report unexpected enrollment reported by the HAL. */ diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java index b3f42be41cd7..fa751007198e 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java @@ -333,6 +333,9 @@ class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession> mALSProbeCallback.getProbe().disable(); } }); + if (getBiometricContext().isAwake()) { + mALSProbeCallback.getProbe().enable(); + } if (session.hasContextMethods()) { return session.getSession().authenticateWithContext(mOperationId, opContext); diff --git a/services/tests/servicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java index bccd8a0b14b4..9ae892286e55 100644 --- a/services/tests/servicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java @@ -18,6 +18,7 @@ package com.android.server.backup; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; @@ -61,6 +62,7 @@ import java.util.function.IntConsumer; public class UserBackupManagerServiceTest { private static final String TEST_PACKAGE = "package1"; private static final String[] TEST_PACKAGES = new String[] { TEST_PACKAGE }; + private static final int WORKER_THREAD_TIMEOUT_MILLISECONDS = 1; @Mock Context mContext; @Mock IBackupManagerMonitor mBackupManagerMonitor; @@ -179,6 +181,7 @@ public class UserBackupManagerServiceTest { mService.agentDisconnected("com.android.foo"); + mService.waitForAsyncOperation(); verify(mOperationStorage).cancelOperation(eq(123), eq(true), any(IntConsumer.class)); verify(mOperationStorage).cancelOperation(eq(456), eq(true), any()); verify(mOperationStorage).cancelOperation(eq(789), eq(true), any()); @@ -207,6 +210,8 @@ public class UserBackupManagerServiceTest { boolean isEnabledStatePersisted = false; boolean shouldUseNewBackupEligibilityRules = false; + private volatile Thread mWorkerThread = null; + TestBackupService(Context context, PackageManager packageManager, LifecycleOperationStorage operationStorage) { super(context, packageManager, operationStorage); @@ -229,5 +234,23 @@ public class UserBackupManagerServiceTest { boolean shouldUseNewBackupEligibilityRules() { return shouldUseNewBackupEligibilityRules; } + + @Override + Thread getThreadForAsyncOperation(String operationName, Runnable operation) { + mWorkerThread = super.getThreadForAsyncOperation(operationName, operation); + return mWorkerThread; + } + + private void waitForAsyncOperation() { + if (mWorkerThread == null) { + return; + } + + try { + mWorkerThread.join(/* millis */ WORKER_THREAD_TIMEOUT_MILLISECONDS); + } catch (InterruptedException e) { + fail("Failed waiting for worker thread to complete: " + e.getMessage()); + } + } } } diff --git a/services/tests/servicestests/src/com/android/server/biometrics/log/ALSProbeTest.java b/services/tests/servicestests/src/com/android/server/biometrics/log/ALSProbeTest.java index 10f0a5cbbc40..68c9ce4a9f86 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/log/ALSProbeTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/log/ALSProbeTest.java @@ -23,6 +23,7 @@ import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; @@ -50,6 +51,9 @@ import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + @Presubmit @SmallTest @RunWith(AndroidTestingRunner.class) @@ -93,7 +97,7 @@ public class ALSProbeTest { mSensorEventListenerCaptor.getValue().onSensorChanged( new SensorEvent(mLightSensor, 1, 2, new float[]{value})); - assertThat(mProbe.getCurrentLux()).isEqualTo(value); + assertThat(mProbe.getMostRecentLux()).isEqualTo(value); } @Test @@ -121,13 +125,17 @@ public class ALSProbeTest { mProbe.destroy(); mProbe.enable(); + AtomicInteger lux = new AtomicInteger(10); + mProbe.awaitNextLux((v) -> lux.set(Math.round(v)), null /* handler */); + verify(mSensorManager, never()).registerListener(any(), any(), anyInt()); verifyNoMoreInteractions(mSensorManager); + assertThat(lux.get()).isLessThan(0); } @Test public void testDisabledReportsNegativeValue() { - assertThat(mProbe.getCurrentLux()).isLessThan(0f); + assertThat(mProbe.getMostRecentLux()).isLessThan(0f); mProbe.enable(); verify(mSensorManager).registerListener( @@ -136,7 +144,7 @@ public class ALSProbeTest { new SensorEvent(mLightSensor, 1, 1, new float[]{4.0f})); mProbe.disable(); - assertThat(mProbe.getCurrentLux()).isLessThan(0f); + assertThat(mProbe.getMostRecentLux()).isLessThan(0f); } @Test @@ -150,7 +158,7 @@ public class ALSProbeTest { verify(mSensorManager).unregisterListener(eq(mSensorEventListenerCaptor.getValue())); verifyNoMoreInteractions(mSensorManager); - assertThat(mProbe.getCurrentLux()).isLessThan(0f); + assertThat(mProbe.getMostRecentLux()).isLessThan(0f); } @Test @@ -166,7 +174,148 @@ public class ALSProbeTest { verify(mSensorManager).unregisterListener(any(SensorEventListener.class)); verifyNoMoreInteractions(mSensorManager); - assertThat(mProbe.getCurrentLux()).isLessThan(0f); + assertThat(mProbe.getMostRecentLux()).isLessThan(0f); + } + + @Test + public void testNextLuxWhenAlreadyEnabledAndNotAvailable() { + testNextLuxWhenAlreadyEnabled(false /* dataIsAvailable */); + } + + @Test + public void testNextLuxWhenAlreadyEnabledAndAvailable() { + testNextLuxWhenAlreadyEnabled(true /* dataIsAvailable */); + } + + private void testNextLuxWhenAlreadyEnabled(boolean dataIsAvailable) { + final List<Integer> values = List.of(1, 2, 3, 4, 6); + mProbe.enable(); + + verify(mSensorManager).registerListener( + mSensorEventListenerCaptor.capture(), any(), anyInt()); + + if (dataIsAvailable) { + for (int v : values) { + mSensorEventListenerCaptor.getValue().onSensorChanged( + new SensorEvent(mLightSensor, 1, 1, new float[]{v})); + } + } + AtomicInteger lux = new AtomicInteger(-1); + mProbe.awaitNextLux((v) -> lux.set(Math.round(v)), null /* handler */); + if (!dataIsAvailable) { + for (int v : values) { + mSensorEventListenerCaptor.getValue().onSensorChanged( + new SensorEvent(mLightSensor, 1, 1, new float[]{v})); + } + } + + mSensorEventListenerCaptor.getValue().onSensorChanged( + new SensorEvent(mLightSensor, 1, 1, new float[]{200f})); + + // should remain enabled + assertThat(lux.get()).isEqualTo(values.get(dataIsAvailable ? values.size() - 1 : 0)); + verify(mSensorManager, never()).unregisterListener(any(SensorEventListener.class)); + verifyNoMoreInteractions(mSensorManager); + + final int anotherValue = 12; + mSensorEventListenerCaptor.getValue().onSensorChanged( + new SensorEvent(mLightSensor, 1, 1, new float[]{12})); + assertThat(mProbe.getMostRecentLux()).isEqualTo(anotherValue); + } + + @Test + public void testNextLuxWhenNotEnabled() { + testNextLuxWhenNotEnabled(false /* enableWhileWaiting */); + } + + @Test + public void testNextLuxWhenNotEnabledButEnabledLater() { + testNextLuxWhenNotEnabled(true /* enableWhileWaiting */); + } + + private void testNextLuxWhenNotEnabled(boolean enableWhileWaiting) { + final List<Integer> values = List.of(1, 2, 3, 4, 6); + mProbe.disable(); + + AtomicInteger lux = new AtomicInteger(-1); + mProbe.awaitNextLux((v) -> lux.set(Math.round(v)), null /* handler */); + + if (enableWhileWaiting) { + mProbe.enable(); + } + + verify(mSensorManager).registerListener( + mSensorEventListenerCaptor.capture(), any(), anyInt()); + for (int v : values) { + mSensorEventListenerCaptor.getValue().onSensorChanged( + new SensorEvent(mLightSensor, 1, 1, new float[]{v})); + } + + // should restore the disabled state + assertThat(lux.get()).isEqualTo(values.get(0)); + verify(mSensorManager, enableWhileWaiting ? never() : times(1)).unregisterListener( + any(SensorEventListener.class)); + verifyNoMoreInteractions(mSensorManager); + } + + @Test + public void testNextLuxIsNotCanceledByDisableOrDestroy() { + final int value = 7; + AtomicInteger lux = new AtomicInteger(-1); + mProbe.awaitNextLux((v) -> lux.set(Math.round(v)), null /* handler */); + + verify(mSensorManager).registerListener( + mSensorEventListenerCaptor.capture(), any(), anyInt()); + + mProbe.destroy(); + mProbe.disable(); + + assertThat(lux.get()).isEqualTo(-1); + + mSensorEventListenerCaptor.getValue().onSensorChanged( + new SensorEvent(mLightSensor, 1, 1, new float[]{value})); + + assertThat(lux.get()).isEqualTo(value); + + mSensorEventListenerCaptor.getValue().onSensorChanged( + new SensorEvent(mLightSensor, 1, 1, new float[]{value + 1})); + + // should remain destroyed + mProbe.enable(); + + assertThat(lux.get()).isEqualTo(value); + verify(mSensorManager).unregisterListener(any(SensorEventListener.class)); + verifyNoMoreInteractions(mSensorManager); + } + + @Test + public void testMultipleNextConsumers() { + final int value = 7; + AtomicInteger lux = new AtomicInteger(-1); + AtomicInteger lux2 = new AtomicInteger(-1); + mProbe.awaitNextLux((v) -> lux.set(Math.round(v)), null /* handler */); + mProbe.awaitNextLux((v) -> lux2.set(Math.round(v)), null /* handler */); + + verify(mSensorManager).registerListener( + mSensorEventListenerCaptor.capture(), any(), anyInt()); + mSensorEventListenerCaptor.getValue().onSensorChanged( + new SensorEvent(mLightSensor, 1, 1, new float[]{value})); + + assertThat(lux.get()).isEqualTo(value); + assertThat(lux2.get()).isEqualTo(value); + } + + @Test + public void testNoNextLuxWhenDestroyed() { + mProbe.destroy(); + + AtomicInteger lux = new AtomicInteger(-20); + mProbe.awaitNextLux((v) -> lux.set(Math.round(v)), null /* handler */); + + assertThat(lux.get()).isEqualTo(-1); + verify(mSensorManager, never()).registerListener( + mSensorEventListenerCaptor.capture(), any(), anyInt()); + verifyNoMoreInteractions(mSensorManager); } private void moveTimeBy(long millis) { diff --git a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricLoggerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricLoggerTest.java index 60dc2eb6081d..88a9646cac8a 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricLoggerTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricLoggerTest.java @@ -121,7 +121,7 @@ public class BiometricLoggerTest { verify(mSink).authenticate(eq(mOpContext), eq(DEFAULT_MODALITY), eq(DEFAULT_ACTION), eq(DEFAULT_CLIENT), anyBoolean(), anyLong(), anyInt(), eq(requireConfirmation), - eq(targetUserId), anyFloat()); + eq(targetUserId), any()); } @Test diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java index dea4d4fb7c64..a5c181d53286 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java @@ -28,6 +28,7 @@ import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.same; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -215,7 +216,7 @@ public class FingerprintAuthenticationClientTest { @Test public void luxProbeWhenAwake() throws RemoteException { - when(mBiometricContext.isAwake()).thenReturn(false, true, false); + when(mBiometricContext.isAwake()).thenReturn(false); when(mBiometricContext.isAod()).thenReturn(false); final FingerprintAuthenticationClient client = createClient(); client.start(mCallback); @@ -228,15 +229,38 @@ public class FingerprintAuthenticationClientTest { verify(mLuxProbe, never()).enable(); reset(mLuxProbe); + when(mBiometricContext.isAwake()).thenReturn(true); + mContextInjector.getValue().accept(opContext); verify(mLuxProbe).enable(); verify(mLuxProbe, never()).disable(); + when(mBiometricContext.isAwake()).thenReturn(false); + mContextInjector.getValue().accept(opContext); verify(mLuxProbe).disable(); } @Test + public void luxProbeEnabledOnStartWhenWake() throws RemoteException { + luxProbeEnabledOnStart(true /* isAwake */); + } + + @Test + public void luxProbeNotEnabledOnStartWhenNotWake() throws RemoteException { + luxProbeEnabledOnStart(false /* isAwake */); + } + + private void luxProbeEnabledOnStart(boolean isAwake) throws RemoteException { + when(mBiometricContext.isAwake()).thenReturn(isAwake); + when(mBiometricContext.isAod()).thenReturn(false); + final FingerprintAuthenticationClient client = createClient(); + client.start(mCallback); + + verify(mLuxProbe, isAwake ? times(1) : never()).enable(); + } + + @Test public void luxProbeDisabledOnAod() throws RemoteException { when(mBiometricContext.isAwake()).thenReturn(false); when(mBiometricContext.isAod()).thenReturn(true); |