Merge "Expose getTaskInfo on TaskView" into tm-qpr-dev
diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java
index ec1c57d..f7d4be3 100644
--- a/core/java/android/os/VibrationEffect.java
+++ b/core/java/android/os/VibrationEffect.java
@@ -830,29 +830,8 @@
* .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 @@
*
* @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 @@
* @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 @@
* @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 ec6b4ac..e4caa38 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -3064,7 +3064,8 @@
// 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 @@
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 285a407..fadad99 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -709,7 +709,7 @@
}
getPositionListener().addSubscriber(mCursorAnchorInfoNotifier, true);
- resumeBlink();
+ makeBlink();
}
void onDetachedFromWindow() {
@@ -1685,17 +1685,12 @@
void onWindowFocusChanged(boolean hasWindowFocus) {
if (hasWindowFocus) {
- if (mBlink != null) {
- mBlink.uncancel();
- makeBlink();
- }
+ resumeBlink();
if (mTextView.hasSelection() && !extractedTextModeWillBeStarted()) {
refreshTextActionMode();
}
} else {
- if (mBlink != null) {
- mBlink.cancel();
- }
+ suspendBlink();
if (mInputContentType != null) {
mInputContentType.enterDown = false;
}
@@ -2851,7 +2846,8 @@
* @return True when the TextView isFocused and has a valid zero-length selection (cursor).
*/
private boolean shouldBlink() {
- if (!isCursorVisible() || !mTextView.isFocused()) return false;
+ if (!isCursorVisible() || !mTextView.isFocused()
+ || mTextView.getWindowVisibility() != mTextView.VISIBLE) return false;
final int start = mTextView.getSelectionStart();
if (start < 0) return false;
@@ -2873,6 +2869,17 @@
}
}
+ /**
+ *
+ * @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 8ca763e..33ea2e4 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -110,11 +110,11 @@
/** 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 @@
/** 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 @@
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 @@
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_IS_EMBEDDED) != 0) {
- sb.append((sb.length() == 0 ? "" : "|") + "IS_EMBEDDED");
+ if ((flags & FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY) != 0) {
+ sb.append(sb.length() == 0 ? "" : "|").append("IN_TASK_WITH_EMBEDDED_ACTIVITY");
+ }
+ 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/activityembedding/ActivityEmbeddingController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java
index e0004fc..521a65c 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 @@
@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 @@
}
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 aeaf6ed..be100bb 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 @@
}
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 063dac3..ab194df 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.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 @@
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 @@
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 @@
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 @@
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/splitscreen/MainStage.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java
index b0080b2..e7ec15e 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 @@
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 86efbe0..8639b36 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 @@
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 946dfde..db35b48 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.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 @@
@StageType
private int mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED;
+ private DefaultMixedHandler mMixedHandler;
+
private final SplitWindowManager.ParentContainerCallbacks mParentContainerCallbacks =
new SplitWindowManager.ParentContainerCallbacks() {
@Override
@@ -326,6 +329,10 @@
transitions.addHandler(this);
}
+ public void setMixedHandler(DefaultMixedHandler mixedHandler) {
+ mMixedHandler = mixedHandler;
+ }
+
@VisibleForTesting
SplitScreenTransitions getSplitTransitions() {
return mSplitTransitions;
@@ -927,13 +934,10 @@
// 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 @@
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 @@
// 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 @@
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 1af9415..6b90eab 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 @@
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 bcf4fbd..3cba929 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 @@
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 @@
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 @@
mPipHandler = pipTouchHandlerOptional.get().getTransitionHandler();
mSplitHandler = splitScreenControllerOptional.get().getTransitionHandler();
mPlayer.addHandler(this);
+ if (mSplitHandler != null) {
+ mSplitHandler.setMixedHandler(this);
+ }
}, this);
}
}
@@ -122,10 +130,12 @@
}
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 @@
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 @@
+ "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 @@
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 @@
} 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 b2e45a6..a7234c1 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 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 84befdd..3792e83 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 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 @@
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 cf43b00..baecf6f 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.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 @@
@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 @@
@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 @@
}
@Test
- public void testStartAnimation_onlyActivityEmbeddingChange() {
+ public void testStartAnimation_containsOnlyFillTaskActivityEmbeddingChange() {
final TransitionInfo info = new TransitionInfo(TRANSIT_OPEN, 0);
- final TransitionInfo.Change embeddingChange = createChange();
- embeddingChange.setFlags(FLAG_IS_EMBEDDED);
+ final TransitionInfo.Change embeddingChange = createEmbeddedChange(TASK_BOUNDS, TASK_BOUNDS,
+ TASK_BOUNDS);
info.addChange(embeddingChange);
- // No-op
+ // 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 = createEmbeddedChange(EMBEDDED_RIGHT_BOUNDS,
+ TASK_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,
@@ -115,8 +171,8 @@
() -> 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/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
index ea9390e..9240abf 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 @@
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 @@
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 3152e65..fa056e2b 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 @@
* {@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 @@
*
* @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 @@
* {@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 @@
* {@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 @@
* @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 @@
* {@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 @@
* {@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 51812f0..a9f4e9c 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.telephony.TelephonyManager;
import android.util.Log;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
@@ -193,19 +194,19 @@
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 @@
}
@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 @@
}
}
- 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 @@
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 7d4dcf8..ebabdf57 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 @@
* 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 @@
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 eac5d275..9656b8a 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 @@
}
}
- override fun onLaunchAnimationCancelled() {
+ override fun onLaunchAnimationCancelled(newKeyguardOccludedState: Boolean?) {
controller.onLaunchAnimationCancelled()
enableDialogDismiss()
dialog.dismiss()
diff --git a/packages/SystemUI/ktfmt_includes.txt b/packages/SystemUI/ktfmt_includes.txt
index 689938a..8182484 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 1aaf19e..c50340c 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 @@
}
/**
- * 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 0000000..732fc57
--- /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/layout/keyguard_esim_area.xml b/packages/SystemUI/res-keyguard/layout/keyguard_esim_area.xml
index db508c9..67fd5b9 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 f2fe520..7db0fe9 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 a21ec29..422bd4c 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 e80cfaf..a1068c6 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 ac131ae..46f6ab2 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 0000000..ccfb6ea
--- /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 0000000..57777a6
--- /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 0000000..b376413
--- /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/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index c827e21..75baeef 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 a802723..37549c9 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 1b169e2..eb6c457 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 b3b75f6..34e2e83 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 @@
// 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 @@
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 b5e5766..19ac2e4 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 @@
override lateinit var animations: ClockAnimations
private set
+ private var smallRegionDarkness = false
+ private var largeRegionDarkness = false
+
init {
val parent = FrameLayout(ctx)
@@ -148,8 +151,14 @@
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 0000000..30c062b
--- /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 0000000..43a882a5
--- /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 0000000..b4bfca1
--- /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 ae9d3df..9d17015 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java
@@ -18,21 +18,14 @@
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 @@
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 @@
@Override
protected void onFinishInflate() {
- mSimImageView = findViewById(R.id.keyguard_sim);
super.onFinishInflate();
if (mEcaView instanceof EmergencyCarrierArea) {
@@ -86,17 +72,4 @@
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 ecd88e6..76f7d78 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
@@ -105,7 +105,7 @@
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 c0971bf..5f45fe3 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java
@@ -19,13 +19,8 @@
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 @@
/**
* 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 @@
@Override
protected void onFinishInflate() {
- mSimImageView = findViewById(R.id.keyguard_sim);
super.onFinishInflate();
if (mEcaView instanceof EmergencyCarrierArea) {
@@ -104,18 +97,4 @@
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 203f9b6..d8cffd7 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.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 @@
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 763b29e..32c1cf9 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -157,6 +157,7 @@
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.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 @@
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 @@
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 @@
@Override
public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) {
+ if (mFaceAcquiredInfoIgnoreList.contains(helpMsgId)) {
+ return;
+ }
handleFaceHelp(helpMsgId, helpString.toString());
}
@@ -1931,6 +1938,11 @@
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 0000000..2c2ab7b
--- /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 f2d8aaa..0000000
--- 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 0000000..fabc1c1
--- /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 0000000..96af42b
--- /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 31a2134..bfbf37a 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 @@
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 @@
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 23d87ff..f5f9655 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 @@
@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/dreams/complication/DoubleShadowTextClock.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/DoubleShadowTextClock.java
index 653f4dc..789ebc5 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 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 0000000..b1dc5a2
--- /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 0000000..cf7e312
--- /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 a3dc779..568143c 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.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 @@
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/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
index bd75ab2..6f38f4f 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 @@
*/
@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 @@
* (fingerprint, tap, etc.) and the keyguard is going away.
*/
fun notifyStartSurfaceBehindRemoteAnimation(
- target: RemoteAnimationTarget,
+ targets: Array<RemoteAnimationTarget>,
startTime: Long,
requestedShowSurfaceBehindKeyguard: Boolean
) {
@@ -467,10 +466,7 @@
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 @@
* keyguard dismiss amount and the method of dismissal.
*/
private fun updateSurfaceBehindAppearAmount() {
- if (surfaceBehindRemoteAnimationTarget == null) {
+ if (surfaceBehindRemoteAnimationTargets == null) {
return
}
@@ -715,63 +711,60 @@
* cancelled).
*/
fun setSurfaceBehindAppearAmount(amount: Float) {
- if (surfaceBehindRemoteAnimationTarget == null) {
- return
- }
+ surfaceBehindRemoteAnimationTargets?.forEach { surfaceBehindRemoteAnimationTarget ->
+ val surfaceHeight: Int = surfaceBehindRemoteAnimationTarget.screenSpaceBounds.height()
- // 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))
- 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 @@
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 @@
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 d4e0f061..be1d162 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 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 @@
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 0000000..7f1ad6d
--- /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 0000000..4887b6a
--- /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 323ee219..b551125 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 c2a8764..5612c22 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 @@
}
/**
+ * 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 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 f03fbcb..b237f2d 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 @@
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 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/dialog/MediaOutputMetricLogger.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputMetricLogger.java
index 6fe06e0..7d3e82c 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 @@
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 @@
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 @@
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 aa10f7e..b565f3c 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 @@
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 0000000..792ae7c
--- /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 5d6d683..dfd9e22 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.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 @@
powerManager: PowerManager,
@Main private val mainHandler: Handler,
private val uiEventLogger: MediaTttReceiverUiEventLogger,
-) : TemporaryViewDisplayController<ChipReceiverInfo>(
+) : TemporaryViewDisplayController<ChipReceiverInfo, MediaTttLogger>(
context,
logger,
windowManager,
@@ -70,6 +71,8 @@
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 @@
) {
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 @@
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
)
- currentView.contentDescription = iconName
+ 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,
+ )
}
override fun animateViewIn(view: ViewGroup) {
@@ -161,15 +177,6 @@
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 bde588c..4379d25 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 @@
* @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 @@
@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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 0c1ebd7..e539f3f 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.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 @@
configurationController: ConfigurationController,
powerManager: PowerManager,
private val uiEventLogger: MediaTttSenderUiEventLogger
-) : TemporaryViewDisplayController<ChipSenderInfo>(
+) : TemporaryViewDisplayController<ChipSenderInfo, MediaTttLogger>(
context,
logger,
windowManager,
@@ -66,6 +67,8 @@
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 @@
) {
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 @@
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 @@
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 @@
// 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 @@
// 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 @@
}
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 0000000..f15720d
--- /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 5842665..e9a6c25 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 @@
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 @@
}
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 @@
*/
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 163ee2a..972b243 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 @@
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/DataSaverTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java
index 9fdf594..2fc99f3 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 @@
@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/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 3429b9d..1011a6d 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.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.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 @@
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 @@
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 abafecc..8d74a09 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 @@
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 @@
/** 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 @@
}
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 9b3fe92..084b7dc 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.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 @@
/**
* 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 @@
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 @@
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 408c61f..e06c977 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 @@
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.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.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 @@
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 @@
LockPatternUtils lockPatternUtils,
ScreenLifecycle screenLifecycle,
KeyguardBypassController keyguardBypassController,
- AccessibilityManager accessibilityManager) {
+ AccessibilityManager accessibilityManager,
+ FaceHelpMessageDeferral faceHelpMessageDeferral) {
mContext = context;
mBroadcastDispatcher = broadcastDispatcher;
mDevicePolicyManager = devicePolicyManager;
@@ -254,6 +254,7 @@
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 @@
}
@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 @@
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 @@
}
@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 @@
}
}
};
-
- 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 c74621d..258f9fc 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 @@
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 31b21c9..553826d 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 @@
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 820f6e4..0b63bbf 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 @@
}
@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 @@
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 @@
// 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 c092216..ee948c0 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 @@
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 75dac1a..d9c0293 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 @@
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 @@
Context context,
NotificationShadeWindowController notificationShadeWindowController,
ConfigurationController configurationController,
- HeadsUpManagerPhone headsUpManager
+ HeadsUpManagerPhone headsUpManager,
+ UnlockedScreenOffAnimationController unlockedScreenOffAnimationController
) {
mContext = context;
initResources();
@@ -115,6 +117,8 @@
mNotificationShadeWindowController.setForcePluginOpenListener((forceOpen) -> {
updateTouchableRegion();
});
+
+ mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController;
}
protected void setup(
@@ -179,7 +183,7 @@
/**
* 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 @@
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 fe84674..0000000
--- 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 681dc6f..9a7c3fa 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 e7fa6d2..aae0f93 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.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.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 @@
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 @@
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 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 @@
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 @@
}
}
- 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 @@
}
@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 @@
}
@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 @@
@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 @@
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 62bda2c..1d5b88e7 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 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 @@
}
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 734eeec..a52e2af 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 @@
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_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 @@
* 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 @@
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 @@
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 @@
* 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 0000000..606a11a
--- /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 d43f739..5e2dde6 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 @@
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 @@
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 @@
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 18369d9..15fdc35 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 @@
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 @@
record: UserRecord,
isGuestUserAutoCreated: Boolean,
isGuestUserResetting: Boolean,
+ isTablet: Boolean = false,
): String {
val resourceId: Int? = getGuestUserRecordNameResourceId(record)
return when {
@@ -80,6 +84,7 @@
isGuestUserResetting = isGuestUserResetting,
isAddUser = record.isAddUser,
isAddSupervisedUser = record.isAddSupervisedUser,
+ isTablet = isTablet,
)
)
}
@@ -108,12 +113,14 @@
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/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt
index e2790e4..a61cd23 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 @@
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 @@
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 419fedf..0000000
--- 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 0000000..c9ccdb3
--- /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 acb5622..3e9cf1e 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 @@
}
@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 @@
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 eecbee5..a78c902 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.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 @@
@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 @@
// 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 @@
`when`(biometricUnlockController.isWakeAndUnlock).thenReturn(true)
keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation(
- remoteAnimationTarget,
+ remoteAnimationTargets,
0 /* startTime */,
false /* requestedShowSurfaceBehindKeyguard */
)
@@ -130,7 +138,7 @@
`when`(biometricUnlockController.isWakeAndUnlock).thenReturn(false)
keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation(
- remoteAnimationTarget,
+ remoteAnimationTargets,
0 /* startTime */,
false /* requestedShowSurfaceBehindKeyguard */
)
@@ -154,7 +162,7 @@
`when`(keyguardStateController.isFlingingToDismissKeyguard).thenReturn(false)
keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation(
- remoteAnimationTarget,
+ remoteAnimationTargets,
0 /* startTime */,
true /* requestedShowSurfaceBehindKeyguard */
)
@@ -176,7 +184,7 @@
`when`(keyguardStateController.isFlingingToDismissKeyguard).thenReturn(true)
keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation(
- remoteAnimationTarget,
+ remoteAnimationTargets,
0 /* startTime */,
true /* requestedShowSurfaceBehindKeyguard */
)
@@ -196,7 +204,7 @@
@Test
fun playCannedUnlockAnimation_ifDidNotRequestShowSurface() {
keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation(
- remoteAnimationTarget,
+ remoteAnimationTargets,
0 /* startTime */,
false /* requestedShowSurfaceBehindKeyguard */
)
@@ -210,7 +218,7 @@
`when`(notificationShadeWindowController.isLaunchingActivity).thenReturn(true)
keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation(
- remoteAnimationTarget,
+ remoteAnimationTargets,
0 /* startTime */,
true /* requestedShowSurfaceBehindKeyguard */
)
@@ -225,11 +233,46 @@
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 d95e5c4..1078cda 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.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 @@
}
@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)
+ logger.logStateChange(stateName, id, packageName)
- val stringWriter = StringWriter()
- buffer.dump(PrintWriter(stringWriter), tailLength = 0)
- val actualString = stringWriter.toString()
-
+ 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 0000000..37f6434
--- /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 e7b4593..d41ad48 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 @@
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 @@
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 52b6eed..ff0faf9 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 @@
null
)
- verify(logger).logStateChange(any(), any())
+ verify(logger).logStateChange(any(), any(), any())
}
@Test
@@ -587,15 +587,29 @@
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 @@
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)
- controllerSender.removeView("fakeRemovalReason")
+ 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/DataSaverTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DataSaverTileTest.kt
new file mode 100644
index 0000000..ce62f2d
--- /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/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
index e730713..b40d5ac 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 @@
FalsingManager.FalsingTapListener listener = getFalsingTapListener();
mStatusBarStateController.setState(KEYGUARD);
- listener.onDoubleTapRequired();
+ listener.onAdditionalTapRequired();
verify(mKeyguardIndicationController).showTransientIndication(anyInt());
}
@@ -1032,7 +1032,7 @@
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 97c0bb2..09add65 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 @@
@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 @@
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 @@
@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 @@
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 @@
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 @@
// 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 @@
@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 @@
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 @@
@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 @@
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 @@
}
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 ac8874b..945cf7f 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 @@
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.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 @@
@Mock
private AccessibilityManager mAccessibilityManager;
@Mock
+ private FaceHelpMessageDeferral mFaceHelpMessageDeferral;
+ @Mock
private ScreenLifecycle mScreenLifecycle;
@Captor
private ArgumentCaptor<DockManager.AlignmentStateListener> mAlignmentListener;
@@ -259,7 +264,8 @@
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 @@
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 @@
// 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 @@
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 @@
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 @@
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 @@
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 3dd36d1..d0391ac 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.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 @@
mBluetoothControllerImpl = new BluetoothControllerImpl(mContext,
mMockDumpManager,
+ mock(BluetoothLogger.class),
mTestableLooper.getLooper(),
mTestableLooper.getLooper(),
mMockBluetoothManager);
@@ -233,4 +235,11 @@
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 e616c26..921b7ef 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.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 @@
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 @@
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 @@
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 @@
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 @@
configurationController,
powerManager,
R.layout.media_ttt_chip,
+ "Window Title",
+ "WAKE_REASON",
) {
var mostRecentViewInfo: ViewInfo? = null
@@ -371,7 +240,6 @@
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 @@
}
}
-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 0000000..c9f2b4d
--- /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 98bae3d..811e96c 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 @@
// show Alert
mAlert = mAlertDialog.create();
+ mAlert.getWindow().setHideOverlayWindows(true);
mAlert.show();
// set Alert Timeout
diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java
index c3b4792..0915c21 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 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 219092b..1266db5 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 @@
* 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 b093d25..95d71a3 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.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 @@
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 6650f43..488fe67 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.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 @@
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 @@
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 @@
if (occludesKeyguard(wc)) {
flags |= FLAG_OCCLUDES_KEYGUARD;
}
- if (wc.isEmbedded()) {
- flags |= FLAG_IS_EMBEDDED;
- }
return flags;
}
}
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 8f186e4..8cf32ba 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 @@
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_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 @@
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 @@
// 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 @@
final AsyncRotationController asyncRotationController =
mDisplayContent.getAsyncRotationController();
assertNotNull(asyncRotationController);
- assertTrue(asyncRotationController.shouldFreezeInsetsPosition(statusBar));
+ assertShouldFreezeInsetsPosition(asyncRotationController, statusBar, true);
statusBar.setOrientationChanging(true);
player.startTransition();
@@ -823,7 +826,7 @@
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 @@
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 @@
}
@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 @@
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 0000000..d11ca49
--- /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 0000000..3385784
--- /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 66ad6f1..b8ef195 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 0000000..eb7f307
--- /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 0000000..2e661fb
--- /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 0000000..2e12498
--- /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 42a37df..45a4730 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 @@
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 0000000..d60143c
--- /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 0000000..d2c9b37
--- /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 0000000..4d6125c
--- /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 0000000..68aae45
--- /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) {
+
+ }
+}