diff options
151 files changed, 4662 insertions, 1203 deletions
diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java index ec1c57d675c1..f7d4be3960f8 100644 --- a/core/java/android/os/VibrationEffect.java +++ b/core/java/android/os/VibrationEffect.java @@ -830,29 +830,8 @@ public abstract class VibrationEffect implements Parcelable { * .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 1.0f, 100) * .compose();}</pre> * - * <p>Composition elements can also be {@link VibrationEffect} instances, including other - * compositions, and off durations, which are periods of time when the vibrator will be - * turned off. Here is an example of a composition that "warms up" with a light tap, - * a stronger double tap, then repeats a vibration pattern indefinitely: - * - * <pre> - * {@code VibrationEffect repeatingEffect = VibrationEffect.startComposition() - * .addPrimitive(VibrationEffect.Composition.PRIMITIVE_LOW_TICK) - * .addOffDuration(Duration.ofMillis(10)) - * .addEffect(VibrationEffect.createPredefined(VibrationEffect.EFFECT_DOUBLE_CLICK)) - * .addOffDuration(Duration.ofMillis(50)) - * .addEffect(VibrationEffect.createWaveform(pattern, repeatIndex)) - * .compose();}</pre> - * * <p>When choosing to play a composed effect, you should check that individual components are - * supported by the device by using the appropriate vibrator method: - * - * <ul> - * <li>Primitive support can be checked using {@link Vibrator#arePrimitivesSupported}. - * <li>Effect support can be checked using {@link Vibrator#areEffectsSupported}. - * <li>Amplitude control for one-shot and waveforms with amplitude values can be checked - * using {@link Vibrator#hasAmplitudeControl}. - * </ul> + * supported by the device by using {@link Vibrator#arePrimitivesSupported}. * * @see VibrationEffect#startComposition() */ @@ -1021,9 +1000,6 @@ public abstract class VibrationEffect implements Parcelable { * * @param primitiveId The primitive to add * @return This {@link Composition} object to enable adding multiple elements in one chain. - * - * @throws UnreachableAfterRepeatingIndefinitelyException if the composition is currently - * ending with a repeating effect. */ @NonNull public Composition addPrimitive(@PrimitiveType int primitiveId) { @@ -1038,9 +1014,6 @@ public abstract class VibrationEffect implements Parcelable { * @param primitiveId The primitive to add * @param scale The scale to apply to the intensity of the primitive. * @return This {@link Composition} object to enable adding multiple elements in one chain. - * - * @throws UnreachableAfterRepeatingIndefinitelyException if the composition is currently - * ending with a repeating effect. */ @NonNull public Composition addPrimitive(@PrimitiveType int primitiveId, @@ -1056,9 +1029,6 @@ public abstract class VibrationEffect implements Parcelable { * @param delay The amount of time in milliseconds to wait before playing this primitive, * starting at the time the previous element in this composition is finished. * @return This {@link Composition} object to enable adding multiple elements in one chain. - * - * @throws UnreachableAfterRepeatingIndefinitelyException if the composition is currently - * ending with a repeating effect. */ @NonNull public Composition addPrimitive(@PrimitiveType int primitiveId, diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index ec6b4acd6aff..e4caa385bc9b 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -3064,7 +3064,8 @@ public final class ViewRootImpl implements ViewParent, // WindowManagerService has reported back a frame from a configuration not yet // handled by the client. In this case, we need to accept the configuration so we // do not lay out and draw with the wrong configuration. - if (!mPendingMergedConfiguration.equals(mLastReportedMergedConfiguration)) { + if (mRelayoutRequested + && !mPendingMergedConfiguration.equals(mLastReportedMergedConfiguration)) { if (DEBUG_CONFIGURATION) Log.v(mTag, "Visible with new config: " + mPendingMergedConfiguration.getMergedConfiguration()); performConfigurationChange(new MergedConfiguration(mPendingMergedConfiguration), @@ -8085,7 +8086,8 @@ public final class ViewRootImpl implements ViewParent, final int measuredWidth = mView.getMeasuredWidth(); final int measuredHeight = mView.getMeasuredHeight(); final boolean relayoutAsync; - if (LOCAL_LAYOUT && !mFirst && viewVisibility == mViewVisibility + if (LOCAL_LAYOUT + && (mViewFrameInfo.flags & FrameInfo.FLAG_WINDOW_VISIBILITY_CHANGED) == 0 && mWindowAttributes.type != TYPE_APPLICATION_STARTING && mSyncSeqId <= mLastSyncSeqId && winConfigFromAm.diff(winConfigFromWm, false /* compareUndefined */) == 0) { diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java index 285a40779b90..fadad99f0885 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -709,7 +709,7 @@ public class Editor { } getPositionListener().addSubscriber(mCursorAnchorInfoNotifier, true); - resumeBlink(); + makeBlink(); } void onDetachedFromWindow() { @@ -1685,17 +1685,12 @@ public class Editor { void onWindowFocusChanged(boolean hasWindowFocus) { if (hasWindowFocus) { - if (mBlink != null) { - mBlink.uncancel(); - makeBlink(); - } + resumeBlink(); if (mTextView.hasSelection() && !extractedTextModeWillBeStarted()) { refreshTextActionMode(); } } else { - if (mBlink != null) { - mBlink.cancel(); - } + suspendBlink(); if (mInputContentType != null) { mInputContentType.enterDown = false; } @@ -2851,7 +2846,8 @@ public class Editor { * @return True when the TextView isFocused and has a valid zero-length selection (cursor). */ private boolean shouldBlink() { - if (!isCursorVisible() || !mTextView.isFocused()) return false; + if (!isCursorVisible() || !mTextView.isFocused() + || mTextView.getWindowVisibility() != mTextView.VISIBLE) return false; final int start = mTextView.getSelectionStart(); if (start < 0) return false; @@ -2873,6 +2869,17 @@ public class Editor { } } + /** + * + * @return whether the Blink runnable is blinking or not, if null return false. + * @hide + */ + @VisibleForTesting + public boolean isBlinking() { + if (mBlink == null) return false; + return !mBlink.mCancelled; + } + private class Blink implements Runnable { private boolean mCancelled; diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java index 8ca763e81757..33ea2e4af473 100644 --- a/core/java/android/window/TransitionInfo.java +++ b/core/java/android/window/TransitionInfo.java @@ -110,11 +110,11 @@ public final class TransitionInfo implements Parcelable { /** The container is an input-method window. */ public static final int FLAG_IS_INPUT_METHOD = 1 << 8; - /** The container is ActivityEmbedding embedded. */ - public static final int FLAG_IS_EMBEDDED = 1 << 9; + /** The container is in a Task with embedded activity. */ + public static final int FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY = 1 << 9; - /** The first unused bit. This can be used by remotes to attach custom flags to this change. */ - public static final int FLAG_FIRST_CUSTOM = 1 << 10; + /** The container fills its parent Task before and after the transition. */ + public static final int FLAG_FILLS_TASK = 1 << 10; /** The container is going to show IME on its task after the transition. */ public static final int FLAG_WILL_IME_SHOWN = 1 << 11; @@ -125,6 +125,9 @@ public final class TransitionInfo implements Parcelable { /** The container attaches work profile thumbnail for cross profile animation. */ public static final int FLAG_CROSS_PROFILE_WORK_THUMBNAIL = 1 << 13; + /** The first unused bit. This can be used by remotes to attach custom flags to this change. */ + public static final int FLAG_FIRST_CUSTOM = 1 << 14; + /** @hide */ @IntDef(prefix = { "FLAG_" }, value = { FLAG_NONE, @@ -137,9 +140,12 @@ public final class TransitionInfo implements Parcelable { FLAG_OCCLUDES_KEYGUARD, FLAG_DISPLAY_HAS_ALERT_WINDOWS, FLAG_IS_INPUT_METHOD, - FLAG_IS_EMBEDDED, - FLAG_FIRST_CUSTOM, - FLAG_WILL_IME_SHOWN + FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY, + FLAG_FILLS_TASK, + FLAG_WILL_IME_SHOWN, + FLAG_CROSS_PROFILE_OWNER_THUMBNAIL, + FLAG_CROSS_PROFILE_WORK_THUMBNAIL, + FLAG_FIRST_CUSTOM }) public @interface ChangeFlags {} @@ -322,28 +328,31 @@ public final class TransitionInfo implements Parcelable { sb.append("IS_INPUT_METHOD"); } if ((flags & FLAG_TRANSLUCENT) != 0) { - sb.append((sb.length() == 0 ? "" : "|") + "TRANSLUCENT"); + sb.append(sb.length() == 0 ? "" : "|").append("TRANSLUCENT"); } if ((flags & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) != 0) { - sb.append((sb.length() == 0 ? "" : "|") + "STARTING_WINDOW_TRANSFER"); + sb.append(sb.length() == 0 ? "" : "|").append("STARTING_WINDOW_TRANSFER"); } if ((flags & FLAG_IS_VOICE_INTERACTION) != 0) { - sb.append((sb.length() == 0 ? "" : "|") + "IS_VOICE_INTERACTION"); + sb.append(sb.length() == 0 ? "" : "|").append("IS_VOICE_INTERACTION"); } if ((flags & FLAG_IS_DISPLAY) != 0) { - sb.append((sb.length() == 0 ? "" : "|") + "IS_DISPLAY"); + sb.append(sb.length() == 0 ? "" : "|").append("IS_DISPLAY"); } if ((flags & FLAG_OCCLUDES_KEYGUARD) != 0) { - sb.append((sb.length() == 0 ? "" : "|") + "OCCLUDES_KEYGUARD"); + sb.append(sb.length() == 0 ? "" : "|").append("OCCLUDES_KEYGUARD"); } if ((flags & FLAG_DISPLAY_HAS_ALERT_WINDOWS) != 0) { - sb.append((sb.length() == 0 ? "" : "|") + "DISPLAY_HAS_ALERT_WINDOWS"); + sb.append(sb.length() == 0 ? "" : "|").append("DISPLAY_HAS_ALERT_WINDOWS"); + } + if ((flags & FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY) != 0) { + sb.append(sb.length() == 0 ? "" : "|").append("IN_TASK_WITH_EMBEDDED_ACTIVITY"); } - if ((flags & FLAG_IS_EMBEDDED) != 0) { - sb.append((sb.length() == 0 ? "" : "|") + "IS_EMBEDDED"); + if ((flags & FLAG_FILLS_TASK) != 0) { + sb.append(sb.length() == 0 ? "" : "|").append("FILLS_TASK"); } if ((flags & FLAG_FIRST_CUSTOM) != 0) { - sb.append((sb.length() == 0 ? "" : "|") + "FIRST_CUSTOM"); + sb.append(sb.length() == 0 ? "" : "|").append("FIRST_CUSTOM"); } return sb.toString(); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java index a8764e05c3e2..d76ad3d27c70 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java @@ -517,7 +517,9 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback, getViewTreeObserver().removeOnComputeInternalInsetsListener(this); } - ActivityManager.RunningTaskInfo getTaskInfo() { + /** Returns the task info for the task in the TaskView. */ + @Nullable + public ActivityManager.RunningTaskInfo getTaskInfo() { return mTaskInfo; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java index e0004fcaa060..521a65cc4df6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java @@ -16,7 +16,8 @@ package com.android.wm.shell.activityembedding; -import static android.window.TransitionInfo.FLAG_IS_EMBEDDED; +import static android.window.TransitionInfo.FLAG_FILLS_TASK; +import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY; import static java.util.Objects.requireNonNull; @@ -84,12 +85,23 @@ public class ActivityEmbeddingController implements Transitions.TransitionHandle @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback) { - // TODO(b/207070762) Handle AE animation as a part of other transitions. - // Only handle the transition if all containers are embedded. + boolean containsEmbeddingSplit = false; for (TransitionInfo.Change change : info.getChanges()) { - if (!isEmbedded(change)) { + if (!change.hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)) { + // Only animate the transition if all changes are in a Task with ActivityEmbedding. return false; } + if (!containsEmbeddingSplit && !change.hasFlags(FLAG_FILLS_TASK)) { + // Whether the Task contains any ActivityEmbedding split before or after the + // transition. + containsEmbeddingSplit = true; + } + } + if (!containsEmbeddingSplit) { + // Let the system to play the default animation if there is no ActivityEmbedding split + // window. This allows to play the app customized animation when there is no embedding, + // such as the device is in a folded state. + return false; } // Start ActivityEmbedding animation. @@ -119,8 +131,4 @@ public class ActivityEmbeddingController implements Transitions.TransitionHandle } callback.onTransitionFinished(null /* wct */, null /* wctCB */); } - - private static boolean isEmbedded(@NonNull TransitionInfo.Change change) { - return (change.getFlags() & FLAG_IS_EMBEDDED) != 0; - } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java index aeaf6eda9809..be100bb1dd34 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java @@ -1270,7 +1270,7 @@ public class BubbleStackView extends FrameLayout } final boolean seen = getPrefBoolean(ManageEducationViewKt.PREF_MANAGED_EDUCATION); final boolean shouldShow = (!seen || BubbleDebugConfig.forceShowUserEducation(mContext)) - && mExpandedBubble != null; + && mExpandedBubble != null && mExpandedBubble.getExpandedView() != null; if (BubbleDebugConfig.DEBUG_USER_EDUCATION) { Log.d(TAG, "Show manage edu: " + shouldShow); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/DismissView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/DismissView.kt index 063dac3d4109..ab194dfb3ce9 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/DismissView.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/DismissView.kt @@ -24,8 +24,8 @@ import android.util.IntProperty import android.view.Gravity import android.view.View import android.view.ViewGroup -import android.view.WindowManager import android.view.WindowInsets +import android.view.WindowManager import android.widget.FrameLayout import androidx.dynamicanimation.animation.DynamicAnimation import androidx.dynamicanimation.animation.SpringForce.DAMPING_RATIO_LOW_BOUNCY @@ -41,6 +41,7 @@ class DismissView(context: Context) : FrameLayout(context) { var circle = DismissCircleView(context) var isShowing = false + var targetSizeResId: Int private val animator = PhysicsAnimator.getInstance(circle) private val spring = PhysicsAnimator.SpringConfig(STIFFNESS_LOW, DAMPING_RATIO_LOW_BOUNCY) @@ -70,7 +71,8 @@ class DismissView(context: Context) : FrameLayout(context) { setVisibility(View.INVISIBLE) setBackgroundDrawable(gradientDrawable) - val targetSize: Int = resources.getDimensionPixelSize(R.dimen.dismiss_circle_size) + targetSizeResId = R.dimen.dismiss_circle_size + val targetSize: Int = resources.getDimensionPixelSize(targetSizeResId) addView(circle, LayoutParams(targetSize, targetSize, Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL)) // start with circle offscreen so it's animated up @@ -126,7 +128,7 @@ class DismissView(context: Context) : FrameLayout(context) { layoutParams.height = resources.getDimensionPixelSize( R.dimen.floating_dismiss_gradient_height) - val targetSize: Int = resources.getDimensionPixelSize(R.dimen.dismiss_circle_size) + val targetSize = resources.getDimensionPixelSize(targetSizeResId) circle.layoutParams.width = targetSize circle.layoutParams.height = targetSize circle.requestLayout() @@ -153,4 +155,4 @@ class DismissView(context: Context) : FrameLayout(context) { setPadding(0, 0, 0, navInset.bottom + resources.getDimensionPixelSize(R.dimen.floating_dismiss_bottom_margin)) } -}
\ No newline at end of file +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java index 18ce3642335d..247ba605ae58 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java @@ -599,12 +599,12 @@ public abstract class WMShellModule { Context context, ShellInit shellInit, ShellTaskOrganizer shellTaskOrganizer, RootDisplayAreaOrganizer rootDisplayAreaOrganizer, - @ShellMainThread Handler mainHandler + @ShellMainThread Handler mainHandler, + Transitions transitions ) { if (DesktopMode.IS_SUPPORTED) { return Optional.of(new DesktopModeController(context, shellInit, shellTaskOrganizer, - rootDisplayAreaOrganizer, - mainHandler)); + rootDisplayAreaOrganizer, mainHandler, transitions)); } else { return Optional.empty(); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java index 7d34ea481de6..c07ce1065302 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java @@ -18,6 +18,7 @@ package com.android.wm.shell.desktopmode; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.view.WindowManager.TRANSIT_CHANGE; import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE; @@ -37,6 +38,7 @@ import com.android.wm.shell.RootDisplayAreaOrganizer; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.annotations.ShellMainThread; import com.android.wm.shell.sysui.ShellInit; +import com.android.wm.shell.transition.Transitions; /** * Handles windowing changes when desktop mode system setting changes @@ -47,15 +49,18 @@ public class DesktopModeController { private final ShellTaskOrganizer mShellTaskOrganizer; private final RootDisplayAreaOrganizer mRootDisplayAreaOrganizer; private final SettingsObserver mSettingsObserver; + private final Transitions mTransitions; public DesktopModeController(Context context, ShellInit shellInit, ShellTaskOrganizer shellTaskOrganizer, RootDisplayAreaOrganizer rootDisplayAreaOrganizer, - @ShellMainThread Handler mainHandler) { + @ShellMainThread Handler mainHandler, + Transitions transitions) { mContext = context; mShellTaskOrganizer = shellTaskOrganizer; mRootDisplayAreaOrganizer = rootDisplayAreaOrganizer; mSettingsObserver = new SettingsObserver(mContext, mainHandler); + mTransitions = transitions; shellInit.addInitCallback(this::onInit, this); } @@ -89,7 +94,11 @@ public class DesktopModeController { } wct.merge(mRootDisplayAreaOrganizer.prepareWindowingModeChange(displayId, targetWindowingMode), true /* transfer */); - mRootDisplayAreaOrganizer.applyTransaction(wct); + if (Transitions.ENABLE_SHELL_TRANSITIONS) { + mTransitions.startTransition(TRANSIT_CHANGE, wct, null); + } else { + mRootDisplayAreaOrganizer.applyTransaction(wct); + } } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java index 3740a1b1c335..7d1259a732c9 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java @@ -256,7 +256,7 @@ public class FullscreenTaskListener<T extends AutoCloseable> windowDecor = mWindowDecorOfVanishedTasks.removeReturnOld(taskInfo.taskId); } - if (mWindowDecorViewModelOptional.isPresent()) { + if (mWindowDecorViewModelOptional.isPresent() && windowDecor != null) { mWindowDecorViewModelOptional.get().setupWindowDecorationForTransition( taskInfo, startT, finishT, windowDecor); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java index b0080b24c609..e7ec15e70c11 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java @@ -45,11 +45,6 @@ class MainStage extends StageTaskListener { iconProvider); } - @Override - void dismiss(WindowContainerTransaction wct, boolean toTop) { - deactivate(wct, toTop); - } - boolean isActive() { return mIsActive; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java index 86efbe0af79c..8639b36faf4c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java @@ -42,11 +42,6 @@ class SideStage extends StageTaskListener { iconProvider); } - @Override - void dismiss(WindowContainerTransaction wct, boolean toTop) { - removeAllTasks(wct, toTop); - } - boolean removeAllTasks(WindowContainerTransaction wct, boolean toTop) { if (mChildrenTaskInfo.size() == 0) return false; wct.reparentTasks( diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index 946dfdecc5d4..db35b48eb898 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -123,6 +123,7 @@ import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.recents.RecentTasksController; import com.android.wm.shell.splitscreen.SplitScreen.StageType; import com.android.wm.shell.splitscreen.SplitScreenController.ExitReason; +import com.android.wm.shell.transition.DefaultMixedHandler; import com.android.wm.shell.transition.LegacyTransitions; import com.android.wm.shell.transition.Transitions; import com.android.wm.shell.util.SplitBounds; @@ -203,6 +204,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, @StageType private int mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED; + private DefaultMixedHandler mMixedHandler; + private final SplitWindowManager.ParentContainerCallbacks mParentContainerCallbacks = new SplitWindowManager.ParentContainerCallbacks() { @Override @@ -326,6 +329,10 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, transitions.addHandler(this); } + public void setMixedHandler(DefaultMixedHandler mixedHandler) { + mMixedHandler = mixedHandler; + } + @VisibleForTesting SplitScreenTransitions getSplitTransitions() { return mSplitTransitions; @@ -927,13 +934,10 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, // Expand to top side split as full screen for fading out decor animation and dismiss // another side split(Moving its children to bottom). mIsExiting = true; - final StageTaskListener tempFullStage = childrenToTop; - final StageTaskListener dismissStage = mMainStage == childrenToTop - ? mSideStage : mMainStage; - tempFullStage.resetBounds(wct); - wct.setSmallestScreenWidthDp(tempFullStage.mRootTaskInfo.token, + childrenToTop.resetBounds(wct); + wct.reorder(childrenToTop.mRootTaskInfo.token, true); + wct.setSmallestScreenWidthDp(childrenToTop.mRootTaskInfo.token, SMALLEST_SCREEN_WIDTH_DP_UNDEFINED); - dismissStage.dismiss(wct, false /* toTop */); } mSyncQueue.queue(wct); mSyncQueue.runInSync(t -> { @@ -950,7 +954,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, childrenToTop.fadeOutDecor(() -> { WindowContainerTransaction finishedWCT = new WindowContainerTransaction(); mIsExiting = false; - childrenToTop.dismiss(finishedWCT, true /* toTop */); + mMainStage.deactivate(finishedWCT, childrenToTop == mMainStage /* toTop */); + mSideStage.removeAllTasks(finishedWCT, childrenToTop == mSideStage /* toTop */); finishedWCT.reorder(mRootTaskInfo.token, false /* toTop */); finishedWCT.setForceTranslucent(mRootTaskInfo.token, true); finishedWCT.setBounds(mSideStage.mRootTaskInfo.token, mTempRect1); @@ -1879,8 +1884,25 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, // Use normal animations. return false; + } else if (mMixedHandler != null && hasDisplayChange(info)) { + // A display-change has been un-expectedly inserted into the transition. Redirect + // handling to the mixed-handler to deal with splitting it up. + if (mMixedHandler.animatePendingSplitWithDisplayChange(transition, info, + startTransaction, finishTransaction, finishCallback)) { + return true; + } } + return startPendingAnimation(transition, info, startTransaction, finishTransaction, + finishCallback); + } + + /** Starts the pending transition animation. */ + public boolean startPendingAnimation(@NonNull IBinder transition, + @NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction startTransaction, + @NonNull SurfaceControl.Transaction finishTransaction, + @NonNull Transitions.TransitionFinishCallback finishCallback) { boolean shouldAnimate = true; if (mSplitTransitions.isPendingEnter(transition)) { shouldAnimate = startPendingEnterAnimation( @@ -1899,6 +1921,15 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, return true; } + private boolean hasDisplayChange(TransitionInfo info) { + boolean has = false; + for (int iC = 0; iC < info.getChanges().size() && !has; ++iC) { + final TransitionInfo.Change change = info.getChanges().get(iC); + has = change.getMode() == TRANSIT_CHANGE && (change.getFlags() & FLAG_IS_DISPLAY) != 0; + } + return has; + } + /** Called to clean-up state and do house-keeping after the animation is done. */ public void onTransitionAnimationComplete() { // If still playing, let it finish. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java index 1af9415fca3a..6b90eabe3bd2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java @@ -106,11 +106,6 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener { taskOrganizer.createRootTask(displayId, WINDOWING_MODE_MULTI_WINDOW, this); } - /** - * General function for dismiss this stage. - */ - void dismiss(WindowContainerTransaction wct, boolean toTop) {} - int getChildCount() { return mChildrenTaskInfo.size(); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java index bcf4fbda0e0c..3cba92956f95 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java @@ -17,6 +17,7 @@ package com.android.wm.shell.transition; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; +import static android.view.WindowManager.TRANSIT_CHANGE; import static android.view.WindowManager.TRANSIT_TO_BACK; import static android.window.TransitionInfo.FLAG_IS_WALLPAPER; @@ -57,6 +58,9 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler { private static class MixedTransition { static final int TYPE_ENTER_PIP_FROM_SPLIT = 1; + /** Both the display and split-state (enter/exit) is changing */ + static final int TYPE_DISPLAY_AND_SPLIT_CHANGE = 2; + /** The default animation for this mixed transition. */ static final int ANIM_TYPE_DEFAULT = 0; @@ -69,6 +73,7 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler { Transitions.TransitionFinishCallback mFinishCallback = null; Transitions.TransitionHandler mLeftoversHandler = null; + WindowContainerTransaction mFinishWCT = null; /** * Mixed transitions are made up of multiple "parts". This keeps track of how many @@ -95,6 +100,9 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler { mPipHandler = pipTouchHandlerOptional.get().getTransitionHandler(); mSplitHandler = splitScreenControllerOptional.get().getTransitionHandler(); mPlayer.addHandler(this); + if (mSplitHandler != null) { + mSplitHandler.setMixedHandler(this); + } }, this); } } @@ -122,10 +130,12 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler { } private TransitionInfo subCopy(@NonNull TransitionInfo info, - @WindowManager.TransitionType int newType) { - final TransitionInfo out = new TransitionInfo(newType, info.getFlags()); - for (int i = 0; i < info.getChanges().size(); ++i) { - out.getChanges().add(info.getChanges().get(i)); + @WindowManager.TransitionType int newType, boolean withChanges) { + final TransitionInfo out = new TransitionInfo(newType, withChanges ? info.getFlags() : 0); + if (withChanges) { + for (int i = 0; i < info.getChanges().size(); ++i) { + out.getChanges().add(info.getChanges().get(i)); + } } out.setRootLeash(info.getRootLeash(), info.getRootOffset().x, info.getRootOffset().y); out.setAnimationOptions(info.getAnimationOptions()); @@ -157,6 +167,8 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler { if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT) { return animateEnterPipFromSplit(mixed, info, startTransaction, finishTransaction, finishCallback); + } else if (mixed.mType == MixedTransition.TYPE_DISPLAY_AND_SPLIT_CHANGE) { + return false; } else { mActiveTransitions.remove(mixed); throw new IllegalStateException("Starting mixed animation without a known mixed type? " @@ -173,7 +185,7 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler { + "entering PIP while Split-Screen is active."); TransitionInfo.Change pipChange = null; TransitionInfo.Change wallpaper = null; - final TransitionInfo everythingElse = subCopy(info, TRANSIT_TO_BACK); + final TransitionInfo everythingElse = subCopy(info, TRANSIT_TO_BACK, true /* changes */); boolean homeIsOpening = false; for (int i = info.getChanges().size() - 1; i >= 0; --i) { TransitionInfo.Change change = info.getChanges().get(i); @@ -254,6 +266,87 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler { return true; } + private void unlinkMissingParents(TransitionInfo from) { + for (int i = 0; i < from.getChanges().size(); ++i) { + final TransitionInfo.Change chg = from.getChanges().get(i); + if (chg.getParent() == null) continue; + if (from.getChange(chg.getParent()) == null) { + from.getChanges().get(i).setParent(null); + } + } + } + + private boolean isWithinTask(TransitionInfo info, TransitionInfo.Change chg) { + TransitionInfo.Change curr = chg; + while (curr != null) { + if (curr.getTaskInfo() != null) return true; + if (curr.getParent() == null) break; + curr = info.getChange(curr.getParent()); + } + return false; + } + + /** + * This is intended to be called by SplitCoordinator as a helper to mix an already-pending + * split transition with a display-change. The use-case for this is when a display + * change/rotation gets collected into a split-screen enter/exit transition which has already + * been claimed by StageCoordinator.handleRequest . This happens during launcher tests. + */ + public boolean animatePendingSplitWithDisplayChange(@NonNull IBinder transition, + @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startT, + @NonNull SurfaceControl.Transaction finishT, + @NonNull Transitions.TransitionFinishCallback finishCallback) { + final TransitionInfo everythingElse = subCopy(info, info.getType(), true /* withChanges */); + final TransitionInfo displayPart = subCopy(info, TRANSIT_CHANGE, false /* withChanges */); + for (int i = info.getChanges().size() - 1; i >= 0; --i) { + TransitionInfo.Change change = info.getChanges().get(i); + if (isWithinTask(info, change)) continue; + displayPart.addChange(change); + everythingElse.getChanges().remove(i); + } + if (displayPart.getChanges().isEmpty()) return false; + unlinkMissingParents(everythingElse); + final MixedTransition mixed = new MixedTransition( + MixedTransition.TYPE_DISPLAY_AND_SPLIT_CHANGE, transition); + mixed.mFinishCallback = finishCallback; + mActiveTransitions.add(mixed); + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Animation is a mix of display change " + + "and split change."); + // We need to split the transition into 2 parts: the split part and the display part. + mixed.mInFlightSubAnimations = 2; + + Transitions.TransitionFinishCallback finishCB = (wct, wctCB) -> { + --mixed.mInFlightSubAnimations; + if (wctCB != null) { + throw new IllegalArgumentException("Can't mix transitions that require finish" + + " sync callback"); + } + if (wct != null) { + if (mixed.mFinishWCT == null) { + mixed.mFinishWCT = wct; + } else { + mixed.mFinishWCT.merge(wct, true /* transfer */); + } + } + if (mixed.mInFlightSubAnimations > 0) return; + mActiveTransitions.remove(mixed); + mixed.mFinishCallback.onTransitionFinished(mixed.mFinishWCT, null /* wctCB */); + }; + + // Dispatch the display change. This will most-likely be taken by the default handler. + // Do this first since the first handler used will apply the startT; the display change + // needs to take a screenshot before that happens so we need it to be the first handler. + mixed.mLeftoversHandler = mPlayer.dispatchTransition(mixed.mTransition, displayPart, + startT, finishT, finishCB, mSplitHandler); + + // Note: at this point, startT has probably already been applied, so we are basically + // giving splitHandler an empty startT. This is currently OK because display-change will + // grab a screenshot and paste it on top anyways. + mSplitHandler.startPendingAnimation( + transition, everythingElse, startT, finishT, finishCB); + return true; + } + @Override public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget, @@ -279,6 +372,8 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler { } else { mPipHandler.end(); } + } else if (mixed.mType == MixedTransition.TYPE_DISPLAY_AND_SPLIT_CHANGE) { + // queue } else { throw new IllegalStateException("Playing a mixed transition with unknown type? " + mixed.mType); 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 b2e45a6b3a5c..a7234c1d3cb8 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 @@ -17,7 +17,7 @@ package com.android.wm.shell.activityembedding; import static android.view.WindowManager.TRANSIT_OPEN; -import static android.window.TransitionInfo.FLAG_IS_EMBEDDED; +import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; @@ -57,7 +57,7 @@ public class ActivityEmbeddingAnimationRunnerTests extends ActivityEmbeddingAnim public void testStartAnimation() { final TransitionInfo info = new TransitionInfo(TRANSIT_OPEN, 0); final TransitionInfo.Change embeddingChange = createChange(); - embeddingChange.setFlags(FLAG_IS_EMBEDDED); + embeddingChange.setFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY); info.addChange(embeddingChange); doReturn(mAnimator).when(mAnimRunner).createAnimator(any(), any(), any(), any()); 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 84befdddabdb..3792e8361284 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 @@ -16,6 +16,9 @@ package com.android.wm.shell.activityembedding; +import static android.window.TransitionInfo.FLAG_FILLS_TASK; +import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY; + import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static org.junit.Assert.assertNotNull; @@ -24,6 +27,8 @@ import static org.mockito.Mockito.mock; import android.animation.Animator; import android.annotation.CallSuper; +import android.annotation.NonNull; +import android.graphics.Rect; import android.os.IBinder; import android.view.SurfaceControl; import android.window.TransitionInfo; @@ -80,4 +85,23 @@ abstract class ActivityEmbeddingAnimationTestBase extends ShellTestCase { return new TransitionInfo.Change(mock(WindowContainerToken.class), mock(SurfaceControl.class)); } + + /** + * Creates a mock {@link TransitionInfo.Change} with + * {@link TransitionInfo#FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY} flag. + */ + static TransitionInfo.Change createEmbeddedChange(@NonNull Rect startBounds, + @NonNull Rect endBounds, @NonNull Rect taskBounds) { + final TransitionInfo.Change change = createChange(); + change.setFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY); + change.setStartAbsBounds(startBounds); + change.setEndAbsBounds(endBounds); + if (taskBounds.width() == startBounds.width() + && taskBounds.height() == startBounds.height() + && taskBounds.width() == endBounds.width() + && taskBounds.height() == endBounds.height()) { + change.setFlags(FLAG_FILLS_TASK); + } + return 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 cf43b0030d2a..baecf6fe6673 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 @@ -17,7 +17,6 @@ package com.android.wm.shell.activityembedding; import static android.view.WindowManager.TRANSIT_OPEN; -import static android.window.TransitionInfo.FLAG_IS_EMBEDDED; import static com.android.dx.mockito.inline.extended.ExtendedMockito.never; @@ -29,6 +28,7 @@ import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; +import android.graphics.Rect; import android.window.TransitionInfo; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -48,6 +48,10 @@ import org.junit.runner.RunWith; @RunWith(AndroidJUnit4.class) public class ActivityEmbeddingControllerTests extends ActivityEmbeddingAnimationTestBase { + private static final Rect TASK_BOUNDS = new Rect(0, 0, 1000, 500); + private static final Rect EMBEDDED_LEFT_BOUNDS = new Rect(0, 0, 500, 500); + private static final Rect EMBEDDED_RIGHT_BOUNDS = new Rect(500, 0, 1000, 500); + @Before public void setup() { super.setUp(); @@ -77,13 +81,13 @@ public class ActivityEmbeddingControllerTests extends ActivityEmbeddingAnimation @Test public void testStartAnimation_containsNonActivityEmbeddingChange() { final TransitionInfo info = new TransitionInfo(TRANSIT_OPEN, 0); - final TransitionInfo.Change embeddingChange = createChange(); - embeddingChange.setFlags(FLAG_IS_EMBEDDED); + final TransitionInfo.Change embeddingChange = createEmbeddedChange(EMBEDDED_LEFT_BOUNDS, + EMBEDDED_LEFT_BOUNDS, TASK_BOUNDS); final TransitionInfo.Change nonEmbeddingChange = createChange(); info.addChange(embeddingChange); info.addChange(nonEmbeddingChange); - // No-op + // No-op because it contains non-embedded change. assertFalse(mController.startAnimation(mTransition, info, mStartTransaction, mFinishTransaction, mFinishCallback)); verify(mAnimRunner, never()).startAnimation(any(), any(), any(), any()); @@ -93,13 +97,65 @@ public class ActivityEmbeddingControllerTests extends ActivityEmbeddingAnimation } @Test - public void testStartAnimation_onlyActivityEmbeddingChange() { + public void testStartAnimation_containsOnlyFillTaskActivityEmbeddingChange() { + final TransitionInfo info = new TransitionInfo(TRANSIT_OPEN, 0); + final TransitionInfo.Change embeddingChange = createEmbeddedChange(TASK_BOUNDS, TASK_BOUNDS, + TASK_BOUNDS); + info.addChange(embeddingChange); + + // No-op because it only contains embedded change that fills the Task. We will let the + // default handler to animate such transition. + assertFalse(mController.startAnimation(mTransition, info, mStartTransaction, + mFinishTransaction, mFinishCallback)); + verify(mAnimRunner, never()).startAnimation(any(), any(), any(), any()); + verifyNoMoreInteractions(mStartTransaction); + verifyNoMoreInteractions(mFinishTransaction); + verifyNoMoreInteractions(mFinishCallback); + } + + @Test + public void testStartAnimation_containsActivityEmbeddingSplitChange() { + // Change that occupies only part of the Task. + final TransitionInfo info = new TransitionInfo(TRANSIT_OPEN, 0); + final TransitionInfo.Change embeddingChange = createEmbeddedChange(EMBEDDED_LEFT_BOUNDS, + EMBEDDED_LEFT_BOUNDS, TASK_BOUNDS); + info.addChange(embeddingChange); + + // ActivityEmbeddingController will handle such transition. + assertTrue(mController.startAnimation(mTransition, info, mStartTransaction, + mFinishTransaction, mFinishCallback)); + verify(mAnimRunner).startAnimation(mTransition, info, mStartTransaction, + mFinishTransaction); + verify(mStartTransaction).apply(); + verifyNoMoreInteractions(mFinishTransaction); + } + + @Test + public void testStartAnimation_containsChangeEnterActivityEmbeddingSplit() { + // Change that is entering ActivityEmbedding split. + final TransitionInfo info = new TransitionInfo(TRANSIT_OPEN, 0); + final TransitionInfo.Change embeddingChange = createEmbeddedChange(TASK_BOUNDS, + EMBEDDED_LEFT_BOUNDS, TASK_BOUNDS); + info.addChange(embeddingChange); + + // ActivityEmbeddingController will handle such transition. + assertTrue(mController.startAnimation(mTransition, info, mStartTransaction, + mFinishTransaction, mFinishCallback)); + verify(mAnimRunner).startAnimation(mTransition, info, mStartTransaction, + mFinishTransaction); + verify(mStartTransaction).apply(); + verifyNoMoreInteractions(mFinishTransaction); + } + + @Test + public void testStartAnimation_containsChangeExitActivityEmbeddingSplit() { + // Change that is exiting ActivityEmbedding split. final TransitionInfo info = new TransitionInfo(TRANSIT_OPEN, 0); - final TransitionInfo.Change embeddingChange = createChange(); - embeddingChange.setFlags(FLAG_IS_EMBEDDED); + final TransitionInfo.Change embeddingChange = createEmbeddedChange(EMBEDDED_RIGHT_BOUNDS, + TASK_BOUNDS, TASK_BOUNDS); info.addChange(embeddingChange); - // No-op + // ActivityEmbeddingController will handle such transition. assertTrue(mController.startAnimation(mTransition, info, mStartTransaction, mFinishTransaction, mFinishCallback)); verify(mAnimRunner).startAnimation(mTransition, info, mStartTransaction, @@ -115,8 +171,8 @@ public class ActivityEmbeddingControllerTests extends ActivityEmbeddingAnimation () -> mController.onAnimationFinished(mTransition)); final TransitionInfo info = new TransitionInfo(TRANSIT_OPEN, 0); - final TransitionInfo.Change embeddingChange = createChange(); - embeddingChange.setFlags(FLAG_IS_EMBEDDED); + final TransitionInfo.Change embeddingChange = createEmbeddedChange(EMBEDDED_LEFT_BOUNDS, + EMBEDDED_LEFT_BOUNDS, TASK_BOUNDS); info.addChange(embeddingChange); mController.startAnimation(mTransition, info, mStartTransaction, mFinishTransaction, mFinishCallback); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java index ef532e449bd6..577942505b13 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java @@ -43,6 +43,7 @@ import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.sysui.ShellInit; +import com.android.wm.shell.transition.Transitions; import org.junit.Before; import org.junit.Test; @@ -63,6 +64,8 @@ public class DesktopModeControllerTest extends ShellTestCase { private ShellExecutor mTestExecutor; @Mock private Handler mMockHandler; + @Mock + private Transitions mMockTransitions; private DesktopModeController mController; private ShellInit mShellInit; @@ -72,7 +75,7 @@ public class DesktopModeControllerTest extends ShellTestCase { mShellInit = Mockito.spy(new ShellInit(mTestExecutor)); mController = new DesktopModeController(mContext, mShellInit, mShellTaskOrganizer, - mRootDisplayAreaOrganizer, mMockHandler); + mRootDisplayAreaOrganizer, mMockHandler, mMockTransitions); mShellInit.init(); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java index ea9390ee8efd..9240abfbe47f 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java @@ -225,7 +225,6 @@ public class StageCoordinatorTests extends ShellTestCase { mStageCoordinator.exitSplitScreen(testTaskId, EXIT_REASON_RETURN_HOME); verify(mMainStage).reorderChild(eq(testTaskId), eq(true), any(WindowContainerTransaction.class)); - verify(mSideStage).dismiss(any(WindowContainerTransaction.class), eq(false)); verify(mMainStage).resetBounds(any(WindowContainerTransaction.class)); } @@ -239,7 +238,6 @@ public class StageCoordinatorTests extends ShellTestCase { verify(mSideStage).reorderChild(eq(testTaskId), eq(true), any(WindowContainerTransaction.class)); verify(mSideStage).resetBounds(any(WindowContainerTransaction.class)); - verify(mMainStage).dismiss(any(WindowContainerTransaction.class), eq(false)); } @Test diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java index 3152e65d5a36..fa056e2b77bd 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java @@ -16,6 +16,22 @@ package com.android.settingslib.bluetooth; +import static android.bluetooth.BluetoothAdapter.STATE_CONNECTED; +import static android.bluetooth.BluetoothAdapter.STATE_CONNECTING; +import static android.bluetooth.BluetoothAdapter.STATE_DISCONNECTED; +import static android.bluetooth.BluetoothAdapter.STATE_DISCONNECTING; +import static android.bluetooth.BluetoothAdapter.STATE_OFF; +import static android.bluetooth.BluetoothAdapter.STATE_ON; +import static android.bluetooth.BluetoothAdapter.STATE_TURNING_OFF; +import static android.bluetooth.BluetoothAdapter.STATE_TURNING_ON; + +import android.annotation.IntDef; +import android.annotation.Nullable; + +import androidx.annotation.NonNull; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; /** * BluetoothCallback provides a callback interface for the settings @@ -33,7 +49,7 @@ public interface BluetoothCallback { * {@link android.bluetooth.BluetoothAdapter#STATE_ON}, * {@link android.bluetooth.BluetoothAdapter#STATE_TURNING_OFF}. */ - default void onBluetoothStateChanged(int bluetoothState) {} + default void onBluetoothStateChanged(@AdapterState int bluetoothState) {} /** * It will be called when the local Bluetooth adapter has started @@ -54,14 +70,14 @@ public interface BluetoothCallback { * * @param cachedDevice the Bluetooth device. */ - default void onDeviceAdded(CachedBluetoothDevice cachedDevice) {} + default void onDeviceAdded(@NonNull CachedBluetoothDevice cachedDevice) {} /** * It will be called when requiring to remove a remote device from CachedBluetoothDevice list * * @param cachedDevice the Bluetooth device. */ - default void onDeviceDeleted(CachedBluetoothDevice cachedDevice) {} + default void onDeviceDeleted(@NonNull CachedBluetoothDevice cachedDevice) {} /** * It will be called when bond state of a remote device is changed. @@ -73,7 +89,8 @@ public interface BluetoothCallback { * {@link android.bluetooth.BluetoothDevice#BOND_BONDING}, * {@link android.bluetooth.BluetoothDevice#BOND_BONDED}. */ - default void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState) {} + default void onDeviceBondStateChanged( + @NonNull CachedBluetoothDevice cachedDevice, int bondState) {} /** * It will be called in following situations: @@ -89,7 +106,9 @@ public interface BluetoothCallback { * {@link android.bluetooth.BluetoothAdapter#STATE_CONNECTED}, * {@link android.bluetooth.BluetoothAdapter#STATE_DISCONNECTING}. */ - default void onConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state) {} + default void onConnectionStateChanged( + @Nullable CachedBluetoothDevice cachedDevice, + @ConnectionState int state) {} /** * It will be called when device been set as active for {@code bluetoothProfile} @@ -101,7 +120,8 @@ public interface BluetoothCallback { * @param activeDevice the active Bluetooth device. * @param bluetoothProfile the profile of active Bluetooth device. */ - default void onActiveDeviceChanged(CachedBluetoothDevice activeDevice, int bluetoothProfile) {} + default void onActiveDeviceChanged( + @Nullable CachedBluetoothDevice activeDevice, int bluetoothProfile) {} /** * It will be called in following situations: @@ -124,8 +144,10 @@ public interface BluetoothCallback { * {@link android.bluetooth.BluetoothProfile#STATE_DISCONNECTING}. * @param bluetoothProfile the BluetoothProfile id. */ - default void onProfileConnectionStateChanged(CachedBluetoothDevice cachedDevice, - int state, int bluetoothProfile) { + default void onProfileConnectionStateChanged( + @NonNull CachedBluetoothDevice cachedDevice, + @ConnectionState int state, + int bluetoothProfile) { } /** @@ -138,6 +160,24 @@ public interface BluetoothCallback { * {@link android.bluetooth.BluetoothAdapter#STATE_DISCONNECTED}, * {@link android.bluetooth.BluetoothAdapter#STATE_CONNECTED} */ - default void onAclConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state) { - } + default void onAclConnectionStateChanged( + @NonNull CachedBluetoothDevice cachedDevice, int state) {} + + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = { "STATE_" }, value = { + STATE_DISCONNECTED, + STATE_CONNECTING, + STATE_CONNECTED, + STATE_DISCONNECTING, + }) + @interface ConnectionState {} + + @IntDef(prefix = { "STATE_" }, value = { + STATE_OFF, + STATE_TURNING_ON, + STATE_ON, + STATE_TURNING_OFF, + }) + @Retention(RetentionPolicy.SOURCE) + @interface AdapterState {} } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java index 51812f043908..a9f4e9c74103 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java @@ -32,6 +32,7 @@ import android.os.UserHandle; import android.telephony.TelephonyManager; import android.util.Log; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; @@ -193,19 +194,19 @@ public class BluetoothEventManager { return deviceAdded; } - void dispatchDeviceAdded(CachedBluetoothDevice cachedDevice) { + void dispatchDeviceAdded(@NonNull CachedBluetoothDevice cachedDevice) { for (BluetoothCallback callback : mCallbacks) { callback.onDeviceAdded(cachedDevice); } } - void dispatchDeviceRemoved(CachedBluetoothDevice cachedDevice) { + void dispatchDeviceRemoved(@NonNull CachedBluetoothDevice cachedDevice) { for (BluetoothCallback callback : mCallbacks) { callback.onDeviceDeleted(cachedDevice); } } - void dispatchProfileConnectionStateChanged(CachedBluetoothDevice device, int state, + void dispatchProfileConnectionStateChanged(@NonNull CachedBluetoothDevice device, int state, int bluetoothProfile) { for (BluetoothCallback callback : mCallbacks) { callback.onProfileConnectionStateChanged(device, state, bluetoothProfile); @@ -228,7 +229,8 @@ public class BluetoothEventManager { } @VisibleForTesting - void dispatchActiveDeviceChanged(CachedBluetoothDevice activeDevice, + void dispatchActiveDeviceChanged( + @Nullable CachedBluetoothDevice activeDevice, int bluetoothProfile) { for (CachedBluetoothDevice cachedDevice : mDeviceManager.getCachedDevicesCopy()) { boolean isActive = Objects.equals(cachedDevice, activeDevice); @@ -239,7 +241,7 @@ public class BluetoothEventManager { } } - private void dispatchAclStateChanged(CachedBluetoothDevice activeDevice, int state) { + private void dispatchAclStateChanged(@NonNull CachedBluetoothDevice activeDevice, int state) { for (BluetoothCallback callback : mCallbacks) { callback.onAclConnectionStateChanged(activeDevice, state); } @@ -456,6 +458,7 @@ public class BluetoothEventManager { Log.w(TAG, "ActiveDeviceChangedHandler: action is null"); return; } + @Nullable CachedBluetoothDevice activeDevice = mDeviceManager.findDevice(device); int bluetoothProfile = 0; if (Objects.equals(action, BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED)) { diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt index 7d4dcf88542b..ebabdf571dfd 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt @@ -352,8 +352,11 @@ class ActivityLaunchAnimator( * The animation was cancelled. Note that [onLaunchAnimationEnd] will still be called after * this if the animation was already started, i.e. if [onLaunchAnimationStart] was called * before the cancellation. + * + * If this launch animation affected the occlusion state of the keyguard, WM will provide + * us with [newKeyguardOccludedState] so that we can set the occluded state appropriately. */ - fun onLaunchAnimationCancelled() {} + fun onLaunchAnimationCancelled(newKeyguardOccludedState: Boolean? = null) {} } @VisibleForTesting @@ -667,7 +670,7 @@ class ActivityLaunchAnimator( removeTimeout() context.mainExecutor.execute { animation?.cancel() - controller.onLaunchAnimationCancelled() + controller.onLaunchAnimationCancelled(newKeyguardOccludedState = isKeyguardOccluded) } } diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt index eac5d275092a..9656b8a99d41 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt @@ -238,7 +238,7 @@ constructor( } } - override fun onLaunchAnimationCancelled() { + override fun onLaunchAnimationCancelled(newKeyguardOccludedState: Boolean?) { controller.onLaunchAnimationCancelled() enableDialogDismiss() dialog.dismiss() diff --git a/packages/SystemUI/compose/gallery/res/drawable/kitten1.jpeg b/packages/SystemUI/compose/gallery/res/drawable/kitten1.jpeg Binary files differnew file mode 100644 index 000000000000..6241b0b44bb6 --- /dev/null +++ b/packages/SystemUI/compose/gallery/res/drawable/kitten1.jpeg diff --git a/packages/SystemUI/compose/gallery/res/drawable/kitten2.jpeg b/packages/SystemUI/compose/gallery/res/drawable/kitten2.jpeg Binary files differnew file mode 100644 index 000000000000..870ef13ee2d9 --- /dev/null +++ b/packages/SystemUI/compose/gallery/res/drawable/kitten2.jpeg diff --git a/packages/SystemUI/compose/gallery/res/drawable/kitten3.jpeg b/packages/SystemUI/compose/gallery/res/drawable/kitten3.jpeg Binary files differnew file mode 100644 index 000000000000..bb7261c10033 --- /dev/null +++ b/packages/SystemUI/compose/gallery/res/drawable/kitten3.jpeg diff --git a/packages/SystemUI/compose/gallery/res/drawable/kitten4.jpeg b/packages/SystemUI/compose/gallery/res/drawable/kitten4.jpeg Binary files differnew file mode 100644 index 000000000000..e34b7ddf58ce --- /dev/null +++ b/packages/SystemUI/compose/gallery/res/drawable/kitten4.jpeg diff --git a/packages/SystemUI/compose/gallery/res/drawable/kitten5.jpeg b/packages/SystemUI/compose/gallery/res/drawable/kitten5.jpeg Binary files differnew file mode 100644 index 000000000000..9cde24be59ef --- /dev/null +++ b/packages/SystemUI/compose/gallery/res/drawable/kitten5.jpeg diff --git a/packages/SystemUI/compose/gallery/res/drawable/kitten6.jpeg b/packages/SystemUI/compose/gallery/res/drawable/kitten6.jpeg Binary files differnew file mode 100644 index 000000000000..17825b639a26 --- /dev/null +++ b/packages/SystemUI/compose/gallery/res/drawable/kitten6.jpeg diff --git a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/GalleryApp.kt b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/GalleryApp.kt index 2e6456bcc4e1..6805bf83dff4 100644 --- a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/GalleryApp.kt +++ b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/GalleryApp.kt @@ -28,25 +28,25 @@ import com.android.systemui.compose.theme.SystemUITheme /** The gallery app screens. */ object GalleryAppScreens { - val Typography = ChildScreen("typography") { TypographyScreen() } - val MaterialColors = ChildScreen("material_colors") { MaterialColorsScreen() } - val AndroidColors = ChildScreen("android_colors") { AndroidColorsScreen() } - val Buttons = ChildScreen("buttons") { ButtonsScreen() } - val ExampleFeature = ChildScreen("example_feature") { ExampleFeatureScreen() } + private val Typography = ChildScreen("typography") { TypographyScreen() } + private val MaterialColors = ChildScreen("material_colors") { MaterialColorsScreen() } + private val AndroidColors = ChildScreen("android_colors") { AndroidColorsScreen() } + private val Buttons = ChildScreen("buttons") { ButtonsScreen() } + private val ExampleFeature = ChildScreen("example_feature") { ExampleFeatureScreen() } - val PeopleEmpty = + private val PeopleEmpty = ChildScreen("people_empty") { navController -> EmptyPeopleScreen(onResult = { navController.popBackStack() }) } - val PeopleFew = + private val PeopleFew = ChildScreen("people_few") { navController -> FewPeopleScreen(onResult = { navController.popBackStack() }) } - val PeopleFull = + private val PeopleFull = ChildScreen("people_full") { navController -> FullPeopleScreen(onResult = { navController.popBackStack() }) } - val People = + private val People = ParentScreen( "people", mapOf( @@ -55,6 +55,52 @@ object GalleryAppScreens { "Full" to PeopleFull, ) ) + private val UserSwitcherSingleUser = + ChildScreen("user_switcher_single") { navController -> + UserSwitcherScreen( + userCount = 1, + onFinished = navController::popBackStack, + ) + } + private val UserSwitcherThreeUsers = + ChildScreen("user_switcher_three") { navController -> + UserSwitcherScreen( + userCount = 3, + onFinished = navController::popBackStack, + ) + } + private val UserSwitcherFourUsers = + ChildScreen("user_switcher_four") { navController -> + UserSwitcherScreen( + userCount = 4, + onFinished = navController::popBackStack, + ) + } + private val UserSwitcherFiveUsers = + ChildScreen("user_switcher_five") { navController -> + UserSwitcherScreen( + userCount = 5, + onFinished = navController::popBackStack, + ) + } + private val UserSwitcherSixUsers = + ChildScreen("user_switcher_six") { navController -> + UserSwitcherScreen( + userCount = 6, + onFinished = navController::popBackStack, + ) + } + private val UserSwitcher = + ParentScreen( + "user_switcher", + mapOf( + "Single" to UserSwitcherSingleUser, + "Three" to UserSwitcherThreeUsers, + "Four" to UserSwitcherFourUsers, + "Five" to UserSwitcherFiveUsers, + "Six" to UserSwitcherSixUsers, + ) + ) val Home = ParentScreen( @@ -66,6 +112,7 @@ object GalleryAppScreens { "Example feature" to ExampleFeature, "Buttons" to Buttons, "People" to People, + "User Switcher" to UserSwitcher, ) ) } diff --git a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/UserSwitcherScreen.kt b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/UserSwitcherScreen.kt new file mode 100644 index 000000000000..fe9707d22684 --- /dev/null +++ b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/UserSwitcherScreen.kt @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.compose.gallery + +import androidx.compose.runtime.Composable +import androidx.compose.ui.platform.LocalContext +import com.android.systemui.user.Fakes.fakeUserSwitcherViewModel +import com.android.systemui.user.ui.compose.UserSwitcherScreen + +@Composable +fun UserSwitcherScreen( + userCount: Int, + onFinished: () -> Unit, +) { + val context = LocalContext.current.applicationContext + UserSwitcherScreen( + viewModel = fakeUserSwitcherViewModel(context, userCount = userCount), + onFinished = onFinished, + ) +} diff --git a/packages/SystemUI/compose/gallery/src/com/android/systemui/user/Fakes.kt b/packages/SystemUI/compose/gallery/src/com/android/systemui/user/Fakes.kt index 02d76f404a32..91a73ea16dc4 100644 --- a/packages/SystemUI/compose/gallery/src/com/android/systemui/user/Fakes.kt +++ b/packages/SystemUI/compose/gallery/src/com/android/systemui/user/Fakes.kt @@ -58,12 +58,29 @@ object Fakes { (0 until userCount).map { index -> UserModel( id = index, - name = Text.Loaded("user_$index"), + name = + Text.Loaded( + when (index % 6) { + 0 -> "Ross Geller" + 1 -> "Phoebe Buffay" + 2 -> "Monica Geller" + 3 -> "Rachel Greene" + 4 -> "Chandler Bing" + else -> "Joey Tribbiani" + } + ), image = checkNotNull( AppCompatResources.getDrawable( context, - R.drawable.ic_avatar_guest_user + when (index % 6) { + 0 -> R.drawable.kitten1 + 1 -> R.drawable.kitten2 + 2 -> R.drawable.kitten3 + 3 -> R.drawable.kitten4 + 4 -> R.drawable.kitten5 + else -> R.drawable.kitten6 + }, ) ), isSelected = index == 0, diff --git a/packages/SystemUI/ktfmt_includes.txt b/packages/SystemUI/ktfmt_includes.txt index 689938aded44..818248439d0a 100644 --- a/packages/SystemUI/ktfmt_includes.txt +++ b/packages/SystemUI/ktfmt_includes.txt @@ -498,7 +498,6 @@ -packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherContainer.kt -packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherController.kt -packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherFeatureController.kt --packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessor.kt -packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt -packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt -packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiActivityModel.kt diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/FalsingManager.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/FalsingManager.java index 1aaf19e8793f..c50340cfd247 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/FalsingManager.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/FalsingManager.java @@ -147,10 +147,10 @@ public interface FalsingManager { } /** - * Listener that is alerted when a double tap is required to confirm a single tap. + * Listener that is alerted when an additional tap is required to confirm a single tap. **/ interface FalsingTapListener { - void onDoubleTapRequired(); + void onAdditionalTapRequired(); } /** Passed to {@link FalsingManager#onProximityEvent}. */ diff --git a/packages/SystemUI/res-keyguard/drawable/kg_bouncer_secondary_button.xml b/packages/SystemUI/res-keyguard/drawable/kg_bouncer_secondary_button.xml new file mode 100644 index 000000000000..732fc57dbded --- /dev/null +++ b/packages/SystemUI/res-keyguard/drawable/kg_bouncer_secondary_button.xml @@ -0,0 +1,43 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2022 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<inset xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" + android:insetTop="6dp" + android:insetBottom="6dp"> + <ripple android:color="?android:attr/colorControlHighlight"> + <item android:id="@android:id/mask"> + <shape android:shape="rectangle"> + <solid android:color="?android:attr/textColorSecondary"/> + <corners android:radius="24dp"/> + </shape> + </item> + <item> + <shape android:shape="rectangle"> + <corners android:radius="24dp"/> + <solid android:color="@android:color/transparent"/> + <stroke android:color="?androidprv:attr/colorAccentTertiary" + android:width="1dp" + /> + <padding android:left="16dp" + android:top="12dp" + android:right="16dp" + android:bottom="12dp"/> + </shape> + </item> + </ripple> +</inset> diff --git a/packages/SystemUI/res-keyguard/drawable/qs_invert_colors_icon_off.xml b/packages/SystemUI/res-keyguard/drawable/qs_invert_colors_icon_off.xml new file mode 100644 index 000000000000..a34712386d52 --- /dev/null +++ b/packages/SystemUI/res-keyguard/drawable/qs_invert_colors_icon_off.xml @@ -0,0 +1,109 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright (C) 2022 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> +<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:aapt="http://schemas.android.com/aapt"> + <target android:name="_R_G_L_0_G_D_0_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="17" + android:propertyName="pathData" + android:startOffset="0" + android:valueFrom="M7.38 -0.28 C5.69,-2.81 1.5,-8.5 0.01,-7.32 C0.03,-6.22 -0.06,6.16 -0.06,7.78 C1.56,7.81 5.44,7.19 7.37,2.06 C7.37,1.21 7.35,1.69 7.35,1 C7.35,0.09 7.38,0.59 7.38,-0.28c " + android:valueTo="M6 -2.38 C1.81,-6.69 -0.5,-10 -1.72,-7.1 C-1.73,-6.22 -1.73,5.88 -1.7,7.16 C0.25,8.45 3,7.75 5.47,4.91 C6.33,3.91 7.21,2.42 7.24,1.13 C7.26,-0.05 6.87,-1.49 6,-2.38c " + android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + <objectAnimator + android:duration="33" + android:propertyName="pathData" + android:startOffset="17" + android:valueFrom="M6 -2.38 C1.81,-6.69 -0.5,-10 -1.72,-7.1 C-1.73,-6.22 -1.73,5.88 -1.7,7.16 C0.25,8.45 3,7.75 5.47,4.91 C6.33,3.91 7.21,2.42 7.24,1.13 C7.26,-0.05 6.87,-1.49 6,-2.38c " + android:valueTo="M4.94 -3.95 C-0.31,-9.06 0.52,-9.42 -5.75,-3.22 C-8.31,-0.69 -7.05,3 -6.94,3.22 C-3.63,9.31 2.63,9.5 5.94,4.59 C6.61,3.6 7.43,1.16 7.12,0 C6.72,-1.45 6.01,-2.9 4.94,-3.95c " + android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + <objectAnimator + android:duration="67" + android:propertyName="pathData" + android:startOffset="50" + android:valueFrom="M4.94 -3.95 C-0.31,-9.06 0.52,-9.42 -5.75,-3.22 C-8.31,-0.69 -7.05,3 -6.94,3.22 C-3.63,9.31 2.63,9.5 5.94,4.59 C6.61,3.6 7.43,1.16 7.12,0 C6.72,-1.45 6.01,-2.9 4.94,-3.95c " + android:valueTo="M3.07 -5.83 C-0.44,-10.25 -2.56,-6.87 -6.11,-2.6 C-7.03,-1.49 -7.23,1.85 -7.18,2.06 C-5.84,7.25 -0.33,9.23 3.14,6.75 C3.17,5.1 3.17,3.58 3.22,0 C3.25,-3.04 3.1,-4.1 3.07,-5.83c " + android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + <objectAnimator + android:duration="83" + android:propertyName="pathData" + android:startOffset="117" + android:valueFrom="M3.07 -5.83 C-0.44,-10.25 -2.56,-6.87 -6.11,-2.6 C-7.03,-1.49 -7.23,1.85 -7.18,2.06 C-5.84,7.25 -0.33,9.23 3.14,6.75 C3.17,5.1 3.17,3.58 3.22,0 C3.25,-3.04 3.1,-4.1 3.07,-5.83c " + android:valueTo="M0 -8.18 C-1.81,-7.06 -3.89,-5.21 -5.95,-2.81 C-7.56,-0.94 -7.37,0.88 -7.37,1.09 C-7.37,6.31 -2.19,7.99 0,7.99 C0,6.34 -0.01,3.91 -0.01,0 C-0.01,-3.91 0,-6 0,-8.18c " + android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.2,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="time_group"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="433" + android:propertyName="translateX" + android:startOffset="0" + android:valueFrom="0" + android:valueTo="1" + android:valueType="floatType" /> + </set> + </aapt:attr> + </target> + <aapt:attr name="android:drawable"> + <vector + android:width="24dp" + android:height="24dp" + android:viewportHeight="24" + android:viewportWidth="24"> + <group android:name="_R_G"> + <group + android:name="_R_G_L_0_G" + android:translateX="12" + android:translateY="12"> + <path + android:name="_R_G_L_0_G_D_0_P_0" + android:fillAlpha="1" + android:fillColor="#edf2eb" + android:fillType="nonZero" + android:pathData=" M7.38 -0.28 C5.69,-2.81 1.5,-8.5 0.01,-7.32 C0.03,-6.22 -0.06,6.16 -0.06,7.78 C1.56,7.81 5.44,7.19 7.37,2.06 C7.37,1.21 7.35,1.69 7.35,1 C7.35,0.09 7.38,0.59 7.38,-0.28c " /> + <path + android:name="_R_G_L_0_G_D_1_P_0" + android:fillAlpha="1" + android:fillColor="#ffffff" + android:fillType="nonZero" + android:pathData=" M0 -7.19 C0,-7.19 2.93,-4.32 4.38,-2.87 C5.25,-2 5.88,-0.62 5.88,1.19 C5.88,4.5 2.8,6.97 0,7 C-2.8,7.03 -6,4.37 -6,1.13 C-6,-0.43 -5.38,-1.9 -4.25,-3.01 C-4.25,-3.01 0,-7.19 0,-7.19 M-5.65 -4.44 C-5.65,-4.44 -5.65,-4.44 -5.65,-4.44 C-7.1,-3.01 -8,-1.04 -8,1.13 C-8,5.48 -4.42,9 0,9 C4.42,9 8,5.48 8,1.13 C8,-1.04 7.1,-3.01 5.65,-4.44 C5.65,-4.44 5.65,-4.44 5.65,-4.44 C5.65,-4.44 0,-10 0,-10 C0,-10 -5.65,-4.44 -5.65,-4.44c " /> + </group> + </group> + <group android:name="time_group" /> + </vector> + </aapt:attr> +</animated-vector> diff --git a/packages/SystemUI/res-keyguard/drawable/qs_invert_colors_icon_on.xml b/packages/SystemUI/res-keyguard/drawable/qs_invert_colors_icon_on.xml new file mode 100644 index 000000000000..c5609d8e465b --- /dev/null +++ b/packages/SystemUI/res-keyguard/drawable/qs_invert_colors_icon_on.xml @@ -0,0 +1,109 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright (C) 2022 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> +<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:aapt="http://schemas.android.com/aapt"> + <target android:name="_R_G_L_0_G_D_0_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="100" + android:propertyName="pathData" + android:startOffset="0" + android:valueFrom="M0 -7.49 C-2.58,-7.49 -7.47,-2.59 -7.47,0.57 C-7.47,0.77 -7.37,0.88 -7.37,1.09 C-7.37,6.31 -2.19,7.55 0,7.55 C0,5.91 -0.01,3.91 -0.01,0 C-0.01,-3.91 0,-5.31 0,-7.49c " + android:valueTo="M3.11 -6.83 C-1.37,-10.12 -6.04,-3.87 -7.22,0.09 C-7.26,0.29 -7.23,1.85 -7.18,2.06 C-5.84,7.25 -0.33,9.23 3.14,6.75 C3.17,5.1 3.17,3.58 3.22,0 C3.25,-3.04 3.14,-5.1 3.11,-6.83c " + android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.833,0.833 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + <objectAnimator + android:duration="67" + android:propertyName="pathData" + android:startOffset="100" + android:valueFrom="M3.11 -6.83 C-1.37,-10.12 -6.04,-3.87 -7.22,0.09 C-7.26,0.29 -7.23,1.85 -7.18,2.06 C-5.84,7.25 -0.33,9.23 3.14,6.75 C3.17,5.1 3.17,3.58 3.22,0 C3.25,-3.04 3.14,-5.1 3.11,-6.83c " + android:valueTo="M5 -3.95 C-0.25,-9.87 -0.47,-8.74 -5,-3.63 C-7.94,-0.31 -7.05,3 -6.94,3.22 C-3.63,9.31 2.94,9.19 7,3.59 C7.06,1.94 7,3.19 7.12,0 C7.19,-2 5.06,-2.75 5,-3.95c " + android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + <objectAnimator + android:duration="67" + android:propertyName="pathData" + android:startOffset="167" + android:valueFrom="M5 -3.95 C-0.25,-9.87 -0.47,-8.74 -5,-3.63 C-7.94,-0.31 -7.05,3 -6.94,3.22 C-3.63,9.31 2.94,9.19 7,3.59 C7.06,1.94 7,3.19 7.12,0 C7.19,-2 5.06,-2.75 5,-3.95c " + android:valueTo="M4.82 -3.81 C1,-7.87 0.75,-9.37 -1.72,-7.1 C-1.73,-6.22 -1.73,5.88 -1.7,7.16 C0.25,8.45 4.38,7.38 6.4,3.22 C6.77,2.46 7,1.69 6.74,0 C6.56,-1.16 5.85,-2.71 4.82,-3.81c " + android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + <objectAnimator + android:duration="100" + android:propertyName="pathData" + android:startOffset="233" + android:valueFrom="M4.82 -3.81 C1,-7.87 0.75,-9.37 -1.72,-7.1 C-1.73,-6.22 -1.73,5.88 -1.7,7.16 C0.25,8.45 4.38,7.38 6.4,3.22 C6.77,2.46 7,1.69 6.74,0 C6.56,-1.16 5.85,-2.71 4.82,-3.81c " + android:valueTo="M5.63 -2.97 C3.65,-4.93 0.75,-8.37 0.01,-7.32 C0.03,-6.22 -0.03,5.66 -0.03,7.28 C1.59,7.31 4.13,7.94 6.31,3.44 C6.68,2.66 7,1.37 6.87,0.06 C6.77,-0.84 6.13,-2.47 5.63,-2.97c " + android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.2,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="time_group"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="433" + android:propertyName="translateX" + android:startOffset="0" + android:valueFrom="0" + android:valueTo="1" + android:valueType="floatType" /> + </set> + </aapt:attr> + </target> + <aapt:attr name="android:drawable"> + <vector + android:width="24dp" + android:height="24dp" + android:viewportHeight="24" + android:viewportWidth="24"> + <group android:name="_R_G"> + <group + android:name="_R_G_L_0_G" + android:translateX="12" + android:translateY="12"> + <path + android:name="_R_G_L_0_G_D_0_P_0" + android:fillAlpha="1" + android:fillColor="#edf2eb" + android:fillType="nonZero" + android:pathData=" M0 -7.49 C-2.58,-7.49 -7.47,-2.59 -7.47,0.57 C-7.47,0.77 -7.37,0.88 -7.37,1.09 C-7.37,6.31 -2.19,7.55 0,7.55 C0,5.91 -0.01,3.91 -0.01,0 C-0.01,-3.91 0,-5.31 0,-7.49c " /> + <path + android:name="_R_G_L_0_G_D_1_P_0" + android:fillAlpha="1" + android:fillColor="#ffffff" + android:fillType="nonZero" + android:pathData=" M0 -7.19 C0,-7.19 2.93,-4.32 4.38,-2.87 C5.25,-2 5.88,-0.62 5.88,1.19 C5.88,4.5 2.8,6.97 0,7 C-2.8,7.03 -6,4.37 -6,1.13 C-6,-0.43 -5.38,-1.9 -4.25,-3.01 C-4.25,-3.01 0,-7.19 0,-7.19 M-5.65 -4.44 C-5.65,-4.44 -5.65,-4.44 -5.65,-4.44 C-7.1,-3.01 -8,-1.04 -8,1.13 C-8,5.48 -4.42,9 0,9 C4.42,9 8,5.48 8,1.13 C8,-1.04 7.1,-3.01 5.65,-4.44 C5.65,-4.44 5.65,-4.44 5.65,-4.44 C5.65,-4.44 0,-10 0,-10 C0,-10 -5.65,-4.44 -5.65,-4.44c " /> + </group> + </group> + <group android:name="time_group" /> + </vector> + </aapt:attr> +</animated-vector> diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_esim_area.xml b/packages/SystemUI/res-keyguard/layout/keyguard_esim_area.xml index db508c91e0de..67fd5b9a78bd 100644 --- a/packages/SystemUI/res-keyguard/layout/keyguard_esim_area.xml +++ b/packages/SystemUI/res-keyguard/layout/keyguard_esim_area.xml @@ -17,15 +17,17 @@ */ --> -<!-- This contains disable esim buttonas shared by sim_pin/sim_puk screens --> -<com.android.keyguard.KeyguardEsimArea - xmlns:android="http://schemas.android.com/apk/res/android" +<!-- This contains disable eSim buttons shared by sim_pin/sim_puk screens --> +<com.android.keyguard.KeyguardEsimArea xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/keyguard_disable_esim" + style="@style/Keyguard.TextView" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:id="@+id/keyguard_disable_esim" - android:visibility="gone" + android:background="@drawable/kg_bouncer_secondary_button" + android:drawablePadding="10dp" + android:drawableStart="@drawable/ic_no_sim" + android:drawableTint="?android:attr/textColorPrimary" android:text="@string/disable_carrier_button_text" - style="@style/Keyguard.TextView" - android:textAppearance="?android:attr/textAppearanceMedium" + android:textColor="?android:attr/textColorPrimary" android:textSize="@dimen/kg_status_line_font_size" - android:textAllCaps="@bool/kg_use_all_caps" /> + android:visibility="gone" /> diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_sim_pin_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_sim_pin_view.xml index f2fe520f340f..7db0fe908ec0 100644 --- a/packages/SystemUI/res-keyguard/layout/keyguard_sim_pin_view.xml +++ b/packages/SystemUI/res-keyguard/layout/keyguard_sim_pin_view.xml @@ -47,8 +47,7 @@ <include layout="@layout/keyguard_esim_area" android:id="@+id/keyguard_esim_area" android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginTop="@dimen/eca_overlap" /> + android:layout_height="wrap_content" /> <RelativeLayout android:id="@+id/row0" android:layout_width="match_parent" diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_sim_puk_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_sim_puk_view.xml index a21ec29267fe..422bd4c12e8e 100644 --- a/packages/SystemUI/res-keyguard/layout/keyguard_sim_puk_view.xml +++ b/packages/SystemUI/res-keyguard/layout/keyguard_sim_puk_view.xml @@ -51,8 +51,7 @@ <include layout="@layout/keyguard_esim_area" android:id="@+id/keyguard_esim_area" android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginTop="@dimen/eca_overlap" /> + android:layout_height="wrap_content" /> <RelativeLayout android:id="@+id/row0" diff --git a/packages/SystemUI/res-keyguard/values-sw600dp/dimens.xml b/packages/SystemUI/res-keyguard/values-sw600dp/dimens.xml index e80cfafdd71a..a1068c65bae2 100644 --- a/packages/SystemUI/res-keyguard/values-sw600dp/dimens.xml +++ b/packages/SystemUI/res-keyguard/values-sw600dp/dimens.xml @@ -31,8 +31,4 @@ <!-- Overload default clock widget parameters --> <dimen name="widget_big_font_size">100dp</dimen> <dimen name="widget_label_font_size">18sp</dimen> - - <!-- EmergencyCarrierArea overlap - amount to overlap the emergency button and carrier text. - Should be 0 on devices with plenty of room (e.g. tablets) --> - <dimen name="eca_overlap">0dip</dimen> </resources> diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml index ac131ae1c99f..46f6ab2399d1 100644 --- a/packages/SystemUI/res-keyguard/values/dimens.xml +++ b/packages/SystemUI/res-keyguard/values/dimens.xml @@ -44,10 +44,6 @@ <dimen name="keyguard_eca_top_margin">18dp</dimen> <dimen name="keyguard_eca_bottom_margin">12dp</dimen> - <!-- EmergencyCarrierArea overlap - amount to overlap the emergency button and carrier text. - Should be 0 on devices with plenty of room (e.g. tablets) --> - <dimen name="eca_overlap">-10dip</dimen> - <!-- Slice header --> <dimen name="widget_title_font_size">20dp</dimen> <dimen name="widget_title_line_height">24dp</dimen> diff --git a/packages/SystemUI/res/drawable/ic_no_sim.xml b/packages/SystemUI/res/drawable/ic_no_sim.xml new file mode 100644 index 000000000000..ccfb6ea642cc --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_no_sim.xml @@ -0,0 +1,10 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24" + android:tint="?attr/colorControlNormal"> + <path + android:fillColor="@android:color/white" + android:pathData="M20,17.175 L18,15.175V4Q18,4 18,4Q18,4 18,4H10.85L8.85,6L7.4,4.6L10,2H18Q18.825,2 19.413,2.587Q20,3.175 20,4ZM20.5,23.3 L6,8.8V20Q6,20 6,20Q6,20 6,20H18Q18,20 18,20Q18,20 18,20V17.975L20,19.975V20Q20,20.825 19.413,21.413Q18.825,22 18,22H6Q5.175,22 4.588,21.413Q4,20.825 4,20V8L4.6,7.4L0.7,3.5L2.125,2.1L21.9,21.875ZM13.525,10.675Q13.525,10.675 13.525,10.675Q13.525,10.675 13.525,10.675ZM11.65,14.475Q11.65,14.475 11.65,14.475Q11.65,14.475 11.65,14.475Q11.65,14.475 11.65,14.475Q11.65,14.475 11.65,14.475Z"/> +</vector> diff --git a/packages/SystemUI/res/drawable/qs_data_saver_icon_off.xml b/packages/SystemUI/res/drawable/qs_data_saver_icon_off.xml new file mode 100644 index 000000000000..57777a670d33 --- /dev/null +++ b/packages/SystemUI/res/drawable/qs_data_saver_icon_off.xml @@ -0,0 +1,84 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright (C) 2022 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:aapt="http://schemas.android.com/aapt"> + <target android:name="_R_G_L_1_G_D_0_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="133" + android:propertyName="fillAlpha" + android:startOffset="0" + android:valueFrom="1" + android:valueTo="0" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="time_group"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="150" + android:propertyName="translateX" + android:startOffset="0" + android:valueFrom="0" + android:valueTo="1" + android:valueType="floatType" /> + </set> + </aapt:attr> + </target> + <aapt:attr name="android:drawable"> + <vector + android:width="24dp" + android:height="24dp" + android:viewportHeight="24" + android:viewportWidth="24"> + <group android:name="_R_G"> + <group + android:name="_R_G_L_1_G" + android:pivotY="1" + android:rotation="180" + android:translateX="12" + android:translateY="10.984"> + <path + android:name="_R_G_L_1_G_D_0_P_0" + android:fillAlpha="1" + android:fillColor="#ffffff" + android:fillType="nonZero" + android:pathData=" M3 2 C3,2 1,2 1,2 C1,2 1,4 1,4 C1,4 -1,4 -1,4 C-1,4 -1,2 -1,2 C-1,2 -3,2 -3,2 C-3,2 -3,0 -3,0 C-3,0 -1,0 -1,0 C-1,0 -1,-2 -1,-2 C-1,-2 1,-2 1,-2 C1,-2 1,0 1,0 C1,0 3,0 3,0 C3,0 3,2 3,2c " /> + </group> + <group + android:name="_R_G_L_0_G" + android:rotation="1440" + android:translateX="12" + android:translateY="12"> + <path + android:name="_R_G_L_0_G_D_0_P_0" + android:fillAlpha="1" + android:fillColor="#ffffff" + android:fillType="nonZero" + android:pathData=" M9.15 4.05 C9.15,4.05 6.55,2.55 6.55,2.55 C6.7,2.13 6.81,1.71 6.89,1.29 C6.96,0.86 7,0.43 7,0 C7,-1.77 6.43,-3.31 5.3,-4.62 C4.17,-5.94 2.73,-6.72 1,-6.95 C1,-6.95 1,-9.95 1,-9.95 C3.57,-9.7 5.71,-8.62 7.43,-6.72 C9.14,-4.82 10,-2.58 10,0 C10,0.7 9.94,1.39 9.81,2.08 C9.69,2.76 9.47,3.42 9.15,4.05c M0 10 C-1.38,10 -2.68,9.74 -3.9,9.21 C-5.12,8.69 -6.17,7.98 -7.07,7.08 C-7.97,6.18 -8.69,5.12 -9.21,3.9 C-9.74,2.68 -10,1.38 -10,0 C-10,-2.58 -9.14,-4.82 -7.42,-6.72 C-5.71,-8.62 -3.57,-9.7 -1,-9.95 C-1,-9.95 -1,-6.95 -1,-6.95 C-2.73,-6.72 -4.17,-5.94 -5.3,-4.62 C-6.43,-3.31 -7,-1.77 -7,0 C-7,1.95 -6.32,3.6 -4.96,4.96 C-3.6,6.32 -1.95,7 0,7 C1.07,7 2.08,6.78 3.04,6.33 C4,5.88 4.82,5.23 5.5,4.4 C5.5,4.4 8.1,5.9 8.1,5.9 C7.15,7.2 5.97,8.21 4.55,8.93 C3.13,9.64 1.62,10 0,10c " /> + </group> + </group> + <group android:name="time_group" /> + </vector> + </aapt:attr> +</animated-vector> diff --git a/packages/SystemUI/res/drawable/qs_data_saver_icon_on.xml b/packages/SystemUI/res/drawable/qs_data_saver_icon_on.xml new file mode 100644 index 000000000000..b3764137793a --- /dev/null +++ b/packages/SystemUI/res/drawable/qs_data_saver_icon_on.xml @@ -0,0 +1,110 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright (C) 2022 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:aapt="http://schemas.android.com/aapt"> + <target android:name="_R_G_L_1_G_D_0_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="333" + android:propertyName="pathData" + android:startOffset="0" + android:valueFrom="M1.02 2 C1.02,2 1,2 1,2 C1,2 1,1.98 1,1.98 C1,1.98 -1,1.98 -1,1.98 C-1,1.98 -1,2 -1,2 C-1,2 -1,2 -1,2 C-1,2 -1,0 -1,0 C-1,0 -1,0 -1,0 C-1,0 -1,0 -1,0 C-1,0 1,0 1,0 C1,0 1,0 1,0 C1,0 1.02,0 1.02,0 C1.02,0 1.02,2 1.02,2c " + android:valueTo="M3 2 C3,2 1,2 1,2 C1,2 1,4 1,4 C1,4 -1,4 -1,4 C-1,4 -1,2 -1,2 C-1,2 -3,2 -3,2 C-3,2 -3,0 -3,0 C-3,0 -1,0 -1,0 C-1,0 -1,-2 -1,-2 C-1,-2 1,-2 1,-2 C1,-2 1,0 1,0 C1,0 3,0 3,0 C3,0 3,2 3,2c " + android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.2,0.2 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_0_G"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="500" + android:propertyName="rotation" + android:startOffset="0" + android:valueFrom="0" + android:valueTo="613.191" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.086,0.422 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + <objectAnimator + android:duration="417" + android:propertyName="rotation" + android:startOffset="500" + android:valueFrom="613.191" + android:valueTo="720" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.334,1.012 0.833,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="time_group"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="933" + android:propertyName="translateX" + android:startOffset="0" + android:valueFrom="0" + android:valueTo="1" + android:valueType="floatType" /> + </set> + </aapt:attr> + </target> + <aapt:attr name="android:drawable"> + <vector + android:width="24dp" + android:height="24dp" + android:viewportHeight="24" + android:viewportWidth="24"> + <group android:name="_R_G"> + <group + android:name="_R_G_L_1_G" + android:translateX="12" + android:translateY="10.75"> + <path + android:name="_R_G_L_1_G_D_0_P_0" + android:fillAlpha="1" + android:fillColor="#ffffff" + android:fillType="nonZero" + android:pathData=" M1.02 2 C1.02,2 1,2 1,2 C1,2 1,1.98 1,1.98 C1,1.98 -1,1.98 -1,1.98 C-1,1.98 -1,2 -1,2 C-1,2 -1,2 -1,2 C-1,2 -1,0 -1,0 C-1,0 -1,0 -1,0 C-1,0 -1,0 -1,0 C-1,0 1,0 1,0 C1,0 1,0 1,0 C1,0 1.02,0 1.02,0 C1.02,0 1.02,2 1.02,2c " /> + </group> + <group + android:name="_R_G_L_0_G" + android:rotation="0" + android:translateX="12" + android:translateY="12"> + <path + android:name="_R_G_L_0_G_D_0_P_0" + android:fillAlpha="1" + android:fillColor="#ffffff" + android:fillType="nonZero" + android:pathData=" M9.15 4.05 C9.15,4.05 6.55,2.55 6.55,2.55 C6.7,2.13 6.81,1.71 6.89,1.29 C6.96,0.86 7,0.43 7,0 C7,-1.77 6.43,-3.31 5.3,-4.62 C4.17,-5.94 2.73,-6.72 1,-6.95 C1,-6.95 1,-9.95 1,-9.95 C3.57,-9.7 5.71,-8.62 7.43,-6.72 C9.14,-4.82 10,-2.58 10,0 C10,0.7 9.94,1.39 9.81,2.08 C9.69,2.76 9.47,3.42 9.15,4.05c M0 10 C-1.38,10 -2.68,9.74 -3.9,9.21 C-5.12,8.69 -6.17,7.98 -7.07,7.08 C-7.97,6.18 -8.69,5.12 -9.21,3.9 C-9.74,2.68 -10,1.38 -10,0 C-10,-2.58 -9.14,-4.82 -7.42,-6.72 C-5.71,-8.62 -3.57,-9.7 -1,-9.95 C-1,-9.95 -1,-6.95 -1,-6.95 C-2.73,-6.72 -4.17,-5.94 -5.3,-4.62 C-6.43,-3.31 -7,-1.77 -7,0 C-7,1.95 -6.32,3.6 -4.96,4.96 C-3.6,6.32 -1.95,7 0,7 C1.07,7 2.08,6.78 3.04,6.33 C4,5.88 4.82,5.23 5.5,4.4 C5.5,4.4 8.1,5.9 8.1,5.9 C7.15,7.2 5.97,8.21 4.55,8.93 C3.13,9.64 1.62,10 0,10c " /> + </group> + </group> + <group android:name="time_group" /> + </vector> + </aapt:attr> +</animated-vector> diff --git a/packages/SystemUI/res/drawable/qs_nightlight_icon_off.xml b/packages/SystemUI/res/drawable/qs_nightlight_icon_off.xml new file mode 100644 index 000000000000..0769a85dae49 --- /dev/null +++ b/packages/SystemUI/res/drawable/qs_nightlight_icon_off.xml @@ -0,0 +1,94 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + Copyright (C) 2022 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:aapt="http://schemas.android.com/aapt"> + <target android:name="_R_G_L_0_G_T_1"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="283" + android:propertyName="translateY" + android:startOffset="0" + android:valueFrom="12.125" + android:valueTo="12.312" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.1,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_0_G_T_1"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="283" + android:propertyName="rotation" + android:startOffset="0" + android:valueFrom="-45" + android:valueTo="0" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.1,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="time_group"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="300" + android:propertyName="translateX" + android:startOffset="0" + android:valueFrom="0" + android:valueTo="1" + android:valueType="floatType" /> + </set> + </aapt:attr> + </target> + <aapt:attr name="android:drawable"> + <vector + android:width="24dp" + android:height="24dp" + android:viewportHeight="24" + android:viewportWidth="24"> + <group android:name="_R_G"> + <group + android:name="_R_G_L_0_G_T_1" + android:rotation="-45" + android:translateX="12.875" + android:translateY="12.125"> + <group + android:name="_R_G_L_0_G" + android:translateX="-2.375"> + <path + android:name="_R_G_L_0_G_D_0_P_0" + android:pathData=" M3.33 8.62 C2.09,8.68 0.59,8.47 -0.64,7.95 C-1.65,7.53 -2.75,6.9 -3.59,6.09 C-4.38,5.32 -5.19,4.17 -5.61,3.08 C-6.04,1.99 -6.25,0.83 -6.25,-0.39 C-6.25,-1.72 -5.98,-2.85 -5.55,-3.94 C-5.13,-5.02 -4.37,-6 -3.59,-6.78 C-2.63,-7.75 -1.63,-8.28 -0.52,-8.77 C0.48,-9.2 2.04,-9.41 3.15,-9.38 C4.22,-9.35 4.94,-9.16 5.81,-8.79 C5.95,-8.73 6.28,-8.6 6.18,-8.55 C3.44,-6.63 1.83,-3.66 1.83,-0.39 C1.83,2.53 3.47,5.72 6.15,7.86 C6.16,7.9 6.03,7.97 5.91,8.04 C5.12,8.44 4.31,8.56 3.33,8.62c " + android:strokeAlpha="1" + android:strokeColor="#ffffff" + android:strokeLineCap="round" + android:strokeLineJoin="round" + android:strokeWidth="2" /> + </group> + </group> + </group> + <group android:name="time_group" /> + </vector> + </aapt:attr> +</animated-vector> diff --git a/packages/SystemUI/res/drawable/qs_nightlight_icon_on.xml b/packages/SystemUI/res/drawable/qs_nightlight_icon_on.xml new file mode 100644 index 000000000000..5ffe262d8077 --- /dev/null +++ b/packages/SystemUI/res/drawable/qs_nightlight_icon_on.xml @@ -0,0 +1,94 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + Copyright (C) 2022 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:aapt="http://schemas.android.com/aapt"> + <target android:name="_R_G_L_0_G_T_1"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="400" + android:propertyName="translateY" + android:startOffset="0" + android:valueFrom="12.312" + android:valueTo="12.125" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.1,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_0_G_T_1"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="400" + android:propertyName="rotation" + android:startOffset="0" + android:valueFrom="0" + android:valueTo="-45" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.1,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="time_group"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="417" + android:propertyName="translateX" + android:startOffset="0" + android:valueFrom="0" + android:valueTo="1" + android:valueType="floatType" /> + </set> + </aapt:attr> + </target> + <aapt:attr name="android:drawable"> + <vector + android:width="24dp" + android:height="24dp" + android:viewportHeight="24" + android:viewportWidth="24"> + <group android:name="_R_G"> + <group + android:name="_R_G_L_0_G_T_1" + android:rotation="0" + android:translateX="12.875" + android:translateY="12.312"> + <group + android:name="_R_G_L_0_G" + android:translateX="-2.375"> + <path + android:name="_R_G_L_0_G_D_0_P_0" + android:pathData=" M3.33 8.62 C2.09,8.68 0.59,8.47 -0.64,7.95 C-1.65,7.53 -2.75,6.9 -3.59,6.09 C-4.38,5.32 -5.19,4.17 -5.61,3.08 C-6.04,1.99 -6.25,0.83 -6.25,-0.39 C-6.25,-1.72 -5.98,-2.85 -5.55,-3.94 C-5.13,-5.02 -4.37,-6 -3.6,-6.78 C-2.63,-7.75 -1.63,-8.28 -0.52,-8.77 C0.48,-9.2 2.04,-9.41 3.14,-9.38 C4.22,-9.35 4.94,-9.16 5.81,-8.79 C5.94,-8.73 6.28,-8.6 6.18,-8.55 C3.44,-6.63 1.83,-3.66 1.83,-0.39 C1.83,2.53 3.47,5.72 6.15,7.86 C6.16,7.9 6.03,7.97 5.91,8.04 C5.13,8.43 4.31,8.56 3.33,8.62c " + android:strokeAlpha="1" + android:strokeColor="#ffffff" + android:strokeLineCap="round" + android:strokeLineJoin="round" + android:strokeWidth="2" /> + </group> + </group> + </group> + <group android:name="time_group" /> + </vector> + </aapt:attr> +</animated-vector> diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml index c827e21a20e6..75baeeff6025 100644 --- a/packages/SystemUI/res/values/colors.xml +++ b/packages/SystemUI/res/values/colors.xml @@ -241,6 +241,10 @@ <color name="dream_overlay_aqi_very_unhealthy">#AD1457</color> <color name="dream_overlay_aqi_hazardous">#880E4F</color> <color name="dream_overlay_aqi_unknown">#BDC1C6</color> + + <!-- Dream overlay text shadows --> <color name="dream_overlay_clock_key_text_shadow_color">#4D000000</color> <color name="dream_overlay_clock_ambient_text_shadow_color">#4D000000</color> + <color name="dream_overlay_status_bar_key_text_shadow_color">#66000000</color> + <color name="dream_overlay_status_bar_ambient_text_shadow_color">#59000000</color> </resources> diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index a8027238a0bf..37549c927a90 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -611,6 +611,33 @@ 2 - Override the setting to never bypass keyguard --> <integer name="config_face_unlock_bypass_override">0</integer> + <!-- Messages that should NOT be shown to the user during face authentication on keyguard. + This includes both lockscreen and bouncer. This should be used to hide messages that may be + too chatty or messages that the user can't do much about. Entries are defined in + android.hardware.biometrics.face@1.0 types.hal. + + Although not visibly shown to the user, these acquired messages (sent per face auth frame) + are still counted towards the total frames to determine whether a deferred message + (see config_face_help_msgs_defer_until_timeout) meets the threshold % of frames to show on + face timeout. --> + <integer-array name="config_face_acquire_device_entry_ignorelist" translatable="false" > + </integer-array> + + <!-- Which face help messages to defer until face auth times out. If face auth is cancelled + or ends on another error, then the message is never surfaced. May also never surface + if it doesn't meet a threshold % of authentication frames specified by. + config_face_help_msgs_defer_until_timeout_threshold. --> + <integer-array name="config_face_help_msgs_defer_until_timeout"> + </integer-array> + + <!-- Percentage of face auth frames received required to show a deferred message at + FACE_ERROR_TIMEOUT. See config_face_help_msgs_defer_until_timeout for messages + that are deferred.--> + <item name="config_face_help_msgs_defer_until_timeout_threshold" + translatable="false" format="float" type="dimen"> + .75 + </item> + <!-- Which face help messages to surface when fingerprint is also enrolled. Message ids correspond with the acquired ids in BiometricFaceConstants --> <integer-array name="config_face_help_msgs_when_fingerprint_enrolled"> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 1b169e2e1a31..eb6c45747924 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -1547,10 +1547,20 @@ <dimen name="broadcast_dialog_btn_text_size">16sp</dimen> <dimen name="broadcast_dialog_btn_minHeight">44dp</dimen> <dimen name="broadcast_dialog_margin">16dp</dimen> + + <!-- Shadow for dream overlay clock complication --> <dimen name="dream_overlay_clock_key_text_shadow_dx">0dp</dimen> <dimen name="dream_overlay_clock_key_text_shadow_dy">0dp</dimen> - <dimen name="dream_overlay_clock_key_text_shadow_radius">5dp</dimen> + <dimen name="dream_overlay_clock_key_text_shadow_radius">3dp</dimen> <dimen name="dream_overlay_clock_ambient_text_shadow_dx">0dp</dimen> <dimen name="dream_overlay_clock_ambient_text_shadow_dy">0dp</dimen> <dimen name="dream_overlay_clock_ambient_text_shadow_radius">1dp</dimen> + + <!-- Shadow for dream overlay status bar complications --> + <dimen name="dream_overlay_status_bar_key_text_shadow_dx">0.5dp</dimen> + <dimen name="dream_overlay_status_bar_key_text_shadow_dy">0.5dp</dimen> + <dimen name="dream_overlay_status_bar_key_text_shadow_radius">1dp</dimen> + <dimen name="dream_overlay_status_bar_ambient_text_shadow_dx">0.5dp</dimen> + <dimen name="dream_overlay_status_bar_ambient_text_shadow_dy">0.5dp</dimen> + <dimen name="dream_overlay_status_bar_ambient_text_shadow_radius">2dp</dimen> </resources> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt index b3b75f68e71e..34e2e83d7643 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt @@ -151,8 +151,6 @@ class AnimatableClockView @JvmOverloads constructor( // relayout if the text didn't actually change. if (!TextUtils.equals(text, formattedText)) { text = formattedText - lastTextUpdate = getTimestamp() - // Because the TextLayout may mutate under the hood as a result of the new text, we // notify the TextAnimator that it may have changed and request a measure/layout. A // crash will occur on the next invocation of setTextStyle if the layout is mutated @@ -161,6 +159,7 @@ class AnimatableClockView @JvmOverloads constructor( textAnimator?.updateLayout(layout) } requestLayout() + lastTextUpdate = getTimestamp() } } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt index b5e57667fbb8..19ac2e479bcb 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt @@ -90,6 +90,9 @@ class DefaultClock( override lateinit var animations: ClockAnimations private set + private var smallRegionDarkness = false + private var largeRegionDarkness = false + init { val parent = FrameLayout(ctx) @@ -148,8 +151,14 @@ class DefaultClock( smallClockIsDark: Boolean, largeClockIsDark: Boolean ) { - updateClockColor(smallClock, smallClockIsDark) - updateClockColor(largeClock, largeClockIsDark) + if (smallRegionDarkness != smallClockIsDark) { + smallRegionDarkness = smallClockIsDark + updateClockColor(smallClock, smallClockIsDark) + } + if (largeRegionDarkness != largeClockIsDark) { + largeRegionDarkness = largeClockIsDark + updateClockColor(largeClock, largeClockIsDark) + } } override fun onLocaleChanged(locale: Locale) { diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java new file mode 100644 index 000000000000..30c062b66da9 --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java @@ -0,0 +1,380 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.systemui.shared.system; + +import android.graphics.HardwareRenderer; +import android.graphics.Matrix; +import android.graphics.Rect; +import android.os.Handler; +import android.os.Handler.Callback; +import android.os.Message; +import android.os.Trace; +import android.view.SurfaceControl; +import android.view.SurfaceControl.Transaction; +import android.view.View; +import android.view.ViewRootImpl; + +import java.util.function.Consumer; + +/** + * Helper class to apply surface transactions in sync with RenderThread. + * + * NOTE: This is a modification of {@link android.view.SyncRtSurfaceTransactionApplier}, we can't + * currently reference that class from the shared lib as it is hidden. + */ +public class SyncRtSurfaceTransactionApplierCompat { + + public static final int FLAG_ALL = 0xffffffff; + public static final int FLAG_ALPHA = 1; + public static final int FLAG_MATRIX = 1 << 1; + public static final int FLAG_WINDOW_CROP = 1 << 2; + public static final int FLAG_LAYER = 1 << 3; + public static final int FLAG_CORNER_RADIUS = 1 << 4; + public static final int FLAG_BACKGROUND_BLUR_RADIUS = 1 << 5; + public static final int FLAG_VISIBILITY = 1 << 6; + public static final int FLAG_RELATIVE_LAYER = 1 << 7; + public static final int FLAG_SHADOW_RADIUS = 1 << 8; + + private static final int MSG_UPDATE_SEQUENCE_NUMBER = 0; + + private final SurfaceControl mBarrierSurfaceControl; + private final ViewRootImpl mTargetViewRootImpl; + private final Handler mApplyHandler; + + private int mSequenceNumber = 0; + private int mPendingSequenceNumber = 0; + private Runnable mAfterApplyCallback; + + /** + * @param targetView The view in the surface that acts as synchronization anchor. + */ + public SyncRtSurfaceTransactionApplierCompat(View targetView) { + mTargetViewRootImpl = targetView != null ? targetView.getViewRootImpl() : null; + mBarrierSurfaceControl = mTargetViewRootImpl != null + ? mTargetViewRootImpl.getSurfaceControl() : null; + + mApplyHandler = new Handler(new Callback() { + @Override + public boolean handleMessage(Message msg) { + if (msg.what == MSG_UPDATE_SEQUENCE_NUMBER) { + onApplyMessage(msg.arg1); + return true; + } + return false; + } + }); + } + + private void onApplyMessage(int seqNo) { + mSequenceNumber = seqNo; + if (mSequenceNumber == mPendingSequenceNumber && mAfterApplyCallback != null) { + Runnable r = mAfterApplyCallback; + mAfterApplyCallback = null; + r.run(); + } + } + + /** + * Schedules applying surface parameters on the next frame. + * + * @param params The surface parameters to apply. DO NOT MODIFY the list after passing into + * this method to avoid synchronization issues. + */ + public void scheduleApply(final SyncRtSurfaceTransactionApplierCompat.SurfaceParams... params) { + if (mTargetViewRootImpl == null || mTargetViewRootImpl.getView() == null) { + return; + } + + mPendingSequenceNumber++; + final int toApplySeqNo = mPendingSequenceNumber; + mTargetViewRootImpl.registerRtFrameCallback(new HardwareRenderer.FrameDrawingCallback() { + @Override + public void onFrameDraw(long frame) { + if (mBarrierSurfaceControl == null || !mBarrierSurfaceControl.isValid()) { + Message.obtain(mApplyHandler, MSG_UPDATE_SEQUENCE_NUMBER, toApplySeqNo, 0) + .sendToTarget(); + return; + } + Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Sync transaction frameNumber=" + frame); + Transaction t = new Transaction(); + for (int i = params.length - 1; i >= 0; i--) { + SyncRtSurfaceTransactionApplierCompat.SurfaceParams surfaceParams = + params[i]; + surfaceParams.applyTo(t); + } + if (mTargetViewRootImpl != null) { + mTargetViewRootImpl.mergeWithNextTransaction(t, frame); + } else { + t.apply(); + } + Trace.traceEnd(Trace.TRACE_TAG_VIEW); + Message.obtain(mApplyHandler, MSG_UPDATE_SEQUENCE_NUMBER, toApplySeqNo, 0) + .sendToTarget(); + } + }); + + // Make sure a frame gets scheduled. + mTargetViewRootImpl.getView().invalidate(); + } + + /** + * Calls the runnable when any pending apply calls have completed + */ + public void addAfterApplyCallback(final Runnable afterApplyCallback) { + if (mSequenceNumber == mPendingSequenceNumber) { + afterApplyCallback.run(); + } else { + if (mAfterApplyCallback == null) { + mAfterApplyCallback = afterApplyCallback; + } else { + final Runnable oldCallback = mAfterApplyCallback; + mAfterApplyCallback = new Runnable() { + @Override + public void run() { + afterApplyCallback.run(); + oldCallback.run(); + } + }; + } + } + } + + public static void applyParams(TransactionCompat t, + SyncRtSurfaceTransactionApplierCompat.SurfaceParams params) { + params.applyTo(t.mTransaction); + } + + /** + * Creates an instance of SyncRtSurfaceTransactionApplier, deferring until the target view is + * attached if necessary. + */ + public static void create(final View targetView, + final Consumer<SyncRtSurfaceTransactionApplierCompat> callback) { + if (targetView == null) { + // No target view, no applier + callback.accept(null); + } else if (targetView.getViewRootImpl() != null) { + // Already attached, we're good to go + callback.accept(new SyncRtSurfaceTransactionApplierCompat(targetView)); + } else { + // Haven't been attached before we can get the view root + targetView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() { + @Override + public void onViewAttachedToWindow(View v) { + targetView.removeOnAttachStateChangeListener(this); + callback.accept(new SyncRtSurfaceTransactionApplierCompat(targetView)); + } + + @Override + public void onViewDetachedFromWindow(View v) { + // Do nothing + } + }); + } + } + + public static class SurfaceParams { + public static class Builder { + final SurfaceControl surface; + int flags; + float alpha; + float cornerRadius; + int backgroundBlurRadius; + Matrix matrix; + Rect windowCrop; + int layer; + SurfaceControl relativeTo; + int relativeLayer; + boolean visible; + float shadowRadius; + + /** + * @param surface The surface to modify. + */ + public Builder(SurfaceControl surface) { + this.surface = surface; + } + + /** + * @param alpha The alpha value to apply to the surface. + * @return this Builder + */ + public Builder withAlpha(float alpha) { + this.alpha = alpha; + flags |= FLAG_ALPHA; + return this; + } + + /** + * @param matrix The matrix to apply to the surface. + * @return this Builder + */ + public Builder withMatrix(Matrix matrix) { + this.matrix = new Matrix(matrix); + flags |= FLAG_MATRIX; + return this; + } + + /** + * @param windowCrop The window crop to apply to the surface. + * @return this Builder + */ + public Builder withWindowCrop(Rect windowCrop) { + this.windowCrop = new Rect(windowCrop); + flags |= FLAG_WINDOW_CROP; + return this; + } + + /** + * @param layer The layer to assign the surface. + * @return this Builder + */ + public Builder withLayer(int layer) { + this.layer = layer; + flags |= FLAG_LAYER; + return this; + } + + /** + * @param relativeTo The surface that's set relative layer to. + * @param relativeLayer The relative layer. + * @return this Builder + */ + public Builder withRelativeLayerTo(SurfaceControl relativeTo, int relativeLayer) { + this.relativeTo = relativeTo; + this.relativeLayer = relativeLayer; + flags |= FLAG_RELATIVE_LAYER; + return this; + } + + /** + * @param radius the Radius for rounded corners to apply to the surface. + * @return this Builder + */ + public Builder withCornerRadius(float radius) { + this.cornerRadius = radius; + flags |= FLAG_CORNER_RADIUS; + return this; + } + + /** + * @param radius the Radius for the shadows to apply to the surface. + * @return this Builder + */ + public Builder withShadowRadius(float radius) { + this.shadowRadius = radius; + flags |= FLAG_SHADOW_RADIUS; + return this; + } + + /** + * @param radius the Radius for blur to apply to the background surfaces. + * @return this Builder + */ + public Builder withBackgroundBlur(int radius) { + this.backgroundBlurRadius = radius; + flags |= FLAG_BACKGROUND_BLUR_RADIUS; + return this; + } + + /** + * @param visible The visibility to apply to the surface. + * @return this Builder + */ + public Builder withVisibility(boolean visible) { + this.visible = visible; + flags |= FLAG_VISIBILITY; + return this; + } + + /** + * @return a new SurfaceParams instance + */ + public SurfaceParams build() { + return new SurfaceParams(surface, flags, alpha, matrix, windowCrop, layer, + relativeTo, relativeLayer, cornerRadius, backgroundBlurRadius, visible, + shadowRadius); + } + } + + private SurfaceParams(SurfaceControl surface, int flags, float alpha, Matrix matrix, + Rect windowCrop, int layer, SurfaceControl relativeTo, int relativeLayer, + float cornerRadius, int backgroundBlurRadius, boolean visible, float shadowRadius) { + this.flags = flags; + this.surface = surface; + this.alpha = alpha; + this.matrix = matrix; + this.windowCrop = windowCrop; + this.layer = layer; + this.relativeTo = relativeTo; + this.relativeLayer = relativeLayer; + this.cornerRadius = cornerRadius; + this.backgroundBlurRadius = backgroundBlurRadius; + this.visible = visible; + this.shadowRadius = shadowRadius; + } + + private final int flags; + private final float[] mTmpValues = new float[9]; + + public final SurfaceControl surface; + public final float alpha; + public final float cornerRadius; + public final int backgroundBlurRadius; + public final Matrix matrix; + public final Rect windowCrop; + public final int layer; + public final SurfaceControl relativeTo; + public final int relativeLayer; + public final boolean visible; + public final float shadowRadius; + + public void applyTo(SurfaceControl.Transaction t) { + if ((flags & FLAG_MATRIX) != 0) { + t.setMatrix(surface, matrix, mTmpValues); + } + if ((flags & FLAG_WINDOW_CROP) != 0) { + t.setWindowCrop(surface, windowCrop); + } + if ((flags & FLAG_ALPHA) != 0) { + t.setAlpha(surface, alpha); + } + if ((flags & FLAG_LAYER) != 0) { + t.setLayer(surface, layer); + } + if ((flags & FLAG_CORNER_RADIUS) != 0) { + t.setCornerRadius(surface, cornerRadius); + } + if ((flags & FLAG_BACKGROUND_BLUR_RADIUS) != 0) { + t.setBackgroundBlurRadius(surface, backgroundBlurRadius); + } + if ((flags & FLAG_VISIBILITY) != 0) { + if (visible) { + t.show(surface); + } else { + t.hide(surface); + } + } + if ((flags & FLAG_RELATIVE_LAYER) != 0) { + t.setRelativeLayer(surface, relativeTo, relativeLayer); + } + if ((flags & FLAG_SHADOW_RADIUS) != 0) { + t.setShadowRadius(surface, shadowRadius); + } + } + } +} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TransactionCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TransactionCompat.java new file mode 100644 index 000000000000..43a882a5f6be --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TransactionCompat.java @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.systemui.shared.system; + +import android.graphics.Matrix; +import android.graphics.Rect; +import android.view.SurfaceControl; +import android.view.SurfaceControl.Transaction; + +public class TransactionCompat { + + final Transaction mTransaction; + + final float[] mTmpValues = new float[9]; + + public TransactionCompat() { + mTransaction = new Transaction(); + } + + public void apply() { + mTransaction.apply(); + } + + public TransactionCompat show(SurfaceControl surfaceControl) { + mTransaction.show(surfaceControl); + return this; + } + + public TransactionCompat hide(SurfaceControl surfaceControl) { + mTransaction.hide(surfaceControl); + return this; + } + + public TransactionCompat setPosition(SurfaceControl surfaceControl, float x, float y) { + mTransaction.setPosition(surfaceControl, x, y); + return this; + } + + public TransactionCompat setSize(SurfaceControl surfaceControl, int w, int h) { + mTransaction.setBufferSize(surfaceControl, w, h); + return this; + } + + public TransactionCompat setLayer(SurfaceControl surfaceControl, int z) { + mTransaction.setLayer(surfaceControl, z); + return this; + } + + public TransactionCompat setAlpha(SurfaceControl surfaceControl, float alpha) { + mTransaction.setAlpha(surfaceControl, alpha); + return this; + } + + public TransactionCompat setOpaque(SurfaceControl surfaceControl, boolean opaque) { + mTransaction.setOpaque(surfaceControl, opaque); + return this; + } + + public TransactionCompat setMatrix(SurfaceControl surfaceControl, float dsdx, float dtdx, + float dtdy, float dsdy) { + mTransaction.setMatrix(surfaceControl, dsdx, dtdx, dtdy, dsdy); + return this; + } + + public TransactionCompat setMatrix(SurfaceControl surfaceControl, Matrix matrix) { + mTransaction.setMatrix(surfaceControl, matrix, mTmpValues); + return this; + } + + public TransactionCompat setWindowCrop(SurfaceControl surfaceControl, Rect crop) { + mTransaction.setWindowCrop(surfaceControl, crop); + return this; + } + + public TransactionCompat setCornerRadius(SurfaceControl surfaceControl, float radius) { + mTransaction.setCornerRadius(surfaceControl, radius); + return this; + } + + public TransactionCompat setBackgroundBlurRadius(SurfaceControl surfaceControl, int radius) { + mTransaction.setBackgroundBlurRadius(surfaceControl, radius); + return this; + } + + public TransactionCompat setColor(SurfaceControl surfaceControl, float[] color) { + mTransaction.setColor(surfaceControl, color); + return this; + } + + public static void setRelativeLayer(Transaction t, SurfaceControl surfaceControl, + SurfaceControl relativeTo, int z) { + t.setRelativeLayer(surfaceControl, relativeTo, z); + } +} diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimInputView.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardSimInputView.kt new file mode 100644 index 000000000000..b4bfca1185f4 --- /dev/null +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimInputView.kt @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.keyguard + +import android.content.Context +import android.util.AttributeSet +import android.widget.ImageView +import androidx.core.graphics.drawable.DrawableCompat +import com.android.systemui.R + +abstract class KeyguardSimInputView(context: Context, attrs: AttributeSet) : + KeyguardPinBasedInputView(context, attrs) { + private var simImageView: ImageView? = null + private var disableESimButton: KeyguardEsimArea? = null + + override fun onFinishInflate() { + simImageView = findViewById(R.id.keyguard_sim) + disableESimButton = findViewById(R.id.keyguard_esim_area) + super.onFinishInflate() + } + + /** Set UI state based on whether there is a locked eSim card */ + fun setESimLocked(isESimLocked: Boolean, subId: Int) { + disableESimButton?.setSubscriptionId(subId) + disableESimButton?.visibility = if (isESimLocked) VISIBLE else GONE + simImageView?.visibility = if (isESimLocked) GONE else VISIBLE + } + + override fun reloadColors() { + super.reloadColors() + val customAttrs = intArrayOf(android.R.attr.textColorSecondary) + val a = context.obtainStyledAttributes(customAttrs) + val imageColor = a.getColor(0, 0) + a.recycle() + simImageView?.let { + val wrappedDrawable = DrawableCompat.wrap(it.drawable) + DrawableCompat.setTint(wrappedDrawable, imageColor) + } + } +} diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java index ae9d3dfec3b2..9d170150a709 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java @@ -18,21 +18,14 @@ package com.android.keyguard; import android.content.Context; import android.content.res.Configuration; -import android.content.res.TypedArray; -import android.graphics.drawable.Drawable; import android.util.AttributeSet; -import android.view.View; -import android.widget.ImageView; - -import androidx.core.graphics.drawable.DrawableCompat; import com.android.systemui.R; /** * Displays a PIN pad for unlocking. */ -public class KeyguardSimPinView extends KeyguardPinBasedInputView { - private ImageView mSimImageView; +public class KeyguardSimPinView extends KeyguardSimInputView { public static final String TAG = "KeyguardSimPinView"; public KeyguardSimPinView(Context context) { @@ -43,12 +36,6 @@ public class KeyguardSimPinView extends KeyguardPinBasedInputView { super(context, attrs); } - public void setEsimLocked(boolean locked, int subscriptionId) { - KeyguardEsimArea esimButton = findViewById(R.id.keyguard_esim_area); - esimButton.setSubscriptionId(subscriptionId); - esimButton.setVisibility(locked ? View.VISIBLE : View.GONE); - } - @Override protected void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); @@ -68,7 +55,6 @@ public class KeyguardSimPinView extends KeyguardPinBasedInputView { @Override protected void onFinishInflate() { - mSimImageView = findViewById(R.id.keyguard_sim); super.onFinishInflate(); if (mEcaView instanceof EmergencyCarrierArea) { @@ -86,17 +72,4 @@ public class KeyguardSimPinView extends KeyguardPinBasedInputView { return getContext().getString( com.android.internal.R.string.keyguard_accessibility_sim_pin_unlock); } - - @Override - public void reloadColors() { - super.reloadColors(); - - int[] customAttrs = {android.R.attr.textColorSecondary}; - TypedArray a = getContext().obtainStyledAttributes(customAttrs); - int imageColor = a.getColor(0, 0); - a.recycle(); - Drawable wrappedDrawable = DrawableCompat.wrap(mSimImageView.getDrawable()); - DrawableCompat.setTint(wrappedDrawable, imageColor); - } } - diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java index ecd88e6fe90c..76f7d785071d 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java @@ -105,7 +105,7 @@ public class KeyguardSimPinViewController showDefaultMessage(); } - mView.setEsimLocked(KeyguardEsimArea.isEsimLocked(mView.getContext(), mSubId), mSubId); + mView.setESimLocked(KeyguardEsimArea.isEsimLocked(mView.getContext(), mSubId), mSubId); } @Override diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java index c0971bf8c16d..5f45fe31a779 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java @@ -19,13 +19,8 @@ package com.android.keyguard; import static com.android.systemui.util.PluralMessageFormaterKt.icuMessageFormat; import android.content.Context; -import android.content.res.TypedArray; -import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.util.Log; -import android.widget.ImageView; - -import androidx.core.graphics.drawable.DrawableCompat; import com.android.systemui.R; @@ -33,8 +28,7 @@ import com.android.systemui.R; /** * Displays a PIN pad for entering a PUK (Pin Unlock Kode) provided by a carrier. */ -public class KeyguardSimPukView extends KeyguardPinBasedInputView { - private ImageView mSimImageView; +public class KeyguardSimPukView extends KeyguardSimInputView { private static final boolean DEBUG = KeyguardConstants.DEBUG; public static final String TAG = "KeyguardSimPukView"; @@ -86,7 +80,6 @@ public class KeyguardSimPukView extends KeyguardPinBasedInputView { @Override protected void onFinishInflate() { - mSimImageView = findViewById(R.id.keyguard_sim); super.onFinishInflate(); if (mEcaView instanceof EmergencyCarrierArea) { @@ -104,18 +97,4 @@ public class KeyguardSimPukView extends KeyguardPinBasedInputView { return getContext().getString( com.android.internal.R.string.keyguard_accessibility_sim_puk_unlock); } - - @Override - public void reloadColors() { - super.reloadColors(); - - int[] customAttrs = {android.R.attr.textColorSecondary}; - TypedArray a = getContext().obtainStyledAttributes(customAttrs); - int imageColor = a.getColor(0, 0); - a.recycle(); - Drawable wrappedDrawable = DrawableCompat.wrap(mSimImageView.getDrawable()); - DrawableCompat.setTint(wrappedDrawable, imageColor); - } } - - diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java index 203f9b660536..d8cffd7984ba 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java @@ -30,7 +30,6 @@ import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.util.Log; -import android.view.View; import android.view.WindowManager; import android.widget.ImageView; @@ -173,11 +172,9 @@ public class KeyguardSimPukViewController if (mShowDefaultMessage) { showDefaultMessage(); } - boolean isEsimLocked = KeyguardEsimArea.isEsimLocked(mView.getContext(), mSubId); - KeyguardEsimArea esimButton = mView.findViewById(R.id.keyguard_esim_area); - esimButton.setSubscriptionId(mSubId); - esimButton.setVisibility(isEsimLocked ? View.VISIBLE : View.GONE); + mView.setESimLocked(KeyguardEsimArea.isEsimLocked(mView.getContext(), mSubId), mSubId); + mPasswordEntry.requestFocus(); } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index 763b29e52cf2..32c1cf9802ab 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -157,6 +157,7 @@ import com.google.android.collect.Lists; import java.io.PrintWriter; import java.lang.ref.WeakReference; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; @@ -167,6 +168,7 @@ import java.util.Set; import java.util.TimeZone; import java.util.concurrent.Executor; import java.util.function.Consumer; +import java.util.stream.Collectors; import javax.inject.Inject; import javax.inject.Provider; @@ -281,6 +283,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private final AuthController mAuthController; private final StatusBarStateController mStatusBarStateController; private final UiEventLogger mUiEventLogger; + private final Set<Integer> mFaceAcquiredInfoIgnoreList; private int mStatusBarState; private final StatusBarStateController.StateListener mStatusBarStateControllerListener = new StatusBarStateController.StateListener() { @@ -1023,6 +1026,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private void handleFaceAuthFailed() { Assert.isMainThread(); + mLogger.d("onFaceAuthFailed"); mFaceCancelSignal = null; setFaceRunningState(BIOMETRIC_STATE_STOPPED); for (int i = 0; i < mCallbacks.size(); i++) { @@ -1639,6 +1643,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab @Override public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) { + if (mFaceAcquiredInfoIgnoreList.contains(helpMsgId)) { + return; + } handleFaceHelp(helpMsgId, helpString.toString()); } @@ -1931,6 +1938,11 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mActiveUnlockConfig.setKeyguardUpdateMonitor(this); mWakeOnFingerprintAcquiredStart = context.getResources() .getBoolean(com.android.internal.R.bool.kg_wake_on_acquire_start); + mFaceAcquiredInfoIgnoreList = Arrays.stream( + mContext.getResources().getIntArray( + R.array.config_face_acquire_device_entry_ignorelist)) + .boxed() + .collect(Collectors.toSet()); mHandler = new Handler(mainLooper) { @Override diff --git a/packages/SystemUI/src/com/android/keyguard/logging/BiometricMessageDeferralLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/BiometricMessageDeferralLogger.kt new file mode 100644 index 000000000000..2c2ab7b39161 --- /dev/null +++ b/packages/SystemUI/src/com/android/keyguard/logging/BiometricMessageDeferralLogger.kt @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.keyguard.logging + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.LogLevel.DEBUG +import com.android.systemui.log.dagger.BiometricMessagesLog +import javax.inject.Inject + +/** Helper class for logging for [com.android.systemui.biometrics.FaceHelpMessageDeferral] */ +@SysUISingleton +class FaceMessageDeferralLogger +@Inject +constructor(@BiometricMessagesLog private val logBuffer: LogBuffer) : + BiometricMessageDeferralLogger(logBuffer, "FaceMessageDeferralLogger") + +open class BiometricMessageDeferralLogger( + private val logBuffer: LogBuffer, + private val tag: String +) { + fun reset() { + logBuffer.log(tag, DEBUG, "reset") + } + + fun logUpdateMessage(acquiredInfo: Int, helpString: String) { + logBuffer.log( + tag, + DEBUG, + { + int1 = acquiredInfo + str1 = helpString + }, + { "updateMessage acquiredInfo=$int1 helpString=$str1" } + ) + } + + fun logFrameProcessed( + acquiredInfo: Int, + totalFrames: Int, + mostFrequentAcquiredInfoToDefer: String? // may not meet the threshold + ) { + logBuffer.log( + tag, + DEBUG, + { + int1 = acquiredInfo + int2 = totalFrames + str1 = mostFrequentAcquiredInfoToDefer + }, + { + "frameProcessed acquiredInfo=$int1 totalFrames=$int2 " + + "messageToShowOnTimeout=$str1" + } + ) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricMessageDeferral.kt b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricMessageDeferral.kt deleted file mode 100644 index f2d8aaa30f21..000000000000 --- a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricMessageDeferral.kt +++ /dev/null @@ -1,89 +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.biometrics - -/** - * Provides whether an acquired error message should be shown immediately when its received (see - * [shouldDefer]) or should be shown when the biometric error is received [getDeferredMessage]. - * @property excludedMessages messages that are excluded from counts - * @property messagesToDefer messages that shouldn't show immediately when received, but may be - * shown later if the message is the most frequent message processed and meets [THRESHOLD] - * percentage of all messages (excluding [excludedMessages]) - */ -class BiometricMessageDeferral( - private val excludedMessages: Set<Int>, - private val messagesToDefer: Set<Int> -) { - private val msgCounts: MutableMap<Int, Int> = HashMap() // msgId => frequency of msg - private val msgIdToCharSequence: MutableMap<Int, CharSequence> = HashMap() // msgId => message - private var totalRelevantMessages = 0 - private var mostFrequentMsgIdToDefer: Int? = null - - /** Reset all saved counts. */ - fun reset() { - totalRelevantMessages = 0 - msgCounts.clear() - msgIdToCharSequence.clear() - } - - /** Whether the given message should be deferred instead of being shown immediately. */ - fun shouldDefer(acquiredMsgId: Int): Boolean { - return messagesToDefer.contains(acquiredMsgId) - } - - /** - * Adds the acquiredMsgId to the counts if it's not in [excludedMessages]. We still count - * messages that shouldn't be deferred in these counts. - */ - fun processMessage(acquiredMsgId: Int, helpString: CharSequence) { - if (excludedMessages.contains(acquiredMsgId)) { - return - } - - totalRelevantMessages++ - msgIdToCharSequence[acquiredMsgId] = helpString - - val newAcquiredMsgCount = msgCounts.getOrDefault(acquiredMsgId, 0) + 1 - msgCounts[acquiredMsgId] = newAcquiredMsgCount - if ( - messagesToDefer.contains(acquiredMsgId) && - (mostFrequentMsgIdToDefer == null || - newAcquiredMsgCount > msgCounts.getOrDefault(mostFrequentMsgIdToDefer!!, 0)) - ) { - mostFrequentMsgIdToDefer = acquiredMsgId - } - } - - /** - * Get the most frequent deferred message that meets the [THRESHOLD] percentage of processed - * messages excluding [excludedMessages]. - * @return null if no messages have been deferred OR deferred messages didn't meet the - * [THRESHOLD] percentage of messages to show. - */ - fun getDeferredMessage(): CharSequence? { - mostFrequentMsgIdToDefer?.let { - if (msgCounts.getOrDefault(it, 0) > (THRESHOLD * totalRelevantMessages)) { - return msgIdToCharSequence[mostFrequentMsgIdToDefer] - } - } - - return null - } - companion object { - const val THRESHOLD = .5f - } -} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/FaceHelpMessageDeferral.kt b/packages/SystemUI/src/com/android/systemui/biometrics/FaceHelpMessageDeferral.kt new file mode 100644 index 000000000000..fabc1c1bb908 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/FaceHelpMessageDeferral.kt @@ -0,0 +1,141 @@ +/* + * 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.biometrics + +import android.content.res.Resources +import com.android.keyguard.logging.BiometricMessageDeferralLogger +import com.android.keyguard.logging.FaceMessageDeferralLogger +import com.android.systemui.Dumpable +import com.android.systemui.R +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.dump.DumpManager +import java.io.PrintWriter +import java.util.* +import javax.inject.Inject + +/** + * Provides whether a face acquired help message should be shown immediately when its received or + * should be shown when face auth times out. See [updateMessage] and [getDeferredMessage]. + */ +@SysUISingleton +class FaceHelpMessageDeferral +@Inject +constructor( + @Main resources: Resources, + logBuffer: FaceMessageDeferralLogger, + dumpManager: DumpManager +) : + BiometricMessageDeferral( + resources.getIntArray(R.array.config_face_help_msgs_defer_until_timeout).toHashSet(), + resources.getFloat(R.dimen.config_face_help_msgs_defer_until_timeout_threshold), + logBuffer, + dumpManager + ) + +/** + * @property messagesToDefer messages that shouldn't show immediately when received, but may be + * shown later if the message is the most frequent acquiredInfo processed and meets [threshold] + * percentage of all passed acquired frames. + */ +open class BiometricMessageDeferral( + private val messagesToDefer: Set<Int>, + private val threshold: Float, + private val logBuffer: BiometricMessageDeferralLogger, + dumpManager: DumpManager +) : Dumpable { + private val acquiredInfoToFrequency: MutableMap<Int, Int> = HashMap() + private val acquiredInfoToHelpString: MutableMap<Int, String> = HashMap() + private var mostFrequentAcquiredInfoToDefer: Int? = null + private var totalFrames = 0 + + init { + dumpManager.registerDumpable(this.javaClass.name, this) + } + + override fun dump(pw: PrintWriter, args: Array<out String>) { + pw.println("messagesToDefer=$messagesToDefer") + pw.println("totalFrames=$totalFrames") + pw.println("threshold=$threshold") + } + + /** Reset all saved counts. */ + fun reset() { + totalFrames = 0 + mostFrequentAcquiredInfoToDefer = null + acquiredInfoToFrequency.clear() + acquiredInfoToHelpString.clear() + logBuffer.reset() + } + + /** Updates the message associated with the acquiredInfo if it's a message we may defer. */ + fun updateMessage(acquiredInfo: Int, helpString: String) { + if (!messagesToDefer.contains(acquiredInfo)) { + return + } + if (!Objects.equals(acquiredInfoToHelpString[acquiredInfo], helpString)) { + logBuffer.logUpdateMessage(acquiredInfo, helpString) + acquiredInfoToHelpString[acquiredInfo] = helpString + } + } + + /** Whether the given message should be deferred instead of being shown immediately. */ + fun shouldDefer(acquiredMsgId: Int): Boolean { + return messagesToDefer.contains(acquiredMsgId) + } + + /** Adds the acquiredInfo frame to the counts. We account for all frames. */ + fun processFrame(acquiredInfo: Int) { + if (messagesToDefer.isEmpty()) { + return + } + + totalFrames++ + + val newAcquiredInfoCount = acquiredInfoToFrequency.getOrDefault(acquiredInfo, 0) + 1 + acquiredInfoToFrequency[acquiredInfo] = newAcquiredInfoCount + if ( + messagesToDefer.contains(acquiredInfo) && + (mostFrequentAcquiredInfoToDefer == null || + newAcquiredInfoCount > + acquiredInfoToFrequency.getOrDefault(mostFrequentAcquiredInfoToDefer!!, 0)) + ) { + mostFrequentAcquiredInfoToDefer = acquiredInfo + } + + logBuffer.logFrameProcessed( + acquiredInfo, + totalFrames, + mostFrequentAcquiredInfoToDefer?.toString() + ) + } + + /** + * Get the most frequent deferred message that meets the [threshold] percentage of processed + * frames. + * @return null if no acquiredInfo have been deferred OR deferred messages didn't meet the + * [threshold] percentage. + */ + fun getDeferredMessage(): CharSequence? { + mostFrequentAcquiredInfoToDefer?.let { + if (acquiredInfoToFrequency.getOrDefault(it, 0) > (threshold * totalFrames)) { + return acquiredInfoToHelpString[it] + } + } + return null + } +} diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/BluetoothLogger.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/BluetoothLogger.kt new file mode 100644 index 000000000000..96af42bfda22 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/bluetooth/BluetoothLogger.kt @@ -0,0 +1,102 @@ +/* + * 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.bluetooth + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.LogLevel +import com.android.systemui.log.dagger.BluetoothLog +import javax.inject.Inject + +/** Helper class for logging bluetooth events. */ +@SysUISingleton +class BluetoothLogger @Inject constructor(@BluetoothLog private val logBuffer: LogBuffer) { + fun logActiveDeviceChanged(address: String?, profileId: Int) = + logBuffer.log( + TAG, + LogLevel.DEBUG, + { + str1 = address + int1 = profileId + }, + { "ActiveDeviceChanged. address=$str1 profileId=$int1" } + ) + + fun logDeviceConnectionStateChanged(address: String?, state: String) = + logBuffer.log( + TAG, + LogLevel.DEBUG, + { + str1 = address + str2 = state + }, + { "DeviceConnectionStateChanged. address=$str1 state=$str2" } + ) + + fun logAclConnectionStateChanged(address: String, state: String) = + logBuffer.log( + TAG, + LogLevel.DEBUG, + { + str1 = address + str2 = state + }, + { "AclConnectionStateChanged. address=$str1 state=$str2" } + ) + + fun logProfileConnectionStateChanged(address: String?, state: String, profileId: Int) = + logBuffer.log( + TAG, + LogLevel.DEBUG, + { + str1 = address + str2 = state + int1 = profileId + }, + { "ProfileConnectionStateChanged. address=$str1 state=$str2 profileId=$int1" } + ) + + fun logStateChange(state: String) = + logBuffer.log( + TAG, + LogLevel.DEBUG, + { str1 = state }, + { "BluetoothStateChanged. state=$str1" } + ) + + fun logBondStateChange(address: String, state: Int) = + logBuffer.log( + TAG, + LogLevel.DEBUG, + { + str1 = address + int1 = state + }, + { "DeviceBondStateChanged. address=$str1 state=$int1" } + ) + + fun logDeviceAdded(address: String) = + logBuffer.log(TAG, LogLevel.DEBUG, { str1 = address }, { "DeviceAdded. address=$str1" }) + + fun logDeviceDeleted(address: String) = + logBuffer.log(TAG, LogLevel.DEBUG, { str1 = address }, { "DeviceDeleted. address=$str1" }) + + fun logDeviceAttributesChanged() = + logBuffer.log(TAG, LogLevel.DEBUG, {}, { "DeviceAttributesChanged." }) +} + +private const val TAG = "BluetoothLog" diff --git a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java index 31a2134851a2..bfbf37a5d3e9 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java @@ -291,7 +291,7 @@ public class BrightLineFalsingManager implements FalsingManager { FalsingClassifier.Result.falsed( 0, getClass().getSimpleName(), "bad history")); logDebug("False Single Tap: true (bad history)"); - mFalsingTapListeners.forEach(FalsingTapListener::onDoubleTapRequired); + mFalsingTapListeners.forEach(FalsingTapListener::onAdditionalTapRequired); return true; } else { mPriorResults = getPassedResult(0.1); @@ -321,7 +321,7 @@ public class BrightLineFalsingManager implements FalsingManager { mHistoryTracker.falseBelief(), mHistoryTracker.falseConfidence()); mPriorResults = Collections.singleton(result); - logDebug("False Double Tap: " + result.isFalse()); + logDebug("False Double Tap: " + result.isFalse() + " reason=" + result.getReason()); return result.isFalse(); } diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java index 23d87ff980ca..f5f9655ef24b 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java @@ -303,9 +303,7 @@ class FalsingCollectorImpl implements FalsingCollector { @Override public void onTouchEvent(MotionEvent ev) { - if (!mKeyguardStateController.isShowing() - || (mStatusBarStateController.isDozing() - && !mStatusBarStateController.isPulsing())) { + if (!mKeyguardStateController.isShowing()) { avoidGesture(); return; } diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/drawable/CircularDrawable.kt b/packages/SystemUI/src/com/android/systemui/common/ui/drawable/CircularDrawable.kt new file mode 100644 index 000000000000..ec71c3824156 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/common/ui/drawable/CircularDrawable.kt @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.common.ui.drawable + +import android.graphics.Canvas +import android.graphics.Path +import android.graphics.Rect +import android.graphics.drawable.Drawable +import android.graphics.drawable.DrawableWrapper +import kotlin.math.min + +/** Renders the wrapped [Drawable] as a circle. */ +class CircularDrawable( + drawable: Drawable, +) : DrawableWrapper(drawable) { + private val path: Path by lazy { Path() } + + override fun onBoundsChange(bounds: Rect) { + super.onBoundsChange(bounds) + updateClipPath() + } + + override fun draw(canvas: Canvas) { + canvas.save() + canvas.clipPath(path) + drawable?.draw(canvas) + canvas.restore() + } + + private fun updateClipPath() { + path.reset() + path.addCircle( + bounds.centerX().toFloat(), + bounds.centerY().toFloat(), + min(bounds.width(), bounds.height()) / 2f, + Path.Direction.CW + ) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/DoubleShadowTextClock.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/DoubleShadowTextClock.java index 653f4dc66200..789ebc517271 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/complication/DoubleShadowTextClock.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/DoubleShadowTextClock.java @@ -17,24 +17,21 @@ package com.android.systemui.dreams.complication; import android.content.Context; +import android.content.res.Resources; import android.graphics.Canvas; import android.util.AttributeSet; import android.widget.TextClock; import com.android.systemui.R; +import com.android.systemui.dreams.complication.DoubleShadowTextHelper.ShadowInfo; + +import kotlin.Unit; /** * Extension of {@link TextClock} which draws two shadows on the text (ambient and key shadows) */ public class DoubleShadowTextClock extends TextClock { - private final float mAmbientShadowBlur; - private final int mAmbientShadowColor; - private final float mKeyShadowBlur; - private final float mKeyShadowOffsetX; - private final float mKeyShadowOffsetY; - private final int mKeyShadowColor; - private final float mAmbientShadowOffsetX; - private final float mAmbientShadowOffsetY; + private final DoubleShadowTextHelper mShadowHelper; public DoubleShadowTextClock(Context context) { this(context, null); @@ -46,38 +43,28 @@ public class DoubleShadowTextClock extends TextClock { public DoubleShadowTextClock(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); - mKeyShadowBlur = context.getResources() - .getDimensionPixelSize(R.dimen.dream_overlay_clock_key_text_shadow_radius); - mKeyShadowOffsetX = context.getResources() - .getDimensionPixelSize(R.dimen.dream_overlay_clock_key_text_shadow_dx); - mKeyShadowOffsetY = context.getResources() - .getDimensionPixelSize(R.dimen.dream_overlay_clock_key_text_shadow_dy); - mKeyShadowColor = context.getResources().getColor( - R.color.dream_overlay_clock_key_text_shadow_color); - mAmbientShadowBlur = context.getResources() - .getDimensionPixelSize(R.dimen.dream_overlay_clock_ambient_text_shadow_radius); - mAmbientShadowColor = context.getResources().getColor( - R.color.dream_overlay_clock_ambient_text_shadow_color); - mAmbientShadowOffsetX = context.getResources() - .getDimensionPixelSize(R.dimen.dream_overlay_clock_ambient_text_shadow_dx); - mAmbientShadowOffsetY = context.getResources() - .getDimensionPixelSize(R.dimen.dream_overlay_clock_ambient_text_shadow_dy); + + final Resources resources = context.getResources(); + final ShadowInfo keyShadowInfo = new ShadowInfo( + resources.getDimensionPixelSize(R.dimen.dream_overlay_clock_key_text_shadow_radius), + resources.getDimensionPixelSize(R.dimen.dream_overlay_clock_key_text_shadow_dx), + resources.getDimensionPixelSize(R.dimen.dream_overlay_clock_key_text_shadow_dy), + resources.getColor(R.color.dream_overlay_clock_key_text_shadow_color)); + + final ShadowInfo ambientShadowInfo = new ShadowInfo( + resources.getDimensionPixelSize( + R.dimen.dream_overlay_clock_ambient_text_shadow_radius), + resources.getDimensionPixelSize(R.dimen.dream_overlay_clock_ambient_text_shadow_dx), + resources.getDimensionPixelSize(R.dimen.dream_overlay_clock_ambient_text_shadow_dy), + resources.getColor(R.color.dream_overlay_clock_ambient_text_shadow_color)); + mShadowHelper = new DoubleShadowTextHelper(keyShadowInfo, ambientShadowInfo); } @Override public void onDraw(Canvas canvas) { - // We enhance the shadow by drawing the shadow twice - getPaint().setShadowLayer(mAmbientShadowBlur, mAmbientShadowOffsetX, mAmbientShadowOffsetY, - mAmbientShadowColor); - super.onDraw(canvas); - canvas.save(); - canvas.clipRect(getScrollX(), getScrollY() + getExtendedPaddingTop(), - getScrollX() + getWidth(), - getScrollY() + getHeight()); - - getPaint().setShadowLayer( - mKeyShadowBlur, mKeyShadowOffsetX, mKeyShadowOffsetY, mKeyShadowColor); - super.onDraw(canvas); - canvas.restore(); + mShadowHelper.applyShadows(this, canvas, () -> { + super.onDraw(canvas); + return Unit.INSTANCE; + }); } } diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/DoubleShadowTextHelper.kt b/packages/SystemUI/src/com/android/systemui/dreams/complication/DoubleShadowTextHelper.kt new file mode 100644 index 000000000000..b1dc5a2e5dea --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/DoubleShadowTextHelper.kt @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.dreams.complication + +import android.graphics.Canvas +import android.widget.TextView +import androidx.annotation.ColorInt + +class DoubleShadowTextHelper +constructor( + private val keyShadowInfo: ShadowInfo, + private val ambientShadowInfo: ShadowInfo, +) { + data class ShadowInfo( + val blur: Float, + val offsetX: Float = 0f, + val offsetY: Float = 0f, + @ColorInt val color: Int + ) + + fun applyShadows(view: TextView, canvas: Canvas, onDrawCallback: () -> Unit) { + // We enhance the shadow by drawing the shadow twice + view.paint.setShadowLayer( + ambientShadowInfo.blur, + ambientShadowInfo.offsetX, + ambientShadowInfo.offsetY, + ambientShadowInfo.color + ) + onDrawCallback() + canvas.save() + canvas.clipRect( + view.scrollX, + view.scrollY + view.extendedPaddingTop, + view.scrollX + view.width, + view.scrollY + view.height + ) + + view.paint.setShadowLayer( + keyShadowInfo.blur, + keyShadowInfo.offsetX, + keyShadowInfo.offsetY, + keyShadowInfo.color + ) + onDrawCallback() + canvas.restore() + } +} diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/DoubleShadowTextView.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/DoubleShadowTextView.java new file mode 100644 index 000000000000..cf7e3127dedf --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/DoubleShadowTextView.java @@ -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.dreams.complication; + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Canvas; +import android.util.AttributeSet; +import android.widget.TextView; + +import com.android.systemui.R; + +import kotlin.Unit; + +/** + * Extension of {@link TextView} which draws two shadows on the text (ambient and key shadows} + */ +public class DoubleShadowTextView extends TextView { + private final DoubleShadowTextHelper mShadowHelper; + + public DoubleShadowTextView(Context context) { + this(context, null); + } + + public DoubleShadowTextView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public DoubleShadowTextView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + + final Resources resources = context.getResources(); + final DoubleShadowTextHelper.ShadowInfo + keyShadowInfo = new DoubleShadowTextHelper.ShadowInfo( + resources.getDimensionPixelSize( + R.dimen.dream_overlay_status_bar_key_text_shadow_radius), + resources.getDimensionPixelSize( + R.dimen.dream_overlay_status_bar_key_text_shadow_dx), + resources.getDimensionPixelSize( + R.dimen.dream_overlay_status_bar_key_text_shadow_dy), + resources.getColor(R.color.dream_overlay_status_bar_key_text_shadow_color)); + + final DoubleShadowTextHelper.ShadowInfo + ambientShadowInfo = new DoubleShadowTextHelper.ShadowInfo( + resources.getDimensionPixelSize( + R.dimen.dream_overlay_status_bar_ambient_text_shadow_radius), + resources.getDimensionPixelSize( + R.dimen.dream_overlay_status_bar_ambient_text_shadow_dx), + resources.getDimensionPixelSize( + R.dimen.dream_overlay_status_bar_ambient_text_shadow_dy), + resources.getColor(R.color.dream_overlay_status_bar_ambient_text_shadow_color)); + mShadowHelper = new DoubleShadowTextHelper(keyShadowInfo, ambientShadowInfo); + } + + @Override + public void onDraw(Canvas canvas) { + mShadowHelper.applyShadows(this, canvas, () -> { + super.onDraw(canvas); + return Unit.INSTANCE; + }); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java b/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java index a3dc77993d30..568143c8df71 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java +++ b/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java @@ -42,6 +42,8 @@ import android.util.Pair; import android.util.Slog; import android.widget.Toast; +import androidx.annotation.NonNull; + import com.android.settingslib.bluetooth.BluetoothCallback; import com.android.settingslib.bluetooth.BluetoothUtils; import com.android.settingslib.bluetooth.CachedBluetoothDevice; @@ -601,13 +603,14 @@ public class KeyboardUI extends CoreStartable implements InputManager.OnTabletMo private final class BluetoothCallbackHandler implements BluetoothCallback { @Override - public void onBluetoothStateChanged(int bluetoothState) { + public void onBluetoothStateChanged(@BluetoothCallback.AdapterState int bluetoothState) { mHandler.obtainMessage(MSG_ON_BLUETOOTH_STATE_CHANGED, bluetoothState, 0).sendToTarget(); } @Override - public void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState) { + public void onDeviceBondStateChanged( + @NonNull CachedBluetoothDevice cachedDevice, int bondState) { mHandler.obtainMessage(MSG_ON_DEVICE_BOND_STATE_CHANGED, bondState, 0, cachedDevice).sendToTarget(); } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java index 2da92326a7fd..ddcd0533c588 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java @@ -202,7 +202,8 @@ public class KeyguardService extends Service { } } - // Wrap Keyguard going away animation + // Wrap Keyguard going away animation. + // Note: Also used for wrapping occlude by Dream animation. It works (with some redundancy). private static IRemoteTransition wrap(IRemoteAnimationRunner runner) { return new IRemoteTransition.Stub() { final ArrayMap<IBinder, IRemoteTransitionFinishedCallback> mFinishCallbacks = @@ -388,6 +389,27 @@ public class KeyguardService extends Service { f = new TransitionFilter(); f.mTypeSet = new int[]{TRANSIT_KEYGUARD_UNOCCLUDE}; mShellTransitions.registerRemote(f, unoccludeTransition); + + Slog.d(TAG, "KeyguardService registerRemote: TRANSIT_KEYGUARD_OCCLUDE for DREAM"); + // Register for occluding by Dream + f = new TransitionFilter(); + f.mFlags = TRANSIT_FLAG_KEYGUARD_LOCKED; + f.mRequirements = new TransitionFilter.Requirement[]{ + new TransitionFilter.Requirement(), new TransitionFilter.Requirement()}; + // First require at-least one app of type DREAM showing that occludes. + f.mRequirements[0].mActivityType = WindowConfiguration.ACTIVITY_TYPE_DREAM; + f.mRequirements[0].mMustBeIndependent = false; + f.mRequirements[0].mFlags = FLAG_OCCLUDES_KEYGUARD; + f.mRequirements[0].mModes = new int[]{TRANSIT_OPEN, TRANSIT_TO_FRONT}; + // Then require that we aren't closing any occludes (because this would mean a + // regular task->task or activity->activity animation not involving keyguard). + f.mRequirements[1].mNot = true; + f.mRequirements[1].mMustBeIndependent = false; + f.mRequirements[1].mFlags = FLAG_OCCLUDES_KEYGUARD; + f.mRequirements[1].mModes = new int[]{TRANSIT_CLOSE, TRANSIT_TO_BACK}; + mShellTransitions.registerRemote(f, new RemoteTransition( + wrap(mKeyguardViewMediator.getOccludeByDreamAnimationRunner()), + getIApplicationThread())); } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt index bd75ab2adb54..6f38f4f53b7c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt @@ -241,9 +241,8 @@ class KeyguardUnlockAnimationController @Inject constructor( */ @VisibleForTesting var surfaceTransactionApplier: SyncRtSurfaceTransactionApplier? = null - private var surfaceBehindRemoteAnimationTarget: RemoteAnimationTarget? = null + private var surfaceBehindRemoteAnimationTargets: Array<RemoteAnimationTarget>? = null private var surfaceBehindRemoteAnimationStartTime: Long = 0 - private var surfaceBehindParams: SyncRtSurfaceTransactionApplier.SurfaceParams? = null /** * Alpha value applied to [surfaceBehindRemoteAnimationTarget], which is the surface of the @@ -458,7 +457,7 @@ class KeyguardUnlockAnimationController @Inject constructor( * (fingerprint, tap, etc.) and the keyguard is going away. */ fun notifyStartSurfaceBehindRemoteAnimation( - target: RemoteAnimationTarget, + targets: Array<RemoteAnimationTarget>, startTime: Long, requestedShowSurfaceBehindKeyguard: Boolean ) { @@ -467,10 +466,7 @@ class KeyguardUnlockAnimationController @Inject constructor( keyguardViewController.viewRootImpl.view) } - // New animation, new params. - surfaceBehindParams = null - - surfaceBehindRemoteAnimationTarget = target + surfaceBehindRemoteAnimationTargets = targets surfaceBehindRemoteAnimationStartTime = startTime // If we specifically requested that the surface behind be made visible (vs. it being made @@ -597,7 +593,7 @@ class KeyguardUnlockAnimationController @Inject constructor( * keyguard dismiss amount and the method of dismissal. */ private fun updateSurfaceBehindAppearAmount() { - if (surfaceBehindRemoteAnimationTarget == null) { + if (surfaceBehindRemoteAnimationTargets == null) { return } @@ -715,63 +711,60 @@ class KeyguardUnlockAnimationController @Inject constructor( * cancelled). */ fun setSurfaceBehindAppearAmount(amount: Float) { - if (surfaceBehindRemoteAnimationTarget == null) { - return - } + surfaceBehindRemoteAnimationTargets?.forEach { surfaceBehindRemoteAnimationTarget -> + val surfaceHeight: Int = surfaceBehindRemoteAnimationTarget.screenSpaceBounds.height() + + var scaleFactor = (SURFACE_BEHIND_START_SCALE_FACTOR + + (1f - SURFACE_BEHIND_START_SCALE_FACTOR) * + MathUtils.clamp(amount, 0f, 1f)) - // Otherwise, animate in the surface's scale/transltion. - val surfaceHeight: Int = surfaceBehindRemoteAnimationTarget!!.screenSpaceBounds.height() - - var scaleFactor = (SURFACE_BEHIND_START_SCALE_FACTOR + - (1f - SURFACE_BEHIND_START_SCALE_FACTOR) * - MathUtils.clamp(amount, 0f, 1f)) - - // If we're dismissing via swipe to the Launcher, we'll play in-window scale animations, so - // don't also scale the window. - if (keyguardStateController.isDismissingFromSwipe && - willUnlockWithInWindowLauncherAnimations) { - scaleFactor = 1f - } - - // Scale up from a point at the center-bottom of the surface. - surfaceBehindMatrix.setScale( - scaleFactor, - scaleFactor, - surfaceBehindRemoteAnimationTarget!!.screenSpaceBounds.width() / 2f, - surfaceHeight * SURFACE_BEHIND_SCALE_PIVOT_Y - ) - - // Translate up from the bottom. - surfaceBehindMatrix.postTranslate( - 0f, - surfaceHeight * SURFACE_BEHIND_START_TRANSLATION_Y * (1f - amount) - ) - - // If we're snapping the keyguard back, immediately begin fading it out. - val animationAlpha = - if (keyguardStateController.isSnappingKeyguardBackAfterSwipe) amount - else surfaceBehindAlpha - - // SyncRtSurfaceTransactionApplier cannot apply transaction when the target view is unable - // to draw - val sc: SurfaceControl? = surfaceBehindRemoteAnimationTarget?.leash - if (keyguardViewController.viewRootImpl.view?.visibility != View.VISIBLE && - sc?.isValid == true) { - with(SurfaceControl.Transaction()) { - setMatrix(sc, surfaceBehindMatrix, tmpFloat) - setCornerRadius(sc, roundedCornerRadius) - setAlpha(sc, animationAlpha) - apply() + // If we're dismissing via swipe to the Launcher, we'll play in-window scale animations, + // so don't also scale the window. + if (keyguardStateController.isDismissingFromSwipe && + willUnlockWithInWindowLauncherAnimations) { + scaleFactor = 1f } - } else { - applyParamsToSurface( - SyncRtSurfaceTransactionApplier.SurfaceParams.Builder( - surfaceBehindRemoteAnimationTarget!!.leash) - .withMatrix(surfaceBehindMatrix) - .withCornerRadius(roundedCornerRadius) - .withAlpha(animationAlpha) - .build() + + // Translate up from the bottom. + surfaceBehindMatrix.setTranslate( + surfaceBehindRemoteAnimationTarget.screenSpaceBounds.left.toFloat(), + surfaceHeight * SURFACE_BEHIND_START_TRANSLATION_Y * (1f - amount) ) + + // Scale up from a point at the center-bottom of the surface. + surfaceBehindMatrix.postScale( + scaleFactor, + scaleFactor, + keyguardViewController.viewRootImpl.width / 2f, + surfaceHeight * SURFACE_BEHIND_SCALE_PIVOT_Y + ) + + // If we're snapping the keyguard back, immediately begin fading it out. + val animationAlpha = + if (keyguardStateController.isSnappingKeyguardBackAfterSwipe) amount + else surfaceBehindAlpha + + // SyncRtSurfaceTransactionApplier cannot apply transaction when the target view is + // unable to draw + val sc: SurfaceControl? = surfaceBehindRemoteAnimationTarget.leash + if (keyguardViewController.viewRootImpl.view?.visibility != View.VISIBLE && + sc?.isValid == true) { + with(SurfaceControl.Transaction()) { + setMatrix(sc, surfaceBehindMatrix, tmpFloat) + setCornerRadius(sc, roundedCornerRadius) + setAlpha(sc, animationAlpha) + apply() + } + } else { + applyParamsToSurface( + SyncRtSurfaceTransactionApplier.SurfaceParams.Builder( + surfaceBehindRemoteAnimationTarget.leash) + .withMatrix(surfaceBehindMatrix) + .withCornerRadius(roundedCornerRadius) + .withAlpha(animationAlpha) + .build() + ) + } } } @@ -796,8 +789,7 @@ class KeyguardUnlockAnimationController @Inject constructor( launcherUnlockController?.setUnlockAmount(1f, false /* forceIfAnimating */) // That target is no longer valid since the animation finished, null it out. - surfaceBehindRemoteAnimationTarget = null - surfaceBehindParams = null + surfaceBehindRemoteAnimationTargets = null playingCannedUnlockAnimation = false willUnlockWithInWindowLauncherAnimations = false @@ -829,7 +821,6 @@ class KeyguardUnlockAnimationController @Inject constructor( private fun applyParamsToSurface(params: SyncRtSurfaceTransactionApplier.SurfaceParams) { surfaceTransactionApplier!!.scheduleApply(params) - surfaceBehindParams = params } private fun fadeInSurfaceBehind() { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index d4e0f061ba06..be1d162e2b08 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -831,7 +831,7 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, public void onLaunchAnimationStart(boolean isExpandingFullyAbove) {} @Override - public void onLaunchAnimationCancelled() { + public void onLaunchAnimationCancelled(@Nullable Boolean newKeyguardOccludedState) { Log.d(TAG, "Occlude launch animation cancelled. Occluded state is now: " + mOccluded); } @@ -2587,18 +2587,10 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, mInteractionJankMonitor.begin( createInteractionJankMonitorConf("DismissPanel")); - // Apply the opening animation on root task if exists - RemoteAnimationTarget aniTarget = apps[0]; - for (RemoteAnimationTarget tmpTarget : apps) { - if (tmpTarget.taskId != -1 && !tmpTarget.hasAnimatingParent) { - aniTarget = tmpTarget; - break; - } - } // Pass the surface and metadata to the unlock animation controller. mKeyguardUnlockAnimationControllerLazy.get() .notifyStartSurfaceBehindRemoteAnimation( - aniTarget, startTime, mSurfaceBehindRemoteAnimationRequested); + apps, startTime, mSurfaceBehindRemoteAnimationRequested); } else { mInteractionJankMonitor.begin( createInteractionJankMonitorConf("RemoteAnimationDisabled")); diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/BiometricMessagesLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/BiometricMessagesLog.java new file mode 100644 index 000000000000..7f1ad6d20c16 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/BiometricMessagesLog.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.log.dagger; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import javax.inject.Qualifier; + +/** + * A {@link com.android.systemui.log.LogBuffer} for BiometricMessages processing such as + * {@link com.android.systemui.biometrics.FaceHelpMessageDeferral} + */ +@Qualifier +@Documented +@Retention(RetentionPolicy.RUNTIME) +public @interface BiometricMessagesLog { +} diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/BluetoothLog.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/BluetoothLog.kt new file mode 100644 index 000000000000..4887b6a14658 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/BluetoothLog.kt @@ -0,0 +1,22 @@ +/* + * 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 javax.inject.Qualifier + +/** A [com.android.systemui.log.LogBuffer] for bluetooth. */ +@Qualifier @MustBeDocumented @Retention(AnnotationRetention.RUNTIME) annotation class BluetoothLog diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardUpdateMonitorLog.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardUpdateMonitorLog.kt index 323ee21953ea..b551125fccc7 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardUpdateMonitorLog.kt +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardUpdateMonitorLog.kt @@ -1,4 +1,9 @@ package com.android.systemui.log.dagger +import javax.inject.Qualifier + /** A [com.android.systemui.log.LogBuffer] for KeyguardUpdateMonitor. */ +@Qualifier +@MustBeDocumented +@Retention(AnnotationRetention.RUNTIME) annotation class KeyguardUpdateMonitorLog diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java index c2a87649adef..5612c22311fb 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java @@ -287,6 +287,17 @@ public class LogModule { } /** + * Provides a {@link LogBuffer} for use by + * {@link com.android.systemui.biometrics.FaceHelpMessageDeferral}. + */ + @Provides + @SysUISingleton + @BiometricMessagesLog + public static LogBuffer provideBiometricMessagesLogBuffer(LogBufferFactory factory) { + return factory.create("BiometricMessagesLog", 150); + } + + /** * Provides a {@link LogBuffer} for use by the status bar network controller. */ @Provides @@ -305,4 +316,14 @@ public class LogModule { public static LogBuffer provideKeyguardUpdateMonitorLogBuffer(LogBufferFactory factory) { return factory.create("KeyguardUpdateMonitorLog", 200); } + + /** + * Provides a {@link LogBuffer} for bluetooth-related logs. + */ + @Provides + @SysUISingleton + @BluetoothLog + public static LogBuffer providerBluetoothLogBuffer(LogBufferFactory factory) { + return factory.create("BluetoothLog", 50); + } } diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/StatusBarConnectivityLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/StatusBarConnectivityLog.java index f03fbcba41b7..b237f2d74483 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/StatusBarConnectivityLog.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/StatusBarConnectivityLog.java @@ -19,7 +19,6 @@ package com.android.systemui.log.dagger; import static java.lang.annotation.RetentionPolicy.RUNTIME; import com.android.systemui.log.LogBuffer; -import com.android.systemui.statusbar.pipeline.ConnectivityInfoProcessor; import java.lang.annotation.Documented; import java.lang.annotation.Retention; @@ -27,7 +26,7 @@ import java.lang.annotation.Retention; import javax.inject.Qualifier; /** - * A {@link LogBuffer} for events processed by {@link ConnectivityInfoProcessor} + * A {@link LogBuffer} for status bar connectivity events. */ @Qualifier @Documented diff --git a/packages/SystemUI/src/com/android/systemui/media/SquigglyProgress.kt b/packages/SystemUI/src/com/android/systemui/media/SquigglyProgress.kt index 88f6f3dd9d0e..6bc94cd5f525 100644 --- a/packages/SystemUI/src/com/android/systemui/media/SquigglyProgress.kt +++ b/packages/SystemUI/src/com/android/systemui/media/SquigglyProgress.kt @@ -60,6 +60,8 @@ class SquigglyProgress : Drawable() { linePaint.strokeWidth = value } + // Enables a transition region where the amplitude + // of the wave is reduced linearly across it. var transitionEnabled = true set(value) { field = value @@ -116,44 +118,40 @@ class SquigglyProgress : Drawable() { } val progress = level / 10_000f - val totalProgressPx = bounds.width() * progress - val waveProgressPx = bounds.width() * ( + val totalWidth = bounds.width().toFloat() + val totalProgressPx = totalWidth * progress + val waveProgressPx = totalWidth * ( if (!transitionEnabled || progress > matchedWaveEndpoint) progress else lerp(minWaveEndpoint, matchedWaveEndpoint, lerpInv(0f, matchedWaveEndpoint, progress))) // Build Wiggly Path - val waveStart = -phaseOffset - val waveEnd = waveProgressPx - val transitionLength = if (transitionEnabled) transitionPeriods * waveLength else 0.01f + val waveStart = -phaseOffset - waveLength / 2f + val waveEnd = if (transitionEnabled) totalWidth else waveProgressPx // helper function, computes amplitude for wave segment val computeAmplitude: (Float, Float) -> Float = { x, sign -> - sign * heightFraction * lineAmplitude * - lerpInvSat(waveEnd, waveEnd - transitionLength, x) + if (transitionEnabled) { + val length = transitionPeriods * waveLength + val coeff = lerpInvSat( + waveProgressPx + length / 2f, + waveProgressPx - length / 2f, + x) + sign * heightFraction * lineAmplitude * coeff + } else { + sign * heightFraction * lineAmplitude + } } - var currentX = waveEnd - var waveSign = if (phaseOffset < waveLength / 2) 1f else -1f + // Reset path object to the start path.rewind() + path.moveTo(waveStart, 0f) - // Draw flat line from end to wave endpoint - path.moveTo(bounds.width().toFloat(), 0f) - path.lineTo(waveEnd, 0f) - - // First wave has shortened wavelength - // approx quarter wave gets us to first wave peak - // shouldn't be big enough to notice it's not a sin wave - currentX -= phaseOffset % (waveLength / 2) - val controlRatio = 0.25f + // Build the wave, incrementing by half the wavelength each time + var currentX = waveStart + var waveSign = 1f var currentAmp = computeAmplitude(currentX, waveSign) - path.cubicTo( - waveEnd, currentAmp * controlRatio, - lerp(currentX, waveEnd, controlRatio), currentAmp, - currentX, currentAmp) - - // Other waves have full wavelength - val dist = -1 * waveLength / 2f - while (currentX > waveStart) { + val dist = waveLength / 2f + while (currentX < waveEnd) { waveSign = -waveSign val nextX = currentX + dist val midX = currentX + dist / 2 @@ -166,34 +164,35 @@ class SquigglyProgress : Drawable() { currentX = nextX } - // Draw path; clip to progress position + // translate to the start position of the progress bar for all draw commands + val clipTop = lineAmplitude + strokeWidth canvas.save() canvas.translate(bounds.left.toFloat(), bounds.centerY().toFloat()) - canvas.clipRect( - 0f, - -lineAmplitude - strokeWidth, - totalProgressPx, - lineAmplitude + strokeWidth) - canvas.drawPath(path, wavePaint) - canvas.restore() - // Draw path; clip between progression position & far edge + // Draw path up to progress position canvas.save() - canvas.translate(bounds.left.toFloat(), bounds.centerY().toFloat()) - canvas.clipRect( - totalProgressPx, - -lineAmplitude - strokeWidth, - bounds.width().toFloat(), - lineAmplitude + strokeWidth) - canvas.drawPath(path, linePaint) + canvas.clipRect(0f, -1f * clipTop, totalProgressPx, clipTop) + canvas.drawPath(path, wavePaint) canvas.restore() + if (transitionEnabled) { + // If there's a smooth transition, we draw the rest of the + // path in a different color (using different clip params) + canvas.save() + canvas.clipRect(totalProgressPx, -1f * clipTop, totalWidth, clipTop) + canvas.drawPath(path, linePaint) + canvas.restore() + } else { + // No transition, just draw a flat line to the end of the region. + // The discontinuity is hidden by the progress bar thumb shape. + canvas.drawLine(totalProgressPx, 0f, totalWidth, 0f, linePaint) + } + // Draw round line cap at the beginning of the wave - val startAmp = cos(abs(waveEnd - phaseOffset) / waveLength * TWO_PI) - canvas.drawPoint( - bounds.left.toFloat(), - bounds.centerY() + startAmp * lineAmplitude * heightFraction, - wavePaint) + val startAmp = cos(abs(waveStart) / waveLength * TWO_PI) + canvas.drawPoint(0f, startAmp * lineAmplitude * heightFraction, wavePaint) + + canvas.restore() } override fun getOpacity(): Int { @@ -233,4 +232,4 @@ class SquigglyProgress : Drawable() { linePaint.color = ColorUtils.setAlphaComponent(tintColor, (DISABLED_ALPHA * (alpha / 255f)).toInt()) } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputMetricLogger.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputMetricLogger.java index 6fe06e085556..7d3e82c9d47f 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputMetricLogger.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputMetricLogger.java @@ -107,7 +107,8 @@ public class MediaOutputMetricLogger { SysUiStatsLog.write( SysUiStatsLog.MEDIAOUTPUT_OP_INTERACTION_REPORT, SysUiStatsLog.MEDIA_OUTPUT_OP_INTERACTION_REPORTED__INTERACTION_TYPE__ADJUST_VOLUME, - getInteractionDeviceType(source)); + getInteractionDeviceType(source), + getLoggingPackageName()); } /** @@ -121,7 +122,8 @@ public class MediaOutputMetricLogger { SysUiStatsLog.write( SysUiStatsLog.MEDIAOUTPUT_OP_INTERACTION_REPORT, SysUiStatsLog.MEDIA_OUTPUT_OP_INTERACTION_REPORTED__INTERACTION_TYPE__STOP_CASTING, - SysUiStatsLog.MEDIA_OUTPUT_OP_INTERACTION_REPORTED__TARGET__UNKNOWN_TYPE); + SysUiStatsLog.MEDIA_OUTPUT_OP_INTERACTION_REPORTED__TARGET__UNKNOWN_TYPE, + getLoggingPackageName()); } /** @@ -135,7 +137,8 @@ public class MediaOutputMetricLogger { SysUiStatsLog.write( SysUiStatsLog.MEDIAOUTPUT_OP_INTERACTION_REPORT, SysUiStatsLog.MEDIA_OUTPUT_OP_INTERACTION_REPORTED__INTERACTION_TYPE__EXPANSION, - getInteractionDeviceType(source)); + getInteractionDeviceType(source), + getLoggingPackageName()); } /** diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt index aa10f7e2738f..b565f3c22f24 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt @@ -18,38 +18,57 @@ package com.android.systemui.media.taptotransfer.common import com.android.systemui.log.LogBuffer import com.android.systemui.log.LogLevel +import com.android.systemui.temporarydisplay.TemporaryViewLogger /** * A logger for media tap-to-transfer events. * - * @property deviceTypeTag the type of device triggering the logs -- "Sender" or "Receiver". + * @param deviceTypeTag the type of device triggering the logs -- "Sender" or "Receiver". */ class MediaTttLogger( - private val deviceTypeTag: String, - private val buffer: LogBuffer -){ + deviceTypeTag: String, + buffer: LogBuffer +) : TemporaryViewLogger(buffer, BASE_TAG + deviceTypeTag) { /** Logs a change in the chip state for the given [mediaRouteId]. */ - fun logStateChange(stateName: String, mediaRouteId: String) { + fun logStateChange(stateName: String, mediaRouteId: String, packageName: String?) { buffer.log( - BASE_TAG + deviceTypeTag, + tag, LogLevel.DEBUG, { str1 = stateName str2 = mediaRouteId + str3 = packageName }, - { "State changed to $str1 for ID=$str2" } + { "State changed to $str1 for ID=$str2 package=$str3" } ) } - /** Logs that we removed the chip for the given [reason]. */ - fun logChipRemoval(reason: String) { + /** Logs that we couldn't find information for [packageName]. */ + fun logPackageNotFound(packageName: String) { buffer.log( - BASE_TAG + deviceTypeTag, + tag, LogLevel.DEBUG, - { str1 = reason }, - { "Chip removed due to $str1" } + { str1 = packageName }, + { "Package $str1 could not be found" } ) } + + /** + * Logs that a removal request has been bypassed (ignored). + * + * @param removalReason the reason that the chip removal was requested. + * @param bypassReason the reason that the request was bypassed. + */ + fun logRemovalBypass(removalReason: String, bypassReason: String) { + buffer.log( + tag, + LogLevel.DEBUG, + { + str1 = removalReason + str2 = bypassReason + }, + { "Chip removal requested due to $str1; however, removal was ignored because $str2" }) + } } private const val BASE_TAG = "MediaTtt" diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt new file mode 100644 index 000000000000..792ae7ca6049 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt @@ -0,0 +1,112 @@ +/* + * 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.media.taptotransfer.common + +import android.content.Context +import android.content.pm.PackageManager +import android.graphics.drawable.Drawable +import com.android.internal.widget.CachingIconView +import com.android.settingslib.Utils +import com.android.systemui.R + +/** Utility methods for media tap-to-transfer. */ +class MediaTttUtils { + companion object { + // Used in CTS tests UpdateMediaTapToTransferSenderDisplayTest and + // UpdateMediaTapToTransferReceiverDisplayTest + const val WINDOW_TITLE = "Media Transfer Chip View" + const val WAKE_REASON = "MEDIA_TRANSFER_ACTIVATED" + + /** + * Returns the information needed to display the icon. + * + * The information will either contain app name and icon of the app playing media, or a + * default name and icon if we can't find the app name/icon. + * + * @param appPackageName the package name of the app playing the media. + * @param logger the logger to use for any errors. + */ + fun getIconInfoFromPackageName( + context: Context, + appPackageName: String?, + logger: MediaTttLogger + ): IconInfo { + if (appPackageName != null) { + try { + val contentDescription = + context.packageManager + .getApplicationInfo( + appPackageName, + PackageManager.ApplicationInfoFlags.of(0) + ) + .loadLabel(context.packageManager) + .toString() + return IconInfo( + contentDescription, + drawable = context.packageManager.getApplicationIcon(appPackageName), + isAppIcon = true + ) + } catch (e: PackageManager.NameNotFoundException) { + logger.logPackageNotFound(appPackageName) + } + } + return IconInfo( + contentDescription = + context.getString(R.string.media_output_dialog_unknown_launch_app_name), + drawable = + context.resources.getDrawable(R.drawable.ic_cast).apply { + this.setTint( + Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary) + ) + }, + isAppIcon = false + ) + } + + /** + * Sets an icon to be displayed by the given view. + * + * @param iconSize the size in pixels that the icon should be. If null, the size of + * [appIconView] will not be adjusted. + */ + fun setIcon( + appIconView: CachingIconView, + icon: Drawable, + iconContentDescription: CharSequence, + iconSize: Int? = null, + ) { + iconSize?.let { size -> + val lp = appIconView.layoutParams + lp.width = size + lp.height = size + appIconView.layoutParams = lp + } + + appIconView.contentDescription = iconContentDescription + appIconView.setImageDrawable(icon) + } + } +} + +data class IconInfo( + val contentDescription: String, + val drawable: Drawable, + /** + * True if [drawable] is the app's icon, and false if [drawable] is some generic default icon. + */ + val isAppIcon: Boolean +) diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt index 5d6d683f93f6..dfd9e22c14b1 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt @@ -35,6 +35,7 @@ import com.android.systemui.R import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.media.taptotransfer.common.MediaTttLogger +import com.android.systemui.media.taptotransfer.common.MediaTttUtils import com.android.systemui.statusbar.CommandQueue import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.temporarydisplay.DEFAULT_TIMEOUT_MILLIS @@ -61,7 +62,7 @@ class MediaTttChipControllerReceiver @Inject constructor( powerManager: PowerManager, @Main private val mainHandler: Handler, private val uiEventLogger: MediaTttReceiverUiEventLogger, -) : TemporaryViewDisplayController<ChipReceiverInfo>( +) : TemporaryViewDisplayController<ChipReceiverInfo, MediaTttLogger>( context, logger, windowManager, @@ -70,6 +71,8 @@ class MediaTttChipControllerReceiver @Inject constructor( configurationController, powerManager, R.layout.media_ttt_chip_receiver, + MediaTttUtils.WINDOW_TITLE, + MediaTttUtils.WAKE_REASON, ) { @SuppressLint("WrongConstant") // We're allowed to use LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS override val windowLayoutParams = commonWindowLayoutParams.apply { @@ -107,7 +110,7 @@ class MediaTttChipControllerReceiver @Inject constructor( ) { val chipState: ChipStateReceiver? = ChipStateReceiver.getReceiverStateFromId(displayState) val stateName = chipState?.name ?: "Invalid" - logger.logStateChange(stateName, routeInfo.id) + logger.logStateChange(stateName, routeInfo.id, routeInfo.clientPackageName) if (chipState == null) { Log.e(RECEIVER_TAG, "Unhandled MediaTransferReceiverState $displayState") @@ -137,13 +140,26 @@ class MediaTttChipControllerReceiver @Inject constructor( override fun updateView(newInfo: ChipReceiverInfo, currentView: ViewGroup) { super.updateView(newInfo, currentView) - val iconName = setIcon( - currentView, - newInfo.routeInfo.clientPackageName, - newInfo.appIconDrawableOverride, - newInfo.appNameOverride + + val iconInfo = MediaTttUtils.getIconInfoFromPackageName( + context, newInfo.routeInfo.clientPackageName, logger + ) + val iconDrawable = newInfo.appIconDrawableOverride ?: iconInfo.drawable + val iconContentDescription = newInfo.appNameOverride ?: iconInfo.contentDescription + val iconSize = context.resources.getDimensionPixelSize( + if (iconInfo.isAppIcon) { + R.dimen.media_ttt_icon_size_receiver + } else { + R.dimen.media_ttt_generic_icon_size_receiver + } + ) + + MediaTttUtils.setIcon( + currentView.requireViewById(R.id.app_icon), + iconDrawable, + iconContentDescription, + iconSize, ) - currentView.contentDescription = iconName } override fun animateViewIn(view: ViewGroup) { @@ -161,15 +177,6 @@ class MediaTttChipControllerReceiver @Inject constructor( startRipple(view.requireViewById(R.id.ripple)) } - override fun getIconSize(isAppIcon: Boolean): Int? = - context.resources.getDimensionPixelSize( - if (isAppIcon) { - R.dimen.media_ttt_icon_size_receiver - } else { - R.dimen.media_ttt_generic_icon_size_receiver - } - ) - /** Returns the amount that the chip will be translated by in its intro animation. */ private fun getTranslationAmount(): Int { return context.resources.getDimensionPixelSize(R.dimen.media_ttt_receiver_vert_translation) diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt index bde588c14fc8..4379d25406bf 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt @@ -34,8 +34,7 @@ import com.android.systemui.temporarydisplay.DEFAULT_TIMEOUT_MILLIS * @property stateInt the integer from [StatusBarManager] corresponding with this state. * @property stringResId the res ID of the string that should be displayed in the chip. Null if the * state should not have the chip be displayed. - * @property isMidTransfer true if the state represents that a transfer is currently ongoing. - * @property isTransferFailure true if the state represents that the transfer has failed. + * @property transferStatus the transfer status that the chip state represents. * @property timeout the amount of time this chip should display on the screen before it times out * and disappears. */ @@ -43,8 +42,7 @@ enum class ChipStateSender( @StatusBarManager.MediaTransferSenderState val stateInt: Int, val uiEvent: UiEventLogger.UiEventEnum, @StringRes val stringResId: Int?, - val isMidTransfer: Boolean = false, - val isTransferFailure: Boolean = false, + val transferStatus: TransferStatus, val timeout: Long = DEFAULT_TIMEOUT_MILLIS ) { /** @@ -56,6 +54,7 @@ enum class ChipStateSender( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST, MediaTttSenderUiEvents.MEDIA_TTT_SENDER_ALMOST_CLOSE_TO_START_CAST, R.string.media_move_closer_to_start_cast, + transferStatus = TransferStatus.NOT_STARTED, ), /** @@ -68,6 +67,7 @@ enum class ChipStateSender( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_END_CAST, MediaTttSenderUiEvents.MEDIA_TTT_SENDER_ALMOST_CLOSE_TO_END_CAST, R.string.media_move_closer_to_end_cast, + transferStatus = TransferStatus.NOT_STARTED, ), /** @@ -78,7 +78,7 @@ enum class ChipStateSender( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED, MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_RECEIVER_TRIGGERED, R.string.media_transfer_playing_different_device, - isMidTransfer = true, + transferStatus = TransferStatus.IN_PROGRESS, timeout = TRANSFER_TRIGGERED_TIMEOUT_MILLIS ), @@ -90,7 +90,7 @@ enum class ChipStateSender( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED, MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_THIS_DEVICE_TRIGGERED, R.string.media_transfer_playing_this_device, - isMidTransfer = true, + transferStatus = TransferStatus.IN_PROGRESS, timeout = TRANSFER_TRIGGERED_TIMEOUT_MILLIS ), @@ -100,7 +100,8 @@ enum class ChipStateSender( TRANSFER_TO_RECEIVER_SUCCEEDED( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED, MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_RECEIVER_SUCCEEDED, - R.string.media_transfer_playing_different_device + R.string.media_transfer_playing_different_device, + transferStatus = TransferStatus.SUCCEEDED, ) { override fun undoClickListener( controllerSender: MediaTttChipControllerSender, @@ -135,7 +136,8 @@ enum class ChipStateSender( TRANSFER_TO_THIS_DEVICE_SUCCEEDED( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED, MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_THIS_DEVICE_SUCCEEDED, - R.string.media_transfer_playing_this_device + R.string.media_transfer_playing_this_device, + transferStatus = TransferStatus.SUCCEEDED, ) { override fun undoClickListener( controllerSender: MediaTttChipControllerSender, @@ -169,7 +171,7 @@ enum class ChipStateSender( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_FAILED, MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_RECEIVER_FAILED, R.string.media_transfer_failed, - isTransferFailure = true + transferStatus = TransferStatus.FAILED, ), /** A state representing that a transfer back to this device has failed. */ @@ -177,14 +179,15 @@ enum class ChipStateSender( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_FAILED, MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_THIS_DEVICE_FAILED, R.string.media_transfer_failed, - isTransferFailure = true + transferStatus = TransferStatus.FAILED, ), /** A state representing that this device is far away from any receiver device. */ FAR_FROM_RECEIVER( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER, MediaTttSenderUiEvents.MEDIA_TTT_SENDER_FAR_FROM_RECEIVER, - stringResId = null + stringResId = null, + transferStatus = TransferStatus.TOO_FAR, ); /** diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt index 0c1ebd70c572..e539f3fd842d 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt @@ -34,6 +34,7 @@ import com.android.systemui.animation.ViewHierarchyAnimator import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.media.taptotransfer.common.MediaTttLogger +import com.android.systemui.media.taptotransfer.common.MediaTttUtils import com.android.systemui.statusbar.CommandQueue import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.temporarydisplay.TemporaryDisplayRemovalReason @@ -57,7 +58,7 @@ class MediaTttChipControllerSender @Inject constructor( configurationController: ConfigurationController, powerManager: PowerManager, private val uiEventLogger: MediaTttSenderUiEventLogger -) : TemporaryViewDisplayController<ChipSenderInfo>( +) : TemporaryViewDisplayController<ChipSenderInfo, MediaTttLogger>( context, logger, windowManager, @@ -66,6 +67,8 @@ class MediaTttChipControllerSender @Inject constructor( configurationController, powerManager, R.layout.media_ttt_chip, + MediaTttUtils.WINDOW_TITLE, + MediaTttUtils.WAKE_REASON, ) { override val windowLayoutParams = commonWindowLayoutParams.apply { gravity = Gravity.TOP.or(Gravity.CENTER_HORIZONTAL) @@ -94,7 +97,7 @@ class MediaTttChipControllerSender @Inject constructor( ) { val chipState: ChipStateSender? = ChipStateSender.getSenderStateFromId(displayState) val stateName = chipState?.name ?: "Invalid" - logger.logStateChange(stateName, routeInfo.id) + logger.logStateChange(stateName, routeInfo.id, routeInfo.clientPackageName) if (chipState == null) { Log.e(SENDER_TAG, "Unhandled MediaTransferSenderState $displayState") @@ -103,7 +106,7 @@ class MediaTttChipControllerSender @Inject constructor( uiEventLogger.logSenderStateChange(chipState) if (chipState == ChipStateSender.FAR_FROM_RECEIVER) { - removeView(removalReason = ChipStateSender.FAR_FROM_RECEIVER::class.simpleName!!) + removeView(removalReason = ChipStateSender.FAR_FROM_RECEIVER.name) } else { displayView(ChipSenderInfo(chipState, routeInfo, undoCallback)) } @@ -118,7 +121,14 @@ class MediaTttChipControllerSender @Inject constructor( val chipState = newInfo.state // App icon - val iconName = setIcon(currentView, newInfo.routeInfo.clientPackageName) + val iconInfo = MediaTttUtils.getIconInfoFromPackageName( + context, newInfo.routeInfo.clientPackageName, logger + ) + MediaTttUtils.setIcon( + currentView.requireViewById(R.id.app_icon), + iconInfo.drawable, + iconInfo.contentDescription + ) // Text val otherDeviceName = newInfo.routeInfo.name.toString() @@ -127,7 +137,7 @@ class MediaTttChipControllerSender @Inject constructor( // Loading currentView.requireViewById<View>(R.id.loading).visibility = - chipState.isMidTransfer.visibleIfTrue() + (chipState.transferStatus == TransferStatus.IN_PROGRESS).visibleIfTrue() // Undo val undoView = currentView.requireViewById<View>(R.id.undo) @@ -139,12 +149,12 @@ class MediaTttChipControllerSender @Inject constructor( // Failure currentView.requireViewById<View>(R.id.failure_icon).visibility = - chipState.isTransferFailure.visibleIfTrue() + (chipState.transferStatus == TransferStatus.FAILED).visibleIfTrue() // For accessibility currentView.requireViewById<ViewGroup>( R.id.media_ttt_sender_chip_inner - ).contentDescription = "$iconName $chipText" + ).contentDescription = "${iconInfo.contentDescription} $chipText" } override fun animateViewIn(view: ViewGroup) { @@ -162,10 +172,17 @@ class MediaTttChipControllerSender @Inject constructor( } override fun removeView(removalReason: String) { - // Don't remove the chip if we're mid-transfer since the user should still be able to - // see the status of the transfer. (But do remove it if it's finally timed out.) - if (info?.state?.isMidTransfer == true && - removalReason != TemporaryDisplayRemovalReason.REASON_TIMEOUT) { + // Don't remove the chip if we're in progress or succeeded, since the user should still be + // able to see the status of the transfer. (But do remove it if it's finally timed out.) + val transferStatus = info?.state?.transferStatus + if ( + (transferStatus == TransferStatus.IN_PROGRESS || + transferStatus == TransferStatus.SUCCEEDED) && + removalReason != TemporaryDisplayRemovalReason.REASON_TIMEOUT + ) { + logger.logRemovalBypass( + removalReason, bypassReason = "transferStatus=${transferStatus.name}" + ) return } super.removeView(removalReason) diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/TransferStatus.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/TransferStatus.kt new file mode 100644 index 000000000000..f15720df5245 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/TransferStatus.kt @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.media.taptotransfer.sender + +/** Represents the different possible transfer states that we could be in. */ +enum class TransferStatus { + /** The transfer hasn't started yet. */ + NOT_STARTED, + /** The transfer is currently ongoing but hasn't completed yet. */ + IN_PROGRESS, + /** The transfer has completed successfully. */ + SUCCEEDED, + /** The transfer has completed with a failure. */ + FAILED, + /** The device is too far away to do a transfer. */ + TOO_FAR, +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java index 58426656e4bf..e9a6c25c0e6d 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java @@ -50,6 +50,7 @@ public class QSIconViewImpl extends QSIconView { protected int mIconSizePx; private boolean mAnimationEnabled = true; private int mState = -1; + private boolean mDisabledByPolicy = false; private int mTint; @Nullable private QSTile.Icon mLastIcon; @@ -159,14 +160,10 @@ public class QSIconViewImpl extends QSIconView { } protected void setIcon(ImageView iv, QSTile.State state, boolean allowAnimations) { - if (state.disabledByPolicy) { - iv.setColorFilter(getContext().getColor(R.color.qs_tile_disabled_color)); - } else { - iv.clearColorFilter(); - } - if (state.state != mState) { + if (state.state != mState || state.disabledByPolicy != mDisabledByPolicy) { int color = getColor(state); mState = state.state; + mDisabledByPolicy = state.disabledByPolicy; if (mTint != 0 && allowAnimations && shouldAnimate(iv)) { animateGrayScale(mTint, color, iv, () -> updateIcon(iv, state, allowAnimations)); } else { @@ -241,8 +238,8 @@ public class QSIconViewImpl extends QSIconView { */ private static int getIconColorForState(Context context, QSTile.State state) { if (state.disabledByPolicy || state.state == Tile.STATE_UNAVAILABLE) { - return Utils.applyAlpha(QSTileViewImpl.UNAVAILABLE_ALPHA, - Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary)); + return Utils.getColorAttrDefaultColor( + context, com.android.internal.R.attr.textColorTertiary); } else if (state.state == Tile.STATE_INACTIVE) { return Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary); } else if (state.state == Tile.STATE_ACTIVE) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt index 163ee2af600c..972b24343d10 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt @@ -100,14 +100,15 @@ open class QSTileViewImpl @JvmOverloads constructor( Utils.getColorAttrDefaultColor(context, com.android.internal.R.attr.textColorOnAccent) private val colorLabelInactive = Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary) - private val colorLabelUnavailable = Utils.applyAlpha(UNAVAILABLE_ALPHA, colorLabelInactive) + private val colorLabelUnavailable = + Utils.getColorAttrDefaultColor(context, com.android.internal.R.attr.textColorTertiary) private val colorSecondaryLabelActive = Utils.getColorAttrDefaultColor(context, android.R.attr.textColorSecondaryInverse) private val colorSecondaryLabelInactive = Utils.getColorAttrDefaultColor(context, android.R.attr.textColorSecondary) private val colorSecondaryLabelUnavailable = - Utils.applyAlpha(UNAVAILABLE_ALPHA, colorSecondaryLabelInactive) + Utils.getColorAttrDefaultColor(context, com.android.internal.R.attr.textColorTertiary) private lateinit var label: TextView protected lateinit var secondaryLabel: TextView diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java index c2a82a76d3e8..a31500c6bb18 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java @@ -49,7 +49,6 @@ import javax.inject.Inject; /** Quick settings tile: Invert colors **/ public class ColorInversionTile extends QSTileImpl<BooleanState> { - private final Icon mIcon = ResourceIcon.get(drawable.ic_invert_colors); private final SettingObserver mSetting; @Inject @@ -123,7 +122,9 @@ public class ColorInversionTile extends QSTileImpl<BooleanState> { state.value = enabled; state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE; state.label = mContext.getString(R.string.quick_settings_inversion_label); - state.icon = mIcon; + state.icon = ResourceIcon.get(state.value + ? drawable.qs_invert_colors_icon_on + : drawable.qs_invert_colors_icon_off); state.expandedAccessibilityClassName = Switch.class.getName(); state.contentDescription = state.label; } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java index 9fdf5940c392..2fc99f323611 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java @@ -128,13 +128,13 @@ public class DataSaverTile extends QSTileImpl<BooleanState> implements @Override protected void handleUpdateState(BooleanState state, Object arg) { - state.value = arg instanceof Boolean ? (Boolean) arg + state.value = arg instanceof Boolean ? ((Boolean) arg).booleanValue() : mDataSaverController.isDataSaverEnabled(); state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE; state.label = mContext.getString(R.string.data_saver); state.contentDescription = state.label; - state.icon = ResourceIcon.get(state.value ? R.drawable.ic_data_saver - : R.drawable.ic_data_saver_off); + state.icon = ResourceIcon.get(state.value ? R.drawable.qs_data_saver_icon_on + : R.drawable.qs_data_saver_icon_off); state.expandedAccessibilityClassName = Switch.class.getName(); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java index 81813db5abfd..0e9f6599522f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java @@ -144,9 +144,10 @@ public class NightDisplayTile extends QSTileImpl<BooleanState> implements protected void handleUpdateState(BooleanState state, Object arg) { state.value = mManager.isNightDisplayActivated(); state.label = mContext.getString(R.string.quick_settings_night_display_label); - state.icon = ResourceIcon.get(com.android.internal.R.drawable.ic_qs_night_display_on); state.expandedAccessibilityClassName = Switch.class.getName(); state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE; + state.icon = ResourceIcon.get(state.value ? R.drawable.qs_nightlight_icon_on + : R.drawable.qs_nightlight_icon_off); state.secondaryLabel = getSecondaryLabel(state.value); state.contentDescription = TextUtils.isEmpty(state.secondaryLabel) ? state.label diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index 3429b9d0e503..1011a6d831e6 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -33,6 +33,7 @@ import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_Q import static com.android.systemui.statusbar.StatusBarState.KEYGUARD; import static com.android.systemui.statusbar.StatusBarState.SHADE; import static com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED; +import static com.android.systemui.statusbar.VibratorHelper.TOUCH_VIBRATION_ATTRIBUTES; import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_ALL; import static com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_FOLD_TO_AOD; import static com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManagerKt.STATE_CLOSED; @@ -62,6 +63,7 @@ import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; import android.os.Bundle; import android.os.Handler; import android.os.PowerManager; +import android.os.Process; import android.os.Trace; import android.os.UserManager; import android.os.VibrationEffect; @@ -236,6 +238,9 @@ public final class NotificationPanelViewController extends PanelViewController { private static final boolean SPEW_LOGCAT = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.VERBOSE); private static final boolean DEBUG_DRAWABLE = false; + private static final VibrationEffect ADDITIONAL_TAP_REQUIRED_VIBRATION_EFFECT = + VibrationEffect.get(VibrationEffect.EFFECT_STRENGTH_MEDIUM, false); + /** * The parallax amount of the quick settings translation when dragging down the panel */ @@ -682,14 +687,22 @@ public final class NotificationPanelViewController extends PanelViewController { private final FalsingTapListener mFalsingTapListener = new FalsingTapListener() { @Override - public void onDoubleTapRequired() { + public void onAdditionalTapRequired() { if (mStatusBarStateController.getState() == StatusBarState.SHADE_LOCKED) { mTapAgainViewController.show(); } else { mKeyguardIndicationController.showTransientIndication( R.string.notification_tap_again); } - mVibratorHelper.vibrate(VibrationEffect.EFFECT_STRENGTH_MEDIUM); + + if (!mStatusBarStateController.isDozing()) { + mVibratorHelper.vibrate( + Process.myUid(), + mView.getContext().getPackageName(), + ADDITIONAL_TAP_REQUIRED_VIBRATION_EFFECT, + "falsing-additional-tap-required", + TOUCH_VIBRATION_ATTRIBUTES); + } } }; diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java index abafecce2965..8d74a09ab8b1 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java @@ -73,7 +73,7 @@ public class NotificationShadeWindowViewController { private final AmbientState mAmbientState; private final PulsingGestureListener mPulsingGestureListener; - private GestureDetector mGestureDetector; + private GestureDetector mPulsingWakeupGestureHandler; private View mBrightnessMirror; private boolean mTouchActive; private boolean mTouchCancelled; @@ -149,7 +149,8 @@ public class NotificationShadeWindowViewController { /** Inflates the {@link R.layout#status_bar_expanded} layout and sets it up. */ public void setupExpandedStatusBar() { mStackScrollLayout = mView.findViewById(R.id.notification_stack_scroller); - mGestureDetector = new GestureDetector(mView.getContext(), mPulsingGestureListener); + mPulsingWakeupGestureHandler = new GestureDetector(mView.getContext(), + mPulsingGestureListener); mView.setInteractionEventHandler(new NotificationShadeWindowView.InteractionEventHandler() { @Override @@ -196,7 +197,7 @@ public class NotificationShadeWindowViewController { } mFalsingCollector.onTouchEvent(ev); - mGestureDetector.onTouchEvent(ev); + mPulsingWakeupGestureHandler.onTouchEvent(ev); mStatusBarKeyguardViewManager.onTouch(ev); if (mBrightnessMirror != null && mBrightnessMirror.getVisibility() == View.VISIBLE) { diff --git a/packages/SystemUI/src/com/android/systemui/shade/PulsingGestureListener.kt b/packages/SystemUI/src/com/android/systemui/shade/PulsingGestureListener.kt index 9b3fe92f9225..084b7dc3a646 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/PulsingGestureListener.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/PulsingGestureListener.kt @@ -26,6 +26,7 @@ import com.android.systemui.Dumpable import com.android.systemui.dock.DockManager import com.android.systemui.dump.DumpManager import com.android.systemui.plugins.FalsingManager +import com.android.systemui.plugins.FalsingManager.LOW_PENALTY import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.statusbar.phone.CentralSurfaces import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent @@ -36,12 +37,12 @@ import javax.inject.Inject /** * If tap and/or double tap to wake is enabled, this gestureListener will wake the display on - * tap/double tap when the device is pulsing (AoD 2). Taps are gated by the proximity sensor and - * falsing manager. + * tap/double tap when the device is pulsing (AoD2) or transitioning to AoD. Taps are gated by the + * proximity sensor and falsing manager. * - * Touches go through the [NotificationShadeWindowViewController] when the device is pulsing. - * Otherwise, if the device is dozing and NOT pulsing, wake-ups are handled by - * [com.android.systemui.doze.DozeSensors]. + * Touches go through the [NotificationShadeWindowViewController] when the device is dozing but the + * screen is still ON and not in the true AoD display state. When the device is in the true AoD + * display state, wake-ups are handled by [com.android.systemui.doze.DozeSensors]. */ @CentralSurfacesComponent.CentralSurfacesScope class PulsingGestureListener @Inject constructor( @@ -75,12 +76,12 @@ class PulsingGestureListener @Inject constructor( dumpManager.registerDumpable(this) } - override fun onSingleTapConfirmed(e: MotionEvent): Boolean { - if (statusBarStateController.isPulsing && + override fun onSingleTapUp(e: MotionEvent): Boolean { + if (statusBarStateController.isDozing && singleTapEnabled && !dockManager.isDocked && !falsingManager.isProximityNear && - !falsingManager.isFalseTap(FalsingManager.MODERATE_PENALTY) + !falsingManager.isFalseTap(LOW_PENALTY) ) { centralSurfaces.wakeUpIfDozing( SystemClock.uptimeMillis(), @@ -91,8 +92,15 @@ class PulsingGestureListener @Inject constructor( return false } - override fun onDoubleTap(e: MotionEvent): Boolean { - if (statusBarStateController.isPulsing && + /** + * Receives [MotionEvent.ACTION_DOWN], [MotionEvent.ACTION_MOVE], and [MotionEvent.ACTION_UP] + * motion events for a double tap. + */ + override fun onDoubleTapEvent(e: MotionEvent): Boolean { + // React to the [MotionEvent.ACTION_UP] event after double tap is detected. Falsing + // checks MUST be on the ACTION_UP event. + if (e.actionMasked == MotionEvent.ACTION_UP && + statusBarStateController.isDozing && (doubleTapEnabled || singleTapEnabled) && !falsingManager.isProximityNear && !falsingManager.isFalseDoubleTap diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java index 408c61f8f387..e06c97756ea7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java @@ -19,9 +19,6 @@ package com.android.systemui.statusbar; import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_FINANCED; import static android.app.admin.DevicePolicyResources.Strings.SystemUi.KEYGUARD_MANAGEMENT_DISCLOSURE; import static android.app.admin.DevicePolicyResources.Strings.SystemUi.KEYGUARD_NAMED_MANAGEMENT_DISCLOSURE; -import static android.hardware.biometrics.BiometricFaceConstants.FACE_ACQUIRED_FIRST_FRAME_RECEIVED; -import static android.hardware.biometrics.BiometricFaceConstants.FACE_ACQUIRED_GOOD; -import static android.hardware.biometrics.BiometricFaceConstants.FACE_ACQUIRED_START; import static android.hardware.biometrics.BiometricFaceConstants.FACE_ACQUIRED_TOO_DARK; import static android.hardware.biometrics.BiometricSourceType.FACE; import static android.view.View.GONE; @@ -53,6 +50,7 @@ import android.content.pm.UserInfo; import android.content.res.ColorStateList; import android.content.res.Resources; import android.graphics.Color; +import android.hardware.biometrics.BiometricFaceConstants; import android.hardware.biometrics.BiometricSourceType; import android.hardware.face.FaceManager; import android.hardware.fingerprint.FingerprintManager; @@ -82,7 +80,7 @@ import com.android.keyguard.KeyguardUpdateMonitorCallback; import com.android.settingslib.Utils; import com.android.settingslib.fuelgauge.BatteryStatus; import com.android.systemui.R; -import com.android.systemui.biometrics.BiometricMessageDeferral; +import com.android.systemui.biometrics.FaceHelpMessageDeferral; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Background; @@ -184,6 +182,7 @@ public class KeyguardIndicationController { private long mChargingTimeRemaining; private String mBiometricErrorMessageToShowOnScreenOn; private final Set<Integer> mCoExFaceAcquisitionMsgIdsToShow; + private final FaceHelpMessageDeferral mFaceAcquiredMessageDeferral; private boolean mInited; private KeyguardUpdateMonitorCallback mUpdateMonitorCallback; @@ -233,7 +232,8 @@ public class KeyguardIndicationController { LockPatternUtils lockPatternUtils, ScreenLifecycle screenLifecycle, KeyguardBypassController keyguardBypassController, - AccessibilityManager accessibilityManager) { + AccessibilityManager accessibilityManager, + FaceHelpMessageDeferral faceHelpMessageDeferral) { mContext = context; mBroadcastDispatcher = broadcastDispatcher; mDevicePolicyManager = devicePolicyManager; @@ -254,6 +254,7 @@ public class KeyguardIndicationController { mScreenLifecycle = screenLifecycle; mScreenLifecycle.addObserver(mScreenObserver); + mFaceAcquiredMessageDeferral = faceHelpMessageDeferral; mCoExFaceAcquisitionMsgIdsToShow = new HashSet<>(); int[] msgIds = context.getResources().getIntArray( com.android.systemui.R.array.config_face_help_msgs_when_fingerprint_enrolled); @@ -1041,8 +1042,22 @@ public class KeyguardIndicationController { } @Override + public void onBiometricAcquired(BiometricSourceType biometricSourceType, int acquireInfo) { + if (biometricSourceType == FACE) { + mFaceAcquiredMessageDeferral.processFrame(acquireInfo); + } + } + + @Override public void onBiometricHelp(int msgId, String helpString, BiometricSourceType biometricSourceType) { + if (biometricSourceType == FACE) { + mFaceAcquiredMessageDeferral.updateMessage(msgId, helpString); + if (mFaceAcquiredMessageDeferral.shouldDefer(msgId)) { + return; + } + } + // TODO(b/141025588): refactor to reduce repetition of code/comments // Only checking if unlocking with Biometric is allowed (no matter strong or non-strong // as long as primary auth, i.e. PIN/pattern/password, is not required), so it's ok to @@ -1053,17 +1068,6 @@ public class KeyguardIndicationController { return; } - if (biometricSourceType == FACE) { - if (msgId == KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_RECOGNIZED) { - mFaceAcquiredMessageDeferral.reset(); - } else { - mFaceAcquiredMessageDeferral.processMessage(msgId, helpString); - if (mFaceAcquiredMessageDeferral.shouldDefer(msgId)) { - return; - } - } - } - final boolean faceAuthSoftError = biometricSourceType == FACE && msgId != BIOMETRIC_HELP_FACE_NOT_RECOGNIZED; final boolean faceAuthFailed = biometricSourceType == FACE @@ -1109,11 +1113,23 @@ public class KeyguardIndicationController { } @Override + public void onBiometricAuthFailed(BiometricSourceType biometricSourceType) { + if (biometricSourceType == FACE) { + mFaceAcquiredMessageDeferral.reset(); + } + } + + @Override public void onBiometricError(int msgId, String errString, BiometricSourceType biometricSourceType) { CharSequence deferredFaceMessage = null; if (biometricSourceType == FACE) { - deferredFaceMessage = mFaceAcquiredMessageDeferral.getDeferredMessage(); + if (msgId == BiometricFaceConstants.FACE_ERROR_TIMEOUT) { + deferredFaceMessage = mFaceAcquiredMessageDeferral.getDeferredMessage(); + if (DEBUG) { + Log.d(TAG, "showDeferredFaceMessage msgId=" + deferredFaceMessage); + } + } mFaceAcquiredMessageDeferral.reset(); } @@ -1308,14 +1324,4 @@ public class KeyguardIndicationController { } } }; - - private final BiometricMessageDeferral mFaceAcquiredMessageDeferral = - new BiometricMessageDeferral( - Set.of( - FACE_ACQUIRED_GOOD, - FACE_ACQUIRED_START, - FACE_ACQUIRED_FIRST_FRAME_RECEIVED - ), - Set.of(FACE_ACQUIRED_TOO_DARK) - ); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java index c74621df94c9..258f9fc5449f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java @@ -38,7 +38,7 @@ import javax.inject.Inject; public class VibratorHelper { private final Vibrator mVibrator; - private static final VibrationAttributes TOUCH_VIBRATION_ATTRIBUTES = + public static final VibrationAttributes TOUCH_VIBRATION_ATTRIBUTES = VibrationAttributes.createForUsage(VibrationAttributes.USAGE_TOUCH); private final Executor mExecutor; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt index 31b21c9b5321..553826dda919 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt @@ -136,7 +136,7 @@ class NotificationLaunchAnimatorController( headsUpManager.removeNotification(notificationKey, true /* releaseImmediately */, animate) } - override fun onLaunchAnimationCancelled() { + override fun onLaunchAnimationCancelled(newKeyguardOccludedState: Boolean?) { // TODO(b/184121838): Should we call InteractionJankMonitor.cancel if the animation started // here? notificationShadeWindowViewController.setExpandAnimationRunning(false) 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 820f6e4ff718..0b63bbfec877 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -1737,13 +1737,18 @@ public class CentralSurfacesImpl extends CoreStartable implements } @Override - public void onLaunchAnimationCancelled() { + public void onLaunchAnimationCancelled(@Nullable Boolean newKeyguardOccludedState) { + if (newKeyguardOccludedState != null) { + mKeyguardViewMediator.setOccluded( + newKeyguardOccludedState, false /* animate */); + } + // Set mIsLaunchingActivityOverLockscreen to false before actually finishing the // animation so that we can assume that mIsLaunchingActivityOverLockscreen // being true means that we will collapse the shade (or at least run the // post collapse runnables) later on. CentralSurfacesImpl.this.mIsLaunchingActivityOverLockscreen = false; - getDelegate().onLaunchAnimationCancelled(); + getDelegate().onLaunchAnimationCancelled(newKeyguardOccludedState); } }; } else if (dismissShade) { @@ -3623,6 +3628,7 @@ public class CentralSurfacesImpl extends CoreStartable implements dismissVolumeDialog(); mWakeUpCoordinator.setFullyAwake(false); mKeyguardBypassController.onStartedGoingToSleep(); + mStatusBarTouchableRegionManager.updateTouchableRegion(); // The unlocked screen off and fold to aod animations might use our LightRevealScrim - // we need to be expanded for it to be visible. @@ -3651,6 +3657,7 @@ public class CentralSurfacesImpl extends CoreStartable implements // once we fully woke up. updateRevealEffect(true /* wakingUp */); updateNotificationPanelTouchState(); + mStatusBarTouchableRegionManager.updateTouchableRegion(); // If we are waking up during the screen off animation, we should undo making the // expanded visible (we did that so the LightRevealScrim would be visible). diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt index c0922163903f..ee948c04df38 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt @@ -51,7 +51,7 @@ class StatusBarLaunchAnimatorController( centralSurfaces.notificationPanelViewController.applyLaunchAnimationProgress(linearProgress) } - override fun onLaunchAnimationCancelled() { + override fun onLaunchAnimationCancelled(newKeyguardOccludedState: Boolean?) { delegate.onLaunchAnimationCancelled() centralSurfaces.notificationPanelViewController.setIsLaunchAnimationRunning(false) centralSurfaces.onLaunchAnimationCancelled(isLaunchForActivity) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java index 75dac1a093dd..d9c0293fe13d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java @@ -55,6 +55,7 @@ public final class StatusBarTouchableRegionManager implements Dumpable { private final Context mContext; private final HeadsUpManagerPhone mHeadsUpManager; private final NotificationShadeWindowController mNotificationShadeWindowController; + private final UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController; private boolean mIsStatusBarExpanded = false; private boolean mShouldAdjustInsets = false; @@ -72,7 +73,8 @@ public final class StatusBarTouchableRegionManager implements Dumpable { Context context, NotificationShadeWindowController notificationShadeWindowController, ConfigurationController configurationController, - HeadsUpManagerPhone headsUpManager + HeadsUpManagerPhone headsUpManager, + UnlockedScreenOffAnimationController unlockedScreenOffAnimationController ) { mContext = context; initResources(); @@ -115,6 +117,8 @@ public final class StatusBarTouchableRegionManager implements Dumpable { mNotificationShadeWindowController.setForcePluginOpenListener((forceOpen) -> { updateTouchableRegion(); }); + + mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController; } protected void setup( @@ -179,7 +183,7 @@ public final class StatusBarTouchableRegionManager implements Dumpable { /** * Set the touchable portion of the status bar based on what elements are visible. */ - private void updateTouchableRegion() { + public void updateTouchableRegion() { boolean hasCutoutInset = (mNotificationShadeWindowView != null) && (mNotificationShadeWindowView.getRootWindowInsets() != null) && (mNotificationShadeWindowView.getRootWindowInsets().getDisplayCutout() != null); @@ -242,12 +246,25 @@ public final class StatusBarTouchableRegionManager implements Dumpable { touchableRegion.union(bounds); } + /** + * Helper to let us know when calculating the region is not needed because we know the entire + * screen needs to be touchable. + */ + private boolean shouldMakeEntireScreenTouchable() { + // The touchable region is always the full area when expanded, whether we're showing the + // shade or the bouncer. It's also fully touchable when the screen off animation is playing + // since we don't want stray touches to go through the light reveal scrim to whatever is + // underneath. + return mIsStatusBarExpanded + || mCentralSurfaces.isBouncerShowing() + || mUnlockedScreenOffAnimationController.isAnimationPlaying(); + } + private final OnComputeInternalInsetsListener mOnComputeInternalInsetsListener = new OnComputeInternalInsetsListener() { @Override public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo info) { - if (mIsStatusBarExpanded || mCentralSurfaces.isBouncerShowing()) { - // The touchable region is always the full area when expanded + if (shouldMakeEntireScreenTouchable()) { return; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessor.kt deleted file mode 100644 index fe846747d0c6..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessor.kt +++ /dev/null @@ -1,55 +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.statusbar.pipeline - -import android.content.Context -import com.android.systemui.CoreStartable -import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.dagger.qualifiers.Application -import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel -import javax.inject.Inject -import javax.inject.Provider -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.collect -import kotlinx.coroutines.launch - -/** - * A temporary object that collects on [WifiViewModel] flows for debugging purposes. - * - * This will eventually get migrated to a view binder that will use the flow outputs to set state on - * views. For now, this just collects on flows so that the information gets logged. - */ -@SysUISingleton -class ConnectivityInfoProcessor @Inject constructor( - context: Context, - // TODO(b/238425913): Don't use the application scope; instead, use the status bar view's - // scope so we only do work when there's UI that cares about it. - @Application private val scope: CoroutineScope, - private val statusBarPipelineFlags: StatusBarPipelineFlags, - private val wifiViewModelProvider: Provider<WifiViewModel>, -) : CoreStartable(context) { - override fun start() { - if (!statusBarPipelineFlags.isNewPipelineBackendEnabled()) { - return - } - // TODO(b/238425913): The view binder should do this instead. For now, do it here so we can - // see the logs. - scope.launch { - wifiViewModelProvider.get().isActivityInVisible.collect { } - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt index 681dc6fc13a2..9a7c3fae780c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt @@ -16,25 +16,15 @@ package com.android.systemui.statusbar.pipeline.dagger -import com.android.systemui.CoreStartable -import com.android.systemui.statusbar.pipeline.ConnectivityInfoProcessor import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryImpl import dagger.Binds import dagger.Module -import dagger.multibindings.ClassKey -import dagger.multibindings.IntoMap @Module abstract class StatusBarPipelineModule { - /** Inject into ConnectivityInfoProcessor. */ - @Binds - @IntoMap - @ClassKey(ConnectivityInfoProcessor::class) - abstract fun bindConnectivityInfoProcessor(cip: ConnectivityInfoProcessor): CoreStartable - @Binds abstract fun connectivityRepository(impl: ConnectivityRepositoryImpl): ConnectivityRepository diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java index e7fa6d239012..aae0f93a0e19 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java @@ -27,7 +27,6 @@ import android.os.Looper; import android.os.Message; import android.os.UserHandle; import android.os.UserManager; -import android.util.Log; import androidx.annotation.NonNull; @@ -37,6 +36,7 @@ import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.bluetooth.LocalBluetoothProfile; import com.android.settingslib.bluetooth.LocalBluetoothProfileManager; +import com.android.systemui.bluetooth.BluetoothLogger; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; @@ -57,9 +57,9 @@ import javax.inject.Inject; public class BluetoothControllerImpl implements BluetoothController, BluetoothCallback, CachedBluetoothDevice.Callback, LocalBluetoothProfileManager.ServiceListener { private static final String TAG = "BluetoothController"; - private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private final DumpManager mDumpManager; + private final BluetoothLogger mLogger; private final LocalBluetoothManager mLocalBluetoothManager; private final UserManager mUserManager; private final int mCurrentUser; @@ -70,6 +70,7 @@ public class BluetoothControllerImpl implements BluetoothController, BluetoothCa private final List<CachedBluetoothDevice> mConnectedDevices = new ArrayList<>(); private boolean mEnabled; + @ConnectionState private int mConnectionState = BluetoothAdapter.STATE_DISCONNECTED; private boolean mAudioProfileOnly; private boolean mIsActive; @@ -83,10 +84,12 @@ public class BluetoothControllerImpl implements BluetoothController, BluetoothCa public BluetoothControllerImpl( Context context, DumpManager dumpManager, + BluetoothLogger logger, @Background Looper bgLooper, @Main Looper mainLooper, @Nullable LocalBluetoothManager localBluetoothManager) { mDumpManager = dumpManager; + mLogger = logger; mLocalBluetoothManager = localBluetoothManager; mBgHandler = new Handler(bgLooper); mHandler = new H(mainLooper); @@ -116,7 +119,7 @@ public class BluetoothControllerImpl implements BluetoothController, BluetoothCa return; } pw.print(" mEnabled="); pw.println(mEnabled); - pw.print(" mConnectionState="); pw.println(stateToString(mConnectionState)); + pw.print(" mConnectionState="); pw.println(connectionStateToString(mConnectionState)); pw.print(" mAudioProfileOnly="); pw.println(mAudioProfileOnly); pw.print(" mIsActive="); pw.println(mIsActive); pw.print(" mConnectedDevices="); pw.println(getConnectedDevices()); @@ -127,7 +130,7 @@ public class BluetoothControllerImpl implements BluetoothController, BluetoothCa } } - private static String stateToString(int state) { + private static String connectionStateToString(@ConnectionState int state) { switch (state) { case BluetoothAdapter.STATE_CONNECTED: return "CONNECTED"; @@ -320,8 +323,8 @@ public class BluetoothControllerImpl implements BluetoothController, BluetoothCa } @Override - public void onBluetoothStateChanged(int bluetoothState) { - if (DEBUG) Log.d(TAG, "BluetoothStateChanged=" + stateToString(bluetoothState)); + public void onBluetoothStateChanged(@AdapterState int bluetoothState) { + mLogger.logStateChange(BluetoothAdapter.nameForState(bluetoothState)); mEnabled = bluetoothState == BluetoothAdapter.STATE_ON || bluetoothState == BluetoothAdapter.STATE_TURNING_ON; mState = bluetoothState; @@ -330,24 +333,25 @@ public class BluetoothControllerImpl implements BluetoothController, BluetoothCa } @Override - public void onDeviceAdded(CachedBluetoothDevice cachedDevice) { - if (DEBUG) Log.d(TAG, "DeviceAdded=" + cachedDevice.getAddress()); + public void onDeviceAdded(@NonNull CachedBluetoothDevice cachedDevice) { + mLogger.logDeviceAdded(cachedDevice.getAddress()); cachedDevice.registerCallback(this); updateConnected(); mHandler.sendEmptyMessage(H.MSG_PAIRED_DEVICES_CHANGED); } @Override - public void onDeviceDeleted(CachedBluetoothDevice cachedDevice) { - if (DEBUG) Log.d(TAG, "DeviceDeleted=" + cachedDevice.getAddress()); + public void onDeviceDeleted(@NonNull CachedBluetoothDevice cachedDevice) { + mLogger.logDeviceDeleted(cachedDevice.getAddress()); mCachedState.remove(cachedDevice); updateConnected(); mHandler.sendEmptyMessage(H.MSG_PAIRED_DEVICES_CHANGED); } @Override - public void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState) { - if (DEBUG) Log.d(TAG, "DeviceBondStateChanged=" + cachedDevice.getAddress()); + public void onDeviceBondStateChanged( + @NonNull CachedBluetoothDevice cachedDevice, int bondState) { + mLogger.logBondStateChange(cachedDevice.getAddress(), bondState); mCachedState.remove(cachedDevice); updateConnected(); mHandler.sendEmptyMessage(H.MSG_PAIRED_DEVICES_CHANGED); @@ -355,50 +359,47 @@ public class BluetoothControllerImpl implements BluetoothController, BluetoothCa @Override public void onDeviceAttributesChanged() { - if (DEBUG) Log.d(TAG, "DeviceAttributesChanged"); + mLogger.logDeviceAttributesChanged(); updateConnected(); mHandler.sendEmptyMessage(H.MSG_PAIRED_DEVICES_CHANGED); } @Override - public void onConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state) { - if (DEBUG) { - Log.d(TAG, "ConnectionStateChanged=" + cachedDevice.getAddress() + " " - + stateToString(state)); - } + public void onConnectionStateChanged( + @Nullable CachedBluetoothDevice cachedDevice, + @ConnectionState int state) { + mLogger.logDeviceConnectionStateChanged( + getAddressOrNull(cachedDevice), connectionStateToString(state)); mCachedState.remove(cachedDevice); updateConnected(); mHandler.sendEmptyMessage(H.MSG_STATE_CHANGED); } @Override - public void onProfileConnectionStateChanged(CachedBluetoothDevice cachedDevice, - int state, int bluetoothProfile) { - if (DEBUG) { - Log.d(TAG, "ProfileConnectionStateChanged=" + cachedDevice.getAddress() + " " - + stateToString(state) + " profileId=" + bluetoothProfile); - } + public void onProfileConnectionStateChanged( + @NonNull CachedBluetoothDevice cachedDevice, + @ConnectionState int state, + int bluetoothProfile) { + mLogger.logProfileConnectionStateChanged( + cachedDevice.getAddress(), connectionStateToString(state), bluetoothProfile); mCachedState.remove(cachedDevice); updateConnected(); mHandler.sendEmptyMessage(H.MSG_STATE_CHANGED); } @Override - public void onActiveDeviceChanged(CachedBluetoothDevice activeDevice, int bluetoothProfile) { - if (DEBUG) { - Log.d(TAG, "ActiveDeviceChanged=" + activeDevice.getAddress() - + " profileId=" + bluetoothProfile); - } + public void onActiveDeviceChanged( + @Nullable CachedBluetoothDevice activeDevice, int bluetoothProfile) { + mLogger.logActiveDeviceChanged(getAddressOrNull(activeDevice), bluetoothProfile); updateActive(); mHandler.sendEmptyMessage(H.MSG_STATE_CHANGED); } @Override - public void onAclConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state) { - if (DEBUG) { - Log.d(TAG, "ACLConnectionStateChanged=" + cachedDevice.getAddress() + " " - + stateToString(state)); - } + public void onAclConnectionStateChanged( + @NonNull CachedBluetoothDevice cachedDevice, int state) { + mLogger.logAclConnectionStateChanged( + cachedDevice.getAddress(), connectionStateToString(state)); mCachedState.remove(cachedDevice); updateConnected(); mHandler.sendEmptyMessage(H.MSG_STATE_CHANGED); @@ -415,6 +416,11 @@ public class BluetoothControllerImpl implements BluetoothController, BluetoothCa return state; } + @Nullable + private String getAddressOrNull(@Nullable CachedBluetoothDevice device) { + return device == null ? null : device.getAddress(); + } + @Override public void onServiceConnected() { updateConnected(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java index 62bda2c76e9f..1d5b88e7dee0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java @@ -1122,11 +1122,19 @@ public class UserSwitcherController implements Dumpable { } public String getName(Context context, UserRecord item) { + return getName(context, item, false); + } + + /** + * Returns the name for the given {@link UserRecord}. + */ + public String getName(Context context, UserRecord item, boolean isTablet) { return LegacyUserUiHelper.getUserRecordName( context, item, mController.isGuestUserAutoCreated(), - mController.isGuestUserResetting()); + mController.isGuestUserResetting(), + isTablet); } protected static ColorFilter getDisabledUserAvatarColorFilter() { @@ -1136,8 +1144,12 @@ public class UserSwitcherController implements Dumpable { } protected static Drawable getIconDrawable(Context context, UserRecord item) { + return getIconDrawable(context, item, false); + } + protected static Drawable getIconDrawable(Context context, UserRecord item, + boolean isTablet) { int iconRes = LegacyUserUiHelper.getUserSwitcherActionIconResourceId( - item.isAddUser, item.isGuest, item.isAddSupervisedUser); + item.isAddUser, item.isGuest, item.isAddSupervisedUser, isTablet); return context.getDrawable(iconRes); } diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt index 734eeecec215..a52e2aff52c1 100644 --- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt +++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt @@ -19,12 +19,10 @@ package com.android.systemui.temporarydisplay import android.annotation.LayoutRes import android.annotation.SuppressLint import android.content.Context -import android.content.pm.PackageManager import android.graphics.PixelFormat import android.graphics.drawable.Drawable import android.os.PowerManager import android.os.SystemClock -import android.util.Log import android.view.LayoutInflater import android.view.ViewGroup import android.view.WindowManager @@ -33,11 +31,7 @@ import android.view.accessibility.AccessibilityManager.FLAG_CONTENT_CONTROLS import android.view.accessibility.AccessibilityManager.FLAG_CONTENT_ICONS import android.view.accessibility.AccessibilityManager.FLAG_CONTENT_TEXT import androidx.annotation.CallSuper -import com.android.internal.widget.CachingIconView -import com.android.settingslib.Utils -import com.android.systemui.R import com.android.systemui.dagger.qualifiers.Main -import com.android.systemui.media.taptotransfer.common.MediaTttLogger import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.util.concurrency.DelayableExecutor @@ -50,17 +44,22 @@ import com.android.systemui.util.concurrency.DelayableExecutor * The generic type T is expected to contain all the information necessary for the subclasses to * display the view in a certain state, since they receive <T> in [updateView]. * - * TODO(b/245610654): Remove all the media-specific logic from this class. + * @property windowTitle the title to use for the window that displays the temporary view. Should be + * normally cased, like "Window Title". + * @property wakeReason a string used for logging if we needed to wake the screen in order to + * display the temporary view. Should be screaming snake cased, like WAKE_REASON. */ -abstract class TemporaryViewDisplayController<T : TemporaryViewInfo>( +abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : TemporaryViewLogger>( internal val context: Context, - internal val logger: MediaTttLogger, + internal val logger: U, internal val windowManager: WindowManager, @Main private val mainExecutor: DelayableExecutor, private val accessibilityManager: AccessibilityManager, private val configurationController: ConfigurationController, private val powerManager: PowerManager, @LayoutRes private val viewLayoutRes: Int, + private val windowTitle: String, + private val wakeReason: String, ) { /** * Window layout params that will be used as a starting point for the [windowLayoutParams] of @@ -72,7 +71,7 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo>( height = WindowManager.LayoutParams.WRAP_CONTENT type = WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL - title = WINDOW_TITLE + title = windowTitle format = PixelFormat.TRANSLUCENT setTrustedOverlay() } @@ -115,10 +114,10 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo>( powerManager.wakeUp( SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_APPLICATION, - "com.android.systemui:media_tap_to_transfer_activated" + "com.android.systemui:$wakeReason", ) } - + logger.logChipAddition() inflateAndUpdateView(newInfo) } @@ -192,80 +191,8 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo>( * appears. */ open fun animateViewIn(view: ViewGroup) {} - - /** - * Returns the size that the icon should be, or null if no size override is needed. - */ - open fun getIconSize(isAppIcon: Boolean): Int? = null - - /** - * An internal method to set the icon on the view. - * - * This is in the common superclass since both the sender and the receiver show an icon. - * - * @param appPackageName the package name of the app playing the media. Will be used to fetch - * the app icon and app name if overrides aren't provided. - * - * @return the content description of the icon. - */ - internal fun setIcon( - currentView: ViewGroup, - appPackageName: String?, - appIconDrawableOverride: Drawable? = null, - appNameOverride: CharSequence? = null, - ): CharSequence { - val appIconView = currentView.requireViewById<CachingIconView>(R.id.app_icon) - val iconInfo = getIconInfo(appPackageName) - - getIconSize(iconInfo.isAppIcon)?.let { size -> - val lp = appIconView.layoutParams - lp.width = size - lp.height = size - appIconView.layoutParams = lp - } - - appIconView.contentDescription = appNameOverride ?: iconInfo.iconName - appIconView.setImageDrawable(appIconDrawableOverride ?: iconInfo.icon) - return appIconView.contentDescription - } - - /** - * Returns the information needed to display the icon. - * - * The information will either contain app name and icon of the app playing media, or a default - * name and icon if we can't find the app name/icon. - */ - private fun getIconInfo(appPackageName: String?): IconInfo { - if (appPackageName != null) { - try { - return IconInfo( - iconName = context.packageManager.getApplicationInfo( - appPackageName, PackageManager.ApplicationInfoFlags.of(0) - ).loadLabel(context.packageManager).toString(), - icon = context.packageManager.getApplicationIcon(appPackageName), - isAppIcon = true - ) - } catch (e: PackageManager.NameNotFoundException) { - Log.w(TAG, "Cannot find package $appPackageName", e) - } - } - return IconInfo( - iconName = context.getString(R.string.media_output_dialog_unknown_launch_app_name), - icon = context.resources.getDrawable(R.drawable.ic_cast).apply { - this.setTint( - Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary) - ) - }, - isAppIcon = false - ) - } } -// Used in CTS tests UpdateMediaTapToTransferSenderDisplayTest and -// UpdateMediaTapToTransferReceiverDisplayTest -private const val WINDOW_TITLE = "Media Transfer Chip View" -private val TAG = TemporaryViewDisplayController::class.simpleName!! - object TemporaryDisplayRemovalReason { const val REASON_TIMEOUT = "TIMEOUT" const val REASON_SCREEN_TAP = "SCREEN_TAP" diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt new file mode 100644 index 000000000000..606a11a84686 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.temporarydisplay + +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.LogLevel + +/** A logger for temporary view changes -- see [TemporaryViewDisplayController]. */ +open class TemporaryViewLogger( + internal val buffer: LogBuffer, + internal val tag: String, +) { + /** Logs that we added the chip to a new window. */ + fun logChipAddition() { + buffer.log(tag, LogLevel.DEBUG, {}, { "Chip added" }) + } + + /** Logs that we removed the chip for the given [reason]. */ + fun logChipRemoval(reason: String) { + buffer.log(tag, LogLevel.DEBUG, { str1 = reason }, { "Chip removed due to $str1" }) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt index d43f739f9e71..5e2dde6be046 100644 --- a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt @@ -173,8 +173,8 @@ open class UserSwitcherActivity @Inject constructor( this, R.layout.user_switcher_fullscreen_popup_item, layoutInflater, - { item: UserRecord -> adapter.getName(this@UserSwitcherActivity, item) }, - { item: UserRecord -> adapter.findUserIcon(item).mutate().apply { + { 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() @@ -322,6 +322,9 @@ open class UserSwitcherActivity @Inject constructor( return flags.isEnabled(Flags.MODERN_USER_SWITCHER_ACTIVITY) } + /** + * Provides views to populate the option menu. + */ private class ItemAdapter( val parentContext: Context, val resource: Int, @@ -375,20 +378,20 @@ open class UserSwitcherActivity @Inject constructor( return view } - override fun getName(context: Context, item: UserRecord): String { + override fun getName(context: Context, item: UserRecord, isTablet: Boolean): String { return if (item == manageUserRecord) { getString(R.string.manage_users) } else { - super.getName(context, item) + super.getName(context, item, isTablet) } } - fun findUserIcon(item: UserRecord): Drawable { + 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) + return getIconDrawable(this@UserSwitcherActivity, item, isTablet) } val userIcon = userManager.getUserIcon(item.info.id) if (userIcon != null) { 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 18369d9a71d2..15fdc352d864 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 @@ -49,8 +49,11 @@ object LegacyUserUiHelper { isAddUser: Boolean, isGuest: Boolean, isAddSupervisedUser: Boolean, + isTablet: Boolean = false, ): Int { - return if (isAddUser) { + return if (isAddUser && isTablet) { + R.drawable.ic_account_circle_filled + } else if (isAddUser) { R.drawable.ic_add } else if (isGuest) { R.drawable.ic_account_circle @@ -67,6 +70,7 @@ object LegacyUserUiHelper { record: UserRecord, isGuestUserAutoCreated: Boolean, isGuestUserResetting: Boolean, + isTablet: Boolean = false, ): String { val resourceId: Int? = getGuestUserRecordNameResourceId(record) return when { @@ -80,6 +84,7 @@ object LegacyUserUiHelper { isGuestUserResetting = isGuestUserResetting, isAddUser = record.isAddUser, isAddSupervisedUser = record.isAddSupervisedUser, + isTablet = isTablet, ) ) } @@ -108,12 +113,14 @@ object LegacyUserUiHelper { isGuestUserResetting: Boolean, isAddUser: Boolean, isAddSupervisedUser: Boolean, + isTablet: Boolean = false, ): Int { check(isGuest || isAddUser || isAddSupervisedUser) return when { isGuest && isGuestUserAutoCreated && isGuestUserResetting -> com.android.settingslib.R.string.guest_resetting + isGuest && isTablet -> com.android.settingslib.R.string.guest_new_guest isGuest && isGuestUserAutoCreated -> com.android.internal.R.string.guest_name isGuest -> com.android.internal.R.string.guest_name isAddUser -> com.android.settingslib.R.string.user_add_user diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/binder/UserSwitcherViewBinder.kt b/packages/SystemUI/src/com/android/systemui/user/ui/binder/UserSwitcherViewBinder.kt index 83a3d0d0457a..d7ad3cefaf06 100644 --- a/packages/SystemUI/src/com/android/systemui/user/ui/binder/UserSwitcherViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/user/ui/binder/UserSwitcherViewBinder.kt @@ -129,7 +129,10 @@ object UserSwitcherViewBinder { viewModel.users.collect { users -> val viewPool = view.children.filter { it.tag == USER_VIEW_TAG }.toMutableList() - viewPool.forEach { view.removeView(it) } + viewPool.forEach { + view.removeView(it) + flowWidget.removeView(it) + } users.forEach { userViewModel -> val userView = if (viewPool.isNotEmpty()) { 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 66ce01b7a86e..398341d256d2 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 @@ -20,6 +20,7 @@ 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.power.domain.interactor.PowerInteractor import com.android.systemui.user.domain.interactor.UserInteractor import com.android.systemui.user.legacyhelper.ui.LegacyUserUiHelper @@ -130,7 +131,7 @@ private constructor( return UserViewModel( viewKey = model.id, name = model.name, - image = model.image, + image = CircularDrawable(model.image), isSelectionMarkerVisible = model.isSelected, alpha = if (model.isSelectable) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt index e2790e47fe06..a61cd23b60fc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt @@ -161,7 +161,18 @@ class ActivityLaunchAnimatorTest : SysuiTestCase() { runner.onAnimationStart(0, emptyArray(), emptyArray(), emptyArray(), iCallback) waitForIdleSync() - verify(controller).onLaunchAnimationCancelled() + verify(controller).onLaunchAnimationCancelled(false /* newKeyguardOccludedState */) + verify(controller, never()).onLaunchAnimationStart(anyBoolean()) + } + + @Test + fun passesOccludedStateToLaunchAnimationCancelled_ifTrue() { + val runner = activityLaunchAnimator.createRunner(controller) + runner.onAnimationCancelled(true /* isKeyguardOccluded */) + runner.onAnimationStart(0, emptyArray(), emptyArray(), emptyArray(), iCallback) + + waitForIdleSync() + verify(controller).onLaunchAnimationCancelled(true /* newKeyguardOccludedState */) verify(controller, never()).onLaunchAnimationStart(anyBoolean()) } @@ -253,7 +264,7 @@ private class TestLaunchAnimatorController(override var launchContainer: ViewGro assertOnMainThread() } - override fun onLaunchAnimationCancelled() { + override fun onLaunchAnimationCancelled(newKeyguardOccludedState: Boolean?) { assertOnMainThread() } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricMessageDeferralTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricMessageDeferralTest.kt deleted file mode 100644 index 419fedf99c15..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricMessageDeferralTest.kt +++ /dev/null @@ -1,147 +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.biometrics - -import android.testing.AndroidTestingRunner -import androidx.test.filters.SmallTest -import com.android.systemui.SysuiTestCase -import org.junit.Assert.assertEquals -import org.junit.Assert.assertFalse -import org.junit.Assert.assertNull -import org.junit.Assert.assertTrue -import org.junit.Test -import org.junit.runner.RunWith - -@SmallTest -@RunWith(AndroidTestingRunner::class) -class BiometricMessageDeferralTest : SysuiTestCase() { - - @Test - fun testProcessNoMessages_noDeferredMessage() { - val biometricMessageDeferral = BiometricMessageDeferral(setOf(), setOf()) - - assertNull(biometricMessageDeferral.getDeferredMessage()) - } - - @Test - fun testProcessNonDeferredMessages_noDeferredMessage() { - val biometricMessageDeferral = BiometricMessageDeferral(setOf(), setOf(1, 2)) - - // WHEN there are no deferred messages processed - for (i in 0..3) { - biometricMessageDeferral.processMessage(4, "test") - } - - // THEN getDeferredMessage is null - assertNull(biometricMessageDeferral.getDeferredMessage()) - } - - @Test - fun testAllProcessedMessagesWereDeferred() { - val biometricMessageDeferral = BiometricMessageDeferral(setOf(), setOf(1)) - - // WHEN all the processed messages are a deferred message - for (i in 0..3) { - biometricMessageDeferral.processMessage(1, "test") - } - - // THEN deferredMessage will return the string associated with the deferred msgId - assertEquals("test", biometricMessageDeferral.getDeferredMessage()) - } - - @Test - fun testReturnsMostFrequentDeferredMessage() { - val biometricMessageDeferral = BiometricMessageDeferral(setOf(), setOf(1, 2)) - - // WHEN there's two msgId=1 processed and one msgId=2 processed - biometricMessageDeferral.processMessage(1, "msgId-1") - biometricMessageDeferral.processMessage(1, "msgId-1") - biometricMessageDeferral.processMessage(1, "msgId-1") - biometricMessageDeferral.processMessage(2, "msgId-2") - - // THEN the most frequent deferred message is that meets the threshold is returned - assertEquals("msgId-1", biometricMessageDeferral.getDeferredMessage()) - } - - @Test - fun testDeferredMessage_mustMeetThreshold() { - val biometricMessageDeferral = BiometricMessageDeferral(setOf(), setOf(1)) - - // WHEN more nonDeferredMessages are shown than the deferred message - val totalMessages = 10 - val nonDeferredMessagesCount = - (totalMessages * BiometricMessageDeferral.THRESHOLD).toInt() + 1 - for (i in 0 until nonDeferredMessagesCount) { - biometricMessageDeferral.processMessage(4, "non-deferred-msg") - } - for (i in nonDeferredMessagesCount until totalMessages) { - biometricMessageDeferral.processMessage(1, "msgId-1") - } - - // THEN there's no deferred message because it didn't meet the threshold - assertNull(biometricMessageDeferral.getDeferredMessage()) - } - - @Test - fun testDeferredMessage_manyExcludedMessages_getDeferredMessage() { - val biometricMessageDeferral = BiometricMessageDeferral(setOf(3), setOf(1)) - - // WHEN more excludedMessages are shown than the deferred message - val totalMessages = 10 - val excludedMessagesCount = (totalMessages * BiometricMessageDeferral.THRESHOLD).toInt() + 1 - for (i in 0 until excludedMessagesCount) { - biometricMessageDeferral.processMessage(3, "excluded-msg") - } - for (i in excludedMessagesCount until totalMessages) { - biometricMessageDeferral.processMessage(1, "msgId-1") - } - - // THEN there IS a deferred message because the deferred msg meets the threshold amongst the - // non-excluded messages - assertEquals("msgId-1", biometricMessageDeferral.getDeferredMessage()) - } - - @Test - fun testResetClearsOutCounts() { - val biometricMessageDeferral = BiometricMessageDeferral(setOf(), setOf(1, 2)) - - // GIVEN two msgId=1 events processed - biometricMessageDeferral.processMessage(1, "msgId-1") - biometricMessageDeferral.processMessage(1, "msgId-1") - - // WHEN counts are reset and then a single deferred message is processed (msgId=2) - biometricMessageDeferral.reset() - biometricMessageDeferral.processMessage(2, "msgId-2") - - // THEN msgId-2 is the deferred message since the two msgId=1 events were reset - assertEquals("msgId-2", biometricMessageDeferral.getDeferredMessage()) - } - - @Test - fun testShouldDefer() { - // GIVEN should defer msgIds 1 and 2 - val biometricMessageDeferral = BiometricMessageDeferral(setOf(3), setOf(1, 2)) - - // THEN shouldDefer returns true for ids 1 & 2 - assertTrue(biometricMessageDeferral.shouldDefer(1)) - assertTrue(biometricMessageDeferral.shouldDefer(2)) - - // THEN should defer returns false for ids 3 & 4 - assertFalse(biometricMessageDeferral.shouldDefer(3)) - assertFalse(biometricMessageDeferral.shouldDefer(4)) - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/FaceHelpMessageDeferralTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/FaceHelpMessageDeferralTest.kt new file mode 100644 index 000000000000..c9ccdb36da89 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/FaceHelpMessageDeferralTest.kt @@ -0,0 +1,188 @@ +/* + * 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.biometrics + +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.keyguard.logging.BiometricMessageDeferralLogger +import com.android.systemui.SysuiTestCase +import com.android.systemui.dump.DumpManager +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertNull +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(AndroidTestingRunner::class) +class FaceHelpMessageDeferralTest : SysuiTestCase() { + val threshold = .75f + @Mock lateinit var logger: BiometricMessageDeferralLogger + @Mock lateinit var dumpManager: DumpManager + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + } + + @Test + fun testProcessFrame_logs() { + val biometricMessageDeferral = createMsgDeferral(setOf(1)) + biometricMessageDeferral.processFrame(1) + verify(logger).logFrameProcessed(1, 1, "1") + } + + @Test + fun testUpdateMessage_logs() { + val biometricMessageDeferral = createMsgDeferral(setOf(1)) + biometricMessageDeferral.updateMessage(1, "hi") + verify(logger).logUpdateMessage(1, "hi") + } + + @Test + fun testReset_logs() { + val biometricMessageDeferral = createMsgDeferral(setOf(1)) + biometricMessageDeferral.reset() + verify(logger).reset() + } + + @Test + fun testProcessNoMessages_noDeferredMessage() { + val biometricMessageDeferral = createMsgDeferral(emptySet()) + + assertNull(biometricMessageDeferral.getDeferredMessage()) + } + + @Test + fun testProcessNonDeferredMessages_noDeferredMessage() { + val biometricMessageDeferral = createMsgDeferral(setOf(1, 2)) + + // WHEN there are no deferred messages processed + for (i in 0..3) { + biometricMessageDeferral.processFrame(4) + biometricMessageDeferral.updateMessage(4, "test") + } + + // THEN getDeferredMessage is null + assertNull(biometricMessageDeferral.getDeferredMessage()) + } + + @Test + fun testProcessMessagesWithDeferredMessage_deferredMessageWasNeverGivenAString() { + val biometricMessageDeferral = createMsgDeferral(setOf(1, 2)) + + biometricMessageDeferral.processFrame(1) + + assertNull(biometricMessageDeferral.getDeferredMessage()) + } + + @Test + fun testAllProcessedMessagesWereDeferred() { + val biometricMessageDeferral = createMsgDeferral(setOf(1)) + + // WHEN all the processed messages are a deferred message + for (i in 0..3) { + biometricMessageDeferral.processFrame(1) + biometricMessageDeferral.updateMessage(1, "test") + } + + // THEN deferredMessage will return the string associated with the deferred msgId + assertEquals("test", biometricMessageDeferral.getDeferredMessage()) + } + + @Test + fun testReturnsMostFrequentDeferredMessage() { + val biometricMessageDeferral = createMsgDeferral(setOf(1, 2)) + + // WHEN there's 80%of the messages are msgId=1 and 20% is msgId=2 + biometricMessageDeferral.processFrame(1) + biometricMessageDeferral.processFrame(1) + biometricMessageDeferral.processFrame(1) + biometricMessageDeferral.processFrame(1) + biometricMessageDeferral.updateMessage(1, "msgId-1") + + biometricMessageDeferral.processFrame(2) + biometricMessageDeferral.updateMessage(2, "msgId-2") + + // THEN the most frequent deferred message is that meets the threshold is returned + assertEquals("msgId-1", biometricMessageDeferral.getDeferredMessage()) + } + + @Test + fun testDeferredMessage_mustMeetThreshold() { + val biometricMessageDeferral = createMsgDeferral(setOf(1)) + + // WHEN more nonDeferredMessages are shown than the deferred message + val totalMessages = 10 + val nonDeferredMessagesCount = (totalMessages * threshold).toInt() + 1 + for (i in 0 until nonDeferredMessagesCount) { + biometricMessageDeferral.processFrame(4) + biometricMessageDeferral.updateMessage(4, "non-deferred-msg") + } + for (i in nonDeferredMessagesCount until totalMessages) { + biometricMessageDeferral.processFrame(1) + biometricMessageDeferral.updateMessage(1, "msgId-1") + } + + // THEN there's no deferred message because it didn't meet the threshold + assertNull(biometricMessageDeferral.getDeferredMessage()) + } + + @Test + fun testResetClearsOutCounts() { + val biometricMessageDeferral = createMsgDeferral(setOf(1, 2)) + + // GIVEN two msgId=1 events processed + biometricMessageDeferral.processFrame( + 1, + ) + biometricMessageDeferral.updateMessage(1, "msgId-1") + biometricMessageDeferral.processFrame(1) + biometricMessageDeferral.updateMessage(1, "msgId-1") + + // WHEN counts are reset and then a single deferred message is processed (msgId=2) + biometricMessageDeferral.reset() + biometricMessageDeferral.processFrame(2) + biometricMessageDeferral.updateMessage(2, "msgId-2") + + // THEN msgId-2 is the deferred message since the two msgId=1 events were reset + assertEquals("msgId-2", biometricMessageDeferral.getDeferredMessage()) + } + + @Test + fun testShouldDefer() { + // GIVEN should defer msgIds 1 and 2 + val biometricMessageDeferral = createMsgDeferral(setOf(1, 2)) + + // THEN shouldDefer returns true for ids 1 & 2 + assertTrue(biometricMessageDeferral.shouldDefer(1)) + assertTrue(biometricMessageDeferral.shouldDefer(2)) + + // THEN should defer returns false for ids 3 & 4 + assertFalse(biometricMessageDeferral.shouldDefer(3)) + assertFalse(biometricMessageDeferral.shouldDefer(4)) + } + + private fun createMsgDeferral(messagesToDefer: Set<Int>): BiometricMessageDeferral { + return BiometricMessageDeferral(messagesToDefer, threshold, logger, dumpManager) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java index acb5622c9790..3e9cf1e51b63 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java @@ -229,7 +229,10 @@ public class FalsingCollectorImplTest extends SysuiTestCase { } @Test - public void testAvoidDozingNotPulsing() { + public void testGestureWhenDozing() { + // We check the FalsingManager for taps during the transition to AoD (dozing=true, + // pulsing=false), so the FalsingCollector needs to continue to analyze events that occur + // while the device is dozing. MotionEvent down = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0); MotionEvent up = MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP, 0, 0, 0); @@ -239,13 +242,13 @@ public class FalsingCollectorImplTest extends SysuiTestCase { mFalsingCollector.onTouchEvent(down); verify(mFalsingDataProvider, never()).onMotionEvent(any(MotionEvent.class)); - // Up event would normally flush the up event, but doesn't. + // Up event flushes mFalsingCollector.onTouchEvent(up); - verify(mFalsingDataProvider, never()).onMotionEvent(any(MotionEvent.class)); + verify(mFalsingDataProvider, times(2)).onMotionEvent(any(MotionEvent.class)); } @Test - public void testAvoidDozingButPulsing() { + public void testGestureWhenPulsing() { MotionEvent down = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0); MotionEvent up = MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP, 0, 0, 0); diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt index eecbee56d203..a78c902a1f30 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt @@ -27,11 +27,11 @@ import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentCaptor.forClass import org.mockito.Mock +import org.mockito.Mockito.`when` import org.mockito.Mockito.mock import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.Mockito.verifyNoMoreInteractions -import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations @RunWith(AndroidTestingRunner::class) @@ -60,7 +60,18 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { @Mock private lateinit var launcherUnlockAnimationController: ILauncherUnlockAnimationController.Stub - private lateinit var remoteAnimationTarget: RemoteAnimationTarget + private var surfaceControl1 = mock(SurfaceControl::class.java) + private var remoteTarget1 = RemoteAnimationTarget( + 0 /* taskId */, 0, surfaceControl1, false, Rect(), Rect(), 0, Point(), Rect(), Rect(), + mock(WindowConfiguration::class.java), false, surfaceControl1, Rect(), + mock(ActivityManager.RunningTaskInfo::class.java), false) + + private var surfaceControl2 = mock(SurfaceControl::class.java) + private var remoteTarget2 = RemoteAnimationTarget( + 1 /* taskId */, 0, surfaceControl2, false, Rect(), Rect(), 0, Point(), Rect(), Rect(), + mock(WindowConfiguration::class.java), false, surfaceControl2, Rect(), + mock(ActivityManager.RunningTaskInfo::class.java), false) + private lateinit var remoteAnimationTargets: Array<RemoteAnimationTarget> @Before fun setUp() { @@ -77,10 +88,7 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { // All of these fields are final, so we can't mock them, but are needed so that the surface // appear amount setter doesn't short circuit. - remoteAnimationTarget = RemoteAnimationTarget( - 0, 0, null, false, Rect(), Rect(), 0, Point(), Rect(), Rect(), - mock(WindowConfiguration::class.java), false, mock(SurfaceControl::class.java), Rect(), - mock(ActivityManager.RunningTaskInfo::class.java), false) + remoteAnimationTargets = arrayOf(remoteTarget1) // Set the surface applier to our mock so that we can verify the arguments passed to it. // This applier does not have any side effects within the unlock animation controller, so @@ -99,7 +107,7 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { `when`(biometricUnlockController.isWakeAndUnlock).thenReturn(true) keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation( - remoteAnimationTarget, + remoteAnimationTargets, 0 /* startTime */, false /* requestedShowSurfaceBehindKeyguard */ ) @@ -130,7 +138,7 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { `when`(biometricUnlockController.isWakeAndUnlock).thenReturn(false) keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation( - remoteAnimationTarget, + remoteAnimationTargets, 0 /* startTime */, false /* requestedShowSurfaceBehindKeyguard */ ) @@ -154,7 +162,7 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { `when`(keyguardStateController.isFlingingToDismissKeyguard).thenReturn(false) keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation( - remoteAnimationTarget, + remoteAnimationTargets, 0 /* startTime */, true /* requestedShowSurfaceBehindKeyguard */ ) @@ -176,7 +184,7 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { `when`(keyguardStateController.isFlingingToDismissKeyguard).thenReturn(true) keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation( - remoteAnimationTarget, + remoteAnimationTargets, 0 /* startTime */, true /* requestedShowSurfaceBehindKeyguard */ ) @@ -196,7 +204,7 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { @Test fun playCannedUnlockAnimation_ifDidNotRequestShowSurface() { keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation( - remoteAnimationTarget, + remoteAnimationTargets, 0 /* startTime */, false /* requestedShowSurfaceBehindKeyguard */ ) @@ -210,7 +218,7 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { `when`(notificationShadeWindowController.isLaunchingActivity).thenReturn(true) keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation( - remoteAnimationTarget, + remoteAnimationTargets, 0 /* startTime */, true /* requestedShowSurfaceBehindKeyguard */ ) @@ -225,11 +233,46 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { keyguardUnlockAnimationController.willUnlockWithInWindowLauncherAnimations = true keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation( - remoteAnimationTarget, + remoteAnimationTargets, 0 /* startTime */, false /* requestedShowSurfaceBehindKeyguard */ ) assertTrue(keyguardUnlockAnimationController.isPlayingCannedUnlockAnimation()) } + + /** + * If we are not wake and unlocking, we expect the unlock animation to play normally. + */ + @Test + fun surfaceAnimation_multipleTargets() { + keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation( + arrayOf(remoteTarget1, remoteTarget2), + 0 /* startTime */, + false /* requestedShowSurfaceBehindKeyguard */ + ) + + // Set appear to 50%, we'll just verify that we're not applying the identity matrix which + // means an animation is in progress. + keyguardUnlockAnimationController.setSurfaceBehindAppearAmount(0.5f) + + val captor = forClass(SyncRtSurfaceTransactionApplier.SurfaceParams::class.java) + verify(surfaceTransactionApplier, times(2)).scheduleApply(captor.capture()) + + val allParams = captor.allValues + + val remainingTargets = mutableListOf(surfaceControl1, surfaceControl2) + allParams.forEach { params -> + assertTrue(!params.matrix.isIdentity) + remainingTargets.remove(params.surface) + } + + // Make sure we called applyParams with each of the surface controls once. The order does + // not matter, so don't explicitly check for that. + assertTrue(remainingTargets.isEmpty()) + + // Since the animation is running, we should not have finished the remote animation. + verify(keyguardViewMediator, times(0)).onKeyguardExitRemoteAnimationFinished( + false /* cancelled */) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerTest.kt index d95e5c48256c..1078cdaa57c4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerTest.kt @@ -23,11 +23,11 @@ import com.android.systemui.log.LogBuffer import com.android.systemui.log.LogBufferFactory import com.android.systemui.log.LogcatEchoTracker import com.google.common.truth.Truth.assertThat +import java.io.PrintWriter +import java.io.StringWriter import org.junit.Before import org.junit.Test import org.mockito.Mockito.mock -import java.io.PrintWriter -import java.io.StringWriter @SmallTest class MediaTttLoggerTest : SysuiTestCase() { @@ -43,32 +43,46 @@ class MediaTttLoggerTest : SysuiTestCase() { } @Test - fun logStateChange_bufferHasDeviceTypeTagAndStateNameAndId() { + fun logStateChange_bufferHasDeviceTypeTagAndParamInfo() { val stateName = "test state name" val id = "test id" + val packageName = "this.is.a.package" - logger.logStateChange(stateName, id) - - val stringWriter = StringWriter() - buffer.dump(PrintWriter(stringWriter), tailLength = 0) - val actualString = stringWriter.toString() + logger.logStateChange(stateName, id, packageName) + val actualString = getStringFromBuffer() assertThat(actualString).contains(DEVICE_TYPE_TAG) assertThat(actualString).contains(stateName) assertThat(actualString).contains(id) + assertThat(actualString).contains(packageName) } @Test - fun logChipRemoval_bufferHasDeviceTypeAndReason() { - val reason = "test reason" - logger.logChipRemoval(reason) + fun logPackageNotFound_bufferHasPackageName() { + val packageName = "this.is.a.package" + logger.logPackageNotFound(packageName) + + val actualString = getStringFromBuffer() + assertThat(actualString).contains(packageName) + } + + @Test + fun logRemovalBypass_bufferHasReasons() { + val removalReason = "fakeRemovalReason" + val bypassReason = "fakeBypassReason" + + logger.logRemovalBypass(removalReason, bypassReason) + + val actualString = getStringFromBuffer() + assertThat(actualString).contains(removalReason) + assertThat(actualString).contains(bypassReason) + } + + private fun getStringFromBuffer(): String { val stringWriter = StringWriter() buffer.dump(PrintWriter(stringWriter), tailLength = 0) - val actualString = stringWriter.toString() - - assertThat(actualString).contains(DEVICE_TYPE_TAG) - assertThat(actualString).contains(reason) + return stringWriter.toString() } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt new file mode 100644 index 000000000000..37f6434ea069 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt @@ -0,0 +1,138 @@ +/* + * 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.media.taptotransfer.common + +import android.content.pm.ApplicationInfo +import android.content.pm.PackageManager +import android.graphics.drawable.Drawable +import android.widget.FrameLayout +import androidx.test.filters.SmallTest +import com.android.internal.widget.CachingIconView +import com.android.systemui.R +import com.android.systemui.SysuiTestCase +import com.android.systemui.util.mockito.any +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.mockito.Mock +import org.mockito.Mockito +import org.mockito.Mockito.`when` as whenever +import org.mockito.MockitoAnnotations + +@SmallTest +class MediaTttUtilsTest : SysuiTestCase() { + + private lateinit var appIconFromPackageName: Drawable + @Mock private lateinit var packageManager: PackageManager + @Mock private lateinit var applicationInfo: ApplicationInfo + @Mock private lateinit var logger: MediaTttLogger + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + + // Set up our package manager to give valid information for [PACKAGE_NAME] only + appIconFromPackageName = context.getDrawable(R.drawable.ic_cake)!! + whenever(packageManager.getApplicationIcon(PACKAGE_NAME)).thenReturn(appIconFromPackageName) + whenever(applicationInfo.loadLabel(packageManager)).thenReturn(APP_NAME) + whenever( + packageManager.getApplicationInfo(any(), any<PackageManager.ApplicationInfoFlags>()) + ) + .thenThrow(PackageManager.NameNotFoundException()) + whenever( + packageManager.getApplicationInfo( + Mockito.eq(PACKAGE_NAME), + any<PackageManager.ApplicationInfoFlags>() + ) + ) + .thenReturn(applicationInfo) + context.setMockPackageManager(packageManager) + } + + @Test + fun getIconInfoFromPackageName_nullPackageName_returnsDefault() { + val iconInfo = + MediaTttUtils.getIconInfoFromPackageName(context, appPackageName = null, logger) + + assertThat(iconInfo.isAppIcon).isFalse() + assertThat(iconInfo.contentDescription) + .isEqualTo(context.getString(R.string.media_output_dialog_unknown_launch_app_name)) + } + + @Test + fun getIconInfoFromPackageName_invalidPackageName_returnsDefault() { + val iconInfo = MediaTttUtils.getIconInfoFromPackageName(context, "fakePackageName", logger) + + assertThat(iconInfo.isAppIcon).isFalse() + assertThat(iconInfo.contentDescription) + .isEqualTo(context.getString(R.string.media_output_dialog_unknown_launch_app_name)) + } + + @Test + fun getIconInfoFromPackageName_validPackageName_returnsAppInfo() { + val iconInfo = MediaTttUtils.getIconInfoFromPackageName(context, PACKAGE_NAME, logger) + + assertThat(iconInfo.isAppIcon).isTrue() + assertThat(iconInfo.drawable).isEqualTo(appIconFromPackageName) + assertThat(iconInfo.contentDescription).isEqualTo(APP_NAME) + } + + @Test + fun setIcon_viewHasIconAndContentDescription() { + val view = CachingIconView(context) + val icon = context.getDrawable(R.drawable.ic_celebration)!! + val contentDescription = "Happy birthday!" + + MediaTttUtils.setIcon(view, icon, contentDescription) + + assertThat(view.drawable).isEqualTo(icon) + assertThat(view.contentDescription).isEqualTo(contentDescription) + } + + @Test + fun setIcon_iconSizeNull_viewSizeDoesNotChange() { + val view = CachingIconView(context) + val size = 456 + view.layoutParams = FrameLayout.LayoutParams(size, size) + + MediaTttUtils.setIcon(view, context.getDrawable(R.drawable.ic_cake)!!, "desc") + + assertThat(view.layoutParams.width).isEqualTo(size) + assertThat(view.layoutParams.height).isEqualTo(size) + } + + @Test + fun setIcon_iconSizeProvided_viewSizeUpdates() { + val view = CachingIconView(context) + val size = 456 + view.layoutParams = FrameLayout.LayoutParams(size, size) + + val newSize = 40 + MediaTttUtils.setIcon( + view, + context.getDrawable(R.drawable.ic_cake)!!, + "desc", + iconSize = newSize + ) + + assertThat(view.layoutParams.width).isEqualTo(newSize) + assertThat(view.layoutParams.height).isEqualTo(newSize) + } +} + +private const val PACKAGE_NAME = "com.android.systemui" +private const val APP_NAME = "Fake App Name" diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt index e7b4593b0ebb..d41ad48676b4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt @@ -173,37 +173,72 @@ class MediaTttChipControllerReceiverTest : SysuiTestCase() { null ) - verify(logger).logStateChange(any(), any()) + verify(logger).logStateChange(any(), any(), any()) } @Test - fun setIcon_isAppIcon_usesAppIconSize() { - controllerReceiver.displayView(getChipReceiverInfo()) + fun updateView_noOverrides_usesInfoFromAppIcon() { + controllerReceiver.displayView( + ChipReceiverInfo(routeInfo, appIconDrawableOverride = null, appNameOverride = null) + ) + + val view = getChipView() + assertThat(view.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable) + assertThat(view.getAppIconView().contentDescription).isEqualTo(APP_NAME) + } + + @Test + fun updateView_appIconOverride_usesOverride() { + val drawableOverride = context.getDrawable(R.drawable.ic_celebration)!! + + controllerReceiver.displayView( + ChipReceiverInfo(routeInfo, drawableOverride, appNameOverride = null) + ) + + val view = getChipView() + assertThat(view.getAppIconView().drawable).isEqualTo(drawableOverride) + } + + @Test + fun updateView_appNameOverride_usesOverride() { + val appNameOverride = "Sweet New App" + + controllerReceiver.displayView( + ChipReceiverInfo(routeInfo, appIconDrawableOverride = null, appNameOverride) + ) + + val view = getChipView() + assertThat(view.getAppIconView().contentDescription).isEqualTo(appNameOverride) + } + + @Test + fun updateView_isAppIcon_usesAppIconSize() { + controllerReceiver.displayView(getChipReceiverInfo(packageName = PACKAGE_NAME)) val chipView = getChipView() - controllerReceiver.setIcon(chipView, PACKAGE_NAME) chipView.measure( View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED) ) - val expectedSize = controllerReceiver.getIconSize(isAppIcon = true) + val expectedSize = + context.resources.getDimensionPixelSize(R.dimen.media_ttt_icon_size_receiver) assertThat(chipView.getAppIconView().measuredWidth).isEqualTo(expectedSize) assertThat(chipView.getAppIconView().measuredHeight).isEqualTo(expectedSize) } @Test - fun setIcon_notAppIcon_usesGenericIconSize() { - controllerReceiver.displayView(getChipReceiverInfo()) + fun updateView_notAppIcon_usesGenericIconSize() { + controllerReceiver.displayView(getChipReceiverInfo(packageName = null)) val chipView = getChipView() - controllerReceiver.setIcon(chipView, appPackageName = null) chipView.measure( View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED) ) - val expectedSize = controllerReceiver.getIconSize(isAppIcon = false) + val expectedSize = + context.resources.getDimensionPixelSize(R.dimen.media_ttt_generic_icon_size_receiver) assertThat(chipView.getAppIconView().measuredWidth).isEqualTo(expectedSize) assertThat(chipView.getAppIconView().measuredHeight).isEqualTo(expectedSize) } @@ -226,8 +261,13 @@ class MediaTttChipControllerReceiverTest : SysuiTestCase() { return viewCaptor.value as ViewGroup } - private fun getChipReceiverInfo(): ChipReceiverInfo = - ChipReceiverInfo(routeInfo, null, null) + private fun getChipReceiverInfo(packageName: String?): ChipReceiverInfo { + val routeInfo = MediaRoute2Info.Builder("id", "Test route name") + .addFeature("feature") + .setClientPackageName(packageName) + .build() + return ChipReceiverInfo(routeInfo, null, null) + } private fun ViewGroup.getAppIconView() = this.requireViewById<ImageView>(R.id.app_icon) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt index 52b6eed9a14d..ff0faf98fe1e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt @@ -299,7 +299,7 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { null ) - verify(logger).logStateChange(any(), any()) + verify(logger).logStateChange(any(), any(), any()) } @Test @@ -587,15 +587,29 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { fakeExecutor.runAllReady() verify(windowManager, never()).removeView(any()) + verify(logger).logRemovalBypass(any(), any()) } @Test - fun transferToReceiverTriggeredThenFarFromReceiver_eventuallyTimesOut() { - val state = transferToReceiverTriggered() - controllerSender.displayView(state) - fakeClock.advanceTime(1000L) - controllerSender.removeView("fakeRemovalReason") + fun transferToReceiverTriggeredThenFarFromReceiver_viewStillDisplayed() { + controllerSender.displayView(transferToReceiverTriggered()) + + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER, + routeInfo, + null + ) + fakeExecutor.runAllReady() + verify(windowManager, never()).removeView(any()) + verify(logger).logRemovalBypass(any(), any()) + } + + @Test + fun transferToReceiverTriggeredThenRemoveView_eventuallyTimesOut() { + controllerSender.displayView(transferToReceiverTriggered()) + + controllerSender.removeView("fakeRemovalReason") fakeClock.advanceTime(TIMEOUT + 1L) verify(windowManager).removeView(any()) @@ -610,20 +624,106 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { fakeExecutor.runAllReady() verify(windowManager, never()).removeView(any()) + verify(logger).logRemovalBypass(any(), any()) } @Test - fun transferToThisDeviceTriggeredThenFarFromReceiver_eventuallyTimesOut() { - val state = transferToThisDeviceTriggered() - controllerSender.displayView(state) - fakeClock.advanceTime(1000L) + fun transferToThisDeviceTriggeredThenRemoveView_eventuallyTimesOut() { + controllerSender.displayView(transferToThisDeviceTriggered()) + controllerSender.removeView("fakeRemovalReason") + fakeClock.advanceTime(TIMEOUT + 1L) + + verify(windowManager).removeView(any()) + } + + @Test + fun transferToThisDeviceTriggeredThenFarFromReceiver_viewStillDisplayed() { + controllerSender.displayView(transferToThisDeviceTriggered()) + + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER, + routeInfo, + null + ) + fakeExecutor.runAllReady() + + verify(windowManager, never()).removeView(any()) + verify(logger).logRemovalBypass(any(), any()) + } + + @Test + fun transferToReceiverSucceededThenRemoveView_viewStillDisplayed() { + controllerSender.displayView(transferToReceiverSucceeded()) + + controllerSender.removeView("fakeRemovalReason") + fakeExecutor.runAllReady() + verify(windowManager, never()).removeView(any()) + verify(logger).logRemovalBypass(any(), any()) + } + + @Test + fun transferToReceiverSucceededThenRemoveView_eventuallyTimesOut() { + controllerSender.displayView(transferToReceiverSucceeded()) + + controllerSender.removeView("fakeRemovalReason") fakeClock.advanceTime(TIMEOUT + 1L) verify(windowManager).removeView(any()) } + @Test + fun transferToReceiverSucceededThenFarFromReceiver_viewStillDisplayed() { + controllerSender.displayView(transferToReceiverSucceeded()) + + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER, + routeInfo, + null + ) + fakeExecutor.runAllReady() + + verify(windowManager, never()).removeView(any()) + verify(logger).logRemovalBypass(any(), any()) + } + + @Test + fun transferToThisDeviceSucceededThenRemoveView_viewStillDisplayed() { + controllerSender.displayView(transferToThisDeviceSucceeded()) + + controllerSender.removeView("fakeRemovalReason") + fakeExecutor.runAllReady() + + verify(windowManager, never()).removeView(any()) + verify(logger).logRemovalBypass(any(), any()) + } + + @Test + fun transferToThisDeviceSucceededThenRemoveView_eventuallyTimesOut() { + controllerSender.displayView(transferToThisDeviceSucceeded()) + + controllerSender.removeView("fakeRemovalReason") + fakeClock.advanceTime(TIMEOUT + 1L) + + verify(windowManager).removeView(any()) + } + + @Test + fun transferToThisDeviceSucceededThenFarFromReceiver_viewStillDisplayed() { + controllerSender.displayView(transferToThisDeviceSucceeded()) + + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER, + routeInfo, + null + ) + fakeExecutor.runAllReady() + + verify(windowManager, never()).removeView(any()) + verify(logger).logRemovalBypass(any(), any()) + } + private fun ViewGroup.getAppIconView() = this.requireViewById<ImageView>(R.id.app_icon) private fun ViewGroup.getChipText(): String = diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorInversionTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorInversionTileTest.java index bf682a8d4e0a..3fd25019e2a5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorInversionTileTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorInversionTileTest.java @@ -33,12 +33,15 @@ import androidx.test.filters.SmallTest; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.UiEventLogger; +import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.classifier.FalsingManagerFake; import com.android.systemui.plugins.ActivityStarter; +import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSTileHost; import com.android.systemui.qs.logging.QSLogger; +import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.settings.UserTracker; import com.android.systemui.util.settings.FakeSettings; import com.android.systemui.util.settings.SecureSettings; @@ -54,6 +57,8 @@ import org.mockito.MockitoAnnotations; @TestableLooper.RunWithLooper(setAsMainLooper = true) @SmallTest public class ColorInversionTileTest extends SysuiTestCase { + private static final Integer COLOR_INVERSION_DISABLED = 0; + private static final Integer COLOR_INVERSION_ENABLED = 1; @Mock private QSTileHost mHost; @@ -113,4 +118,24 @@ public class ColorInversionTileTest extends SysuiTestCase { assertThat(IntentCaptor.getValue().getAction()).isEqualTo( Settings.ACTION_COLOR_INVERSION_SETTINGS); } + + @Test + public void testIcon_whenColorInversionDisabled_isOffState() { + QSTile.BooleanState state = new QSTile.BooleanState(); + + mTile.handleUpdateState(state, COLOR_INVERSION_DISABLED); + + assertThat(state.icon) + .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_invert_colors_icon_off)); + } + + @Test + public void testIcon_whenColorInversionEnabled_isOnState() { + QSTile.BooleanState state = new QSTile.BooleanState(); + + mTile.handleUpdateState(state, COLOR_INVERSION_ENABLED); + + assertThat(state.icon) + .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_invert_colors_icon_on)); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DataSaverTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DataSaverTileTest.kt new file mode 100644 index 000000000000..ce62f2d1cf36 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DataSaverTileTest.kt @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles + +import android.os.Handler +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import androidx.test.filters.SmallTest +import com.android.internal.logging.MetricsLogger +import com.android.internal.logging.testing.UiEventLoggerFake +import com.android.systemui.R +import com.android.systemui.SysuiTestCase +import com.android.systemui.animation.DialogLaunchAnimator +import com.android.systemui.classifier.FalsingManagerFake +import com.android.systemui.plugins.ActivityStarter +import com.android.systemui.plugins.qs.QSTile +import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.qs.QSHost +import com.android.systemui.qs.logging.QSLogger +import com.android.systemui.qs.tileimpl.QSTileImpl +import com.android.systemui.statusbar.policy.DataSaverController +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito +import org.mockito.MockitoAnnotations + +@RunWith(AndroidTestingRunner::class) +@TestableLooper.RunWithLooper(setAsMainLooper = true) +@SmallTest +class DataSaverTileTest : SysuiTestCase() { + + @Mock private lateinit var mHost: QSHost + @Mock private lateinit var mMetricsLogger: MetricsLogger + @Mock private lateinit var mStatusBarStateController: StatusBarStateController + @Mock private lateinit var mActivityStarter: ActivityStarter + @Mock private lateinit var mQsLogger: QSLogger + private val falsingManager = FalsingManagerFake() + @Mock private lateinit var statusBarStateController: StatusBarStateController + @Mock private lateinit var activityStarter: ActivityStarter + @Mock private lateinit var dataSaverController: DataSaverController + @Mock private lateinit var dialogLaunchAnimator: DialogLaunchAnimator + + private val uiEventLogger = UiEventLoggerFake() + private lateinit var testableLooper: TestableLooper + private lateinit var tile: DataSaverTile + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + testableLooper = TestableLooper.get(this) + + Mockito.`when`(mHost.context).thenReturn(mContext) + Mockito.`when`(mHost.uiEventLogger).thenReturn(uiEventLogger) + + tile = + DataSaverTile( + mHost, + testableLooper.looper, + Handler(testableLooper.looper), + falsingManager, + mMetricsLogger, + statusBarStateController, + activityStarter, + mQsLogger, + dataSaverController, + dialogLaunchAnimator + ) + } + + @Test + fun testIcon_whenDataSaverEnabled_isOnState() { + val state = QSTile.BooleanState() + + tile.handleUpdateState(state, true) + + assertThat(state.icon) + .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_data_saver_icon_on)) + } + + @Test + fun testIcon_whenDataSaverDisabled_isOffState() { + val state = QSTile.BooleanState() + + tile.handleUpdateState(state, false) + + assertThat(state.icon) + .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_data_saver_icon_off)) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NightDisplayTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NightDisplayTileTest.kt new file mode 100644 index 000000000000..188c3a3d9e42 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NightDisplayTileTest.kt @@ -0,0 +1,132 @@ +/* + * 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.qs.tiles + +import android.hardware.display.ColorDisplayManager +import android.hardware.display.NightDisplayListener +import android.os.Handler +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import androidx.test.filters.SmallTest +import com.android.internal.logging.MetricsLogger +import com.android.internal.logging.UiEventLogger +import com.android.internal.logging.testing.UiEventLoggerFake +import com.android.systemui.R +import com.android.systemui.SysuiTestCase +import com.android.systemui.classifier.FalsingManagerFake +import com.android.systemui.dagger.NightDisplayListenerModule +import com.android.systemui.plugins.ActivityStarter +import com.android.systemui.plugins.qs.QSTile +import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.qs.QSHost +import com.android.systemui.qs.logging.QSLogger +import com.android.systemui.qs.tileimpl.QSTileImpl +import com.android.systemui.statusbar.policy.LocationController +import com.google.common.truth.Truth +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.anyInt +import org.mockito.Mockito.`when` as whenever +import org.mockito.MockitoAnnotations + +@RunWith(AndroidTestingRunner::class) +@TestableLooper.RunWithLooper(setAsMainLooper = true) +@SmallTest +class NightDisplayTileTest : SysuiTestCase() { + @Mock + private lateinit var mHost: QSHost + + @Mock + private lateinit var mMetricsLogger: MetricsLogger + + @Mock + private lateinit var mStatusBarStateController: StatusBarStateController + + @Mock + private lateinit var mActivityStarter: ActivityStarter + + @Mock + private lateinit var mQsLogger: QSLogger + + @Mock + private lateinit var mLocationController: LocationController + + @Mock + private lateinit var mColorDisplayManager: ColorDisplayManager + + @Mock + private lateinit var mNightDisplayListenerBuilder: NightDisplayListenerModule.Builder + + @Mock + private lateinit var mNightDisplayListener: NightDisplayListener + + private lateinit var mTestableLooper: TestableLooper + private lateinit var mTile: NightDisplayTile + + private val mUiEventLogger: UiEventLogger = UiEventLoggerFake() + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + mTestableLooper = TestableLooper.get(this) + whenever(mHost.context).thenReturn(mContext) + whenever(mHost.uiEventLogger).thenReturn(mUiEventLogger) + whenever(mHost.userContext).thenReturn(mContext) + whenever(mNightDisplayListenerBuilder.setUser(anyInt())).thenReturn( + mNightDisplayListenerBuilder + ) + whenever(mNightDisplayListenerBuilder.build()).thenReturn(mNightDisplayListener) + + mTile = NightDisplayTile( + mHost, + mTestableLooper.looper, + Handler(mTestableLooper.looper), + FalsingManagerFake(), + mMetricsLogger, + mStatusBarStateController, + mActivityStarter, + mQsLogger, + mLocationController, + mColorDisplayManager, + mNightDisplayListenerBuilder + ) + } + + @Test + fun testIcon_whenDisabled_showsOffState() { + whenever(mColorDisplayManager.isNightDisplayActivated).thenReturn(false) + val state = QSTile.BooleanState() + + mTile.handleUpdateState(state, /* arg= */ null) + + Truth.assertThat(state.icon) + .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_nightlight_icon_off)) + } + + @Test + fun testIcon_whenEnabled_showsOnState() { + whenever(mColorDisplayManager.isNightDisplayActivated).thenReturn(true) + val state = QSTile.BooleanState() + + mTile.handleUpdateState(state, /* arg= */ null) + + Truth.assertThat(state.icon) + .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_nightlight_icon_on)) + } +} 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 e73071335c9d..b40d5ac69d7b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java @@ -1022,7 +1022,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { FalsingManager.FalsingTapListener listener = getFalsingTapListener(); mStatusBarStateController.setState(KEYGUARD); - listener.onDoubleTapRequired(); + listener.onAdditionalTapRequired(); verify(mKeyguardIndicationController).showTransientIndication(anyInt()); } @@ -1032,7 +1032,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { FalsingManager.FalsingTapListener listener = getFalsingTapListener(); mStatusBarStateController.setState(SHADE_LOCKED); - listener.onDoubleTapRequired(); + listener.onAdditionalTapRequired(); verify(mTapAgainViewController).show(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/PulsingGestureListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/PulsingGestureListenerTest.kt index 97c0bb2c09ca..09add652483e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/PulsingGestureListenerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/PulsingGestureListenerTest.kt @@ -89,7 +89,7 @@ class PulsingGestureListenerTest : SysuiTestCase() { @Test fun testGestureDetector_singleTapEnabled() { - whenever(statusBarStateController.isPulsing).thenReturn(true) + whenever(statusBarStateController.isDozing).thenReturn(true) // GIVEN tap is enabled, prox not covered whenever(ambientDisplayConfiguration.tapGestureEnabled(anyInt())).thenReturn(true) @@ -100,7 +100,7 @@ class PulsingGestureListenerTest : SysuiTestCase() { whenever(falsingManager.isFalseTap(anyInt())).thenReturn(false) // WHEN there's a tap - underTest.onSingleTapConfirmed(downEv) + underTest.onSingleTapUp(upEv) // THEN wake up device if dozing verify(centralSurfaces).wakeUpIfDozing(anyLong(), anyObject(), anyString()) @@ -108,7 +108,7 @@ class PulsingGestureListenerTest : SysuiTestCase() { @Test fun testGestureDetector_doubleTapEnabled() { - whenever(statusBarStateController.isPulsing).thenReturn(true) + whenever(statusBarStateController.isDozing).thenReturn(true) // GIVEN double tap is enabled, prox not covered whenever(ambientDisplayConfiguration.doubleTapGestureEnabled(anyInt())).thenReturn(true) @@ -119,15 +119,27 @@ class PulsingGestureListenerTest : SysuiTestCase() { whenever(falsingManager.isFalseDoubleTap).thenReturn(false) // WHEN there's a double tap - underTest.onDoubleTap(downEv) + underTest.onDoubleTapEvent(upEv) // THEN wake up device if dozing verify(centralSurfaces).wakeUpIfDozing(anyLong(), anyObject(), anyString()) } @Test + fun testGestureDetector_doubleTapEnabled_onDownEvent_noFalsingCheck() { + // GIVEN tap is enabled + whenever(ambientDisplayConfiguration.tapGestureEnabled(anyInt())).thenReturn(true) + + // WHEN there's a double tap on DOWN event + underTest.onDoubleTapEvent(downEv) + + // THEN don't check the falsing manager, should only be checked on the UP event + verify(falsingManager, never()).isFalseDoubleTap() + } + + @Test fun testGestureDetector_singleTapEnabled_falsing() { - whenever(statusBarStateController.isPulsing).thenReturn(true) + whenever(statusBarStateController.isDozing).thenReturn(true) // GIVEN tap is enabled, prox not covered whenever(ambientDisplayConfiguration.tapGestureEnabled(anyInt())).thenReturn(true) @@ -138,29 +150,43 @@ class PulsingGestureListenerTest : SysuiTestCase() { whenever(falsingManager.isFalseTap(anyInt())).thenReturn(true) // WHEN there's a tap - underTest.onSingleTapConfirmed(downEv) + underTest.onSingleTapUp(upEv) // THEN the device doesn't wake up verify(centralSurfaces, never()).wakeUpIfDozing(anyLong(), anyObject(), anyString()) } @Test - fun testGestureDetector_notPulsing_noFalsingCheck() { - whenever(statusBarStateController.isPulsing).thenReturn(false) + fun testSingleTap_notDozing_noFalsingCheck() { + whenever(statusBarStateController.isDozing).thenReturn(false) - // GIVEN tap is enabled, prox not covered + // GIVEN tap is enabled whenever(ambientDisplayConfiguration.tapGestureEnabled(anyInt())).thenReturn(true) // WHEN there's a tap - underTest.onSingleTapConfirmed(downEv) + underTest.onSingleTapUp(upEv) - // THEN the falsing manager never gets a call (because the device wasn't pulsing + // THEN the falsing manager never gets a call (because the device wasn't dozing + // during the tap) + verify(falsingManager, never()).isFalseTap(anyInt()) + } + + @Test + fun testDoubleTap_notDozing_noFalsingCheck() { + whenever(statusBarStateController.isDozing).thenReturn(false) + + // GIVEN tap is enabled + whenever(ambientDisplayConfiguration.tapGestureEnabled(anyInt())).thenReturn(true) + // WHEN there's a tap + underTest.onDoubleTapEvent(upEv) + + // THEN the falsing manager never gets a call (because the device wasn't dozing // during the tap) verify(falsingManager, never()).isFalseTap(anyInt()) } @Test fun testGestureDetector_doubleTapEnabled_falsing() { - whenever(statusBarStateController.isPulsing).thenReturn(true) + whenever(statusBarStateController.isDozing).thenReturn(true) // GIVEN double tap is enabled, prox not covered whenever(ambientDisplayConfiguration.doubleTapGestureEnabled(anyInt())).thenReturn(true) @@ -170,8 +196,8 @@ class PulsingGestureListenerTest : SysuiTestCase() { // GIVEN the falsing manager thinks the tap is a false tap whenever(falsingManager.isFalseDoubleTap).thenReturn(true) - // WHEN there's a tap - underTest.onDoubleTap(downEv) + // WHEN there's a double tap ACTION_UP event + underTest.onDoubleTapEvent(upEv) // THEN the device doesn't wake up verify(centralSurfaces, never()).wakeUpIfDozing(anyLong(), anyObject(), anyString()) @@ -179,7 +205,7 @@ class PulsingGestureListenerTest : SysuiTestCase() { @Test fun testGestureDetector_singleTapEnabled_proxCovered() { - whenever(statusBarStateController.isPulsing).thenReturn(true) + whenever(statusBarStateController.isDozing).thenReturn(true) // GIVEN tap is enabled, not a false tap based on classifiers whenever(ambientDisplayConfiguration.tapGestureEnabled(anyInt())).thenReturn(true) @@ -190,7 +216,7 @@ class PulsingGestureListenerTest : SysuiTestCase() { whenever(falsingManager.isProximityNear()).thenReturn(true) // WHEN there's a tap - underTest.onSingleTapConfirmed(downEv) + underTest.onSingleTapUp(upEv) // THEN the device doesn't wake up verify(centralSurfaces, never()).wakeUpIfDozing(anyLong(), anyObject(), anyString()) @@ -198,7 +224,7 @@ class PulsingGestureListenerTest : SysuiTestCase() { @Test fun testGestureDetector_doubleTapEnabled_proxCovered() { - whenever(statusBarStateController.isPulsing).thenReturn(true) + whenever(statusBarStateController.isDozing).thenReturn(true) // GIVEN double tap is enabled, not a false tap based on classifiers whenever(ambientDisplayConfiguration.doubleTapGestureEnabled(anyInt())).thenReturn(true) @@ -209,7 +235,7 @@ class PulsingGestureListenerTest : SysuiTestCase() { whenever(falsingManager.isProximityNear()).thenReturn(true) // WHEN there's a tap - underTest.onDoubleTap(downEv) + underTest.onDoubleTapEvent(upEv) // THEN the device doesn't wake up verify(centralSurfaces, never()).wakeUpIfDozing(anyLong(), anyObject(), anyString()) @@ -227,3 +253,4 @@ class PulsingGestureListenerTest : SysuiTestCase() { } private val downEv = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0) +private val upEv = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_UP, 0f, 0f, 0) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java index ac8874b0e131..945cf7f8774f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java @@ -19,8 +19,10 @@ package com.android.systemui.statusbar; import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_DEFAULT; import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_FINANCED; import static android.content.pm.UserInfo.FLAG_MANAGED_PROFILE; +import static android.hardware.biometrics.BiometricFaceConstants.FACE_ACQUIRED_TOO_DARK; import static android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_TIMEOUT; +import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_RECOGNIZED; import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED; import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_ALIGNMENT; import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_BATTERY; @@ -86,6 +88,7 @@ import com.android.keyguard.KeyguardUpdateMonitorCallback; import com.android.settingslib.fuelgauge.BatteryStatus; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; +import com.android.systemui.biometrics.FaceHelpMessageDeferral; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dock.DockManager; import com.android.systemui.keyguard.KeyguardIndication; @@ -167,6 +170,8 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { @Mock private AccessibilityManager mAccessibilityManager; @Mock + private FaceHelpMessageDeferral mFaceHelpMessageDeferral; + @Mock private ScreenLifecycle mScreenLifecycle; @Captor private ArgumentCaptor<DockManager.AlignmentStateListener> mAlignmentListener; @@ -259,7 +264,8 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { mKeyguardStateController, mStatusBarStateController, mKeyguardUpdateMonitor, mDockManager, mBroadcastDispatcher, mDevicePolicyManager, mIBatteryStats, mUserManager, mExecutor, mExecutor, mFalsingManager, mLockPatternUtils, - mScreenLifecycle, mKeyguardBypassController, mAccessibilityManager); + mScreenLifecycle, mKeyguardBypassController, mAccessibilityManager, + mFaceHelpMessageDeferral); mController.init(); mController.setIndicationArea(mIndicationArea); verify(mStatusBarStateController).addCallback(mStatusBarStateListenerCaptor.capture()); @@ -535,7 +541,7 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { mController.setVisible(true); mController.getKeyguardCallback().onBiometricHelp( - KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_RECOGNIZED, message, + BIOMETRIC_HELP_FACE_NOT_RECOGNIZED, message, BiometricSourceType.FACE); verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE, message); reset(mRotateTextViewController); @@ -582,7 +588,7 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { // WHEN there's a face not recognized message mController.getKeyguardCallback().onBiometricHelp( - KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_RECOGNIZED, + BIOMETRIC_HELP_FACE_NOT_RECOGNIZED, message, BiometricSourceType.FACE); @@ -748,8 +754,10 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible( 0)).thenReturn(false); - // WHEN help message received + // WHEN help message received and deferred message is valid final String helpString = "helpMsg"; + when(mFaceHelpMessageDeferral.getDeferredMessage()).thenReturn(helpString); + when(mFaceHelpMessageDeferral.shouldDefer(FACE_ACQUIRED_TOO_DARK)).thenReturn(true); mKeyguardUpdateMonitorCallback.onBiometricHelp( BiometricFaceConstants.FACE_ACQUIRED_TOO_DARK, helpString, @@ -777,8 +785,10 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible( 0)).thenReturn(true); - // WHEN help message received + // WHEN help message received and deferredMessage is valid final String helpString = "helpMsg"; + when(mFaceHelpMessageDeferral.getDeferredMessage()).thenReturn(helpString); + when(mFaceHelpMessageDeferral.shouldDefer(FACE_ACQUIRED_TOO_DARK)).thenReturn(true); mKeyguardUpdateMonitorCallback.onBiometricHelp( BiometricFaceConstants.FACE_ACQUIRED_TOO_DARK, helpString, @@ -1123,7 +1133,6 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP, pressToOpen); } - @Test public void coEx_faceSuccess_touchExplorationEnabled_showsFaceUnlockedSwipeToOpen() { // GIVEN bouncer isn't showing, can skip bouncer, udfps is supported, a11y enabled @@ -1269,6 +1278,87 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE, swipeToOpen); } + @Test + public void faceOnAcquired_processFrame() { + createController(); + + // WHEN face sends an acquired message + final int acquireInfo = 1; + mKeyguardUpdateMonitorCallback.onBiometricAcquired(BiometricSourceType.FACE, acquireInfo); + + // THEN face help message deferral should process the acquired frame + verify(mFaceHelpMessageDeferral).processFrame(acquireInfo); + } + + @Test + public void fingerprintOnAcquired_noProcessFrame() { + createController(); + + // WHEN fingerprint sends an acquired message + mKeyguardUpdateMonitorCallback.onBiometricAcquired(BiometricSourceType.FINGERPRINT, 1); + + // THEN face help message deferral should NOT process any acquired frames + verify(mFaceHelpMessageDeferral, never()).processFrame(anyInt()); + } + + @Test + public void onBiometricHelp_fingerprint_faceHelpMessageDeferralDoesNothing() { + createController(); + + // WHEN fingerprint sends an onBiometricHelp + mKeyguardUpdateMonitorCallback.onBiometricHelp( + 1, + "placeholder", + BiometricSourceType.FINGERPRINT); + + // THEN face help message deferral is NOT: reset, updated, or checked for shouldDefer + verify(mFaceHelpMessageDeferral, never()).reset(); + verify(mFaceHelpMessageDeferral, never()).updateMessage(anyInt(), anyString()); + verify(mFaceHelpMessageDeferral, never()).shouldDefer(anyInt()); + } + + @Test + public void onBiometricFailed_resetFaceHelpMessageDeferral() { + createController(); + + // WHEN face sends an onBiometricHelp BIOMETRIC_HELP_FACE_NOT_RECOGNIZED + mKeyguardUpdateMonitorCallback.onBiometricAuthFailed(BiometricSourceType.FACE); + + // THEN face help message deferral is reset + verify(mFaceHelpMessageDeferral).reset(); + } + + @Test + public void onBiometricError_resetFaceHelpMessageDeferral() { + createController(); + + // WHEN face has an error + mKeyguardUpdateMonitorCallback.onBiometricError(4, "string", + BiometricSourceType.FACE); + + // THEN face help message deferral is reset + verify(mFaceHelpMessageDeferral).reset(); + } + + @Test + public void onBiometricHelp_faceAcquiredInfo_faceHelpMessageDeferral() { + createController(); + + // WHEN face sends an onBiometricHelp BIOMETRIC_HELP_FACE_NOT_RECOGNIZED + final int msgId = 1; + final String helpString = "test"; + mKeyguardUpdateMonitorCallback.onBiometricHelp( + msgId, + "test", + BiometricSourceType.FACE); + + // THEN face help message deferral is NOT reset and message IS updated + verify(mFaceHelpMessageDeferral, never()).reset(); + verify(mFaceHelpMessageDeferral).updateMessage(msgId, helpString); + } + + + private void sendUpdateDisclosureBroadcast() { mBroadcastReceiver.onReceive(mContext, new Intent()); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java index 3dd36d134cf7..d0391ac0795c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java @@ -41,6 +41,7 @@ import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.bluetooth.LocalBluetoothProfile; import com.android.settingslib.bluetooth.LocalBluetoothProfileManager; import com.android.systemui.SysuiTestCase; +import com.android.systemui.bluetooth.BluetoothLogger; import com.android.systemui.dump.DumpManager; import org.junit.Before; @@ -81,6 +82,7 @@ public class BluetoothControllerImplTest extends SysuiTestCase { mBluetoothControllerImpl = new BluetoothControllerImpl(mContext, mMockDumpManager, + mock(BluetoothLogger.class), mTestableLooper.getLooper(), mTestableLooper.getLooper(), mMockBluetoothManager); @@ -233,4 +235,11 @@ public class BluetoothControllerImplTest extends SysuiTestCase { assertTrue(mBluetoothControllerImpl.isBluetoothAudioActive()); assertTrue(mBluetoothControllerImpl.isBluetoothAudioProfileOnly()); } + + /** Regression test for b/246876230. */ + @Test + public void testOnActiveDeviceChanged_null_noCrash() { + mBluetoothControllerImpl.onActiveDeviceChanged(null, BluetoothProfile.HEADSET); + // No assert, just need no crash. + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt index e616c26377d2..921b7efc38eb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt @@ -17,20 +17,14 @@ package com.android.systemui.temporarydisplay import android.content.Context -import android.content.pm.ApplicationInfo -import android.content.pm.PackageManager -import android.graphics.drawable.Drawable import android.os.PowerManager -import android.view.View import android.view.ViewGroup import android.view.WindowManager import android.view.accessibility.AccessibilityManager -import android.widget.ImageView import androidx.test.filters.SmallTest import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.dagger.qualifiers.Main -import com.android.systemui.media.taptotransfer.common.MediaTttLogger import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener import com.android.systemui.util.concurrency.DelayableExecutor @@ -42,9 +36,7 @@ import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test -import org.mockito.ArgumentCaptor import org.mockito.Mock -import org.mockito.Mockito.eq import org.mockito.Mockito.never import org.mockito.Mockito.reset import org.mockito.Mockito.verify @@ -58,13 +50,8 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() { private lateinit var fakeClock: FakeSystemClock private lateinit var fakeExecutor: FakeExecutor - private lateinit var appIconFromPackageName: Drawable @Mock - private lateinit var packageManager: PackageManager - @Mock - private lateinit var applicationInfo: ApplicationInfo - @Mock - private lateinit var logger: MediaTttLogger + private lateinit var logger: TemporaryViewLogger @Mock private lateinit var accessibilityManager: AccessibilityManager @Mock @@ -78,17 +65,6 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() { fun setUp() { MockitoAnnotations.initMocks(this) - appIconFromPackageName = context.getDrawable(R.drawable.ic_cake)!! - whenever(packageManager.getApplicationIcon(PACKAGE_NAME)).thenReturn(appIconFromPackageName) - whenever(applicationInfo.loadLabel(packageManager)).thenReturn(APP_NAME) - whenever(packageManager.getApplicationInfo( - any(), any<PackageManager.ApplicationInfoFlags>() - )).thenThrow(PackageManager.NameNotFoundException()) - whenever(packageManager.getApplicationInfo( - eq(PACKAGE_NAME), any<PackageManager.ApplicationInfoFlags>() - )).thenReturn(applicationInfo) - context.setMockPackageManager(packageManager) - whenever(accessibilityManager.getRecommendedTimeoutMillis(any(), any())) .thenReturn(TIMEOUT_MS.toInt()) @@ -229,117 +205,8 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() { verify(windowManager, never()).removeView(any()) } - @Test - fun setIcon_nullAppIconDrawableAndNullPackageName_stillHasIcon() { - underTest.displayView(getState()) - val view = getView() - - underTest.setIcon(view, appPackageName = null, appIconDrawableOverride = null) - - assertThat(view.getAppIconView().drawable).isNotNull() - } - - @Test - fun setIcon_nullAppIconDrawableAndInvalidPackageName_stillHasIcon() { - underTest.displayView(getState()) - val view = getView() - - underTest.setIcon( - view, appPackageName = "fakePackageName", appIconDrawableOverride = null - ) - - assertThat(view.getAppIconView().drawable).isNotNull() - } - - @Test - fun setIcon_nullAppIconDrawable_iconIsFromPackageName() { - underTest.displayView(getState()) - val view = getView() - - underTest.setIcon(view, PACKAGE_NAME, appIconDrawableOverride = null, null) - - assertThat(view.getAppIconView().drawable).isEqualTo(appIconFromPackageName) - } - - @Test - fun setIcon_hasAppIconDrawable_iconIsDrawable() { - underTest.displayView(getState()) - val view = getView() - - val drawable = context.getDrawable(R.drawable.ic_alarm)!! - underTest.setIcon(view, PACKAGE_NAME, drawable, null) - - assertThat(view.getAppIconView().drawable).isEqualTo(drawable) - } - - @Test - fun setIcon_nullAppNameAndNullPackageName_stillHasContentDescription() { - underTest.displayView(getState()) - val view = getView() - - underTest.setIcon(view, appPackageName = null, appNameOverride = null) - - assertThat(view.getAppIconView().contentDescription.toString()).isNotEmpty() - } - - @Test - fun setIcon_nullAppNameAndInvalidPackageName_stillHasContentDescription() { - underTest.displayView(getState()) - val view = getView() - - underTest.setIcon( - view, appPackageName = "fakePackageName", appNameOverride = null - ) - - assertThat(view.getAppIconView().contentDescription.toString()).isNotEmpty() - } - - @Test - fun setIcon_nullAppName_iconContentDescriptionIsFromPackageName() { - underTest.displayView(getState()) - val view = getView() - - underTest.setIcon(view, PACKAGE_NAME, null, appNameOverride = null) - - assertThat(view.getAppIconView().contentDescription).isEqualTo(APP_NAME) - } - - @Test - fun setIcon_hasAppName_iconContentDescriptionIsAppNameOverride() { - underTest.displayView(getState()) - val view = getView() - - val appName = "Override App Name" - underTest.setIcon(view, PACKAGE_NAME, null, appName) - - assertThat(view.getAppIconView().contentDescription).isEqualTo(appName) - } - - @Test - fun setIcon_iconSizeMatchesGetIconSize() { - underTest.displayView(getState()) - val view = getView() - - underTest.setIcon(view, PACKAGE_NAME) - view.measure( - View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), - View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED) - ) - - assertThat(view.getAppIconView().measuredWidth).isEqualTo(ICON_SIZE) - assertThat(view.getAppIconView().measuredHeight).isEqualTo(ICON_SIZE) - } - private fun getState(name: String = "name") = ViewInfo(name) - private fun getView(): ViewGroup { - val viewCaptor = ArgumentCaptor.forClass(View::class.java) - verify(windowManager).addView(viewCaptor.capture(), any()) - return viewCaptor.value as ViewGroup - } - - private fun ViewGroup.getAppIconView() = this.requireViewById<ImageView>(R.id.app_icon) - private fun getConfigurationListener(): ConfigurationListener { val callbackCaptor = argumentCaptor<ConfigurationListener>() verify(configurationController).addCallback(capture(callbackCaptor)) @@ -348,13 +215,13 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() { inner class TestController( context: Context, - logger: MediaTttLogger, + logger: TemporaryViewLogger, windowManager: WindowManager, @Main mainExecutor: DelayableExecutor, accessibilityManager: AccessibilityManager, configurationController: ConfigurationController, powerManager: PowerManager, - ) : TemporaryViewDisplayController<ViewInfo>( + ) : TemporaryViewDisplayController<ViewInfo, TemporaryViewLogger>( context, logger, windowManager, @@ -363,6 +230,8 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() { configurationController, powerManager, R.layout.media_ttt_chip, + "Window Title", + "WAKE_REASON", ) { var mostRecentViewInfo: ViewInfo? = null @@ -371,7 +240,6 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() { super.updateView(newInfo, currentView) mostRecentViewInfo = newInfo } - override fun getIconSize(isAppIcon: Boolean): Int = ICON_SIZE } inner class ViewInfo(val name: String) : TemporaryViewInfo { @@ -379,7 +247,4 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() { } } -private const val PACKAGE_NAME = "com.android.systemui" -private const val APP_NAME = "Fake App Name" private const val TIMEOUT_MS = 10000L -private const val ICON_SIZE = 47 diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewLoggerTest.kt new file mode 100644 index 000000000000..c9f2b4db81ef --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewLoggerTest.kt @@ -0,0 +1,70 @@ +/* + * 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.temporarydisplay + +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.dump.DumpManager +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.LogBufferFactory +import com.android.systemui.log.LogcatEchoTracker +import com.google.common.truth.Truth.assertThat +import java.io.PrintWriter +import java.io.StringWriter +import org.junit.Before +import org.junit.Test +import org.mockito.Mockito + +@SmallTest +class TemporaryViewLoggerTest : SysuiTestCase() { + private lateinit var buffer: LogBuffer + private lateinit var logger: TemporaryViewLogger + + @Before + fun setUp() { + buffer = + LogBufferFactory(DumpManager(), Mockito.mock(LogcatEchoTracker::class.java)) + .create("buffer", 10) + logger = TemporaryViewLogger(buffer, TAG) + } + + @Test + fun logChipAddition_bufferHasLog() { + logger.logChipAddition() + + val stringWriter = StringWriter() + buffer.dump(PrintWriter(stringWriter), tailLength = 0) + val actualString = stringWriter.toString() + + assertThat(actualString).contains(TAG) + } + + @Test + fun logChipRemoval_bufferHasTagAndReason() { + val reason = "test reason" + logger.logChipRemoval(reason) + + val stringWriter = StringWriter() + buffer.dump(PrintWriter(stringWriter), tailLength = 0) + val actualString = stringWriter.toString() + + assertThat(actualString).contains(TAG) + assertThat(actualString).contains(reason) + } +} + +private const val TAG = "TestTag" diff --git a/services/core/java/com/android/server/logcat/LogAccessDialogActivity.java b/services/core/java/com/android/server/logcat/LogAccessDialogActivity.java index 98bae3dc247b..811e96ce6d82 100644 --- a/services/core/java/com/android/server/logcat/LogAccessDialogActivity.java +++ b/services/core/java/com/android/server/logcat/LogAccessDialogActivity.java @@ -96,6 +96,7 @@ public class LogAccessDialogActivity extends Activity implements // show Alert mAlert = mAlertDialog.create(); + mAlert.getWindow().setHideOverlayWindows(true); mAlert.show(); // set Alert Timeout diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java index 0fac808fb13b..4d55d4e545ee 100644 --- a/services/core/java/com/android/server/notification/ManagedServices.java +++ b/services/core/java/com/android/server/notification/ManagedServices.java @@ -1786,8 +1786,8 @@ abstract public class ManagedServices { * from receiving events from the profile. */ public boolean isPermittedForProfile(int userId) { - if (!mUserProfiles.canProfileUseBoundServices(userId)) { - return false; + if (!mUserProfiles.isProfileUser(userId)) { + return true; } DevicePolicyManager dpm = (DevicePolicyManager) mContext.getSystemService(DEVICE_POLICY_SERVICE); @@ -1862,16 +1862,16 @@ abstract public class ManagedServices { } } - public boolean canProfileUseBoundServices(int userId) { + public boolean isProfileUser(int userId) { synchronized (mCurrentProfiles) { UserInfo user = mCurrentProfiles.get(userId); if (user == null) { return false; } if (user.isManagedProfile() || user.isCloneProfile()) { - return false; + return true; } - return true; + return false; } } } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 4e0cc61a061f..f11801f61d58 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -1828,7 +1828,7 @@ public class NotificationManagerService extends SystemService { } else if (action.equals(Intent.ACTION_USER_SWITCHED)) { final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, USER_NULL); mUserProfiles.updateCache(context); - if (mUserProfiles.canProfileUseBoundServices(userId)) { + if (!mUserProfiles.isProfileUser(userId)) { // reload per-user settings mSettingsObserver.update(null); // Refresh managed services @@ -1842,7 +1842,7 @@ public class NotificationManagerService extends SystemService { final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, USER_NULL); if (userId != USER_NULL) { mUserProfiles.updateCache(context); - if (mUserProfiles.canProfileUseBoundServices(userId)) { + if (!mUserProfiles.isProfileUser(userId)) { allowDefaultApprovedServices(userId); } } @@ -1860,7 +1860,7 @@ public class NotificationManagerService extends SystemService { final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, USER_NULL); mUserProfiles.updateCache(context); mAssistants.onUserUnlocked(userId); - if (mUserProfiles.canProfileUseBoundServices(userId)) { + if (!mUserProfiles.isProfileUser(userId)) { mConditionProviders.onUserUnlocked(userId); mListeners.onUserUnlocked(userId); mZenModeHelper.onUserUnlocked(userId); diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java index c3b479219853..0915c21dda59 100644 --- a/services/core/java/com/android/server/pm/DeletePackageHelper.java +++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java @@ -657,6 +657,18 @@ final class DeletePackageHelper { final String packageName = versionedPackage.getPackageName(); final long versionCode = versionedPackage.getLongVersionCode(); + if (mPm.mProtectedPackages.isPackageDataProtected(userId, packageName)) { + mPm.mHandler.post(() -> { + try { + Slog.w(TAG, "Attempted to delete protected package: " + packageName); + observer.onPackageDeleted(packageName, + PackageManager.DELETE_FAILED_INTERNAL_ERROR, null); + } catch (RemoteException re) { + } + }); + return; + } + try { if (mPm.mInjector.getLocalService(ActivityTaskManagerInternal.class) .isBaseOfLockedTask(packageName)) { diff --git a/services/core/java/com/android/server/wm/AsyncRotationController.java b/services/core/java/com/android/server/wm/AsyncRotationController.java index 219092baface..1266db5bff98 100644 --- a/services/core/java/com/android/server/wm/AsyncRotationController.java +++ b/services/core/java/com/android/server/wm/AsyncRotationController.java @@ -357,7 +357,12 @@ class AsyncRotationController extends FadeAnimationController implements Consume * or seamless transformation in a rotated display. */ boolean shouldFreezeInsetsPosition(WindowState w) { - return mTransitionOp != OP_LEGACY && w.mTransitionController.inTransition() + if (TransitionController.SYNC_METHOD != BLASTSyncEngine.METHOD_BLAST) { + // Expect a screenshot layer has covered the screen, so it is fine to let client side + // insets animation runner update the position directly. + return false; + } + return mTransitionOp != OP_LEGACY && !mIsStartTransactionCommitted && isTargetToken(w.mToken); } diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index b093d2566710..95d71a36313f 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -245,6 +245,7 @@ import com.android.internal.util.function.pooled.PooledLambda; import com.android.internal.util.function.pooled.PooledPredicate; import com.android.server.inputmethod.InputMethodManagerInternal; import com.android.server.policy.WindowManagerPolicy; +import com.android.server.wm.utils.RegionUtils; import com.android.server.wm.utils.RotationCache; import com.android.server.wm.utils.WmDisplayCutout; @@ -5780,6 +5781,12 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp if (w.isVisible() && !w.inPinnedWindowingMode()) { w.getKeepClearAreas(outRestricted, outUnrestricted, tmpMatrix, tmpFloat9); + + if (w.mIsImWindow) { + Region touchableRegion = Region.obtain(); + w.getEffectiveTouchableRegion(touchableRegion); + RegionUtils.forEachRect(touchableRegion, rect -> outUnrestricted.add(rect)); + } } // We stop traversing when we reach the base of a fullscreen app. diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index 6650f43c15ae..488fe676d265 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -46,8 +46,9 @@ import static android.view.WindowManager.TransitionFlags; import static android.view.WindowManager.TransitionType; import static android.view.WindowManager.transitTypeToString; import static android.window.TransitionInfo.FLAG_DISPLAY_HAS_ALERT_WINDOWS; +import static android.window.TransitionInfo.FLAG_FILLS_TASK; +import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY; import static android.window.TransitionInfo.FLAG_IS_DISPLAY; -import static android.window.TransitionInfo.FLAG_IS_EMBEDDED; import static android.window.TransitionInfo.FLAG_IS_INPUT_METHOD; import static android.window.TransitionInfo.FLAG_IS_VOICE_INTERACTION; import static android.window.TransitionInfo.FLAG_IS_WALLPAPER; @@ -1873,8 +1874,10 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe flags |= FLAG_WILL_IME_SHOWN; } } + Task parentTask = null; final ActivityRecord record = wc.asActivityRecord(); if (record != null) { + parentTask = record.getTask(); if (record.mUseTransferredAnimation) { flags |= FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT; } @@ -1882,6 +1885,26 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe flags |= FLAG_IS_VOICE_INTERACTION; } } + final TaskFragment taskFragment = wc.asTaskFragment(); + if (taskFragment != null && task == null) { + parentTask = taskFragment.getTask(); + } + if (parentTask != null) { + if (parentTask.forAllLeafTaskFragments(TaskFragment::isEmbedded)) { + // Whether this is in a Task with embedded activity. + flags |= FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY; + } + final Rect taskBounds = parentTask.getBounds(); + final Rect startBounds = mAbsoluteBounds; + final Rect endBounds = wc.getBounds(); + if (taskBounds.width() == startBounds.width() + && taskBounds.height() == startBounds.height() + && taskBounds.width() == endBounds.width() + && taskBounds.height() == endBounds.height()) { + // Whether the container fills the Task bounds before and after the transition. + flags |= FLAG_FILLS_TASK; + } + } final DisplayContent dc = wc.asDisplayContent(); if (dc != null) { flags |= FLAG_IS_DISPLAY; @@ -1898,9 +1921,6 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe if (occludesKeyguard(wc)) { flags |= FLAG_OCCLUDES_KEYGUARD; } - if (wc.isEmbedded()) { - flags |= FLAG_IS_EMBEDDED; - } return flags; } } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java index 49879efe4b51..798604306b43 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java @@ -1704,8 +1704,8 @@ public class ManagedServicesTest extends UiServiceTestCase { } @Test - public void testInfoIsPermittedForProfile_notAllowed() { - when(mUserProfiles.canProfileUseBoundServices(anyInt())).thenReturn(false); + public void testInfoIsPermittedForProfile_notProfile() { + when(mUserProfiles.isProfileUser(anyInt())).thenReturn(false); IInterface service = mock(IInterface.class); when(service.asBinder()).thenReturn(mock(IBinder.class)); @@ -1714,12 +1714,12 @@ public class ManagedServicesTest extends UiServiceTestCase { services.registerSystemService(service, null, 10, 1000); ManagedServices.ManagedServiceInfo info = services.checkServiceTokenLocked(service); - assertFalse(info.isPermittedForProfile(0)); + assertTrue(info.isPermittedForProfile(0)); } @Test - public void testInfoIsPermittedForProfile_allows() { - when(mUserProfiles.canProfileUseBoundServices(anyInt())).thenReturn(true); + public void testInfoIsPermittedForProfile_profileAndDpmAllows() { + when(mUserProfiles.isProfileUser(anyInt())).thenReturn(true); when(mDpm.isNotificationListenerServicePermitted(anyString(), anyInt())).thenReturn(true); IInterface service = mock(IInterface.class); @@ -1734,6 +1734,22 @@ public class ManagedServicesTest extends UiServiceTestCase { } @Test + public void testInfoIsPermittedForProfile_profileAndDpmDenies() { + when(mUserProfiles.isProfileUser(anyInt())).thenReturn(true); + when(mDpm.isNotificationListenerServicePermitted(anyString(), anyInt())).thenReturn(false); + + IInterface service = mock(IInterface.class); + when(service.asBinder()).thenReturn(mock(IBinder.class)); + ManagedServices services = new TestManagedServices(getContext(), mLock, mUserProfiles, + mIpm, APPROVAL_BY_PACKAGE); + services.registerSystemService(service, null, 10, 1000); + ManagedServices.ManagedServiceInfo info = services.checkServiceTokenLocked(service); + info.component = new ComponentName("a","b"); + + assertFalse(info.isPermittedForProfile(0)); + } + + @Test public void testUserProfiles_canProfileUseBoundServices_managedProfile() { List<UserInfo> users = new ArrayList<>(); UserInfo profile = new UserInfo(ActivityManager.getCurrentUser(), "current", 0); @@ -1750,9 +1766,9 @@ public class ManagedServicesTest extends UiServiceTestCase { ManagedServices.UserProfiles profiles = new ManagedServices.UserProfiles(); profiles.updateCache(mContext); - assertTrue(profiles.canProfileUseBoundServices(ActivityManager.getCurrentUser())); - assertFalse(profiles.canProfileUseBoundServices(12)); - assertFalse(profiles.canProfileUseBoundServices(13)); + assertFalse(profiles.isProfileUser(ActivityManager.getCurrentUser())); + assertTrue(profiles.isProfileUser(12)); + assertTrue(profiles.isProfileUser(13)); } private void resetComponentsAndPackages() { diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java index 8f186e40e201..8cf32baa49eb 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java @@ -19,6 +19,7 @@ package com.android.server.wm; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET; import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS; @@ -31,7 +32,8 @@ import static android.view.WindowManager.TRANSIT_CHANGE; import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_OPEN; import static android.view.WindowManager.TRANSIT_TO_BACK; -import static android.window.TransitionInfo.FLAG_IS_EMBEDDED; +import static android.window.TransitionInfo.FLAG_FILLS_TASK; +import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY; import static android.window.TransitionInfo.FLAG_IS_WALLPAPER; import static android.window.TransitionInfo.FLAG_SHOW_WALLPAPER; import static android.window.TransitionInfo.FLAG_TRANSLUCENT; @@ -727,7 +729,7 @@ public class TransitionTests extends WindowTestsBase { assertTrue(ime.mToken.inTransition()); assertTrue(task.inTransition()); assertTrue(asyncRotationController.isTargetToken(decorToken)); - assertTrue(asyncRotationController.shouldFreezeInsetsPosition(navBar)); + assertShouldFreezeInsetsPosition(asyncRotationController, statusBar, true); screenDecor.setOrientationChanging(false); // Status bar finishes drawing before the start transaction. Its fade-in animation will be @@ -742,6 +744,7 @@ public class TransitionTests extends WindowTestsBase { // The transaction is committed, so fade-in animation for status bar is consumed. transactionCommittedListener.onTransactionCommitted(); assertFalse(asyncRotationController.isTargetToken(statusBar.mToken)); + assertShouldFreezeInsetsPosition(asyncRotationController, navBar, false); // Navigation bar finishes drawing after the start transaction, so its fade-in animation // can execute directly. @@ -777,7 +780,7 @@ public class TransitionTests extends WindowTestsBase { final AsyncRotationController asyncRotationController = mDisplayContent.getAsyncRotationController(); assertNotNull(asyncRotationController); - assertTrue(asyncRotationController.shouldFreezeInsetsPosition(statusBar)); + assertShouldFreezeInsetsPosition(asyncRotationController, statusBar, true); statusBar.setOrientationChanging(true); player.startTransition(); @@ -823,7 +826,7 @@ public class TransitionTests extends WindowTestsBase { final AsyncRotationController asyncRotationController = mDisplayContent.getAsyncRotationController(); assertNotNull(asyncRotationController); - assertTrue(asyncRotationController.shouldFreezeInsetsPosition(statusBar)); + assertShouldFreezeInsetsPosition(asyncRotationController, statusBar, true); assertTrue(app.getTask().inTransition()); player.start(); @@ -858,6 +861,15 @@ public class TransitionTests extends WindowTestsBase { assertNull(mDisplayContent.getAsyncRotationController()); } + private static void assertShouldFreezeInsetsPosition(AsyncRotationController controller, + WindowState w, boolean freeze) { + if (TransitionController.SYNC_METHOD != BLASTSyncEngine.METHOD_BLAST) { + // Non blast sync should never freeze insets position. + freeze = false; + } + assertEquals(freeze, controller.shouldFreezeInsetsPosition(w)); + } + @Test public void testDeferRotationForTransientLaunch() { final TestTransitionPlayer player = registerTestTransitionPlayer(); @@ -1062,12 +1074,14 @@ public class TransitionTests extends WindowTestsBase { } @Test - public void testIsEmbeddedChange() { + public void testFlagInTaskWithEmbeddedActivity() { final Transition transition = createTestTransition(TRANSIT_OPEN); final ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges; final ArraySet<WindowContainer> participants = transition.mParticipants; final Task task = createTask(mDisplayContent); + final ActivityRecord nonEmbeddedActivity = createActivityRecord(task); + assertFalse(nonEmbeddedActivity.isEmbedded()); final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run); mAtm.mTaskFragmentOrganizerController.registerOrganizer( ITaskFragmentOrganizer.Stub.asInterface(organizer.getOrganizerToken().asBinder())); @@ -1082,20 +1096,72 @@ public class TransitionTests extends WindowTestsBase { changes.put(embeddedTf, new Transition.ChangeInfo(true /* vis */, false /* exChg */)); changes.put(closingActivity, new Transition.ChangeInfo(true /* vis */, false /* exChg */)); changes.put(openingActivity, new Transition.ChangeInfo(false /* vis */, true /* exChg */)); + changes.put(nonEmbeddedActivity, new Transition.ChangeInfo(true /* vis */, + false /* exChg */)); // End states. closingActivity.mVisibleRequested = false; openingActivity.mVisibleRequested = true; + nonEmbeddedActivity.mVisibleRequested = false; participants.add(closingActivity); participants.add(openingActivity); + participants.add(nonEmbeddedActivity); + final ArrayList<WindowContainer> targets = Transition.calculateTargets( + participants, changes); + final TransitionInfo info = Transition.calculateTransitionInfo( + transition.mType, 0 /* flags */, targets, changes, mMockT); + + // All windows in the Task should have FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY because the Task + // contains embedded activity. + assertEquals(3, info.getChanges().size()); + assertTrue(info.getChanges().get(0).hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)); + assertTrue(info.getChanges().get(1).hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)); + assertTrue(info.getChanges().get(2).hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)); + } + + @Test + public void testFlagFillsTask() { + final Transition transition = createTestTransition(TRANSIT_OPEN); + final ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges; + final ArraySet<WindowContainer> participants = transition.mParticipants; + + final Task task = createTask(mDisplayContent); + // Set to multi-windowing mode in order to set bounds. + task.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); + final Rect taskBounds = new Rect(0, 0, 500, 1000); + task.setBounds(taskBounds); + final ActivityRecord nonEmbeddedActivity = createActivityRecord(task); + final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run); + mAtm.mTaskFragmentOrganizerController.registerOrganizer( + ITaskFragmentOrganizer.Stub.asInterface(organizer.getOrganizerToken().asBinder())); + final TaskFragment embeddedTf = new TaskFragmentBuilder(mAtm) + .setParentTask(task) + .createActivityCount(1) + .setOrganizer(organizer) + .build(); + final ActivityRecord embeddedActivity = embeddedTf.getTopMostActivity(); + // Start states. + changes.put(task, new Transition.ChangeInfo(true /* vis */, false /* exChg */)); + changes.put(nonEmbeddedActivity, new Transition.ChangeInfo(true /* vis */, + false /* exChg */)); + changes.put(embeddedTf, new Transition.ChangeInfo(false /* vis */, true /* exChg */)); + // End states. + nonEmbeddedActivity.mVisibleRequested = false; + embeddedActivity.mVisibleRequested = true; + embeddedTf.setBounds(new Rect(0, 0, 500, 500)); + + participants.add(nonEmbeddedActivity); + participants.add(embeddedTf); final ArrayList<WindowContainer> targets = Transition.calculateTargets( participants, changes); final TransitionInfo info = Transition.calculateTransitionInfo( transition.mType, 0 /* flags */, targets, changes, mMockT); + // The embedded with bounds overridden should not have the flag. assertEquals(2, info.getChanges().size()); - assertTrue((info.getChanges().get(0).getFlags() & FLAG_IS_EMBEDDED) != 0); - assertTrue((info.getChanges().get(1).getFlags() & FLAG_IS_EMBEDDED) != 0); + assertFalse(info.getChanges().get(0).hasFlags(FLAG_FILLS_TASK)); + assertEquals(embeddedTf.getBounds(), info.getChanges().get(0).getEndAbsBounds()); + assertFalse(info.getChanges().get(1).hasFlags(FLAG_FILLS_TASK)); } @Test diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/GameAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/GameAppHelper.kt new file mode 100644 index 000000000000..d11ca4950d16 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/GameAppHelper.kt @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm.flicker.helpers + +import android.app.Instrumentation +import android.support.test.launcherhelper.ILauncherStrategy +import android.support.test.launcherhelper.LauncherStrategyFactory +import androidx.test.uiautomator.By +import androidx.test.uiautomator.Direction +import androidx.test.uiautomator.Until +import com.android.server.wm.flicker.testapp.ActivityOptions +import com.android.server.wm.traces.common.FlickerComponentName +import com.android.server.wm.traces.parser.toFlickerComponent +import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper + +class GameAppHelper @JvmOverloads constructor( + instr: Instrumentation, + launcherName: String = ActivityOptions.GAME_ACTIVITY_LAUNCHER_NAME, + component: FlickerComponentName = + ActivityOptions.GAME_ACTIVITY_COMPONENT_NAME.toFlickerComponent(), + launcherStrategy: ILauncherStrategy = + LauncherStrategyFactory.getInstance(instr).launcherStrategy, +) : StandardAppHelper(instr, launcherName, component, launcherStrategy) { + + /** + * Swipes down in the mock game app. + * + * @return true if the swipe operation is successful. + */ + fun swipeDown(): Boolean { + val gameView = uiDevice.wait( + Until.findObject(By.res(getPackage(), GAME_APP_VIEW_RES)), WAIT_TIME_MS) + require(gameView != null) { "Mock game app view not found." } + + val bound = gameView.getVisibleBounds() + return uiDevice.swipe( + bound.centerX(), bound.top, bound.centerX(), bound.centerY(), SWIPE_STEPS) + } + + /** + * Switches to a recent app by quick switch gesture. This function can be used in both portrait + * and landscape mode. + * + * @param wmHelper Helper used to get window region. + * @param direction UiAutomator Direction enum to indicate the swipe direction. + * + * @return true if the swipe operation is successful. + */ + fun switchToPreviousAppByQuickSwitchGesture( + wmHelper: WindowManagerStateHelper, + direction: Direction + ): Boolean { + val ratioForScreenBottom = 0.97 + val fullView = wmHelper.getWindowRegion(component) + require(!fullView.isEmpty) { "Target $component view not found." } + + val bound = fullView.bounds + val targetYPos = bound.bottom * ratioForScreenBottom + val endX = when (direction) { + Direction.LEFT -> bound.left + Direction.RIGHT -> bound.right + else -> { + throw IllegalStateException("Only left or right direction is allowed.") + } + } + return uiDevice.swipe( + bound.centerX(), targetYPos.toInt(), endX, targetYPos.toInt(), SWIPE_STEPS) + } + + /** + * Waits for view idel with timeout, then checkes the target object whether visible on screen. + * + * @param packageName The targe application's package name. + * @param identifier The resource id of the target object. + * @param timeout The timeout duration in milliseconds. + * + * @return true if the target object exists. + */ + @JvmOverloads + fun isTargetObjVisible( + packageName: String, + identifier: String, + timeout: Long = WAIT_TIME_MS + ): Boolean { + uiDevice.waitForIdle(timeout) + return uiDevice.hasObject(By.res(packageName, identifier)) + } + + companion object { + private const val GAME_APP_VIEW_RES = "container" + private const val WAIT_TIME_MS = 3_000L + private const val SWIPE_STEPS = 30 + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/MailAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/MailAppHelper.kt new file mode 100644 index 000000000000..3385784de8af --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/MailAppHelper.kt @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm.flicker.helpers + +import android.app.Instrumentation +import android.support.test.launcherhelper.ILauncherStrategy +import android.support.test.launcherhelper.LauncherStrategyFactory +import androidx.test.uiautomator.By +import androidx.test.uiautomator.Direction +import androidx.test.uiautomator.UiObject2 +import androidx.test.uiautomator.Until +import com.android.server.wm.flicker.testapp.ActivityOptions +import com.android.server.wm.traces.common.FlickerComponentName +import com.android.server.wm.traces.parser.toFlickerComponent + +class MailAppHelper @JvmOverloads constructor( + instr: Instrumentation, + launcherName: String = ActivityOptions.MAIL_ACTIVITY_LAUNCHER_NAME, + component: FlickerComponentName = + ActivityOptions.MAIL_ACTIVITY_COMPONENT_NAME.toFlickerComponent(), + launcherStrategy: ILauncherStrategy = LauncherStrategyFactory + .getInstance(instr) + .launcherStrategy +) : StandardAppHelper(instr, launcherName, component, launcherStrategy) { + + fun openMail(rowIdx: Int) { + val rowSel = By.res(getPackage(), "mail_row_item_text") + .textEndsWith(String.format("%04d", rowIdx)) + var row: UiObject2? = null + for (i in 1..1000) { + row = uiDevice.wait(Until.findObject(rowSel), SHORT_WAIT_TIME_MS) + if (row != null) break + scrollDown() + } + require(row != null) {""} + row.click() + uiDevice.wait(Until.gone(By.res(getPackage(), MAIL_LIST_RES_ID)), FIND_TIMEOUT) + } + + fun scrollDown() { + val listView = waitForMailList() + listView.scroll(Direction.DOWN, 1.0f) + } + + fun waitForMailList(): UiObject2 { + var sel = By.res(getPackage(), MAIL_LIST_RES_ID).scrollable(true) + val ret = uiDevice.wait(Until.findObject(sel), FIND_TIMEOUT) + require(ret != null) {""} + return ret + } + + companion object { + private const val SHORT_WAIT_TIME_MS = 5000L + private const val MAIL_LIST_RES_ID = "mail_recycle_view" + } +} diff --git a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml index 66ad6f1263e4..b8ef1954d5fc 100644 --- a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml +++ b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml @@ -185,5 +185,34 @@ <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity> + <service + android:name=".AssistantInteractionSessionService" + android:exported="true" + android:permission="android.permission.BIND_VOICE_INTERACTION" /> + <service + android:name=".AssistantRecognitionService" + android:exported="true" + android:label="Test Voice Interaction Service"> + <intent-filter> + <action android:name="android.speech.RecognitionService" /> + <category android:name="android.intent.category.DEFAULT" /> + </intent-filter> + <meta-data + android:name="android.speech" + android:resource="@xml/recognition_service" /> + </service> + <service + android:name=".AssistantInteractionService" + android:exported="true" + android:label="Test Voice Interaction Service" + android:permission="android.permission.BIND_VOICE_INTERACTION"> + <intent-filter> + <action android:name="android.service.voice.VoiceInteractionService" /> + </intent-filter> + <meta-data + android:name="android.voice_interaction" + android:resource="@xml/interaction_service" /> + </service> </application> + <uses-permission android:name="android.permission.ACTIVITY_RECOGNITION" /> </manifest> diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/assistant_session.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/assistant_session.xml new file mode 100644 index 000000000000..eb7f3074ebfb --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/assistant_session.xml @@ -0,0 +1,26 @@ +<!-- + ~ 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. + --> + +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent"> + <FrameLayout + android:id="@+id/vis_frame" + android:layout_width="match_parent" + android:layout_height="300dp" + android:layout_gravity="bottom" + android:background="#37474F"/> +</FrameLayout> diff --git a/tests/FlickerTests/test-apps/flickerapp/res/xml/interaction_service.xml b/tests/FlickerTests/test-apps/flickerapp/res/xml/interaction_service.xml new file mode 100644 index 000000000000..2e661fbd3122 --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/res/xml/interaction_service.xml @@ -0,0 +1,20 @@ +<!-- + ~ 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. + --> + +<voice-interaction-service xmlns:android="http://schemas.android.com/apk/res/android" + android:recognitionService="com.android.server.wm.flicker.testapp.AssistantRecognitionService" + android:sessionService="com.android.server.wm.flicker.testapp.AssistantInteractionSessionService" + android:supportsAssist="true" /> diff --git a/tests/FlickerTests/test-apps/flickerapp/res/xml/recognition_service.xml b/tests/FlickerTests/test-apps/flickerapp/res/xml/recognition_service.xml new file mode 100644 index 000000000000..2e124982084f --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/res/xml/recognition_service.xml @@ -0,0 +1,17 @@ +<!-- + ~ 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. + --> + +<recognition-service xmlns:android="http://schemas.android.com/apk/res/android" /> diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java index 42a37df191dc..45a47303990c 100644 --- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java @@ -88,6 +88,10 @@ public class ActivityOptions { new ComponentName(FLICKER_APP_PACKAGE, FLICKER_APP_PACKAGE + ".NotificationActivity"); + public static final String MAIL_ACTIVITY_LAUNCHER_NAME = "MailActivity"; + public static final ComponentName MAIL_ACTIVITY_COMPONENT_NAME = new ComponentName( + FLICKER_APP_PACKAGE, FLICKER_APP_PACKAGE + ".MailActivity"); + public static final String GAME_ACTIVITY_LAUNCHER_NAME = "GameApp"; public static final ComponentName GAME_ACTIVITY_COMPONENT_NAME = new ComponentName(FLICKER_APP_PACKAGE, diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/AssistantInteractionService.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/AssistantInteractionService.java new file mode 100644 index 000000000000..d60143cdf40a --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/AssistantInteractionService.java @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm.flicker.testapp; + +import android.service.voice.VoiceInteractionService; + +public class AssistantInteractionService extends VoiceInteractionService { +} diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/AssistantInteractionSession.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/AssistantInteractionSession.java new file mode 100644 index 000000000000..d2c9b37704b8 --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/AssistantInteractionSession.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm.flicker.testapp; + +import android.content.Context; +import android.graphics.Color; +import android.os.Bundle; +import android.service.voice.VoiceInteractionSession; +import android.view.View; +import android.view.Window; + +public class AssistantInteractionSession extends VoiceInteractionSession { + + public AssistantInteractionSession(Context context) { + super(context); + } + + @Override + public void onCreate() { + Window window = getWindow().getWindow(); + if (window != null) { + window.getDecorView().setBackgroundColor(Color.TRANSPARENT); + + } + View rootView = getLayoutInflater().inflate(R.layout.assistant_session, null); + setContentView(rootView); + setUiEnabled(false); + } + + @Override + public void onShow(Bundle args, int showFlags) { + setUiEnabled(true); + } + + @Override + public void onHide() { + setUiEnabled(false); + } +} diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/AssistantInteractionSessionService.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/AssistantInteractionSessionService.java new file mode 100644 index 000000000000..4d6125c9a5d8 --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/AssistantInteractionSessionService.java @@ -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.server.wm.flicker.testapp; + +import android.os.Bundle; +import android.service.voice.VoiceInteractionSession; +import android.service.voice.VoiceInteractionSessionService; + +public class AssistantInteractionSessionService extends VoiceInteractionSessionService { + @Override + public VoiceInteractionSession onNewSession(Bundle args) { + return new AssistantInteractionSession(this); + } +} diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/AssistantRecognitionService.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/AssistantRecognitionService.java new file mode 100644 index 000000000000..68aae4520fe9 --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/AssistantRecognitionService.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm.flicker.testapp; + +import android.content.Intent; +import android.speech.RecognitionService; + +public class AssistantRecognitionService extends RecognitionService { + @Override + protected void onStartListening(Intent recognizerIntent, Callback listener) { + + } + + @Override + protected void onCancel(Callback listener) { + + } + + @Override + protected void onStopListening(Callback listener) { + + } +} |