Merge "[Flexiglass] Position NotificationShelf by the placeholders" into main
diff --git a/apct-tests/perftests/core/src/android/permission/AppOpsPerfTest.kt b/apct-tests/perftests/core/src/android/permission/AppOpsPerfTest.kt
deleted file mode 100644
index daf991c..0000000
--- a/apct-tests/perftests/core/src/android/permission/AppOpsPerfTest.kt
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (C) 2023 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 android.permission
-
-import android.app.AppOpsManager
-import android.content.Context
-import android.perftests.utils.PerfStatusReporter
-import androidx.test.core.app.ApplicationProvider
-import androidx.test.filters.LargeTest
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-
-@LargeTest
-/**
- * Performance unit tests for app ops APIs.
- *
- * The APIs under test are used for checking permissions and tracking permission accesses and are
- * therefore invoked frequently by the system for all permission-protected data accesses, hence
- * these APIs should be monitored closely for performance.
- */
-class AppOpsPerfTest {
- @get:Rule val perfStatusReporter = PerfStatusReporter()
- private lateinit var appOpsManager: AppOpsManager
- private lateinit var opPackageName: String
- private var opPackageUid: Int = 0
-
- @Before
- fun setUp() {
- val context: Context = ApplicationProvider.getApplicationContext()
- appOpsManager = context.getSystemService<AppOpsManager>(AppOpsManager::class.java)!!
- opPackageName = context.getOpPackageName()
- opPackageUid = context.getPackageManager().getPackageUid(opPackageName, 0)
- }
-
- @Test
- fun testNoteOp() {
- val state = perfStatusReporter.benchmarkState
- while (state.keepRunning()) {
- appOpsManager.noteOp(
- AppOpsManager.OPSTR_FINE_LOCATION,
- opPackageUid,
- opPackageName,
- null,
- null
- )
- }
- }
-
- @Test
- fun testUnsafeCheckOp() {
- val state = perfStatusReporter.benchmarkState
- while (state.keepRunning()) {
- appOpsManager.unsafeCheckOp(
- AppOpsManager.OPSTR_FINE_LOCATION,
- opPackageUid,
- opPackageName
- )
- }
- }
-}
diff --git a/apct-tests/perftests/core/src/android/permission/OWNERS b/apct-tests/perftests/core/src/android/permission/OWNERS
deleted file mode 100644
index b4b2b9e..0000000
--- a/apct-tests/perftests/core/src/android/permission/OWNERS
+++ /dev/null
@@ -1,3 +0,0 @@
-# Bug component: 137825
-
-include platform/frameworks/base:/core/java/android/permission/OWNERS
\ No newline at end of file
diff --git a/core/api/current.txt b/core/api/current.txt
index 831cf01..5eeb299 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -55309,7 +55309,7 @@
field public static final int TYPE_MAGNIFICATION_OVERLAY = 6; // 0x6
field public static final int TYPE_SPLIT_SCREEN_DIVIDER = 5; // 0x5
field public static final int TYPE_SYSTEM = 3; // 0x3
- field @FlaggedApi("android.view.accessibility.add_type_window_control") public static final int TYPE_WINDOW_CONTROL = 7; // 0x7
+ field @FlaggedApi("android.view.accessibility.enable_type_window_control") public static final int TYPE_WINDOW_CONTROL = 7; // 0x7
}
public class CaptioningManager {
diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java
index 3f9c819..6efca0c 100644
--- a/core/java/android/service/dreams/DreamService.java
+++ b/core/java/android/service/dreams/DreamService.java
@@ -1389,7 +1389,8 @@
convertToComponentName(
rawMetadata.getString(
com.android.internal.R.styleable.Dream_settingsActivity),
- serviceInfo),
+ serviceInfo,
+ packageManager),
rawMetadata.getDrawable(
com.android.internal.R.styleable.Dream_previewImage),
rawMetadata.getBoolean(R.styleable.Dream_showClockAndComplications,
@@ -1404,26 +1405,38 @@
}
@Nullable
- private static ComponentName convertToComponentName(@Nullable String flattenedString,
- ServiceInfo serviceInfo) {
+ private static ComponentName convertToComponentName(
+ @Nullable String flattenedString,
+ ServiceInfo serviceInfo,
+ PackageManager packageManager) {
if (flattenedString == null) {
return null;
}
- if (!flattenedString.contains("/")) {
- return new ComponentName(serviceInfo.packageName, flattenedString);
+ final ComponentName cn =
+ flattenedString.contains("/")
+ ? ComponentName.unflattenFromString(flattenedString)
+ : new ComponentName(serviceInfo.packageName, flattenedString);
+
+ if (cn == null) {
+ return null;
}
// Ensure that the component is from the same package as the dream service. If not,
// treat the component as invalid and return null instead.
- final ComponentName cn = ComponentName.unflattenFromString(flattenedString);
- if (cn == null) return null;
if (!cn.getPackageName().equals(serviceInfo.packageName)) {
Log.w(TAG,
"Inconsistent package name in component: " + cn.getPackageName()
+ ", should be: " + serviceInfo.packageName);
return null;
}
+
+ // Ensure that the activity exists. If not, treat the component as invalid and return null.
+ if (new Intent().setComponent(cn).resolveActivityInfo(packageManager, 0) == null) {
+ Log.w(TAG, "Dream settings activity not found: " + cn);
+ return null;
+ }
+
return cn;
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 2377b86..ac59b2a 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -7511,6 +7511,8 @@
final KeyEvent event = (KeyEvent)q.mEvent;
if (mView.dispatchKeyEventPreIme(event)) {
return FINISH_HANDLED;
+ } else if (q.forPreImeOnly()) {
+ return FINISH_NOT_HANDLED;
}
return FORWARD;
}
@@ -10007,6 +10009,7 @@
public static final int FLAG_RESYNTHESIZED = 1 << 4;
public static final int FLAG_UNHANDLED = 1 << 5;
public static final int FLAG_MODIFIED_FOR_COMPATIBILITY = 1 << 6;
+ public static final int FLAG_PRE_IME_ONLY = 1 << 7;
public QueuedInputEvent mNext;
@@ -10014,6 +10017,13 @@
public InputEventReceiver mReceiver;
public int mFlags;
+ public boolean forPreImeOnly() {
+ if ((mFlags & FLAG_PRE_IME_ONLY) != 0) {
+ return true;
+ }
+ return false;
+ }
+
public boolean shouldSkipIme() {
if ((mFlags & FLAG_DELIVER_POST_IME) != 0) {
return true;
@@ -10040,6 +10050,7 @@
hasPrevious = flagToString("FINISHED_HANDLED", FLAG_FINISHED_HANDLED, hasPrevious, sb);
hasPrevious = flagToString("RESYNTHESIZED", FLAG_RESYNTHESIZED, hasPrevious, sb);
hasPrevious = flagToString("UNHANDLED", FLAG_UNHANDLED, hasPrevious, sb);
+ hasPrevious = flagToString("FLAG_PRE_IME_ONLY", FLAG_PRE_IME_ONLY, hasPrevious, sb);
if (!hasPrevious) {
sb.append("0");
}
@@ -10096,7 +10107,7 @@
}
@UnsupportedAppUsage
- void enqueueInputEvent(InputEvent event,
+ QueuedInputEvent enqueueInputEvent(InputEvent event,
InputEventReceiver receiver, int flags, boolean processImmediately) {
QueuedInputEvent q = obtainQueuedInputEvent(event, receiver, flags);
@@ -10135,6 +10146,7 @@
} else {
scheduleProcessInputEvents();
}
+ return q;
}
private void scheduleProcessInputEvents() {
@@ -12456,29 +12468,45 @@
+ "IWindow:%s Session:%s",
mOnBackInvokedDispatcher, mBasePackageName, mWindow, mWindowSession));
}
- mOnBackInvokedDispatcher.attachToWindow(mWindowSession, mWindow,
+ mOnBackInvokedDispatcher.attachToWindow(mWindowSession, mWindow, this,
mImeBackAnimationController);
}
- private void sendBackKeyEvent(int action) {
+ /**
+ * Sends {@link KeyEvent#ACTION_DOWN ACTION_DOWN} and {@link KeyEvent#ACTION_UP ACTION_UP}
+ * back key events
+ *
+ * @param preImeOnly whether the back events should be sent to the pre-ime stage only
+ * @return whether the event was handled (i.e. onKeyPreIme consumed it if preImeOnly=true)
+ */
+ public boolean injectBackKeyEvents(boolean preImeOnly) {
+ boolean consumed;
+ try {
+ processingBackKey(true);
+ sendBackKeyEvent(KeyEvent.ACTION_DOWN, preImeOnly);
+ consumed = sendBackKeyEvent(KeyEvent.ACTION_UP, preImeOnly);
+ } finally {
+ processingBackKey(false);
+ }
+ return consumed;
+ }
+
+ private boolean sendBackKeyEvent(int action, boolean preImeOnly) {
long when = SystemClock.uptimeMillis();
final KeyEvent ev = new KeyEvent(when, when, action,
KeyEvent.KEYCODE_BACK, 0 /* repeat */, 0 /* metaState */,
KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /* scancode */,
KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY,
InputDevice.SOURCE_KEYBOARD);
- enqueueInputEvent(ev, null /* receiver */, 0 /* flags */, true /* processImmediately */);
+ int flags = preImeOnly ? QueuedInputEvent.FLAG_PRE_IME_ONLY : 0;
+ QueuedInputEvent q = enqueueInputEvent(ev, null /* receiver */, flags,
+ true /* processImmediately */);
+ return (q.mFlags & QueuedInputEvent.FLAG_FINISHED_HANDLED) != 0;
}
private void registerCompatOnBackInvokedCallback() {
mCompatOnBackInvokedCallback = () -> {
- try {
- processingBackKey(true);
- sendBackKeyEvent(KeyEvent.ACTION_DOWN);
- sendBackKeyEvent(KeyEvent.ACTION_UP);
- } finally {
- processingBackKey(false);
- }
+ injectBackKeyEvents(/* preImeOnly */ false);
};
if (mOnBackInvokedDispatcher.hasImeOnBackInvokedDispatcher()) {
Log.d(TAG, "Skip registering CompatOnBackInvokedCallback on IME dispatcher");
diff --git a/core/java/android/view/accessibility/AccessibilityWindowInfo.java b/core/java/android/view/accessibility/AccessibilityWindowInfo.java
index 749f977..c92593f 100644
--- a/core/java/android/view/accessibility/AccessibilityWindowInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityWindowInfo.java
@@ -97,7 +97,7 @@
/**
* Window type: A system window that has the function to control an associated window.
*/
- @FlaggedApi(Flags.FLAG_ADD_TYPE_WINDOW_CONTROL)
+ @FlaggedApi(Flags.FLAG_ENABLE_TYPE_WINDOW_CONTROL)
public static final int TYPE_WINDOW_CONTROL = 7;
/* Special values for window IDs */
@@ -880,7 +880,7 @@
* @hide
*/
public static String typeToString(int type) {
- if (Flags.addTypeWindowControl() && type == TYPE_WINDOW_CONTROL) {
+ if (Flags.enableTypeWindowControl() && type == TYPE_WINDOW_CONTROL) {
return "TYPE_WINDOW_CONTROL";
}
diff --git a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
index ab7b226..95d001f 100644
--- a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
+++ b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
@@ -122,9 +122,8 @@
flag {
namespace: "accessibility"
- name: "add_type_window_control"
+ name: "enable_type_window_control"
is_exported: true
- is_fixed_read_only: true
description: "adds new TYPE_WINDOW_CONTROL to AccessibilityWindowInfo for detecting Window Decorations"
bug: "320445550"
}
diff --git a/core/java/android/widget/flags/notification_widget_flags.aconfig b/core/java/android/widget/flags/notification_widget_flags.aconfig
index 503e542..56a2cf7 100644
--- a/core/java/android/widget/flags/notification_widget_flags.aconfig
+++ b/core/java/android/widget/flags/notification_widget_flags.aconfig
@@ -57,3 +57,13 @@
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "conversation_layout_use_maximum_child_height"
+ namespace: "systemui"
+ description: "MessagingChild always needs to be measured during MessagingLinearLayout onMeasure."
+ bug: "324537506"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
\ No newline at end of file
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
index 8bd39fb..8ded608 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -663,6 +663,7 @@
private final Rect mStartAbsBounds = new Rect();
private final Rect mEndAbsBounds = new Rect();
private final Point mEndRelOffset = new Point();
+ private final Point mEndParentSize = new Point();
private ActivityManager.RunningTaskInfo mTaskInfo = null;
private boolean mAllowEnterPip;
private int mStartDisplayId = INVALID_DISPLAY;
@@ -697,6 +698,7 @@
mStartAbsBounds.readFromParcel(in);
mEndAbsBounds.readFromParcel(in);
mEndRelOffset.readFromParcel(in);
+ mEndParentSize.readFromParcel(in);
mTaskInfo = in.readTypedObject(ActivityManager.RunningTaskInfo.CREATOR);
mAllowEnterPip = in.readBoolean();
mStartDisplayId = in.readInt();
@@ -721,6 +723,7 @@
out.mStartAbsBounds.set(mStartAbsBounds);
out.mEndAbsBounds.set(mEndAbsBounds);
out.mEndRelOffset.set(mEndRelOffset);
+ out.mEndParentSize.set(mEndParentSize);
out.mTaskInfo = mTaskInfo;
out.mAllowEnterPip = mAllowEnterPip;
out.mStartDisplayId = mStartDisplayId;
@@ -781,6 +784,13 @@
}
/**
+ * Sets the size of its parent container after the change.
+ */
+ public void setEndParentSize(int width, int height) {
+ mEndParentSize.set(width, height);
+ }
+
+ /**
* Sets the taskinfo of this container if this is a task. WARNING: this takes the
* reference, so don't modify it afterwards.
*/
@@ -916,6 +926,14 @@
return mEndRelOffset;
}
+ /**
+ * Returns the size of parent container after the change.
+ */
+ @NonNull
+ public Point getEndParentSize() {
+ return mEndParentSize;
+ }
+
/** @return the leash or surface to animate for this container */
@NonNull
public SurfaceControl getLeash() {
@@ -1003,6 +1021,7 @@
mStartAbsBounds.writeToParcel(dest, flags);
mEndAbsBounds.writeToParcel(dest, flags);
mEndRelOffset.writeToParcel(dest, flags);
+ mEndParentSize.writeToParcel(dest, flags);
dest.writeTypedObject(mTaskInfo, flags);
dest.writeBoolean(mAllowEnterPip);
dest.writeInt(mStartDisplayId);
@@ -1055,6 +1074,9 @@
if (mEndRelOffset.x != 0 || mEndRelOffset.y != 0) {
sb.append(" eo="); sb.append(mEndRelOffset);
}
+ if (!mEndParentSize.equals(0, 0)) {
+ sb.append(" epz=").append(mEndParentSize);
+ }
sb.append(" d=");
if (mStartDisplayId != mEndDisplayId) {
sb.append(mStartDisplayId).append("->");
diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java
index 4ca64e7..4fb6e69 100644
--- a/core/java/android/window/WindowOnBackInvokedDispatcher.java
+++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java
@@ -37,6 +37,7 @@
import android.view.IWindowSession;
import android.view.ImeBackAnimationController;
import android.view.MotionEvent;
+import android.view.ViewRootImpl;
import androidx.annotation.VisibleForTesting;
@@ -49,6 +50,7 @@
import java.util.HashMap;
import java.util.Objects;
import java.util.TreeMap;
+import java.util.function.BooleanSupplier;
import java.util.function.Supplier;
/**
@@ -68,6 +70,7 @@
public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
private IWindowSession mWindowSession;
private IWindow mWindow;
+ private ViewRootImpl mViewRoot;
@VisibleForTesting
public final BackTouchTracker mTouchTracker = new BackTouchTracker();
@VisibleForTesting
@@ -134,10 +137,12 @@
* is attached a window.
*/
public void attachToWindow(@NonNull IWindowSession windowSession, @NonNull IWindow window,
+ @Nullable ViewRootImpl viewRoot,
@Nullable ImeBackAnimationController imeBackAnimationController) {
synchronized (mLock) {
mWindowSession = windowSession;
mWindow = window;
+ mViewRoot = viewRoot;
mImeBackAnimationController = imeBackAnimationController;
if (!mAllCallbacks.isEmpty()) {
setTopOnBackInvokedCallback(getTopCallback());
@@ -151,6 +156,7 @@
clear();
mWindow = null;
mWindowSession = null;
+ mViewRoot = null;
mImeBackAnimationController = null;
}
}
@@ -176,8 +182,6 @@
return;
}
if (callback instanceof ImeOnBackInvokedDispatcher.ImeOnBackInvokedCallback) {
- // Fall back to compat back key injection if legacy back behaviour should be used.
- if (!isOnBackInvokedCallbackEnabled()) return;
if (callback instanceof ImeOnBackInvokedDispatcher.DefaultImeOnBackAnimationCallback
&& mImeBackAnimationController != null) {
// register ImeBackAnimationController instead to play predictive back animation
@@ -300,6 +304,14 @@
}
}
+ private boolean callOnKeyPreIme() {
+ if (mViewRoot != null && !isOnBackInvokedCallbackEnabled(mViewRoot.mContext)) {
+ return mViewRoot.injectBackKeyEvents(/*preImeOnly*/ true);
+ } else {
+ return false;
+ }
+ }
+
private void setTopOnBackInvokedCallback(@Nullable OnBackInvokedCallback callback) {
if (mWindowSession == null || mWindow == null) {
return;
@@ -308,8 +320,8 @@
OnBackInvokedCallbackInfo callbackInfo = null;
if (callback != null) {
int priority = mAllCallbacks.get(callback);
- final IOnBackInvokedCallback iCallback = new OnBackInvokedCallbackWrapper(
- callback, mTouchTracker, mProgressAnimator, mHandler);
+ final IOnBackInvokedCallback iCallback = new OnBackInvokedCallbackWrapper(callback,
+ mTouchTracker, mProgressAnimator, mHandler, this::callOnKeyPreIme);
callbackInfo = new OnBackInvokedCallbackInfo(
iCallback,
priority,
@@ -399,16 +411,20 @@
private final BackTouchTracker mTouchTracker;
@NonNull
private final Handler mHandler;
+ @NonNull
+ private final BooleanSupplier mOnKeyPreIme;
OnBackInvokedCallbackWrapper(
@NonNull OnBackInvokedCallback callback,
@NonNull BackTouchTracker touchTracker,
@NonNull BackProgressAnimator progressAnimator,
- @NonNull Handler handler) {
+ @NonNull Handler handler,
+ @NonNull BooleanSupplier onKeyPreIme) {
mCallback = new WeakReference<>(callback);
mTouchTracker = touchTracker;
mProgressAnimator = progressAnimator;
mHandler = handler;
+ mOnKeyPreIme = onKeyPreIme;
}
@Override
@@ -460,6 +476,7 @@
public void onBackInvoked() throws RemoteException {
mHandler.post(() -> {
mTouchTracker.reset();
+ if (consumedByOnKeyPreIme()) return;
boolean isInProgress = mProgressAnimator.isBackAnimationInProgress();
final OnBackInvokedCallback callback = mCallback.get();
if (callback == null) {
@@ -481,6 +498,30 @@
});
}
+ private boolean consumedByOnKeyPreIme() {
+ final OnBackInvokedCallback callback = mCallback.get();
+ if (callback instanceof ImeBackAnimationController
+ || callback instanceof ImeOnBackInvokedDispatcher.ImeOnBackInvokedCallback) {
+ // call onKeyPreIme API if the current callback is an IME callback and the app has
+ // not set enableOnBackInvokedCallback="false"
+ try {
+ boolean consumed = mOnKeyPreIme.getAsBoolean();
+ if (consumed) {
+ // back event intercepted by app in onKeyPreIme -> cancel the IME animation.
+ final OnBackAnimationCallback animationCallback =
+ getBackAnimationCallback();
+ if (animationCallback != null) {
+ mProgressAnimator.onBackCancelled(animationCallback::onBackCancelled);
+ }
+ return true;
+ }
+ } catch (Exception e) {
+ Log.d(TAG, "Failed to call onKeyPreIme", e);
+ }
+ }
+ return false;
+ }
+
@Override
public void setTriggerBack(boolean triggerBack) throws RemoteException {
mTouchTracker.setTriggerBack(triggerBack);
diff --git a/core/java/com/android/internal/widget/ConversationLayout.java b/core/java/com/android/internal/widget/ConversationLayout.java
index bd654fa..2bfbf84 100644
--- a/core/java/com/android/internal/widget/ConversationLayout.java
+++ b/core/java/com/android/internal/widget/ConversationLayout.java
@@ -16,6 +16,8 @@
package com.android.internal.widget;
+import static android.widget.flags.Flags.conversationLayoutUseMaximumChildHeight;
+
import static com.android.internal.widget.MessagingGroup.IMAGE_DISPLAY_LOCATION_EXTERNAL;
import static com.android.internal.widget.MessagingGroup.IMAGE_DISPLAY_LOCATION_INLINE;
@@ -1407,6 +1409,38 @@
}
}
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+ // ConversationLayout needs to set its height to its biggest child to show the content
+ // properly.
+ // FrameLayout measures its match_parent children twice when any of FLs dimension is not
+ // specified. However, its sets its own dimensions before the second measurement pass.
+ // Content CutOff happens when children have bigger height on its second measurement.
+ if (conversationLayoutUseMaximumChildHeight()) {
+ int maxHeight = getMeasuredHeight();
+ final int count = getChildCount();
+
+ for (int i = 0; i < count; i++) {
+ final View child = getChildAt(i);
+ if (child == null || child.getVisibility() == GONE) {
+ continue;
+ }
+
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ maxHeight = Math.max(maxHeight,
+ child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
+ }
+
+ maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
+ if (maxHeight != getMeasuredHeight()) {
+ setMeasuredDimension(getMeasuredWidth(), maxHeight);
+ }
+ }
+ }
+
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java
index b64eeca..e9ad1c2 100644
--- a/core/tests/coretests/src/android/app/NotificationTest.java
+++ b/core/tests/coretests/src/android/app/NotificationTest.java
@@ -804,6 +804,7 @@
}
@Test
+ @Ignore // b/347089000 - Restore or delete
public void testColors_ensureColors_colorized_producesValidPalette_white() {
validateColorizedPaletteForColor(Color.WHITE);
}
diff --git a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
index b0190a5..d4482f2 100644
--- a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
+++ b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
@@ -112,7 +112,7 @@
doReturn(mApplicationInfo).when(mContext).getApplicationInfo();
mDispatcher = new WindowOnBackInvokedDispatcher(mContext, Looper.getMainLooper());
- mDispatcher.attachToWindow(mWindowSession, mWindow, mImeBackAnimationController);
+ mDispatcher.attachToWindow(mWindowSession, mWindow, null, mImeBackAnimationController);
}
private void waitForIdle() {
@@ -455,25 +455,26 @@
@Test
public void registerImeCallbacks_onBackInvokedCallbackEnabled() throws RemoteException {
- mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mDefaultImeCallback);
+ verifyImeCallackRegistrations();
+ }
+
+ @Test
+ public void registerImeCallbacks_onBackInvokedCallbackDisabled() throws RemoteException {
+ doReturn(false).when(mApplicationInfo).isOnBackInvokedCallbackEnabled();
+ verifyImeCallackRegistrations();
+ }
+
+ private void verifyImeCallackRegistrations() throws RemoteException {
+ // verify default callback is replaced with ImeBackAnimationController
+ mDispatcher.registerOnBackInvokedCallbackUnchecked(mDefaultImeCallback, PRIORITY_DEFAULT);
assertCallbacksSize(/* default */ 1, /* overlay */ 0);
assertSetCallbackInfo();
assertTopCallback(mImeBackAnimationController);
- mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mImeCallback);
+ // verify regular ime callback is successfully registered
+ mDispatcher.registerOnBackInvokedCallbackUnchecked(mImeCallback, PRIORITY_DEFAULT);
assertCallbacksSize(/* default */ 2, /* overlay */ 0);
assertSetCallbackInfo();
assertTopCallback(mImeCallback);
}
-
- @Test
- public void registerImeCallbacks_legacyBack() throws RemoteException {
- doReturn(false).when(mApplicationInfo).isOnBackInvokedCallbackEnabled();
-
- mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mDefaultImeCallback);
- assertNoSetCallbackInfo();
-
- mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mImeCallback);
- assertNoSetCallbackInfo();
- }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
index a426b20..5a42817 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
@@ -31,6 +31,7 @@
import android.animation.Animator;
import android.animation.ValueAnimator;
import android.content.Context;
+import android.graphics.Point;
import android.graphics.Rect;
import android.os.IBinder;
import android.util.ArraySet;
@@ -45,6 +46,7 @@
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.window.flags.Flags;
import com.android.wm.shell.activityembedding.ActivityEmbeddingAnimationAdapter.SnapshotAdapter;
import com.android.wm.shell.common.ScreenshotUtils;
import com.android.wm.shell.shared.TransitionUtil;
@@ -398,7 +400,15 @@
// This is because the TaskFragment surface/change won't contain the Activity's before its
// reparent.
Animation changeAnimation = null;
- Rect parentBounds = new Rect();
+ final Rect parentBounds = new Rect();
+ // We use a single boolean value to record the backdrop override because the override used
+ // for overlay and we restrict to single overlay animation. We should fix the assumption
+ // if we allow multiple overlay transitions.
+ // The backdrop logic is mainly for animations of split animations. The backdrop should be
+ // disabled if there is any open/close target in the same transition as the change target.
+ // However, the overlay change animation usually contains one change target, and shows
+ // backdrop unexpectedly.
+ Boolean overrideShowBackdrop = null;
for (TransitionInfo.Change change : info.getChanges()) {
if (change.getMode() != TRANSIT_CHANGE
|| change.getStartAbsBounds().equals(change.getEndAbsBounds())) {
@@ -421,17 +431,17 @@
}
}
- // The TaskFragment may be enter/exit split, so we take the union of both as the parent
- // size.
- parentBounds.union(boundsAnimationChange.getStartAbsBounds());
- parentBounds.union(boundsAnimationChange.getEndAbsBounds());
- if (boundsAnimationChange != change) {
- // Union the change starting bounds in case the activity is resized and reparented
- // to a TaskFragment. In that case, the TaskFragment may not cover the activity's
- // starting bounds.
- parentBounds.union(change.getStartAbsBounds());
+ final TransitionInfo.AnimationOptions options = boundsAnimationChange
+ .getAnimationOptions();
+ if (options != null) {
+ final Animation overrideAnimation = mAnimationSpec.loadCustomAnimationFromOptions(
+ options, TRANSIT_CHANGE);
+ if (overrideAnimation != null) {
+ overrideShowBackdrop = overrideAnimation.getShowBackdrop();
+ }
}
+ calculateParentBounds(change, boundsAnimationChange, parentBounds);
// There are two animations in the array. The first one is for the start leash
// (snapshot), and the second one is for the end leash (TaskFragment).
final Animation[] animations = mAnimationSpec.createChangeBoundsChangeAnimations(change,
@@ -466,7 +476,7 @@
// If there is no corresponding open/close window with the change, we should show background
// color to cover the empty part of the screen.
- boolean shouldShouldBackgroundColor = true;
+ boolean shouldShowBackgroundColor = true;
// Handle the other windows that don't have bounds change in the same transition.
for (TransitionInfo.Change change : info.getChanges()) {
if (handledChanges.contains(change)) {
@@ -483,16 +493,18 @@
animation = ActivityEmbeddingAnimationSpec.createNoopAnimation(change);
} else if (TransitionUtil.isClosingType(change.getMode())) {
animation = mAnimationSpec.createChangeBoundsCloseAnimation(change, parentBounds);
- shouldShouldBackgroundColor = false;
+ shouldShowBackgroundColor = false;
} else {
animation = mAnimationSpec.createChangeBoundsOpenAnimation(change, parentBounds);
- shouldShouldBackgroundColor = false;
+ shouldShowBackgroundColor = false;
}
adapters.add(new ActivityEmbeddingAnimationAdapter(animation, change,
TransitionUtil.getRootFor(change, info)));
}
- if (shouldShouldBackgroundColor && changeAnimation != null) {
+ shouldShowBackgroundColor = overrideShowBackdrop != null
+ ? overrideShowBackdrop : shouldShowBackgroundColor;
+ if (shouldShowBackgroundColor && changeAnimation != null) {
// Change animation may leave part of the screen empty. Show background color to cover
// that.
changeAnimation.setShowBackdrop(true);
@@ -502,6 +514,39 @@
}
/**
+ * Calculates parent bounds of the animation target by {@code change}.
+ */
+ @VisibleForTesting
+ static void calculateParentBounds(@NonNull TransitionInfo.Change change,
+ @NonNull TransitionInfo.Change boundsAnimationChange, @NonNull Rect outParentBounds) {
+ if (Flags.activityEmbeddingOverlayPresentationFlag()) {
+ final Point endParentSize = change.getEndParentSize();
+ if (endParentSize.equals(0, 0)) {
+ return;
+ }
+ final Point endRelPosition = change.getEndRelOffset();
+ final Point endAbsPosition = new Point(change.getEndAbsBounds().left,
+ change.getEndAbsBounds().top);
+ final Point parentEndAbsPosition = new Point(endAbsPosition.x - endRelPosition.x,
+ endAbsPosition.y - endRelPosition.y);
+ outParentBounds.set(parentEndAbsPosition.x, parentEndAbsPosition.y,
+ parentEndAbsPosition.x + endParentSize.x,
+ parentEndAbsPosition.y + endParentSize.y);
+ } else {
+ // The TaskFragment may be enter/exit split, so we take the union of both as
+ // the parent size.
+ outParentBounds.union(boundsAnimationChange.getStartAbsBounds());
+ outParentBounds.union(boundsAnimationChange.getEndAbsBounds());
+ if (boundsAnimationChange != change) {
+ // Union the change starting bounds in case the activity is resized and
+ // reparented to a TaskFragment. In that case, the TaskFragment may not cover
+ // the activity's starting bounds.
+ outParentBounds.union(change.getStartAbsBounds());
+ }
+ }
+ }
+
+ /**
* Takes a screenshot of the given {@code screenshotChange} surface if WM Core hasn't taken one.
* The screenshot leash should be attached to the {@code animationChange} surface which we will
* animate later.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
index b986862..8d49614 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
@@ -18,6 +18,8 @@
import static android.app.ActivityOptions.ANIM_CUSTOM;
+import static android.view.WindowManager.TRANSIT_CHANGE;
+import static android.window.TransitionInfo.AnimationOptions.DEFAULT_ANIMATION_RESOURCES_ID;
import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_NONE;
import static com.android.wm.shell.transition.TransitionAnimationHelper.loadAttributeAnimation;
@@ -27,6 +29,8 @@
import android.annotation.Nullable;
import android.content.Context;
import android.graphics.Rect;
+import android.util.Log;
+import android.view.WindowManager;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.view.animation.AnimationSet;
@@ -203,7 +207,7 @@
Animation loadOpenAnimation(@NonNull TransitionInfo info,
@NonNull TransitionInfo.Change change, @NonNull Rect wholeAnimationBounds) {
final boolean isEnter = TransitionUtil.isOpeningType(change.getMode());
- final Animation customAnimation = loadCustomAnimation(info, change, isEnter);
+ final Animation customAnimation = loadCustomAnimation(info, change);
final Animation animation;
if (customAnimation != null) {
animation = customAnimation;
@@ -230,7 +234,7 @@
Animation loadCloseAnimation(@NonNull TransitionInfo info,
@NonNull TransitionInfo.Change change, @NonNull Rect wholeAnimationBounds) {
final boolean isEnter = TransitionUtil.isOpeningType(change.getMode());
- final Animation customAnimation = loadCustomAnimation(info, change, isEnter);
+ final Animation customAnimation = loadCustomAnimation(info, change);
final Animation animation;
if (customAnimation != null) {
animation = customAnimation;
@@ -263,18 +267,40 @@
@Nullable
private Animation loadCustomAnimation(@NonNull TransitionInfo info,
- @NonNull TransitionInfo.Change change, boolean isEnter) {
+ @NonNull TransitionInfo.Change change) {
final TransitionInfo.AnimationOptions options;
if (Flags.moveAnimationOptionsToChange()) {
options = change.getAnimationOptions();
} else {
options = info.getAnimationOptions();
}
+ return loadCustomAnimationFromOptions(options, change.getMode());
+ }
+
+ @Nullable
+ Animation loadCustomAnimationFromOptions(@Nullable TransitionInfo.AnimationOptions options,
+ @WindowManager.TransitionType int mode) {
if (options == null || options.getType() != ANIM_CUSTOM) {
return null;
}
+ final int resId;
+ if (TransitionUtil.isOpeningType(mode)) {
+ resId = options.getEnterResId();
+ } else if (TransitionUtil.isClosingType(mode)) {
+ resId = options.getExitResId();
+ } else if (mode == TRANSIT_CHANGE) {
+ resId = options.getChangeResId();
+ } else {
+ Log.w(TAG, "Unknown transit type:" + mode);
+ resId = DEFAULT_ANIMATION_RESOURCES_ID;
+ }
+ // Use the default animation if the resources ID is not specified.
+ if (resId == DEFAULT_ANIMATION_RESOURCES_ID) {
+ return null;
+ }
+
final Animation anim = mTransitionAnimation.loadAnimationRes(options.getPackageName(),
- isEnter ? options.getEnterResId() : options.getExitResId());
+ resId);
if (anim != null) {
return anim;
}
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 bd20c11..731f75bf 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
@@ -20,9 +20,11 @@
import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
import static android.window.TransitionInfo.FLAG_IS_BEHIND_STARTING_WINDOW;
+import static com.android.wm.shell.activityembedding.ActivityEmbeddingAnimationRunner.calculateParentBounds;
import static com.android.wm.shell.transition.Transitions.TRANSIT_TASK_FRAGMENT_DRAG_RESIZE;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doNothing;
@@ -32,6 +34,9 @@
import static org.mockito.Mockito.verifyNoMoreInteractions;
import android.animation.Animator;
+import android.annotation.NonNull;
+import android.graphics.Point;
+import android.graphics.Rect;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
@@ -130,7 +135,7 @@
@Test
public void testInvalidCustomAnimation_disableAnimationOptionsPerChange() {
final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN, 0)
- .addChange(createChange(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY))
+ .addChange(createChange(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY, TRANSIT_OPEN))
.build();
info.setAnimationOptions(TransitionInfo.AnimationOptions
.makeCustomAnimOptions("packageName", 0 /* enterResId */, 0 /* exitResId */,
@@ -148,7 +153,7 @@
@Test
public void testInvalidCustomAnimation_enableAnimationOptionsPerChange() {
final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN, 0)
- .addChange(createChange(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY))
+ .addChange(createChange(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY, TRANSIT_OPEN))
.build();
info.getChanges().getFirst().setAnimationOptions(TransitionInfo.AnimationOptions
.makeCustomAnimOptions("packageName", 0 /* enterResId */, 0 /* exitResId */,
@@ -161,4 +166,128 @@
// An invalid custom animation is equivalent to jump-cut.
assertEquals(0, animator.getDuration());
}
+
+ @DisableFlags(Flags.FLAG_ACTIVITY_EMBEDDING_OVERLAY_PRESENTATION_FLAG)
+ @Test
+ public void testCalculateParentBounds_flagDisabled() {
+ final Rect parentBounds = new Rect(0, 0, 2000, 2000);
+ final Rect primaryBounds = new Rect();
+ final Rect secondaryBounds = new Rect();
+ parentBounds.splitVertically(primaryBounds, secondaryBounds);
+
+ final TransitionInfo.Change change = createChange(0 /* flags */);
+ change.setStartAbsBounds(secondaryBounds);
+
+ final TransitionInfo.Change boundsAnimationChange = createChange(0 /* flags */);
+ boundsAnimationChange.setStartAbsBounds(primaryBounds);
+ boundsAnimationChange.setEndAbsBounds(primaryBounds);
+ final Rect actualParentBounds = new Rect();
+
+ calculateParentBounds(change, boundsAnimationChange, actualParentBounds);
+
+ assertEquals(parentBounds, actualParentBounds);
+
+ actualParentBounds.setEmpty();
+
+ boundsAnimationChange.setStartAbsBounds(secondaryBounds);
+ boundsAnimationChange.setEndAbsBounds(primaryBounds);
+
+ calculateParentBounds(boundsAnimationChange, boundsAnimationChange, actualParentBounds);
+
+ assertEquals(parentBounds, actualParentBounds);
+ }
+
+ // TODO(b/243518738): Rewrite with TestParameter
+ @EnableFlags(Flags.FLAG_ACTIVITY_EMBEDDING_OVERLAY_PRESENTATION_FLAG)
+ @Test
+ public void testCalculateParentBounds_flagEnabled() {
+ TransitionInfo.Change change;
+ final TransitionInfo.Change stubChange = createChange(0 /* flags */);
+ final Rect actualParentBounds = new Rect();
+ Rect parentBounds = new Rect(0, 0, 2000, 2000);
+ Rect endAbsBounds = new Rect(0, 0, 2000, 2000);
+ change = prepareChangeForParentBoundsCalculationTest(
+ new Point(0, 0) /* endRelOffset */,
+ endAbsBounds,
+ new Point() /* endParentSize */
+ );
+
+ calculateParentBounds(change, stubChange, actualParentBounds);
+
+ assertTrue("Parent bounds must be empty because end parent size is not set.",
+ actualParentBounds.isEmpty());
+
+ String testString = "Parent start with (0, 0)";
+ change = prepareChangeForParentBoundsCalculationTest(
+ new Point(endAbsBounds.left - parentBounds.left,
+ endAbsBounds.top - parentBounds.top),
+ endAbsBounds, new Point(parentBounds.width(), parentBounds.height()));
+
+ calculateParentBounds(change, stubChange, actualParentBounds);
+
+ assertEquals(testString + ": Parent bounds must be " + parentBounds, parentBounds,
+ actualParentBounds);
+
+ testString = "Container not start with (0, 0)";
+ parentBounds = new Rect(0, 0, 2000, 2000);
+ endAbsBounds = new Rect(1000, 500, 2000, 1500);
+ change = prepareChangeForParentBoundsCalculationTest(
+ new Point(endAbsBounds.left - parentBounds.left,
+ endAbsBounds.top - parentBounds.top),
+ endAbsBounds, new Point(parentBounds.width(), parentBounds.height()));
+
+ calculateParentBounds(change, stubChange, actualParentBounds);
+
+ assertEquals(testString + ": Parent bounds must be " + parentBounds, parentBounds,
+ actualParentBounds);
+
+ testString = "Parent container on the right";
+ parentBounds = new Rect(1000, 0, 2000, 2000);
+ endAbsBounds = new Rect(1000, 500, 1500, 1500);
+ change = prepareChangeForParentBoundsCalculationTest(
+ new Point(endAbsBounds.left - parentBounds.left,
+ endAbsBounds.top - parentBounds.top),
+ endAbsBounds, new Point(parentBounds.width(), parentBounds.height()));
+
+ calculateParentBounds(change, stubChange, actualParentBounds);
+
+ assertEquals(testString + ": Parent bounds must be " + parentBounds, parentBounds,
+ actualParentBounds);
+
+ testString = "Parent container on the bottom";
+ parentBounds = new Rect(0, 1000, 2000, 2000);
+ endAbsBounds = new Rect(500, 1500, 1500, 2000);
+ change = prepareChangeForParentBoundsCalculationTest(
+ new Point(endAbsBounds.left - parentBounds.left,
+ endAbsBounds.top - parentBounds.top),
+ endAbsBounds, new Point(parentBounds.width(), parentBounds.height()));
+
+ calculateParentBounds(change, stubChange, actualParentBounds);
+
+ assertEquals(testString + ": Parent bounds must be " + parentBounds, parentBounds,
+ actualParentBounds);
+
+ testString = "Parent container in the middle";
+ parentBounds = new Rect(500, 500, 1500, 1500);
+ endAbsBounds = new Rect(1000, 500, 1500, 1000);
+ change = prepareChangeForParentBoundsCalculationTest(
+ new Point(endAbsBounds.left - parentBounds.left,
+ endAbsBounds.top - parentBounds.top),
+ endAbsBounds, new Point(parentBounds.width(), parentBounds.height()));
+
+ calculateParentBounds(change, stubChange, actualParentBounds);
+
+ assertEquals(testString + ": Parent bounds must be " + parentBounds, parentBounds,
+ actualParentBounds);
+ }
+
+ @NonNull
+ private static TransitionInfo.Change prepareChangeForParentBoundsCalculationTest(
+ @NonNull Point endRelOffset, @NonNull Rect endAbsBounds, @NonNull Point endParentSize) {
+ final TransitionInfo.Change change = createChange(0 /* flags */);
+ change.setEndRelOffset(endRelOffset.x, endRelOffset.y);
+ change.setEndAbsBounds(endAbsBounds);
+ change.setEndParentSize(endParentSize.x, endParentSize.y);
+ return change;
+ }
}
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 0b2265d..c18d7ec 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,7 @@
package com.android.wm.shell.activityembedding;
+import static android.view.WindowManager.TRANSIT_NONE;
import static android.window.TransitionInfo.FLAG_FILLS_TASK;
import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
@@ -31,6 +32,7 @@
import android.graphics.Rect;
import android.os.IBinder;
import android.view.SurfaceControl;
+import android.view.WindowManager;
import android.window.TransitionInfo;
import android.window.WindowContainerToken;
@@ -82,11 +84,27 @@
spyOn(mFinishCallback);
}
- /** Creates a mock {@link TransitionInfo.Change}. */
+ /**
+ * Creates a mock {@link TransitionInfo.Change}.
+ *
+ * @param flags the {@link TransitionInfo.ChangeFlags} of the change
+ */
static TransitionInfo.Change createChange(@TransitionInfo.ChangeFlags int flags) {
+ return createChange(flags, TRANSIT_NONE);
+ }
+
+ /**
+ * Creates a mock {@link TransitionInfo.Change}.
+ *
+ * @param flags the {@link TransitionInfo.ChangeFlags} of the change
+ * @param mode the transition mode of the change
+ */
+ static TransitionInfo.Change createChange(@TransitionInfo.ChangeFlags int flags,
+ @WindowManager.TransitionType int mode) {
TransitionInfo.Change c = new TransitionInfo.Change(mock(WindowContainerToken.class),
mock(SurfaceControl.class));
c.setFlags(flags);
+ c.setMode(mode);
return c;
}
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorActivity.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorActivity.kt
index 5728c8c..35addb3 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorActivity.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorActivity.kt
@@ -24,7 +24,6 @@
import androidx.activity.viewModels
import com.android.credentialmanager.ui.theme.WearCredentialSelectorTheme
import com.android.credentialmanager.ui.WearApp
-import com.google.android.horologist.annotations.ExperimentalHorologistApi
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint(ComponentActivity::class)
@@ -32,7 +31,6 @@
private val viewModel: CredentialSelectorViewModel by viewModels()
- @OptIn(ExperimentalHorologistApi::class)
override fun onCreate(savedInstanceState: Bundle?) {
Log.d(TAG, "onCreate, intent: $intent")
super.onCreate(savedInstanceState)
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/CredentialsScreenChip.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/CredentialsScreenChip.kt
index c641d7f..25bc381 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/CredentialsScreenChip.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/CredentialsScreenChip.kt
@@ -15,6 +15,7 @@
*/
package com.android.credentialmanager.ui.components
+import androidx.wear.compose.material.MaterialTheme as WearMaterialTheme
import androidx.compose.foundation.layout.Row
import androidx.compose.material3.Icon
import android.graphics.drawable.Drawable
@@ -22,7 +23,11 @@
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.Lock
+import androidx.compose.material.icons.outlined.LockOpen
import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
@@ -46,7 +51,7 @@
onClick: () -> Unit,
secondaryLabel: String? = null,
icon: Drawable? = null,
- isAuthenticationEntryLocked: Boolean = false,
+ isAuthenticationEntryLocked: Boolean? = null,
textAlign: TextAlign = TextAlign.Center,
modifier: Modifier = Modifier,
colors: ChipColors = ChipDefaults.secondaryChipColors()
@@ -77,7 +82,7 @@
text: @Composable () -> Unit,
secondaryLabel: String? = null,
icon: Drawable? = null,
- isAuthenticationEntryLocked: Boolean = false,
+ isAuthenticationEntryLocked: Boolean? = null,
modifier: Modifier = Modifier,
colors: ChipColors = ChipDefaults.primaryChipColors(),
) {
@@ -94,16 +99,23 @@
text = secondaryLabel,
)
- if (isAuthenticationEntryLocked)
- // TODO(b/324465527) change this to lock icon and correct size once figma mocks are
- // updated
- Icon(
- bitmap = checkNotNull(icon?.toBitmap()?.asImageBitmap()),
- // Decorative purpose only.
- contentDescription = null,
- modifier = Modifier.size(10.dp),
- tint = Color.Unspecified
- )
+ if (isAuthenticationEntryLocked != null) {
+ if (isAuthenticationEntryLocked) {
+ Icon(
+ Icons.Outlined.Lock,
+ contentDescription = null,
+ modifier = Modifier.size(12.dp).align(Alignment.CenterVertically),
+ tint = WearMaterialTheme.colors.onSurfaceVariant
+ )
+ } else {
+ Icon(
+ Icons.Outlined.LockOpen,
+ contentDescription = null,
+ modifier = Modifier.size(12.dp).align(Alignment.CenterVertically),
+ tint = WearMaterialTheme.colors.onSurfaceVariant
+ )
+ }
+ }
}
}
}
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/Texts.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/Texts.kt
index 22f6bf0..282fea0 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/Texts.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/Texts.kt
@@ -22,7 +22,6 @@
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.text.TextLayoutResult
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
@@ -57,7 +56,6 @@
text: String,
textAlign: TextAlign = TextAlign.Center,
modifier: Modifier = Modifier,
- onTextLayout: (TextLayoutResult) -> Unit = {},
) {
Text(
modifier = modifier.padding(start = 8.dp, end = 8.dp).wrapContentSize(),
@@ -67,7 +65,6 @@
overflow = TextOverflow.Ellipsis,
textAlign = textAlign,
maxLines = 2,
- onTextLayout = onTextLayout,
)
}
@@ -79,7 +76,6 @@
maxLines: Int = 1,
modifier: Modifier = Modifier,
color: Color = WearMaterialTheme.colors.onSurface,
- onTextLayout: (TextLayoutResult) -> Unit = {},
) {
Text(
modifier = modifier.wrapContentSize(),
@@ -89,7 +85,6 @@
overflow = TextOverflow.Ellipsis,
textAlign = textAlign,
maxLines = maxLines,
- onTextLayout = onTextLayout,
)
}
@@ -97,7 +92,6 @@
fun WearSecondaryLabel(
text: String,
modifier: Modifier = Modifier,
- onTextLayout: (TextLayoutResult) -> Unit = {},
) {
Text(
modifier = modifier.wrapContentSize(),
@@ -107,6 +101,5 @@
overflow = TextOverflow.Ellipsis,
textAlign = TextAlign.Start,
maxLines = 1,
- onTextLayout = onTextLayout,
)
}
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/mappers/CredentialSelectorUiStateGetMapper.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/mappers/CredentialSelectorUiStateGetMapper.kt
index 473094c..2656275 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/mappers/CredentialSelectorUiStateGetMapper.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/mappers/CredentialSelectorUiStateGetMapper.kt
@@ -43,7 +43,8 @@
var icon: Drawable? = null
// provide icon if all entries have the same provider
- if (sortedEntries.all {it.providerId == sortedEntries[0].providerId}) {
+ if (sortedEntries.isNotEmpty() &&
+ sortedEntries.all {it.providerId == sortedEntries[0].providerId}) {
icon = providerInfos[0].icon
}
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFlattenScreen.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFlattenScreen.kt
index fb81e73..36e9792 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFlattenScreen.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFlattenScreen.kt
@@ -75,7 +75,10 @@
CredentialsScreenChip(
label = credential.userName,
onClick = { selectEntry(credential, false) },
- secondaryLabel = credential.credentialTypeDisplayName,
+ secondaryLabel =
+ credential.credentialTypeDisplayName.ifEmpty {
+ credential.providerDisplayName
+ },
icon = credential.icon,
textAlign = TextAlign.Start
)
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFoldScreen.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFoldScreen.kt
index 7addc74..ce2bad0 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFoldScreen.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFoldScreen.kt
@@ -61,13 +61,12 @@
val credentials = credentialSelectorUiState.sortedEntries
item {
var title = stringResource(R.string.choose_sign_in_title)
-
- if (credentials.isEmpty()) {
- title = stringResource(R.string.choose_sign_in_title)
- } else if (credentials.all{ it.credentialType == CredentialType.PASSKEY }) {
- title = stringResource(R.string.choose_passkey_title)
- } else if (credentials.all { it.credentialType == CredentialType.PASSWORD }) {
- title = stringResource(R.string.choose_password_title)
+ if (credentials.isNotEmpty()) {
+ if (credentials.all { it.credentialType == CredentialType.PASSKEY }) {
+ title = stringResource(R.string.choose_passkey_title)
+ } else if (credentials.all { it.credentialType == CredentialType.PASSWORD }) {
+ title = stringResource(R.string.choose_password_title)
+ }
}
SignInHeader(
@@ -77,16 +76,19 @@
}
credentials.forEach { credential: CredentialEntryInfo ->
- item {
- CredentialsScreenChip(
- label = credential.userName,
- onClick = { selectEntry(credential, false) },
- secondaryLabel = credential.credentialTypeDisplayName,
- icon = credential.icon,
- )
- CredentialsScreenChipSpacer()
+ item {
+ CredentialsScreenChip(
+ label = credential.userName,
+ onClick = { selectEntry(credential, false) },
+ secondaryLabel =
+ credential.credentialTypeDisplayName.ifEmpty {
+ credential.providerDisplayName
+ },
+ icon = credential.icon,
+ )
+ CredentialsScreenChipSpacer()
+ }
}
- }
credentialSelectorUiState.authenticationEntryList.forEach { authenticationEntryInfo ->
item {
@@ -96,7 +98,6 @@
CredentialsScreenChipSpacer()
}
}
-
item {
Spacer(modifier = Modifier.size(8.dp))
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
index 87bac83..7c2f7fe 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
@@ -59,9 +59,11 @@
import com.android.systemui.communal.ui.compose.extensions.allowGestures
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.communal.util.CommunalColors
+import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor.Companion.TO_GONE_DURATION
import com.android.systemui.res.R
import com.android.systemui.scene.shared.model.SceneDataSourceDelegator
import com.android.systemui.scene.ui.composable.SceneTransitionLayoutDataSource
+import kotlin.time.DurationUnit
object Communal {
object Elements {
@@ -91,6 +93,10 @@
spec = tween(durationMillis = 250)
fade(AllElements)
}
+ to(CommunalScenes.Blank, key = CommunalTransitionKeys.SimpleFade) {
+ spec = tween(durationMillis = TO_GONE_DURATION.toInt(DurationUnit.MILLISECONDS))
+ fade(AllElements)
+ }
to(CommunalScenes.Communal) {
spec = tween(durationMillis = 1000)
translate(Communal.Elements.Grid, Edge.Right)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractorTest.kt
index 6e48b99..43293c7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractorTest.kt
@@ -83,24 +83,27 @@
}
@Test
- fun snapToSceneForActivity() =
+ fun changeSceneForActivityStartOnDismissKeyguard() =
testScope.runTest {
val currentScene by collectLastValue(underTest.currentScene)
- assertThat(currentScene).isEqualTo(CommunalScenes.Blank)
-
- underTest.snapToSceneForActivityStart(CommunalScenes.Communal)
+ underTest.snapToScene(CommunalScenes.Communal)
assertThat(currentScene).isEqualTo(CommunalScenes.Communal)
+
+ underTest.changeSceneForActivityStartOnDismissKeyguard()
+ assertThat(currentScene).isEqualTo(CommunalScenes.Blank)
}
@Test
- fun snapToSceneForActivity_willNotChangeScene_forEditModeActivity() =
+ fun changeSceneForActivityStartOnDismissKeyguard_willNotChangeScene_forEditModeActivity() =
testScope.runTest {
val currentScene by collectLastValue(underTest.currentScene)
- assertThat(currentScene).isEqualTo(CommunalScenes.Blank)
+ underTest.snapToScene(CommunalScenes.Communal)
+ assertThat(currentScene).isEqualTo(CommunalScenes.Communal)
underTest.setEditModeState(EditModeState.STARTING)
- underTest.snapToSceneForActivityStart(CommunalScenes.Communal)
- assertThat(currentScene).isEqualTo(CommunalScenes.Blank)
+
+ underTest.changeSceneForActivityStartOnDismissKeyguard()
+ assertThat(currentScene).isEqualTo(CommunalScenes.Communal)
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceconfig/data/repository/DeviceConfigRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceconfig/data/repository/DeviceConfigRepositoryTest.kt
new file mode 100644
index 0000000..79115ae
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceconfig/data/repository/DeviceConfigRepositoryTest.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.deviceconfig.data.repository
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.concurrency.fakeExecutor
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.android.systemui.util.fakeDeviceConfigProxy
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class DeviceConfigRepositoryTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val dataSource = kosmos.fakeDeviceConfigProxy
+
+ private val underTest = kosmos.deviceConfigRepository
+
+ @Test
+ fun booleanProperty() =
+ testScope.runTest {
+ val property by collectLastValue(underTest.property("namespace", "name", false))
+ assertThat(property).isFalse()
+
+ dataSource.setProperty("namespace", "name", "true", /* makeDefault= */ false)
+ kosmos.fakeExecutor.runAllReady()
+ assertThat(property).isTrue()
+
+ dataSource.setProperty("namespace", "name", "false", /* makeDefault= */ false)
+ kosmos.fakeExecutor.runAllReady()
+ assertThat(property).isFalse()
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceconfig/domain/interactor/DeviceConfigInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceconfig/domain/interactor/DeviceConfigInteractorTest.kt
new file mode 100644
index 0000000..6ec4cea
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceconfig/domain/interactor/DeviceConfigInteractorTest.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2024 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.deviceconfig.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.concurrency.fakeExecutor
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.android.systemui.util.fakeDeviceConfigProxy
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class DeviceConfigInteractorTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val dataSource = kosmos.fakeDeviceConfigProxy
+
+ private val underTest = kosmos.deviceConfigInteractor
+
+ @Test
+ fun booleanProperty() =
+ testScope.runTest {
+ val property by collectLastValue(underTest.property("namespace", "name", false))
+ assertThat(property).isFalse()
+
+ dataSource.setProperty("namespace", "name", "true", /* makeDefault= */ false)
+ kosmos.fakeExecutor.runAllReady()
+ assertThat(property).isTrue()
+
+ dataSource.setProperty("namespace", "name", "false", /* makeDefault= */ false)
+ kosmos.fakeExecutor.runAllReady()
+ assertThat(property).isFalse()
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
index ee8a22c..5a39de8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
@@ -778,6 +778,7 @@
DREAM_COMPONENT,
false /*shouldShowComplication*/
)
+ testScope.runCurrent()
mMainExecutor.runAllReady()
assertThat(lifecycleRegistry.currentState).isEqualTo(Lifecycle.State.STARTED)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt
index 3d3c778..74eee9b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt
@@ -144,7 +144,7 @@
longPressEffect.handleActionUp()
// THEN the effect reverses
- assertEffectReverses()
+ assertEffectReverses(QSLongPressEffect.State.RUNNING_BACKWARDS_FROM_UP)
}
@Test
@@ -171,18 +171,17 @@
longPressEffect.handleActionCancel()
// THEN the effect gets reversed
- assertEffectReverses()
+ assertEffectReverses(QSLongPressEffect.State.RUNNING_BACKWARDS_FROM_CANCEL)
}
@Test
- fun onAnimationComplete_keyguardDismissible_effectEndsWithPrepare() =
+ fun onAnimationComplete_keyguardDismissible_effectCompletes() =
testWhileInState(QSLongPressEffect.State.RUNNING_FORWARD) {
// GIVEN that the animation completes
longPressEffect.handleAnimationComplete()
- // THEN the long-press effect completes and the view is called to prepare
+ // THEN the long-press effect completes
assertEffectCompleted()
- verify(callback, times(1)).onPrepareForLaunch()
}
@Test
@@ -200,6 +199,26 @@
}
@Test
+ fun onAnimationComplete_whenRunningBackwardsFromUp_endsWithFinishedReversing() =
+ testWhileInState(QSLongPressEffect.State.RUNNING_BACKWARDS_FROM_UP) {
+ // GIVEN that the animation completes
+ longPressEffect.handleAnimationComplete()
+
+ // THEN the callback for finished reversing is used.
+ verify(callback, times(1)).onEffectFinishedReversing()
+ }
+
+ @Test
+ fun onAnimationComplete_whenRunningBackwardsFromCancel_endsInIdle() =
+ testWhileInState(QSLongPressEffect.State.RUNNING_BACKWARDS_FROM_CANCEL) {
+ // GIVEN that the animation completes
+ longPressEffect.handleAnimationComplete()
+
+ // THEN the effect ends in the idle state.
+ assertThat(longPressEffect.state).isEqualTo(QSLongPressEffect.State.IDLE)
+ }
+
+ @Test
fun onActionDown_whileRunningBackwards_cancels() =
testWhileInState(QSLongPressEffect.State.RUNNING_FORWARD) {
// GIVEN an action cancel occurs and the effect gets reversed
@@ -223,11 +242,8 @@
}
@Test
- fun onAnimationComplete_whileRunningBackwards_goesToIdle() =
- testWhileInState(QSLongPressEffect.State.RUNNING_BACKWARDS) {
- // GIVEN an action cancel occurs and the effect gets reversed
- longPressEffect.handleActionCancel()
-
+ fun onAnimationComplete_whileRunningBackwardsFromCancel_goesToIdle() =
+ testWhileInState(QSLongPressEffect.State.RUNNING_BACKWARDS_FROM_CANCEL) {
// GIVEN that the animation completes
longPressEffect.handleAnimationComplete()
@@ -307,12 +323,16 @@
/**
* Asserts that the effect did not start by checking that:
* 1. No haptics are played
- * 2. The internal state is not [QSLongPressEffect.State.RUNNING_BACKWARDS] or
- * [QSLongPressEffect.State.RUNNING_FORWARD]
+ * 2. The internal state is not [QSLongPressEffect.State.RUNNING_BACKWARDS_FROM_UP] or
+ * [QSLongPressEffect.State.RUNNING_FORWARD] or
+ * [QSLongPressEffect.State.RUNNING_BACKWARDS_FROM_CANCEL]
*/
private fun assertEffectDidNotStart() {
assertThat(longPressEffect.state).isNotEqualTo(QSLongPressEffect.State.RUNNING_FORWARD)
- assertThat(longPressEffect.state).isNotEqualTo(QSLongPressEffect.State.RUNNING_BACKWARDS)
+ assertThat(longPressEffect.state)
+ .isNotEqualTo(QSLongPressEffect.State.RUNNING_BACKWARDS_FROM_UP)
+ assertThat(longPressEffect.state)
+ .isNotEqualTo(QSLongPressEffect.State.RUNNING_BACKWARDS_FROM_CANCEL)
assertThat(vibratorHelper.totalVibrations).isEqualTo(0)
}
@@ -330,12 +350,14 @@
}
/**
- * Assert that the effect gets reverted by checking that:
- * 1. The internal state is [QSLongPressEffect.State.RUNNING_BACKWARDS]
- * 2. An action to reverse the animator is emitted
+ * Assert that the effect gets reverted by checking that the callback to reverse the animator is
+ * used, and that the state is given reversing state.
+ *
+ * @param[reversingState] Either [QSLongPressEffect.State.RUNNING_BACKWARDS_FROM_CANCEL] or
+ * [QSLongPressEffect.State.RUNNING_BACKWARDS_FROM_UP]
*/
- private fun assertEffectReverses() {
- assertThat(longPressEffect.state).isEqualTo(QSLongPressEffect.State.RUNNING_BACKWARDS)
+ private fun assertEffectReverses(reversingState: QSLongPressEffect.State) {
+ assertThat(longPressEffect.state).isEqualTo(reversingState)
verify(callback, times(1)).onReverseAnimator()
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
index 5dac37a..48caf3e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
@@ -140,6 +140,27 @@
}
@Test
+ fun panelAlpha() =
+ testScope.runTest {
+ assertThat(underTest.panelAlpha.value).isEqualTo(1f)
+
+ underTest.setPanelAlpha(0.1f)
+ assertThat(underTest.panelAlpha.value).isEqualTo(0.1f)
+
+ underTest.setPanelAlpha(0.2f)
+ assertThat(underTest.panelAlpha.value).isEqualTo(0.2f)
+
+ underTest.setPanelAlpha(0.3f)
+ assertThat(underTest.panelAlpha.value).isEqualTo(0.3f)
+
+ underTest.setPanelAlpha(0.5f)
+ assertThat(underTest.panelAlpha.value).isEqualTo(0.5f)
+
+ underTest.setPanelAlpha(1.0f)
+ assertThat(underTest.panelAlpha.value).isEqualTo(1f)
+ }
+
+ @Test
fun topClippingBounds() =
testScope.runTest {
assertThat(underTest.topClippingBounds.value).isNull()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/navigation/data/repository/NavigationRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigation/data/repository/NavigationRepositoryTest.kt
new file mode 100644
index 0000000..e45aa05
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigation/data/repository/NavigationRepositoryTest.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2024 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.navigation.data.repository
+
+import android.view.WindowManagerPolicyConstants
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.navigationbar.NavigationModeController.ModeChangedListener
+import com.android.systemui.navigationbar.navigationModeController
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class NavigationRepositoryTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val navigationModeControllerMock = kosmos.navigationModeController
+
+ private val underTest = kosmos.navigationRepository
+
+ private var currentMode = WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON
+ private val modeChangedListeners = mutableListOf<ModeChangedListener>()
+
+ @Before
+ fun setUp() {
+ whenever(navigationModeControllerMock.addListener(any())).thenAnswer { invocation ->
+ val listener = invocation.arguments[0] as ModeChangedListener
+ modeChangedListeners.add(listener)
+ currentMode
+ }
+ }
+
+ @Test
+ fun isGesturalMode() =
+ testScope.runTest {
+ val isGesturalMode by collectLastValue(underTest.isGesturalMode)
+ assertThat(isGesturalMode).isFalse()
+
+ currentMode = WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL
+ notifyModeChangedListeners()
+ assertThat(isGesturalMode).isTrue()
+
+ currentMode = WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON
+ notifyModeChangedListeners()
+ assertThat(isGesturalMode).isFalse()
+ }
+
+ private fun notifyModeChangedListeners() {
+ modeChangedListeners.forEach { listener -> listener.onNavigationModeChanged(currentMode) }
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/navigation/domain/interactor/NavigationInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigation/domain/interactor/NavigationInteractorTest.kt
new file mode 100644
index 0000000..88beeb2
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigation/domain/interactor/NavigationInteractorTest.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2024 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.navigation.domain.interactor
+
+import android.view.WindowManagerPolicyConstants
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.navigationbar.NavigationModeController.ModeChangedListener
+import com.android.systemui.navigationbar.navigationModeController
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class NavigationInteractorTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val navigationModeControllerMock = kosmos.navigationModeController
+
+ private val underTest = kosmos.navigationInteractor
+
+ private var currentMode = WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON
+ private val modeChangedListeners = mutableListOf<ModeChangedListener>()
+
+ @Before
+ fun setUp() {
+ whenever(navigationModeControllerMock.addListener(any())).thenAnswer { invocation ->
+ val listener = invocation.arguments[0] as ModeChangedListener
+ modeChangedListeners.add(listener)
+ currentMode
+ }
+ }
+
+ @Test
+ fun isGesturalMode() =
+ testScope.runTest {
+ val isGesturalMode by collectLastValue(underTest.isGesturalMode)
+ assertThat(isGesturalMode).isFalse()
+
+ currentMode = WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL
+ notifyModeChangedListeners()
+ assertThat(isGesturalMode).isTrue()
+
+ currentMode = WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON
+ notifyModeChangedListeners()
+ assertThat(isGesturalMode).isFalse()
+ }
+
+ private fun notifyModeChangedListeners() {
+ modeChangedListeners.forEach { listener -> listener.onNavigationModeChanged(currentMode) }
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt
index b7e08da..ff40e43 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt
@@ -54,7 +54,7 @@
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
- whenever(logBufferFactory.create(any(), any(), any())).thenReturn(logBuffer)
+ whenever(logBufferFactory.create(any(), any(), any(), any())).thenReturn(logBuffer)
val tileSpec: TileSpec = TileSpec.create("chatty_tile")
underTest =
QSTileLogger(mapOf(tileSpec to chattyLogBuffer), logBufferFactory, statusBarController)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/CommunalSmartspaceControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/CommunalSmartspaceControllerTest.kt
index ad4b98b..eac86e5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/CommunalSmartspaceControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/CommunalSmartspaceControllerTest.kt
@@ -20,6 +20,7 @@
import android.app.smartspace.SmartspaceTarget
import android.content.Context
import android.graphics.drawable.Drawable
+import android.os.Handler
import android.testing.TestableLooper
import android.view.View
import android.widget.FrameLayout
@@ -86,6 +87,8 @@
override fun setUiSurface(uiSurface: String) {}
+ override fun setBgHandler(bgHandler: Handler?) {}
+
override fun setDozeAmount(amount: Float) {}
override fun setIntentStarter(intentStarter: BcSmartspaceDataPlugin.IntentStarter?) {}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt
index 3a38631..e774aed 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt
@@ -20,6 +20,7 @@
import android.app.smartspace.SmartspaceTarget
import android.content.Context
import android.graphics.drawable.Drawable
+import android.os.Handler
import android.testing.TestableLooper
import android.view.View
import android.view.ViewGroup
@@ -119,6 +120,8 @@
override fun setUiSurface(uiSurface: String) {}
+ override fun setBgHandler(bgHandler: Handler?) {}
+
override fun setDozeAmount(amount: Float) {}
override fun setIntentStarter(intentStarter: BcSmartspaceDataPlugin.IntentStarter?) {}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
index 497484f90..f14c96ded 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
@@ -23,11 +23,15 @@
import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.FlagsParameterization
import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.ObservableTransitionState
import com.android.systemui.Flags.FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX
import com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT
import com.android.systemui.SysuiTestCase
+import com.android.systemui.bouncer.data.repository.keyguardBouncerRepository
import com.android.systemui.common.shared.model.NotificationContainerBounds
import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
+import com.android.systemui.communal.data.repository.communalSceneRepository
+import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
import com.android.systemui.flags.BrokenWithSceneContainer
@@ -134,6 +138,9 @@
val largeScreenHeaderHelper
get() = kosmos.mockLargeScreenHeaderHelper
+ val communalSceneRepository
+ get() = kosmos.communalSceneRepository
+
lateinit var underTest: SharedNotificationContainerViewModel
@Before
@@ -845,26 +852,24 @@
@Test
@DisableSceneContainer
fun updateBounds_fromGone_withoutTransitions() =
- testScope.runTest {
- // Start step is already at 1.0
- val runningStep = TransitionStep(GONE, AOD, 1.0f, TransitionState.RUNNING)
- val finishStep = TransitionStep(GONE, AOD, 1.0f, TransitionState.FINISHED)
+ testScope.runTest {
+ // Start step is already at 1.0
+ val runningStep = TransitionStep(GONE, AOD, 1.0f, TransitionState.RUNNING)
+ val finishStep = TransitionStep(GONE, AOD, 1.0f, TransitionState.FINISHED)
- val bounds by collectLastValue(underTest.bounds)
- val top = 123f
- val bottom = 456f
+ val bounds by collectLastValue(underTest.bounds)
+ val top = 123f
+ val bottom = 456f
- kosmos.fakeKeyguardTransitionRepository.sendTransitionStep(runningStep)
- runCurrent()
- kosmos.fakeKeyguardTransitionRepository.sendTransitionStep(finishStep)
- runCurrent()
- keyguardRootViewModel.onNotificationContainerBoundsChanged(top, bottom)
- runCurrent()
+ kosmos.fakeKeyguardTransitionRepository.sendTransitionStep(runningStep)
+ runCurrent()
+ kosmos.fakeKeyguardTransitionRepository.sendTransitionStep(finishStep)
+ runCurrent()
+ keyguardRootViewModel.onNotificationContainerBoundsChanged(top, bottom)
+ runCurrent()
- assertThat(bounds).isEqualTo(
- NotificationContainerBounds(top = top, bottom = bottom)
- )
- }
+ assertThat(bounds).isEqualTo(NotificationContainerBounds(top = top, bottom = bottom))
+ }
@Test
fun alphaOnFullQsExpansion() =
@@ -1020,6 +1025,230 @@
assertThat(fadeIn[0]).isEqualTo(false)
}
+ @Test
+ @BrokenWithSceneContainer(330311871)
+ fun alpha_isZero_fromPrimaryBouncerToGoneWhileCommunalSceneVisible() =
+ testScope.runTest {
+ val viewState = ViewStateAccessor()
+ val alpha by collectLastValue(underTest.keyguardAlpha(viewState))
+
+ showPrimaryBouncer()
+ showCommunalScene()
+
+ // PRIMARY_BOUNCER->GONE transition is started
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.PRIMARY_BOUNCER,
+ to = GONE,
+ transitionState = TransitionState.STARTED,
+ value = 0f,
+ )
+ )
+ runCurrent()
+
+ // PRIMARY_BOUNCER->GONE transition running
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.PRIMARY_BOUNCER,
+ to = GONE,
+ transitionState = TransitionState.RUNNING,
+ value = 0.1f,
+ )
+ )
+ runCurrent()
+ assertThat(alpha).isEqualTo(0f)
+
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.PRIMARY_BOUNCER,
+ to = GONE,
+ transitionState = TransitionState.RUNNING,
+ value = 0.9f,
+ )
+ )
+ runCurrent()
+ assertThat(alpha).isEqualTo(0f)
+
+ hideCommunalScene()
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.PRIMARY_BOUNCER,
+ to = GONE,
+ transitionState = TransitionState.FINISHED,
+ value = 1f
+ )
+ )
+ runCurrent()
+ assertThat(alpha).isEqualTo(0f)
+ }
+
+ @Test
+ @BrokenWithSceneContainer(330311871)
+ fun alpha_fromPrimaryBouncerToGoneWhenCommunalSceneNotVisible() =
+ testScope.runTest {
+ val viewState = ViewStateAccessor()
+ val alpha by collectLastValue(underTest.keyguardAlpha(viewState))
+
+ showPrimaryBouncer()
+ hideCommunalScene()
+
+ // PRIMARY_BOUNCER->GONE transition is started
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.PRIMARY_BOUNCER,
+ to = GONE,
+ transitionState = TransitionState.STARTED,
+ )
+ )
+ runCurrent()
+
+ // PRIMARY_BOUNCER->GONE transition running
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.PRIMARY_BOUNCER,
+ to = GONE,
+ transitionState = TransitionState.RUNNING,
+ value = 0.1f,
+ )
+ )
+ runCurrent()
+ assertThat(alpha).isIn(Range.closedOpen(0f, 1f))
+
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.PRIMARY_BOUNCER,
+ to = GONE,
+ transitionState = TransitionState.RUNNING,
+ value = 0.9f,
+ )
+ )
+ runCurrent()
+ assertThat(alpha).isIn(Range.closedOpen(0f, 1f))
+
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.PRIMARY_BOUNCER,
+ to = GONE,
+ transitionState = TransitionState.FINISHED,
+ value = 1f
+ )
+ )
+ runCurrent()
+ assertThat(alpha).isEqualTo(0f)
+ }
+
+ @Test
+ @BrokenWithSceneContainer(330311871)
+ fun alpha_isZero_fromAlternateBouncerToGoneWhileCommunalSceneVisible() =
+ testScope.runTest {
+ val viewState = ViewStateAccessor()
+ val alpha by collectLastValue(underTest.keyguardAlpha(viewState))
+
+ showAlternateBouncer()
+ showCommunalScene()
+
+ // ALTERNATE_BOUNCER->GONE transition is started
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.ALTERNATE_BOUNCER,
+ to = GONE,
+ transitionState = TransitionState.STARTED,
+ value = 0f,
+ )
+ )
+ runCurrent()
+
+ // ALTERNATE_BOUNCER->GONE transition running
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.ALTERNATE_BOUNCER,
+ to = GONE,
+ transitionState = TransitionState.RUNNING,
+ value = 0.1f,
+ )
+ )
+ runCurrent()
+ assertThat(alpha).isEqualTo(0f)
+
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.ALTERNATE_BOUNCER,
+ to = GONE,
+ transitionState = TransitionState.RUNNING,
+ value = 0.9f,
+ )
+ )
+ runCurrent()
+ assertThat(alpha).isEqualTo(0f)
+
+ hideCommunalScene()
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.ALTERNATE_BOUNCER,
+ to = GONE,
+ transitionState = TransitionState.FINISHED,
+ value = 1f
+ )
+ )
+ runCurrent()
+ assertThat(alpha).isEqualTo(0f)
+ }
+
+ @Test
+ @BrokenWithSceneContainer(330311871)
+ fun alpha_fromAlternateBouncerToGoneWhenCommunalSceneNotVisible() =
+ testScope.runTest {
+ val viewState = ViewStateAccessor()
+ val alpha by collectLastValue(underTest.keyguardAlpha(viewState))
+
+ showAlternateBouncer()
+ hideCommunalScene()
+
+ // ALTERNATE_BOUNCER->GONE transition is started
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.ALTERNATE_BOUNCER,
+ to = GONE,
+ transitionState = TransitionState.STARTED,
+ )
+ )
+ runCurrent()
+
+ // ALTERNATE_BOUNCER->GONE transition running
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.ALTERNATE_BOUNCER,
+ to = GONE,
+ transitionState = TransitionState.RUNNING,
+ value = 0.1f,
+ )
+ )
+ runCurrent()
+ assertThat(alpha).isIn(Range.closedOpen(0f, 1f))
+
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.ALTERNATE_BOUNCER,
+ to = GONE,
+ transitionState = TransitionState.RUNNING,
+ value = 0.9f,
+ )
+ )
+ runCurrent()
+ assertThat(alpha).isIn(Range.closedOpen(0f, 1f))
+
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.ALTERNATE_BOUNCER,
+ to = GONE,
+ transitionState = TransitionState.FINISHED,
+ value = 1f
+ )
+ )
+ runCurrent()
+ assertThat(alpha).isEqualTo(0f)
+ }
+
private suspend fun TestScope.showLockscreen() {
shadeTestUtil.setQsExpansion(0f)
shadeTestUtil.setLockscreenShadeExpansion(0f)
@@ -1071,4 +1300,52 @@
testScope,
)
}
+
+ private suspend fun TestScope.showPrimaryBouncer() {
+ shadeTestUtil.setQsExpansion(0f)
+ shadeTestUtil.setLockscreenShadeExpansion(0f)
+ runCurrent()
+ keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
+ runCurrent()
+ kosmos.keyguardBouncerRepository.setPrimaryShow(true)
+ runCurrent()
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.GLANCEABLE_HUB,
+ to = KeyguardState.PRIMARY_BOUNCER,
+ testScope,
+ )
+ }
+
+ private suspend fun TestScope.showAlternateBouncer() {
+ shadeTestUtil.setQsExpansion(0f)
+ shadeTestUtil.setLockscreenShadeExpansion(0f)
+ runCurrent()
+ keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
+ runCurrent()
+ kosmos.keyguardBouncerRepository.setPrimaryShow(false)
+ runCurrent()
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.GLANCEABLE_HUB,
+ to = KeyguardState.ALTERNATE_BOUNCER,
+ testScope,
+ )
+ }
+
+ private fun TestScope.showCommunalScene() {
+ val transitionState =
+ MutableStateFlow<ObservableTransitionState>(
+ ObservableTransitionState.Idle(CommunalScenes.Communal)
+ )
+ communalSceneRepository.setTransitionState(transitionState)
+ runCurrent()
+ }
+
+ private fun TestScope.hideCommunalScene() {
+ val transitionState =
+ MutableStateFlow<ObservableTransitionState>(
+ ObservableTransitionState.Idle(CommunalScenes.Blank)
+ )
+ communalSceneRepository.setTransitionState(transitionState)
+ runCurrent()
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImplTest.kt
index 57d3251..1656a2e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImplTest.kt
@@ -60,6 +60,7 @@
import com.google.common.truth.Truth.assertThat
import java.util.Optional
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -132,6 +133,7 @@
communalSceneInteractor = communalSceneInteractor,
)
whenever(userTracker.userHandle).thenReturn(UserHandle.OWNER)
+ whenever(communalSceneInteractor.isIdleOnCommunal).thenReturn(MutableStateFlow(false))
}
@Test
diff --git a/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java b/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java
index 9ad4012..074277c 100644
--- a/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java
+++ b/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java
@@ -24,6 +24,7 @@
import android.content.ActivityNotFoundException;
import android.content.Intent;
import android.graphics.drawable.Drawable;
+import android.os.Handler;
import android.os.Parcelable;
import android.util.Log;
import android.view.View;
@@ -123,6 +124,9 @@
*/
void setUiSurface(String uiSurface);
+ /** Set background handler to make binder calls. */
+ void setBgHandler(Handler bgHandler);
+
/**
* Range [0.0 - 1.0] when transitioning from Lockscreen to/from AOD
*/
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java
index dafd5f8..030d147 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java
@@ -72,6 +72,7 @@
private final Handler mHandler;
private boolean mIsFadeEffectEnabled;
private Runnable mSpringAnimationsEndAction;
+ private PointF mAnimationEndPosition = new PointF();
// Cache the animations state of {@link DynamicAnimation.TRANSLATION_X} and {@link
// DynamicAnimation.TRANSLATION_Y} to be well controlled by the touch handler
@@ -104,10 +105,12 @@
@Override
public void onRadiiAnimationStop() {}
});
+ mAnimationEndPosition = mMenuView.getMenuPosition();
}
void moveToPosition(PointF position) {
moveToPosition(position, /* animateMovement = */ false);
+ mAnimationEndPosition = position;
}
/* Moves position without updating underlying percentage position. Can be animated. */
@@ -129,6 +132,7 @@
} else {
DynamicAnimation.TRANSLATION_X.setValue(mMenuView, positionX);
}
+ mAnimationEndPosition.x = positionX;
}
void moveToPositionY(float positionY) {
@@ -144,6 +148,7 @@
} else {
DynamicAnimation.TRANSLATION_Y.setValue(mMenuView, positionY);
}
+ mAnimationEndPosition.y = positionY;
}
void moveToPositionYIfNeeded(float positionY) {
@@ -259,6 +264,9 @@
cancelAnimation(property);
mPositionAnimations.put(property, flingAnimation);
+ if (finalPosition != null) {
+ setAnimationEndPosition(property, finalPosition);
+ }
flingAnimation.start();
}
@@ -292,6 +300,7 @@
cancelAnimation(property);
mPositionAnimations.put(property, springAnimation);
+ setAnimationEndPosition(property, finalPosition);
springAnimation.animateToFinalPosition(finalPosition);
}
@@ -385,6 +394,21 @@
mPositionAnimations.get(property).cancel();
}
+ private void setAnimationEndPosition(
+ DynamicAnimation.ViewProperty property, Float endPosition) {
+ if (property.equals(DynamicAnimation.TRANSLATION_X)) {
+ mAnimationEndPosition.x = endPosition;
+ }
+ if (property.equals(DynamicAnimation.TRANSLATION_Y)) {
+ mAnimationEndPosition.y = endPosition;
+ }
+ }
+
+ void skipAnimations() {
+ cancelAnimations();
+ moveToPosition(mAnimationEndPosition, false);
+ }
+
@VisibleForTesting
DynamicAnimation getAnimation(DynamicAnimation.ViewProperty property) {
return mPositionAnimations.getOrDefault(property, null);
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
index 0c67c50..ae9775a0 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
@@ -334,6 +334,7 @@
mDragToInteractView.updateResources();
mDismissView.updateResources();
mDragToInteractAnimationController.updateResources();
+ mMenuAnimationController.skipAnimations();
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt
index 20d8a2a..fd540c4 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt
@@ -22,6 +22,7 @@
import com.android.systemui.communal.data.repository.CommunalSceneRepository
import com.android.systemui.communal.domain.model.CommunalTransitionProgressModel
import com.android.systemui.communal.shared.model.CommunalScenes
+import com.android.systemui.communal.shared.model.CommunalTransitionKeys
import com.android.systemui.communal.shared.model.EditModeState
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
@@ -60,14 +61,14 @@
communalSceneRepository.snapToScene(newScene, delayMillis)
}
- /** Immediately snaps to the new scene when activity is started. */
- fun snapToSceneForActivityStart(newScene: SceneKey, delayMillis: Long = 0) {
+ /** Changes to Blank scene when starting an activity after dismissing keyguard. */
+ fun changeSceneForActivityStartOnDismissKeyguard() {
// skip if we're starting edit mode activity, as it will be handled later by changeScene
// with transition key [CommunalTransitionKeys.ToEditMode].
if (_editModeState.value == EditModeState.STARTING) {
return
}
- snapToScene(newScene, delayMillis)
+ changeScene(CommunalScenes.Blank, CommunalTransitionKeys.SimpleFade)
}
/**
@@ -144,8 +145,14 @@
*
* This flow will be true during any transition and when idle on the communal scene.
*/
- val isCommunalVisible: Flow<Boolean> =
- transitionState.map {
- !(it is ObservableTransitionState.Idle && it.currentScene == CommunalScenes.Blank)
- }
+ val isCommunalVisible: StateFlow<Boolean> =
+ transitionState
+ .map {
+ !(it is ObservableTransitionState.Idle && it.currentScene == CommunalScenes.Blank)
+ }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = false,
+ )
}
diff --git a/packages/SystemUI/src/com/android/systemui/deviceconfig/data/repository/DeviceConfigRepository.kt b/packages/SystemUI/src/com/android/systemui/deviceconfig/data/repository/DeviceConfigRepository.kt
new file mode 100644
index 0000000..250b432
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/deviceconfig/data/repository/DeviceConfigRepository.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2024 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.deviceconfig.data.repository
+
+import android.provider.DeviceConfig
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.util.DeviceConfigProxy
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
+import java.util.concurrent.Executor
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOn
+
+@SysUISingleton
+class DeviceConfigRepository
+@Inject
+constructor(
+ @Background private val backgroundExecutor: Executor,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
+ private val dataSource: DeviceConfigProxy,
+) {
+
+ fun property(namespace: String, name: String, default: Boolean): Flow<Boolean> {
+ return conflatedCallbackFlow {
+ val listener = { properties: DeviceConfig.Properties ->
+ if (properties.keyset.contains(name)) {
+ trySend(properties.getBoolean(name, default))
+ }
+ }
+
+ dataSource.addOnPropertiesChangedListener(
+ namespace,
+ backgroundExecutor,
+ listener,
+ )
+ trySend(dataSource.getBoolean(namespace, name, default))
+
+ awaitClose { dataSource.removeOnPropertiesChangedListener(listener) }
+ }
+ .flowOn(backgroundDispatcher)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/deviceconfig/domain/interactor/DeviceConfigInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceconfig/domain/interactor/DeviceConfigInteractor.kt
new file mode 100644
index 0000000..d04f8bc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/deviceconfig/domain/interactor/DeviceConfigInteractor.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2024 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.deviceconfig.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.deviceconfig.data.repository.DeviceConfigRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+
+@SysUISingleton
+class DeviceConfigInteractor
+@Inject
+constructor(
+ private val repository: DeviceConfigRepository,
+) {
+
+ fun property(namespace: String, name: String, default: Boolean): Flow<Boolean> {
+ return repository.property(namespace, name, default)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt
index be4c903..7b5139a 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt
@@ -97,14 +97,15 @@
State.IDLE -> {
setState(State.TIMEOUT_WAIT)
}
- State.RUNNING_BACKWARDS -> callback?.onCancelAnimator()
+ State.RUNNING_BACKWARDS_FROM_UP,
+ State.RUNNING_BACKWARDS_FROM_CANCEL -> callback?.onCancelAnimator()
else -> {}
}
}
fun handleActionUp() {
if (state == State.RUNNING_FORWARD) {
- setState(State.RUNNING_BACKWARDS)
+ setState(State.RUNNING_BACKWARDS_FROM_UP)
callback?.onReverseAnimator()
}
}
@@ -113,7 +114,7 @@
when (state) {
State.TIMEOUT_WAIT -> setState(State.IDLE)
State.RUNNING_FORWARD -> {
- setState(State.RUNNING_BACKWARDS)
+ setState(State.RUNNING_BACKWARDS_FROM_CANCEL)
callback?.onReverseAnimator()
}
else -> {}
@@ -127,20 +128,24 @@
/** This function is called both when an animator completes or gets cancelled */
fun handleAnimationComplete() {
- if (state == State.RUNNING_FORWARD) {
- setState(State.IDLE)
- vibrate(snapEffect)
- if (keyguardStateController.isUnlocked) {
- callback?.onPrepareForLaunch()
- qsTile?.longClick(expandable)
- } else {
- callback?.onResetProperties()
- qsTile?.longClick(expandable)
+ when (state) {
+ State.RUNNING_FORWARD -> {
+ setState(State.IDLE)
+ vibrate(snapEffect)
+ if (keyguardStateController.isUnlocked) {
+ qsTile?.longClick(expandable)
+ } else {
+ callback?.onResetProperties()
+ qsTile?.longClick(expandable)
+ }
}
- }
- if (state != State.TIMEOUT_WAIT) {
- // This will happen if the animator did not finish by being cancelled
- setState(State.IDLE)
+ State.RUNNING_BACKWARDS_FROM_UP -> {
+ setState(State.IDLE)
+ callback?.onEffectFinishedReversing()
+ qsTile?.click(expandable)
+ }
+ State.RUNNING_BACKWARDS_FROM_CANCEL -> setState(State.IDLE)
+ else -> {}
}
}
@@ -191,20 +196,23 @@
enum class State {
IDLE, /* The effect is idle waiting for touch input */
- TIMEOUT_WAIT, /* The effect is waiting for a [PRESSED_TIMEOUT] period */
+ TIMEOUT_WAIT, /* The effect is waiting for a tap timeout period */
RUNNING_FORWARD, /* The effect is running normally */
- RUNNING_BACKWARDS, /* The effect was interrupted and is now running backwards */
+ /* The effect was interrupted by an ACTION_UP and is now running backwards */
+ RUNNING_BACKWARDS_FROM_UP,
+ /* The effect was interrupted by an ACTION_CANCEL and is now running backwards */
+ RUNNING_BACKWARDS_FROM_CANCEL,
}
/** Callbacks to notify view and animator actions */
interface Callback {
- /** Prepare for an activity launch */
- fun onPrepareForLaunch()
-
/** Reset the tile visual properties */
fun onResetProperties()
+ /** Event where the effect completed by being reversed */
+ fun onEffectFinishedReversing()
+
/** Start the effect animator */
fun onStartAnimator()
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index d508b2b..cdf3b06 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -78,6 +78,8 @@
val keyguardAlpha: StateFlow<Float>
+ val panelAlpha: MutableStateFlow<Float>
+
/**
* Observable for whether the keyguard is showing.
*
@@ -250,6 +252,9 @@
/** Sets the current amount of alpha that should be used for rendering the keyguard. */
fun setKeyguardAlpha(alpha: Float)
+ /** Temporary shim for fading out content when the brightness slider is used */
+ fun setPanelAlpha(alpha: Float)
+
/** Whether the device is actively dreaming */
fun setDreaming(isDreaming: Boolean)
@@ -338,6 +343,8 @@
private val _keyguardAlpha = MutableStateFlow(1f)
override val keyguardAlpha = _keyguardAlpha.asStateFlow()
+ override val panelAlpha: MutableStateFlow<Float> = MutableStateFlow(1f)
+
private val _clockShouldBeCentered = MutableStateFlow(true)
override val clockShouldBeCentered: Flow<Boolean> = _clockShouldBeCentered.asStateFlow()
@@ -659,6 +666,10 @@
_keyguardAlpha.value = alpha
}
+ override fun setPanelAlpha(alpha: Float) {
+ panelAlpha.value = alpha
+ }
+
override fun setDreaming(isDreaming: Boolean) {
this.isDreaming.value = isDreaming
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index ef96be0..ab1194e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -321,6 +321,10 @@
@Deprecated("Use the relevant TransitionViewModel")
val keyguardAlpha: Flow<Float> = repository.keyguardAlpha
+ /** Temporary shim for fading out content when the brightness slider is used */
+ @Deprecated("SceneContainer uses NotificationStackAppearanceInteractor")
+ val panelAlpha: StateFlow<Float> = repository.panelAlpha.asStateFlow()
+
/**
* When the lockscreen can be dismissed, emit an alpha value as the user swipes up. This is
* useful just before the code commits to moving to GONE.
@@ -458,6 +462,10 @@
repository.setKeyguardAlpha(alpha)
}
+ fun setPanelAlpha(alpha: Float) {
+ repository.setPanelAlpha(alpha)
+ }
+
fun setAnimateDozingTransitions(animate: Boolean) {
repository.setAnimateDozingTransitions(animate)
}
diff --git a/packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt b/packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt
index e4465ac..6351d7d 100644
--- a/packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt
@@ -19,10 +19,13 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dump.DumpManager
import com.android.systemui.log.LogBufferHelper.Companion.adjustMaxSize
+import com.android.systemui.log.echo.LogcatEchoTrackerAlways
import javax.inject.Inject
@SysUISingleton
-class LogBufferFactory @Inject constructor(
+class LogBufferFactory
+@Inject
+constructor(
private val dumpManager: DumpManager,
private val logcatEchoTracker: LogcatEchoTracker
) {
@@ -30,9 +33,11 @@
fun create(
name: String,
maxSize: Int,
- systrace: Boolean = true
+ systrace: Boolean = true,
+ alwaysLogToLogcat: Boolean = false,
): LogBuffer {
- val buffer = LogBuffer(name, adjustMaxSize(maxSize), logcatEchoTracker, systrace)
+ val echoTracker = if (alwaysLogToLogcat) LogcatEchoTrackerAlways else logcatEchoTracker
+ val buffer = LogBuffer(name, adjustMaxSize(maxSize), echoTracker, systrace)
dumpManager.registerBuffer(name, buffer)
return buffer
}
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 1e79f42..c7fde48 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -622,7 +622,8 @@
@SysUISingleton
@SceneFrameworkLog
public static LogBuffer provideSceneFrameworkLogBuffer(LogBufferFactory factory) {
- return factory.create("SceneFramework", 50);
+ return factory
+ .create("SceneFramework", 50, /* systrace */ true, /* alwaysLogToLogcat */ true);
}
/** Provides a {@link LogBuffer} for the bluetooth QS tile dialog. */
diff --git a/packages/SystemUI/src/com/android/systemui/log/echo/LogcatEchoTrackerAlways.kt b/packages/SystemUI/src/com/android/systemui/log/echo/LogcatEchoTrackerAlways.kt
new file mode 100644
index 0000000..ce096b3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/echo/LogcatEchoTrackerAlways.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2024 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.echo
+
+import com.android.systemui.log.LogcatEchoTracker
+import com.android.systemui.log.core.LogLevel
+
+/**
+ * The buffer and all of its tags will be logged to logcat at all times.
+ *
+ * This can be used for buffers that are important and should appear in bugreports in logcat
+ * directly.
+ */
+object LogcatEchoTrackerAlways : LogcatEchoTracker {
+ override fun isBufferLoggable(bufferName: String, level: LogLevel): Boolean = true
+
+ override fun isTagLoggable(tagName: String, level: LogLevel): Boolean = true
+}
diff --git a/packages/SystemUI/src/com/android/systemui/navigation/data/repository/NavigationRepository.kt b/packages/SystemUI/src/com/android/systemui/navigation/data/repository/NavigationRepository.kt
new file mode 100644
index 0000000..4409e2c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/navigation/data/repository/NavigationRepository.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2024 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.navigation.data.repository
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.navigationbar.NavigationModeController
+import com.android.systemui.shared.system.QuickStepContract
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
+import javax.inject.Inject
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+
+@SysUISingleton
+class NavigationRepository
+@Inject
+constructor(
+ private val controller: NavigationModeController,
+) {
+
+ /** Whether the current navigation bar mode is edge-to-edge. */
+ val isGesturalMode: Flow<Boolean> = conflatedCallbackFlow {
+ val listener =
+ NavigationModeController.ModeChangedListener { mode ->
+ trySend(QuickStepContract.isGesturalMode(mode))
+ }
+
+ val currentMode = controller.addListener(listener)
+ trySend(QuickStepContract.isGesturalMode(currentMode))
+
+ awaitClose { controller.removeListener(listener) }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/navigation/domain/interactor/NavigationInteractor.kt b/packages/SystemUI/src/com/android/systemui/navigation/domain/interactor/NavigationInteractor.kt
new file mode 100644
index 0000000..0f9c883
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/navigation/domain/interactor/NavigationInteractor.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2024 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.navigation.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.navigation.data.repository.NavigationRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+
+@SysUISingleton
+class NavigationInteractor
+@Inject
+constructor(
+ repository: NavigationRepository,
+) {
+
+ /** Whether the current navigation bar mode is edge-to-edge. */
+ val isGesturalMode: Flow<Boolean> = repository.isGesturalMode
+}
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 b1b67cf..44c846b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
@@ -350,6 +350,10 @@
initialLongPressProperties?.width = width
finalLongPressProperties?.width = LONG_PRESS_EFFECT_WIDTH_SCALE * width
+
+ val deltaW = (LONG_PRESS_EFFECT_WIDTH_SCALE - 1f) * width
+ paddingForLaunch.left = -deltaW.toInt() / 2
+ paddingForLaunch.right = deltaW.toInt() / 2
}
private fun maybeUpdateLongPressEffectHeight(height: Float) {
@@ -357,6 +361,10 @@
initialLongPressProperties?.height = height
finalLongPressProperties?.height = LONG_PRESS_EFFECT_HEIGHT_SCALE * height
+
+ val deltaH = (LONG_PRESS_EFFECT_HEIGHT_SCALE - 1f) * height
+ paddingForLaunch.top = -deltaH.toInt() / 2
+ paddingForLaunch.bottom = deltaH.toInt() / 2
}
override fun onFocusChanged(gainFocus: Boolean, direction: Int, previouslyFocusedRect: Rect?) {
@@ -432,14 +440,16 @@
longPressEffect?.callback =
object : QSLongPressEffect.Callback {
- override fun onPrepareForLaunch() {
- prepareForLaunch()
- }
-
override fun onResetProperties() {
resetLongPressEffectProperties()
}
+ override fun onEffectFinishedReversing() {
+ // The long-press effect properties finished at the same starting point.
+ // This is the same as if the properties were reset
+ haveLongPressPropertiesBeenReset = true
+ }
+
override fun onStartAnimator() {
if (longPressEffectAnimator?.isRunning != true) {
longPressEffectAnimator =
@@ -1043,6 +1053,7 @@
getOverlayColorForState(Tile.STATE_ACTIVE),
Utils.getColorAttrDefaultColor(context, R.attr.onShadeActive),
)
+ prepareForLaunch()
}
private fun changeCornerRadius(radius: Float) {
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
index 08175c3..4738dbd 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
@@ -41,6 +41,7 @@
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
/**
@@ -102,7 +103,14 @@
* 2. When transitioning, which scenes are being transitioned between.
* 3. When transitioning, what the progress of the transition is.
*/
- val transitionState: StateFlow<ObservableTransitionState> = repository.transitionState
+ val transitionState: StateFlow<ObservableTransitionState> =
+ repository.transitionState
+ .onEach { logger.logSceneTransition(it) }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.Eagerly,
+ initialValue = repository.transitionState.value,
+ )
/**
* The key of the scene that the UI is currently transitioning to or `null` if there is no
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt
index 9d6720b..cf1518e 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt
@@ -16,6 +16,7 @@
package com.android.systemui.scene.shared.logger
+import com.android.compose.animation.scene.ObservableTransitionState
import com.android.compose.animation.scene.SceneKey
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.LogLevel
@@ -84,6 +85,30 @@
)
}
+ fun logSceneTransition(transitionState: ObservableTransitionState) {
+ when (transitionState) {
+ is ObservableTransitionState.Transition -> {
+ logBuffer.log(
+ tag = TAG,
+ level = LogLevel.INFO,
+ messageInitializer = {
+ str1 = transitionState.fromScene.toString()
+ str2 = transitionState.toScene.toString()
+ },
+ messagePrinter = { "Scene transition started: $str1 → $str2" },
+ )
+ }
+ is ObservableTransitionState.Idle -> {
+ logBuffer.log(
+ tag = TAG,
+ level = LogLevel.INFO,
+ messageInitializer = { str1 = transitionState.currentScene.toString() },
+ messagePrinter = { "Scene transition idle on: $str1" },
+ )
+ }
+ }
+ }
+
fun logVisibilityChange(
from: Boolean,
to: Boolean,
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 8b78f54..9624e0f 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -483,7 +483,9 @@
private float mBottomAreaShadeAlpha;
final ValueAnimator mBottomAreaShadeAlphaAnimator;
private final AnimatableProperty mPanelAlphaAnimator = AnimatableProperty.from("panelAlpha",
- NotificationPanelView::setPanelAlphaInternal,
+ (view, alpha) -> {
+ setAlphaInternal(alpha);
+ },
NotificationPanelView::getCurrentPanelAlpha,
R.id.panel_alpha_animator_tag, R.id.panel_alpha_animator_start_tag,
R.id.panel_alpha_animator_end_tag);
@@ -3074,6 +3076,11 @@
}
}
+ private void setAlphaInternal(float alpha) {
+ mKeyguardInteractor.setPanelAlpha(alpha / 255f);
+ mView.setPanelAlphaInternal(alpha);
+ }
+
@Override
public void setAlphaChangeAnimationEndAction(Runnable r) {
mPanelAlphaEndAction = r;
diff --git a/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceViewComponent.kt b/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceViewComponent.kt
index 6d951bf..abffd3c 100644
--- a/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceViewComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceViewComponent.kt
@@ -18,8 +18,10 @@
import android.app.ActivityOptions
import android.app.PendingIntent
import android.content.Intent
+import android.os.Handler
import android.view.View
import android.view.ViewGroup
+import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.BcSmartspaceDataPlugin
import com.android.systemui.plugins.BcSmartspaceDataPlugin.UI_SURFACE_DREAM
@@ -51,19 +53,20 @@
@Provides
fun providesSmartspaceView(
- activityStarter: ActivityStarter,
- falsingManager: FalsingManager,
- parent: ViewGroup,
- @Named(PLUGIN) plugin: BcSmartspaceDataPlugin,
- viewWithCustomLayout: View?,
- onAttachListener: View.OnAttachStateChangeListener
- ):
- BcSmartspaceDataPlugin.SmartspaceView {
+ activityStarter: ActivityStarter,
+ falsingManager: FalsingManager,
+ parent: ViewGroup,
+ @Named(PLUGIN) plugin: BcSmartspaceDataPlugin,
+ viewWithCustomLayout: View?,
+ onAttachListener: View.OnAttachStateChangeListener,
+ @Background bgHandler: Handler,
+ ): BcSmartspaceDataPlugin.SmartspaceView {
val ssView = viewWithCustomLayout
as? BcSmartspaceDataPlugin.SmartspaceView
?: plugin.getView(parent)
// Currently, this is only used to provide SmartspaceView on Dream surface.
ssView.setUiSurface(UI_SURFACE_DREAM)
+ ssView.setBgHandler(bgHandler)
ssView.registerDataProvider(plugin)
ssView.setIntentStarter(object : BcSmartspaceDataPlugin.IntentStarter {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
index ee2c9cc..af8a89d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
@@ -109,6 +109,7 @@
@Main private val uiExecutor: Executor,
@Background private val bgExecutor: Executor,
@Main private val handler: Handler,
+ @Background private val bgHandler: Handler,
@Named(DATE_SMARTSPACE_DATA_PLUGIN)
optionalDatePlugin: Optional<BcSmartspaceDataPlugin>,
@Named(WEATHER_SMARTSPACE_DATA_PLUGIN)
@@ -412,6 +413,7 @@
val ssView = plugin.getView(parent)
configPlugin?.let { ssView.registerConfigProvider(it) }
+ ssView.setBgHandler(bgHandler)
ssView.setUiSurface(BcSmartspaceDataPlugin.UI_SURFACE_LOCK_SCREEN_AOD)
ssView.setTimeChangedDelegate(SmartspaceTimeChangedDelegate(keyguardUpdateMonitor))
ssView.registerDataProvider(plugin)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflater.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflater.kt
index 6fc82c9..463192c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflater.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflater.kt
@@ -244,7 +244,7 @@
return SingleIcon(null)
}
val userKey = user.getKeyOrName()
- var conversationIcon: Icon? = null
+ var conversationIcon: Icon? = shortcutIcon
var conversationText: CharSequence? = conversationTitle
val groups = groupMessages(messages, historicMessages)
@@ -253,10 +253,6 @@
if (!isGroupConversation) {
// Conversation is one-to-one, load the single icon
// Let's resolve the icon / text from the last sender
- if (shortcutIcon != null) {
- conversationIcon = shortcutIcon
- }
-
for (i in messages.lastIndex downTo 0) {
val message = messages[i]
val sender = message.senderPerson
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 6a3055f..d984685 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -68,7 +68,6 @@
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.keyguard.MigrateClocksToBlueprint;
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository;
import com.android.systemui.keyguard.shared.model.KeyguardState;
import com.android.systemui.keyguard.shared.model.TransitionStep;
@@ -1320,7 +1319,10 @@
updateAlpha();
}
- void setMaxAlphaFromView(float alpha) {
+ /**
+ * Max alpha from the containing view. Used by brightness slider as an example.
+ */
+ public void setMaxAlphaFromView(float alpha) {
mMaxAlphaFromView = alpha;
updateAlpha();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
index cf5366b..497ffca 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
@@ -154,6 +154,14 @@
}
}
+ if (!SceneContainerFlag.isEnabled) {
+ launch {
+ // For when the entire view should fade, such as with the brightness
+ // slider
+ viewModel.panelAlpha.collect { controller.setMaxAlphaFromView(it) }
+ }
+ }
+
if (communalHub()) {
launch {
viewModel.glanceableHubAlpha.collect {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
index 77d8f50..8e58ffb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
@@ -21,6 +21,7 @@
import androidx.annotation.VisibleForTesting
import com.android.systemui.common.shared.model.NotificationContainerBounds
+import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dump.DumpManager
@@ -75,6 +76,7 @@
import com.android.systemui.util.kotlin.BooleanFlowOperators.anyOf
import com.android.systemui.util.kotlin.FlowDumperImpl
import com.android.systemui.util.kotlin.Utils.Companion.sample as sampleCombine
+import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -136,6 +138,7 @@
private val primaryBouncerToLockscreenTransitionViewModel:
PrimaryBouncerToLockscreenTransitionViewModel,
private val aodBurnInViewModel: AodBurnInViewModel,
+ private val communalSceneInteractor: CommunalSceneInteractor,
unfoldTransitionInteractor: UnfoldTransitionInteractor,
) : FlowDumperImpl(dumpManager) {
private val statesForConstrainedNotifications: Set<KeyguardState> =
@@ -470,6 +473,19 @@
}
.dumpWhileCollecting("isTransitioningToHiddenKeyguard")
+ val panelAlpha = keyguardInteractor.panelAlpha
+
+ private fun bouncerToGoneNotificationAlpha(viewState: ViewStateAccessor): Flow<Float> =
+ merge(
+ primaryBouncerToGoneTransitionViewModel.notificationAlpha,
+ alternateBouncerToGoneTransitionViewModel.notificationAlpha(viewState),
+ )
+ .sample(communalSceneInteractor.isCommunalVisible) { alpha, isCommunalVisible ->
+ // when glanceable hub is visible, hide notifications during the transition to GONE
+ if (isCommunalVisible) 0f else alpha
+ }
+ .dumpWhileCollecting("bouncerToGoneNotificationAlpha")
+
fun keyguardAlpha(viewState: ViewStateAccessor): Flow<Float> {
// All transition view models are mututally exclusive, and safe to merge
val alphaTransitions =
@@ -477,7 +493,7 @@
keyguardInteractor.dismissAlpha.dumpWhileCollecting(
"keyguardInteractor.dismissAlpha"
),
- alternateBouncerToGoneTransitionViewModel.notificationAlpha(viewState),
+ bouncerToGoneNotificationAlpha(viewState),
aodToGoneTransitionViewModel.notificationAlpha(viewState),
aodToLockscreenTransitionViewModel.notificationAlpha,
aodToOccludedTransitionViewModel.lockscreenAlpha(viewState),
@@ -495,7 +511,6 @@
occludedToAodTransitionViewModel.lockscreenAlpha,
occludedToGoneTransitionViewModel.notificationAlpha(viewState),
occludedToLockscreenTransitionViewModel.lockscreenAlpha,
- primaryBouncerToGoneTransitionViewModel.notificationAlpha,
primaryBouncerToLockscreenTransitionViewModel.lockscreenAlpha,
glanceableHubToLockscreenTransitionViewModel.keyguardAlpha,
lockscreenToGlanceableHubTransitionViewModel.keyguardAlpha,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt
index e400ab6..639560f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt
@@ -247,9 +247,10 @@
val actuallyShowOverLockscreen =
showOverLockscreen &&
intent.isActivity &&
- (skipLockscreenChecks || activityIntentHelper.wouldPendingShowOverLockscreen(
- intent,
- lockScreenUserManager.currentUserId
+ (skipLockscreenChecks ||
+ activityIntentHelper.wouldPendingShowOverLockscreen(
+ intent,
+ lockScreenUserManager.currentUserId
))
val animate =
@@ -470,12 +471,16 @@
shadeControllerLazy.get().collapseShadeForActivityStart()
}
if (communalHub()) {
- communalSceneInteractor.snapToSceneForActivityStart(CommunalScenes.Blank)
+ communalSceneInteractor.changeSceneForActivityStartOnDismissKeyguard()
}
return deferred
}
override fun willRunAnimationOnKeyguard(): Boolean {
+ if (communalHub() && communalSceneInteractor.isIdleOnCommunal.value) {
+ // Override to false when launching activity over the hub that requires auth
+ return false
+ }
return willAnimateOnKeyguard
}
}
@@ -557,7 +562,7 @@
override fun onTransitionAnimationStart(isExpandingFullyAbove: Boolean) {
super.onTransitionAnimationStart(isExpandingFullyAbove)
if (communalHub()) {
- communalSceneInteractor.snapToSceneForActivityStart(
+ communalSceneInteractor.snapToScene(
CommunalScenes.Blank,
ActivityTransitionAnimator.TIMINGS.totalDuration
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
index 066ca1c..b944d72 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
@@ -136,6 +136,9 @@
private lateinit var handler: Handler
@Mock
+ private lateinit var bgHandler: Handler
+
+ @Mock
private lateinit var datePlugin: BcSmartspaceDataPlugin
@Mock
@@ -265,6 +268,7 @@
executor,
bgExecutor,
handler,
+ bgHandler,
Optional.of(datePlugin),
Optional.of(weatherPlugin),
Optional.of(plugin),
@@ -762,6 +766,7 @@
// THEN the existing session is reused and views are registered
verify(smartspaceManager, never()).createSmartspaceSession(any())
verify(smartspaceView2).setUiSurface(BcSmartspaceDataPlugin.UI_SURFACE_LOCK_SCREEN_AOD)
+ verify(smartspaceView2).setBgHandler(bgHandler)
verify(smartspaceView2).setTimeChangedDelegate(any())
verify(smartspaceView2).registerDataProvider(plugin)
verify(smartspaceView2).registerConfigProvider(configPlugin)
@@ -838,6 +843,7 @@
verify(dateSmartspaceView).setUiSurface(
BcSmartspaceDataPlugin.UI_SURFACE_LOCK_SCREEN_AOD)
verify(dateSmartspaceView).setTimeChangedDelegate(any())
+ verify(dateSmartspaceView).setBgHandler(bgHandler)
verify(dateSmartspaceView).registerDataProvider(datePlugin)
verify(dateSmartspaceView).setPrimaryTextColor(anyInt())
@@ -851,6 +857,7 @@
verify(weatherSmartspaceView).setUiSurface(
BcSmartspaceDataPlugin.UI_SURFACE_LOCK_SCREEN_AOD)
verify(weatherSmartspaceView).setTimeChangedDelegate(any())
+ verify(weatherSmartspaceView).setBgHandler(bgHandler)
verify(weatherSmartspaceView).registerDataProvider(weatherPlugin)
verify(weatherSmartspaceView).setPrimaryTextColor(anyInt())
@@ -863,6 +870,7 @@
verify(smartspaceView).setUiSurface(BcSmartspaceDataPlugin.UI_SURFACE_LOCK_SCREEN_AOD)
verify(smartspaceView).setTimeChangedDelegate(any())
+ verify(smartspaceView).setBgHandler(bgHandler)
verify(smartspaceView).registerDataProvider(plugin)
verify(smartspaceView).registerConfigProvider(configPlugin)
verify(smartspaceSession)
@@ -988,6 +996,9 @@
override fun setUiSurface(uiSurface: String) {
}
+ override fun setBgHandler(bgHandler: Handler?) {
+ }
+
override fun setTimeChangedDelegate(
delegate: BcSmartspaceDataPlugin.TimeChangedDelegate?
) {}
@@ -1020,6 +1031,9 @@
override fun setUiSurface(uiSurface: String) {
}
+ override fun setBgHandler(bgHandler: Handler?) {
+ }
+
override fun setTimeChangedDelegate(
delegate: BcSmartspaceDataPlugin.TimeChangedDelegate?
) {}
@@ -1048,6 +1062,9 @@
override fun setUiSurface(uiSurface: String) {
}
+ override fun setBgHandler(bgHandler: Handler?) {
+ }
+
override fun setTimeChangedDelegate(
delegate: BcSmartspaceDataPlugin.TimeChangedDelegate?
) {}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
index 4654a4d..6efb7d8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
@@ -35,6 +35,7 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
@@ -783,8 +784,8 @@
mDialog.show(SHOW_REASON_UNKNOWN);
mTestableLooper.processAllMessages();
- verify(mVolumeDialogInteractor).onDialogShown();
- verify(mVolumeDialogInteractor).onDialogDismissed(); // dismiss by timeout
+ verify(mVolumeDialogInteractor, atLeastOnce()).onDialogShown();
+ verify(mVolumeDialogInteractor, atLeastOnce()).onDialogDismissed(); // dismiss by timeout
}
/**
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceconfig/data/repository/DeviceConfigRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceconfig/data/repository/DeviceConfigRepositoryKosmos.kt
new file mode 100644
index 0000000..e4efadbd
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceconfig/data/repository/DeviceConfigRepositoryKosmos.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2024 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.deviceconfig.data.repository
+
+import com.android.systemui.concurrency.fakeExecutor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.util.deviceConfigProxy
+
+val Kosmos.deviceConfigRepository by Fixture {
+ DeviceConfigRepository(
+ backgroundExecutor = fakeExecutor,
+ backgroundDispatcher = testDispatcher,
+ dataSource = deviceConfigProxy,
+ )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceconfig/domain/interactor/DeviceConfigInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceconfig/domain/interactor/DeviceConfigInteractorKosmos.kt
new file mode 100644
index 0000000..d538497
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceconfig/domain/interactor/DeviceConfigInteractorKosmos.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 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.deviceconfig.domain.interactor
+
+import com.android.systemui.deviceconfig.data.repository.deviceConfigRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+
+val Kosmos.deviceConfigInteractor by Fixture {
+ DeviceConfigInteractor(
+ repository = deviceConfigRepository,
+ )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
index 2d100f0..dca531a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
@@ -119,6 +119,8 @@
private val _keyguardAlpha = MutableStateFlow(1f)
override val keyguardAlpha: StateFlow<Float> = _keyguardAlpha
+ override val panelAlpha: MutableStateFlow<Float> = MutableStateFlow(1f)
+
override val lastRootViewTapPosition: MutableStateFlow<Point?> = MutableStateFlow(null)
override val ambientIndicationVisible: MutableStateFlow<Boolean> = MutableStateFlow(false)
@@ -259,6 +261,10 @@
_keyguardAlpha.value = alpha
}
+ override fun setPanelAlpha(alpha: Float) {
+ panelAlpha.value = alpha
+ }
+
fun setIsEncryptedOrLockdown(value: Boolean) {
_isEncryptedOrLockdown.value = value
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/navigation/data/repository/NavigationRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/navigation/data/repository/NavigationRepositoryKosmos.kt
new file mode 100644
index 0000000..717a300
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/navigation/data/repository/NavigationRepositoryKosmos.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 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.navigation.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.navigationbar.navigationModeController
+
+val Kosmos.navigationRepository by Fixture {
+ NavigationRepository(
+ controller = navigationModeController,
+ )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/navigation/domain/interactor/NavigationInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/navigation/domain/interactor/NavigationInteractorKosmos.kt
new file mode 100644
index 0000000..80fe3b9
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/navigation/domain/interactor/NavigationInteractorKosmos.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 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.navigation.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.navigation.data.repository.navigationRepository
+
+val Kosmos.navigationInteractor by Fixture {
+ NavigationInteractor(
+ repository = navigationRepository,
+ )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/navigationbar/NavigationModeControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/navigationbar/NavigationModeControllerKosmos.kt
new file mode 100644
index 0000000..f2fb1a8
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/navigationbar/NavigationModeControllerKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2024 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.navigationbar
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.util.mockito.mock
+
+val Kosmos.navigationModeController by Fixture { mock<NavigationModeController>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
index 299486f..ffd8aab 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.notification.stack.ui.viewmodel
+import com.android.systemui.communal.domain.interactor.communalSceneInteractor
import com.android.systemui.dump.dumpManager
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
@@ -86,6 +87,7 @@
primaryBouncerToLockscreenTransitionViewModel =
primaryBouncerToLockscreenTransitionViewModel,
aodBurnInViewModel = aodBurnInViewModel,
+ communalSceneInteractor = communalSceneInteractor,
unfoldTransitionInteractor = unfoldTransitionInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/DeviceConfigProxyKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/DeviceConfigProxyKosmos.kt
new file mode 100644
index 0000000..cf902ef
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/DeviceConfigProxyKosmos.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2024 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.util
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+
+val Kosmos.fakeDeviceConfigProxy by Fixture { DeviceConfigProxyFake() }
+
+val Kosmos.deviceConfigProxy by Fixture<DeviceConfigProxy> { fakeDeviceConfigProxy }
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index abc6bf6..b89120b 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -17,6 +17,7 @@
package com.android.server.wallpaper;
import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
+import static android.Manifest.permission.MANAGE_EXTERNAL_STORAGE;
import static android.Manifest.permission.READ_WALLPAPER_INTERNAL;
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
import static android.app.WallpaperManager.COMMAND_REAPPLY;
@@ -100,7 +101,6 @@
import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserManager;
-import android.os.storage.StorageManager;
import android.service.wallpaper.IWallpaperConnection;
import android.service.wallpaper.IWallpaperEngine;
import android.service.wallpaper.IWallpaperService;
@@ -2207,10 +2207,7 @@
IWallpaperManagerCallback cb, final int which, Bundle outParams, int wallpaperUserId,
boolean getCropped) {
final boolean hasPrivilege = hasPermission(READ_WALLPAPER_INTERNAL);
- if (!hasPrivilege) {
- mContext.getSystemService(StorageManager.class).checkPermissionReadImages(true,
- Binder.getCallingPid(), Binder.getCallingUid(), callingPkg, callingFeatureId);
- }
+ if (!hasPrivilege) checkPermission(MANAGE_EXTERNAL_STORAGE);
wallpaperUserId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
Binder.getCallingUid(), wallpaperUserId, false, true, "getWallpaper", null);
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 63ca469..bc45c70 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -2816,6 +2816,9 @@
final Rect parentBounds = parent.getBounds();
change.setEndRelOffset(bounds.left - parentBounds.left,
bounds.top - parentBounds.top);
+ if (Flags.activityEmbeddingOverlayPresentationFlag()) {
+ change.setEndParentSize(parentBounds.width(), parentBounds.height());
+ }
int endRotation = target.getWindowConfiguration().getRotation();
if (activityRecord != null) {
// TODO(b/227427984): Shell needs to aware letterbox.
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 43a2a93..e122fe0 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -4250,6 +4250,10 @@
private void clearOrgOwnedProfileOwnerUserRestrictions(UserHandle parentUserHandle) {
mUserManager.setUserRestriction(
UserManager.DISALLOW_REMOVE_MANAGED_PROFILE, false, parentUserHandle);
+ if (mInjector.userManagerIsHeadlessSystemUserMode()) {
+ mUserManager.setUserRestriction(UserManager.DISALLOW_REMOVE_MANAGED_PROFILE,
+ false, UserHandle.SYSTEM);
+ }
mUserManager.setUserRestriction(
UserManager.DISALLOW_ADD_USER, false, parentUserHandle);
}
@@ -18009,6 +18013,12 @@
mUserManager.setUserRestriction(UserManager.DISALLOW_REMOVE_MANAGED_PROFILE,
isProfileOwnerOnOrganizationOwnedDevice,
parentUser);
+ if (mInjector.userManagerIsHeadlessSystemUserMode()) {
+ // For HSUM, additionally set this on user 0 to block ADB from removing the profile.
+ mUserManager.setUserRestriction(UserManager.DISALLOW_REMOVE_MANAGED_PROFILE,
+ isProfileOwnerOnOrganizationOwnedDevice,
+ UserHandle.SYSTEM);
+ }
mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_USER,
isProfileOwnerOnOrganizationOwnedDevice,
parentUser);
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 107c294..611a4eb 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -1924,7 +1924,11 @@
startRotationResolverService(context, t);
startSystemCaptionsManagerService(context, t);
startTextToSpeechManagerService(context, t);
- startWearableSensingService(t);
+ if (!isWatch || !android.server.Flags.removeWearableSensingServiceFromWear()) {
+ startWearableSensingService(t);
+ } else {
+ Slog.d(TAG, "Not starting WearableSensingService");
+ }
startOnDeviceIntelligenceService(t);
if (deviceHasConfigString(
diff --git a/services/java/com/android/server/flags.aconfig b/services/java/com/android/server/flags.aconfig
index 38354e8..e8aa68c 100644
--- a/services/java/com/android/server/flags.aconfig
+++ b/services/java/com/android/server/flags.aconfig
@@ -14,4 +14,11 @@
namespace: "wear_frameworks"
description: "Remove TextServiceManagerService on Wear"
bug: "323720705"
+}
+
+flag {
+ name: "remove_wearable_sensing_service_from_wear"
+ namespace: "wear_frameworks"
+ description: "Remove WearableSensingManagerService on Wear"
+ bug: "340929916"
}
\ No newline at end of file
diff --git a/services/tests/dreamservicetests/AndroidManifest.xml b/services/tests/dreamservicetests/AndroidManifest.xml
index 6092ef6..449521b 100644
--- a/services/tests/dreamservicetests/AndroidManifest.xml
+++ b/services/tests/dreamservicetests/AndroidManifest.xml
@@ -53,6 +53,35 @@
android:name="android.service.dream"
android:resource="@xml/test_dream_metadata_invalid" />
</service>
+
+ <service
+ android:name="com.android.server.dreams.TestDreamServiceWithNonexistentSettings"
+ android:exported="false"
+ android:label="Test Dream" >
+ <intent-filter>
+ <action android:name="android.service.dreams.DreamService" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ <meta-data
+ android:name="android.service.dream"
+ android:resource="@xml/test_dream_metadata_nonexistent_settings" />
+ </service>
+
+ <service
+ android:name="com.android.server.dreams.TestDreamServiceNoPackageNonexistentSettings"
+ android:exported="false"
+ android:label="Test Dream" >
+ <intent-filter>
+ <action android:name="android.service.dreams.DreamService" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ <meta-data
+ android:name="android.service.dream"
+ android:resource="@xml/test_dream_metadata_nopackage_nonexistent_settings" />
+ </service>
+
+ <activity android:name=".TestDreamSettingsActivity">
+ </activity>
</application>
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
diff --git a/services/tests/dreamservicetests/res/xml/test_dream_metadata_nonexistent_settings.xml b/services/tests/dreamservicetests/res/xml/test_dream_metadata_nonexistent_settings.xml
new file mode 100644
index 0000000..a19ef0c
--- /dev/null
+++ b/services/tests/dreamservicetests/res/xml/test_dream_metadata_nonexistent_settings.xml
@@ -0,0 +1,20 @@
+<!--
+ ~ Copyright (C) 2024 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.
+ -->
+
+<!-- The settings activity does not exist, which is invalid. -->
+<dream xmlns:android="http://schemas.android.com/apk/res/android"
+ android:settingsActivity="com.android.frameworks.dreamservicetests/.TestDreamSettingsActivity2"
+ android:showClockAndComplications="false"/>
diff --git a/services/tests/dreamservicetests/res/xml/test_dream_metadata_nopackage_nonexistent_settings.xml b/services/tests/dreamservicetests/res/xml/test_dream_metadata_nopackage_nonexistent_settings.xml
new file mode 100644
index 0000000..0453e92
--- /dev/null
+++ b/services/tests/dreamservicetests/res/xml/test_dream_metadata_nopackage_nonexistent_settings.xml
@@ -0,0 +1,20 @@
+<!--
+ ~ Copyright (C) 2024 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.
+ -->
+
+<!-- The settings activity does not exist, which is invalid. -->
+<dream xmlns:android="http://schemas.android.com/apk/res/android"
+ android:settingsActivity="com.android.frameworks.dreamservicetests.TestDreamSettingsActivity2"
+ android:showClockAndComplications="false"/>
diff --git a/services/tests/dreamservicetests/src/com/android/server/dreams/DreamServiceTest.java b/services/tests/dreamservicetests/src/com/android/server/dreams/DreamServiceTest.java
index b98af6b..b4e1abf 100644
--- a/services/tests/dreamservicetests/src/com/android/server/dreams/DreamServiceTest.java
+++ b/services/tests/dreamservicetests/src/com/android/server/dreams/DreamServiceTest.java
@@ -91,6 +91,28 @@
}
@Test
+ public void testMetadataParsing_nonexistentSettingsActivity()
+ throws PackageManager.NameNotFoundException {
+ final String testDreamClassName =
+ "com.android.server.dreams.TestDreamServiceWithNonexistentSettings";
+ final DreamService.DreamMetadata metadata = getDreamMetadata(testDreamClassName);
+
+ assertThat(metadata.settingsActivity).isNull();
+ assertThat(metadata.dreamCategory).isEqualTo(DreamService.DREAM_CATEGORY_DEFAULT);
+ }
+
+ @Test
+ public void testMetadataParsing_noPackage_nonexistentSettingsActivity()
+ throws PackageManager.NameNotFoundException {
+ final String testDreamClassName =
+ "com.android.server.dreams.TestDreamServiceNoPackageNonexistentSettings";
+ final DreamService.DreamMetadata metadata = getDreamMetadata(testDreamClassName);
+
+ assertThat(metadata.settingsActivity).isNull();
+ assertThat(metadata.dreamCategory).isEqualTo(DreamService.DREAM_CATEGORY_DEFAULT);
+ }
+
+ @Test
public void testMetadataParsing_exceptionReading() {
final PackageManager packageManager = Mockito.mock(PackageManager.class);
final ServiceInfo serviceInfo = Mockito.mock(ServiceInfo.class);
diff --git a/services/tests/dreamservicetests/src/com/android/server/dreams/TestDreamServiceWithNonexistentSettings.java b/services/tests/dreamservicetests/src/com/android/server/dreams/TestDreamServiceWithNonexistentSettings.java
new file mode 100644
index 0000000..de00175
--- /dev/null
+++ b/services/tests/dreamservicetests/src/com/android/server/dreams/TestDreamServiceWithNonexistentSettings.java
@@ -0,0 +1,25 @@
+/*
+ * 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.dreams;
+
+import android.service.dreams.DreamService;
+
+/**
+ * Dream service implementation for unit testing, where the settings activity doesn't exist.
+ */
+public class TestDreamServiceWithNonexistentSettings extends DreamService {
+}
diff --git a/services/tests/dreamservicetests/src/com/android/server/dreams/TestDreamSettingsActivity.java b/services/tests/dreamservicetests/src/com/android/server/dreams/TestDreamSettingsActivity.java
new file mode 100644
index 0000000..bb8db8a
--- /dev/null
+++ b/services/tests/dreamservicetests/src/com/android/server/dreams/TestDreamSettingsActivity.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2024 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.dreams;
+
+public class TestDreamSettingsActivity {
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
index a39a1a8..c67d1ec 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
@@ -550,7 +550,7 @@
}).when(appWindow.mSession).setOnBackInvokedCallbackInfo(eq(appWindow.mClient), any());
addToWindowMap(appWindow, true);
- dispatcher.attachToWindow(appWindow.mSession, appWindow.mClient, null);
+ dispatcher.attachToWindow(appWindow.mSession, appWindow.mClient, null, null);
OnBackInvokedCallback appCallback = createBackCallback(appLatch);
diff --git a/packages/SystemUI/tests/utils/src/android/animation/AnimatorTestRule.java b/tests/testables/src/android/animation/AnimatorTestRule.java
similarity index 100%
rename from packages/SystemUI/tests/utils/src/android/animation/AnimatorTestRule.java
rename to tests/testables/src/android/animation/AnimatorTestRule.java
diff --git a/tests/testables/tests/Android.bp b/tests/testables/tests/Android.bp
index 06449e0..d6a4754 100644
--- a/tests/testables/tests/Android.bp
+++ b/tests/testables/tests/Android.bp
@@ -26,14 +26,18 @@
platform_apis: true,
srcs: [
"src/**/*.java",
+ "src/**/*.kt",
"src/**/I*.aidl",
],
resource_dirs: ["res"],
static_libs: [
+ "androidx.core_core-animation",
+ "androidx.core_core-ktx",
"androidx.test.rules",
"hamcrest-library",
"mockito-target-inline-minus-junit4",
"testables",
+ "truth",
],
compile_multilib: "both",
jni_libs: [
diff --git a/packages/SystemUI/tests/src/android/animation/AnimatorTestRuleIsolationTest.kt b/tests/testables/tests/src/android/animation/AnimatorTestRuleIsolationTest.kt
similarity index 94%
rename from packages/SystemUI/tests/src/android/animation/AnimatorTestRuleIsolationTest.kt
rename to tests/testables/tests/src/android/animation/AnimatorTestRuleIsolationTest.kt
index f23fbee..5abebee 100644
--- a/packages/SystemUI/tests/src/android/animation/AnimatorTestRuleIsolationTest.kt
+++ b/tests/testables/tests/src/android/animation/AnimatorTestRuleIsolationTest.kt
@@ -19,7 +19,6 @@
import android.testing.TestableLooper.RunWithLooper
import androidx.core.animation.doOnEnd
import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
import com.google.common.truth.Truth.assertThat
import org.junit.Rule
import org.junit.Test
@@ -32,7 +31,7 @@
@RunWith(AndroidTestingRunner::class)
@SmallTest
@RunWithLooper
-class AnimatorTestRuleIsolationTest : SysuiTestCase() {
+class AnimatorTestRuleIsolationTest {
@get:Rule val animatorTestRule = AnimatorTestRule(this)
@@ -41,7 +40,7 @@
// GIVEN global state is reset at the start of the test
didTouchA = false
didTouchB = false
- // WHEN starting 2 animations of different durations, and setting didTouchA at the end
+ // WHEN starting 2 animations of different durations, and setting didTouch{A,B} at the end
ObjectAnimator.ofFloat(0f, 1f).apply {
duration = 100
doOnEnd { didTouchA = true }
@@ -49,7 +48,7 @@
}
ObjectAnimator.ofFloat(0f, 1f).apply {
duration = 150
- doOnEnd { didTouchA = true }
+ doOnEnd { didTouchB = true }
start()
}
// WHEN when you advance time so that only one of the animations has ended
@@ -65,7 +64,7 @@
// GIVEN global state is reset at the start of the test
didTouchA = false
didTouchB = false
- // WHEN starting 2 animations of different durations, and setting didTouchB at the end
+ // WHEN starting 2 animations of different durations, and setting didTouch{A,B} at the end
ObjectAnimator.ofFloat(0f, 1f).apply {
duration = 100
doOnEnd { didTouchB = true }
@@ -73,7 +72,7 @@
}
ObjectAnimator.ofFloat(0f, 1f).apply {
duration = 150
- doOnEnd { didTouchB = true }
+ doOnEnd { didTouchA = true }
start()
}
animatorTestRule.advanceTimeBy(100)
diff --git a/packages/SystemUI/tests/src/android/animation/AnimatorTestRulePrecisionTest.kt b/tests/testables/tests/src/android/animation/AnimatorTestRulePrecisionTest.kt
similarity index 96%
rename from packages/SystemUI/tests/src/android/animation/AnimatorTestRulePrecisionTest.kt
rename to tests/testables/tests/src/android/animation/AnimatorTestRulePrecisionTest.kt
index fd5f157..9eeaad5 100644
--- a/packages/SystemUI/tests/src/android/animation/AnimatorTestRulePrecisionTest.kt
+++ b/tests/testables/tests/src/android/animation/AnimatorTestRulePrecisionTest.kt
@@ -17,10 +17,9 @@
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
+import android.view.animation.LinearInterpolator
import androidx.core.animation.doOnEnd
import androidx.test.filters.SmallTest
-import com.android.app.animation.Interpolators
-import com.android.systemui.SysuiTestCase
import com.google.common.truth.Truth.assertThat
import org.junit.Rule
import org.junit.Test
@@ -29,7 +28,7 @@
@RunWith(AndroidTestingRunner::class)
@SmallTest
@RunWithLooper
-class AnimatorTestRulePrecisionTest : SysuiTestCase() {
+class AnimatorTestRulePrecisionTest {
@get:Rule val animatorTestRule = AnimatorTestRule(this)
@@ -43,7 +42,7 @@
crossinline onEndAction: (animator: Animator) -> Unit,
) {
ObjectAnimator.ofFloat(this, propertyName, 0f, 1f).also {
- it.interpolator = Interpolators.LINEAR
+ it.interpolator = LINEAR_INTERPOLATOR
it.duration = duration
it.startDelay = startDelay
it.doOnEnd(onEndAction)
@@ -190,4 +189,8 @@
assertThat(ended2).isTrue()
assertThat(AnimationHandler.getAnimationCount()).isEqualTo(0)
}
+
+ private companion object {
+ private val LINEAR_INTERPOLATOR = LinearInterpolator()
+ }
}