Merge "Shift Key gestures handled by InputManager to IMS from PWM" into main
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 4350545..5db79fe 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -8288,12 +8288,12 @@
}
Context c = null;
ApplicationInfo ai = info.applicationInfo;
- if (context.getPackageName().equals(ai.packageName)) {
+ if (context != null && context.getPackageName().equals(ai.packageName)) {
c = context;
} else if (mInitialApplication != null &&
mInitialApplication.getPackageName().equals(ai.packageName)) {
c = mInitialApplication;
- } else {
+ } else if (context != null) {
try {
c = context.createPackageContext(ai.packageName,
Context.CONTEXT_INCLUDE_CODE);
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index 52c84dc..26f919f 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -778,8 +778,18 @@
public List<LauncherActivityInfo> getActivityList(String packageName, UserHandle user) {
logErrorForInvalidProfileAccess(user);
try {
- return convertToActivityList(mService.getLauncherActivities(mContext.getPackageName(),
- packageName, user), user);
+ final List<LauncherActivityInfo> activityList = convertToActivityList(
+ mService.getLauncherActivities(
+ mContext.getPackageName(),
+ packageName,
+ user
+ ), user);
+ if (activityList.isEmpty()) {
+ // b/350144057
+ Log.d(TAG, "getActivityList: No launchable activities found for"
+ + "packageName=" + packageName + ", user=" + user);
+ }
+ return activityList;
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index a4a7a98..1ca4574 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -3763,7 +3763,8 @@
}
private static final String CACHE_KEY_IS_USER_UNLOCKED_PROPERTY =
- "cache_key.is_user_unlocked";
+ PropertyInvalidatedCache.createPropertyName(
+ PropertyInvalidatedCache.MODULE_SYSTEM, "is_user_unlocked");
private final PropertyInvalidatedCache<Integer, Boolean> mIsUserUnlockedCache =
new PropertyInvalidatedCache<Integer, Boolean>(
@@ -6694,7 +6695,9 @@
}
/* Cache key for anything that assumes that userIds cannot be re-used without rebooting. */
- private static final String CACHE_KEY_STATIC_USER_PROPERTIES = "cache_key.static_user_props";
+ private static final String CACHE_KEY_STATIC_USER_PROPERTIES =
+ PropertyInvalidatedCache.createPropertyName(
+ PropertyInvalidatedCache.MODULE_SYSTEM, "static_user_props");
private final PropertyInvalidatedCache<Integer, String> mProfileTypeCache =
new PropertyInvalidatedCache<Integer, String>(32, CACHE_KEY_STATIC_USER_PROPERTIES) {
@@ -6721,7 +6724,9 @@
}
/* Cache key for UserProperties object. */
- private static final String CACHE_KEY_USER_PROPERTIES = "cache_key.user_properties";
+ private static final String CACHE_KEY_USER_PROPERTIES =
+ PropertyInvalidatedCache.createPropertyName(
+ PropertyInvalidatedCache.MODULE_SYSTEM, "user_properties");
// TODO: It would be better to somehow have this as static, so that it can work cross-context.
private final PropertyInvalidatedCache<Integer, UserProperties> mUserPropertiesCache =
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index ad457ce..384add5 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -70,6 +70,7 @@
import android.hardware.HardwareBuffer;
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManager.DisplayListener;
+import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
@@ -370,6 +371,7 @@
private float mDefaultDimAmount = 0.05f;
SurfaceControl mBbqSurfaceControl;
BLASTBufferQueue mBlastBufferQueue;
+ IBinder mBbqApplyToken = new Binder();
private SurfaceControl mScreenshotSurfaceControl;
private Point mScreenshotSize = new Point();
@@ -2390,6 +2392,7 @@
if (mBlastBufferQueue == null) {
mBlastBufferQueue = new BLASTBufferQueue("Wallpaper", mBbqSurfaceControl,
width, height, format);
+ mBlastBufferQueue.setApplyToken(mBbqApplyToken);
// We only return the Surface the first time, as otherwise
// it hasn't changed and there is no need to update.
ret = mBlastBufferQueue.createSurface();
diff --git a/core/java/android/text/flags/flags.aconfig b/core/java/android/text/flags/flags.aconfig
index 3c61f4f..3846972 100644
--- a/core/java/android/text/flags/flags.aconfig
+++ b/core/java/android/text/flags/flags.aconfig
@@ -84,17 +84,6 @@
}
flag {
- name: "fix_font_update_failure"
- namespace: "text"
- description: "There was a bug of updating system font from Android 13 to 14. This flag for fixing the migration failure."
- is_fixed_read_only: true
- bug: "331717791"
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
-
-flag {
name: "fix_misaligned_context_menu"
namespace: "text"
description: "Fix the context menu misalignment and incosistent icon size."
@@ -154,26 +143,6 @@
}
flag {
- name: "portuguese_hyphenator"
- namespace: "text"
- description: "Portuguese taiored hyphenator"
- bug: "344656282"
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
-
-flag {
- name: "dont_break_email_in_nobreak_tag"
- namespace: "text"
- description: "Prevent line break inside email."
- bug: "350691716"
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
-
-flag {
name: "handwriting_gesture_with_transformation"
namespace: "text"
description: "Fix handwriting gesture is not working when view has transformation."
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index 71ceaf7..53935e8 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -1341,7 +1341,7 @@
public HdrCapabilities getHdrCapabilities() {
synchronized (mLock) {
updateDisplayInfoLocked();
- if (mDisplayInfo.hdrCapabilities == null) {
+ if (mDisplayInfo.hdrCapabilities == null || mDisplayInfo.isForceSdr) {
return null;
}
int[] supportedHdrTypes;
@@ -1363,6 +1363,7 @@
supportedHdrTypes[index++] = enabledType;
}
}
+
return new HdrCapabilities(supportedHdrTypes,
mDisplayInfo.hdrCapabilities.mMaxLuminance,
mDisplayInfo.hdrCapabilities.mMaxAverageLuminance,
diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java
index 157cec8..cac3e3c 100644
--- a/core/java/android/view/DisplayInfo.java
+++ b/core/java/android/view/DisplayInfo.java
@@ -230,6 +230,9 @@
/** The formats disabled by user **/
public int[] userDisabledHdrTypes = {};
+ /** When true, all HDR capabilities are disabled **/
+ public boolean isForceSdr;
+
/**
* Indicates whether the display can be switched into a mode with minimal post
* processing.
@@ -440,6 +443,7 @@
&& colorMode == other.colorMode
&& Arrays.equals(supportedColorModes, other.supportedColorModes)
&& Objects.equals(hdrCapabilities, other.hdrCapabilities)
+ && isForceSdr == other.isForceSdr
&& Arrays.equals(userDisabledHdrTypes, other.userDisabledHdrTypes)
&& minimalPostProcessingSupported == other.minimalPostProcessingSupported
&& logicalDensityDpi == other.logicalDensityDpi
@@ -502,6 +506,7 @@
supportedColorModes = Arrays.copyOf(
other.supportedColorModes, other.supportedColorModes.length);
hdrCapabilities = other.hdrCapabilities;
+ isForceSdr = other.isForceSdr;
userDisabledHdrTypes = other.userDisabledHdrTypes;
minimalPostProcessingSupported = other.minimalPostProcessingSupported;
logicalDensityDpi = other.logicalDensityDpi;
@@ -567,6 +572,7 @@
supportedColorModes[i] = source.readInt();
}
hdrCapabilities = source.readParcelable(null, android.view.Display.HdrCapabilities.class);
+ isForceSdr = source.readBoolean();
minimalPostProcessingSupported = source.readBoolean();
logicalDensityDpi = source.readInt();
physicalXDpi = source.readFloat();
@@ -636,6 +642,7 @@
dest.writeInt(supportedColorModes[i]);
}
dest.writeParcelable(hdrCapabilities, flags);
+ dest.writeBoolean(isForceSdr);
dest.writeBoolean(minimalPostProcessingSupported);
dest.writeInt(logicalDensityDpi);
dest.writeFloat(physicalXDpi);
@@ -874,6 +881,8 @@
sb.append(Arrays.toString(appsSupportedModes));
sb.append(", hdrCapabilities ");
sb.append(hdrCapabilities);
+ sb.append(", isForceSdr ");
+ sb.append(isForceSdr);
sb.append(", userDisabledHdrTypes ");
sb.append(Arrays.toString(userDisabledHdrTypes));
sb.append(", minimalPostProcessingSupported ");
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index e10cc28..9ff5031 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -829,6 +829,7 @@
private final SurfaceControl mSurfaceControl = new SurfaceControl();
private BLASTBufferQueue mBlastBufferQueue;
+ private IBinder mBbqApplyToken = new Binder();
private final HdrRenderState mHdrRenderState = new HdrRenderState(this);
@@ -2743,6 +2744,10 @@
mBlastBufferQueue = new BLASTBufferQueue(mTag, mSurfaceControl,
mSurfaceSize.x, mSurfaceSize.y, mWindowAttributes.format);
mBlastBufferQueue.setTransactionHangCallback(sTransactionHangCallback);
+ // If we create and destroy BBQ without recreating the SurfaceControl, we can end up
+ // queuing buffers on multiple apply tokens causing out of order buffer submissions. We
+ // fix this by setting the same apply token on all BBQs created by this VRI.
+ mBlastBufferQueue.setApplyToken(mBbqApplyToken);
Surface blastSurface;
if (addSchandleToVriSurface()) {
blastSurface = mBlastBufferQueue.createSurfaceWithHandle();
diff --git a/core/java/android/view/inputmethod/ImeTracker.java b/core/java/android/view/inputmethod/ImeTracker.java
index b9751c8..d90455a 100644
--- a/core/java/android/view/inputmethod/ImeTracker.java
+++ b/core/java/android/view/inputmethod/ImeTracker.java
@@ -920,7 +920,8 @@
final Configuration.Builder builder = Configuration.Builder.withSurface(
cujType,
jankContext.getDisplayContext(),
- jankContext.getTargetSurfaceControl())
+ jankContext.getTargetSurfaceControl(),
+ jankContext.getDisplayContext().getMainThreadHandler())
.setTag(String.format(Locale.US, "%d@%d@%s", animType,
useSeparatedThread ? 0 : 1, jankContext.getHostPackageName()));
InteractionJankMonitor.getInstance().begin(builder);
diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java
index c7e1fba..ef08e49 100644
--- a/core/java/com/android/internal/jank/InteractionJankMonitor.java
+++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java
@@ -330,9 +330,10 @@
* @param cujType the specific {@link Cuj.CujType}.
* @return boolean true if the tracker is started successfully, false otherwise.
*/
- public boolean begin(SurfaceControl surface, Context context, @Cuj.CujType int cujType) {
+ public boolean begin(SurfaceControl surface, Context context, Handler handler,
+ @Cuj.CujType int cujType) {
try {
- return begin(Configuration.Builder.withSurface(cujType, context, surface));
+ return begin(Configuration.Builder.withSurface(cujType, context, surface, handler));
} catch (IllegalArgumentException ex) {
Log.d(TAG, "Build configuration failed!", ex);
return false;
@@ -348,11 +349,12 @@
* @param tag a tag containing extra information about the interaction.
* @return boolean true if the tracker is started successfully, false otherwise.
*/
- public boolean begin(SurfaceControl surface, Context context, @Cuj.CujType int cujType,
+ public boolean begin(SurfaceControl surface, Context context, Handler handler,
+ @Cuj.CujType int cujType,
String tag) {
try {
final Configuration.Builder builder =
- Configuration.Builder.withSurface(cujType, context, surface);
+ Configuration.Builder.withSurface(cujType, context, surface, handler);
if (!TextUtils.isEmpty(tag)) {
builder.setTag(tag);
}
@@ -689,20 +691,23 @@
private SurfaceControl mAttrSurfaceControl;
private final @Cuj.CujType int mAttrCujType;
private boolean mAttrDeferMonitor = true;
+ private Handler mHandler = null;
/**
* Creates a builder which instruments only surface.
* @param cuj The enum defined in {@link Cuj.CujType}.
* @param context context
* @param surfaceControl surface control
+ * @param uiThreadHandler UI thread for that surface
* @return builder
*/
public static Builder withSurface(@Cuj.CujType int cuj, @NonNull Context context,
- @NonNull SurfaceControl surfaceControl) {
+ @NonNull SurfaceControl surfaceControl, @NonNull Handler uiThreadHandler) {
return new Builder(cuj)
.setContext(context)
.setSurfaceControl(surfaceControl)
- .setSurfaceOnly(true);
+ .setSurfaceOnly(true)
+ .setHandler(uiThreadHandler);
}
/**
@@ -722,6 +727,18 @@
}
/**
+ * Specifies the UI thread handler. If not provided, the View's one will be used.
+ * If only a surface is provided without handler, the app main thread will be used.
+ *
+ * @param uiThreadHandler handler associated to the cuj UI thread
+ * @return builder
+ */
+ public Builder setHandler(Handler uiThreadHandler) {
+ mHandler = uiThreadHandler;
+ return this;
+ }
+
+ /**
* Specifies a view, must be set if {@link #setSurfaceOnly(boolean)} is set to false.
* @param view an attached view
* @return builder
@@ -798,13 +815,13 @@
return new Configuration(
mAttrCujType, mAttrView, mAttrTag, mAttrTimeout,
mAttrSurfaceOnly, mAttrContext, mAttrSurfaceControl,
- mAttrDeferMonitor);
+ mAttrDeferMonitor, mHandler);
}
}
private Configuration(@Cuj.CujType int cuj, View view, @NonNull String tag, long timeout,
boolean surfaceOnly, Context context, SurfaceControl surfaceControl,
- boolean deferMonitor) {
+ boolean deferMonitor, Handler handler) {
mCujType = cuj;
mTag = tag;
mSessionName = generateSessionName(Cuj.getNameOfCuj(cuj), tag);
@@ -816,8 +833,16 @@
: (view != null ? view.getContext().getApplicationContext() : null);
mSurfaceControl = surfaceControl;
mDeferMonitor = deferMonitor;
+ if (handler != null) {
+ mHandler = handler;
+ } else if (mSurfaceOnly) {
+ Log.w(TAG, "No UIThread provided for " + mSessionName
+ + " (surface only). Defaulting to app main thread.");
+ mHandler = mContext.getMainThreadHandler();
+ } else {
+ mHandler = mView.getHandler();
+ }
validate();
- mHandler = mSurfaceOnly ? mContext.getMainThreadHandler() : mView.getHandler();
}
@VisibleForTesting
@@ -858,6 +883,12 @@
shouldThrow = true;
msg.append("Must pass in a valid surface control if only instrument surface; ");
}
+ if (mHandler == null) {
+ shouldThrow = true;
+ msg.append(
+ "Must pass a UI thread handler when only a surface control is "
+ + "provided.");
+ }
} else {
if (!hasValidView()) {
shouldThrow = true;
diff --git a/core/jni/android_graphics_BLASTBufferQueue.cpp b/core/jni/android_graphics_BLASTBufferQueue.cpp
index 70505a4..b9c3bf7 100644
--- a/core/jni/android_graphics_BLASTBufferQueue.cpp
+++ b/core/jni/android_graphics_BLASTBufferQueue.cpp
@@ -16,16 +16,16 @@
#define LOG_TAG "BLASTBufferQueue"
-#include <nativehelper/JNIHelp.h>
-
#include <android_runtime/AndroidRuntime.h>
#include <android_runtime/android_view_Surface.h>
-#include <utils/Log.h>
-#include <utils/RefBase.h>
-
+#include <android_util_Binder.h>
#include <gui/BLASTBufferQueue.h>
#include <gui/Surface.h>
#include <gui/SurfaceComposerClient.h>
+#include <nativehelper/JNIHelp.h>
+#include <utils/Log.h>
+#include <utils/RefBase.h>
+
#include "core_jni_helpers.h"
namespace android {
@@ -209,6 +209,12 @@
reinterpret_cast<jlong>(transaction));
}
+static void nativeSetApplyToken(JNIEnv* env, jclass clazz, jlong ptr, jobject applyTokenObject) {
+ sp<BLASTBufferQueue> queue = reinterpret_cast<BLASTBufferQueue*>(ptr);
+ sp<IBinder> token(ibinderForJavaObject(env, applyTokenObject));
+ return queue->setApplyToken(std::move(token));
+}
+
static const JNINativeMethod gMethods[] = {
/* name, signature, funcPtr */
// clang-format off
@@ -227,6 +233,7 @@
{"nativeSetTransactionHangCallback",
"(JLandroid/graphics/BLASTBufferQueue$TransactionHangCallback;)V",
(void*)nativeSetTransactionHangCallback},
+ {"nativeSetApplyToken", "(JLandroid/os/IBinder;)V", (void*)nativeSetApplyToken},
// clang-format on
};
diff --git a/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java b/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java
index 5af272c..e0823b8 100644
--- a/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java
+++ b/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java
@@ -24,6 +24,7 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
@@ -117,6 +118,7 @@
// Simulate a trace session and see if begin / end are invoked.
assertThat(monitor.begin(mSurfaceControl, mActivity.getApplicationContext(),
+ mActivity.getMainThreadHandler(),
Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isTrue();
verify(tracker).begin();
assertThat(monitor.end(Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isTrue();
@@ -188,6 +190,25 @@
assertThat(generateSessionName(cujName, tooLongTag)).isEqualTo(expectedTrimmedName);
}
+ @Test
+ public void validateConfiguration_surfaceOnlyAndNotDeferMonitor_throwsError() {
+ Configuration.Builder builder = Configuration.Builder.withSurface(1,
+ mActivity.getApplicationContext(), mSurfaceControl,
+ mActivity.getMainThreadHandler()).setDeferMonitorForAnimationStart(false);
+
+ assertThrows(IllegalStateException.class, builder::build);
+ }
+
+ @Test
+ public void validateConfiguration_surfaceOnlyAndDeferMonitor_doesNotThrowError() {
+ Configuration.Builder builder = Configuration.Builder.withSurface(1,
+ mActivity.getApplicationContext(),
+ mSurfaceControl, mActivity.getMainThreadHandler()).setDeferMonitorForAnimationStart(
+ true);
+
+ builder.build(); // no exception.
+ }
+
private InteractionJankMonitor createMockedInteractionJankMonitor() {
InteractionJankMonitor monitor = spy(new InteractionJankMonitor(mWorker));
doReturn(true).when(monitor).shouldMonitor();
diff --git a/graphics/java/android/graphics/BLASTBufferQueue.java b/graphics/java/android/graphics/BLASTBufferQueue.java
index c52f700..90723b2 100644
--- a/graphics/java/android/graphics/BLASTBufferQueue.java
+++ b/graphics/java/android/graphics/BLASTBufferQueue.java
@@ -17,6 +17,7 @@
package android.graphics;
import android.annotation.NonNull;
+import android.os.IBinder;
import android.view.Surface;
import android.view.SurfaceControl;
@@ -47,6 +48,7 @@
long frameNumber);
private static native void nativeSetTransactionHangCallback(long ptr,
TransactionHangCallback callback);
+ private static native void nativeSetApplyToken(long ptr, IBinder applyToken);
public interface TransactionHangCallback {
void onTransactionHang(String reason);
@@ -204,4 +206,8 @@
public void setTransactionHangCallback(TransactionHangCallback hangCallback) {
nativeSetTransactionHangCallback(mNativeObject, hangCallback);
}
+
+ public void setApplyToken(IBinder applyToken) {
+ nativeSetApplyToken(mNativeObject, applyToken);
+ }
}
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java
index 9027bf3..88878c6 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java
@@ -40,6 +40,7 @@
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.app.ActivityManager;
+import android.app.TaskInfo;
import android.app.WindowConfiguration;
import android.graphics.Rect;
import android.util.ArrayMap;
@@ -339,6 +340,52 @@
return target;
}
+ /**
+ * Creates a new RemoteAnimationTarget from the provided change and leash
+ */
+ public static RemoteAnimationTarget newSyntheticTarget(ActivityManager.RunningTaskInfo taskInfo,
+ SurfaceControl leash, @TransitionInfo.TransitionMode int mode, int order,
+ boolean isTranslucent) {
+ int taskId;
+ boolean isNotInRecents;
+ WindowConfiguration windowConfiguration;
+
+ if (taskInfo != null) {
+ taskId = taskInfo.taskId;
+ isNotInRecents = !taskInfo.isRunning;
+ windowConfiguration = taskInfo.configuration.windowConfiguration;
+ } else {
+ taskId = INVALID_TASK_ID;
+ isNotInRecents = true;
+ windowConfiguration = new WindowConfiguration();
+ }
+
+ Rect localBounds = new Rect();
+ RemoteAnimationTarget target = new RemoteAnimationTarget(
+ taskId,
+ newModeToLegacyMode(mode),
+ // TODO: once we can properly sync transactions across process,
+ // then get rid of this leash.
+ leash,
+ isTranslucent,
+ null,
+ // TODO(shell-transitions): we need to send content insets? evaluate how its used.
+ new Rect(0, 0, 0, 0),
+ order,
+ null,
+ localBounds,
+ new Rect(),
+ windowConfiguration,
+ isNotInRecents,
+ null,
+ new Rect(),
+ taskInfo,
+ false,
+ INVALID_WINDOW_TYPE
+ );
+ return target;
+ }
+
private static RemoteAnimationTarget getDividerTarget(TransitionInfo.Change change,
SurfaceControl leash) {
return new RemoteAnimationTarget(-1 /* taskId */, newModeToLegacyMode(change.getMode()),
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
index 452d12a..7e6f434 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -46,7 +46,6 @@
import android.util.SparseArray;
import android.view.SurfaceControl;
import android.window.ITaskOrganizerController;
-import android.window.ScreenCapture;
import android.window.StartingWindowInfo;
import android.window.StartingWindowRemovalInfo;
import android.window.TaskAppearedInfo;
@@ -55,7 +54,6 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.ProtoLog;
import com.android.internal.util.FrameworkStatsLog;
-import com.android.wm.shell.common.ScreenshotUtils;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.compatui.CompatUIController;
import com.android.wm.shell.compatui.api.CompatUIHandler;
@@ -74,7 +72,6 @@
import java.util.List;
import java.util.Objects;
import java.util.Optional;
-import java.util.function.Consumer;
/**
* Unified task organizer for all components in the shell.
@@ -561,19 +558,6 @@
mRecentTasks.ifPresent(recentTasks -> recentTasks.onTaskAdded(info.getTaskInfo()));
}
- /**
- * Take a screenshot of a task.
- */
- public void screenshotTask(RunningTaskInfo taskInfo, Rect crop,
- Consumer<ScreenCapture.ScreenshotHardwareBuffer> consumer) {
- final TaskAppearedInfo info = mTasks.get(taskInfo.taskId);
- if (info == null) {
- return;
- }
- ScreenshotUtils.captureLayer(info.getLeash(), crop, consumer);
- }
-
-
@Override
public void onTaskInfoChanged(RunningTaskInfo taskInfo) {
synchronized (mLock) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index f478b44..3e5adf3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -117,6 +117,7 @@
*/
private static final long MAX_ANIMATION_DURATION = 2000;
private final LatencyTracker mLatencyTracker;
+ @ShellMainThread private final Handler mHandler;
/** True when a back gesture is ongoing */
private boolean mBackGestureStarted = false;
@@ -218,7 +219,8 @@
@NonNull BackAnimationBackground backAnimationBackground,
ShellBackAnimationRegistry shellBackAnimationRegistry,
ShellCommandHandler shellCommandHandler,
- Transitions transitions) {
+ Transitions transitions,
+ @ShellMainThread Handler handler) {
this(
shellInit,
shellController,
@@ -230,7 +232,8 @@
backAnimationBackground,
shellBackAnimationRegistry,
shellCommandHandler,
- transitions);
+ transitions,
+ handler);
}
@VisibleForTesting
@@ -245,7 +248,8 @@
@NonNull BackAnimationBackground backAnimationBackground,
ShellBackAnimationRegistry shellBackAnimationRegistry,
ShellCommandHandler shellCommandHandler,
- Transitions transitions) {
+ Transitions transitions,
+ @NonNull @ShellMainThread Handler handler) {
mShellController = shellController;
mShellExecutor = shellExecutor;
mActivityTaskManager = activityTaskManager;
@@ -263,6 +267,7 @@
mTransitions = transitions;
mBackTransitionHandler = new BackTransitionHandler();
mTransitions.addHandler(mBackTransitionHandler);
+ mHandler = handler;
updateTouchableArea();
}
@@ -399,7 +404,7 @@
}
}
- private static class IBackAnimationImpl extends IBackAnimation.Stub
+ private class IBackAnimationImpl extends IBackAnimation.Stub
implements ExternalInterfaceBinder {
private BackAnimationController mController;
@@ -417,7 +422,8 @@
callback,
runner,
controller.mContext,
- CUJ_PREDICTIVE_BACK_HOME)));
+ CUJ_PREDICTIVE_BACK_HOME,
+ mHandler)));
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java
index e24df0b..9ca9b73 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java
@@ -20,6 +20,7 @@
import android.annotation.NonNull;
import android.content.Context;
+import android.os.Handler;
import android.os.RemoteException;
import android.util.Log;
import android.view.IRemoteAnimationFinishedCallback;
@@ -31,6 +32,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.jank.Cuj.CujType;
import com.android.internal.jank.InteractionJankMonitor;
+import com.android.wm.shell.shared.annotations.ShellMainThread;
/**
* Used to register the animation callback and runner, it will trigger result if gesture was finish
@@ -45,6 +47,8 @@
private final IRemoteAnimationRunner mRunner;
private final @CujType int mCujType;
private final Context mContext;
+ @ShellMainThread
+ private final Handler mHandler;
// Whether we are waiting to receive onAnimationStart
private boolean mWaitingAnimation;
@@ -56,18 +60,35 @@
@NonNull IOnBackInvokedCallback callback,
@NonNull IRemoteAnimationRunner runner,
@NonNull Context context,
- @CujType int cujType) {
+ @CujType int cujType,
+ @ShellMainThread Handler handler) {
mCallback = callback;
mRunner = runner;
mCujType = cujType;
mContext = context;
+ mHandler = handler;
}
public BackAnimationRunner(
@NonNull IOnBackInvokedCallback callback,
@NonNull IRemoteAnimationRunner runner,
- @NonNull Context context) {
- this(callback, runner, context, NO_CUJ);
+ @NonNull Context context,
+ @ShellMainThread Handler handler
+ ) {
+ this(callback, runner, context, NO_CUJ, handler);
+ }
+
+ /**
+ * @deprecated Use {@link BackAnimationRunner} constructor providing an handler for the ui
+ * thread of the animation.
+ */
+ @Deprecated
+ public BackAnimationRunner(
+ @NonNull IOnBackInvokedCallback callback,
+ @NonNull IRemoteAnimationRunner runner,
+ @NonNull Context context
+ ) {
+ this(callback, runner, context, NO_CUJ, context.getMainThreadHandler());
}
/** Returns the registered animation runner */
@@ -100,7 +121,7 @@
mWaitingAnimation = false;
if (shouldMonitorCUJ(apps)) {
interactionJankMonitor.begin(
- apps[0].leash, mContext, mCujType);
+ apps[0].leash, mContext, mHandler, mCujType);
}
try {
getRunner().onAnimationStart(TRANSIT_OLD_UNSET, apps, wallpapers,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt
index 32e809a..3733930 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt
@@ -26,6 +26,7 @@
import android.graphics.PointF
import android.graphics.Rect
import android.graphics.RectF
+import android.os.Handler
import android.os.RemoteException
import android.util.TimeUtils
import android.view.Choreographer
@@ -53,6 +54,7 @@
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.protolog.ShellProtoLogGroup
import com.android.wm.shell.shared.animation.Interpolators
+import com.android.wm.shell.shared.annotations.ShellMainThread
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min
@@ -61,7 +63,8 @@
private val context: Context,
private val background: BackAnimationBackground,
private val rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
- protected val transaction: SurfaceControl.Transaction
+ protected val transaction: SurfaceControl.Transaction,
+ @ShellMainThread handler: Handler,
) : ShellBackAnimation() {
protected val startClosingRect = RectF()
@@ -80,7 +83,13 @@
private var statusbarHeight = SystemBarUtils.getStatusBarHeight(context)
private val backAnimationRunner =
- BackAnimationRunner(Callback(), Runner(), context, Cuj.CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY)
+ BackAnimationRunner(
+ Callback(),
+ Runner(),
+ context,
+ Cuj.CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY,
+ handler,
+ )
private val initialTouchPos = PointF()
private val transformMatrix = Matrix()
private val tmpFloat9 = FloatArray(9)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
index 3fcceca..7a56979 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
@@ -34,6 +34,7 @@
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
+import android.os.Handler;
import android.os.RemoteException;
import android.view.Choreographer;
import android.view.IRemoteAnimationFinishedCallback;
@@ -52,6 +53,7 @@
import com.android.internal.protolog.ProtoLog;
import com.android.wm.shell.R;
import com.android.wm.shell.shared.animation.Interpolators;
+import com.android.wm.shell.shared.annotations.ShellMainThread;
import javax.inject.Inject;
@@ -113,9 +115,10 @@
private float mVerticalMargin;
@Inject
- public CrossTaskBackAnimation(Context context, BackAnimationBackground background) {
+ public CrossTaskBackAnimation(Context context, BackAnimationBackground background,
+ @ShellMainThread Handler handler) {
mBackAnimationRunner = new BackAnimationRunner(
- new Callback(), new Runner(), context, CUJ_PREDICTIVE_BACK_CROSS_TASK);
+ new Callback(), new Runner(), context, CUJ_PREDICTIVE_BACK_CROSS_TASK, handler);
mBackground = background;
mContext = context;
loadResources();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomCrossActivityBackAnimation.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomCrossActivityBackAnimation.kt
index b02f97b..2f7666b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomCrossActivityBackAnimation.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomCrossActivityBackAnimation.kt
@@ -18,6 +18,7 @@
import android.content.Context
import android.graphics.Rect
import android.graphics.RectF
+import android.os.Handler
import android.util.MathUtils
import android.view.SurfaceControl
import android.view.animation.Animation
@@ -30,6 +31,7 @@
import com.android.internal.protolog.ProtoLog
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.protolog.ShellProtoLogGroup
+import com.android.wm.shell.shared.annotations.ShellMainThread
import javax.inject.Inject
import kotlin.math.max
import kotlin.math.min
@@ -40,13 +42,15 @@
background: BackAnimationBackground,
rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
transaction: SurfaceControl.Transaction,
- private val customAnimationLoader: CustomAnimationLoader
+ private val customAnimationLoader: CustomAnimationLoader,
+ @ShellMainThread handler: Handler,
) :
CrossActivityBackAnimation(
context,
background,
rootTaskDisplayAreaOrganizer,
- transaction
+ transaction,
+ handler
) {
private var enterAnimation: Animation? = null
@@ -59,7 +63,8 @@
constructor(
context: Context,
background: BackAnimationBackground,
- rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer
+ rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
+ @ShellMainThread handler: Handler,
) : this(
context,
background,
@@ -67,7 +72,8 @@
SurfaceControl.Transaction(),
CustomAnimationLoader(
TransitionAnimation(context, false /* debug */, "CustomCrossActivityBackAnimation")
- )
+ ),
+ handler,
)
override fun preparePreCommitClosingRectMovement(swipeEdge: Int) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/DefaultCrossActivityBackAnimation.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/back/DefaultCrossActivityBackAnimation.kt
index 66d8a5f..eecd769 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/DefaultCrossActivityBackAnimation.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/DefaultCrossActivityBackAnimation.kt
@@ -16,11 +16,13 @@
package com.android.wm.shell.back
import android.content.Context
+import android.os.Handler
import android.view.SurfaceControl
import android.window.BackEvent
import com.android.wm.shell.R
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.shared.animation.Interpolators
+import com.android.wm.shell.shared.annotations.ShellMainThread
import javax.inject.Inject
import kotlin.math.max
@@ -30,13 +32,15 @@
constructor(
context: Context,
background: BackAnimationBackground,
- rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer
+ rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
+ @ShellMainThread handler: Handler,
) :
CrossActivityBackAnimation(
context,
background,
rootTaskDisplayAreaOrganizer,
- SurfaceControl.Transaction()
+ SurfaceControl.Transaction(),
+ handler
) {
private val postCommitInterpolator = Interpolators.EMPHASIZED
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index d253736..c545d73 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -2007,7 +2007,7 @@
@Override
public void selectionChanged(BubbleViewProvider selectedBubble) {
// Only need to update the layer view if we're currently expanded for selection changes.
- if (mLayerView != null && mLayerView.isExpanded()) {
+ if (mLayerView != null && isStackExpanded()) {
mLayerView.showExpandedView(selectedBubble);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
index 1367b7e..1c9c195 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
@@ -186,10 +186,6 @@
if (expandedView == null) {
return;
}
- if (mExpandedBubble != null && mIsExpanded && b.getKey().equals(mExpandedBubble.getKey())) {
- // Already showing this bubble, skip animating
- return;
- }
if (mExpandedBubble != null && !b.getKey().equals(mExpandedBubble.getKey())) {
removeView(mExpandedView);
mExpandedView = null;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
index b8aa1b1..4b55fd0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
@@ -48,6 +48,7 @@
import android.content.res.Resources;
import android.graphics.Point;
import android.graphics.Rect;
+import android.os.Handler;
import android.view.Display;
import android.view.InsetsSourceControl;
import android.view.InsetsState;
@@ -75,6 +76,7 @@
import com.android.wm.shell.common.split.DividerSnapAlgorithm.SnapTarget;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.shared.animation.Interpolators;
+import com.android.wm.shell.shared.annotations.ShellMainThread;
import com.android.wm.shell.shared.split.SplitScreenConstants.PersistentSnapPosition;
import com.android.wm.shell.shared.split.SplitScreenConstants.SnapPosition;
import com.android.wm.shell.shared.split.SplitScreenConstants.SplitPosition;
@@ -116,6 +118,8 @@
new PathInterpolator(0.2f, 0f, 0f, 1f);
private static final Interpolator GROW_INTERPOLATOR =
new PathInterpolator(0.45f, 0f, 0.5f, 1f);
+ @ShellMainThread
+ private final Handler mHandler;
private int mDividerWindowWidth;
private int mDividerInsets;
@@ -166,7 +170,8 @@
SplitLayoutHandler splitLayoutHandler,
SplitWindowManager.ParentContainerCallbacks parentContainerCallbacks,
DisplayController displayController, DisplayImeController displayImeController,
- ShellTaskOrganizer taskOrganizer, int parallaxType) {
+ ShellTaskOrganizer taskOrganizer, int parallaxType, @ShellMainThread Handler handler) {
+ mHandler = handler;
mContext = context.createConfigurationContext(configuration);
mOrientation = configuration.orientation;
mRotation = configuration.windowConfiguration.getRotation();
@@ -598,7 +603,8 @@
}
void onStartDragging() {
- mInteractionJankMonitor.begin(getDividerLeash(), mContext, CUJ_SPLIT_SCREEN_RESIZE);
+ mInteractionJankMonitor.begin(getDividerLeash(), mContext, mHandler,
+ CUJ_SPLIT_SCREEN_RESIZE);
}
void onDraggingCancelled() {
@@ -756,7 +762,7 @@
@Override
public void onAnimationStart(Animator animation) {
mInteractionJankMonitor.begin(getDividerLeash(),
- mContext, CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER);
+ mContext, mHandler, CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER);
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index 4adea23..722fe1f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -444,7 +444,9 @@
BackAnimationBackground backAnimationBackground,
Optional<ShellBackAnimationRegistry> shellBackAnimationRegistry,
ShellCommandHandler shellCommandHandler,
- Transitions transitions) {
+ Transitions transitions,
+ @ShellMainThread Handler handler
+ ) {
if (BackAnimationController.IS_ENABLED) {
return shellBackAnimationRegistry.map(
(animations) ->
@@ -457,7 +459,8 @@
backAnimationBackground,
animations,
shellCommandHandler,
- transitions));
+ transitions,
+ handler));
}
return Optional.empty();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index b151c8b..b47adb4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -355,7 +355,8 @@
@ShellMainThread ShellExecutor mainExecutor,
@ShellAnimationThread ShellExecutor animExecutor,
@DynamicOverride DesktopModeTaskRepository desktopModeTaskRepository,
- InteractionJankMonitor interactionJankMonitor) {
+ InteractionJankMonitor interactionJankMonitor,
+ @ShellMainThread Handler handler) {
return new FreeformTaskTransitionHandler(
shellInit,
transitions,
@@ -365,7 +366,8 @@
mainExecutor,
animExecutor,
desktopModeTaskRepository,
- interactionJankMonitor);
+ interactionJankMonitor,
+ handler);
}
@WMSingleton
@@ -487,10 +489,11 @@
@Provides
static RecentsTransitionHandler provideRecentsTransitionHandler(
ShellInit shellInit,
+ ShellTaskOrganizer shellTaskOrganizer,
Transitions transitions,
Optional<RecentTasksController> recentTasksController,
HomeTransitionObserver homeTransitionObserver) {
- return new RecentsTransitionHandler(shellInit, transitions,
+ return new RecentsTransitionHandler(shellInit, shellTaskOrganizer, transitions,
recentTasksController.orElse(null), homeTransitionObserver);
}
@@ -616,6 +619,7 @@
RecentsTransitionHandler recentsTransitionHandler,
MultiInstanceHelper multiInstanceHelper,
@ShellMainThread ShellExecutor mainExecutor,
+ @ShellMainThread Handler mainHandler,
Optional<DesktopTasksLimiter> desktopTasksLimiter,
Optional<RecentTasksController> recentTasksController,
InteractionJankMonitor interactionJankMonitor) {
@@ -628,7 +632,7 @@
dragToDesktopTransitionHandler, desktopModeTaskRepository,
desktopModeLoggerTransitionObserver, launchAdjacentController,
recentsTransitionHandler, multiInstanceHelper, mainExecutor, desktopTasksLimiter,
- recentTasksController.orElse(null), interactionJankMonitor);
+ recentTasksController.orElse(null), interactionJankMonitor, mainHandler);
}
@WMSingleton
@@ -638,7 +642,8 @@
Transitions transitions,
@DynamicOverride DesktopModeTaskRepository desktopModeTaskRepository,
ShellTaskOrganizer shellTaskOrganizer,
- InteractionJankMonitor interactionJankMonitor) {
+ InteractionJankMonitor interactionJankMonitor,
+ @ShellMainThread Handler handler) {
int maxTaskLimit = DesktopModeStatus.getMaxTaskLimit(context);
if (!DesktopModeStatus.canEnterDesktopMode(context)
|| !ENABLE_DESKTOP_WINDOWING_TASK_LIMIT.isEnabled(context)
@@ -652,7 +657,8 @@
shellTaskOrganizer,
maxTaskLimit,
interactionJankMonitor,
- context)
+ context,
+ handler)
);
}
@@ -699,9 +705,10 @@
static ExitDesktopTaskTransitionHandler provideExitDesktopTaskTransitionHandler(
Transitions transitions,
Context context,
- InteractionJankMonitor interactionJankMonitor) {
+ InteractionJankMonitor interactionJankMonitor,
+ @ShellMainThread Handler handler) {
return new ExitDesktopTaskTransitionHandler(
- transitions, context, interactionJankMonitor);
+ transitions, context, interactionJankMonitor, handler);
}
@WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java
index 037fbb2..3a4764d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java
@@ -101,7 +101,8 @@
DisplayInsetsController displayInsetsController,
TabletopModeController pipTabletopController,
Optional<OneHandedController> oneHandedController,
- @ShellMainThread ShellExecutor mainExecutor) {
+ @ShellMainThread ShellExecutor mainExecutor,
+ @ShellMainThread Handler handler) {
return Optional.ofNullable(PipController.create(
context, shellInit, shellCommandHandler, shellController,
displayController, pipAnimationController, pipAppOpsListener,
@@ -111,7 +112,7 @@
pipTransitionState, pipTouchHandler, pipTransitionController,
windowManagerShellWrapper, taskStackListener, pipParamsChangedForwarder,
displayInsetsController, pipTabletopController, oneHandedController,
- mainExecutor));
+ mainExecutor, handler));
}
// Handler is used by Icon.loadDrawableAsync
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index c74f4a7..853284a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -35,6 +35,7 @@
import android.graphics.Rect
import android.graphics.Region
import android.os.Binder
+import android.os.Handler
import android.os.IBinder
import android.os.SystemProperties
import android.util.Size
@@ -136,7 +137,8 @@
@ShellMainThread private val mainExecutor: ShellExecutor,
private val desktopTasksLimiter: Optional<DesktopTasksLimiter>,
private val recentTasksController: RecentTasksController?,
- private val interactionJankMonitor: InteractionJankMonitor
+ private val interactionJankMonitor: InteractionJankMonitor,
+ @ShellMainThread private val handler: Handler,
) :
RemoteCallable<DesktopTasksController>,
Transitions.TransitionHandler,
@@ -387,7 +389,7 @@
taskSurface: SurfaceControl,
) {
logV("startDragToDesktop taskId=%d", taskInfo.taskId)
- interactionJankMonitor.begin(taskSurface, context,
+ interactionJankMonitor.begin(taskSurface, context, handler,
CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD)
dragToDesktopTransitionHandler.startDragToDesktopTransition(
taskInfo.taskId,
@@ -791,7 +793,7 @@
releaseVisualIndicator()
if (!taskInfo.isResizeable && DesktopModeFlags.DISABLE_SNAP_RESIZE.isEnabled(context)) {
interactionJankMonitor.begin(
- taskSurface, context, CUJ_DESKTOP_MODE_SNAP_RESIZE, "drag_non_resizable"
+ taskSurface, context, handler, CUJ_DESKTOP_MODE_SNAP_RESIZE, "drag_non_resizable"
)
// reposition non-resizable app back to its original position before being dragged
@@ -804,7 +806,7 @@
)
} else {
interactionJankMonitor.begin(
- taskSurface, context, CUJ_DESKTOP_MODE_SNAP_RESIZE, "drag_resizable"
+ taskSurface, context, handler, CUJ_DESKTOP_MODE_SNAP_RESIZE, "drag_resizable"
)
snapToHalfScreen(taskInfo, taskSurface, currentDragBounds, position)
}
@@ -1604,7 +1606,7 @@
when (indicatorType) {
IndicatorType.TO_DESKTOP_INDICATOR -> {
// Start a new jank interaction for the drag release to desktop window animation.
- interactionJankMonitor.begin(taskSurface, context,
+ interactionJankMonitor.begin(taskSurface, context, handler,
CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE, "to_desktop")
finalizeDragToDesktop(taskInfo)
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
index 597637d..dae37f4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
@@ -18,6 +18,7 @@
import android.app.ActivityManager.RunningTaskInfo
import android.content.Context
+import android.os.Handler
import android.os.IBinder
import android.view.SurfaceControl
import android.view.WindowManager.TRANSIT_TO_BACK
@@ -29,6 +30,7 @@
import com.android.internal.protolog.ProtoLog
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.protolog.ShellProtoLogGroup
+import com.android.wm.shell.shared.annotations.ShellMainThread
import com.android.wm.shell.transition.Transitions
import com.android.wm.shell.transition.Transitions.TransitionObserver
@@ -45,7 +47,8 @@
private val shellTaskOrganizer: ShellTaskOrganizer,
private val maxTasksLimit: Int,
private val interactionJankMonitor: InteractionJankMonitor,
- private val context: Context
+ private val context: Context,
+ @ShellMainThread private val handler: Handler,
) {
private val minimizeTransitionObserver = MinimizeTransitionObserver()
@VisibleForTesting
@@ -125,7 +128,7 @@
if (mActiveTaskDetails != null && mActiveTaskDetails.transitionInfo != null) {
// Begin minimize window CUJ instrumentation.
interactionJankMonitor.begin(
- mActiveTaskDetails.transitionInfo?.rootLeash, context,
+ mActiveTaskDetails.transitionInfo?.rootLeash, context, handler,
CUJ_DESKTOP_MODE_MINIMIZE_WINDOW
)
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java
index e87be52..dedd44f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java
@@ -29,6 +29,7 @@
import android.content.res.Resources;
import android.graphics.Point;
import android.graphics.Rect;
+import android.os.Handler;
import android.os.IBinder;
import android.util.DisplayMetrics;
import android.view.SurfaceControl;
@@ -44,6 +45,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.jank.Cuj;
import com.android.internal.jank.InteractionJankMonitor;
+import com.android.wm.shell.shared.annotations.ShellMainThread;
import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource;
import com.android.wm.shell.transition.Transitions;
@@ -63,6 +65,8 @@
private final Context mContext;
private final Transitions mTransitions;
private final InteractionJankMonitor mInteractionJankMonitor;
+ @ShellMainThread
+ private final Handler mHandler;
private final List<IBinder> mPendingTransitionTokens = new ArrayList<>();
private Consumer<SurfaceControl.Transaction> mOnAnimationFinishedCallback;
private final Supplier<SurfaceControl.Transaction> mTransactionSupplier;
@@ -71,20 +75,24 @@
public ExitDesktopTaskTransitionHandler(
Transitions transitions,
Context context,
- InteractionJankMonitor interactionJankMonitor
- ) {
- this(transitions, SurfaceControl.Transaction::new, context, interactionJankMonitor);
+ InteractionJankMonitor interactionJankMonitor,
+ @ShellMainThread Handler handler
+ ) {
+ this(transitions, SurfaceControl.Transaction::new, context, interactionJankMonitor,
+ handler);
}
private ExitDesktopTaskTransitionHandler(
Transitions transitions,
Supplier<SurfaceControl.Transaction> supplier,
Context context,
- InteractionJankMonitor interactionJankMonitor) {
+ InteractionJankMonitor interactionJankMonitor,
+ @ShellMainThread Handler handler) {
mTransitions = transitions;
mTransactionSupplier = supplier;
mContext = context;
mInteractionJankMonitor = interactionJankMonitor;
+ mHandler = handler;
}
/**
@@ -154,7 +162,7 @@
final SurfaceControl sc = change.getLeash();
final Rect endBounds = change.getEndAbsBounds();
mInteractionJankMonitor
- .begin(sc, mContext, Cuj.CUJ_DESKTOP_MODE_EXIT_MODE);
+ .begin(sc, mContext, mHandler, Cuj.CUJ_DESKTOP_MODE_EXIT_MODE);
// Hide the first (fullscreen) frame because the animation will start from the freeform
// size.
startT.hide(sc)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java
index 832e2d2..517e209 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java
@@ -28,6 +28,7 @@
import android.app.WindowConfiguration;
import android.content.Context;
import android.graphics.Rect;
+import android.os.Handler;
import android.os.IBinder;
import android.util.ArrayMap;
import android.view.SurfaceControl;
@@ -43,6 +44,7 @@
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
+import com.android.wm.shell.shared.annotations.ShellMainThread;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
import com.android.wm.shell.windowdecor.WindowDecorViewModel;
@@ -65,6 +67,8 @@
private final InteractionJankMonitor mInteractionJankMonitor;
private final ShellExecutor mMainExecutor;
private final ShellExecutor mAnimExecutor;
+ @ShellMainThread
+ private final Handler mHandler;
private final List<IBinder> mPendingTransitionTokens = new ArrayList<>();
@@ -79,7 +83,8 @@
ShellExecutor mainExecutor,
ShellExecutor animExecutor,
DesktopModeTaskRepository desktopModeTaskRepository,
- InteractionJankMonitor interactionJankMonitor) {
+ InteractionJankMonitor interactionJankMonitor,
+ @ShellMainThread Handler handler) {
mTransitions = transitions;
mContext = context;
mWindowDecorViewModel = windowDecorViewModel;
@@ -88,6 +93,7 @@
mInteractionJankMonitor = interactionJankMonitor;
mMainExecutor = mainExecutor;
mAnimExecutor = animExecutor;
+ mHandler = handler;
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
shellInit.addInitCallback(this::onInit, this);
}
@@ -267,7 +273,7 @@
change.getTaskInfo().displayId) == 1) {
// Starting the jank trace if closing the last window in desktop mode.
mInteractionJankMonitor.begin(
- sc, mContext, CUJ_DESKTOP_MODE_EXIT_MODE_ON_LAST_WINDOW_CLOSE);
+ sc, mContext, mHandler, CUJ_DESKTOP_MODE_EXIT_MODE_ON_LAST_WINDOW_CLOSE);
}
animator.addListener(
new AnimatorListenerAdapter() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
index 1827923..15472eb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
@@ -55,6 +55,7 @@
import com.android.wm.shell.common.TaskStackListenerCallback;
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.shared.annotations.ExternalThread;
+import com.android.wm.shell.shared.annotations.ShellMainThread;
import com.android.wm.shell.sysui.ConfigurationChangeListener;
import com.android.wm.shell.sysui.KeyguardChangeListener;
import com.android.wm.shell.sysui.ShellCommandHandler;
@@ -203,7 +204,7 @@
DisplayController displayController, DisplayLayout displayLayout,
TaskStackListenerImpl taskStackListener,
InteractionJankMonitor jankMonitor, UiEventLogger uiEventLogger,
- ShellExecutor mainExecutor, Handler mainHandler) {
+ ShellExecutor mainExecutor, @ShellMainThread Handler mainHandler) {
OneHandedSettingsUtil settingsUtil = new OneHandedSettingsUtil();
OneHandedAccessibilityUtil accessibilityUtil = new OneHandedAccessibilityUtil(context);
OneHandedTimeoutHandler timeoutHandler = new OneHandedTimeoutHandler(mainExecutor);
@@ -217,7 +218,7 @@
mainExecutor);
OneHandedDisplayAreaOrganizer organizer = new OneHandedDisplayAreaOrganizer(
context, displayLayout, settingsUtil, animationController, tutorialHandler,
- jankMonitor, mainExecutor);
+ jankMonitor, mainExecutor, mainHandler);
OneHandedUiEventLogger oneHandedUiEventsLogger = new OneHandedUiEventLogger(uiEventLogger);
return new OneHandedController(context, shellInit, shellCommandHandler, shellController,
displayController, organizer, touchHandler, tutorialHandler, settingsUtil,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java
index d157ca8..95e633d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java
@@ -23,6 +23,7 @@
import android.content.Context;
import android.graphics.Rect;
+import android.os.Handler;
import android.os.SystemProperties;
import android.text.TextUtils;
import android.util.ArrayMap;
@@ -42,6 +43,7 @@
import com.android.wm.shell.R;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.shared.annotations.ShellMainThread;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -70,6 +72,8 @@
private final OneHandedSettingsUtil mOneHandedSettingsUtil;
private final InteractionJankMonitor mJankMonitor;
private final Context mContext;
+ @ShellMainThread
+ private final Handler mHandler;
private boolean mIsReady;
private float mLastVisualOffset = 0;
@@ -136,9 +140,11 @@
OneHandedAnimationController animationController,
OneHandedTutorialHandler tutorialHandler,
InteractionJankMonitor jankMonitor,
- ShellExecutor mainExecutor) {
+ ShellExecutor mainExecutor,
+ @ShellMainThread Handler handler) {
super(mainExecutor);
mContext = context;
+ mHandler = handler;
setDisplayLayout(displayLayout);
mOneHandedSettingsUtil = oneHandedSettingsUtil;
mAnimationController = animationController;
@@ -333,7 +339,7 @@
getDisplayAreaTokenMap().entrySet().iterator().next();
final InteractionJankMonitor.Configuration.Builder builder =
InteractionJankMonitor.Configuration.Builder.withSurface(
- cujType, mContext, firstEntry.getValue());
+ cujType, mContext, firstEntry.getValue(), mHandler);
if (!TextUtils.isEmpty(tag)) {
builder.setTag(tag);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index deb7691..af68442 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -44,6 +44,7 @@
import android.content.res.Configuration;
import android.graphics.Point;
import android.graphics.Rect;
+import android.os.Handler;
import android.os.RemoteException;
import android.os.SystemProperties;
import android.util.Pair;
@@ -93,6 +94,7 @@
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.pip.PipTransitionState;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.shared.annotations.ShellMainThread;
import com.android.wm.shell.sysui.ConfigurationChangeListener;
import com.android.wm.shell.sysui.KeyguardChangeListener;
import com.android.wm.shell.sysui.ShellCommandHandler;
@@ -146,6 +148,8 @@
private Optional<OneHandedController> mOneHandedController;
private final ShellCommandHandler mShellCommandHandler;
private final ShellController mShellController;
+ @ShellMainThread
+ private final Handler mHandler;
protected final PipImpl mImpl;
private final Rect mTmpInsetBounds = new Rect();
@@ -405,7 +409,8 @@
DisplayInsetsController displayInsetsController,
TabletopModeController pipTabletopController,
Optional<OneHandedController> oneHandedController,
- ShellExecutor mainExecutor) {
+ ShellExecutor mainExecutor,
+ Handler handler) {
if (!context.getPackageManager().hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)) {
ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: Device doesn't support Pip feature", TAG);
@@ -418,7 +423,8 @@
pipDisplayLayoutState, pipMotionHelper, pipMediaController, phonePipMenuController,
pipTaskOrganizer, pipTransitionState, pipTouchHandler, pipTransitionController,
windowManagerShellWrapper, taskStackListener, pipParamsChangedForwarder,
- displayInsetsController, pipTabletopController, oneHandedController, mainExecutor)
+ displayInsetsController, pipTabletopController, oneHandedController, mainExecutor,
+ handler)
.mImpl;
}
@@ -446,11 +452,13 @@
DisplayInsetsController displayInsetsController,
TabletopModeController tabletopModeController,
Optional<OneHandedController> oneHandedController,
- ShellExecutor mainExecutor
+ ShellExecutor mainExecutor,
+ @ShellMainThread Handler handler
) {
mContext = context;
mShellCommandHandler = shellCommandHandler;
mShellController = shellController;
+ mHandler = handler;
mImpl = new PipImpl();
mWindowManagerShellWrapper = windowManagerShellWrapper;
mDisplayController = displayController;
@@ -1047,7 +1055,8 @@
// Begin InteractionJankMonitor with PIP transition CUJs
final InteractionJankMonitor.Configuration.Builder builder =
InteractionJankMonitor.Configuration.Builder.withSurface(
- CUJ_PIP_TRANSITION, mContext, mPipTaskOrganizer.getSurfaceControl())
+ CUJ_PIP_TRANSITION, mContext, mPipTaskOrganizer.getSurfaceControl(),
+ mHandler)
.setTag(getTransitionTag(direction))
.setTimeout(2000);
InteractionJankMonitor.getInstance().begin(builder);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
index c660000..8077aee 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
@@ -20,9 +20,12 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.KEYGUARD_VISIBILITY_TRANSIT_FLAGS;
import static android.view.WindowManager.TRANSIT_CHANGE;
+import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_LOCKED;
+import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_PIP;
import static android.view.WindowManager.TRANSIT_SLEEP;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
@@ -41,6 +44,7 @@
import android.content.Intent;
import android.graphics.Color;
import android.graphics.Rect;
+import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
@@ -64,6 +68,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.IResultReceiver;
import com.android.internal.protolog.ProtoLog;
+import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.pip.PipUtils;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
@@ -79,10 +84,15 @@
* Handles the Recents (overview) animation. Only one of these can run at a time. A recents
* transition must be created via {@link #startRecentsTransition}. Anything else will be ignored.
*/
-public class RecentsTransitionHandler implements Transitions.TransitionHandler {
+public class RecentsTransitionHandler implements Transitions.TransitionHandler,
+ Transitions.TransitionObserver {
private static final String TAG = "RecentsTransitionHandler";
+ // A placeholder for a synthetic transition that isn't backed by a true system transition
+ public static final IBinder SYNTHETIC_TRANSITION = new Binder();
+
private final Transitions mTransitions;
+ private final ShellTaskOrganizer mShellTaskOrganizer;
private final ShellExecutor mExecutor;
@Nullable
private final RecentTasksController mRecentTasksController;
@@ -99,19 +109,26 @@
private final HomeTransitionObserver mHomeTransitionObserver;
private @Nullable Color mBackgroundColor;
- public RecentsTransitionHandler(ShellInit shellInit, Transitions transitions,
+ public RecentsTransitionHandler(
+ @NonNull ShellInit shellInit,
+ @NonNull ShellTaskOrganizer shellTaskOrganizer,
+ @NonNull Transitions transitions,
@Nullable RecentTasksController recentTasksController,
- HomeTransitionObserver homeTransitionObserver) {
+ @NonNull HomeTransitionObserver homeTransitionObserver) {
+ mShellTaskOrganizer = shellTaskOrganizer;
mTransitions = transitions;
mExecutor = transitions.getMainExecutor();
mRecentTasksController = recentTasksController;
mHomeTransitionObserver = homeTransitionObserver;
if (!Transitions.ENABLE_SHELL_TRANSITIONS) return;
if (recentTasksController == null) return;
- shellInit.addInitCallback(() -> {
- recentTasksController.setTransitionHandler(this);
- transitions.addHandler(this);
- }, this);
+ shellInit.addInitCallback(this::onInit, this);
+ }
+
+ private void onInit() {
+ mRecentTasksController.setTransitionHandler(this);
+ mTransitions.addHandler(this);
+ mTransitions.registerObserver(this);
}
/** Register a mixer handler. {@see RecentsMixedHandler}*/
@@ -138,17 +155,59 @@
mBackgroundColor = color;
}
+ /**
+ * Starts a new real/synthetic recents transition.
+ */
@VisibleForTesting
public IBinder startRecentsTransition(PendingIntent intent, Intent fillIn, Bundle options,
IApplicationThread appThread, IRecentsAnimationRunner listener) {
+ // only care about latest one.
+ mAnimApp = appThread;
+
+ // TODO(b/366021931): Formalize this later
+ final boolean isSyntheticRequest = options.containsKey("is_synthetic_recents_transition");
+ if (isSyntheticRequest) {
+ return startSyntheticRecentsTransition(listener);
+ } else {
+ return startRealRecentsTransition(intent, fillIn, options, listener);
+ }
+ }
+
+ /**
+ * Starts a synthetic recents transition that is not backed by a real WM transition.
+ */
+ private IBinder startSyntheticRecentsTransition(@NonNull IRecentsAnimationRunner listener) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+ "RecentsTransitionHandler.startRecentsTransition(synthetic)");
+ final RecentsController lastController = getLastController();
+ if (lastController != null) {
+ lastController.cancel(lastController.isSyntheticTransition()
+ ? "existing_running_synthetic_transition"
+ : "existing_running_transition");
+ return null;
+ }
+
+ // Create a new synthetic transition and start it immediately
+ final RecentsController controller = new RecentsController(listener);
+ controller.startSyntheticTransition();
+ mControllers.add(controller);
+ return SYNTHETIC_TRANSITION;
+ }
+
+ /**
+ * Starts a real WM-backed recents transition.
+ */
+ private IBinder startRealRecentsTransition(PendingIntent intent, Intent fillIn, Bundle options,
+ IRecentsAnimationRunner listener) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
"RecentsTransitionHandler.startRecentsTransition");
- // only care about latest one.
- mAnimApp = appThread;
- WindowContainerTransaction wct = new WindowContainerTransaction();
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
wct.sendPendingIntent(intent, fillIn, options);
- final RecentsController controller = new RecentsController(listener);
+
+ // Find the mixed handler which should handle this request (if we are in a state where a
+ // mixed handler is needed). This is slightly convoluted because starting the transition
+ // requires the handler, but the mixed handler also needs a reference to the transition.
RecentsMixedHandler mixer = null;
Consumer<IBinder> setTransitionForMixer = null;
for (int i = 0; i < mMixers.size(); ++i) {
@@ -160,12 +219,11 @@
}
final IBinder transition = mTransitions.startTransition(TRANSIT_TO_FRONT, wct,
mixer == null ? this : mixer);
- for (int i = 0; i < mStateListeners.size(); i++) {
- mStateListeners.get(i).onTransitionStarted(transition);
- }
if (mixer != null) {
setTransitionForMixer.accept(transition);
}
+
+ final RecentsController controller = new RecentsController(listener);
if (transition != null) {
controller.setTransition(transition);
mControllers.add(controller);
@@ -187,11 +245,28 @@
return null;
}
- private int findController(IBinder transition) {
+ /**
+ * Returns if there is currently a pending or active recents transition.
+ */
+ @Nullable
+ private RecentsController getLastController() {
+ return !mControllers.isEmpty() ? mControllers.getLast() : null;
+ }
+
+ /**
+ * Finds an existing controller for the provided {@param transition}, or {@code null} if none
+ * exists.
+ */
+ @Nullable
+ @VisibleForTesting
+ RecentsController findController(@NonNull IBinder transition) {
for (int i = mControllers.size() - 1; i >= 0; --i) {
- if (mControllers.get(i).mTransition == transition) return i;
+ final RecentsController controller = mControllers.get(i);
+ if (controller.mTransition == transition) {
+ return controller;
+ }
}
- return -1;
+ return null;
}
@Override
@@ -199,13 +274,12 @@
SurfaceControl.Transaction startTransaction,
SurfaceControl.Transaction finishTransaction,
Transitions.TransitionFinishCallback finishCallback) {
- final int controllerIdx = findController(transition);
- if (controllerIdx < 0) {
+ final RecentsController controller = findController(transition);
+ if (controller == null) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
"RecentsTransitionHandler.startAnimation: no controller found");
return false;
}
- final RecentsController controller = mControllers.get(controllerIdx);
final IApplicationThread animApp = mAnimApp;
mAnimApp = null;
if (!controller.start(info, startTransaction, finishTransaction, finishCallback)) {
@@ -221,13 +295,12 @@
public void mergeAnimation(IBinder transition, TransitionInfo info,
SurfaceControl.Transaction t, IBinder mergeTarget,
Transitions.TransitionFinishCallback finishCallback) {
- final int targetIdx = findController(mergeTarget);
- if (targetIdx < 0) {
+ final RecentsController controller = findController(mergeTarget);
+ if (controller == null) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
"RecentsTransitionHandler.mergeAnimation: no controller found");
return;
}
- final RecentsController controller = mControllers.get(targetIdx);
controller.merge(info, t, finishCallback);
}
@@ -244,8 +317,21 @@
}
}
+ @Override
+ public void onTransitionReady(@NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction) {
+ RecentsController controller = findController(SYNTHETIC_TRANSITION);
+ if (controller != null) {
+ // Cancel the existing synthetic transition if there is one
+ controller.cancel("incoming_transition");
+ }
+ }
+
/** There is only one of these and it gets reset on finish. */
- private class RecentsController extends IRecentsAnimationController.Stub {
+ @VisibleForTesting
+ class RecentsController extends IRecentsAnimationController.Stub {
+
private final int mInstanceId;
private IRecentsAnimationRunner mListener;
@@ -307,7 +393,8 @@
mDeathHandler = () -> {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
"[%d] RecentsController.DeathRecipient: binder died", mInstanceId);
- finish(mWillFinishToHome, false /* leaveHint */, null /* finishCb */);
+ finishInner(mWillFinishToHome, false /* leaveHint */, null /* finishCb */,
+ "deathRecipient");
};
try {
mListener.asBinder().linkToDeath(mDeathHandler, 0 /* flags */);
@@ -317,6 +404,9 @@
}
}
+ /**
+ * Sets the started transition for this instance of the recents transition.
+ */
void setTransition(IBinder transition) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
"[%d] RecentsController.setTransition: id=%s", mInstanceId, transition);
@@ -330,6 +420,10 @@
}
void cancel(boolean toHome, boolean withScreenshots, String reason) {
+ if (cancelSyntheticTransition(reason)) {
+ return;
+ }
+
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
"[%d] RecentsController.cancel: toHome=%b reason=%s",
mInstanceId, toHome, reason);
@@ -341,7 +435,7 @@
}
}
if (mFinishCB != null) {
- finishInner(toHome, false /* userLeave */, null /* finishCb */);
+ finishInner(toHome, false /* userLeave */, null /* finishCb */, "cancel");
} else {
cleanUp();
}
@@ -436,6 +530,91 @@
}
}
+ /**
+ * Starts a new transition that is not backed by a system transition.
+ */
+ void startSyntheticTransition() {
+ mTransition = SYNTHETIC_TRANSITION;
+
+ // TODO(b/366021931): Update mechanism for pulling the home task, for now add home as
+ // both opening and closing since there's some pre-existing
+ // dependencies on having a closing task
+ final ActivityManager.RunningTaskInfo homeTask =
+ mShellTaskOrganizer.getRunningTasks(DEFAULT_DISPLAY).stream()
+ .filter(task -> task.getActivityType() == ACTIVITY_TYPE_HOME)
+ .findFirst()
+ .get();
+ final RemoteAnimationTarget openingTarget = TransitionUtil.newSyntheticTarget(
+ homeTask, mShellTaskOrganizer.getHomeTaskOverlayContainer(), TRANSIT_OPEN,
+ 0, true /* isTranslucent */);
+ final RemoteAnimationTarget closingTarget = TransitionUtil.newSyntheticTarget(
+ homeTask, mShellTaskOrganizer.getHomeTaskOverlayContainer(), TRANSIT_CLOSE,
+ 0, true /* isTranslucent */);
+ final ArrayList<RemoteAnimationTarget> apps = new ArrayList<>();
+ apps.add(openingTarget);
+ apps.add(closingTarget);
+ try {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+ "[%d] RecentsController.start: calling onAnimationStart with %d apps",
+ mInstanceId, apps.size());
+ mListener.onAnimationStart(this,
+ apps.toArray(new RemoteAnimationTarget[apps.size()]),
+ new RemoteAnimationTarget[0],
+ new Rect(0, 0, 0, 0), new Rect(), new Bundle());
+ for (int i = 0; i < mStateListeners.size(); i++) {
+ mStateListeners.get(i).onAnimationStateChanged(true);
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error starting recents animation", e);
+ cancel("startSynthetricTransition() failed");
+ }
+ }
+
+ /**
+ * Returns whether this transition is backed by a real system transition or not.
+ */
+ boolean isSyntheticTransition() {
+ return mTransition == SYNTHETIC_TRANSITION;
+ }
+
+ /**
+ * Called when a synthetic transition is canceled.
+ */
+ boolean cancelSyntheticTransition(String reason) {
+ if (!isSyntheticTransition()) {
+ return false;
+ }
+
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+ "[%d] RecentsController.cancelSyntheticTransition reason=%s",
+ mInstanceId, reason);
+ try {
+ // TODO(b/366021931): Notify the correct tasks once we build actual targets, and
+ // clean up leashes accordingly
+ mListener.onAnimationCanceled(new int[0], new TaskSnapshot[0]);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error canceling previous recents animation", e);
+ }
+ cleanUp();
+ return true;
+ }
+
+ /**
+ * Called when a synthetic transition is finished.
+ * @return
+ */
+ boolean finishSyntheticTransition() {
+ if (!isSyntheticTransition()) {
+ return false;
+ }
+
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+ "[%d] RecentsController.finishSyntheticTransition", mInstanceId);
+ // TODO(b/366021931): Clean up leashes accordingly
+ cleanUp();
+ return true;
+ }
+
boolean start(TransitionInfo info, SurfaceControl.Transaction t,
SurfaceControl.Transaction finishT, Transitions.TransitionFinishCallback finishCB) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
@@ -662,7 +841,7 @@
// Set the callback once again so we can finish correctly.
mFinishCB = finishCB;
finishInner(true /* toHome */, false /* userLeave */,
- null /* finishCb */);
+ null /* finishCb */, "takeOverAnimation");
}, updatedStates);
});
}
@@ -810,7 +989,7 @@
sendCancelWithSnapshots();
mExecutor.executeDelayed(
() -> finishInner(true /* toHome */, false /* userLeaveHint */,
- null /* finishCb */), 0);
+ null /* finishCb */, "merge"), 0);
return;
}
if (recentsOpening != null) {
@@ -1005,7 +1184,7 @@
return;
}
final int displayId = mInfo.getRootCount() > 0 ? mInfo.getRoot(0).getDisplayId()
- : Display.DEFAULT_DISPLAY;
+ : DEFAULT_DISPLAY;
// transient launches don't receive focus automatically. Since we are taking over
// the gesture now, take focus explicitly.
// This also moves recents back to top if the user gestured before a switch
@@ -1038,11 +1217,16 @@
@Override
@SuppressLint("NewApi")
public void finish(boolean toHome, boolean sendUserLeaveHint, IResultReceiver finishCb) {
- mExecutor.execute(() -> finishInner(toHome, sendUserLeaveHint, finishCb));
+ mExecutor.execute(() -> finishInner(toHome, sendUserLeaveHint, finishCb,
+ "requested"));
}
private void finishInner(boolean toHome, boolean sendUserLeaveHint,
- IResultReceiver runnerFinishCb) {
+ IResultReceiver runnerFinishCb, String reason) {
+ if (finishSyntheticTransition()) {
+ return;
+ }
+
if (mFinishCB == null) {
Slog.e(TAG, "Duplicate call to finish");
return;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionStateListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionStateListener.java
index e8733eb..95874c8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionStateListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionStateListener.java
@@ -24,7 +24,4 @@
/** Notifies whether the recents animation is running. */
default void onAnimationStateChanged(boolean running) {
}
-
- /** Notifies that a recents shell transition has started. */
- default void onTransitionStarted(IBinder transition) {}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 4ba84ee..5a905cf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -1654,7 +1654,7 @@
mSplitLayout = new SplitLayout(TAG + "SplitDivider", mContext,
mRootTaskInfo.configuration, this, mParentContainerCallbacks,
mDisplayController, mDisplayImeController, mTaskOrganizer,
- PARALLAX_ALIGN_CENTER /* parallaxType */);
+ PARALLAX_ALIGN_CENTER /* parallaxType */, mMainHandler);
mDisplayInsetsController.addInsetsChangedListener(mDisplayId, mSplitLayout);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index 2c02d4f..d03832d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -1501,16 +1501,16 @@
* transition animation. The Transition system will apply it when
* finishCallback is called by the transition handler.
*/
- void onTransitionReady(@NonNull IBinder transition, @NonNull TransitionInfo info,
+ default void onTransitionReady(@NonNull IBinder transition, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction startTransaction,
- @NonNull SurfaceControl.Transaction finishTransaction);
+ @NonNull SurfaceControl.Transaction finishTransaction) {}
/**
* Called when the transition is starting to play. It isn't called for merged transitions.
*
* @param transition the unique token of this transition
*/
- void onTransitionStarting(@NonNull IBinder transition);
+ default void onTransitionStarting(@NonNull IBinder transition) {}
/**
* Called when a transition is merged into another transition. There won't be any following
@@ -1519,7 +1519,7 @@
* @param merged the unique token of the transition that's merged to another one
* @param playing the unique token of the transition that accepts the merge
*/
- void onTransitionMerged(@NonNull IBinder merged, @NonNull IBinder playing);
+ default void onTransitionMerged(@NonNull IBinder merged, @NonNull IBinder playing) {}
/**
* Called when the transition is finished. This isn't called for merged transitions.
@@ -1527,7 +1527,7 @@
* @param transition the unique token of this transition
* @param aborted {@code true} if this transition is aborted; {@code false} otherwise.
*/
- void onTransitionFinished(@NonNull IBinder transition, boolean aborted);
+ default void onTransitionFinished(@NonNull IBinder transition, boolean aborted) {}
}
@BinderThread
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index b14283f..dc27c82 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -111,6 +111,7 @@
import com.android.wm.shell.desktopmode.DesktopWallpaperActivity;
import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
+import com.android.wm.shell.shared.annotations.ShellMainThread;
import com.android.wm.shell.shared.desktopmode.DesktopModeFlags;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource;
@@ -154,7 +155,7 @@
private final ShellTaskOrganizer mTaskOrganizer;
private final ShellController mShellController;
private final Context mContext;
- private final Handler mMainHandler;
+ private final @ShellMainThread Handler mMainHandler;
private final @ShellBackgroundThread ShellExecutor mBgExecutor;
private final Choreographer mMainChoreographer;
private final DisplayController mDisplayController;
@@ -214,7 +215,7 @@
public DesktopModeWindowDecorViewModel(
Context context,
ShellExecutor shellExecutor,
- Handler mainHandler,
+ @ShellMainThread Handler mainHandler,
Choreographer mainChoreographer,
@ShellBackgroundThread ShellExecutor bgExecutor,
ShellInit shellInit,
@@ -270,7 +271,7 @@
DesktopModeWindowDecorViewModel(
Context context,
ShellExecutor shellExecutor,
- Handler mainHandler,
+ @ShellMainThread Handler mainHandler,
Choreographer mainChoreographer,
@ShellBackgroundThread ShellExecutor bgExecutor,
ShellInit shellInit,
@@ -495,7 +496,8 @@
return;
}
mInteractionJankMonitor.begin(
- decoration.mTaskSurface, mContext, Cuj.CUJ_DESKTOP_MODE_MAXIMIZE_WINDOW, source);
+ decoration.mTaskSurface, mContext, mMainHandler,
+ Cuj.CUJ_DESKTOP_MODE_MAXIMIZE_WINDOW, source);
mDesktopTasksController.toggleDesktopTaskSize(decoration.mTaskInfo);
decoration.closeHandleMenu();
decoration.closeMaximizeMenu();
@@ -512,7 +514,7 @@
Toast.makeText(mContext,
R.string.desktop_mode_non_resizable_snap_text, Toast.LENGTH_SHORT).show();
} else {
- mInteractionJankMonitor.begin(decoration.mTaskSurface, mContext,
+ mInteractionJankMonitor.begin(decoration.mTaskSurface, mContext, mMainHandler,
Cuj.CUJ_DESKTOP_MODE_SNAP_RESIZE, "maximize_menu_resizable");
mDesktopTasksController.snapToHalfScreen(
decoration.mTaskInfo,
@@ -548,7 +550,7 @@
return;
}
final WindowContainerTransaction wct = new WindowContainerTransaction();
- mInteractionJankMonitor.begin(decoration.mTaskSurface, mContext,
+ mInteractionJankMonitor.begin(decoration.mTaskSurface, mContext, mMainHandler,
CUJ_DESKTOP_MODE_ENTER_MODE_APP_HANDLE_MENU);
// App sometimes draws before the insets from WindowDecoration#relayout have
// been added, so they must be added here
@@ -1378,7 +1380,8 @@
mDragStartListener,
mTransitions,
mInteractionJankMonitor,
- mTransactionFactory);
+ mTransactionFactory,
+ mMainHandler);
windowDecoration.setTaskDragResizer(taskPositioner);
final DesktopModeTouchEventListener touchEventListener =
@@ -1602,7 +1605,8 @@
DragPositioningCallbackUtility.DragStartListener dragStartListener,
Transitions transitions,
InteractionJankMonitor interactionJankMonitor,
- Supplier<SurfaceControl.Transaction> transactionFactory) {
+ Supplier<SurfaceControl.Transaction> transactionFactory,
+ Handler handler) {
final TaskPositioner taskPositioner = DesktopModeStatus.isVeiledResizeEnabled()
? new VeiledResizeTaskPositioner(
taskOrganizer,
@@ -1610,7 +1614,8 @@
displayController,
dragStartListener,
transitions,
- interactionJankMonitor)
+ interactionJankMonitor,
+ handler)
: new FluidResizeTaskPositioner(
taskOrganizer,
transitions,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index d43ee44..b1fc55f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -753,9 +753,12 @@
final ActivityInfo activityInfo = pm.getActivityInfo(baseActivity, 0 /* flags */);
final IconProvider provider = new IconProvider(mContext);
final Drawable appIconDrawable = provider.getIcon(activityInfo);
+ final Drawable badgedAppIconDrawable = pm.getUserBadgedIcon(appIconDrawable,
+ UserHandle.of(mTaskInfo.userId));
final BaseIconFactory headerIconFactory = createIconFactory(mContext,
R.dimen.desktop_mode_caption_icon_radius);
- mAppIconBitmap = headerIconFactory.createScaledBitmap(appIconDrawable, MODE_DEFAULT);
+ mAppIconBitmap = headerIconFactory.createIconBitmap(badgedAppIconDrawable,
+ 1f /* scale */);
final BaseIconFactory resizeVeilIconFactory = createIconFactory(mContext,
R.dimen.desktop_mode_resize_veil_icon_size);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
index 5998155..6f3f411 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
@@ -24,6 +24,7 @@
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
+import android.os.Handler;
import android.os.IBinder;
import android.view.Surface;
import android.view.SurfaceControl;
@@ -37,6 +38,7 @@
import com.android.internal.jank.InteractionJankMonitor;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.shared.annotations.ShellMainThread;
import com.android.wm.shell.transition.Transitions;
import java.util.function.Supplier;
@@ -63,14 +65,17 @@
private int mCtrlType;
private boolean mIsResizingOrAnimatingResize;
@Surface.Rotation private int mRotation;
+ @ShellMainThread
+ private final Handler mHandler;
public VeiledResizeTaskPositioner(ShellTaskOrganizer taskOrganizer,
DesktopModeWindowDecoration windowDecoration,
DisplayController displayController,
DragPositioningCallbackUtility.DragStartListener dragStartListener,
- Transitions transitions, InteractionJankMonitor interactionJankMonitor) {
+ Transitions transitions, InteractionJankMonitor interactionJankMonitor,
+ @ShellMainThread Handler handler) {
this(taskOrganizer, windowDecoration, displayController, dragStartListener,
- SurfaceControl.Transaction::new, transitions, interactionJankMonitor);
+ SurfaceControl.Transaction::new, transitions, interactionJankMonitor, handler);
}
public VeiledResizeTaskPositioner(ShellTaskOrganizer taskOrganizer,
@@ -78,7 +83,7 @@
DisplayController displayController,
DragPositioningCallbackUtility.DragStartListener dragStartListener,
Supplier<SurfaceControl.Transaction> supplier, Transitions transitions,
- InteractionJankMonitor interactionJankMonitor) {
+ InteractionJankMonitor interactionJankMonitor, @ShellMainThread Handler handler) {
mDesktopWindowDecoration = windowDecoration;
mTaskOrganizer = taskOrganizer;
mDisplayController = displayController;
@@ -86,6 +91,7 @@
mTransactionSupplier = supplier;
mTransitions = transitions;
mInteractionJankMonitor = interactionJankMonitor;
+ mHandler = handler;
}
@Override
@@ -97,7 +103,7 @@
if (isResizing()) {
// Capture CUJ for re-sizing window in DW mode.
mInteractionJankMonitor.begin(mDesktopWindowDecoration.mTaskSurface,
- mDesktopWindowDecoration.mContext, CUJ_DESKTOP_MODE_RESIZE_WINDOW);
+ mDesktopWindowDecoration.mContext, mHandler, CUJ_DESKTOP_MODE_RESIZE_WINDOW);
if (!mDesktopWindowDecoration.mTaskInfo.isFocused) {
WindowContainerTransaction wct = new WindowContainerTransaction();
wct.reorder(mDesktopWindowDecoration.mTaskInfo.token, true);
@@ -131,7 +137,7 @@
} else if (mCtrlType == CTRL_TYPE_UNDEFINED) {
// Begin window drag CUJ instrumentation only when drag position moves.
mInteractionJankMonitor.begin(mDesktopWindowDecoration.mTaskSurface,
- mDesktopWindowDecoration.mContext, CUJ_DESKTOP_MODE_DRAG_WINDOW);
+ mDesktopWindowDecoration.mContext, mHandler, CUJ_DESKTOP_MODE_DRAG_WINDOW);
final SurfaceControl.Transaction t = mTransactionSupplier.get();
DragPositioningCallbackUtility.setPositionOnDrag(mDesktopWindowDecoration,
mRepositionTaskBounds, mTaskBoundsAtDragStart, mRepositionStartPoint, t, x, y);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
index b53ea38..227060d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
@@ -137,6 +137,8 @@
private Transitions mTransitions;
@Mock
private RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
+ @Mock
+ private Handler mHandler;
private BackAnimationController mController;
private TestableContentResolver mContentResolver;
@@ -161,13 +163,14 @@
mTestableLooper = TestableLooper.get(this);
mShellInit = spy(new ShellInit(mShellExecutor));
mDefaultCrossActivityBackAnimation = new DefaultCrossActivityBackAnimation(mContext,
- mAnimationBackground, mRootTaskDisplayAreaOrganizer);
- mCrossTaskBackAnimation = new CrossTaskBackAnimation(mContext, mAnimationBackground);
+ mAnimationBackground, mRootTaskDisplayAreaOrganizer, mHandler);
+ mCrossTaskBackAnimation = new CrossTaskBackAnimation(mContext, mAnimationBackground,
+ mHandler);
mShellBackAnimationRegistry =
new ShellBackAnimationRegistry(mDefaultCrossActivityBackAnimation,
mCrossTaskBackAnimation, /* dialogCloseAnimation= */ null,
new CustomCrossActivityBackAnimation(mContext, mAnimationBackground,
- mRootTaskDisplayAreaOrganizer),
+ mRootTaskDisplayAreaOrganizer, mHandler),
/* defaultBackToHomeAnimation= */ null);
mController =
new BackAnimationController(
@@ -181,7 +184,8 @@
mAnimationBackground,
mShellBackAnimationRegistry,
mShellCommandHandler,
- mTransitions);
+ mTransitions,
+ mHandler);
mShellInit.init();
mShellExecutor.flushAll();
mTouchableRegion = new Rect(0, 0, 100, 100);
@@ -344,7 +348,8 @@
mAnimationBackground,
mShellBackAnimationRegistry,
mShellCommandHandler,
- mTransitions);
+ mTransitions,
+ mHandler);
shellInit.init();
registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME);
@@ -898,7 +903,8 @@
new BackAnimationRunner(
mAnimatorCallback,
mBackAnimationRunner,
- mContext));
+ mContext,
+ mHandler));
}
private void unregisterAnimation(int type) {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomCrossActivityBackAnimationTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomCrossActivityBackAnimationTest.kt
index 080ad90..5b5ef6f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomCrossActivityBackAnimationTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomCrossActivityBackAnimationTest.kt
@@ -22,6 +22,7 @@
import android.graphics.Color
import android.graphics.Point
import android.graphics.Rect
+import android.os.Handler
import android.os.RemoteException
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
@@ -66,6 +67,7 @@
@Mock private lateinit var transitionAnimation: TransitionAnimation
@Mock private lateinit var appCompatTaskInfo: AppCompatTaskInfo
@Mock private lateinit var transaction: Transaction
+ @Mock private lateinit var handler: Handler
private lateinit var customCrossActivityBackAnimation: CustomCrossActivityBackAnimation
private lateinit var customAnimationLoader: CustomAnimationLoader
@@ -80,7 +82,8 @@
backAnimationBackground,
rootTaskDisplayAreaOrganizer,
transaction,
- customAnimationLoader
+ customAnimationLoader,
+ handler,
)
whenever(transitionAnimation.loadAppTransitionAnimation(eq(PACKAGE_NAME), eq(OPEN_RES_ID)))
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java
index f5847cc..cf69704 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java
@@ -25,6 +25,7 @@
import android.content.res.Configuration;
import android.graphics.Rect;
+import android.os.Handler;
import android.os.SystemClock;
import android.provider.DeviceConfig;
import android.view.InputDevice;
@@ -55,6 +56,7 @@
private @Mock DisplayController mDisplayController;
private @Mock DisplayImeController mDisplayImeController;
private @Mock ShellTaskOrganizer mTaskOrganizer;
+ private @Mock Handler mHandler;
private SplitLayout mSplitLayout;
private DividerView mDividerView;
@@ -65,7 +67,7 @@
Configuration configuration = getConfiguration();
mSplitLayout = new SplitLayout("TestSplitLayout", mContext, configuration,
mSplitLayoutHandler, mCallbacks, mDisplayController, mDisplayImeController,
- mTaskOrganizer, SplitLayout.PARALLAX_NONE);
+ mTaskOrganizer, SplitLayout.PARALLAX_NONE, mHandler);
SplitWindowManager splitWindowManager = new SplitWindowManager("TestSplitWindowManager",
mContext,
configuration, mCallbacks);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
index 82b3a7d..177e47a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
@@ -35,6 +35,7 @@
import android.app.ActivityManager;
import android.content.res.Configuration;
import android.graphics.Rect;
+import android.os.Handler;
import android.window.WindowContainerTransaction;
import androidx.test.annotation.UiThreadTest;
@@ -65,6 +66,7 @@
@Mock DisplayImeController mDisplayImeController;
@Mock ShellTaskOrganizer mTaskOrganizer;
@Mock WindowContainerTransaction mWct;
+ @Mock Handler mHandler;
@Captor ArgumentCaptor<Runnable> mRunnableCaptor;
private SplitLayout mSplitLayout;
@@ -80,7 +82,8 @@
mDisplayController,
mDisplayImeController,
mTaskOrganizer,
- SplitLayout.PARALLAX_NONE));
+ SplitLayout.PARALLAX_NONE,
+ mHandler));
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index b47201e..e610ebd 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -40,6 +40,7 @@
import android.graphics.PointF
import android.graphics.Rect
import android.os.Binder
+import android.os.Handler
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.SetFlagsRule
@@ -181,6 +182,7 @@
private lateinit var mockInteractionJankMonitor: InteractionJankMonitor
@Mock private lateinit var mockSurface: SurfaceControl
@Mock private lateinit var taskbarDesktopTaskListener: TaskbarDesktopTaskListener
+ @Mock private lateinit var mockHandler: Handler
private lateinit var mockitoSession: StaticMockitoSession
private lateinit var controller: DesktopTasksController
@@ -221,7 +223,8 @@
shellTaskOrganizer,
MAX_TASK_LIMIT,
mockInteractionJankMonitor,
- mContext)
+ mContext,
+ mockHandler)
whenever(shellTaskOrganizer.getRunningTasks(anyInt())).thenAnswer { runningTasks }
whenever(transitions.startTransition(anyInt(), any(), isNull())).thenAnswer { Binder() }
@@ -274,7 +277,9 @@
shellExecutor,
Optional.of(desktopTasksLimiter),
recentTasksController,
- mockInteractionJankMonitor)
+ mockInteractionJankMonitor,
+ mockHandler,
+ )
}
@After
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
index 2d0e428..61d03ca 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
@@ -18,6 +18,7 @@
import android.app.ActivityManager.RunningTaskInfo
import android.os.Binder
+import android.os.Handler
import android.platform.test.flag.junit.SetFlagsRule
import android.testing.AndroidTestingRunner
import android.view.Display.DEFAULT_DISPLAY
@@ -70,6 +71,7 @@
@Mock lateinit var shellTaskOrganizer: ShellTaskOrganizer
@Mock lateinit var transitions: Transitions
@Mock lateinit var interactionJankMonitor: InteractionJankMonitor
+ @Mock lateinit var handler: Handler
private lateinit var mockitoSession: StaticMockitoSession
private lateinit var desktopTasksLimiter: DesktopTasksLimiter
@@ -85,7 +87,7 @@
desktopTasksLimiter =
DesktopTasksLimiter(transitions, desktopTaskRepo, shellTaskOrganizer, MAX_TASK_LIMIT,
- interactionJankMonitor, mContext)
+ interactionJankMonitor, mContext, handler)
}
@After
@@ -97,7 +99,7 @@
fun createDesktopTasksLimiter_withZeroLimit_shouldThrow() {
assertFailsWith<IllegalArgumentException> {
DesktopTasksLimiter(transitions, desktopTaskRepo, shellTaskOrganizer, 0,
- interactionJankMonitor, mContext)
+ interactionJankMonitor, mContext, handler)
}
}
@@ -105,7 +107,7 @@
fun createDesktopTasksLimiter_withNegativeLimit_shouldThrow() {
assertFailsWith<IllegalArgumentException> {
DesktopTasksLimiter(transitions, desktopTaskRepo, shellTaskOrganizer, -5,
- interactionJankMonitor, mContext)
+ interactionJankMonitor, mContext, handler)
}
}
@@ -334,7 +336,7 @@
fun getTaskToMinimizeIfNeeded_tasksAboveLimit_otherLimit_returnsBackTask() {
desktopTasksLimiter =
DesktopTasksLimiter(transitions, desktopTaskRepo, shellTaskOrganizer, MAX_TASK_LIMIT2,
- interactionJankMonitor, mContext)
+ interactionJankMonitor, mContext, handler)
val tasks = (1..MAX_TASK_LIMIT2 + 1).map { setUpFreeformTask() }
val minimizedTask = desktopTasksLimiter.getTaskToMinimizeIfNeeded(
@@ -375,6 +377,7 @@
verify(interactionJankMonitor).begin(
any(),
eq(mContext),
+ eq(handler),
eq(CUJ_DESKTOP_MODE_MINIMIZE_WINDOW))
desktopTasksLimiter.getTransitionObserver().onTransitionFinished(
@@ -403,7 +406,9 @@
verify(interactionJankMonitor).begin(
any(),
eq(mContext),
- eq(CUJ_DESKTOP_MODE_MINIMIZE_WINDOW))
+ eq(handler),
+ eq(CUJ_DESKTOP_MODE_MINIMIZE_WINDOW),
+ )
desktopTasksLimiter.getTransitionObserver().onTransitionFinished(
transition,
@@ -432,6 +437,7 @@
verify(interactionJankMonitor).begin(
any(),
eq(mContext),
+ eq(handler),
eq(CUJ_DESKTOP_MODE_MINIMIZE_WINDOW))
desktopTasksLimiter.getTransitionObserver().onTransitionMerged(
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandlerTest.java
index e0463b4..fefa933 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandlerTest.java
@@ -34,6 +34,7 @@
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Point;
+import android.os.Handler;
import android.os.IBinder;
import android.util.DisplayMetrics;
import android.view.SurfaceControl;
@@ -81,6 +82,8 @@
Transitions.TransitionFinishCallback mTransitionFinishCallback;
@Mock
ShellExecutor mExecutor;
+ @Mock
+ Handler mHandler;
private Point mPoint;
private ExitDesktopTaskTransitionHandler mExitDesktopTaskTransitionHandler;
@@ -97,7 +100,7 @@
.thenReturn(getContext().getResources().getDisplayMetrics());
mExitDesktopTaskTransitionHandler = new ExitDesktopTaskTransitionHandler(mTransitions,
- mContext, mInteractionJankMonitor);
+ mContext, mInteractionJankMonitor, mHandler);
mPoint = new Point(0, 0);
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizerTest.java
index 9c7f723..9146906 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizerTest.java
@@ -37,6 +37,7 @@
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Binder;
+import android.os.Handler;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.Display;
@@ -100,6 +101,8 @@
OneHandedSettingsUtil mMockSettingsUitl;
@Mock
InteractionJankMonitor mJankMonitor;
+ @Mock
+ Handler mMockHandler;
List<DisplayAreaAppearedInfo> mDisplayAreaAppearedInfoList = new ArrayList<>();
@@ -142,7 +145,8 @@
mMockAnimationController,
mTutorialHandler,
mJankMonitor,
- mMockShellMainExecutor));
+ mMockShellMainExecutor,
+ mMockHandler));
for (int i = 0; i < DISPLAYAREA_INFO_COUNT; i++) {
mDisplayAreaAppearedInfoList.add(getDummyDisplayAreaInfo());
@@ -429,7 +433,8 @@
mMockAnimationController,
mTutorialHandler,
mJankMonitor,
- mMockShellMainExecutor));
+ mMockShellMainExecutor,
+ mMockHandler));
assertThat(testSpiedDisplayAreaOrganizer.isReady()).isFalse();
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
index f3944d5..9600351 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
@@ -40,6 +40,7 @@
import android.graphics.Point;
import android.graphics.Rect;
import android.os.Bundle;
+import android.os.Handler;
import android.os.RemoteException;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -115,6 +116,7 @@
@Mock private PipParamsChangedForwarder mMockPipParamsChangedForwarder;
@Mock private DisplayInsetsController mMockDisplayInsetsController;
@Mock private TabletopModeController mMockTabletopModeController;
+ @Mock private Handler mMockHandler;
@Mock private DisplayLayout mMockDisplayLayout1;
@Mock private DisplayLayout mMockDisplayLayout2;
@@ -138,7 +140,7 @@
mMockPipTransitionController, mMockWindowManagerShellWrapper,
mMockTaskStackListener, mMockPipParamsChangedForwarder,
mMockDisplayInsetsController, mMockTabletopModeController,
- mMockOneHandedController, mMockExecutor);
+ mMockOneHandedController, mMockExecutor, mMockHandler);
mShellInit.init();
when(mMockPipBoundsAlgorithm.getSnapAlgorithm()).thenReturn(mMockPipSnapAlgorithm);
when(mMockPipTouchHandler.getMotionHelper()).thenReturn(mMockPipMotionHelper);
@@ -230,7 +232,7 @@
mMockPipTransitionController, mMockWindowManagerShellWrapper,
mMockTaskStackListener, mMockPipParamsChangedForwarder,
mMockDisplayInsetsController, mMockTabletopModeController,
- mMockOneHandedController, mMockExecutor));
+ mMockOneHandedController, mMockExecutor, mMockHandler));
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java
new file mode 100644
index 0000000..769acf7
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.recents;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+
+import static org.junit.Assert.assertNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityTaskManager;
+import android.app.IApplicationThread;
+import android.app.KeyguardManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.platform.test.flag.junit.SetFlagsRule;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import com.android.dx.mockito.inline.extended.StaticMockitoSession;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.TestShellExecutor;
+import com.android.wm.shell.common.DisplayInsetsController;
+import com.android.wm.shell.common.TaskStackListenerImpl;
+import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
+import com.android.wm.shell.sysui.ShellCommandHandler;
+import com.android.wm.shell.sysui.ShellController;
+import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.transition.HomeTransitionObserver;
+import com.android.wm.shell.transition.Transitions;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.quality.Strictness;
+
+import java.util.Optional;
+
+/**
+ * Tests for {@link RecentTasksController}
+ *
+ * Usage: atest WMShellUnitTests:RecentsTransitionHandlerTest
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class RecentsTransitionHandlerTest extends ShellTestCase {
+
+ @Mock
+ private Context mContext;
+ @Mock
+ private TaskStackListenerImpl mTaskStackListener;
+ @Mock
+ private ShellCommandHandler mShellCommandHandler;
+ @Mock
+ private DesktopModeTaskRepository mDesktopModeTaskRepository;
+ @Mock
+ private ActivityTaskManager mActivityTaskManager;
+ @Mock
+ private DisplayInsetsController mDisplayInsetsController;
+ @Mock
+ private IRecentTasksListener mRecentTasksListener;
+ @Mock
+ private TaskStackTransitionObserver mTaskStackTransitionObserver;
+
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
+ private ShellTaskOrganizer mShellTaskOrganizer;
+ private RecentTasksController mRecentTasksController;
+ private RecentTasksController mRecentTasksControllerReal;
+ private RecentsTransitionHandler mRecentsTransitionHandler;
+ private ShellInit mShellInit;
+ private ShellController mShellController;
+ private TestShellExecutor mMainExecutor;
+ private static StaticMockitoSession sMockitoSession;
+
+ @Before
+ public void setUp() {
+ sMockitoSession = mockitoSession().initMocks(this).strictness(Strictness.LENIENT)
+ .mockStatic(DesktopModeStatus.class).startMocking();
+ ExtendedMockito.doReturn(true)
+ .when(() -> DesktopModeStatus.canEnterDesktopMode(any()));
+
+ mMainExecutor = new TestShellExecutor();
+ when(mContext.getPackageManager()).thenReturn(mock(PackageManager.class));
+ when(mContext.getSystemService(KeyguardManager.class))
+ .thenReturn(mock(KeyguardManager.class));
+ mShellInit = spy(new ShellInit(mMainExecutor));
+ mShellController = spy(new ShellController(mContext, mShellInit, mShellCommandHandler,
+ mDisplayInsetsController, mMainExecutor));
+ mRecentTasksControllerReal = new RecentTasksController(mContext, mShellInit,
+ mShellController, mShellCommandHandler, mTaskStackListener, mActivityTaskManager,
+ Optional.of(mDesktopModeTaskRepository), mTaskStackTransitionObserver,
+ mMainExecutor);
+ mRecentTasksController = spy(mRecentTasksControllerReal);
+ mShellTaskOrganizer = new ShellTaskOrganizer(mShellInit, mShellCommandHandler,
+ null /* sizeCompatUI */, Optional.empty(), Optional.of(mRecentTasksController),
+ mMainExecutor);
+
+ final Transitions transitions = mock(Transitions.class);
+ doReturn(mMainExecutor).when(transitions).getMainExecutor();
+ mRecentsTransitionHandler = new RecentsTransitionHandler(mShellInit, mShellTaskOrganizer,
+ transitions, mRecentTasksController, mock(HomeTransitionObserver.class));
+
+ mShellInit.init();
+ }
+
+ @After
+ public void tearDown() {
+ sMockitoSession.finishMocking();
+ }
+
+ @Test
+ public void testStartSyntheticRecentsTransition_callsOnAnimationStart() throws Exception {
+ final IRecentsAnimationRunner runner = mock(IRecentsAnimationRunner.class);
+ doReturn(new Binder()).when(runner).asBinder();
+ Bundle options = new Bundle();
+ options.putBoolean("is_synthetic_recents_transition", true);
+ IBinder transition = mRecentsTransitionHandler.startRecentsTransition(
+ mock(PendingIntent.class), new Intent(), options, mock(IApplicationThread.class),
+ runner);
+ verify(runner).onAnimationStart(any(), any(), any(), any(), any(), any());
+
+ // Finish and verify no transition remains
+ mRecentsTransitionHandler.findController(transition).finish(true /* toHome */,
+ false /* sendUserLeaveHint */, null /* finishCb */);
+ mMainExecutor.flushAll();
+ assertNull(mRecentsTransitionHandler.findController(transition));
+ }
+
+ @Test
+ public void testStartSyntheticRecentsTransition_callsOnAnimationCancel() throws Exception {
+ final IRecentsAnimationRunner runner = mock(IRecentsAnimationRunner.class);
+ doReturn(new Binder()).when(runner).asBinder();
+ Bundle options = new Bundle();
+ options.putBoolean("is_synthetic_recents_transition", true);
+ IBinder transition = mRecentsTransitionHandler.startRecentsTransition(
+ mock(PendingIntent.class), new Intent(), options, mock(IApplicationThread.class),
+ runner);
+ verify(runner).onAnimationStart(any(), any(), any(), any(), any(), any());
+
+ mRecentsTransitionHandler.findController(transition).cancel("test");
+ mMainExecutor.flushAll();
+ verify(runner).onAnimationCanceled(any(), any());
+ assertNull(mRecentsTransitionHandler.findController(transition));
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
index 61a725f..fec9e3e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
@@ -1211,7 +1211,7 @@
mTransactionPool, createTestDisplayController(), mMainExecutor,
mMainHandler, mAnimExecutor, mock(HomeTransitionObserver.class));
final RecentsTransitionHandler recentsHandler =
- new RecentsTransitionHandler(shellInit, transitions,
+ new RecentsTransitionHandler(shellInit, mock(ShellTaskOrganizer.class), transitions,
mock(RecentTasksController.class), mock(HomeTransitionObserver.class));
transitions.replaceDefaultHandlerForTest(mDefaultHandler);
shellInit.init();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
index a18fbf0..85bc7cc 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
@@ -247,7 +247,18 @@
whenever(mockDisplayController.getDisplayLayout(any())).thenReturn(mockDisplayLayout)
whenever(mockDisplayLayout.stableInsets()).thenReturn(STABLE_INSETS)
whenever(mockInputMonitorFactory.create(any(), any())).thenReturn(mockInputMonitor)
- whenever(mockTaskPositionerFactory.create(any(), any(), any(), any(), any(), any(), any()))
+ whenever(
+ mockTaskPositionerFactory.create(
+ any(),
+ any(),
+ any(),
+ any(),
+ any(),
+ any(),
+ any(),
+ any()
+ )
+ )
.thenReturn(mockTaskPositioner)
doReturn(mockToast).`when` { Toast.makeText(any(), anyInt(), anyInt()) }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
index 7784af6..ab41d9c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
@@ -21,6 +21,7 @@
import android.content.res.Resources
import android.graphics.Point
import android.graphics.Rect
+import android.os.Handler
import android.os.IBinder
import android.testing.AndroidTestingRunner
import android.view.Display
@@ -107,6 +108,8 @@
private lateinit var mockResources: Resources
@Mock
private lateinit var mockInteractionJankMonitor: InteractionJankMonitor
+ @Mock
+ private lateinit var mockHandler: Handler
private lateinit var taskPositioner: VeiledResizeTaskPositioner
@@ -155,7 +158,8 @@
mockDragStartListener,
mockTransactionFactory,
mockTransitions,
- mockInteractionJankMonitor
+ mockInteractionJankMonitor,
+ mockHandler,
)
}
diff --git a/libs/hwui/platform/host/thread/ThreadBase.h b/libs/hwui/platform/host/thread/ThreadBase.h
index d709430..b4e7bd3 100644
--- a/libs/hwui/platform/host/thread/ThreadBase.h
+++ b/libs/hwui/platform/host/thread/ThreadBase.h
@@ -48,7 +48,7 @@
nsecs_t nextWakeup = mQueue.nextWakeup(lock);
std::chrono::nanoseconds duration = std::chrono::nanoseconds::max();
if (nextWakeup < std::numeric_limits<nsecs_t>::max()) {
- int timeout = nextWakeup - WorkQueue::clock::now();
+ nsecs_t timeout = nextWakeup - WorkQueue::clock::now();
if (timeout < 0) timeout = 0;
duration = std::chrono::nanoseconds(timeout);
}
diff --git a/media/java/android/media/projection/MediaProjection.java b/media/java/android/media/projection/MediaProjection.java
index 3cc0ad2..31f8996 100644
--- a/media/java/android/media/projection/MediaProjection.java
+++ b/media/java/android/media/projection/MediaProjection.java
@@ -90,24 +90,24 @@
mDisplayManager = displayManager;
}
- /**
- * Register a listener to receive notifications about when the {@link MediaProjection} or captured
- * content changes state.
- *
- * <p>The callback must be registered before invoking {@link #createVirtualDisplay(String, int,
- * int, int, int, Surface, VirtualDisplay.Callback, Handler)} to ensure that any notifications on
- * the callback are not missed. The client must implement {@link Callback#onStop()} and clean up
- * any resources it is holding, e.g. the {@link VirtualDisplay} and {@link Surface}. This should
- * also update any application UI indicating the MediaProjection status as MediaProjection has
- * stopped.
- *
- * @param callback The callback to call.
- * @param handler The handler on which the callback should be invoked, or null if the callback
- * should be invoked on the calling thread's looper.
- * @throws NullPointerException If the given callback is null.
- * @see #unregisterCallback
- */
- public void registerCallback(@NonNull Callback callback, @Nullable Handler handler) {
+ /**
+ * Register a listener to receive notifications about when the {@link MediaProjection} or
+ * captured content changes state.
+ *
+ * <p>The callback must be registered before invoking {@link #createVirtualDisplay(String, int,
+ * int, int, int, Surface, VirtualDisplay.Callback, Handler)} to ensure that any notifications
+ * on the callback are not missed. The client must implement {@link Callback#onStop()} to
+ * properly handle MediaProjection clean up any resources it is holding, e.g. the {@link
+ * VirtualDisplay} and {@link Surface}. This should also update any application UI indicating
+ * the MediaProjection status as MediaProjection has stopped.
+ *
+ * @param callback The callback to call.
+ * @param handler The handler on which the callback should be invoked, or null if the callback
+ * should be invoked on the calling thread's looper.
+ * @throws NullPointerException If the given callback is null.
+ * @see #unregisterCallback
+ */
+ public void registerCallback(@NonNull Callback callback, @Nullable Handler handler) {
try {
final Callback c = Objects.requireNonNull(callback);
if (handler == null) {
@@ -313,7 +313,7 @@
*/
public abstract static class Callback {
/**
- * Called when the MediaProjection session is no longer valid.
+ * Called when the MediaProjection session has been stopped and is no longer valid.
*
* <p>Once a MediaProjection has been stopped, it's up to the application to release any
* resources it may be holding (e.g. releasing the {@link VirtualDisplay} and {@link
@@ -321,9 +321,9 @@
* it should be updated to indicate that MediaProjection is no longer active.
*
* <p>MediaProjection stopping can be a result of the system stopping the ongoing
- * MediaProjection due to various reasons, such as another MediaProjection session starting.
- * MediaProjection may also stop due to the user explicitly stopping ongoing MediaProjection
- * via any available system-level UI.
+ * MediaProjection due to various reasons, such as another MediaProjection session starting,
+ * a user stopping the session via UI affordances in system-level UI, or the screen being
+ * locked.
*
* <p>After this callback any call to {@link MediaProjection#createVirtualDisplay} will
* fail, even if no such {@link VirtualDisplay} was ever created for this MediaProjection
diff --git a/media/java/android/media/projection/MediaProjectionManager.java b/media/java/android/media/projection/MediaProjectionManager.java
index 03fd2c6..dc55e41 100644
--- a/media/java/android/media/projection/MediaProjectionManager.java
+++ b/media/java/android/media/projection/MediaProjectionManager.java
@@ -60,13 +60,14 @@
* <li>Register a {@link MediaProjection.Callback} by calling {@link
* MediaProjection#registerCallback(MediaProjection.Callback, Handler)}. This is required to
* receive notifications about when the {@link MediaProjection} or captured content changes
- * state. When receiving an `onStop()` callback, the client must clean up any resources it is
- * holding, e.g. the {@link VirtualDisplay} and {@link Surface}. The MediaProjection may
- * further no longer create any new {@link VirtualDisplay}s via {@link
- * MediaProjection#createVirtualDisplay(String, int, int, int, int, Surface,
- * VirtualDisplay.Callback, Handler)}. Note that the `onStop()` callback can be a result of
- * the system stopping MediaProjection due to various reasons or the user stopping the
- * MediaProjection via UI affordances in system-level UI.
+ * state. When receiving an `onStop()` callback the {@link MediaProjection} session has been
+ * finished and the client must clean up any resources it is holding, e.g. the {@link
+ * VirtualDisplay} and {@link Surface}. The MediaProjection may further no longer create any
+ * new {@link VirtualDisplay}s via {@link MediaProjection#createVirtualDisplay(String, int,
+ * int, int, int, Surface, VirtualDisplay.Callback, Handler)}. Note that the `onStop()`
+ * callback can be a result of the system stopping MediaProjection due to various reasons.
+ * This includes the user stopping the MediaProjection via UI affordances in system-level UI,
+ * the screen being locked, or another {@link MediaProjection} session starting.
* <li>Start the screen capture session for media projection by calling {@link
* MediaProjection#createVirtualDisplay(String, int, int, int, int, Surface,
* android.hardware.display.VirtualDisplay.Callback, Handler)}.
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index feee89a..0b094a2 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1409,6 +1409,8 @@
<string name="media_transfer_this_device_name">This phone</string>
<!-- Name of the tablet device. [CHAR LIMIT=30] -->
<string name="media_transfer_this_device_name_tablet">This tablet</string>
+ <!-- Name of the internal speaker. [CHAR LIMIT=30] -->
+ <string name="media_transfer_this_device_name_desktop">This computer (internal)</string>
<!-- Name of the default media output of the TV. [CHAR LIMIT=30] -->
<string name="media_transfer_this_device_name_tv">@string/tv_media_transfer_default</string>
<!-- Name of the internal mic. [CHAR LIMIT=30] -->
@@ -1639,6 +1641,12 @@
<!-- Name of the 3.5mm and usb audio device. [CHAR LIMIT=50] -->
<string name="media_transfer_wired_usb_device_name">Wired headphone</string>
+ <!-- Name of the 3.5mm headphone, used in desktop devices. [CHAR LIMIT=50] -->
+ <string name="media_transfer_headphone_name">Headphone</string>
+
+ <!-- Name of the usb audio device speaker, used in desktop devices. [CHAR LIMIT=50] -->
+ <string name="media_transfer_usb_speaker_name">USB speaker</string>
+
<!-- Name of the 3.5mm audio device mic. [CHAR LIMIT=50] -->
<string name="media_transfer_wired_device_mic_name">Mic jack</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java
index 148e164..c9f9d1b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java
@@ -151,7 +151,19 @@
Log.d(TAG, "The BluetoothLeBroadcastAssistant is null");
return;
}
- mService.addSource(sink, metadata, isGroupOp);
+ try {
+ mService.addSource(sink, metadata, isGroupOp);
+ } catch (IllegalStateException e) {
+ // BT will check callback registration before add source.
+ // If it throw callback exception when bt is disabled, then the failure is intended,
+ // just catch it here.
+ BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+ if (adapter != null && adapter.isEnabled()) {
+ throw e;
+ } else {
+ Log.d(TAG, "Catch addSource failure when bt is disabled: " + e);
+ }
+ }
}
/**
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
index 9eaf8d3..116de56 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
@@ -72,6 +72,8 @@
return context.getString(R.string.media_transfer_this_device_name_tv);
} else if (isTablet()) {
return context.getString(R.string.media_transfer_this_device_name_tablet);
+ } else if (inputRoutingEnabledAndIsDesktop()) {
+ return context.getString(R.string.media_transfer_this_device_name_desktop);
} else {
return context.getString(R.string.media_transfer_this_device_name);
}
@@ -85,10 +87,18 @@
switch (routeInfo.getType()) {
case TYPE_WIRED_HEADSET:
case TYPE_WIRED_HEADPHONES:
+ name =
+ inputRoutingEnabledAndIsDesktop()
+ ? context.getString(R.string.media_transfer_headphone_name)
+ : context.getString(R.string.media_transfer_wired_usb_device_name);
+ break;
case TYPE_USB_DEVICE:
case TYPE_USB_HEADSET:
case TYPE_USB_ACCESSORY:
- name = context.getString(R.string.media_transfer_wired_usb_device_name);
+ name =
+ inputRoutingEnabledAndIsDesktop()
+ ? context.getString(R.string.media_transfer_usb_speaker_name)
+ : context.getString(R.string.media_transfer_wired_usb_device_name);
break;
case TYPE_DOCK:
name = context.getString(R.string.media_transfer_dock_speaker_device_name);
@@ -139,6 +149,16 @@
.contains("tablet");
}
+ static boolean isDesktop() {
+ return Arrays.asList(SystemProperties.get("ro.build.characteristics").split(","))
+ .contains("desktop");
+ }
+
+ static boolean inputRoutingEnabledAndIsDesktop() {
+ return com.android.media.flags.Flags.enableAudioInputDeviceRoutingAndVolumeControl()
+ && isDesktop();
+ }
+
// MediaRoute2Info.getType was made public on API 34, but exists since API 30.
@SuppressWarnings("NewApi")
@Override
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java
index e2d58d6..23cfc01 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java
@@ -47,6 +47,7 @@
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
+import org.robolectric.shadows.ShadowSystemProperties;
@RunWith(RobolectricTestRunner.class)
public class PhoneMediaDeviceTest {
@@ -114,6 +115,31 @@
when(mInfo.getType()).thenReturn(TYPE_BUILTIN_SPEAKER);
+ assertThat(mPhoneMediaDevice.getName()).isEqualTo(getMediaTransferThisDeviceName(mContext));
+ }
+
+ @EnableFlags(Flags.FLAG_ENABLE_AUDIO_INPUT_DEVICE_ROUTING_AND_VOLUME_CONTROL)
+ @Test
+ public void getName_returnCorrectName_desktop() {
+ ShadowSystemProperties.override("ro.build.characteristics", "desktop");
+
+ when(mInfo.getType()).thenReturn(TYPE_WIRED_HEADPHONES);
+
+ assertThat(mPhoneMediaDevice.getName())
+ .isEqualTo(mContext.getString(R.string.media_transfer_headphone_name));
+
+ when(mInfo.getType()).thenReturn(TYPE_WIRED_HEADSET);
+
+ assertThat(mPhoneMediaDevice.getName())
+ .isEqualTo(mContext.getString(R.string.media_transfer_headphone_name));
+
+ when(mInfo.getType()).thenReturn(TYPE_USB_DEVICE);
+
+ assertThat(mPhoneMediaDevice.getName())
+ .isEqualTo(mContext.getString(R.string.media_transfer_usb_speaker_name));
+
+ when(mInfo.getType()).thenReturn(TYPE_BUILTIN_SPEAKER);
+
assertThat(mPhoneMediaDevice.getName())
.isEqualTo(getMediaTransferThisDeviceName(mContext));
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java b/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java
index afbe84c..fbce6ca 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java
@@ -261,22 +261,13 @@
public static HashMap<String, String> getAllFlags(IContentProvider provider) {
HashMap<String, String> allFlags = new HashMap<String, String>();
- try {
- Bundle args = new Bundle();
- args.putInt(Settings.CALL_METHOD_USER_KEY,
- ActivityManager.getService().getCurrentUser().id);
- Bundle b = provider.call(new AttributionSource(Process.myUid(),
- resolveCallingPackage(), null), Settings.AUTHORITY,
- Settings.CALL_METHOD_LIST_CONFIG, null, args);
- if (b != null) {
- Map<String, String> flagsToValues =
- (HashMap) b.getSerializable(Settings.NameValueTable.VALUE);
- allFlags.putAll(flagsToValues);
+ for (DeviceConfig.Properties properties : DeviceConfig.getAllProperties()) {
+ List<String> keys = new ArrayList<>(properties.getKeyset());
+ for (String flagName : properties.getKeyset()) {
+ String fullName = properties.getNamespace() + "/" + flagName;
+ allFlags.put(fullName, properties.getString(flagName, null));
}
- } catch (RemoteException e) {
- throw new RuntimeException("Failed in IPC", e);
}
-
return allFlags;
}
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 207c961..98c491b 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -538,6 +538,13 @@
}
flag {
+ name: "status_bar_connected_displays"
+ namespace: "systemui"
+ description: "Shows the status bar on connected displays"
+ bug: "362720336"
+}
+
+flag {
name: "status_bar_switch_to_spn_from_data_spn"
namespace: "systemui"
description: "Fix usage of the SPN broadcast extras"
@@ -548,13 +555,6 @@
}
flag {
- name: "haptic_volume_slider"
- namespace: "systemui"
- description: "Adds haptic feedback to the volume slider."
- bug: "316953430"
-}
-
-flag {
name: "new_volume_panel"
namespace: "systemui"
description: "Switches to the new volume panel (without Slices)."
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt
index 163b355..8321238 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt
@@ -16,7 +16,6 @@
package com.android.systemui.bouncer.ui.composable
-import android.view.HapticFeedbackConstants
import androidx.annotation.VisibleForTesting
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.AnimationVector1D
@@ -133,10 +132,7 @@
// Perform haptic feedback, but only if the current dot is not null, so we don't perform it
// when the UI first shows up or when the user lifts their pointer/finger.
if (currentDot != null) {
- view.performHapticFeedback(
- HapticFeedbackConstants.VIRTUAL_KEY,
- HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING,
- )
+ viewModel.performDotFeedback(view)
}
if (!isAnimationEnabled) {
@@ -206,10 +202,7 @@
// Show the failure animation if the user entered the wrong input.
LaunchedEffect(animateFailure) {
if (animateFailure) {
- showFailureAnimation(
- dots = dots,
- scalingAnimatables = dotScalingAnimatables,
- )
+ showFailureAnimation(dots = dots, scalingAnimatables = dotScalingAnimatables)
viewModel.onFailureAnimationShown()
}
}
@@ -358,15 +351,10 @@
(1 - checkNotNull(dotAppearMoveUpAnimatables[dot]).value) * initialOffset
drawCircle(
center =
- pixelOffset(
- dot,
- spacing,
- horizontalOffset,
- verticalOffset + appearOffset,
- ),
+ pixelOffset(dot, spacing, horizontalOffset, verticalOffset + appearOffset),
color =
dotColor.copy(alpha = checkNotNull(dotAppearFadeInAnimatables[dot]).value),
- radius = dotRadius * checkNotNull(dotScalingAnimatables[dot]).value
+ radius = dotRadius * checkNotNull(dotScalingAnimatables[dot]).value,
)
}
}
@@ -387,7 +375,7 @@
delayMillis = 33 * dot.y,
durationMillis = 450,
easing = Easings.LegacyDecelerate,
- )
+ ),
)
}
}
@@ -400,7 +388,7 @@
delayMillis = 0,
durationMillis = 450 + (33 * dot.y),
easing = Easings.StandardDecelerate,
- )
+ ),
)
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt
index 489e24e..0830c9b 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt
@@ -16,8 +16,8 @@
package com.android.systemui.bouncer.ui.composable
-import android.view.HapticFeedbackConstants
import android.view.MotionEvent
+import android.view.View
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.AnimationSpec
@@ -72,11 +72,7 @@
/** Renders the PIN button pad. */
@Composable
-fun PinPad(
- viewModel: PinBouncerViewModel,
- verticalSpacing: Dp,
- modifier: Modifier = Modifier,
-) {
+fun PinPad(viewModel: PinBouncerViewModel, verticalSpacing: Dp, modifier: Modifier = Modifier) {
DisposableEffect(Unit) { onDispose { viewModel.onHidden() } }
val isInputEnabled: Boolean by viewModel.isInputEnabled.collectAsStateWithLifecycle()
@@ -104,7 +100,7 @@
columns = columns,
verticalSpacing = verticalSpacing,
horizontalSpacing = calculateHorizontalSpacingBetweenColumns(gridWidth = 300.dp),
- modifier = modifier.focusRequester(focusRequester).sysuiResTag("pin_pad_grid")
+ modifier = modifier.focusRequester(focusRequester).sysuiResTag("pin_pad_grid"),
) {
repeat(9) { index ->
DigitButton(
@@ -126,10 +122,11 @@
),
isInputEnabled = isInputEnabled,
onClicked = viewModel::onBackspaceButtonClicked,
+ onPointerDown = viewModel::onBackspaceButtonPressed,
onLongPressed = viewModel::onBackspaceButtonLongPressed,
appearance = backspaceButtonAppearance,
scaling = buttonScaleAnimatables[9]::value,
- elementId = "delete_button"
+ elementId = "delete_button",
)
DigitButton(
@@ -138,7 +135,7 @@
onClicked = viewModel::onPinButtonClicked,
scaling = buttonScaleAnimatables[10]::value,
isAnimationEnabled = isDigitButtonAnimationEnabled,
- onPointerDown = viewModel::onDigitButtonDown
+ onPointerDown = viewModel::onDigitButtonDown,
)
ActionButton(
@@ -152,7 +149,7 @@
onClicked = viewModel::onAuthenticateButtonClicked,
appearance = confirmButtonAppearance,
scaling = buttonScaleAnimatables[11]::value,
- elementId = "key_enter"
+ elementId = "key_enter",
)
}
}
@@ -162,7 +159,7 @@
digit: Int,
isInputEnabled: Boolean,
onClicked: (Int) -> Unit,
- onPointerDown: () -> Unit,
+ onPointerDown: (View?) -> Unit,
scaling: () -> Float,
isAnimationEnabled: Boolean,
) {
@@ -178,7 +175,7 @@
val scale = if (isAnimationEnabled) scaling() else 1f
scaleX = scale
scaleY = scale
- }
+ },
) { contentColor ->
// TODO(b/281878426): once "color: () -> Color" (added to BasicText in aosp/2568972) makes
// it into Text, use that here, to animate more efficiently.
@@ -197,6 +194,7 @@
onClicked: () -> Unit,
elementId: String,
onLongPressed: (() -> Unit)? = null,
+ onPointerDown: ((View?) -> Unit)? = null,
appearance: ActionButtonAppearance,
scaling: () -> Float,
) {
@@ -222,18 +220,16 @@
foregroundColor = foregroundColor,
isAnimationEnabled = true,
elementId = elementId,
+ onPointerDown = onPointerDown,
modifier =
Modifier.graphicsLayer {
alpha = hiddenAlpha
val scale = scaling()
scaleX = scale
scaleY = scale
- }
+ },
) { contentColor ->
- Icon(
- icon = icon,
- tint = contentColor(),
- )
+ Icon(icon = icon, tint = contentColor())
}
}
@@ -247,22 +243,13 @@
modifier: Modifier = Modifier,
elementId: String? = null,
onLongPressed: (() -> Unit)? = null,
- onPointerDown: (() -> Unit)? = null,
+ onPointerDown: ((View?) -> Unit)? = null,
content: @Composable (contentColor: () -> Color) -> Unit,
) {
val interactionSource = remember { MutableInteractionSource() }
val isPressed by interactionSource.collectIsPressedAsState()
val indication = LocalIndication.current.takeUnless { isPressed }
-
val view = LocalView.current
- LaunchedEffect(isPressed) {
- if (isPressed) {
- view.performHapticFeedback(
- HapticFeedbackConstants.VIRTUAL_KEY,
- HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING,
- )
- }
- }
// Pin button animation specification is asymmetric: fast animation to the pressed state, and a
// slow animation upon release. Note that isPressed is guaranteed to be true for at least the
@@ -277,7 +264,7 @@
animateDpAsState(
if (isAnimationEnabled && isPressed) 24.dp else pinButtonMaxSize / 2,
label = "PinButton round corners",
- animationSpec = tween(animDurationMillis, easing = animEasing)
+ animationSpec = tween(animDurationMillis, easing = animEasing),
)
val colorAnimationSpec: AnimationSpec<Color> = tween(animDurationMillis, easing = animEasing)
val containerColor: Color by
@@ -287,7 +274,7 @@
else -> backgroundColor
},
label = "Pin button container color",
- animationSpec = colorAnimationSpec
+ animationSpec = colorAnimationSpec,
)
val contentColor =
animateColorAsState(
@@ -296,7 +283,7 @@
else -> foregroundColor
},
label = "Pin button container color",
- animationSpec = colorAnimationSpec
+ animationSpec = colorAnimationSpec,
)
Box(
@@ -319,11 +306,11 @@
interactionSource = interactionSource,
indication = indication,
onClick = onClicked,
- onLongClick = onLongPressed
+ onLongClick = onLongPressed,
)
.pointerInteropFilter { motionEvent ->
if (motionEvent.action == MotionEvent.ACTION_DOWN) {
- onPointerDown?.let { it() }
+ onPointerDown?.let { it(view) }
}
false
}
@@ -353,10 +340,7 @@
animatable.animateTo(
targetValue = 1f,
animationSpec =
- tween(
- durationMillis = pinButtonErrorRevertMs,
- easing = Easings.Legacy,
- ),
+ tween(durationMillis = pinButtonErrorRevertMs, easing = Easings.Legacy),
)
}
}
@@ -364,9 +348,7 @@
}
/** Returns the amount of horizontal spacing between columns, in dips. */
-private fun calculateHorizontalSpacingBetweenColumns(
- gridWidth: Dp,
-): Dp {
+private fun calculateHorizontalSpacingBetweenColumns(gridWidth: Dp): Dp {
return (gridWidth - (pinButtonMaxSize * columns)) / (columns - 1)
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/windowinsets/ScreenDecorProvider.kt b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/windowinsets/ScreenDecorProvider.kt
index 296fc27..dcf32b2 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/windowinsets/ScreenDecorProvider.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/windowinsets/ScreenDecorProvider.kt
@@ -16,15 +16,10 @@
package com.android.systemui.common.ui.compose.windowinsets
-import androidx.compose.foundation.layout.WindowInsets
-import androidx.compose.foundation.layout.asPaddingValues
-import androidx.compose.foundation.layout.displayCutout
-import androidx.compose.foundation.layout.systemBars
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.getValue
import androidx.compose.runtime.staticCompositionLocalOf
-import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
@@ -36,9 +31,6 @@
/** The corner radius in px of the current display. */
val LocalScreenCornerRadius = staticCompositionLocalOf { 0.dp }
-/** The screen height in px without accounting for any screen insets (cutouts, status/nav bars) */
-val LocalRawScreenHeight = staticCompositionLocalOf { 0f }
-
@Composable
fun ScreenDecorProvider(
displayCutout: StateFlow<DisplayCutout>,
@@ -48,22 +40,9 @@
val cutout by displayCutout.collectAsStateWithLifecycle()
val screenCornerRadiusDp = with(LocalDensity.current) { screenCornerRadius.toDp() }
- val density = LocalDensity.current
- val navBarHeight =
- with(density) { WindowInsets.systemBars.asPaddingValues().calculateBottomPadding().toPx() }
- val statusBarHeight = WindowInsets.systemBars.asPaddingValues().calculateTopPadding()
- val displayCutoutHeight = WindowInsets.displayCutout.asPaddingValues().calculateTopPadding()
- val screenHeight =
- with(density) {
- (LocalConfiguration.current.screenHeightDp.dp +
- maxOf(statusBarHeight, displayCutoutHeight))
- .toPx()
- } + navBarHeight
-
CompositionLocalProvider(
LocalScreenCornerRadius provides screenCornerRadiusDp,
LocalDisplayCutout provides cutout,
- LocalRawScreenHeight provides screenHeight,
) {
content()
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationStackNestedScrollConnection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationStackNestedScrollConnection.kt
index 897a861..a2ae8bb 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationStackNestedScrollConnection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationStackNestedScrollConnection.kt
@@ -24,9 +24,11 @@
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
+import androidx.compose.ui.platform.LocalConfiguration
+import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.IntOffset
+import androidx.compose.ui.unit.dp
import com.android.compose.nestedscroll.PriorityNestedScrollConnection
-import com.android.systemui.common.ui.compose.windowinsets.LocalRawScreenHeight
import kotlin.math.max
import kotlin.math.roundToInt
import kotlin.math.tanh
@@ -36,9 +38,10 @@
@Composable
fun Modifier.stackVerticalOverscroll(
coroutineScope: CoroutineScope,
- canScrollForward: () -> Boolean
+ canScrollForward: () -> Boolean,
): Modifier {
- val screenHeight = LocalRawScreenHeight.current
+ val screenHeight =
+ with(LocalDensity.current) { LocalConfiguration.current.screenHeightDp.dp.toPx() }
val overscrollOffset = remember { Animatable(0f) }
val stackNestedScrollConnection = remember {
NotificationStackNestedScrollConnection(
@@ -60,10 +63,10 @@
overscrollOffset.animateTo(
targetValue = 0f,
initialVelocity = velocityAvailable,
- animationSpec = tween()
+ animationSpec = tween(),
)
}
- }
+ },
)
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
index 91ecfc1..1b99a96 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
@@ -19,6 +19,7 @@
import android.util.Log
import androidx.compose.animation.core.Animatable
+import androidx.compose.animation.core.AnimationVector1D
import androidx.compose.animation.core.tween
import androidx.compose.foundation.ScrollState
import androidx.compose.foundation.background
@@ -29,6 +30,8 @@
import androidx.compose.foundation.gestures.scrollBy
import androidx.compose.foundation.gestures.scrollable
import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.absoluteOffset
@@ -36,9 +39,11 @@
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.imeAnimationTarget
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.systemBars
+import androidx.compose.foundation.layout.windowInsetsBottomHeight
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.MaterialTheme
@@ -68,6 +73,7 @@
import androidx.compose.ui.layout.onPlaced
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.layout.positionInWindow
+import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.unit.Dp
@@ -81,7 +87,6 @@
import com.android.compose.animation.scene.NestedScrollBehavior
import com.android.compose.animation.scene.SceneScope
import com.android.compose.modifiers.thenIf
-import com.android.systemui.common.ui.compose.windowinsets.LocalRawScreenHeight
import com.android.systemui.common.ui.compose.windowinsets.LocalScreenCornerRadius
import com.android.systemui.res.R
import com.android.systemui.scene.session.ui.composable.SaveableSession
@@ -96,6 +101,7 @@
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationTransitionThresholds.EXPANSION_FOR_MAX_SCRIM_ALPHA
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
import kotlin.math.roundToInt
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
object Notifications {
@@ -171,7 +177,7 @@
setCurrent = { scrollOffset = it },
min = minScrollOffset,
max = maxScrollOffset,
- delta
+ delta,
)
}
@@ -209,8 +215,8 @@
calculateHeadsUpPlaceholderYOffset(
scrollOffset.roundToInt(),
minScrollOffset.roundToInt(),
- stackScrollView.topHeadsUpHeight
- )
+ stackScrollView.topHeadsUpHeight,
+ ),
)
}
.thenIf(isHeadsUp) {
@@ -218,11 +224,8 @@
bottomBehavior = NestedScrollBehavior.EdgeAlways
)
.nestedScroll(nestedScrollConnection)
- .scrollable(
- orientation = Orientation.Vertical,
- state = scrollableState,
- )
- }
+ .scrollable(orientation = Orientation.Vertical, state = scrollableState)
+ },
)
}
@@ -259,6 +262,7 @@
* Adds the space where notification stack should appear in the scene, with a scrim and nested
* scrolling.
*/
+@OptIn(ExperimentalLayoutApi::class)
@Composable
fun SceneScope.NotificationScrollingStack(
shadeSession: SaveableSession,
@@ -291,7 +295,7 @@
val navBarHeight = WindowInsets.systemBars.asPaddingValues().calculateBottomPadding()
val bottomPadding = if (shouldReserveSpaceForNavBar) navBarHeight else 0.dp
- val screenHeight = LocalRawScreenHeight.current
+ val screenHeight = with(density) { LocalConfiguration.current.screenHeightDp.dp.toPx() }
/**
* The height in px of the contents of notification stack. Depending on the number of
@@ -325,6 +329,14 @@
screenHeight - maxScrimTop() - with(density) { navBarHeight.toPx() }
}
+ val isRemoteInputActive by viewModel.isRemoteInputActive.collectAsStateWithLifecycle(false)
+
+ // The bottom Y bound of the currently focused remote input notification.
+ val remoteInputRowBottom by viewModel.remoteInputRowBottomBound.collectAsStateWithLifecycle(0f)
+
+ // The top y bound of the IME.
+ val imeTop = remember { mutableFloatStateOf(0f) }
+
// we are not scrolled to the top unless the scrim is at its maximum offset.
LaunchedEffect(viewModel, scrimOffset) {
snapshotFlow { scrimOffset.value >= 0f }
@@ -342,15 +354,34 @@
LaunchedEffect(syntheticScroll, scrimOffset, scrollState) {
snapshotFlow { syntheticScroll.value }
.collect { delta ->
- val minOffset = minScrimOffset()
- if (scrimOffset.value > minOffset) {
- val remainingDelta = (minOffset - (scrimOffset.value - delta)).coerceAtLeast(0f)
- scrimOffset.snapTo((scrimOffset.value - delta).coerceAtLeast(minOffset))
- if (remainingDelta > 0f) {
- scrollState.scrollBy(remainingDelta)
- }
- } else {
- scrollState.scrollTo(delta.roundToInt())
+ scrollNotificationStack(
+ scope = coroutineScope,
+ delta = delta,
+ animate = false,
+ scrimOffset = scrimOffset,
+ minScrimOffset = minScrimOffset,
+ scrollState = scrollState,
+ )
+ }
+ }
+
+ // if remote input state changes, compare the row and IME's overlap and offset the scrim and
+ // placeholder accordingly.
+ LaunchedEffect(isRemoteInputActive, remoteInputRowBottom, imeTop) {
+ imeTop.floatValue = 0f
+ snapshotFlow { imeTop.floatValue }
+ .collect { imeTopValue ->
+ // only scroll the stack if ime value has been populated (ime placeholder has been
+ // composed at least once), and our remote input row overlaps with the ime bounds.
+ if (isRemoteInputActive && imeTopValue > 0f && remoteInputRowBottom > imeTopValue) {
+ scrollNotificationStack(
+ scope = coroutineScope,
+ delta = remoteInputRowBottom - imeTopValue,
+ animate = true,
+ scrimOffset = scrimOffset,
+ minScrimOffset = minScrimOffset,
+ scrollState = scrollState,
+ )
}
}
}
@@ -394,12 +425,12 @@
scrimOffset.value < 0 &&
layoutState.isTransitioning(
from = Scenes.Shade,
- to = Scenes.QuickSettings
+ to = Scenes.QuickSettings,
)
) {
IntOffset(
x = 0,
- y = (scrimOffset.value * (1 - shadeToQsFraction)).roundToInt()
+ y = (scrimOffset.value * (1 - shadeToQsFraction)).roundToInt(),
)
} else {
IntOffset(x = 0, y = scrimOffset.value.roundToInt())
@@ -458,13 +489,11 @@
.thenIf(shouldFillMaxSize) { Modifier.fillMaxSize() }
.debugBackground(viewModel, DEBUG_BOX_COLOR)
) {
- NotificationPlaceholder(
- stackScrollView = stackScrollView,
- viewModel = viewModel,
+ Column(
modifier =
Modifier.verticalNestedScrollToScene(
topBehavior = NestedScrollBehavior.EdgeWithPreview,
- isExternalOverscrollGesture = { isCurrentGestureOverscroll.value }
+ isExternalOverscrollGesture = { isCurrentGestureOverscroll.value },
)
.thenIf(shadeMode == ShadeMode.Single) {
Modifier.nestedScroll(scrimNestedScrollConnection)
@@ -473,18 +502,31 @@
.verticalScroll(scrollState)
.padding(top = topPadding)
.fillMaxWidth()
- .notificationStackHeight(
- view = stackScrollView,
- totalVerticalPadding = topPadding + bottomPadding,
- )
- .onSizeChanged { size -> stackHeight.intValue = size.height },
- )
+ ) {
+ NotificationPlaceholder(
+ stackScrollView = stackScrollView,
+ viewModel = viewModel,
+ modifier =
+ Modifier.notificationStackHeight(
+ view = stackScrollView,
+ totalVerticalPadding = topPadding + bottomPadding,
+ )
+ .onSizeChanged { size -> stackHeight.intValue = size.height },
+ )
+ Spacer(
+ modifier =
+ Modifier.windowInsetsBottomHeight(WindowInsets.imeAnimationTarget)
+ .onGloballyPositioned { coordinates: LayoutCoordinates ->
+ imeTop.floatValue = screenHeight - coordinates.size.height
+ }
+ )
+ }
}
if (shouldIncludeHeadsUpSpace) {
HeadsUpNotificationSpace(
stackScrollView = stackScrollView,
viewModel = viewModel,
- modifier = Modifier.padding(top = topPadding)
+ modifier = Modifier.padding(top = topPadding),
)
}
}
@@ -572,6 +614,42 @@
)
}
+private suspend fun scrollNotificationStack(
+ scope: CoroutineScope,
+ delta: Float,
+ animate: Boolean,
+ scrimOffset: Animatable<Float, AnimationVector1D>,
+ minScrimOffset: () -> Float,
+ scrollState: ScrollState,
+) {
+ val minOffset = minScrimOffset()
+ if (scrimOffset.value > minOffset) {
+ val remainingDelta =
+ (minOffset - (scrimOffset.value - delta)).coerceAtLeast(0f).roundToInt()
+ if (remainingDelta > 0) {
+ if (animate) {
+ // launch a new coroutine for the remainder animation so that it doesn't suspend the
+ // scrim animation, allowing both to play simultaneously.
+ scope.launch { scrollState.animateScrollTo(remainingDelta) }
+ } else {
+ scrollState.scrollTo(remainingDelta)
+ }
+ }
+ val newScrimOffset = (scrimOffset.value - delta).coerceAtLeast(minOffset)
+ if (animate) {
+ scrimOffset.animateTo(newScrimOffset)
+ } else {
+ scrimOffset.snapTo(newScrimOffset)
+ }
+ } else {
+ if (animate) {
+ scrollState.animateScrollBy(delta)
+ } else {
+ scrollState.scrollBy(delta)
+ }
+ }
+}
+
private fun calculateCornerRadius(
scrimCornerRadius: Dp,
screenCornerRadius: Dp,
@@ -618,7 +696,7 @@
setCurrent: (Float) -> Unit,
min: Float,
max: Float,
- delta: Float
+ delta: Float,
): Float {
return if (delta < 0 && current > min) {
val remainder = (current + delta - min).coerceAtMost(0f)
@@ -631,10 +709,7 @@
} else 0f
}
-private inline fun debugLog(
- viewModel: NotificationsPlaceholderViewModel,
- msg: () -> Any,
-) {
+private inline fun debugLog(viewModel: NotificationsPlaceholderViewModel, msg: () -> Any) {
if (viewModel.isDebugLoggingEnabled) {
Log.d(TAG, msg().toString())
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
index fa92bef34..0c1c165 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
@@ -61,6 +61,7 @@
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.layout.layoutId
+import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.res.colorResource
@@ -79,7 +80,6 @@
import com.android.systemui.battery.BatteryMeterViewController
import com.android.systemui.common.ui.compose.windowinsets.CutoutLocation
import com.android.systemui.common.ui.compose.windowinsets.LocalDisplayCutout
-import com.android.systemui.common.ui.compose.windowinsets.LocalRawScreenHeight
import com.android.systemui.compose.modifiers.sysuiResTag
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.lifecycle.ExclusiveActivatable
@@ -229,17 +229,16 @@
}
.thenIf(cutoutLocation != CutoutLocation.CENTER) { Modifier.displayCutoutPadding() }
) {
+ val density = LocalDensity.current
val isCustomizing by viewModel.qsSceneAdapter.isCustomizing.collectAsStateWithLifecycle()
val isCustomizerShowing by
viewModel.qsSceneAdapter.isCustomizerShowing.collectAsStateWithLifecycle()
val customizingAnimationDuration by
viewModel.qsSceneAdapter.customizerAnimationDuration.collectAsStateWithLifecycle()
- val screenHeight = LocalRawScreenHeight.current
+ val screenHeight = with(density) { LocalConfiguration.current.screenHeightDp.dp.toPx() }
BackHandler(enabled = isCustomizing) { viewModel.qsSceneAdapter.requestCloseCustomizer() }
- val collapsedHeaderHeight =
- with(LocalDensity.current) { ShadeHeader.Dimensions.CollapsedHeight.roundToPx() }
val lifecycleOwner = LocalLifecycleOwner.current
val footerActionsViewModel =
remember(lifecycleOwner, viewModel) {
@@ -268,7 +267,6 @@
val navBarBottomHeight =
WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding()
- val density = LocalDensity.current
val bottomPadding by
animateDpAsState(
targetValue = if (isCustomizing) 0.dp else navBarBottomHeight,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt
index deef652..9552564 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt
@@ -16,18 +16,26 @@
package com.android.systemui.bouncer.ui.viewmodel
+import android.platform.test.annotations.EnableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.keyguard.AuthInteractionProperties
+import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.haptics.msdl.bouncerHapticPlayer
+import com.android.systemui.haptics.msdl.fakeMSDLPlayer
import com.android.systemui.kosmos.testScope
import com.android.systemui.lifecycle.activateIn
import com.android.systemui.testKosmos
+import com.google.android.msdl.data.model.MSDLToken
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
@@ -39,11 +47,15 @@
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
+ private val msdlPlayer = kosmos.fakeMSDLPlayer
+ private val bouncerHapticPlayer = kosmos.bouncerHapticPlayer
+ private val authInteractionProperties = AuthInteractionProperties()
private val underTest =
kosmos.pinBouncerViewModelFactory.create(
isInputEnabled = MutableStateFlow(true),
onIntentionalUserInput = {},
authenticationMethod = AuthenticationMethodModel.Pin,
+ bouncerHapticPlayer = bouncerHapticPlayer,
)
@Before
@@ -77,4 +89,42 @@
underTest.onAuthenticateButtonClicked()
assertThat(animateFailure).isFalse()
}
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ @Test
+ @EnableFlags(Flags.FLAG_MSDL_FEEDBACK)
+ fun onAuthenticationResult_playUnlockTokenIfSuccessful() =
+ testScope.runTest {
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Pin
+ )
+ // Correct PIN:
+ FakeAuthenticationRepository.DEFAULT_PIN.forEach { digit ->
+ underTest.onPinButtonClicked(digit)
+ }
+ underTest.onAuthenticateButtonClicked()
+ runCurrent()
+
+ assertThat(msdlPlayer.latestTokenPlayed).isEqualTo(MSDLToken.UNLOCK)
+ assertThat(msdlPlayer.latestPropertiesPlayed).isEqualTo(authInteractionProperties)
+ }
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ @Test
+ @EnableFlags(Flags.FLAG_MSDL_FEEDBACK)
+ fun onAuthenticationResult_playFailureTokenIfFailure() =
+ testScope.runTest {
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Pin
+ )
+ // Wrong PIN:
+ FakeAuthenticationRepository.DEFAULT_PIN.drop(2).forEach { digit ->
+ underTest.onPinButtonClicked(digit)
+ }
+ underTest.onAuthenticateButtonClicked()
+ runCurrent()
+
+ assertThat(msdlPlayer.latestTokenPlayed).isEqualTo(MSDLToken.FAILURE)
+ assertThat(msdlPlayer.latestPropertiesPlayed).isEqualTo(authInteractionProperties)
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
index 7c773a9..c163c6f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
@@ -16,9 +16,11 @@
package com.android.systemui.bouncer.ui.viewmodel
+import android.platform.test.annotations.EnableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.SceneKey
+import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
import com.android.systemui.authentication.data.repository.authenticationRepository
@@ -27,12 +29,16 @@
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate as Point
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.haptics.msdl.FakeMSDLPlayer
+import com.android.systemui.haptics.msdl.bouncerHapticPlayer
+import com.android.systemui.haptics.msdl.fakeMSDLPlayer
import com.android.systemui.kosmos.testScope
import com.android.systemui.lifecycle.activateIn
import com.android.systemui.res.R
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.testKosmos
+import com.google.android.msdl.data.model.MSDLToken
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -55,10 +61,13 @@
private val authenticationInteractor by lazy { kosmos.authenticationInteractor }
private val sceneInteractor by lazy { kosmos.sceneInteractor }
private val bouncerViewModel by lazy { kosmos.bouncerSceneContentViewModel }
+ private val msdlPlayer: FakeMSDLPlayer = kosmos.fakeMSDLPlayer
+ private val bouncerHapticHelper = kosmos.bouncerHapticPlayer
private val underTest =
kosmos.patternBouncerViewModelFactory.create(
isInputEnabled = MutableStateFlow(true).asStateFlow(),
onIntentionalUserInput = {},
+ bouncerHapticPlayer = bouncerHapticHelper,
)
private val containerSize = 90 // px
@@ -115,10 +124,7 @@
.that(selectedDots)
.isEqualTo(
CORRECT_PATTERN.subList(0, index + 1).map {
- PatternDotViewModel(
- x = it.x,
- y = it.y,
- )
+ PatternDotViewModel(x = it.x, y = it.y)
}
)
assertWithMessage("Wrong current dot for index $index")
@@ -174,7 +180,7 @@
listOf(
PatternDotViewModel(0, 0),
PatternDotViewModel(1, 0),
- PatternDotViewModel(2, 0)
+ PatternDotViewModel(2, 0),
)
)
}
@@ -200,7 +206,7 @@
listOf(
PatternDotViewModel(1, 0),
PatternDotViewModel(1, 1),
- PatternDotViewModel(1, 2)
+ PatternDotViewModel(1, 2),
)
)
}
@@ -228,7 +234,7 @@
listOf(
PatternDotViewModel(2, 0),
PatternDotViewModel(1, 1),
- PatternDotViewModel(0, 2)
+ PatternDotViewModel(0, 2),
)
)
}
@@ -300,10 +306,7 @@
val attempts = FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT + 1
repeat(attempts) { attempt ->
underTest.onDragStart()
- CORRECT_PATTERN.subList(
- 0,
- kosmos.authenticationRepository.minPatternLength - 1,
- )
+ CORRECT_PATTERN.subList(0, kosmos.authenticationRepository.minPatternLength - 1)
.forEach { coordinate ->
underTest.onDrag(
xPx = 30f * coordinate.x + 15,
@@ -341,6 +344,16 @@
assertThat(authResult).isTrue()
}
+ @Test
+ @EnableFlags(Flags.FLAG_MSDL_FEEDBACK)
+ fun performDotFeedback_deliversDragToken() =
+ testScope.runTest {
+ underTest.performDotFeedback(null)
+
+ assertThat(msdlPlayer.latestTokenPlayed).isEqualTo(MSDLToken.DRAG_INDICATOR)
+ assertThat(msdlPlayer.latestPropertiesPlayed).isNull()
+ }
+
private fun dragOverCoordinates(vararg coordinatesDragged: Point) {
underTest.onDragStart()
coordinatesDragged.forEach(::dragToCoordinate)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
index 2ee4aee..af5f2ac 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
@@ -27,6 +27,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.SceneKey
+import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
@@ -35,12 +36,15 @@
import com.android.systemui.bouncer.data.repository.fakeSimBouncerRepository
import com.android.systemui.classifier.fakeFalsingCollector
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.haptics.msdl.bouncerHapticPlayer
+import com.android.systemui.haptics.msdl.fakeMSDLPlayer
import com.android.systemui.kosmos.testScope
import com.android.systemui.lifecycle.activateIn
import com.android.systemui.res.R
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.testKosmos
+import com.google.android.msdl.data.model.MSDLToken
import com.google.common.truth.Truth.assertThat
import kotlin.random.Random
import kotlin.random.nextInt
@@ -64,11 +68,14 @@
private val testScope = kosmos.testScope
private val sceneInteractor by lazy { kosmos.sceneInteractor }
private val authenticationInteractor by lazy { kosmos.authenticationInteractor }
+ private val msdlPlayer = kosmos.fakeMSDLPlayer
+ private val bouncerHapticPlayer = kosmos.bouncerHapticPlayer
private val underTest by lazy {
kosmos.pinBouncerViewModelFactory.create(
isInputEnabled = MutableStateFlow(true),
onIntentionalUserInput = {},
authenticationMethod = AuthenticationMethodModel.Pin,
+ bouncerHapticPlayer = bouncerHapticPlayer,
)
}
@@ -97,6 +104,7 @@
isInputEnabled = MutableStateFlow(true),
onIntentionalUserInput = {},
authenticationMethod = AuthenticationMethodModel.Sim,
+ bouncerHapticPlayer = bouncerHapticPlayer,
)
assertThat(underTest.isSimAreaVisible).isTrue()
@@ -122,6 +130,7 @@
isInputEnabled = MutableStateFlow(true),
onIntentionalUserInput = {},
authenticationMethod = AuthenticationMethodModel.Pin,
+ bouncerHapticPlayer = bouncerHapticPlayer,
)
kosmos.fakeAuthenticationRepository.setAutoConfirmFeatureEnabled(true)
val hintedPinLength by collectLastValue(underTest.hintedPinLength)
@@ -487,11 +496,39 @@
testScope.runTest {
lockDeviceAndOpenPinBouncer()
- underTest.onDigitButtonDown()
+ underTest.onDigitButtonDown(null)
assertTrue(kosmos.fakeFalsingCollector.wasLastGestureAvoided())
}
+ @Test
+ @EnableFlags(Flags.FLAG_MSDL_FEEDBACK)
+ fun onDigiButtonDown_deliversKeyStandardToken() =
+ testScope.runTest {
+ underTest.onDigitButtonDown(null)
+
+ assertThat(msdlPlayer.latestTokenPlayed).isEqualTo(MSDLToken.KEYPRESS_STANDARD)
+ assertThat(msdlPlayer.latestPropertiesPlayed).isNull()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_MSDL_FEEDBACK)
+ fun onBackspaceButtonPressed_deliversKeyDeleteToken() {
+ underTest.onBackspaceButtonPressed(null)
+
+ assertThat(msdlPlayer.latestTokenPlayed).isEqualTo(MSDLToken.KEYPRESS_DELETE)
+ assertThat(msdlPlayer.latestPropertiesPlayed).isNull()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_MSDL_FEEDBACK)
+ fun onBackspaceButtonLongPressed_deliversLongPressToken() {
+ underTest.onBackspaceButtonLongPressed()
+
+ assertThat(msdlPlayer.latestTokenPlayed).isEqualTo(MSDLToken.LONG_PRESS)
+ assertThat(msdlPlayer.latestPropertiesPlayed).isNull()
+ }
+
private fun TestScope.switchToScene(toScene: SceneKey) {
val currentScene by collectLastValue(sceneInteractor.currentScene)
val bouncerHidden = currentScene == Scenes.Bouncer && toScene != Scenes.Bouncer
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt
index c2acc5f..160865d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt
@@ -31,8 +31,11 @@
import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.fakeTrustRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.AuthenticationFlags
+import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
import com.android.systemui.kosmos.testScope
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
@@ -211,7 +214,7 @@
val deviceUnlockStatus by collectLastValue(underTest.deviceUnlockStatus)
kosmos.fakeUserRepository.setSelectedUserInfo(
primaryUser,
- SelectionStatus.SELECTION_COMPLETE
+ SelectionStatus.SELECTION_COMPLETE,
)
kosmos.fakeTrustRepository.setCurrentUserTrusted(true)
@@ -240,6 +243,49 @@
}
@Test
+ fun deviceUnlockStatus_becomesUnlocked_whenFingerprintUnlocked_whileDeviceAsleepInAod() =
+ testScope.runTest {
+ val deviceUnlockStatus by collectLastValue(underTest.deviceUnlockStatus)
+ assertThat(deviceUnlockStatus?.isUnlocked).isFalse()
+
+ kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ testScope = this,
+ )
+ kosmos.powerInteractor.setAsleepForTest()
+ runCurrent()
+
+ assertThat(deviceUnlockStatus?.isUnlocked).isFalse()
+
+ kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+ SuccessFingerprintAuthenticationStatus(0, true)
+ )
+ runCurrent()
+ assertThat(deviceUnlockStatus?.isUnlocked).isTrue()
+ }
+
+ @Test
+ fun deviceUnlockStatus_staysLocked_whenFingerprintUnlocked_whileDeviceAsleep() =
+ testScope.runTest {
+ val deviceUnlockStatus by collectLastValue(underTest.deviceUnlockStatus)
+ assertThat(deviceUnlockStatus?.isUnlocked).isFalse()
+ assertThat(kosmos.keyguardTransitionInteractor.getCurrentState())
+ .isEqualTo(KeyguardState.LOCKSCREEN)
+
+ kosmos.powerInteractor.setAsleepForTest()
+ runCurrent()
+
+ assertThat(deviceUnlockStatus?.isUnlocked).isFalse()
+
+ kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+ SuccessFingerprintAuthenticationStatus(0, true)
+ )
+ runCurrent()
+ assertThat(deviceUnlockStatus?.isUnlocked).isFalse()
+ }
+
+ @Test
fun deviceEntryRestrictionReason_whenFaceOrFingerprintOrTrust_alwaysNull() =
testScope.runTest {
kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false)
@@ -273,7 +319,7 @@
LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to
DeviceEntryRestrictionReason.UserLockdown,
LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW to
- DeviceEntryRestrictionReason.PolicyLockdown
+ DeviceEntryRestrictionReason.PolicyLockdown,
)
}
@@ -285,7 +331,7 @@
kosmos.fakeTrustRepository.setTrustUsuallyManaged(false)
kosmos.fakeSystemPropertiesHelper.set(
DeviceUnlockedInteractor.SYS_BOOT_REASON_PROP,
- "not mainline reboot"
+ "not mainline reboot",
)
runCurrent()
@@ -321,7 +367,7 @@
kosmos.fakeTrustRepository.setTrustUsuallyManaged(false)
kosmos.fakeSystemPropertiesHelper.set(
DeviceUnlockedInteractor.SYS_BOOT_REASON_PROP,
- "not mainline reboot"
+ "not mainline reboot",
)
runCurrent()
@@ -358,7 +404,7 @@
kosmos.fakeTrustRepository.setCurrentUserTrustManaged(false)
kosmos.fakeSystemPropertiesHelper.set(
DeviceUnlockedInteractor.SYS_BOOT_REASON_PROP,
- "not mainline reboot"
+ "not mainline reboot",
)
runCurrent()
@@ -394,12 +440,12 @@
collectLastValue(underTest.deviceEntryRestrictionReason)
kosmos.fakeSystemPropertiesHelper.set(
DeviceUnlockedInteractor.SYS_BOOT_REASON_PROP,
- DeviceUnlockedInteractor.REBOOT_MAINLINE_UPDATE
+ DeviceUnlockedInteractor.REBOOT_MAINLINE_UPDATE,
)
kosmos.fakeBiometricSettingsRepository.setAuthenticationFlags(
AuthenticationFlags(
userId = 1,
- flag = LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT
+ flag = LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT,
)
)
runCurrent()
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 686b518..366b55d 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
@@ -23,6 +23,7 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.ActivityTransitionAnimator
+import com.android.systemui.classifier.falsingManager
import com.android.systemui.haptics.fakeVibratorHelper
import com.android.systemui.kosmos.testScope
import com.android.systemui.log.core.FakeLogBuffer
@@ -68,11 +69,13 @@
vibratorHelper.primitiveDurations[VibrationEffect.Composition.PRIMITIVE_SPIN] = spinDuration
whenever(kosmos.keyguardStateController.isUnlocked).thenReturn(true)
+ kosmos.falsingManager.setFalseLongTap(false)
longPressEffect =
QSLongPressEffect(
vibratorHelper,
kosmos.keyguardStateController,
+ kosmos.falsingManager,
FakeLogBuffer.Factory.create(),
)
longPressEffect.callback = callback
@@ -180,11 +183,7 @@
// THEN the expected texture is played
val reverseHaptics =
- LongPressHapticBuilder.createReversedEffect(
- progress,
- lowTickDuration,
- effectDuration,
- )
+ LongPressHapticBuilder.createReversedEffect(progress, lowTickDuration, effectDuration)
assertThat(reverseHaptics).isNotNull()
assertThat(vibratorHelper.hasVibratedWithEffects(reverseHaptics!!)).isTrue()
}
@@ -224,6 +223,20 @@
}
@Test
+ fun onAnimationComplete_isFalseLongClick_effectEndsInIdleWithReset() =
+ testWhileInState(QSLongPressEffect.State.RUNNING_FORWARD) {
+ // GIVEN that the long-click is false
+ kosmos.falsingManager.setFalseLongTap(true)
+
+ // GIVEN that the animation completes
+ longPressEffect.handleAnimationComplete()
+
+ // THEN the long-press effect ends in the idle state and the properties are reset
+ assertThat(longPressEffect.state).isEqualTo(QSLongPressEffect.State.IDLE)
+ verify(callback, times(1)).onResetProperties()
+ }
+
+ @Test
fun onAnimationComplete_whenRunningBackwardsFromUp_endsWithFinishedReversingAndClick() =
testWhileInState(QSLongPressEffect.State.RUNNING_BACKWARDS_FROM_UP) {
// GIVEN that the animation completes
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/ui/viewmodel/KeyboardTouchpadTutorialViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/ui/viewmodel/KeyboardTouchpadTutorialViewModelTest.kt
index 0c716137..639737b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/ui/viewmodel/KeyboardTouchpadTutorialViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/ui/viewmodel/KeyboardTouchpadTutorialViewModelTest.kt
@@ -25,6 +25,7 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
+import com.android.systemui.inputdevice.tutorial.InputDeviceTutorialLogger
import com.android.systemui.inputdevice.tutorial.domain.interactor.KeyboardTouchpadConnectionInteractor
import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity.Companion.INTENT_TUTORIAL_TYPE_KEY
import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity.Companion.INTENT_TUTORIAL_TYPE_KEYBOARD
@@ -53,6 +54,7 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.mock
+import org.mockito.kotlin.mock
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@@ -81,6 +83,7 @@
Optional.of(kosmos.touchpadGesturesInteractor),
KeyboardTouchpadConnectionInteractor(keyboardRepo, touchpadRepo),
hasTouchpadTutorialScreens,
+ mock<InputDeviceTutorialLogger>(),
SavedStateHandle(mapOf(INTENT_TUTORIAL_TYPE_KEY to startingPeripheral))
)
lifecycle.addObserver(viewModel)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt
index 6c3c7ef..fcf4662 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt
@@ -16,7 +16,10 @@
*/
package com.android.systemui.keyguard.data.quickaffordance
+import android.app.Flags
import android.net.Uri
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
import android.provider.Settings
import android.provider.Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS
import android.provider.Settings.Global.ZEN_MODE_OFF
@@ -25,6 +28,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.settingslib.notification.modes.EnableZenModeDialog
+import com.android.settingslib.notification.modes.TestModeBuilder
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.Expandable
import com.android.systemui.common.shared.model.ContentDescription
@@ -35,7 +39,11 @@
import com.android.systemui.kosmos.testScope
import com.android.systemui.res.R
import com.android.systemui.settings.UserTracker
+import com.android.systemui.shared.settings.data.repository.secureSettingsRepository
import com.android.systemui.statusbar.policy.ZenModeController
+import com.android.systemui.statusbar.policy.data.repository.fakeDeviceProvisioningRepository
+import com.android.systemui.statusbar.policy.data.repository.fakeZenModeRepository
+import com.android.systemui.statusbar.policy.domain.interactor.zenModeInteractor
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.eq
@@ -43,6 +51,7 @@
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.settings.fakeSettings
import com.google.common.truth.Truth.assertThat
+import java.time.Duration
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
@@ -66,8 +75,13 @@
private val kosmos = testKosmos()
private val testDispatcher = kosmos.testDispatcher
private val testScope = kosmos.testScope
+
private val settings = kosmos.fakeSettings
+ private val zenModeRepository = kosmos.fakeZenModeRepository
+ private val deviceProvisioningRepository = kosmos.fakeDeviceProvisioningRepository
+ private val secureSettingsRepository = kosmos.secureSettingsRepository
+
@Mock private lateinit var zenModeController: ZenModeController
@Mock private lateinit var userTracker: UserTracker
@Mock private lateinit var conditionUri: Uri
@@ -85,17 +99,36 @@
DoNotDisturbQuickAffordanceConfig(
context,
zenModeController,
+ kosmos.zenModeInteractor,
settings,
userTracker,
testDispatcher,
+ testScope.backgroundScope,
conditionUri,
enableZenModeDialog,
)
}
@Test
+ @EnableFlags(Flags.FLAG_MODES_UI)
fun dndNotAvailable_pickerStateHidden() =
testScope.runTest {
+ deviceProvisioningRepository.setDeviceProvisioned(false)
+ runCurrent()
+
+ val result = underTest.getPickerScreenState()
+ runCurrent()
+
+ assertEquals(
+ KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice,
+ result,
+ )
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_MODES_UI)
+ fun controllerDndNotAvailable_pickerStateHidden() =
+ testScope.runTest {
// given
whenever(zenModeController.isZenAvailable).thenReturn(false)
@@ -105,13 +138,33 @@
// then
assertEquals(
KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice,
- result
+ result,
)
}
@Test
+ @EnableFlags(Flags.FLAG_MODES_UI)
fun dndAvailable_pickerStateVisible() =
testScope.runTest {
+ deviceProvisioningRepository.setDeviceProvisioned(true)
+ runCurrent()
+
+ val result = underTest.getPickerScreenState()
+ runCurrent()
+
+ assertThat(result)
+ .isInstanceOf(KeyguardQuickAffordanceConfig.PickerScreenState.Default::class.java)
+ val defaultPickerState =
+ result as KeyguardQuickAffordanceConfig.PickerScreenState.Default
+ assertThat(defaultPickerState.configureIntent).isNotNull()
+ assertThat(defaultPickerState.configureIntent?.action)
+ .isEqualTo(Settings.ACTION_ZEN_MODE_SETTINGS)
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_MODES_UI)
+ fun controllerDndAvailable_pickerStateVisible() =
+ testScope.runTest {
// given
whenever(zenModeController.isZenAvailable).thenReturn(true)
@@ -129,7 +182,27 @@
}
@Test
- fun onTriggered_dndModeIsNotZEN_MODE_OFF_setToZEN_MODE_OFF() =
+ @EnableFlags(Flags.FLAG_MODES_UI)
+ fun onTriggered_dndModeIsNotOff_setToOff() =
+ testScope.runTest {
+ val currentModes by collectLastValue(zenModeRepository.modes)
+
+ zenModeRepository.addMode(TestModeBuilder.MANUAL_DND_ACTIVE)
+ secureSettingsRepository.setInt(Settings.Secure.ZEN_DURATION, -2)
+ collectLastValue(underTest.lockScreenState)
+ runCurrent()
+
+ val result = underTest.onTriggered(null)
+ runCurrent()
+
+ val dndMode = currentModes!!.single()
+ assertThat(dndMode.isActive).isFalse()
+ assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled, result)
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_MODES_UI)
+ fun onTriggered_controllerDndModeIsNotZEN_MODE_OFF_setToZEN_MODE_OFF() =
testScope.runTest {
// given
whenever(zenModeController.isZenAvailable).thenReturn(true)
@@ -140,11 +213,12 @@
// when
val result = underTest.onTriggered(null)
+
verify(zenModeController)
.setZen(
spyZenMode.capture(),
spyConditionId.capture(),
- eq(DoNotDisturbQuickAffordanceConfig.TAG)
+ eq(DoNotDisturbQuickAffordanceConfig.TAG),
)
// then
@@ -154,7 +228,28 @@
}
@Test
- fun onTriggered_dndModeIsZEN_MODE_OFF_settingFOREVER_setZenWithoutCondition() =
+ @EnableFlags(Flags.FLAG_MODES_UI)
+ fun onTriggered_dndModeIsOff_settingFOREVER_setZenWithoutCondition() =
+ testScope.runTest {
+ val currentModes by collectLastValue(zenModeRepository.modes)
+
+ zenModeRepository.addMode(TestModeBuilder.MANUAL_DND_INACTIVE)
+ secureSettingsRepository.setInt(Settings.Secure.ZEN_DURATION, ZEN_DURATION_FOREVER)
+ collectLastValue(underTest.lockScreenState)
+ runCurrent()
+
+ val result = underTest.onTriggered(null)
+ runCurrent()
+
+ val dndMode = currentModes!!.single()
+ assertThat(dndMode.isActive).isTrue()
+ assertThat(zenModeRepository.getModeActiveDuration(dndMode.id)).isNull()
+ assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled, result)
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_MODES_UI)
+ fun onTriggered_controllerDndModeIsZEN_MODE_OFF_settingFOREVER_setZenWithoutCondition() =
testScope.runTest {
// given
whenever(zenModeController.isZenAvailable).thenReturn(true)
@@ -169,7 +264,7 @@
.setZen(
spyZenMode.capture(),
spyConditionId.capture(),
- eq(DoNotDisturbQuickAffordanceConfig.TAG)
+ eq(DoNotDisturbQuickAffordanceConfig.TAG),
)
// then
@@ -179,7 +274,27 @@
}
@Test
- fun onTriggered_dndZEN_MODE_OFF_settingNotFOREVERorPROMPT_zenWithCondition() =
+ @EnableFlags(Flags.FLAG_MODES_UI)
+ fun onTriggered_dndModeIsOff_settingNotFOREVERorPROMPT_dndWithDuration() =
+ testScope.runTest {
+ val currentModes by collectLastValue(zenModeRepository.modes)
+ zenModeRepository.addMode(TestModeBuilder.MANUAL_DND_INACTIVE)
+ secureSettingsRepository.setInt(Settings.Secure.ZEN_DURATION, -900)
+ runCurrent()
+
+ val result = underTest.onTriggered(null)
+ runCurrent()
+
+ assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled, result)
+ val dndMode = currentModes!!.single()
+ assertThat(dndMode.isActive).isTrue()
+ assertThat(zenModeRepository.getModeActiveDuration(dndMode.id))
+ .isEqualTo(Duration.ofMinutes(-900))
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_MODES_UI)
+ fun onTriggered_controllerDndZEN_MODE_OFF_settingNotFOREVERorPROMPT_zenWithCondition() =
testScope.runTest {
// given
whenever(zenModeController.isZenAvailable).thenReturn(true)
@@ -194,7 +309,7 @@
.setZen(
spyZenMode.capture(),
spyConditionId.capture(),
- eq(DoNotDisturbQuickAffordanceConfig.TAG)
+ eq(DoNotDisturbQuickAffordanceConfig.TAG),
)
// then
@@ -204,7 +319,28 @@
}
@Test
- fun onTriggered_dndModeIsZEN_MODE_OFF_settingIsPROMPT_showDialog() =
+ @EnableFlags(Flags.FLAG_MODES_UI)
+ fun onTriggered_dndModeIsOff_settingIsPROMPT_showDialog() =
+ testScope.runTest {
+ val expandable: Expandable = mock()
+ zenModeRepository.addMode(TestModeBuilder.MANUAL_DND_INACTIVE)
+ secureSettingsRepository.setInt(Settings.Secure.ZEN_DURATION, ZEN_DURATION_PROMPT)
+ whenever(enableZenModeDialog.createDialog()).thenReturn(mock())
+ collectLastValue(underTest.lockScreenState)
+ runCurrent()
+
+ val result = underTest.onTriggered(expandable)
+
+ assertTrue(result is KeyguardQuickAffordanceConfig.OnTriggeredResult.ShowDialog)
+ assertEquals(
+ expandable,
+ (result as KeyguardQuickAffordanceConfig.OnTriggeredResult.ShowDialog).expandable,
+ )
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_MODES_UI)
+ fun onTriggered_controllerDndModeIsZEN_MODE_OFF_settingIsPROMPT_showDialog() =
testScope.runTest {
// given
val expandable: Expandable = mock()
@@ -222,13 +358,31 @@
assertTrue(result is KeyguardQuickAffordanceConfig.OnTriggeredResult.ShowDialog)
assertEquals(
expandable,
- (result as KeyguardQuickAffordanceConfig.OnTriggeredResult.ShowDialog).expandable
+ (result as KeyguardQuickAffordanceConfig.OnTriggeredResult.ShowDialog).expandable,
)
}
@Test
+ @EnableFlags(Flags.FLAG_MODES_UI)
fun lockScreenState_dndAvailableStartsAsTrue_changeToFalse_StateIsHidden() =
testScope.runTest {
+ deviceProvisioningRepository.setDeviceProvisioned(true)
+ val valueSnapshot = collectLastValue(underTest.lockScreenState)
+ val secondLastValue = valueSnapshot()
+ runCurrent()
+
+ deviceProvisioningRepository.setDeviceProvisioned(false)
+ runCurrent()
+ val lastValue = valueSnapshot()
+
+ assertTrue(secondLastValue is KeyguardQuickAffordanceConfig.LockScreenState.Visible)
+ assertTrue(lastValue is KeyguardQuickAffordanceConfig.LockScreenState.Hidden)
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_MODES_UI)
+ fun lockScreenState_controllerDndAvailableStartsAsTrue_changeToFalse_StateIsHidden() =
+ testScope.runTest {
// given
whenever(zenModeController.isZenAvailable).thenReturn(true)
val callbackCaptor: ArgumentCaptor<ZenModeController.Callback> = argumentCaptor()
@@ -246,7 +400,44 @@
}
@Test
- fun lockScreenState_dndModeStartsAsZEN_MODE_OFF_changeToNotOFF_StateVisible() =
+ @EnableFlags(Flags.FLAG_MODES_UI)
+ fun lockScreenState_dndModeStartsAsOff_changeToOn_StateVisible() =
+ testScope.runTest {
+ val lockScreenState by collectLastValue(underTest.lockScreenState)
+
+ zenModeRepository.addMode(TestModeBuilder.MANUAL_DND_INACTIVE)
+ runCurrent()
+
+ assertThat(lockScreenState)
+ .isEqualTo(
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(
+ Icon.Resource(
+ R.drawable.qs_dnd_icon_off,
+ ContentDescription.Resource(R.string.dnd_is_off),
+ ),
+ ActivationState.Inactive,
+ )
+ )
+
+ zenModeRepository.removeMode(TestModeBuilder.MANUAL_DND_INACTIVE.id)
+ zenModeRepository.addMode(TestModeBuilder.MANUAL_DND_ACTIVE)
+ runCurrent()
+
+ assertThat(lockScreenState)
+ .isEqualTo(
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(
+ Icon.Resource(
+ R.drawable.qs_dnd_icon_on,
+ ContentDescription.Resource(R.string.dnd_is_on),
+ ),
+ ActivationState.Active,
+ )
+ )
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_MODES_UI)
+ fun lockScreenState_controllerDndModeStartsAsZEN_MODE_OFF_changeToNotOFF_StateVisible() =
testScope.runTest {
// given
whenever(zenModeController.isZenAvailable).thenReturn(true)
@@ -265,9 +456,9 @@
KeyguardQuickAffordanceConfig.LockScreenState.Visible(
Icon.Resource(
R.drawable.qs_dnd_icon_off,
- ContentDescription.Resource(R.string.dnd_is_off)
+ ContentDescription.Resource(R.string.dnd_is_off),
),
- ActivationState.Inactive
+ ActivationState.Inactive,
),
secondLastValue,
)
@@ -275,9 +466,9 @@
KeyguardQuickAffordanceConfig.LockScreenState.Visible(
Icon.Resource(
R.drawable.qs_dnd_icon_on,
- ContentDescription.Resource(R.string.dnd_is_on)
+ ContentDescription.Resource(R.string.dnd_is_on),
),
- ActivationState.Active
+ ActivationState.Active,
),
lastValue,
)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
index 763a1a9..3850891 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
@@ -27,6 +27,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.compose.animation.scene.OverlayKey
import com.android.compose.animation.scene.SceneKey
import com.android.internal.logging.uiEventLoggerFake
import com.android.internal.policy.IKeyguardDismissCallback
@@ -88,9 +89,11 @@
import com.android.systemui.scene.data.repository.Transition
import com.android.systemui.scene.domain.interactor.sceneBackInteractor
import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.model.Overlays
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.shared.model.fakeSceneDataSource
import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.shade.shared.flag.DualShade
import com.android.systemui.shared.system.QuickStepContract
import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor
@@ -161,6 +164,7 @@
}
@Test
+ @DisableFlags(DualShade.FLAG_NAME)
fun hydrateVisibility() =
testScope.runTest {
val currentDesiredSceneKey by collectLastValue(sceneInteractor.currentScene)
@@ -221,6 +225,87 @@
}
@Test
+ @EnableFlags(DualShade.FLAG_NAME)
+ fun hydrateVisibility_dualShade() =
+ testScope.runTest {
+ val currentDesiredSceneKey by collectLastValue(sceneInteractor.currentScene)
+ val currentDesiredOverlays by collectLastValue(sceneInteractor.currentOverlays)
+ val isVisible by collectLastValue(sceneInteractor.isVisible)
+ val transitionStateFlow =
+ prepareState(
+ authenticationMethod = AuthenticationMethodModel.Pin,
+ isDeviceUnlocked = true,
+ initialSceneKey = Scenes.Gone,
+ )
+ assertThat(currentDesiredSceneKey).isEqualTo(Scenes.Gone)
+ assertThat(currentDesiredOverlays).isEmpty()
+ assertThat(isVisible).isTrue()
+
+ underTest.start()
+ assertThat(isVisible).isFalse()
+
+ // Expand the notifications shade.
+ fakeSceneDataSource.pause()
+ sceneInteractor.showOverlay(Overlays.NotificationsShade, "reason")
+ transitionStateFlow.value =
+ ObservableTransitionState.Transition.ShowOrHideOverlay(
+ overlay = Overlays.NotificationsShade,
+ fromContent = Scenes.Gone,
+ toContent = Overlays.NotificationsShade,
+ currentScene = Scenes.Gone,
+ currentOverlays = flowOf(emptySet()),
+ progress = flowOf(0.5f),
+ isInitiatedByUserInput = false,
+ isUserInputOngoing = flowOf(false),
+ previewProgress = flowOf(0f),
+ isInPreviewStage = flowOf(false),
+ )
+ assertThat(isVisible).isTrue()
+ fakeSceneDataSource.unpause(expectedScene = Scenes.Gone)
+ transitionStateFlow.value =
+ ObservableTransitionState.Idle(
+ currentScene = Scenes.Gone,
+ currentOverlays = setOf(Overlays.NotificationsShade),
+ )
+ assertThat(isVisible).isTrue()
+
+ // Collapse the notifications shade.
+ fakeSceneDataSource.pause()
+ sceneInteractor.hideOverlay(Overlays.NotificationsShade, "reason")
+ transitionStateFlow.value =
+ ObservableTransitionState.Transition.ShowOrHideOverlay(
+ overlay = Overlays.NotificationsShade,
+ fromContent = Overlays.NotificationsShade,
+ toContent = Scenes.Gone,
+ currentScene = Scenes.Gone,
+ currentOverlays = flowOf(setOf(Overlays.NotificationsShade)),
+ progress = flowOf(0.5f),
+ isInitiatedByUserInput = false,
+ isUserInputOngoing = flowOf(false),
+ previewProgress = flowOf(0f),
+ isInPreviewStage = flowOf(false),
+ )
+ assertThat(isVisible).isTrue()
+ fakeSceneDataSource.unpause(expectedScene = Scenes.Gone)
+ transitionStateFlow.value =
+ ObservableTransitionState.Idle(
+ currentScene = Scenes.Gone,
+ currentOverlays = emptySet(),
+ )
+ assertThat(isVisible).isFalse()
+
+ kosmos.headsUpNotificationRepository.setNotifications(
+ buildNotificationRows(isPinned = true)
+ )
+ assertThat(isVisible).isTrue()
+
+ kosmos.headsUpNotificationRepository.setNotifications(
+ buildNotificationRows(isPinned = false)
+ )
+ assertThat(isVisible).isFalse()
+ }
+
+ @Test
fun hydrateVisibility_basedOnDeviceProvisioning() =
testScope.runTest {
val isVisible by collectLastValue(sceneInteractor.isVisible)
@@ -1621,6 +1706,7 @@
}
@Test
+ @DisableFlags(DualShade.FLAG_NAME)
fun hydrateInteractionState_whileLocked() =
testScope.runTest {
val transitionStateFlow = prepareState(initialSceneKey = Scenes.Lockscreen)
@@ -1707,6 +1793,7 @@
}
@Test
+ @DisableFlags(DualShade.FLAG_NAME)
fun hydrateInteractionState_whileUnlocked() =
testScope.runTest {
val transitionStateFlow =
@@ -1795,6 +1882,186 @@
}
@Test
+ @EnableFlags(DualShade.FLAG_NAME)
+ fun hydrateInteractionState_dualShade_whileLocked() =
+ testScope.runTest {
+ val currentDesiredOverlays by collectLastValue(sceneInteractor.currentOverlays)
+ val transitionStateFlow = prepareState(initialSceneKey = Scenes.Lockscreen)
+ underTest.start()
+ runCurrent()
+ verify(centralSurfaces).setInteracting(StatusBarManager.WINDOW_STATUS_BAR, true)
+ assertThat(currentDesiredOverlays).isEmpty()
+
+ clearInvocations(centralSurfaces)
+ emulateSceneTransition(
+ transitionStateFlow = transitionStateFlow,
+ toScene = Scenes.Bouncer,
+ verifyBeforeTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ verifyDuringTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ verifyAfterTransition = {
+ verify(centralSurfaces)
+ .setInteracting(StatusBarManager.WINDOW_STATUS_BAR, false)
+ },
+ )
+
+ clearInvocations(centralSurfaces)
+ emulateSceneTransition(
+ transitionStateFlow = transitionStateFlow,
+ toScene = Scenes.Lockscreen,
+ verifyBeforeTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ verifyDuringTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ verifyAfterTransition = {
+ verify(centralSurfaces).setInteracting(StatusBarManager.WINDOW_STATUS_BAR, true)
+ },
+ )
+
+ clearInvocations(centralSurfaces)
+ emulateOverlayTransition(
+ transitionStateFlow = transitionStateFlow,
+ toOverlay = Overlays.NotificationsShade,
+ verifyBeforeTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ verifyDuringTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ verifyAfterTransition = {
+ verify(centralSurfaces)
+ .setInteracting(StatusBarManager.WINDOW_STATUS_BAR, false)
+ },
+ )
+
+ clearInvocations(centralSurfaces)
+ emulateSceneTransition(
+ transitionStateFlow = transitionStateFlow,
+ toScene = Scenes.Lockscreen,
+ verifyBeforeTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ verifyDuringTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ verifyAfterTransition = {
+ verify(centralSurfaces).setInteracting(StatusBarManager.WINDOW_STATUS_BAR, true)
+ },
+ )
+
+ clearInvocations(centralSurfaces)
+ emulateOverlayTransition(
+ transitionStateFlow = transitionStateFlow,
+ toOverlay = Overlays.QuickSettingsShade,
+ verifyBeforeTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ verifyDuringTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ verifyAfterTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ )
+ }
+
+ @Test
+ @EnableFlags(DualShade.FLAG_NAME)
+ fun hydrateInteractionState_dualShade_whileUnlocked() =
+ testScope.runTest {
+ val currentDesiredOverlays by collectLastValue(sceneInteractor.currentOverlays)
+ val transitionStateFlow =
+ prepareState(
+ authenticationMethod = AuthenticationMethodModel.Pin,
+ isDeviceUnlocked = true,
+ initialSceneKey = Scenes.Gone,
+ )
+ underTest.start()
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ assertThat(currentDesiredOverlays).isEmpty()
+
+ clearInvocations(centralSurfaces)
+ emulateSceneTransition(
+ transitionStateFlow = transitionStateFlow,
+ toScene = Scenes.Bouncer,
+ verifyBeforeTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ verifyDuringTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ verifyAfterTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ )
+
+ clearInvocations(centralSurfaces)
+ emulateSceneTransition(
+ transitionStateFlow = transitionStateFlow,
+ toScene = Scenes.Lockscreen,
+ verifyBeforeTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ verifyDuringTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ verifyAfterTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ )
+
+ clearInvocations(centralSurfaces)
+ emulateSceneTransition(
+ transitionStateFlow = transitionStateFlow,
+ toScene = Scenes.Shade,
+ verifyBeforeTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ verifyDuringTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ verifyAfterTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ )
+
+ clearInvocations(centralSurfaces)
+ emulateSceneTransition(
+ transitionStateFlow = transitionStateFlow,
+ toScene = Scenes.Lockscreen,
+ verifyBeforeTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ verifyDuringTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ verifyAfterTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ )
+
+ clearInvocations(centralSurfaces)
+ emulateSceneTransition(
+ transitionStateFlow = transitionStateFlow,
+ toScene = Scenes.QuickSettings,
+ verifyBeforeTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ verifyDuringTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ verifyAfterTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ )
+ }
+
+ @Test
fun respondToFalsingDetections() =
testScope.runTest {
val currentScene by collectLastValue(sceneInteractor.currentScene)
@@ -2131,19 +2398,40 @@
verifyAfterTransition: (() -> Unit)? = null,
) {
val fromScene = sceneInteractor.currentScene.value
+ val fromOverlays = sceneInteractor.currentOverlays.value
sceneInteractor.changeScene(toScene, "reason")
runCurrent()
verifyBeforeTransition?.invoke()
transitionStateFlow.value =
- ObservableTransitionState.Transition(
- fromScene = fromScene,
- toScene = toScene,
- currentScene = flowOf(fromScene),
- progress = flowOf(0.5f),
- isInitiatedByUserInput = true,
- isUserInputOngoing = flowOf(true),
- )
+ if (fromOverlays.isEmpty()) {
+ // Regular scene-to-scene transition.
+ ObservableTransitionState.Transition.ChangeScene(
+ fromScene = fromScene,
+ toScene = toScene,
+ currentScene = flowOf(fromScene),
+ currentOverlays = fromOverlays,
+ progress = flowOf(0.5f),
+ isInitiatedByUserInput = true,
+ isUserInputOngoing = flowOf(true),
+ previewProgress = flowOf(0f),
+ isInPreviewStage = flowOf(false),
+ )
+ } else {
+ // An overlay is present; hide it.
+ ObservableTransitionState.Transition.ShowOrHideOverlay(
+ overlay = fromOverlays.first(),
+ fromContent = fromOverlays.first(),
+ toContent = toScene,
+ currentScene = fromScene,
+ currentOverlays = sceneInteractor.currentOverlays,
+ progress = flowOf(0.5f),
+ isInitiatedByUserInput = true,
+ isUserInputOngoing = flowOf(true),
+ previewProgress = flowOf(0f),
+ isInPreviewStage = flowOf(false),
+ )
+ }
runCurrent()
verifyDuringTransition?.invoke()
@@ -2152,6 +2440,60 @@
verifyAfterTransition?.invoke()
}
+ private fun TestScope.emulateOverlayTransition(
+ transitionStateFlow: MutableStateFlow<ObservableTransitionState>,
+ toOverlay: OverlayKey,
+ verifyBeforeTransition: (() -> Unit)? = null,
+ verifyDuringTransition: (() -> Unit)? = null,
+ verifyAfterTransition: (() -> Unit)? = null,
+ ) {
+ val fromScene = sceneInteractor.currentScene.value
+ val fromOverlays = sceneInteractor.currentOverlays.value
+ sceneInteractor.showOverlay(toOverlay, "reason")
+ runCurrent()
+ verifyBeforeTransition?.invoke()
+
+ transitionStateFlow.value =
+ if (fromOverlays.isEmpty()) {
+ // Show a new overlay.
+ ObservableTransitionState.Transition.ShowOrHideOverlay(
+ overlay = toOverlay,
+ fromContent = fromScene,
+ toContent = toOverlay,
+ currentScene = fromScene,
+ currentOverlays = sceneInteractor.currentOverlays,
+ progress = flowOf(0.5f),
+ isInitiatedByUserInput = true,
+ isUserInputOngoing = flowOf(true),
+ previewProgress = flowOf(0f),
+ isInPreviewStage = flowOf(false),
+ )
+ } else {
+ // Overlay-to-overlay transition.
+ ObservableTransitionState.Transition.ReplaceOverlay(
+ fromOverlay = fromOverlays.first(),
+ toOverlay = toOverlay,
+ currentScene = fromScene,
+ currentOverlays = sceneInteractor.currentOverlays,
+ progress = flowOf(0.5f),
+ isInitiatedByUserInput = true,
+ isUserInputOngoing = flowOf(true),
+ previewProgress = flowOf(0f),
+ isInPreviewStage = flowOf(false),
+ )
+ }
+ runCurrent()
+ verifyDuringTransition?.invoke()
+
+ transitionStateFlow.value =
+ ObservableTransitionState.Idle(
+ currentScene = fromScene,
+ currentOverlays = setOf(toOverlay),
+ )
+ runCurrent()
+ verifyAfterTransition?.invoke()
+ }
+
private fun TestScope.prepareState(
isDeviceUnlocked: Boolean = false,
isBypassEnabled: Boolean = false,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt
index fb32855..0f6dc07 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt
@@ -17,7 +17,9 @@
package com.android.systemui.statusbar.policy.domain.interactor
import android.app.AutomaticZenRule
+import android.app.Flags
import android.app.NotificationManager.Policy
+import android.platform.test.annotations.EnableFlags
import android.provider.Settings
import android.provider.Settings.Secure.ZEN_DURATION
import android.provider.Settings.Secure.ZEN_DURATION_FOREVER
@@ -32,6 +34,7 @@
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.testScope
import com.android.systemui.shared.settings.data.repository.secureSettingsRepository
+import com.android.systemui.statusbar.policy.data.repository.fakeDeviceProvisioningRepository
import com.android.systemui.statusbar.policy.data.repository.fakeZenModeRepository
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
@@ -50,10 +53,31 @@
private val testScope = kosmos.testScope
private val zenModeRepository = kosmos.fakeZenModeRepository
private val settingsRepository = kosmos.secureSettingsRepository
+ private val deviceProvisioningRepository = kosmos.fakeDeviceProvisioningRepository
private val underTest = kosmos.zenModeInteractor
@Test
+ fun isZenAvailable_off() =
+ testScope.runTest {
+ val isZenAvailable by collectLastValue(underTest.isZenAvailable)
+ deviceProvisioningRepository.setDeviceProvisioned(false)
+ runCurrent()
+
+ assertThat(isZenAvailable).isFalse()
+ }
+
+ @Test
+ fun isZenAvailable_on() =
+ testScope.runTest {
+ val isZenAvailable by collectLastValue(underTest.isZenAvailable)
+ deviceProvisioningRepository.setDeviceProvisioned(true)
+ runCurrent()
+
+ assertThat(isZenAvailable).isTrue()
+ }
+
+ @Test
fun isZenModeEnabled_off() =
testScope.runTest {
val enabled by collectLastValue(underTest.isZenModeEnabled)
@@ -337,4 +361,22 @@
runCurrent()
assertThat(mainActiveMode).isNull()
}
+
+ @Test
+ @EnableFlags(Flags.FLAG_MODES_UI)
+ fun dndMode_flows() =
+ testScope.runTest {
+ val dndMode by collectLastValue(underTest.dndMode)
+
+ zenModeRepository.addMode(TestModeBuilder.MANUAL_DND_INACTIVE)
+ runCurrent()
+
+ assertThat(dndMode!!.isActive).isFalse()
+
+ zenModeRepository.removeMode(TestModeBuilder.MANUAL_DND_INACTIVE.id)
+ zenModeRepository.addMode(TestModeBuilder.MANUAL_DND_ACTIVE)
+ runCurrent()
+
+ assertThat(dndMode!!.isActive).isTrue()
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt
index 21a45ec..9dcbe1b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt
@@ -616,6 +616,71 @@
}
@Test
+ fun angleDecreaseAfterCancelAnimation_emitsStartClosingEvent() {
+ setFoldState(folded = true)
+ sendHingeAngleEvent(0)
+ foldUpdates.clear()
+
+ setFoldState(folded = false)
+ rotationListener.value.onRotationChanged(1)
+ sendHingeAngleEvent(180)
+ screenOnStatusProvider.notifyScreenTurningOn()
+ screenOnStatusProvider.notifyScreenTurnedOn()
+
+ // Start folding
+ (180 downTo 60).forEach {
+ sendHingeAngleEvent(it)
+ }
+ // Stopped folding and simulate timeout in this posture
+ simulateTimeout()
+
+ assertThat(foldUpdates)
+ .containsExactly(
+ FOLD_UPDATE_START_OPENING, // unfolded
+ FOLD_UPDATE_FINISH_HALF_OPEN, // force-finished the animation because of rotation
+ FOLD_UPDATE_START_CLOSING, // start closing the device
+ FOLD_UPDATE_FINISH_HALF_OPEN, // finished closing and timed-out in this state
+ )
+
+ }
+
+ @Test
+ fun angleIncreaseDecreaseAfterHalfUnfold_emitsStartClosingEvent() {
+ setFoldState(folded = true)
+ sendHingeAngleEvent(0)
+ foldUpdates.clear()
+
+ setFoldState(folded = false)
+ sendHingeAngleEvent(90)
+ screenOnStatusProvider.notifyScreenTurningOn()
+ screenOnStatusProvider.notifyScreenTurnedOn()
+
+ // Stopped folding and simulate timeout in this posture
+ simulateTimeout()
+
+ // Unfold further
+ (90 until 180).forEach {
+ sendHingeAngleEvent(it)
+ }
+ // Start folding
+ (180 downTo 90).forEach {
+ sendHingeAngleEvent(it)
+ }
+
+ // Stopped folding and simulate timeout in this posture
+ simulateTimeout()
+
+ assertThat(foldUpdates)
+ .containsExactly(
+ FOLD_UPDATE_START_OPENING, // unfolded
+ FOLD_UPDATE_FINISH_HALF_OPEN, // force-finished the animation because of rotation
+ FOLD_UPDATE_START_CLOSING, // start closing the device
+ FOLD_UPDATE_FINISH_HALF_OPEN, // finished closing and timed-out in this state
+ )
+
+ }
+
+ @Test
fun onUnfold_onSmallScreen_emitsStartOpening() {
// the new display state might arrive later, so it shouldn't be used to decide to send the
// start opening event, but only for the closing.
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/helper/BouncerHapticPlayer.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/helper/BouncerHapticPlayer.kt
index 19e7537..b8c30fe 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/helper/BouncerHapticPlayer.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/helper/BouncerHapticPlayer.kt
@@ -76,11 +76,7 @@
/** Deliver MSDL feedback when the delete key of the pin bouncer is pressed */
fun playDeleteKeyPressFeedback() = msdlPlayer.get().playToken(MSDLToken.KEYPRESS_DELETE)
- /**
- * Deliver MSDL feedback when the delete key of the pin bouncer is long-pressed
- *
- * @return whether MSDL feedback is allowed to play.
- */
+ /** Deliver MSDL feedback when the delete key of the pin bouncer is long-pressed. */
fun playDeleteKeyLongPressedFeedback() = msdlPlayer.get().playToken(MSDLToken.LONG_PRESS)
/** Deliver MSDL feedback when a numpad key is pressed on the pin bouncer */
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt
index c67b354..873d1b3 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt
@@ -21,6 +21,7 @@
import com.android.systemui.authentication.domain.interactor.AuthenticationResult
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
+import com.android.systemui.bouncer.ui.helper.BouncerHapticPlayer
import com.android.systemui.lifecycle.ExclusiveActivatable
import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.channels.Channel
@@ -42,6 +43,7 @@
/** Name to use for performance tracing purposes. */
val traceName: String,
+ protected val bouncerHapticPlayer: BouncerHapticPlayer? = null,
) : ExclusiveActivatable() {
private val _animateFailure = MutableStateFlow(false)
@@ -80,6 +82,8 @@
return@collectLatest
}
+ performAuthenticationHapticFeedback(authenticationResult)
+
_animateFailure.value = authenticationResult != AuthenticationResult.SUCCEEDED
clearInput()
}
@@ -112,20 +116,23 @@
/** Returns the input entered so far. */
protected abstract fun getInput(): List<Any>
+ /** Perform authentication result haptics */
+ private fun performAuthenticationHapticFeedback(result: AuthenticationResult) {
+ if (result == AuthenticationResult.SKIPPED) return
+
+ bouncerHapticPlayer?.playAuthenticationFeedback(
+ authenticationSucceeded = result == AuthenticationResult.SUCCEEDED
+ )
+ }
+
/**
* Attempts to authenticate the user using the current input value.
*
* @see BouncerInteractor.authenticate
*/
- protected fun tryAuthenticate(
- input: List<Any> = getInput(),
- useAutoConfirm: Boolean = false,
- ) {
+ protected fun tryAuthenticate(input: List<Any> = getInput(), useAutoConfirm: Boolean = false) {
authenticationRequests.trySend(AuthenticationRequest(input, useAutoConfirm))
}
- private data class AuthenticationRequest(
- val input: List<Any>,
- val useAutoConfirm: Boolean,
- )
+ private data class AuthenticationRequest(val input: List<Any>, val useAutoConfirm: Boolean)
}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModel.kt
index 0aada06..0bcb58d 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModel.kt
@@ -30,6 +30,7 @@
import com.android.systemui.bouncer.domain.interactor.BouncerActionButtonInteractor
import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
import com.android.systemui.bouncer.shared.model.BouncerActionButtonModel
+import com.android.systemui.bouncer.ui.helper.BouncerHapticPlayer
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.common.shared.model.Text
import com.android.systemui.dagger.qualifiers.Application
@@ -61,6 +62,7 @@
private val pinViewModelFactory: PinBouncerViewModel.Factory,
private val patternViewModelFactory: PatternBouncerViewModel.Factory,
private val passwordViewModelFactory: PasswordBouncerViewModel.Factory,
+ private val bouncerHapticPlayer: BouncerHapticPlayer,
) : ExclusiveActivatable() {
private val _selectedUserImage = MutableStateFlow<Bitmap?>(null)
val selectedUserImage: StateFlow<Bitmap?> = _selectedUserImage.asStateFlow()
@@ -162,10 +164,7 @@
}
launch {
- combine(
- userSwitcher.users,
- userSwitcher.menu,
- ) { users, actions ->
+ combine(userSwitcher.users, userSwitcher.menu) { users, actions ->
users.map { user ->
UserSwitcherDropdownItemViewModel(
icon = Icon.Loaded(user.image, contentDescription = null),
@@ -178,7 +177,7 @@
icon =
Icon.Resource(
action.iconResourceId,
- contentDescription = null
+ contentDescription = null,
),
text = Text.Resource(action.textResourceId),
onClick = action.onClicked,
@@ -226,7 +225,7 @@
}
private fun getChildViewModel(
- authenticationMethod: AuthenticationMethodModel,
+ authenticationMethod: AuthenticationMethodModel
): AuthMethodBouncerViewModel? {
// If the current child view-model matches the authentication method, reuse it instead of
// creating a new instance.
@@ -241,12 +240,14 @@
authenticationMethod = authenticationMethod,
onIntentionalUserInput = ::onIntentionalUserInput,
isInputEnabled = isInputEnabled,
+ bouncerHapticPlayer = bouncerHapticPlayer,
)
is AuthenticationMethodModel.Sim ->
pinViewModelFactory.create(
authenticationMethod = authenticationMethod,
onIntentionalUserInput = ::onIntentionalUserInput,
isInputEnabled = isInputEnabled,
+ bouncerHapticPlayer = bouncerHapticPlayer,
)
is AuthenticationMethodModel.Password ->
passwordViewModelFactory.create(
@@ -257,6 +258,7 @@
patternViewModelFactory.create(
onIntentionalUserInput = ::onIntentionalUserInput,
isInputEnabled = isInputEnabled,
+ bouncerHapticPlayer = bouncerHapticPlayer,
)
else -> null
}
@@ -317,10 +319,7 @@
return when {
// The wipe dialog takes priority over the lockout dialog.
wipeText != null ->
- DialogViewModel(
- text = wipeText,
- onDismiss = { wipeDialogMessage.value = null },
- )
+ DialogViewModel(text = wipeText, onDismiss = { wipeDialogMessage.value = null })
lockoutText != null ->
DialogViewModel(
text = lockoutText,
@@ -338,7 +337,7 @@
fun onKeyEvent(keyEvent: KeyEvent): Boolean {
return (authMethodViewModel.value as? PinBouncerViewModel)?.onKeyEvent(
keyEvent.type,
- keyEvent.nativeKeyEvent.keyCode
+ keyEvent.nativeKeyEvent.keyCode,
) ?: false
}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt
index 0a866b4..158f102 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt
@@ -18,9 +18,11 @@
import android.content.Context
import android.util.TypedValue
+import android.view.View
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate
import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
+import com.android.systemui.bouncer.ui.helper.BouncerHapticPlayer
import com.android.systemui.res.R
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
@@ -35,7 +37,6 @@
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.launch
/** Holds UI state and handles user input for the pattern bouncer UI. */
@@ -44,6 +45,7 @@
constructor(
private val applicationContext: Context,
interactor: BouncerInteractor,
+ @Assisted bouncerHapticPlayer: BouncerHapticPlayer,
@Assisted isInputEnabled: StateFlow<Boolean>,
@Assisted private val onIntentionalUserInput: () -> Unit,
) :
@@ -51,6 +53,7 @@
interactor = interactor,
isInputEnabled = isInputEnabled,
traceName = "PatternBouncerViewModel",
+ bouncerHapticPlayer = bouncerHapticPlayer,
) {
/** The number of columns in the dot grid. */
@@ -190,14 +193,7 @@
private fun defaultDots(): List<PatternDotViewModel> {
return buildList {
(0 until columnCount).forEach { x ->
- (0 until rowCount).forEach { y ->
- add(
- PatternDotViewModel(
- x = x,
- y = y,
- )
- )
- }
+ (0 until rowCount).forEach { y -> add(PatternDotViewModel(x = x, y = y)) }
}
}
}
@@ -207,14 +203,17 @@
applicationContext.resources.getValue(
com.android.internal.R.dimen.lock_pattern_dot_hit_factor,
outValue,
- true
+ true,
)
max(min(outValue.float, 1f), MIN_DOT_HIT_FACTOR)
}
+ fun performDotFeedback(view: View?) = bouncerHapticPlayer?.playPatternDotFeedback(view)
+
@AssistedFactory
interface Factory {
fun create(
+ bouncerHapticPlayer: BouncerHapticPlayer,
isInputEnabled: StateFlow<Boolean>,
onIntentionalUserInput: () -> Unit,
): PatternBouncerViewModel
@@ -231,7 +230,7 @@
*/
private fun PatternDotViewModel.isOnLineSegment(
first: PatternDotViewModel,
- second: PatternDotViewModel
+ second: PatternDotViewModel,
): Boolean {
val anotherPoint = this
// No need to consider any points outside the bounds of two end points
@@ -253,14 +252,8 @@
return (this in a..b) || (this in b..a)
}
-data class PatternDotViewModel(
- val x: Int,
- val y: Int,
-) {
+data class PatternDotViewModel(val x: Int, val y: Int) {
fun toCoordinate(): AuthenticationPatternCoordinate {
- return AuthenticationPatternCoordinate(
- x = x,
- y = y,
- )
+ return AuthenticationPatternCoordinate(x = x, y = y)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
index da29c62..0cb4260 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
@@ -19,12 +19,14 @@
package com.android.systemui.bouncer.ui.viewmodel
import android.content.Context
+import android.view.HapticFeedbackConstants
import android.view.KeyEvent.KEYCODE_0
import android.view.KeyEvent.KEYCODE_9
import android.view.KeyEvent.KEYCODE_DEL
import android.view.KeyEvent.KEYCODE_NUMPAD_0
import android.view.KeyEvent.KEYCODE_NUMPAD_9
import android.view.KeyEvent.isConfirmKey
+import android.view.View
import androidx.compose.ui.input.key.KeyEvent
import androidx.compose.ui.input.key.KeyEventType
import com.android.keyguard.PinShapeAdapter
@@ -32,6 +34,7 @@
import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
import com.android.systemui.bouncer.domain.interactor.SimBouncerInteractor
import com.android.systemui.bouncer.shared.flag.ComposeBouncerFlags
+import com.android.systemui.bouncer.ui.helper.BouncerHapticPlayer
import com.android.systemui.res.R
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
@@ -56,6 +59,7 @@
applicationContext: Context,
interactor: BouncerInteractor,
private val simBouncerInteractor: SimBouncerInteractor,
+ @Assisted bouncerHapticPlayer: BouncerHapticPlayer,
@Assisted isInputEnabled: StateFlow<Boolean>,
@Assisted private val onIntentionalUserInput: () -> Unit,
@Assisted override val authenticationMethod: AuthenticationMethodModel,
@@ -64,6 +68,7 @@
interactor = interactor,
isInputEnabled = isInputEnabled,
traceName = "PinBouncerViewModel",
+ bouncerHapticPlayer = bouncerHapticPlayer,
) {
/**
* Whether the sim-related UI in the pin view is showing.
@@ -126,10 +131,9 @@
.collect { _hintedPinLength.value = it }
}
launch {
- combine(
- mutablePinInput,
- interactor.isAutoConfirmEnabled,
- ) { mutablePinEntries, isAutoConfirmEnabled ->
+ combine(mutablePinInput, interactor.isAutoConfirmEnabled) {
+ mutablePinEntries,
+ isAutoConfirmEnabled ->
computeBackspaceButtonAppearance(
pinInput = mutablePinEntries,
isAutoConfirmEnabled = isAutoConfirmEnabled,
@@ -183,8 +187,22 @@
mutablePinInput.value = mutablePinInput.value.deleteLast()
}
+ fun onBackspaceButtonPressed(view: View?) {
+ if (bouncerHapticPlayer?.isEnabled == true) {
+ bouncerHapticPlayer.playDeleteKeyPressFeedback()
+ } else {
+ view?.performHapticFeedback(
+ HapticFeedbackConstants.VIRTUAL_KEY,
+ HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING,
+ )
+ }
+ }
+
/** Notifies that the user long-pressed the backspace button. */
fun onBackspaceButtonLongPressed() {
+ if (bouncerHapticPlayer?.isEnabled == true) {
+ bouncerHapticPlayer.playDeleteKeyLongPressedFeedback()
+ }
clearInput()
}
@@ -266,13 +284,24 @@
}
}
- /** Notifies that the user has pressed down on a digit button. */
- fun onDigitButtonDown() {
+ /**
+ * Notifies that the user has pressed down on a digit button. This function also performs haptic
+ * feedback on the view.
+ */
+ fun onDigitButtonDown(view: View?) {
if (ComposeBouncerFlags.isOnlyComposeBouncerEnabled()) {
// Current PIN bouncer informs FalsingInteractor#avoidGesture() upon every Pin button
// touch.
super.onDown()
}
+ if (bouncerHapticPlayer?.isEnabled == true) {
+ bouncerHapticPlayer.playNumpadKeyFeedback()
+ } else {
+ view?.performHapticFeedback(
+ HapticFeedbackConstants.VIRTUAL_KEY,
+ HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING,
+ )
+ }
}
@AssistedFactory
@@ -281,6 +310,7 @@
isInputEnabled: StateFlow<Boolean>,
onIntentionalUserInput: () -> Unit,
authenticationMethod: AuthenticationMethodModel,
+ bouncerHapticPlayer: BouncerHapticPlayer,
): PinBouncerViewModel
}
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java
index c7a47b1..1ada56d 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java
@@ -30,6 +30,7 @@
import android.content.ClipboardManager;
import android.content.Context;
import android.os.Build;
+import android.os.UserHandle;
import android.provider.Settings;
import android.util.Log;
@@ -37,6 +38,7 @@
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.CoreStartable;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.user.utils.UserScopedService;
import javax.inject.Inject;
import javax.inject.Provider;
@@ -67,13 +69,13 @@
public ClipboardListener(Context context,
Provider<ClipboardOverlayController> clipboardOverlayControllerProvider,
ClipboardToast clipboardToast,
- ClipboardManager clipboardManager,
+ UserScopedService<ClipboardManager> clipboardManager,
KeyguardManager keyguardManager,
UiEventLogger uiEventLogger) {
mContext = context;
mOverlayProvider = clipboardOverlayControllerProvider;
mClipboardToast = clipboardToast;
- mClipboardManager = clipboardManager;
+ mClipboardManager = clipboardManager.forUser(UserHandle.CURRENT);
mKeyguardManager = keyguardManager;
mUiEventLogger = uiEventLogger;
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
index 8818c3a..8f913ff 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
@@ -733,9 +733,8 @@
}
@Provides
- @Singleton
- static ClipboardManager provideClipboardManager(Context context) {
- return context.getSystemService(ClipboardManager.class);
+ static UserScopedService<ClipboardManager> provideClipboardManager(Context context) {
+ return new UserScopedServiceImpl<>(context, ClipboardManager.class);
}
@Provides
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt
index e17e530..5259c5d 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt
@@ -26,7 +26,9 @@
import com.android.systemui.deviceentry.shared.model.DeviceUnlockSource
import com.android.systemui.deviceentry.shared.model.DeviceUnlockStatus
import com.android.systemui.flags.SystemPropertiesHelper
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.domain.interactor.TrustInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated
import javax.inject.Inject
@@ -36,6 +38,7 @@
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
@@ -57,6 +60,7 @@
private val powerInteractor: PowerInteractor,
private val biometricSettingsInteractor: DeviceEntryBiometricSettingsInteractor,
private val systemPropertiesHelper: SystemPropertiesHelper,
+ keyguardTransitionInteractor: KeyguardTransitionInteractor,
) {
private val deviceUnlockSource =
@@ -74,7 +78,7 @@
trustInteractor.isTrusted.filter { it }.map { DeviceUnlockSource.TrustAgent },
authenticationInteractor.onAuthenticationResult
.filter { it }
- .map { DeviceUnlockSource.BouncerInput }
+ .map { DeviceUnlockSource.BouncerInput },
)
private val faceEnrolledAndEnabled = biometricSettingsInteractor.isFaceAuthEnrolledAndEnabled
@@ -170,10 +174,20 @@
combine(
powerInteractor.isAsleep,
isInLockdown,
- ::Pair,
+ keyguardTransitionInteractor
+ .transitionValue(KeyguardState.AOD)
+ .map { it == 1f }
+ .distinctUntilChanged(),
+ ::Triple,
)
- .flatMapLatestConflated { (isAsleep, isInLockdown) ->
- if (isAsleep || isInLockdown) {
+ .flatMapLatestConflated { (isAsleep, isInLockdown, isAod) ->
+ val isForceLocked =
+ when {
+ isAsleep && !isAod -> true
+ isInLockdown -> true
+ else -> false
+ }
+ if (isForceLocked) {
flowOf(DeviceUnlockStatus(false, null))
} else {
deviceUnlockSource.map { DeviceUnlockStatus(true, it) }
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 e50c05c..e09e198 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt
@@ -29,6 +29,7 @@
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.LogLevel
import com.android.systemui.log.dagger.QSLog
+import com.android.systemui.plugins.FalsingManager
import com.android.systemui.plugins.qs.QSTile
import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -44,12 +45,12 @@
* @property[vibratorHelper] The [VibratorHelper] to deliver haptic effects.
* @property[effectDuration] The duration of the effect in ms.
*/
-// TODO(b/332902869): In addition from being injectable, we can consider making it a singleton
class QSLongPressEffect
@Inject
constructor(
private val vibratorHelper: VibratorHelper?,
private val keyguardStateController: KeyguardStateController,
+ private val falsingManager: FalsingManager,
@QSLog private val logBuffer: LogBuffer,
) {
@@ -72,7 +73,7 @@
private val durations =
vibratorHelper?.getPrimitiveDurations(
VibrationEffect.Composition.PRIMITIVE_LOW_TICK,
- VibrationEffect.Composition.PRIMITIVE_SPIN
+ VibrationEffect.Composition.PRIMITIVE_SPIN,
)
private var longPressHint: VibrationEffect? = null
@@ -152,15 +153,27 @@
logEvent(qsTile?.tileSpec, state, "animation completed")
when (state) {
State.RUNNING_FORWARD -> {
- vibrate(snapEffect)
- if (keyguardStateController.isUnlocked) {
- setState(State.LONG_CLICKED)
- } else {
+ val wasFalseLongTap = falsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY)
+ if (wasFalseLongTap) {
callback?.onResetProperties()
setState(State.IDLE)
+ logEvent(qsTile?.tileSpec, state, "false long click. No action triggered")
+ } else if (keyguardStateController.isUnlocked) {
+ vibrate(snapEffect)
+ setState(State.LONG_CLICKED)
+ qsTile?.longClick(expandable)
+ logEvent(qsTile?.tileSpec, state, "long click action triggered")
+ } else {
+ vibrate(snapEffect)
+ callback?.onResetProperties()
+ setState(State.IDLE)
+ qsTile?.longClick(expandable)
+ logEvent(
+ qsTile?.tileSpec,
+ state,
+ "properties reset and long click action triggered",
+ )
}
- logEvent(qsTile?.tileSpec, state, "long click action triggered")
- qsTile?.longClick(expandable)
}
State.RUNNING_BACKWARDS_FROM_UP -> {
callback?.onEffectFinishedReversing()
@@ -236,7 +249,7 @@
LongPressHapticBuilder.createLongPressHint(
durations?.get(0) ?: LongPressHapticBuilder.INVALID_DURATION,
durations?.get(1) ?: LongPressHapticBuilder.INVALID_DURATION,
- effectDuration
+ effectDuration,
)
setState(State.IDLE)
return true
@@ -265,7 +278,7 @@
}
override fun dialogTransitionController(
- cuj: DialogCuj?,
+ cuj: DialogCuj?
): DialogTransitionAnimator.Controller? =
DialogTransitionAnimator.Controller.fromView(view, cuj)
}
@@ -298,7 +311,7 @@
str2 = event
str3 = state.name
},
- { "[long-press effect on $str1 tile] $str2 on state: $str3" }
+ { "[long-press effect on $str1 tile] $str2 on state: $str3" },
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/InputDeviceTutorialLogger.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/InputDeviceTutorialLogger.kt
index 9525174..48f5cb6 100644
--- a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/InputDeviceTutorialLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/InputDeviceTutorialLogger.kt
@@ -16,27 +16,27 @@
package com.android.systemui.inputdevice.tutorial
+import com.android.systemui.inputdevice.tutorial.domain.interactor.ConnectionState
+import com.android.systemui.inputdevice.tutorial.ui.viewmodel.Screen as KeyboardTouchpadTutorialScreen
+import com.android.systemui.log.ConstantStringsLogger
+import com.android.systemui.log.ConstantStringsLoggerImpl
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.core.MessageInitializer
+import com.android.systemui.log.core.MessagePrinter
import com.android.systemui.log.dagger.InputDeviceTutorialLog
-import com.android.systemui.touchpad.tutorial.ui.viewmodel.Screen
-import com.google.errorprone.annotations.CompileTimeConstant
+import com.android.systemui.touchpad.tutorial.ui.viewmodel.Screen as TouchpadTutorialScreen
import javax.inject.Inject
private const val TAG = "InputDeviceTutorial"
class InputDeviceTutorialLogger
@Inject
-constructor(@InputDeviceTutorialLog private val buffer: LogBuffer) {
+constructor(@InputDeviceTutorialLog private val buffer: LogBuffer) :
+ ConstantStringsLogger by ConstantStringsLoggerImpl(buffer, TAG) {
- fun log(@CompileTimeConstant s: String) {
- buffer.log(TAG, LogLevel.INFO, message = s)
- }
-
- fun logGoingToScreen(screen: Screen, context: TutorialContext) {
- buffer.log(
- TAG,
- LogLevel.INFO,
+ fun logGoingToScreen(screen: TouchpadTutorialScreen, context: TutorialContext) {
+ logInfo(
{
str1 = screen.toString()
str2 = context.string
@@ -46,7 +46,58 @@
}
fun logCloseTutorial(context: TutorialContext) {
- buffer.log(TAG, LogLevel.INFO, { str1 = context.string }, { "Closing $str1" })
+ logInfo({ str1 = context.string }, { "Closing $str1" })
+ }
+
+ fun logOpenTutorial(context: TutorialContext) {
+ logInfo({ str1 = context.string }, { "Opening $str1" })
+ }
+
+ fun logNextScreenMissingHardware(nextScreen: KeyboardTouchpadTutorialScreen) {
+ buffer.log(
+ TAG,
+ LogLevel.WARNING,
+ { str1 = nextScreen.toString() },
+ { "next screen should be $str1 but required hardware is missing" }
+ )
+ }
+
+ fun logNextScreen(nextScreen: KeyboardTouchpadTutorialScreen) {
+ logInfo({ str1 = nextScreen.toString() }, { "going to $str1 screen" })
+ }
+
+ fun logNewConnectionState(connectionState: ConnectionState) {
+ logInfo(
+ {
+ bool1 = connectionState.touchpadConnected
+ bool2 = connectionState.keyboardConnected
+ },
+ { "Received connection state: touchpad connected: $bool1 keyboard connected: $bool2" }
+ )
+ }
+
+ fun logMovingBetweenScreens(
+ previousScreen: KeyboardTouchpadTutorialScreen?,
+ currentScreen: KeyboardTouchpadTutorialScreen
+ ) {
+ logInfo(
+ {
+ str1 = previousScreen?.toString() ?: "NO_SCREEN"
+ str2 = currentScreen.toString()
+ },
+ { "Moving from $str1 screen to $str2 screen" }
+ )
+ }
+
+ fun logGoingBack(previousScreen: KeyboardTouchpadTutorialScreen) {
+ logInfo({ str1 = previousScreen.toString() }, { "Going back to $str1 screen" })
+ }
+
+ private inline fun logInfo(
+ messageInitializer: MessageInitializer,
+ noinline messagePrinter: MessagePrinter
+ ) {
+ buffer.log(TAG, LogLevel.INFO, messageInitializer, messagePrinter)
}
enum class TutorialContext(val string: String) {
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/view/KeyboardTouchpadTutorialActivity.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/view/KeyboardTouchpadTutorialActivity.kt
index 1adc285..c130c6c 100644
--- a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/view/KeyboardTouchpadTutorialActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/view/KeyboardTouchpadTutorialActivity.kt
@@ -28,6 +28,8 @@
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.lifecycleScope
import com.android.compose.theme.PlatformTheme
+import com.android.systemui.inputdevice.tutorial.InputDeviceTutorialLogger
+import com.android.systemui.inputdevice.tutorial.InputDeviceTutorialLogger.TutorialContext
import com.android.systemui.inputdevice.tutorial.TouchpadTutorialScreensProvider
import com.android.systemui.inputdevice.tutorial.ui.composable.ActionKeyTutorialScreen
import com.android.systemui.inputdevice.tutorial.ui.viewmodel.KeyboardTouchpadTutorialViewModel
@@ -48,6 +50,7 @@
constructor(
private val viewModelFactoryAssistedProvider: ViewModelFactoryAssistedProvider,
private val touchpadTutorialScreensProvider: Optional<TouchpadTutorialScreensProvider>,
+ private val logger: InputDeviceTutorialLogger,
) : ComponentActivity() {
companion object {
@@ -74,6 +77,7 @@
lifecycleScope.launch {
vm.closeActivity.collect { finish ->
if (finish) {
+ logger.logCloseTutorial(TutorialContext.KEYBOARD_TOUCHPAD_TUTORIAL)
finish()
}
}
@@ -81,6 +85,9 @@
setContent {
PlatformTheme { KeyboardTouchpadTutorialContainer(vm, touchpadTutorialScreensProvider) }
}
+ if (savedInstanceState == null) {
+ logger.logOpenTutorial(TutorialContext.KEYBOARD_TOUCHPAD_TUTORIAL)
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/viewmodel/KeyboardTouchpadTutorialViewModel.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/viewmodel/KeyboardTouchpadTutorialViewModel.kt
index 315c102..5cf1967 100644
--- a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/viewmodel/KeyboardTouchpadTutorialViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/viewmodel/KeyboardTouchpadTutorialViewModel.kt
@@ -22,6 +22,7 @@
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
+import com.android.systemui.inputdevice.tutorial.InputDeviceTutorialLogger
import com.android.systemui.inputdevice.tutorial.domain.interactor.ConnectionState
import com.android.systemui.inputdevice.tutorial.domain.interactor.KeyboardTouchpadConnectionInteractor
import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity.Companion.INTENT_TUTORIAL_TYPE_KEY
@@ -47,6 +48,7 @@
private val gesturesInteractor: Optional<TouchpadGesturesInteractor>,
private val keyboardTouchpadConnectionInteractor: KeyboardTouchpadConnectionInteractor,
private val hasTouchpadTutorialScreens: Boolean,
+ private val logger: InputDeviceTutorialLogger,
handle: SavedStateHandle
) : ViewModel(), DefaultLifecycleObserver {
@@ -68,7 +70,10 @@
init {
viewModelScope.launch {
- keyboardTouchpadConnectionInteractor.connectionState.collect { connectionState = it }
+ keyboardTouchpadConnectionInteractor.connectionState.collect {
+ logger.logNewConnectionState(connectionState)
+ connectionState = it
+ }
}
viewModelScope.launch {
@@ -89,7 +94,14 @@
viewModelScope.launch {
// close activity if screen requires touchpad but we don't have it. This can only happen
// when current sysui build doesn't contain touchpad module dependency
- _screen.filterNot { it.canBeShown() }.collect { _closeActivity.value = true }
+ _screen
+ .filterNot { it.canBeShown() }
+ .collect {
+ logger.e(
+ "Touchpad is connected but touchpad module is missing, something went wrong"
+ )
+ _closeActivity.value = true
+ }
}
}
@@ -114,11 +126,14 @@
if (requiredHardwarePresent(nextScreen)) {
break
}
+ logger.logNextScreenMissingHardware(nextScreen)
nextScreen = nextScreen.next()
}
if (nextScreen == null) {
+ logger.d("Final screen reached, closing tutorial")
_closeActivity.value = true
} else {
+ logger.logNextScreen(nextScreen)
_screen.value = nextScreen
screensBackStack.add(nextScreen)
}
@@ -127,6 +142,7 @@
private fun Screen.canBeShown() = requiredHardware != TOUCHPAD || hasTouchpadTutorialScreens
private fun setupDeviceState(previousScreen: Screen?, currentScreen: Screen) {
+ logger.logMovingBetweenScreens(previousScreen, currentScreen)
if (previousScreen?.requiredHardware == currentScreen.requiredHardware) return
previousScreen?.let { clearDeviceStateForScreen(it) }
when (currentScreen.requiredHardware) {
@@ -153,6 +169,7 @@
_closeActivity.value = true
} else {
screensBackStack.removeLast()
+ logger.logGoingBack(screensBackStack.last())
_screen.value = screensBackStack.last()
}
}
@@ -162,6 +179,7 @@
constructor(
private val gesturesInteractor: Optional<TouchpadGesturesInteractor>,
private val keyboardTouchpadConnected: KeyboardTouchpadConnectionInteractor,
+ private val logger: InputDeviceTutorialLogger,
@Assisted private val hasTouchpadTutorialScreens: Boolean,
) : AbstractSavedStateViewModelFactory() {
@@ -180,6 +198,7 @@
gesturesInteractor,
keyboardTouchpadConnected,
hasTouchpadTutorialScreens,
+ logger,
handle
)
as T
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfig.kt
index 406b9f6..be87334 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfig.kt
@@ -25,7 +25,9 @@
import android.provider.Settings.Secure.ZEN_DURATION_FOREVER
import android.provider.Settings.Secure.ZEN_DURATION_PROMPT
import android.service.notification.ZenModeConfig
+import android.util.Log
import com.android.settingslib.notification.modes.EnableZenModeDialog
+import com.android.settingslib.notification.modes.ZenMode
import com.android.settingslib.notification.modes.ZenModeDialogMetricsLogger
import com.android.systemui.animation.Expandable
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
@@ -35,30 +37,38 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
+import com.android.systemui.modes.shared.ModesUi
import com.android.systemui.res.R
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.policy.ZenModeController
+import com.android.systemui.statusbar.policy.domain.interactor.ZenModeInteractor
import com.android.systemui.util.settings.SecureSettings
import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.stateIn
@SysUISingleton
class DoNotDisturbQuickAffordanceConfig
constructor(
private val context: Context,
private val controller: ZenModeController,
+ private val interactor: ZenModeInteractor,
private val secureSettings: SecureSettings,
private val userTracker: UserTracker,
@Background private val backgroundDispatcher: CoroutineDispatcher,
+ @Background private val backgroundScope: CoroutineScope,
private val testConditionId: Uri?,
testDialog: EnableZenModeDialog?,
) : KeyguardQuickAffordanceConfig {
@@ -67,15 +77,45 @@
constructor(
context: Context,
controller: ZenModeController,
+ interactor: ZenModeInteractor,
secureSettings: SecureSettings,
userTracker: UserTracker,
@Background backgroundDispatcher: CoroutineDispatcher,
- ) : this(context, controller, secureSettings, userTracker, backgroundDispatcher, null, null)
+ @Background backgroundScope: CoroutineScope,
+ ) : this(
+ context,
+ controller,
+ interactor,
+ secureSettings,
+ userTracker,
+ backgroundDispatcher,
+ backgroundScope,
+ null,
+ null,
+ )
- private var dndMode: Int = 0
- private var isAvailable = false
+ private var zenMode: Int = 0
+ private var oldIsAvailable = false
private var settingsValue: Int = 0
+ private val dndMode: StateFlow<ZenMode?> by lazy {
+ ModesUi.assertInNewMode()
+ interactor.dndMode.stateIn(
+ scope = backgroundScope,
+ started = SharingStarted.Eagerly,
+ initialValue = null,
+ )
+ }
+
+ private val isAvailable: StateFlow<Boolean> by lazy {
+ ModesUi.assertInNewMode()
+ interactor.isZenAvailable.stateIn(
+ scope = backgroundScope,
+ started = SharingStarted.Eagerly,
+ initialValue = false,
+ )
+ }
+
private val conditionUri: Uri
get() =
testConditionId
@@ -104,42 +144,68 @@
override val pickerIconResourceId: Int = R.drawable.ic_do_not_disturb
override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState> =
- combine(
- conflatedCallbackFlow {
- val callback =
- object : ZenModeController.Callback {
- override fun onZenChanged(zen: Int) {
- dndMode = zen
- trySendWithFailureLogging(updateState(), TAG)
+ if (ModesUi.isEnabled) {
+ combine(isAvailable, dndMode) { isAvailable, dndMode ->
+ if (!isAvailable) {
+ KeyguardQuickAffordanceConfig.LockScreenState.Hidden
+ } else if (dndMode?.isActive == true) {
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(
+ Icon.Resource(
+ R.drawable.qs_dnd_icon_on,
+ ContentDescription.Resource(R.string.dnd_is_on),
+ ),
+ ActivationState.Active,
+ )
+ } else {
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(
+ Icon.Resource(
+ R.drawable.qs_dnd_icon_off,
+ ContentDescription.Resource(R.string.dnd_is_off),
+ ),
+ ActivationState.Inactive,
+ )
+ }
+ }
+ } else {
+ combine(
+ conflatedCallbackFlow {
+ val callback =
+ object : ZenModeController.Callback {
+ override fun onZenChanged(zen: Int) {
+ zenMode = zen
+ trySendWithFailureLogging(updateState(), TAG)
+ }
+
+ override fun onZenAvailableChanged(available: Boolean) {
+ oldIsAvailable = available
+ trySendWithFailureLogging(updateState(), TAG)
+ }
}
- override fun onZenAvailableChanged(available: Boolean) {
- isAvailable = available
- trySendWithFailureLogging(updateState(), TAG)
- }
- }
+ zenMode = controller.zen
+ oldIsAvailable = controller.isZenAvailable
+ trySendWithFailureLogging(updateState(), TAG)
- dndMode = controller.zen
- isAvailable = controller.isZenAvailable
- trySendWithFailureLogging(updateState(), TAG)
+ controller.addCallback(callback)
- controller.addCallback(callback)
-
- awaitClose { controller.removeCallback(callback) }
- },
- secureSettings
- .observerFlow(userTracker.userId, Settings.Secure.ZEN_DURATION)
- .onStart { emit(Unit) }
- .map { secureSettings.getInt(Settings.Secure.ZEN_DURATION, ZEN_MODE_OFF) }
- .flowOn(backgroundDispatcher)
- .distinctUntilChanged()
- .onEach { settingsValue = it }
- ) { callbackFlowValue, _ ->
- callbackFlowValue
+ awaitClose { controller.removeCallback(callback) }
+ },
+ secureSettings
+ .observerFlow(userTracker.userId, Settings.Secure.ZEN_DURATION)
+ .onStart { emit(Unit) }
+ .map { secureSettings.getInt(Settings.Secure.ZEN_DURATION, ZEN_MODE_OFF) }
+ .flowOn(backgroundDispatcher)
+ .distinctUntilChanged()
+ .onEach { settingsValue = it },
+ ) { callbackFlowValue, _ ->
+ callbackFlowValue
+ }
}
override suspend fun getPickerScreenState(): KeyguardQuickAffordanceConfig.PickerScreenState {
- return if (controller.isZenAvailable) {
+ val isZenAvailable = if (ModesUi.isEnabled) isAvailable.value else controller.isZenAvailable
+
+ return if (isZenAvailable) {
KeyguardQuickAffordanceConfig.PickerScreenState.Default(
configureIntent = Intent(Settings.ACTION_ZEN_MODE_SETTINGS)
)
@@ -151,32 +217,63 @@
override fun onTriggered(
expandable: Expandable?
): KeyguardQuickAffordanceConfig.OnTriggeredResult {
- return when {
- !isAvailable -> KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
- dndMode != ZEN_MODE_OFF -> {
- controller.setZen(ZEN_MODE_OFF, null, TAG)
+ return if (ModesUi.isEnabled) {
+ if (!isAvailable.value) {
KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
+ } else {
+ val dnd = dndMode.value
+ if (dnd == null) {
+ Log.wtf(TAG, "Triggered DND but it's null!?")
+ return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
+ }
+ if (dnd.isActive) {
+ interactor.deactivateMode(dnd)
+ return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
+ } else {
+ if (interactor.shouldAskForZenDuration(dnd)) {
+ // NOTE: The dialog handles turning on the mode itself.
+ return KeyguardQuickAffordanceConfig.OnTriggeredResult.ShowDialog(
+ dialog.createDialog(),
+ expandable,
+ )
+ } else {
+ interactor.activateMode(dnd)
+ return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
+ }
+ }
}
- settingsValue == ZEN_DURATION_PROMPT ->
- KeyguardQuickAffordanceConfig.OnTriggeredResult.ShowDialog(
- dialog.createDialog(),
- expandable
- )
- settingsValue == ZEN_DURATION_FOREVER -> {
- controller.setZen(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, TAG)
- KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
- }
- else -> {
- controller.setZen(ZEN_MODE_IMPORTANT_INTERRUPTIONS, conditionUri, TAG)
- KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
+ } else {
+ when {
+ !oldIsAvailable -> KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
+ zenMode != ZEN_MODE_OFF -> {
+ controller.setZen(ZEN_MODE_OFF, null, TAG)
+ KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
+ }
+
+ settingsValue == ZEN_DURATION_PROMPT ->
+ KeyguardQuickAffordanceConfig.OnTriggeredResult.ShowDialog(
+ dialog.createDialog(),
+ expandable,
+ )
+
+ settingsValue == ZEN_DURATION_FOREVER -> {
+ controller.setZen(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, TAG)
+ KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
+ }
+
+ else -> {
+ controller.setZen(ZEN_MODE_IMPORTANT_INTERRUPTIONS, conditionUri, TAG)
+ KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
+ }
}
}
}
private fun updateState(): KeyguardQuickAffordanceConfig.LockScreenState {
- return if (!isAvailable) {
+ ModesUi.assertInLegacyMode()
+ return if (!oldIsAvailable) {
KeyguardQuickAffordanceConfig.LockScreenState.Hidden
- } else if (dndMode == ZEN_MODE_OFF) {
+ } else if (zenMode == ZEN_MODE_OFF) {
KeyguardQuickAffordanceConfig.LockScreenState.Visible(
Icon.Resource(
R.drawable.qs_dnd_icon_off,
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
index e11ffcc..b7e2cf2 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
@@ -61,6 +61,7 @@
import com.android.systemui.scene.session.shared.SessionStorage
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.shared.logger.SceneLogger
+import com.android.systemui.scene.shared.model.Overlays
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.NotificationShadeWindowController
@@ -228,8 +229,10 @@
is ObservableTransitionState.Idle -> {
if (state.currentScene != Scenes.Gone) {
true to "scene is not Gone"
+ } else if (state.currentOverlays.isNotEmpty()) {
+ true to "overlay is shown"
} else {
- false to "scene is Gone"
+ false to "scene is Gone and no overlays are shown"
}
}
is ObservableTransitionState.Transition -> {
@@ -712,19 +715,21 @@
if (isDeviceLocked) {
sceneInteractor.transitionState
.mapNotNull { it as? ObservableTransitionState.Idle }
- .map { it.currentScene }
+ .map { it.currentScene to it.currentOverlays }
.distinctUntilChanged()
- .map { sceneKey ->
- when (sceneKey) {
+ .map { (sceneKey, currentOverlays) ->
+ when {
// When locked, showing the lockscreen scene should be reported
// as "interacting" while showing other scenes should report as
// "not interacting".
//
// This is done here in order to match the legacy
// implementation. The real reason why is lost to lore and myth.
- Scenes.Lockscreen -> true
- Scenes.Bouncer -> false
- Scenes.Shade -> false
+ Overlays.NotificationsShade in currentOverlays -> false
+ Overlays.QuickSettingsShade in currentOverlays -> null
+ sceneKey == Scenes.Lockscreen -> true
+ sceneKey == Scenes.Bouncer -> false
+ sceneKey == Scenes.Shade -> false
else -> null
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt
index 4056e7b..c6c303e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt
@@ -16,9 +16,11 @@
package com.android.systemui.statusbar.core
import android.app.Fragment
-import com.android.systemui.res.R
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.fragments.FragmentHostManager
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.core.StatusBarInitializer.OnStatusBarViewInitializedListener
+import com.android.systemui.statusbar.core.StatusBarInitializer.OnStatusBarViewUpdatedListener
import com.android.systemui.statusbar.phone.PhoneStatusBarTransitions
import com.android.systemui.statusbar.phone.PhoneStatusBarView
import com.android.systemui.statusbar.phone.PhoneStatusBarViewController
@@ -33,31 +35,62 @@
* Responsible for creating the status bar window and initializing the root components of that
* window (see [CollapsedStatusBarFragment])
*/
-@SysUISingleton
-class StatusBarInitializer @Inject constructor(
- private val windowController: StatusBarWindowController,
- private val collapsedStatusBarFragmentProvider: Provider<CollapsedStatusBarFragment>,
- private val creationListeners: Set<@JvmSuppressWildcards OnStatusBarViewInitializedListener>,
-) {
+interface StatusBarInitializer {
- var statusBarViewUpdatedListener: OnStatusBarViewUpdatedListener? = null
+ var statusBarViewUpdatedListener: OnStatusBarViewUpdatedListener?
/**
* Creates the status bar window and root views, and initializes the component.
*
* TODO(b/277764509): Initialize the status bar via [CoreStartable#start].
*/
- fun initializeStatusBar() {
- windowController.fragmentHostManager.addTagListener(
+ fun initializeStatusBar()
+
+ interface OnStatusBarViewInitializedListener {
+
+ /**
+ * The status bar view has been initialized.
+ *
+ * @param component Dagger component that is created when the status bar view is created.
+ * Can be used to retrieve dependencies from that scope, including the status bar root
+ * view.
+ */
+ fun onStatusBarViewInitialized(component: StatusBarFragmentComponent)
+ }
+
+ interface OnStatusBarViewUpdatedListener {
+ fun onStatusBarViewUpdated(
+ statusBarView: PhoneStatusBarView,
+ statusBarViewController: PhoneStatusBarViewController,
+ statusBarTransitions: PhoneStatusBarTransitions,
+ )
+ }
+}
+
+@SysUISingleton
+class StatusBarInitializerImpl
+@Inject
+constructor(
+ private val windowController: StatusBarWindowController,
+ private val collapsedStatusBarFragmentProvider: Provider<CollapsedStatusBarFragment>,
+ private val creationListeners: Set<@JvmSuppressWildcards OnStatusBarViewInitializedListener>,
+) : StatusBarInitializer {
+
+ override var statusBarViewUpdatedListener: OnStatusBarViewUpdatedListener? = null
+
+ override fun initializeStatusBar() {
+ windowController.fragmentHostManager
+ .addTagListener(
CollapsedStatusBarFragment.TAG,
object : FragmentHostManager.FragmentListener {
override fun onFragmentViewCreated(tag: String, fragment: Fragment) {
- val statusBarFragmentComponent = (fragment as CollapsedStatusBarFragment)
- .statusBarFragmentComponent ?: throw IllegalStateException()
+ val statusBarFragmentComponent =
+ (fragment as CollapsedStatusBarFragment).statusBarFragmentComponent
+ ?: throw IllegalStateException()
statusBarViewUpdatedListener?.onStatusBarViewUpdated(
statusBarFragmentComponent.phoneStatusBarView,
statusBarFragmentComponent.phoneStatusBarViewController,
- statusBarFragmentComponent.phoneStatusBarTransitions
+ statusBarFragmentComponent.phoneStatusBarTransitions,
)
creationListeners.forEach { listener ->
listener.onStatusBarViewInitialized(statusBarFragmentComponent)
@@ -67,33 +100,15 @@
override fun onFragmentViewDestroyed(tag: String?, fragment: Fragment?) {
// nop
}
- }
- ).fragmentManager
- .beginTransaction()
- .replace(
- R.id.status_bar_container,
- collapsedStatusBarFragmentProvider.get(),
- CollapsedStatusBarFragment.TAG
- )
- .commit()
- }
-
- interface OnStatusBarViewInitializedListener {
-
- /**
- * The status bar view has been initialized.
- *
- * @param component Dagger component that is created when the status bar view is created.
- * Can be used to retrieve dependencies from that scope, including the status bar root view.
- */
- fun onStatusBarViewInitialized(component: StatusBarFragmentComponent)
- }
-
- interface OnStatusBarViewUpdatedListener {
- fun onStatusBarViewUpdated(
- statusBarView: PhoneStatusBarView,
- statusBarViewController: PhoneStatusBarViewController,
- statusBarTransitions: PhoneStatusBarTransitions
- )
+ },
+ )
+ .fragmentManager
+ .beginTransaction()
+ .replace(
+ R.id.status_bar_container,
+ collapsedStatusBarFragmentProvider.get(),
+ CollapsedStatusBarFragment.TAG,
+ )
+ .commit()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
index 406a664..526c64c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
@@ -20,12 +20,16 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.LogBufferFactory
+import com.android.systemui.statusbar.core.StatusBarInitializer
+import com.android.systemui.statusbar.core.StatusBarInitializerImpl
import com.android.systemui.statusbar.data.StatusBarDataLayerModule
import com.android.systemui.statusbar.phone.LightBarController
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy
import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController
import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallLog
import com.android.systemui.statusbar.ui.SystemBarUtilsProxyImpl
+import com.android.systemui.statusbar.window.StatusBarWindowController
+import com.android.systemui.statusbar.window.StatusBarWindowControllerImpl
import dagger.Binds
import dagger.Module
import dagger.Provides
@@ -57,6 +61,13 @@
@ClassKey(StatusBarSignalPolicy::class)
abstract fun bindStatusBarSignalPolicy(impl: StatusBarSignalPolicy): CoreStartable
+ @Binds abstract fun statusBarInitializer(impl: StatusBarInitializerImpl): StatusBarInitializer
+
+ @Binds
+ abstract fun statusBarWindowController(
+ impl: StatusBarWindowControllerImpl
+ ): StatusBarWindowController
+
companion object {
@Provides
@SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/RemoteInputRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/RemoteInputRepository.kt
index c0302bc..9af4b8c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/RemoteInputRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/RemoteInputRepository.kt
@@ -25,6 +25,7 @@
import javax.inject.Inject
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
/**
* Repository used for tracking the state of notification remote input (e.g. when the user presses
@@ -33,14 +34,21 @@
interface RemoteInputRepository {
/** Whether remote input is currently active for any notification. */
val isRemoteInputActive: Flow<Boolean>
+
+ /**
+ * The bottom bound of the currently focused remote input notification row, or null if there
+ * isn't one.
+ */
+ val remoteInputRowBottomBound: Flow<Float?>
+
+ fun setRemoteInputRowBottomBound(bottom: Float?)
}
@SysUISingleton
class RemoteInputRepositoryImpl
@Inject
-constructor(
- private val notificationRemoteInputManager: NotificationRemoteInputManager,
-) : RemoteInputRepository {
+constructor(private val notificationRemoteInputManager: NotificationRemoteInputManager) :
+ RemoteInputRepository {
override val isRemoteInputActive: Flow<Boolean> = conflatedCallbackFlow {
trySend(false) // initial value is false
val callback =
@@ -52,6 +60,12 @@
notificationRemoteInputManager.addControllerCallback(callback)
awaitClose { notificationRemoteInputManager.removeControllerCallback(callback) }
}
+
+ override val remoteInputRowBottomBound = MutableStateFlow<Float?>(null)
+
+ override fun setRemoteInputRowBottomBound(bottom: Float?) {
+ remoteInputRowBottomBound.value = bottom
+ }
}
@Module
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/domain/interactor/RemoteInputInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/domain/interactor/RemoteInputInteractor.kt
index 68f727b..b83b0cc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/domain/interactor/RemoteInputInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/domain/interactor/RemoteInputInteractor.kt
@@ -20,13 +20,24 @@
import com.android.systemui.statusbar.data.repository.RemoteInputRepository
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.mapNotNull
/**
* Interactor used for business logic pertaining to the notification remote input (e.g. when the
* user presses "reply" on a notification and the keyboard opens).
*/
@SysUISingleton
-class RemoteInputInteractor @Inject constructor(remoteInputRepository: RemoteInputRepository) {
+class RemoteInputInteractor
+@Inject
+constructor(private val remoteInputRepository: RemoteInputRepository) {
/** Is remote input currently active for a notification? */
val isRemoteInputActive: Flow<Boolean> = remoteInputRepository.isRemoteInputActive
+
+ /** The bottom bound of the currently focused remote input notification row. */
+ val remoteInputRowBottomBound: Flow<Float> =
+ remoteInputRepository.remoteInputRowBottomBound.mapNotNull { it }
+
+ fun setRemoteInputRowBottomBound(bottom: Float?) {
+ remoteInputRepository.setRemoteInputRowBottomBound(bottom)
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index cb3e26b..5003a6a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -21,6 +21,7 @@
import static com.android.systemui.statusbar.notification.collection.NotificationEntry.DismissState.PARENT_DISMISSED;
import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_HEADSUP;
+import static com.android.systemui.statusbar.policy.RemoteInputView.FOCUS_ANIMATION_MIN_SCALE;
import static com.android.systemui.util.ColorUtilKt.hexColorString;
import android.animation.Animator;
@@ -83,6 +84,7 @@
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.res.R;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
import com.android.systemui.statusbar.RemoteInputController;
import com.android.systemui.statusbar.SmartReplyController;
import com.android.systemui.statusbar.StatusBarIconView;
@@ -118,6 +120,7 @@
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.InflatedSmartReplyState;
+import com.android.systemui.statusbar.policy.RemoteInputView;
import com.android.systemui.statusbar.policy.SmartReplyConstants;
import com.android.systemui.statusbar.policy.dagger.RemoteInputViewSubcomponent;
import com.android.systemui.util.Compile;
@@ -830,6 +833,20 @@
mPrivateLayout.setRemoteInputController(r);
}
+ /**
+ * Return the cumulative y-value that the actions container expands via its scale animator when
+ * remote input is activated.
+ */
+ public float getRemoteInputActionsContainerExpandedOffset() {
+ if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return 0f;
+ RemoteInputView expandedRemoteInput = mPrivateLayout.getExpandedRemoteInput();
+ if (expandedRemoteInput == null) return 0f;
+ View actionsContainerLayout = expandedRemoteInput.getActionsContainerLayout();
+ if (actionsContainerLayout == null) return 0f;
+
+ return actionsContainerLayout.getHeight() * (1 - FOCUS_ANIMATION_MIN_SCALE) * 0.5f;
+ }
+
public void addChildNotification(ExpandableNotificationRow row) {
addChildNotification(row, -1);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 7543f3b..e7c67f9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -99,6 +99,7 @@
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.notification.ColorUpdateLogger;
import com.android.systemui.statusbar.notification.FakeShadowView;
+import com.android.systemui.statusbar.notification.HeadsUpTouchHelper;
import com.android.systemui.statusbar.notification.LaunchAnimationParameters;
import com.android.systemui.statusbar.notification.NotificationTransitionAnimatorController;
import com.android.systemui.statusbar.notification.NotificationUtils;
@@ -120,7 +121,6 @@
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimShape;
import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView;
import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
-import com.android.systemui.statusbar.notification.HeadsUpTouchHelper;
import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
import com.android.systemui.statusbar.policy.HeadsUpUtil;
import com.android.systemui.statusbar.policy.ScrollAdapter;
@@ -740,6 +740,15 @@
updateFooter();
}
+ void sendRemoteInputRowBottomBound(Float bottom) {
+ if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
+ if (bottom != null) {
+ bottom += getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.notification_content_margin);
+ }
+ mScrollViewFields.sendRemoteInputRowBottomBound(bottom);
+ }
+
/** Setter for filtered notifs, to be removed with the FooterViewRefactor flag. */
public void setHasFilteredOutSeenNotifications(boolean hasFilteredOutSeenNotifications) {
FooterViewRefactor.assertInLegacyMode();
@@ -1274,6 +1283,11 @@
}
@Override
+ public void setRemoteInputRowBottomBoundConsumer(@Nullable Consumer<Float> consumer) {
+ mScrollViewFields.setRemoteInputRowBottomBoundConsumer(consumer);
+ }
+
+ @Override
public void setHeadsUpHeightConsumer(@Nullable Consumer<Float> consumer) {
mScrollViewFields.setHeadsUpHeightConsumer(consumer);
}
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 e5f63c1..dad6894 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
@@ -98,6 +98,9 @@
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.notification.ColorUpdateLogger;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
+import com.android.systemui.statusbar.notification.HeadsUpNotificationViewControllerEmptyImpl;
+import com.android.systemui.statusbar.notification.HeadsUpTouchHelper;
+import com.android.systemui.statusbar.notification.HeadsUpTouchHelper.HeadsUpNotificationViewController;
import com.android.systemui.statusbar.notification.LaunchAnimationParameters;
import com.android.systemui.statusbar.notification.NotificationActivityStarter;
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
@@ -129,9 +132,6 @@
import com.android.systemui.statusbar.notification.shared.GroupHunAnimationFix;
import com.android.systemui.statusbar.notification.stack.ui.viewbinder.NotificationListViewBinder;
import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
-import com.android.systemui.statusbar.notification.HeadsUpNotificationViewControllerEmptyImpl;
-import com.android.systemui.statusbar.notification.HeadsUpTouchHelper;
-import com.android.systemui.statusbar.notification.HeadsUpTouchHelper.HeadsUpNotificationViewController;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
@@ -1605,6 +1605,9 @@
return new RemoteInputController.Delegate() {
public void setRemoteInputActive(NotificationEntry entry,
boolean remoteInputActive) {
+ if (SceneContainerFlag.isEnabled()) {
+ sendRemoteInputRowBottomBound(entry, remoteInputActive);
+ }
mHeadsUpManager.setRemoteInputActive(entry, remoteInputActive);
entry.notifyHeightChanged(true /* needsAnimation */);
if (!FooterViewRefactor.isEnabled()) {
@@ -1620,6 +1623,15 @@
mView.requestDisallowLongPress();
mView.requestDisallowDismiss();
}
+
+ private void sendRemoteInputRowBottomBound(NotificationEntry entry,
+ boolean remoteInputActive) {
+ ExpandableNotificationRow row = entry.getRow();
+ float top = row.getTranslationY();
+ int height = row.getActualHeight();
+ float bottom = top + height + row.getRemoteInputActionsContainerExpandedOffset();
+ mView.sendRemoteInputRowBottomBound(remoteInputActive ? bottom : null);
+ }
};
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ScrollViewFields.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ScrollViewFields.kt
index aa39539..c08ed61 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ScrollViewFields.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ScrollViewFields.kt
@@ -57,6 +57,13 @@
* guts off of this gesture, we can notify the placeholder through here.
*/
var currentGestureInGutsConsumer: Consumer<Boolean>? = null
+
+ /**
+ * When a notification begins remote input, its bottom Y bound is sent to the placeholder
+ * through here in order to adjust to accommodate the IME.
+ */
+ var remoteInputRowBottomBoundConsumer: Consumer<Float?>? = null
+
/**
* Any time the heads up height is recalculated, it should be updated here to be used by the
* placeholder
@@ -75,6 +82,10 @@
fun sendCurrentGestureInGuts(isCurrentGestureInGuts: Boolean) =
currentGestureInGutsConsumer?.accept(isCurrentGestureInGuts)
+ /** send [bottomY] to the [remoteInputRowBottomBoundConsumer], if present. */
+ fun sendRemoteInputRowBottomBound(bottomY: Float?) =
+ remoteInputRowBottomBoundConsumer?.accept(bottomY)
+
/** send the [headsUpHeight] to the [headsUpHeightConsumer], if present. */
fun sendHeadsUpHeight(headsUpHeight: Float) = headsUpHeightConsumer?.accept(headsUpHeight)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt
index 235b4da..41c0293 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt
@@ -74,6 +74,9 @@
/** Set a consumer for current gesture in guts events */
fun setCurrentGestureInGutsConsumer(consumer: Consumer<Boolean>?)
+ /** Set a consumer for current remote input notification row bottom bound events */
+ fun setRemoteInputRowBottomBoundConsumer(consumer: Consumer<Float?>?)
+
/** Set a consumer for heads up height changed events */
fun setHeadsUpHeightConsumer(consumer: Consumer<Float>?)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt
index 6d5553f..2e37dea 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt
@@ -108,10 +108,14 @@
view.setSyntheticScrollConsumer(viewModel.syntheticScrollConsumer)
view.setCurrentGestureOverscrollConsumer(viewModel.currentGestureOverscrollConsumer)
view.setCurrentGestureInGutsConsumer(viewModel.currentGestureInGutsConsumer)
+ view.setRemoteInputRowBottomBoundConsumer(
+ viewModel.remoteInputRowBottomBoundConsumer
+ )
DisposableHandle {
view.setSyntheticScrollConsumer(null)
view.setCurrentGestureOverscrollConsumer(null)
view.setCurrentGestureInGutsConsumer(null)
+ view.setRemoteInputRowBottomBoundConsumer(null)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
index 8d7007b..5b2e02d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
@@ -31,6 +31,7 @@
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.shade.shared.model.ShadeMode
+import com.android.systemui.statusbar.domain.interactor.RemoteInputInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimClipping
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimShape
@@ -56,6 +57,7 @@
dumpManager: DumpManager,
stackAppearanceInteractor: NotificationStackAppearanceInteractor,
shadeInteractor: ShadeInteractor,
+ private val remoteInputInteractor: RemoteInputInteractor,
private val sceneInteractor: SceneInteractor,
// TODO(b/336364825) Remove Lazy when SceneContainerFlag is released -
// while the flag is off, creating this object too early results in a crash
@@ -240,6 +242,10 @@
val currentGestureInGutsConsumer: (Boolean) -> Unit =
stackAppearanceInteractor::setCurrentGestureInGuts
+ /** Receives the bottom bound of the currently focused remote input notification row. */
+ val remoteInputRowBottomBoundConsumer: (Float?) -> Unit =
+ remoteInputInteractor::setRemoteInputRowBottomBound
+
/** Whether the notification stack is scrollable or not. */
val isScrollable: Flow<Boolean> =
combine(sceneInteractor.currentScene, sceneInteractor.currentOverlays) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
index 69c1bf3..c8e8358 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
@@ -24,6 +24,7 @@
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.statusbar.domain.interactor.RemoteInputInteractor
import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds
@@ -49,6 +50,7 @@
private val sceneInteractor: SceneInteractor,
private val shadeInteractor: ShadeInteractor,
private val headsUpNotificationInteractor: HeadsUpNotificationInteractor,
+ remoteInputInteractor: RemoteInputInteractor,
featureFlags: FeatureFlagsClassic,
dumpManager: DumpManager,
) :
@@ -132,6 +134,12 @@
val isCurrentGestureOverscroll: Flow<Boolean> =
interactor.isCurrentGestureOverscroll.dumpWhileCollecting("isCurrentGestureOverScroll")
+ /** Whether remote input is currently active for any notification. */
+ val isRemoteInputActive = remoteInputInteractor.isRemoteInputActive
+
+ /** The bottom bound of the currently focused remote input notification row. */
+ val remoteInputRowBottomBound = remoteInputInteractor.remoteInputRowBottomBound
+
/** Sets whether the notification stack is scrolled to the top. */
fun setScrolledToTop(scrolledToTop: Boolean) {
interactor.setScrolledToTop(scrolledToTop)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
index 31776cf..16d5f8d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -106,7 +106,7 @@
private static final long FOCUS_ANIMATION_CROSSFADE_DURATION = 50;
private static final long FOCUS_ANIMATION_FADE_IN_DELAY = 33;
private static final long FOCUS_ANIMATION_FADE_IN_DURATION = 83;
- private static final float FOCUS_ANIMATION_MIN_SCALE = 0.5f;
+ public static final float FOCUS_ANIMATION_MIN_SCALE = 0.5f;
private static final long DEFOCUS_ANIMATION_FADE_OUT_DELAY = 120;
private static final long DEFOCUS_ANIMATION_CROSSFADE_DELAY = 180;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt
index dbeaa59..ba45942 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt
@@ -27,7 +27,10 @@
import com.android.settingslib.notification.modes.ZenIconLoader
import com.android.settingslib.notification.modes.ZenMode
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.modes.shared.ModesUi
import com.android.systemui.shared.notifications.data.repository.NotificationSettingsRepository
+import com.android.systemui.statusbar.policy.data.repository.DeviceProvisioningRepository
+import com.android.systemui.statusbar.policy.data.repository.UserSetupRepository
import com.android.systemui.statusbar.policy.domain.model.ActiveZenModes
import com.android.systemui.statusbar.policy.domain.model.ZenModeInfo
import java.time.Duration
@@ -51,7 +54,17 @@
private val notificationSettingsRepository: NotificationSettingsRepository,
@Background private val bgDispatcher: CoroutineDispatcher,
private val iconLoader: ZenIconLoader,
+ private val deviceProvisioningRepository: DeviceProvisioningRepository,
+ private val userSetupRepository: UserSetupRepository,
) {
+ val isZenAvailable: Flow<Boolean> =
+ combine(
+ deviceProvisioningRepository.isDeviceProvisioned,
+ userSetupRepository.isUserSetUp,
+ ) { isDeviceProvisioned, isUserSetUp ->
+ isDeviceProvisioned && isUserSetUp
+ }
+
val isZenModeEnabled: Flow<Boolean> =
zenModeRepository.globalZenMode
.map {
@@ -80,6 +93,18 @@
val modes: Flow<List<ZenMode>> = zenModeRepository.modes
+ /**
+ * Returns the special "manual DND" mode.
+ *
+ * This is only meant as a temporary solution for "legacy" UI pieces that handle DND
+ * specifically; any new or migrated features should use modes more generally, through [modes]
+ * or [activeModes].
+ */
+ val dndMode: Flow<ZenMode?> by lazy {
+ ModesUi.assertInNewMode()
+ zenModeRepository.modes.map { modes -> modes.singleOrNull { it.isManualDnd } }
+ }
+
/** Flow returning the currently active mode(s), if any. */
val activeModes: Flow<ActiveZenModes> =
modes
@@ -113,10 +138,11 @@
Log.e(
TAG,
"Interactor cannot handle showing the zen duration prompt. " +
- "Please use EnableZenModeDialog when this setting is active."
+ "Please use EnableZenModeDialog when this setting is active.",
)
null
}
+
ZEN_DURATION_FOREVER -> null
else -> Duration.ofMinutes(zenDuration.toLong())
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.kt
new file mode 100644
index 0000000..421e5c4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.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.statusbar.window
+
+import android.view.View
+import android.view.ViewGroup
+import com.android.systemui.animation.ActivityTransitionAnimator
+import com.android.systemui.fragments.FragmentHostManager
+import java.util.Optional
+
+/** Encapsulates all logic for the status bar window state management. */
+interface StatusBarWindowController {
+ val statusBarHeight: Int
+
+ /** Rereads the status bar height and reapplies the current state if the height is different. */
+ fun refreshStatusBarHeight()
+
+ /** Adds the status bar view to the window manager. */
+ fun attach()
+
+ /** Adds the given view to the status bar window view. */
+ fun addViewToWindow(view: View, layoutParams: ViewGroup.LayoutParams)
+
+ /** Returns the status bar window's background view. */
+ val backgroundView: View
+
+ /** Returns a fragment host manager for the status bar window view. */
+ val fragmentHostManager: FragmentHostManager
+
+ /**
+ * Provides an updated animation controller if we're animating a view in the status bar.
+ *
+ * This is needed because we have to make sure that the status bar window matches the full
+ * screen during the animation and that we are expanding the view below the other status bar
+ * text.
+ *
+ * @param rootView the root view of the animation
+ * @param animationController the default animation controller to use
+ * @return If the animation is on a view in the status bar, returns an Optional containing an
+ * updated animation controller that handles status-bar-related animation details. Returns an
+ * empty optional if the animation is *not* on a view in the status bar.
+ */
+ fun wrapAnimationControllerIfInStatusBar(
+ rootView: View,
+ animationController: ActivityTransitionAnimator.Controller,
+ ): Optional<ActivityTransitionAnimator.Controller>
+
+ /** Set force status bar visible. */
+ fun setForceStatusBarVisible(forceStatusBarVisible: Boolean)
+
+ /**
+ * Sets whether an ongoing process requires the status bar to be forced visible.
+ *
+ * This method is separate from {@link this#setForceStatusBarVisible} because the ongoing
+ * process **takes priority**. For example, if {@link this#setForceStatusBarVisible} is set to
+ * false but this method is set to true, then the status bar **will** be visible.
+ *
+ * TODO(b/195839150): We should likely merge this method and {@link
+ * this#setForceStatusBarVisible} together and use some sort of ranking system instead.
+ */
+ fun setOngoingProcessRequiresStatusBarVisible(visible: Boolean)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerImpl.java
similarity index 84%
rename from packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java
rename to packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerImpl.java
index c30a6b7..1a0327c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerImpl.java
@@ -46,6 +46,8 @@
import android.view.WindowInsets;
import android.view.WindowManager;
+import androidx.annotation.NonNull;
+
import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
import com.android.internal.policy.SystemBarUtils;
import com.android.systemui.animation.ActivityTransitionAnimator;
@@ -67,7 +69,7 @@
* Encapsulates all logic for the status bar window state management.
*/
@SysUISingleton
-public class StatusBarWindowController {
+public class StatusBarWindowControllerImpl implements StatusBarWindowController {
private static final String TAG = "StatusBarWindowController";
private static final boolean DEBUG = false;
@@ -89,7 +91,7 @@
private final Binder mInsetsSourceOwner = new Binder();
@Inject
- public StatusBarWindowController(
+ public StatusBarWindowControllerImpl(
Context context,
@StatusBarWindowModule.InternalWindowView StatusBarWindowView statusBarWindowView,
ViewCaptureAwareWindowManager viewCaptureAwareWindowManager,
@@ -117,14 +119,12 @@
/* attachedViewProvider=*/ () -> mStatusBarWindowView)));
}
+ @Override
public int getStatusBarHeight() {
return mBarHeight;
}
- /**
- * Rereads the status bar height and reapplys the current state if the height
- * is different.
- */
+ @Override
public void refreshStatusBarHeight() {
Trace.beginSection("StatusBarWindowController#refreshStatusBarHeight");
try {
@@ -141,9 +141,7 @@
}
}
- /**
- * Adds the status bar view to the window manager.
- */
+ @Override
public void attach() {
// Now that the status bar window encompasses the sliding panel and its
// translucent backdrop, the entire thing is made TRANSLUCENT and is
@@ -161,54 +159,47 @@
apply(mCurrentState);
}
- /** Adds the given view to the status bar window view. */
- public void addViewToWindow(View view, ViewGroup.LayoutParams layoutParams) {
+ @Override
+ public void addViewToWindow(@NonNull View view, @NonNull ViewGroup.LayoutParams layoutParams) {
mStatusBarWindowView.addView(view, layoutParams);
}
- /** Returns the status bar window's background view. */
+ @NonNull
+ @Override
public View getBackgroundView() {
return mStatusBarWindowView.findViewById(R.id.status_bar_container);
}
- /** Returns a fragment host manager for the status bar window view. */
+ @NonNull
+ @Override
public FragmentHostManager getFragmentHostManager() {
return mFragmentService.getFragmentHostManager(mStatusBarWindowView);
}
- /**
- * Provides an updated animation controller if we're animating a view in the status bar.
- *
- * This is needed because we have to make sure that the status bar window matches the full
- * screen during the animation and that we are expanding the view below the other status bar
- * text.
- *
- * @param rootView the root view of the animation
- * @param animationController the default animation controller to use
- * @return If the animation is on a view in the status bar, returns an Optional containing an
- * updated animation controller that handles status-bar-related animation details. Returns an
- * empty optional if the animation is *not* on a view in the status bar.
- */
+ @NonNull
+ @Override
public Optional<ActivityTransitionAnimator.Controller> wrapAnimationControllerIfInStatusBar(
- View rootView, ActivityTransitionAnimator.Controller animationController) {
+ @NonNull View rootView,
+ @NonNull ActivityTransitionAnimator.Controller animationController) {
if (rootView != mStatusBarWindowView) {
return Optional.empty();
}
animationController.setTransitionContainer(mLaunchAnimationContainer);
- return Optional.of(new DelegateTransitionAnimatorController(animationController) {
- @Override
- public void onTransitionAnimationStart(boolean isExpandingFullyAbove) {
- getDelegate().onTransitionAnimationStart(isExpandingFullyAbove);
- setLaunchAnimationRunning(true);
- }
+ return Optional.of(
+ new DelegateTransitionAnimatorController(animationController) {
+ @Override
+ public void onTransitionAnimationStart(boolean isExpandingFullyAbove) {
+ getDelegate().onTransitionAnimationStart(isExpandingFullyAbove);
+ setLaunchAnimationRunning(true);
+ }
- @Override
- public void onTransitionAnimationEnd(boolean isExpandingFullyAbove) {
- getDelegate().onTransitionAnimationEnd(isExpandingFullyAbove);
- setLaunchAnimationRunning(false);
- }
- });
+ @Override
+ public void onTransitionAnimationEnd(boolean isExpandingFullyAbove) {
+ getDelegate().onTransitionAnimationEnd(isExpandingFullyAbove);
+ setLaunchAnimationRunning(false);
+ }
+ });
}
private WindowManager.LayoutParams getBarLayoutParams(int rotation) {
@@ -275,22 +266,13 @@
}
}
- /** Set force status bar visible. */
+ @Override
public void setForceStatusBarVisible(boolean forceStatusBarVisible) {
mCurrentState.mForceStatusBarVisible = forceStatusBarVisible;
apply(mCurrentState);
}
- /**
- * Sets whether an ongoing process requires the status bar to be forced visible.
- *
- * This method is separate from {@link this#setForceStatusBarVisible} because the ongoing
- * process **takes priority**. For example, if {@link this#setForceStatusBarVisible} is set to
- * false but this method is set to true, then the status bar **will** be visible.
- *
- * TODO(b/195839150): We should likely merge this method and
- * {@link this#setForceStatusBarVisible} together and use some sort of ranking system instead.
- */
+ @Override
public void setOngoingProcessRequiresStatusBarVisible(boolean visible) {
mCurrentState.mOngoingProcessRequiresStatusBarVisible = visible;
apply(mCurrentState);
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/domain/interactor/TouchpadGesturesInteractor.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/domain/interactor/TouchpadGesturesInteractor.kt
index 1a41987..80ea925 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/domain/interactor/TouchpadGesturesInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/domain/interactor/TouchpadGesturesInteractor.kt
@@ -30,12 +30,12 @@
private val logger: InputDeviceTutorialLogger,
) {
fun disableGestures() {
- logger.log("Disabling touchpad gestures across the system")
+ logger.d("Disabling touchpad gestures across the system")
setGesturesState(disabled = true)
}
fun enableGestures() {
- logger.log("Enabling touchpad gestures across the system")
+ logger.d("Enabling touchpad gestures across the system")
setGesturesState(disabled = false)
}
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/TouchpadTutorialActivity.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/TouchpadTutorialActivity.kt
index 6fe6547..d03b2e7 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/TouchpadTutorialActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/TouchpadTutorialActivity.kt
@@ -57,6 +57,7 @@
}
// required to handle 3+ fingers on touchpad
window.addPrivateFlags(WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY)
+ logger.logOpenTutorial(TutorialContext.TOUCHPAD_TUTORIAL)
}
private fun finishTutorial() {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index db4f9ef..7166428 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -35,7 +35,6 @@
import static com.android.internal.jank.InteractionJankMonitor.CUJ_VOLUME_CONTROL;
import static com.android.internal.jank.InteractionJankMonitor.Configuration.Builder;
import static com.android.settingslib.flags.Flags.volumeDialogAudioSharingFix;
-import static com.android.systemui.Flags.hapticVolumeSlider;
import static com.android.systemui.volume.Events.DISMISS_REASON_POSTURE_CHANGED;
import static com.android.systemui.volume.Events.DISMISS_REASON_SETTINGS_CLICKED;
@@ -928,10 +927,8 @@
}
private void addSliderHapticsToRow(VolumeRow row) {
- if (hapticVolumeSlider()) {
- row.createPlugin(mVibratorHelper, mSystemClock);
- HapticSliderViewBinder.bind(row.slider, row.mHapticPlugin);
- }
+ row.createPlugin(mVibratorHelper, mSystemClock);
+ HapticSliderViewBinder.bind(row.slider, row.mHapticPlugin);
}
@VisibleForTesting void addSliderHapticsToRows() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/PatternBouncerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/PatternBouncerTest.kt
index 4b61a0d..088bb02 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/PatternBouncerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/PatternBouncerTest.kt
@@ -25,6 +25,7 @@
import androidx.test.filters.LargeTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.bouncer.ui.viewmodel.patternBouncerViewModelFactory
+import com.android.systemui.haptics.msdl.bouncerHapticPlayer
import com.android.systemui.lifecycle.activateIn
import com.android.systemui.motion.createSysUiComposeMotionTestRule
import com.android.systemui.testKosmos
@@ -55,6 +56,7 @@
kosmos.patternBouncerViewModelFactory.create(
isInputEnabled = MutableStateFlow(true).asStateFlow(),
onIntentionalUserInput = {},
+ bouncerHapticPlayer = kosmos.bouncerHapticPlayer,
)
@Before
@@ -75,11 +77,11 @@
content = { play -> if (play) PatternBouncerUnderTest() },
ComposeRecordingSpec.until(
recordBefore = false,
- checkDone = { motionTestValueOfNode(MotionTestKeys.entryCompleted) }
+ checkDone = { motionTestValueOfNode(MotionTestKeys.entryCompleted) },
) {
feature(MotionTestKeys.dotAppearFadeIn, floatArray)
feature(MotionTestKeys.dotAppearMoveUp, floatArray)
- }
+ },
)
assertThat(motion).timeSeriesMatchesGolden()
@@ -100,7 +102,7 @@
viewModel.onDragEnd()
// Failure animation starts when animateFailure flips to true...
viewModel.animateFailure.takeWhile { !it }.collect {}
- }
+ },
) {
// ... and ends when the composable flips it back to false.
viewModel.animateFailure.takeWhile { it }.collect {}
@@ -111,7 +113,7 @@
content = { PatternBouncerUnderTest() },
ComposeRecordingSpec(failureAnimationMotionControl) {
feature(MotionTestKeys.dotScaling, floatArray)
- }
+ },
)
assertThat(motion).timeSeriesMatchesGolden()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java
index c65a117..d72b72c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java
@@ -32,6 +32,7 @@
import android.content.ClipDescription;
import android.content.ClipboardManager;
import android.os.PersistableBundle;
+import android.os.UserHandle;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.provider.Settings;
@@ -101,8 +102,18 @@
when(mClipboardManager.getPrimaryClip()).thenReturn(mSampleClipData);
when(mClipboardManager.getPrimaryClipSource()).thenReturn(mSampleSource);
- mClipboardListener = new ClipboardListener(getContext(), mOverlayControllerProvider,
- mClipboardToast, mClipboardManager, mKeyguardManager, mUiEventLogger);
+ mClipboardListener = new ClipboardListener(
+ getContext(),
+ mOverlayControllerProvider,
+ mClipboardToast,
+ user -> {
+ if (UserHandle.CURRENT.equals(user)) {
+ return mClipboardManager;
+ }
+ return null;
+ },
+ mKeyguardManager,
+ mUiEventLogger);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index af04309..c710c56 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -168,7 +168,7 @@
import com.android.systemui.statusbar.PulseExpansionHandler;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.StatusBarStateControllerImpl;
-import com.android.systemui.statusbar.core.StatusBarInitializer;
+import com.android.systemui.statusbar.core.StatusBarInitializerImpl;
import com.android.systemui.statusbar.data.repository.FakeStatusBarModeRepository;
import com.android.systemui.statusbar.notification.NotifPipelineFlags;
import com.android.systemui.statusbar.notification.NotificationActivityStarter;
@@ -504,7 +504,7 @@
mock(FragmentService.class),
mLightBarController,
mAutoHideController,
- new StatusBarInitializer(
+ new StatusBarInitializerImpl(
mStatusBarWindowController,
mCollapsedStatusBarFragmentProvider,
emptySet()),
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 1e2648b22..ecc7909 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
@@ -20,7 +20,6 @@
import static android.media.AudioManager.RINGER_MODE_SILENT;
import static android.media.AudioManager.RINGER_MODE_VIBRATE;
-import static com.android.systemui.Flags.FLAG_HAPTIC_VOLUME_SLIDER;
import static com.android.systemui.volume.Events.DISMISS_REASON_UNKNOWN;
import static com.android.systemui.volume.Events.SHOW_REASON_UNKNOWN;
import static com.android.systemui.volume.VolumeDialogControllerImpl.DYNAMIC_STREAM_BROADCAST;
@@ -51,7 +50,6 @@
import android.media.AudioManager;
import android.media.AudioSystem;
import android.os.SystemClock;
-import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.provider.Settings;
import android.testing.TestableLooper;
@@ -285,23 +283,8 @@
}
@Test
- @DisableFlags(FLAG_HAPTIC_VOLUME_SLIDER)
- public void addSliderHaptics_withHapticsDisabled_doesNotDeliverOnProgressChangedHaptics() {
- // GIVEN that the slider haptics flag is disabled and we try to add haptics to volume rows
- mDialog.addSliderHapticsToRows();
-
- // WHEN haptics try to be delivered to a volume stream
- boolean canDeliverHaptics =
- mDialog.canDeliverProgressHapticsToStream(AudioSystem.STREAM_MUSIC, true, 50);
-
- // THEN the result is that haptics are not successfully delivered
- assertFalse(canDeliverHaptics);
- }
-
- @Test
- @EnableFlags(FLAG_HAPTIC_VOLUME_SLIDER)
- public void addSliderHaptics_withHapticsEnabled_canDeliverOnProgressChangedHaptics() {
- // GIVEN that the slider haptics flag is enabled and we try to add haptics to volume rows
+ public void addSliderHaptics_canDeliverOnProgressChangedHaptics() {
+ // GIVEN that the slider haptics are added to rows
mDialog.addSliderHapticsToRows();
// WHEN haptics try to be delivered to a volume stream
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt
index 649e4e8..1b1d8c5 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt
@@ -25,6 +25,8 @@
import com.android.systemui.bouncer.domain.interactor.bouncerActionButtonInteractor
import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
import com.android.systemui.bouncer.domain.interactor.simBouncerInteractor
+import com.android.systemui.bouncer.ui.helper.BouncerHapticPlayer
+import com.android.systemui.haptics.msdl.bouncerHapticPlayer
import com.android.systemui.inputmethod.domain.interactor.inputMethodInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
@@ -34,9 +36,7 @@
import kotlinx.coroutines.flow.StateFlow
val Kosmos.bouncerUserActionsViewModel by Fixture {
- BouncerUserActionsViewModel(
- bouncerInteractor = bouncerInteractor,
- )
+ BouncerUserActionsViewModel(bouncerInteractor = bouncerInteractor)
}
val Kosmos.bouncerUserActionsViewModelFactory by Fixture {
@@ -59,6 +59,7 @@
pinViewModelFactory = pinBouncerViewModelFactory,
patternViewModelFactory = patternBouncerViewModelFactory,
passwordViewModelFactory = passwordBouncerViewModelFactory,
+ bouncerHapticPlayer = bouncerHapticPlayer,
)
}
@@ -76,6 +77,7 @@
isInputEnabled: StateFlow<Boolean>,
onIntentionalUserInput: () -> Unit,
authenticationMethod: AuthenticationMethodModel,
+ bouncerHapticPlayer: BouncerHapticPlayer,
): PinBouncerViewModel {
return PinBouncerViewModel(
applicationContext = applicationContext,
@@ -84,6 +86,7 @@
isInputEnabled = isInputEnabled,
onIntentionalUserInput = onIntentionalUserInput,
authenticationMethod = authenticationMethod,
+ bouncerHapticPlayer = bouncerHapticPlayer,
)
}
}
@@ -92,6 +95,7 @@
val Kosmos.patternBouncerViewModelFactory by Fixture {
object : PatternBouncerViewModel.Factory {
override fun create(
+ bouncerHapticPlayer: BouncerHapticPlayer,
isInputEnabled: StateFlow<Boolean>,
onIntentionalUserInput: () -> Unit,
): PatternBouncerViewModel {
@@ -100,6 +104,7 @@
interactor = bouncerInteractor,
isInputEnabled = isInputEnabled,
onIntentionalUserInput = onIntentionalUserInput,
+ bouncerHapticPlayer = bouncerHapticPlayer,
)
}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorKosmos.kt
index 1ed10fbe..8922b2f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorKosmos.kt
@@ -20,6 +20,7 @@
import com.android.systemui.deviceentry.data.repository.deviceEntryRepository
import com.android.systemui.flags.fakeSystemPropertiesHelper
import com.android.systemui.flags.systemPropertiesHelper
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.domain.interactor.trustInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
@@ -37,5 +38,6 @@
powerInteractor = powerInteractor,
biometricSettingsInteractor = deviceEntryBiometricSettingsInteractor,
systemPropertiesHelper = fakeSystemPropertiesHelper,
+ keyguardTransitionInteractor = keyguardTransitionInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/msdl/FakeMSDLPlayer.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/msdl/FakeMSDLPlayer.kt
index 5ad973a..2b81da3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/msdl/FakeMSDLPlayer.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/msdl/FakeMSDLPlayer.kt
@@ -20,8 +20,10 @@
import com.google.android.msdl.data.model.MSDLToken
import com.google.android.msdl.domain.InteractionProperties
import com.google.android.msdl.domain.MSDLPlayer
+import com.google.android.msdl.logging.MSDLEvent
class FakeMSDLPlayer : MSDLPlayer {
+ private val history = arrayListOf<MSDLEvent>()
var currentFeedbackLevel = FeedbackLevel.DEFAULT
var latestTokenPlayed: MSDLToken? = null
private set
@@ -34,5 +36,8 @@
override fun playToken(token: MSDLToken, properties: InteractionProperties?) {
latestTokenPlayed = token
latestPropertiesPlayed = properties
+ history.add(MSDLEvent(token, properties))
}
+
+ override fun getHistory(): List<MSDLEvent> = history
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/qs/QSLongPressEffectKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/qs/QSLongPressEffectKosmos.kt
index ca748b66..80db1e9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/qs/QSLongPressEffectKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/qs/QSLongPressEffectKosmos.kt
@@ -16,6 +16,7 @@
package com.android.systemui.haptics.qs
+import com.android.systemui.classifier.fakeFalsingManager
import com.android.systemui.haptics.vibratorHelper
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.log.core.FakeLogBuffer
@@ -26,6 +27,7 @@
QSLongPressEffect(
vibratorHelper,
keyguardStateController,
+ fakeFalsingManager,
FakeLogBuffer.Factory.create(),
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeRemoteInputRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeRemoteInputRepository.kt
index c416ea1..91602c2 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeRemoteInputRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeRemoteInputRepository.kt
@@ -16,8 +16,13 @@
package com.android.systemui.statusbar.data.repository
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.flowOf
class FakeRemoteInputRepository : RemoteInputRepository {
override val isRemoteInputActive = MutableStateFlow(false)
+ override val remoteInputRowBottomBound: Flow<Float?> = flowOf(null)
+
+ override fun setRemoteInputRowBottomBound(bottom: Float?) {}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModelKosmos.kt
index 6370a5d..7244d46 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModelKosmos.kt
@@ -22,6 +22,7 @@
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.statusbar.domain.interactor.remoteInputInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationStackAppearanceInteractor
val Kosmos.notificationScrollViewModel by Fixture {
@@ -29,6 +30,7 @@
dumpManager = dumpManager,
stackAppearanceInteractor = notificationStackAppearanceInteractor,
shadeInteractor = shadeInteractor,
+ remoteInputInteractor = remoteInputInteractor,
sceneInteractor = sceneInteractor,
keyguardInteractor = { keyguardInteractor },
)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt
index 8bfc390..e5cf0a9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt
@@ -22,6 +22,7 @@
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.statusbar.domain.interactor.remoteInputInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.headsUpNotificationInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationStackAppearanceInteractor
@@ -31,6 +32,7 @@
sceneInteractor = sceneInteractor,
shadeInteractor = shadeInteractor,
headsUpNotificationInteractor = headsUpNotificationInteractor,
+ remoteInputInteractor = remoteInputInteractor,
featureFlags = featureFlagsClassic,
dumpManager = dumpManager,
)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorKosmos.kt
index 61b53c9..99cd830 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorKosmos.kt
@@ -22,6 +22,8 @@
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.shared.notifications.data.repository.notificationSettingsRepository
+import com.android.systemui.statusbar.policy.data.repository.deviceProvisioningRepository
+import com.android.systemui.statusbar.policy.data.repository.userSetupRepository
import com.android.systemui.statusbar.policy.data.repository.zenModeRepository
val Kosmos.zenModeInteractor by Fixture {
@@ -31,5 +33,7 @@
notificationSettingsRepository = notificationSettingsRepository,
bgDispatcher = testDispatcher,
iconLoader = zenIconLoader,
+ deviceProvisioningRepository = deviceProvisioningRepository,
+ userSetupRepository = userSetupRepository,
)
}
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
index a100974..57b58d8 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
@@ -128,7 +128,11 @@
val currentDirection =
if (angle < lastHingeAngle) FOLD_UPDATE_START_CLOSING else FOLD_UPDATE_START_OPENING
- if (isTransitionInProgress && currentDirection != lastFoldUpdate) {
+ val changedDirectionWhileInTransition =
+ isTransitionInProgress && currentDirection != lastFoldUpdate
+ val unfoldedPastThresholdSinceLastTransition =
+ angle - lastHingeAngleBeforeTransition > HINGE_ANGLE_CHANGE_THRESHOLD_DEGREES
+ if (changedDirectionWhileInTransition || unfoldedPastThresholdSinceLastTransition) {
lastHingeAngleBeforeTransition = lastHingeAngle
}
@@ -153,7 +157,7 @@
isOnLargeScreen // Avoids sending closing event when on small screen.
// Start event is sent regardless due to hall sensor.
) {
- notifyFoldUpdate(transitionUpdate, lastHingeAngle)
+ notifyFoldUpdate(transitionUpdate, angle)
}
if (isTransitionInProgress) {
diff --git a/ravenwood/Android.bp b/ravenwood/Android.bp
index d1a3bf9..10e4f38 100644
--- a/ravenwood/Android.bp
+++ b/ravenwood/Android.bp
@@ -343,6 +343,8 @@
data: [
":framework-res",
":ravenwood-empty-res",
+ ":framework-platform-compat-config",
+ ":services-platform-compat-config",
],
libs: [
"100-framework-minus-apex.ravenwood",
diff --git a/services/companion/java/com/android/server/companion/virtual/OWNERS b/services/companion/java/com/android/server/companion/virtual/OWNERS
index 4fe0592..4b732ac 100644
--- a/services/companion/java/com/android/server/companion/virtual/OWNERS
+++ b/services/companion/java/com/android/server/companion/virtual/OWNERS
@@ -2,7 +2,9 @@
set noparent
-marvinramin@google.com
vladokom@google.com
+marvinramin@google.com
+caen@google.com
+biswarupp@google.com
ogunwale@google.com
michaelwr@google.com
\ No newline at end of file
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index f0cc09f..22ec790 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -73,6 +73,7 @@
import static android.media.audio.Flags.roForegroundAudioControl;
import static android.os.Process.THREAD_GROUP_BACKGROUND;
import static android.os.Process.THREAD_GROUP_DEFAULT;
+import static android.os.Process.THREAD_GROUP_FOREGROUND_WINDOW;
import static android.os.Process.THREAD_GROUP_RESTRICTED;
import static android.os.Process.THREAD_GROUP_TOP_APP;
import static android.os.Process.THREAD_PRIORITY_DISPLAY;
@@ -116,6 +117,7 @@
import static com.android.server.am.ProcessList.PREVIOUS_APP_ADJ;
import static com.android.server.am.ProcessList.SCHED_GROUP_BACKGROUND;
import static com.android.server.am.ProcessList.SCHED_GROUP_DEFAULT;
+import static com.android.server.am.ProcessList.SCHED_GROUP_FOREGROUND_WINDOW;
import static com.android.server.am.ProcessList.SCHED_GROUP_RESTRICTED;
import static com.android.server.am.ProcessList.SCHED_GROUP_TOP_APP;
import static com.android.server.am.ProcessList.SCHED_GROUP_TOP_APP_BOUND;
@@ -1731,6 +1733,11 @@
// The recently used non-top visible freeform app.
schedGroup = SCHED_GROUP_TOP_APP;
mAdjType = "perceptible-freeform-activity";
+ } else if ((flags
+ & WindowProcessController.ACTIVITY_STATE_FLAG_VISIBLE_MULTI_WINDOW_MODE) != 0) {
+ // Currently the only case is from freeform apps which are not close to top.
+ schedGroup = SCHED_GROUP_FOREGROUND_WINDOW;
+ mAdjType = "vis-multi-window-activity";
}
foregroundActivities = true;
mHasVisibleActivities = true;
@@ -3438,6 +3445,9 @@
case SCHED_GROUP_RESTRICTED:
processGroup = THREAD_GROUP_RESTRICTED;
break;
+ case SCHED_GROUP_FOREGROUND_WINDOW:
+ processGroup = THREAD_GROUP_FOREGROUND_WINDOW;
+ break;
default:
processGroup = THREAD_GROUP_DEFAULT;
break;
diff --git a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
index 21842db..fb1c2e9 100644
--- a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
+++ b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
@@ -331,7 +331,7 @@
void forEachNewNode(int slot, @NonNull Consumer<OomAdjusterArgs> callback) {
ProcessRecordNode node = mLastNode[slot].mNext;
final ProcessRecordNode tail = mProcessRecordNodes[slot].TAIL;
- while (node != tail) {
+ while (node != null && node != tail) {
mTmpOomAdjusterArgs.mApp = node.mApp;
if (node.mApp == null) {
// TODO(b/336178916) - Temporary logging for root causing b/336178916.
@@ -365,7 +365,9 @@
}
// Save the next before calling callback, since that may change the node.mNext.
final ProcessRecordNode next = node.mNext;
- callback.accept(mTmpOomAdjusterArgs);
+ if (mTmpOomAdjusterArgs.mApp != null) {
+ callback.accept(mTmpOomAdjusterArgs);
+ }
// There are couple of cases:
// a) The current node is moved to another slot
// - for this case, we'd need to keep using the "next" node.
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index cb918a0..00250b4 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -303,6 +303,9 @@
// Activity manager's version of Process.THREAD_GROUP_TOP_APP
// Disambiguate between actual top app and processes bound to the top app
static final int SCHED_GROUP_TOP_APP_BOUND = 4;
+ // Activity manager's version of Process.THREAD_GROUP_FOREGROUND_WINDOW
+ // The priority is like between default and top-app.
+ static final int SCHED_GROUP_FOREGROUND_WINDOW = 5;
// The minimum number of cached apps we want to be able to keep around,
// without empty apps being able to push them out of memory.
diff --git a/services/core/java/com/android/server/display/DisplayControl.java b/services/core/java/com/android/server/display/DisplayControl.java
index 38eb416..ddea285 100644
--- a/services/core/java/com/android/server/display/DisplayControl.java
+++ b/services/core/java/com/android/server/display/DisplayControl.java
@@ -109,7 +109,7 @@
/**
* Sets the HDR conversion mode for the device.
*
- * Returns the system preferred Hdr output type nn case when HDR conversion mode is
+ * Returns the system preferred HDR output type in case when HDR conversion mode is
* {@link android.hardware.display.HdrConversionMode#HDR_CONVERSION_SYSTEM}.
* Returns Hdr::INVALID in other cases.
* @hide
diff --git a/services/core/java/com/android/server/display/DisplayDeviceInfo.java b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
index 93bd926..acf4db3 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceInfo.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
@@ -318,13 +318,16 @@
*/
public Display.HdrCapabilities hdrCapabilities;
+ /** When true, all HDR capabilities are hidden from public APIs */
+ public boolean isForceSdr;
+
/**
* Indicates whether this display supports Auto Low Latency Mode.
*/
public boolean allmSupported;
/**
- * Indicates whether this display suppors Game content type.
+ * Indicates whether this display supports Game content type.
*/
public boolean gameContentTypeSupported;
@@ -516,6 +519,7 @@
|| !Arrays.equals(supportedModes, other.supportedModes)
|| !Arrays.equals(supportedColorModes, other.supportedColorModes)
|| !Objects.equals(hdrCapabilities, other.hdrCapabilities)
+ || isForceSdr != other.isForceSdr
|| allmSupported != other.allmSupported
|| gameContentTypeSupported != other.gameContentTypeSupported
|| densityDpi != other.densityDpi
@@ -560,6 +564,7 @@
colorMode = other.colorMode;
supportedColorModes = other.supportedColorModes;
hdrCapabilities = other.hdrCapabilities;
+ isForceSdr = other.isForceSdr;
allmSupported = other.allmSupported;
gameContentTypeSupported = other.gameContentTypeSupported;
densityDpi = other.densityDpi;
@@ -603,6 +608,7 @@
sb.append(", colorMode ").append(colorMode);
sb.append(", supportedColorModes ").append(Arrays.toString(supportedColorModes));
sb.append(", hdrCapabilities ").append(hdrCapabilities);
+ sb.append(", isForceSdr ").append(isForceSdr);
sb.append(", allmSupported ").append(allmSupported);
sb.append(", gameContentTypeSupported ").append(gameContentTypeSupported);
sb.append(", density ").append(densityDpi);
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 3c2167e..e7fd8f7 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -48,6 +48,7 @@
import static android.provider.Settings.Secure.RESOLUTION_MODE_FULL;
import static android.provider.Settings.Secure.RESOLUTION_MODE_HIGH;
import static android.provider.Settings.Secure.RESOLUTION_MODE_UNKNOWN;
+import static android.view.Display.HdrCapabilities.HDR_TYPE_INVALID;
import static com.android.server.display.layout.Layout.Display.POSITION_REAR;
@@ -284,7 +285,7 @@
@GuardedBy("mSyncRoot")
private int[] mUserDisabledHdrTypes = {};
@Display.HdrCapabilities.HdrType
- private int[] mSupportedHdrOutputType;
+ private int[] mSupportedHdrOutputTypes;
@GuardedBy("mSyncRoot")
private boolean mAreUserDisabledHdrTypesAllowed = true;
@@ -299,10 +300,10 @@
// HDR conversion mode chosen by user
@GuardedBy("mSyncRoot")
private HdrConversionMode mHdrConversionMode = null;
- // Actual HDR conversion mode, which takes app overrides into account.
- private HdrConversionMode mOverrideHdrConversionMode = null;
+ // Whether app has disabled HDR conversion
+ private boolean mShouldDisableHdrConversion = false;
@GuardedBy("mSyncRoot")
- private int mSystemPreferredHdrOutputType = Display.HdrCapabilities.HDR_TYPE_INVALID;
+ private int mSystemPreferredHdrOutputType = HDR_TYPE_INVALID;
// The synchronization root for the display manager.
@@ -1419,7 +1420,8 @@
}
}
- private void setUserDisabledHdrTypesInternal(int[] userDisabledHdrTypes) {
+ @VisibleForTesting
+ void setUserDisabledHdrTypesInternal(int[] userDisabledHdrTypes) {
synchronized (mSyncRoot) {
if (userDisabledHdrTypes == null) {
Slog.e(TAG, "Null is not an expected argument to "
@@ -1437,6 +1439,7 @@
if (Arrays.equals(mUserDisabledHdrTypes, userDisabledHdrTypes)) {
return;
}
+
String userDisabledFormatsString = "";
if (userDisabledHdrTypes.length != 0) {
userDisabledFormatsString = TextUtils.join(",",
@@ -1452,6 +1455,15 @@
handleLogicalDisplayChangedLocked(display);
});
}
+ /* Note: it may be expected to reset the Conversion Mode when an HDR type is enabled
+ and the Conversion Mode is set to System Preferred. This is handled in the Settings
+ code because in the special case where HDR is indirectly disabled by Force SDR
+ Conversion, manually enabling HDR is not recognized as an action that reduces the
+ disabled HDR count. Thus, this case needs to be checked in the Settings code when we
+ know we're enabling an HDR mode. If we split checking for SystemConversion and
+ isForceSdr in two places, we may have duplicate calls to resetting to System Conversion
+ and get two black screens.
+ */
}
}
@@ -1464,19 +1476,20 @@
return true;
}
- private void setAreUserDisabledHdrTypesAllowedInternal(
+ @VisibleForTesting
+ void setAreUserDisabledHdrTypesAllowedInternal(
boolean areUserDisabledHdrTypesAllowed) {
synchronized (mSyncRoot) {
if (mAreUserDisabledHdrTypesAllowed == areUserDisabledHdrTypesAllowed) {
return;
}
mAreUserDisabledHdrTypesAllowed = areUserDisabledHdrTypesAllowed;
- if (mUserDisabledHdrTypes.length == 0) {
- return;
- }
Settings.Global.putInt(mContext.getContentResolver(),
Settings.Global.ARE_USER_DISABLED_HDR_FORMATS_ALLOWED,
areUserDisabledHdrTypesAllowed ? 1 : 0);
+ if (mUserDisabledHdrTypes.length == 0) {
+ return;
+ }
int userDisabledHdrTypes[] = {};
if (!mAreUserDisabledHdrTypesAllowed) {
userDisabledHdrTypes = mUserDisabledHdrTypes;
@@ -1487,6 +1500,14 @@
display.setUserDisabledHdrTypes(finalUserDisabledHdrTypes);
handleLogicalDisplayChangedLocked(display);
});
+ // When HDR conversion mode is set to SYSTEM, modification to
+ // areUserDisabledHdrTypesAllowed requires refreshing the HDR conversion mode to tell
+ // the system which HDR types it is not allowed to use.
+ if (getHdrConversionModeInternal().getConversionMode()
+ == HdrConversionMode.HDR_CONVERSION_SYSTEM) {
+ setHdrConversionModeInternal(
+ new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_SYSTEM));
+ }
}
}
@@ -2357,7 +2378,7 @@
final int preferredHdrOutputType =
hdrConversionMode.getConversionMode() == HdrConversionMode.HDR_CONVERSION_FORCE
? hdrConversionMode.getPreferredHdrOutputType()
- : Display.HdrCapabilities.HDR_TYPE_INVALID;
+ : HDR_TYPE_INVALID;
Settings.Global.putInt(mContext.getContentResolver(),
Settings.Global.HDR_FORCE_CONVERSION_TYPE, preferredHdrOutputType);
}
@@ -2370,7 +2391,7 @@
? Settings.Global.getInt(mContext.getContentResolver(),
Settings.Global.HDR_FORCE_CONVERSION_TYPE,
Display.HdrCapabilities.HDR_TYPE_DOLBY_VISION)
- : Display.HdrCapabilities.HDR_TYPE_INVALID;
+ : HDR_TYPE_INVALID;
mHdrConversionMode = new HdrConversionMode(conversionMode, preferredHdrOutputType);
setHdrConversionModeInternal(mHdrConversionMode);
}
@@ -2507,22 +2528,38 @@
});
}
+ /**
+ * Returns the HDR output types that are supported by the device's HDR conversion capabilities,
+ * stripping out any user-disabled HDR types if mAreUserDisabledHdrTypesAllowed is false.
+ */
@GuardedBy("mSyncRoot")
- private int[] getEnabledAutoHdrTypesLocked() {
- IntArray autoHdrOutputTypesArray = new IntArray();
+ @VisibleForTesting
+ int[] getEnabledHdrOutputTypesLocked() {
+ if (mAreUserDisabledHdrTypesAllowed) {
+ return getSupportedHdrOutputTypesInternal();
+ }
+ // Strip out all HDR formats that are currently user-disabled
+ IntArray enabledHdrOutputTypesArray = new IntArray();
for (int type : getSupportedHdrOutputTypesInternal()) {
- boolean isDisabled = false;
+ boolean isEnabled = true;
for (int disabledType : mUserDisabledHdrTypes) {
if (type == disabledType) {
- isDisabled = true;
+ isEnabled = false;
break;
}
}
- if (!isDisabled) {
- autoHdrOutputTypesArray.add(type);
+ if (isEnabled) {
+ enabledHdrOutputTypesArray.add(type);
}
}
- return autoHdrOutputTypesArray.toArray();
+ return enabledHdrOutputTypesArray.toArray();
+ }
+
+ @VisibleForTesting
+ int[] getEnabledHdrOutputTypes() {
+ synchronized (mSyncRoot) {
+ return getEnabledHdrOutputTypesLocked();
+ }
}
@GuardedBy("mSyncRoot")
@@ -2531,7 +2568,7 @@
final int preferredHdrOutputType =
mode.getConversionMode() == HdrConversionMode.HDR_CONVERSION_SYSTEM
? mSystemPreferredHdrOutputType : mode.getPreferredHdrOutputType();
- if (preferredHdrOutputType != Display.HdrCapabilities.HDR_TYPE_INVALID) {
+ if (preferredHdrOutputType != HDR_TYPE_INVALID) {
int[] hdrTypesWithLatency = mInjector.getHdrOutputTypesWithLatency();
return ArrayUtils.contains(hdrTypesWithLatency, preferredHdrOutputType);
}
@@ -2565,41 +2602,57 @@
if (!mInjector.getHdrOutputConversionSupport()) {
return;
}
- int[] autoHdrOutputTypes = null;
+
synchronized (mSyncRoot) {
if (hdrConversionMode.getConversionMode() == HdrConversionMode.HDR_CONVERSION_SYSTEM
&& hdrConversionMode.getPreferredHdrOutputType()
- != Display.HdrCapabilities.HDR_TYPE_INVALID) {
+ != HDR_TYPE_INVALID) {
throw new IllegalArgumentException("preferredHdrOutputType must not be set if"
+ " the conversion mode is HDR_CONVERSION_SYSTEM");
}
mHdrConversionMode = hdrConversionMode;
storeHdrConversionModeLocked(mHdrConversionMode);
- // For auto mode, all supported HDR types are allowed except the ones specifically
- // disabled by the user.
+ // If the HDR conversion is HDR_CONVERSION_SYSTEM, all supported HDR types are allowed
+ // except the ones specifically disabled by the user.
+ int[] enabledHdrOutputTypes = null;
if (hdrConversionMode.getConversionMode() == HdrConversionMode.HDR_CONVERSION_SYSTEM) {
- autoHdrOutputTypes = getEnabledAutoHdrTypesLocked();
+ enabledHdrOutputTypes = getEnabledHdrOutputTypesLocked();
}
int conversionMode = hdrConversionMode.getConversionMode();
int preferredHdrType = hdrConversionMode.getPreferredHdrOutputType();
+
// If the HDR conversion is disabled by an app through WindowManager.LayoutParams, then
// set HDR conversion mode to HDR_CONVERSION_PASSTHROUGH.
- if (mOverrideHdrConversionMode == null) {
- // HDR_CONVERSION_FORCE with HDR_TYPE_INVALID is used to represent forcing SDR type.
- // But, internally SDR is selected by using passthrough mode.
- if (conversionMode == HdrConversionMode.HDR_CONVERSION_FORCE
- && preferredHdrType == Display.HdrCapabilities.HDR_TYPE_INVALID) {
- conversionMode = HdrConversionMode.HDR_CONVERSION_PASSTHROUGH;
- }
+ if (mShouldDisableHdrConversion) {
+ conversionMode = HdrConversionMode.HDR_CONVERSION_PASSTHROUGH;
+ preferredHdrType = -1;
+ enabledHdrOutputTypes = null;
} else {
- conversionMode = mOverrideHdrConversionMode.getConversionMode();
- preferredHdrType = mOverrideHdrConversionMode.getPreferredHdrOutputType();
- autoHdrOutputTypes = null;
+ // HDR_CONVERSION_FORCE with HDR_TYPE_INVALID is used to represent forcing SDR type.
+ // But, internally SDR is forced by using passthrough mode and not reporting any
+ // HDR capabilities to apps.
+ if (conversionMode == HdrConversionMode.HDR_CONVERSION_FORCE
+ && preferredHdrType == HDR_TYPE_INVALID) {
+ conversionMode = HdrConversionMode.HDR_CONVERSION_PASSTHROUGH;
+ mLogicalDisplayMapper.forEachLocked(
+ logicalDisplay -> {
+ if (logicalDisplay.setIsForceSdr(true)) {
+ handleLogicalDisplayChangedLocked(logicalDisplay);
+ }
+ });
+ } else {
+ mLogicalDisplayMapper.forEachLocked(
+ logicalDisplay -> {
+ if (logicalDisplay.setIsForceSdr(false)) {
+ handleLogicalDisplayChangedLocked(logicalDisplay);
+ }
+ });
+ }
}
mSystemPreferredHdrOutputType = mInjector.setHdrConversionMode(
- conversionMode, preferredHdrType, autoHdrOutputTypes);
+ conversionMode, preferredHdrType, enabledHdrOutputTypes);
}
}
@@ -2621,8 +2674,8 @@
}
HdrConversionMode mode;
synchronized (mSyncRoot) {
- mode = mOverrideHdrConversionMode != null
- ? mOverrideHdrConversionMode
+ mode = mShouldDisableHdrConversion
+ ? new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_PASSTHROUGH)
: mHdrConversionMode;
// Handle default: PASSTHROUGH. Don't include the system-preferred type.
if (mode == null
@@ -2630,8 +2683,6 @@
return new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_PASSTHROUGH);
}
// Handle default or current mode: SYSTEM. Include the system preferred type.
- // mOverrideHdrConversionMode and mHdrConversionMode do not include the system
- // preferred type, it is kept separately in mSystemPreferredHdrOutputType.
if (mode == null
|| mode.getConversionMode() == HdrConversionMode.HDR_CONVERSION_SYSTEM) {
return new HdrConversionMode(
@@ -2642,10 +2693,10 @@
}
private @Display.HdrCapabilities.HdrType int[] getSupportedHdrOutputTypesInternal() {
- if (mSupportedHdrOutputType == null) {
- mSupportedHdrOutputType = mInjector.getSupportedHdrOutputTypes();
+ if (mSupportedHdrOutputTypes == null) {
+ mSupportedHdrOutputTypes = mInjector.getSupportedHdrOutputTypes();
}
- return mSupportedHdrOutputType;
+ return mSupportedHdrOutputTypes;
}
void setShouldAlwaysRespectAppRequestedModeInternal(boolean enabled) {
@@ -2831,15 +2882,9 @@
// HDR conversion is disabled in two cases:
// - HDR conversion introduces latency and minimal post-processing is requested
// - app requests to disable HDR conversion
- if (mOverrideHdrConversionMode == null && (disableHdrConversion
- || disableHdrConversionForLatency)) {
- mOverrideHdrConversionMode =
- new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_PASSTHROUGH);
- setHdrConversionModeInternal(mHdrConversionMode);
- handleLogicalDisplayChangedLocked(display);
- } else if (mOverrideHdrConversionMode != null && !disableHdrConversion
- && !disableHdrConversionForLatency) {
- mOverrideHdrConversionMode = null;
+ boolean previousShouldDisableHdrConversion = mShouldDisableHdrConversion;
+ mShouldDisableHdrConversion = disableHdrConversion || disableHdrConversionForLatency;
+ if (previousShouldDisableHdrConversion != mShouldDisableHdrConversion) {
setHdrConversionModeInternal(mHdrConversionMode);
handleLogicalDisplayChangedLocked(display);
}
@@ -3530,9 +3575,9 @@
}
int setHdrConversionMode(int conversionMode, int preferredHdrOutputType,
- int[] autoHdrTypes) {
+ int[] allowedHdrOutputTypes) {
return DisplayControl.setHdrConversionMode(conversionMode, preferredHdrOutputType,
- autoHdrTypes);
+ allowedHdrOutputTypes);
}
@Display.HdrCapabilities.HdrType
diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index e8be8a4..007e3a8 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -518,6 +518,7 @@
deviceInfo.supportedColorModes,
deviceInfo.supportedColorModes.length);
mBaseDisplayInfo.hdrCapabilities = deviceInfo.hdrCapabilities;
+ mBaseDisplayInfo.isForceSdr = deviceInfo.isForceSdr;
mBaseDisplayInfo.userDisabledHdrTypes = mUserDisabledHdrTypes;
mBaseDisplayInfo.minimalPostProcessingSupported =
deviceInfo.allmSupported || deviceInfo.gameContentTypeSupported;
@@ -899,6 +900,29 @@
}
/**
+ * Checks whether display is of the type where HDR settings are relevant, and then sets
+ * whether Force SDR conversion mode is active. isForceSdr is checked by the Display when
+ * returning HDR capabilities.
+ *
+ * @param isForceSdr Whether Force SDR conversion mode is active
+ * @return Whether Display Manager should call handleLogicalDisplayChangedLocked()
+ */
+ public boolean setIsForceSdr(boolean isForceSdr) {
+ int displayType = getDisplayInfoLocked().type;
+ boolean isTargetDisplayType = displayType == Display.TYPE_INTERNAL
+ || displayType == Display.TYPE_EXTERNAL
+ || displayType == Display.TYPE_OVERLAY;
+
+ boolean handleLogicalDisplayChangedLocked = false;
+ if (isTargetDisplayType && mBaseDisplayInfo.isForceSdr != isForceSdr) {
+ mBaseDisplayInfo.isForceSdr = isForceSdr;
+ mInfo.set(null);
+ handleLogicalDisplayChangedLocked = true;
+ }
+ return handleLogicalDisplayChangedLocked;
+ }
+
+ /**
* Swap the underlying {@link DisplayDevice} with the specified LogicalDisplay.
*
* @param targetDisplay The display with which to swap display-devices.
diff --git a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
index ea240c7..9d04682 100644
--- a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
+++ b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
@@ -196,12 +196,7 @@
File signatureFile = new File(dir, FONT_SIGNATURE_FILE);
if (!signatureFile.exists()) {
Slog.i(TAG, "The signature file is missing.");
- if (com.android.text.flags.Flags.fixFontUpdateFailure()) {
- return;
- } else {
- FileUtils.deleteContentsAndDir(dir);
- continue;
- }
+ return;
}
byte[] signature;
try {
@@ -226,39 +221,33 @@
FontFileInfo fontFileInfo = validateFontFile(fontFile, signature);
if (fontConfig == null) {
- if (com.android.text.flags.Flags.fixFontUpdateFailure()) {
- // Use preinstalled font config for checking revision number.
- fontConfig = mConfigSupplier.apply(Collections.emptyMap());
- } else {
- fontConfig = getSystemFontConfig();
- }
+ // Use preinstalled font config for checking revision number.
+ fontConfig = mConfigSupplier.apply(Collections.emptyMap());
}
addFileToMapIfSameOrNewer(fontFileInfo, fontConfig, true /* deleteOldFile */);
}
- if (com.android.text.flags.Flags.fixFontUpdateFailure()) {
- // Treat as error if post script name of font family was not installed.
- for (int i = 0; i < config.fontFamilies.size(); ++i) {
- FontUpdateRequest.Family family = config.fontFamilies.get(i);
- for (int j = 0; j < family.getFonts().size(); ++j) {
- FontUpdateRequest.Font font = family.getFonts().get(j);
- if (mFontFileInfoMap.containsKey(font.getPostScriptName())) {
- continue;
- }
-
- if (fontConfig == null) {
- fontConfig = mConfigSupplier.apply(Collections.emptyMap());
- }
-
- if (getFontByPostScriptName(font.getPostScriptName(), fontConfig) != null) {
- continue;
- }
-
- Slog.e(TAG, "Unknown font that has PostScript name "
- + font.getPostScriptName() + " is requested in FontFamily "
- + family.getName());
- return;
+ // Treat as error if post script name of font family was not installed.
+ for (int i = 0; i < config.fontFamilies.size(); ++i) {
+ FontUpdateRequest.Family family = config.fontFamilies.get(i);
+ for (int j = 0; j < family.getFonts().size(); ++j) {
+ FontUpdateRequest.Font font = family.getFonts().get(j);
+ if (mFontFileInfoMap.containsKey(font.getPostScriptName())) {
+ continue;
}
+
+ if (fontConfig == null) {
+ fontConfig = mConfigSupplier.apply(Collections.emptyMap());
+ }
+
+ if (getFontByPostScriptName(font.getPostScriptName(), fontConfig) != null) {
+ continue;
+ }
+
+ Slog.e(TAG, "Unknown font that has PostScript name "
+ + font.getPostScriptName() + " is requested in FontFamily "
+ + family.getName());
+ return;
}
}
@@ -273,9 +262,7 @@
mFontFileInfoMap.clear();
mLastModifiedMillis = 0;
FileUtils.deleteContents(mFilesDir);
- if (com.android.text.flags.Flags.fixFontUpdateFailure()) {
- mConfigFile.delete();
- }
+ mConfigFile.delete();
}
}
}
diff --git a/services/core/java/com/android/server/input/debug/TouchpadVisualizationView.java b/services/core/java/com/android/server/input/debug/TouchpadVisualizationView.java
index 67c3621..2eed9ba 100644
--- a/services/core/java/com/android/server/input/debug/TouchpadVisualizationView.java
+++ b/services/core/java/com/android/server/input/debug/TouchpadVisualizationView.java
@@ -27,20 +27,28 @@
import com.android.server.input.TouchpadHardwareProperties;
import com.android.server.input.TouchpadHardwareState;
+import java.util.ArrayDeque;
+import java.util.HashMap;
+import java.util.Map;
+
public class TouchpadVisualizationView extends View {
private static final String TAG = "TouchpadVizMain";
private static final boolean DEBUG = true;
private static final float DEFAULT_RES_X = 47f;
private static final float DEFAULT_RES_Y = 45f;
+ private static final float MAX_TRACE_HISTORY_DURATION_SECONDS = 1f;
private final TouchpadHardwareProperties mTouchpadHardwareProperties;
private float mScaleFactor;
- TouchpadHardwareState mLatestHardwareState = new TouchpadHardwareState(0, 0, 0, 0,
- new TouchpadFingerState[]{});
+ private final ArrayDeque<TouchpadHardwareState> mHardwareStateHistory =
+ new ArrayDeque<TouchpadHardwareState>();
+ private final Map<Integer, TouchpadFingerState> mTempFingerStatesByTrackingId = new HashMap<>();
private final Paint mOvalStrokePaint;
private final Paint mOvalFillPaint;
+ private final Paint mTracePaint;
+ private final Paint mCenterPointPaint;
private final RectF mTempOvalRect = new RectF();
public TouchpadVisualizationView(Context context,
@@ -55,6 +63,29 @@
mOvalFillPaint = new Paint();
mOvalFillPaint.setAntiAlias(true);
mOvalFillPaint.setARGB(255, 0, 0, 0);
+ mTracePaint = new Paint();
+ mTracePaint.setAntiAlias(false);
+ mTracePaint.setARGB(255, 0, 0, 255);
+ mTracePaint.setStyle(Paint.Style.STROKE);
+ mTracePaint.setStrokeWidth(2);
+ mCenterPointPaint = new Paint();
+ mCenterPointPaint.setAntiAlias(true);
+ mCenterPointPaint.setARGB(255, 255, 0, 0);
+ mCenterPointPaint.setStrokeWidth(2);
+ }
+
+ private void removeOldPoints() {
+ float latestTimestamp = mHardwareStateHistory.getLast().getTimestamp();
+
+ while (!mHardwareStateHistory.isEmpty()) {
+ TouchpadHardwareState oldestPoint = mHardwareStateHistory.getFirst();
+ float onScreenTime = latestTimestamp - oldestPoint.getTimestamp();
+ if (onScreenTime >= MAX_TRACE_HISTORY_DURATION_SECONDS) {
+ mHardwareStateHistory.removeFirst();
+ } else {
+ break;
+ }
+ }
}
private void drawOval(Canvas canvas, float x, float y, float major, float minor, float angle) {
@@ -71,19 +102,22 @@
@Override
protected void onDraw(Canvas canvas) {
+ if (mHardwareStateHistory.isEmpty()) {
+ return;
+ }
+
+ TouchpadHardwareState latestHardwareState = mHardwareStateHistory.getLast();
+
float maximumPressure = 0;
- for (TouchpadFingerState touchpadFingerState : mLatestHardwareState.getFingerStates()) {
+ for (TouchpadFingerState touchpadFingerState : latestHardwareState.getFingerStates()) {
maximumPressure = Math.max(maximumPressure, touchpadFingerState.getPressure());
}
- for (TouchpadFingerState touchpadFingerState : mLatestHardwareState.getFingerStates()) {
- float newX = translateRange(mTouchpadHardwareProperties.getLeft(),
- mTouchpadHardwareProperties.getRight(), 0, getWidth(),
- touchpadFingerState.getPositionX());
+ // Visualizing fingers as ovals
+ for (TouchpadFingerState touchpadFingerState : latestHardwareState.getFingerStates()) {
+ float newX = translateX(touchpadFingerState.getPositionX());
- float newY = translateRange(mTouchpadHardwareProperties.getTop(),
- mTouchpadHardwareProperties.getBottom(), 0, getHeight(),
- touchpadFingerState.getPositionY());
+ float newY = translateY(touchpadFingerState.getPositionY());
float newAngle = translateRange(0, mTouchpadHardwareProperties.getOrientationMaximum(),
0, 90, touchpadFingerState.getOrientation());
@@ -102,6 +136,28 @@
drawOval(canvas, newX, newY, newTouchMajor, newTouchMinor, newAngle);
}
+
+ mTempFingerStatesByTrackingId.clear();
+
+ // Drawing the trace
+ for (TouchpadHardwareState currentHardwareState : mHardwareStateHistory) {
+ for (TouchpadFingerState currentFingerState : currentHardwareState.getFingerStates()) {
+ TouchpadFingerState prevFingerState = mTempFingerStatesByTrackingId.put(
+ currentFingerState.getTrackingId(), currentFingerState);
+
+ if (prevFingerState == null) {
+ continue;
+ }
+
+ float currentX = translateX(currentFingerState.getPositionX());
+ float currentY = translateY(currentFingerState.getPositionY());
+ float prevX = translateX(prevFingerState.getPositionX());
+ float prevY = translateY(prevFingerState.getPositionY());
+
+ canvas.drawLine(prevX, prevY, currentX, currentY, mTracePaint);
+ canvas.drawPoint(currentX, currentY, mCenterPointPaint);
+ }
+ }
}
/**
@@ -114,7 +170,18 @@
logHardwareState(schs);
}
- mLatestHardwareState = schs;
+ if (!mHardwareStateHistory.isEmpty()
+ && mHardwareStateHistory.getLast().getFingerCount() == 0
+ && schs.getFingerCount() > 0) {
+ mHardwareStateHistory.clear();
+ }
+
+ mHardwareStateHistory.addLast(schs);
+ removeOldPoints();
+
+ if (DEBUG) {
+ logFingerTrace();
+ }
invalidate();
}
@@ -128,6 +195,16 @@
mScaleFactor = scaleFactor;
}
+ private float translateX(float x) {
+ return translateRange(mTouchpadHardwareProperties.getLeft(),
+ mTouchpadHardwareProperties.getRight(), 0, getWidth(), x);
+ }
+
+ private float translateY(float y) {
+ return translateRange(mTouchpadHardwareProperties.getTop(),
+ mTouchpadHardwareProperties.getBottom(), 0, getHeight(), y);
+ }
+
private float translateRange(float rangeBeforeMin, float rangeBeforeMax,
float rangeAfterMin, float rangeAfterMax, float value) {
return rangeAfterMin + (value - rangeBeforeMin) / (rangeBeforeMax - rangeBeforeMin) * (
@@ -154,4 +231,10 @@
}
}
-}
+ private void logFingerTrace() {
+ Slog.d(TAG, "Trace size= " + mHardwareStateHistory.size());
+ for (TouchpadFingerState tfs : mHardwareStateHistory.getLast().getFingerStates()) {
+ Slog.d(TAG, "ID= " + tfs.getTrackingId());
+ }
+ }
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/notification/VibratorHelper.java b/services/core/java/com/android/server/notification/VibratorHelper.java
index fbe7772..f54c1f7 100644
--- a/services/core/java/com/android/server/notification/VibratorHelper.java
+++ b/services/core/java/com/android/server/notification/VibratorHelper.java
@@ -218,10 +218,16 @@
* @param uri {@code Uri} an uri including query parameter "vibraiton_uri"
*/
public @Nullable VibrationEffect createVibrationEffectFromSoundUri(Uri uri) {
- if (uri == null) {
+ if (uri == null || uri.isOpaque()) {
return null;
}
- return Utils.parseVibrationEffect(mVibrator, Utils.getVibrationUri(uri));
+
+ try {
+ return Utils.parseVibrationEffect(mVibrator, Utils.getVibrationUri(uri));
+ } catch (Exception e) {
+ Slog.e(TAG, "Failed to get vibration effect: ", e);
+ }
+ return null;
}
/** Returns if a given vibration can be played by the vibrator that does notification buzz. */
diff --git a/services/core/java/com/android/server/pm/BackgroundUserSoundNotifier.java b/services/core/java/com/android/server/pm/BackgroundUserSoundNotifier.java
index 4135161..5aea356 100644
--- a/services/core/java/com/android/server/pm/BackgroundUserSoundNotifier.java
+++ b/services/core/java/com/android/server/pm/BackgroundUserSoundNotifier.java
@@ -18,6 +18,7 @@
import static android.media.AudioAttributes.USAGE_ALARM;
+import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.app.ActivityManager;
import android.app.Notification;
@@ -42,6 +43,8 @@
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
+import java.util.List;
+
public class BackgroundUserSoundNotifier {
private static final boolean DEBUG = false;
@@ -49,11 +52,21 @@
private static final String BUSN_CHANNEL_ID = "bg_user_sound_channel";
private static final String BUSN_CHANNEL_NAME = "BackgroundUserSound";
public static final String ACTION_MUTE_SOUND = "com.android.server.ACTION_MUTE_BG_USER";
- private static final String EXTRA_NOTIFICATION_ID = "com.android.server.EXTRA_CLIENT_UID";
- private static final String EXTRA_CURRENT_USER_ID = "com.android.server.EXTRA_CURRENT_USER_ID";
private static final String ACTION_SWITCH_USER = "com.android.server.ACTION_SWITCH_TO_USER";
- /** ID of user with notification displayed, -1 if notification is not showing*/
- private int mUserWithNotification = -1;
+ private static final String ACTION_DISMISS_NOTIFICATION =
+ "com.android.server.ACTION_DISMISS_NOTIFICATION";
+ /**
+ * The clientUid from the AudioFocusInfo of the background user,
+ * for which an active notification is currently displayed.
+ * Set to -1 if no notification is being shown.
+ * TODO: b/367615180 - add support for multiple simultaneous alarms
+ */
+ @VisibleForTesting
+ int mNotificationClientUid = -1;
+ @VisibleForTesting
+ AudioPolicy mFocusControlAudioPolicy;
+ @VisibleForTesting
+ BackgroundUserListener mBgUserListener;
private final Context mSystemUserContext;
@VisibleForTesting
final NotificationManager mNotificationManager;
@@ -67,11 +80,18 @@
mSystemUserContext = context;
mNotificationManager = mSystemUserContext.getSystemService(NotificationManager.class);
mUserManager = mSystemUserContext.getSystemService(UserManager.class);
+ createNotificationChannel();
+ setupFocusControlAudioPolicy();
+ }
+
+ /**
+ * Creates a dedicated channel for background user related notifications.
+ */
+ private void createNotificationChannel() {
NotificationChannel channel = new NotificationChannel(BUSN_CHANNEL_ID, BUSN_CHANNEL_NAME,
NotificationManager.IMPORTANCE_HIGH);
channel.setSound(null, null);
mNotificationManager.createNotificationChannel(channel);
- setupFocusControlAudioPolicy();
}
private void setupFocusControlAudioPolicy() {
@@ -81,15 +101,16 @@
ActivityManager am = mSystemUserContext.getSystemService(ActivityManager.class);
registerReceiver(am);
- BackgroundUserListener bgUserListener = new BackgroundUserListener(mSystemUserContext);
+ mBgUserListener = new BackgroundUserListener(mSystemUserContext);
AudioPolicy.Builder focusControlPolicyBuilder = new AudioPolicy.Builder(mSystemUserContext);
focusControlPolicyBuilder.setLooper(Looper.getMainLooper());
- focusControlPolicyBuilder.setAudioPolicyFocusListener(bgUserListener);
+ focusControlPolicyBuilder.setAudioPolicyFocusListener(mBgUserListener);
- AudioPolicy mFocusControlAudioPolicy = focusControlPolicyBuilder.build();
+ mFocusControlAudioPolicy = focusControlPolicyBuilder.build();
int status = mSystemUserContext.getSystemService(AudioManager.class)
.registerAudioPolicy(mFocusControlAudioPolicy);
+
if (status != AudioManager.SUCCESS) {
Log.w(LOG_TAG , "Could not register the service's focus"
+ " control audio policy, error: " + status);
@@ -117,123 +138,170 @@
@SuppressLint("MissingPermission")
public void onAudioFocusLoss(AudioFocusInfo afi, boolean wasNotified) {
- BackgroundUserSoundNotifier.this.dismissNotificationIfNecessary(afi);
+ BackgroundUserSoundNotifier.this.dismissNotificationIfNecessary();
}
}
+ @VisibleForTesting
+ BackgroundUserListener getAudioPolicyFocusListener() {
+ return mBgUserListener;
+ }
+
/**
* Registers a BroadcastReceiver for actions related to background user sound notifications.
* When ACTION_MUTE_SOUND is received, it mutes a background user's alarm sound.
* When ACTION_SWITCH_USER is received, a switch to the background user with alarm is started.
*/
- private void registerReceiver(ActivityManager service) {
+ private void registerReceiver(ActivityManager activityManager) {
BroadcastReceiver backgroundUserNotificationBroadcastReceiver = new BroadcastReceiver() {
@SuppressLint("MissingPermission")
@Override
public void onReceive(Context context, Intent intent) {
- if (!(intent.hasExtra(EXTRA_NOTIFICATION_ID)
- && intent.hasExtra(EXTRA_CURRENT_USER_ID)
- && intent.hasExtra(Intent.EXTRA_USER_ID))) {
+ if (mNotificationClientUid == -1) {
return;
}
- final int notificationId = intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1);
+ dismissNotification();
if (DEBUG) {
- Log.d(LOG_TAG,
- "User with alarm id " + intent.getIntExtra(Intent.EXTRA_USER_ID,
- -1) + " current user id " + intent.getIntExtra(
- EXTRA_CURRENT_USER_ID, -1));
+ final int actionIndex = intent.getAction().lastIndexOf(".") + 1;
+ final String action = intent.getAction().substring(actionIndex);
+ Log.d(LOG_TAG, "Action requested: " + action + ", by userId "
+ + ActivityManager.getCurrentUser() + " for alarm on user "
+ + UserHandle.getUserHandleForUid(mNotificationClientUid));
}
- mUserWithNotification = -1;
- mNotificationManager.cancelAsUser(LOG_TAG, notificationId,
- UserHandle.of(intent.getIntExtra(EXTRA_CURRENT_USER_ID, -1)));
+
if (ACTION_MUTE_SOUND.equals(intent.getAction())) {
- final AudioManager audioManager =
- mSystemUserContext.getSystemService(AudioManager.class);
- if (audioManager != null) {
- for (AudioPlaybackConfiguration apc :
- audioManager.getActivePlaybackConfigurations()) {
- if (apc.getAudioAttributes().getUsage() == USAGE_ALARM) {
- if (apc.getPlayerProxy() != null) {
- apc.getPlayerProxy().stop();
- }
- }
- }
- }
+ muteAlarmSounds(mSystemUserContext);
} else if (ACTION_SWITCH_USER.equals(intent.getAction())) {
- service.switchUser(intent.getIntExtra(Intent.EXTRA_USER_ID, -1));
+ activityManager.switchUser(UserHandle.getUserId(mNotificationClientUid));
}
+
+ mNotificationClientUid = -1;
}
};
IntentFilter filter = new IntentFilter();
filter.addAction(ACTION_MUTE_SOUND);
filter.addAction(ACTION_SWITCH_USER);
+ filter.addAction(ACTION_DISMISS_NOTIFICATION);
mSystemUserContext.registerReceiver(backgroundUserNotificationBroadcastReceiver, filter,
Context.RECEIVER_NOT_EXPORTED);
}
/**
+ * Stop player proxy for the ongoing alarm and drop focus for its AudioFocusInfo.
+ */
+ @VisibleForTesting
+ void muteAlarmSounds(Context context) {
+ AudioManager audioManager = context.getSystemService(AudioManager.class);
+ if (audioManager != null) {
+ for (AudioPlaybackConfiguration apc : audioManager.getActivePlaybackConfigurations()) {
+ if (apc.getClientUid() == mNotificationClientUid && apc.getPlayerProxy() != null) {
+ apc.getPlayerProxy().stop();
+ }
+ }
+ }
+ }
+
+ /**
* Check if sound is coming from background user and show notification is required.
*/
@VisibleForTesting
- void notifyForegroundUserAboutSoundIfNecessary(AudioFocusInfo afi, Context
- foregroundContext) throws RemoteException {
+ void notifyForegroundUserAboutSoundIfNecessary(AudioFocusInfo afi, Context foregroundContext)
+ throws RemoteException {
final int userId = UserHandle.getUserId(afi.getClientUid());
final int usage = afi.getAttributes().getUsage();
UserInfo userInfo = mUserManager.getUserInfo(userId);
- if (userInfo != null && userId != foregroundContext.getUserId()) {
+ // Only show notification if the sound is coming from background user and the notification
+ // is not already shown.
+ if (userInfo != null && userId != foregroundContext.getUserId()
+ && mNotificationClientUid == -1) {
//TODO: b/349138482 - Add handling of cases when usage == USAGE_NOTIFICATION_RINGTONE
if (usage == USAGE_ALARM) {
- Intent muteIntent = createIntent(ACTION_MUTE_SOUND, afi, foregroundContext, userId);
- PendingIntent mutePI = PendingIntent.getBroadcast(mSystemUserContext, 0,
- muteIntent, PendingIntent.FLAG_UPDATE_CURRENT
- | PendingIntent.FLAG_IMMUTABLE);
- Intent switchIntent = createIntent(ACTION_SWITCH_USER, afi, foregroundContext,
- userId);
- PendingIntent switchPI = PendingIntent.getBroadcast(mSystemUserContext, 0,
- switchIntent, PendingIntent.FLAG_UPDATE_CURRENT
- | PendingIntent.FLAG_IMMUTABLE);
+ if (DEBUG) {
+ Log.d(LOG_TAG, "Alarm ringing on background user " + userId
+ + ", displaying notification for current user "
+ + foregroundContext.getUserId());
+ }
- mUserWithNotification = foregroundContext.getUserId();
- mNotificationManager.notifyAsUser(LOG_TAG, afi.getClientUid(),
- createNotification(userInfo.name, mutePI, switchPI, foregroundContext),
+ mNotificationClientUid = afi.getClientUid();
+
+ mNotificationManager.notifyAsUser(LOG_TAG, mNotificationClientUid,
+ createNotification(userInfo.name, foregroundContext),
foregroundContext.getUser());
}
}
}
/**
- * If notification is present, dismisses it. To be called when the relevant sound loses focus.
+ * Dismisses notification if the associated focus has been removed from the focus stack.
+ * Notification remains if the focus is temporarily lost due to another client taking over the
+ * focus ownership.
*/
- private void dismissNotificationIfNecessary(AudioFocusInfo afi) {
- if (mUserWithNotification >= 0) {
- mNotificationManager.cancelAsUser(LOG_TAG, afi.getClientUid(),
- UserHandle.of(mUserWithNotification));
+ @VisibleForTesting
+ void dismissNotificationIfNecessary() {
+ if (getAudioFocusInfoForNotification() == null && mNotificationClientUid >= 0) {
+ if (DEBUG) {
+ Log.d(LOG_TAG, "Alarm ringing on background user "
+ + UserHandle.getUserHandleForUid(mNotificationClientUid).getIdentifier()
+ + " left focus stack, dismissing notification");
+ }
+ dismissNotification();
+ mNotificationClientUid = -1;
}
- mUserWithNotification = -1;
}
- private Intent createIntent(String intentAction, AudioFocusInfo afi, Context fgUserContext,
- int userId) {
+ /**
+ * Dismisses notification for all users in case user switch occurred after notification was
+ * shown.
+ */
+ @SuppressLint("MissingPermission")
+ private void dismissNotification() {
+ mNotificationManager.cancelAsUser(LOG_TAG, mNotificationClientUid, UserHandle.ALL);
+ }
+
+ /**
+ * Returns AudioFocusInfo associated with the current notification.
+ */
+ @SuppressLint("MissingPermission")
+ @VisibleForTesting
+ @Nullable
+ AudioFocusInfo getAudioFocusInfoForNotification() {
+ if (mNotificationClientUid >= 0) {
+ List<AudioFocusInfo> stack = mFocusControlAudioPolicy.getFocusStack();
+ for (int i = stack.size() - 1; i >= 0; i--) {
+ if (stack.get(i).getClientUid() == mNotificationClientUid) {
+ return stack.get(i);
+ }
+ }
+ }
+ return null;
+ }
+
+ private PendingIntent createPendingIntent(String intentAction) {
final Intent intent = new Intent(intentAction);
- intent.putExtra(EXTRA_CURRENT_USER_ID, fgUserContext.getUserId());
- intent.putExtra(EXTRA_NOTIFICATION_ID, afi.getClientUid());
- intent.putExtra(Intent.EXTRA_USER_ID, userId);
- return intent;
+ PendingIntent resultPI = PendingIntent.getBroadcast(mSystemUserContext, 0, intent,
+ PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
+ return resultPI;
}
- private Notification createNotification(String userName, PendingIntent muteIntent,
- PendingIntent switchIntent, Context fgContext) {
+ @VisibleForTesting
+ Notification createNotification(String userName, Context fgContext) {
final String title = fgContext.getString(R.string.bg_user_sound_notification_title_alarm,
userName);
final int icon = R.drawable.ic_audio_alarm;
+
+ PendingIntent mutePI = createPendingIntent(ACTION_MUTE_SOUND);
+ PendingIntent switchPI = createPendingIntent(ACTION_SWITCH_USER);
+ PendingIntent dismissNotificationPI = createPendingIntent(ACTION_DISMISS_NOTIFICATION);
+
final Notification.Action mute = new Notification.Action.Builder(null,
fgContext.getString(R.string.bg_user_sound_notification_button_mute),
- muteIntent).build();
+ mutePI).build();
final Notification.Action switchUser = new Notification.Action.Builder(null,
fgContext.getString(R.string.bg_user_sound_notification_button_switch_user),
- switchIntent).build();
+ switchPI).build();
+
Notification.Builder notificationBuilder = new Notification.Builder(mSystemUserContext,
BUSN_CHANNEL_ID)
.setSmallIcon(icon)
@@ -243,16 +311,18 @@
.setOngoing(true)
.setColor(fgContext.getColor(R.color.system_notification_accent_color))
.setContentTitle(title)
- .setContentIntent(muteIntent)
+ .setContentIntent(mutePI)
.setAutoCancel(true)
+ .setDeleteIntent(dismissNotificationPI)
.setVisibility(Notification.VISIBILITY_PUBLIC);
+
if (mUserManager.isUserSwitcherEnabled() && (mUserManager.getUserSwitchability(
- UserHandle.of(fgContext.getUserId())) == UserManager.SWITCHABILITY_STATUS_OK)) {
+ fgContext.getUser()) == UserManager.SWITCHABILITY_STATUS_OK)) {
notificationBuilder.setActions(mute, switchUser);
} else {
notificationBuilder.setActions(mute);
}
+
return notificationBuilder.build();
}
}
-
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index ee15bec..efd58ed 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -92,7 +92,6 @@
import android.multiuser.Flags;
import android.net.Uri;
import android.os.Binder;
-import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.IInterface;
@@ -215,7 +214,7 @@
@VisibleForTesting
static class LauncherAppsImpl extends ILauncherApps.Stub {
- private static final boolean DEBUG = Build.IS_DEBUGGABLE;
+ private static final boolean DEBUG = false;
private static final String TAG = "LauncherAppsService";
private static final String NAMESPACE_MULTIUSER = "multiuser";
private static final String FLAG_NON_SYSTEM_ACCESS_TO_HIDDEN_PROFILES =
@@ -496,28 +495,8 @@
private boolean canAccessProfile(int callingUid, int callingUserId, int callingPid,
int targetUserId, String message) {
- if (DEBUG) {
- final AndroidPackage callingPackage =
- mPackageManagerInternal.getPackage(callingUid);
- final String callingPackageName = callingPackage == null
- ? null : callingPackage.getPackageName();
- Slog.v(TAG, "canAccessProfile called by " + callingPackageName
- + " for user " + callingUserId
- + " requesting to access user "
- + targetUserId + " when invoking " + message);
- }
- if (targetUserId == callingUserId) {
- if (DEBUG) {
- Slog.v(TAG, message + " passed canAccessProfile for targetuser"
- + targetUserId + " because it is the same as the calling user");
- }
- return true;
- }
+ if (targetUserId == callingUserId) return true;
if (injectHasInteractAcrossUsersFullPermission(callingPid, callingUid)) {
- if (DEBUG) {
- Slog.v(TAG, message + " passed because calling process"
- + "has permission to interact across users");
- }
return true;
}
@@ -535,25 +514,11 @@
if (isHiddenProfile(UserHandle.of(targetUserId))
&& !canAccessHiddenProfile(callingUid, callingPid)) {
- Slog.w(TAG, message + " for hidden profile user " + targetUserId
- + " from " + callingUserId + " not allowed");
-
return false;
}
- final boolean ret = mUserManagerInternal.isProfileAccessible(
- callingUserId, targetUserId, message, true);
- if (DEBUG) {
- final AndroidPackage callingPackage =
- mPackageManagerInternal.getPackage(callingUid);
- final String callingPackageName = callingPackage == null
- ? null : callingPackage.getPackageName();
- Slog.v(TAG, "canAccessProfile returned " + ret + " for " + callingPackageName
- + " for user " + callingUserId
- + " requesting to access user "
- + targetUserId + " when invoking " + message);
- }
- return ret;
+ return mUserManagerInternal.isProfileAccessible(callingUserId, targetUserId,
+ message, true);
}
private boolean isHiddenProfile(UserHandle targetUser) {
@@ -1376,10 +1341,6 @@
@Override
public void pinShortcuts(String callingPackage, String packageName, List<String> ids,
UserHandle targetUser) {
- if (DEBUG) {
- Slog.v(TAG, "pinShortcuts: " + callingPackage + " is pinning shortcuts from "
- + packageName + " for user " + targetUser);
- }
if (!mShortcutServiceInternal
.areShortcutsSupportedOnHomeScreen(targetUser.getIdentifier())) {
// Requires strict ACCESS_SHORTCUTS permission for user-profiles with items
@@ -1390,11 +1351,6 @@
}
ensureShortcutPermission(callingPackage);
if (!canAccessProfile(targetUser.getIdentifier(), "Cannot pin shortcuts")) {
- if (DEBUG) {
- Slog.v(TAG, "pinShortcuts: " + callingPackage
- + " is pinning shortcuts from " + packageName
- + " for user " + targetUser + " but cannot access profile");
- }
return;
}
@@ -2451,7 +2407,7 @@
final int callbackUserId = callbackUser.getIdentifier();
final int shortcutUserId = shortcutUser.getIdentifier();
- if (shortcutUser == callbackUser) return true;
+ if ((shortcutUser.equals(callbackUser))) return true;
return mUserManagerInternal.isProfileAccessible(callbackUserId, shortcutUserId,
null, false);
}
@@ -2485,16 +2441,28 @@
final BroadcastCookie cookie =
(BroadcastCookie) mListeners.getBroadcastCookie(i);
if (!isEnabledProfileOf(cookie, user, "onPackageRemoved")) {
+ // b/350144057
+ Slog.d(TAG, "onPackageRemoved: Skipping - profile not enabled"
+ + " or not accessible for user=" + user
+ + ", packageName=" + packageName);
continue;
}
if (!isCallingAppIdAllowed(appIdAllowList, UserHandle.getAppId(
cookie.callingUid))) {
+ // b/350144057
+ Slog.d(TAG, "onPackageRemoved: Skipping - appId not allowed"
+ + " for user=" + user
+ + ", packageName=" + packageName);
continue;
}
try {
+ // b/350144057
+ Slog.d(TAG, "onPackageRemoved: triggering onPackageRemoved"
+ + " for user=" + user
+ + ", packageName=" + packageName);
listener.onPackageRemoved(user, packageName);
} catch (RemoteException re) {
- Slog.d(TAG, "Callback failed ", re);
+ Slog.d(TAG, "onPackageRemoved: Callback failed ", re);
}
}
} finally {
@@ -2524,15 +2492,27 @@
IOnAppsChangedListener listener = mListeners.getBroadcastItem(i);
BroadcastCookie cookie = (BroadcastCookie) mListeners.getBroadcastCookie(i);
if (!isEnabledProfileOf(cookie, user, "onPackageAdded")) {
+ // b/350144057
+ Slog.d(TAG, "onPackageAdded: Skipping - profile not enabled"
+ + " or not accessible for user=" + user
+ + ", packageName=" + packageName);
continue;
}
if (!isPackageVisibleToListener(packageName, cookie, user)) {
+ // b/350144057
+ Slog.d(TAG, "onPackageAdded: Skipping - package filtered"
+ + " for user=" + user
+ + ", packageName=" + packageName);
continue;
}
try {
+ // b/350144057
+ Slog.d(TAG, "onPackageAdded: triggering onPackageAdded"
+ + " for user=" + user
+ + ", packageName=" + packageName);
listener.onPackageAdded(user, packageName);
} catch (RemoteException re) {
- Slog.d(TAG, "Callback failed ", re);
+ Slog.d(TAG, "onPackageAdded: Callback failed ", re);
}
}
} finally {
@@ -2566,7 +2546,7 @@
try {
listener.onPackageChanged(user, packageName);
} catch (RemoteException re) {
- Slog.d(TAG, "Callback failed ", re);
+ Slog.d(TAG, "onPackageChanged: Callback failed ", re);
}
}
} finally {
diff --git a/services/core/java/com/android/server/pm/ShortcutLauncher.java b/services/core/java/com/android/server/pm/ShortcutLauncher.java
index d65e30b..045d4db 100644
--- a/services/core/java/com/android/server/pm/ShortcutLauncher.java
+++ b/services/core/java/com/android/server/pm/ShortcutLauncher.java
@@ -42,7 +42,6 @@
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
-import java.util.stream.Collectors;
/**
* Launcher information used by {@link ShortcutService}.
@@ -129,15 +128,9 @@
*/
public void pinShortcuts(@UserIdInt int packageUserId,
@NonNull String packageName, @NonNull List<String> ids, boolean forPinRequest) {
- if (ShortcutService.DEBUG) {
- Slog.v(TAG, "ShortcutLauncher#pinShortcuts: pin shortcuts from " + packageName
- + " with userId=" + packageUserId + " shortcutIds="
- + ids.stream().collect(Collectors.joining(", ", "[", "]")));
- }
final ShortcutPackage packageShortcuts =
mShortcutUser.getPackageShortcutsIfExists(packageName);
if (packageShortcuts == null) {
- Slog.w(TAG, "ShortcutLauncher#pinShortcuts packageShortcuts is null");
return; // No need to instantiate.
}
@@ -162,10 +155,6 @@
final String id = ids.get(i);
final ShortcutInfo si = packageShortcuts.findShortcutById(id);
if (si == null) {
- if (ShortcutService.DEBUG) {
- Slog.w(TAG, "ShortcutLauncher#pinShortcuts: cannot pin "
- + id + " because it does not exist");
- }
continue;
}
if (si.isDynamic() || si.isLongLived()
@@ -185,13 +174,6 @@
}
}
}
- if (ShortcutService.DEBUG) {
- Slog.v(TAG, "ShortcutLauncher#pinShortcuts: "
- + " newSet: " + newSet.stream().collect(
- Collectors.joining(", ", "[", "]"))
- + " floatingSet: " + floatingSet.stream().collect(
- Collectors.joining(", ", "[", "]")));
- }
mPinnedShortcuts.put(up, newSet);
}
}
diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index c9ad498..60056eb 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -729,11 +729,6 @@
}
pinnedShortcuts.addAll(pinned);
});
- if (ShortcutService.DEBUG) {
- Slog.v(TAG, "ShortcutPackage#refreshPinnedFlags: "
- + " pinnedShortcuts: " + pinnedShortcuts.stream().collect(
- Collectors.joining(", ", "[", "]")));
- }
// Secondly, update the pinned state if necessary.
final List<ShortcutInfo> pinned = findAll(pinnedShortcuts);
if (pinned != null) {
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index ea495c9..a3ff195 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -169,7 +169,7 @@
public class ShortcutService extends IShortcutService.Stub {
static final String TAG = "ShortcutService";
- static final boolean DEBUG = Build.IS_DEBUGGABLE; // STOPSHIP if true
+ static final boolean DEBUG = false; // STOPSHIP if true
static final boolean DEBUG_LOAD = false; // STOPSHIP if true
static final boolean DEBUG_PROCSTATE = false; // STOPSHIP if true
static final boolean DEBUG_REBOOT = Build.IS_DEBUGGABLE;
@@ -3206,11 +3206,6 @@
public void pinShortcuts(int launcherUserId,
@NonNull String callingPackage, @NonNull String packageName,
@NonNull List<String> shortcutIds, int userId) {
- if (DEBUG) {
- Slog.v(TAG, "pinShortcuts: " + callingPackage + ", with userId=" + launcherUserId
- + ", is trying to pin shortcuts from " + packageName
- + " with userId=" + userId);
- }
// Calling permission must be checked by LauncherAppsImpl.
Preconditions.checkStringNotEmpty(packageName, "packageName");
Objects.requireNonNull(shortcutIds, "shortcutIds");
@@ -3235,11 +3230,6 @@
&& !si.isDeclaredInManifest(),
ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO,
callingPackage, launcherUserId, false);
- } else {
- if (DEBUG) {
- Slog.w(TAG, "specified package " + packageName + ", with userId=" + userId
- + ", doesn't exist.");
- }
}
// Get list of shortcuts that will get unpinned.
ArraySet<String> oldPinnedIds = launcher.getPinnedShortcutIds(packageName, userId);
@@ -5458,17 +5448,6 @@
*/
private List<ShortcutInfo> prepareChangedShortcuts(ArraySet<String> changedIds,
ArraySet<String> newIds, List<ShortcutInfo> deletedList, final ShortcutPackage ps) {
- if (DEBUG) {
- Slog.v(TAG, "prepareChangedShortcuts: "
- + " changedIds=" + (changedIds == null
- ? "n/a" : changedIds.stream().collect(Collectors.joining(", ", "[", "]")))
- + " newIds=" + (newIds == null
- ? "n/a" : newIds.stream().collect(Collectors.joining(", ", "[", "]")))
- + " deletedList=" + (deletedList == null
- ? "n/a" : deletedList.stream().map(ShortcutInfo::getId).collect(
- Collectors.joining(", ", "[", "]")))
- + " ps=" + (ps == null ? "n/a" : ps.getPackageName()));
- }
if (ps == null) {
// This can happen when package restore is not finished yet.
return null;
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index a683a8c..89417f3 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -1090,6 +1090,21 @@
mUser0Allocations = DBG_ALLOCATION ? new AtomicInteger() : null;
mPrivateSpaceAutoLockSettingsObserver = new SettingsObserver(mHandler);
emulateSystemUserModeIfNeeded();
+ initPropertyInvalidatedCaches();
+ }
+
+ /**
+ * This method is used to invalidate the caches at server statup,
+ * so that caches can start working.
+ */
+ private static final void initPropertyInvalidatedCaches() {
+ if (android.multiuser.Flags.cachesNotInvalidatedAtStartReadOnly()) {
+ UserManager.invalidateIsUserUnlockedCache();
+ UserManager.invalidateQuietModeEnabledCache();
+ UserManager.invalidateStaticUserProperties();
+ UserManager.invalidateUserPropertiesCache();
+ UserManager.invalidateUserSerialNumberCache();
+ }
}
private boolean doesDeviceHardwareSupportPrivateSpace() {
diff --git a/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java b/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java
index 1994174..3a2cffb 100644
--- a/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java
+++ b/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java
@@ -422,6 +422,7 @@
|| first.brightnessMaximum != second.brightnessMaximum
|| first.brightnessDefault != second.brightnessDefault
|| first.installOrientation != second.installOrientation
+ || first.isForceSdr != second.isForceSdr
|| !Objects.equals(first.layoutLimitedRefreshRate, second.layoutLimitedRefreshRate)
|| !BrightnessSynchronizer.floatEquals(first.hdrSdrRatio, second.hdrSdrRatio)
|| !first.thermalRefreshRateThrottling.contentEquals(
diff --git a/services/core/java/com/android/server/wm/EmbeddedWindowController.java b/services/core/java/com/android/server/wm/EmbeddedWindowController.java
index 5514294e..e007b1d 100644
--- a/services/core/java/com/android/server/wm/EmbeddedWindowController.java
+++ b/services/core/java/com/android/server/wm/EmbeddedWindowController.java
@@ -181,22 +181,30 @@
return true;
}
- boolean transferToHost(@NonNull InputTransferToken embeddedWindowToken,
+ boolean transferToHost(int callingUid, @NonNull InputTransferToken embeddedWindowToken,
@NonNull WindowState transferToHostWindowState) {
EmbeddedWindow ew = getByInputTransferToken(embeddedWindowToken);
if (!isValidTouchGestureParams(transferToHostWindowState, ew)) {
return false;
}
+ if (callingUid != ew.mOwnerUid) {
+ throw new SecurityException(
+ "Transfer request must originate from owner of transferFromToken");
+ }
return mInputManagerService.transferTouchGesture(ew.getInputChannelToken(),
transferToHostWindowState.mInputChannelToken);
}
- boolean transferToEmbedded(WindowState hostWindowState,
+ boolean transferToEmbedded(int callingUid, WindowState hostWindowState,
@NonNull InputTransferToken transferToToken) {
final EmbeddedWindowController.EmbeddedWindow ew = getByInputTransferToken(transferToToken);
if (!isValidTouchGestureParams(hostWindowState, ew)) {
return false;
}
+ if (callingUid != hostWindowState.mOwnerUid) {
+ throw new SecurityException(
+ "Transfer request must originate from owner of transferFromToken");
+ }
return mInputManagerService.transferTouchGesture(hostWindowState.mInputChannelToken,
ew.getInputChannelToken());
}
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index 638e92f..42ea5a8 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -28,6 +28,7 @@
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+import static android.view.Display.INVALID_DISPLAY;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
import static com.android.server.wm.ActivityRecord.State.RESUMED;
@@ -57,6 +58,7 @@
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.internal.util.function.pooled.PooledPredicate;
+import com.android.server.pm.UserManagerInternal;
import com.android.server.wm.LaunchParamsController.LaunchParams;
import java.io.PrintWriter;
@@ -1761,10 +1763,10 @@
* @return last reparented root task, or {@code null} if the root tasks had to be destroyed.
*/
Task remove() {
+ final TaskDisplayArea toDisplayArea = getReparentToTaskDisplayArea(getFocusedRootTask());
mPreferredTopFocusableRootTask = null;
// TODO(b/153090332): Allow setting content removal mode per task display area
final boolean destroyContentOnRemoval = mDisplayContent.shouldDestroyContentOnRemove();
- final TaskDisplayArea toDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea();
Task lastReparentedRootTask = null;
// Root tasks could be reparented from the removed display area to other display area. After
@@ -1830,6 +1832,41 @@
return lastReparentedRootTask;
}
+ /**
+ * Returns the {@link TaskDisplayArea} to which root tasks should be reparented.
+ *
+ * <p>In the automotive multi-user multi-display environment where background users have
+ * UI access on their assigned displays (a.k.a. visible background users), it's not allowed to
+ * launch an activity on an unassigned display. If an activity is attempted to launch on an
+ * unassigned display, it throws an exception.
+ * <p>This method determines the appropriate {@link TaskDisplayArea} for reparenting root tasks
+ * when a display is removed, in order to avoid the exception. If the root task is null,
+ * the visible background user is not supported or the user associated with the root task is
+ * not a visible background user, it returns the default {@link TaskDisplayArea} of the default
+ * display. Otherwise, it returns the default {@link TaskDisplayArea} of the main display
+ * assigned to the user.
+ *
+ * @param rootTask The root task whose {@link TaskDisplayArea} needs to be determined.
+ * @return The {@link TaskDisplayArea} where the root tasks should be reparented to.
+ */
+ private TaskDisplayArea getReparentToTaskDisplayArea(Task rootTask) {
+ final TaskDisplayArea defaultTaskDisplayArea =
+ mRootWindowContainer.getDefaultTaskDisplayArea();
+ if (rootTask == null) {
+ return defaultTaskDisplayArea;
+ }
+ UserManagerInternal userManagerInternal = mAtmService.mWindowManager.mUmInternal;
+ if (!userManagerInternal.isVisibleBackgroundFullUser(rootTask.mUserId)) {
+ return defaultTaskDisplayArea;
+ }
+ int toDisplayId = userManagerInternal.getMainDisplayAssignedToUser(rootTask.mUserId);
+ if (toDisplayId == INVALID_DISPLAY) {
+ return defaultTaskDisplayArea;
+ }
+ DisplayContent dc = mRootWindowContainer.getDisplayContent(toDisplayId);
+ return dc != null ? dc.getDefaultTaskDisplayArea() : defaultTaskDisplayArea;
+ }
+
/** Whether this task display area can request orientation. */
boolean canSpecifyOrientation(@ScreenOrientation int orientation) {
// Only allow to specify orientation if this TDA is the last focused one on this logical
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 33f2dd1..b8f47cc 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -9212,6 +9212,8 @@
final InputApplicationHandle applicationHandle;
final String name;
Objects.requireNonNull(outInputChannel);
+ Objects.requireNonNull(inputTransferToken);
+
synchronized (mGlobalLock) {
WindowState hostWindowState = hostInputTransferToken != null
? mInputToWindowMap.get(hostInputTransferToken.getToken()) : null;
@@ -9236,6 +9238,7 @@
Objects.requireNonNull(transferFromToken);
Objects.requireNonNull(transferToToken);
+ final int callingUid = Binder.getCallingUid();
final long identity = Binder.clearCallingIdentity();
boolean didTransfer;
try {
@@ -9245,12 +9248,14 @@
// represents an embedded window so transfer from host to embedded.
WindowState windowStateTo = mInputToWindowMap.get(transferToToken.getToken());
if (windowStateTo != null) {
- didTransfer = mEmbeddedWindowController.transferToHost(transferFromToken,
+ didTransfer = mEmbeddedWindowController.transferToHost(callingUid,
+ transferFromToken,
windowStateTo);
} else {
WindowState windowStateFrom = mInputToWindowMap.get(
transferFromToken.getToken());
- didTransfer = mEmbeddedWindowController.transferToEmbedded(windowStateFrom,
+ didTransfer = mEmbeddedWindowController.transferToEmbedded(callingUid,
+ windowStateFrom,
transferToToken);
}
}
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index 976be4a..30d6f0a 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -326,6 +326,7 @@
public static final int ACTIVITY_STATE_FLAG_HAS_ACTIVITY_IN_VISIBLE_TASK = 1 << 22;
public static final int ACTIVITY_STATE_FLAG_RESUMED_SPLIT_SCREEN = 1 << 23;
public static final int ACTIVITY_STATE_FLAG_PERCEPTIBLE_FREEFORM = 1 << 24;
+ public static final int ACTIVITY_STATE_FLAG_VISIBLE_MULTI_WINDOW_MODE = 1 << 25;
public static final int ACTIVITY_STATE_FLAG_MASK_MIN_TASK_LAYER = 0x0000ffff;
/**
@@ -1293,8 +1294,12 @@
if (hasResumedFreeform
&& com.android.window.flags.Flags.processPriorityPolicyForMultiWindowMode()
// Exclude task layer 1 because it is already the top most.
- && minTaskLayer > 1 && minTaskLayer <= 1 + MAX_NUM_PERCEPTIBLE_FREEFORM) {
- stateFlags |= ACTIVITY_STATE_FLAG_PERCEPTIBLE_FREEFORM;
+ && minTaskLayer > 1) {
+ if (minTaskLayer <= 1 + MAX_NUM_PERCEPTIBLE_FREEFORM) {
+ stateFlags |= ACTIVITY_STATE_FLAG_PERCEPTIBLE_FREEFORM;
+ } else {
+ stateFlags |= ACTIVITY_STATE_FLAG_VISIBLE_MULTI_WINDOW_MODE;
+ }
}
stateFlags |= minTaskLayer & ACTIVITY_STATE_FLAG_MASK_MIN_TASK_LAYER;
if (visible) {
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
index 8b80f85..255dcb0 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -27,6 +27,7 @@
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION;
import static android.view.ContentRecordingSession.RECORD_CONTENT_DISPLAY;
import static android.view.ContentRecordingSession.RECORD_CONTENT_TASK;
+import static android.view.Display.HdrCapabilities.HDR_TYPE_INVALID;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
import static com.android.server.display.ExternalDisplayPolicy.ENABLE_ON_CONNECT;
@@ -195,8 +196,8 @@
private static final String VIRTUAL_DISPLAY_NAME = "Test Virtual Display";
private static final String PACKAGE_NAME = "com.android.frameworks.displayservicetests";
private static final long STANDARD_DISPLAY_EVENTS = DisplayManager.EVENT_FLAG_DISPLAY_ADDED
- | DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
- | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED;
+ | DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
+ | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED;
private static final long STANDARD_AND_CONNECTION_DISPLAY_EVENTS =
STANDARD_DISPLAY_EVENTS | DisplayManager.EVENT_FLAG_DISPLAY_CONNECTION_CHANGED;
@@ -238,6 +239,8 @@
private UserManager mUserManager;
+ private int[] mAllowedHdrOutputTypes;
+
private final DisplayManagerService.Injector mShortMockedInjector =
new DisplayManagerService.Injector() {
@Override
@@ -256,11 +259,12 @@
displayAdapterListener, flags,
mMockedDisplayNotificationManager,
new LocalDisplayAdapter.Injector() {
- @Override
- public LocalDisplayAdapter.SurfaceControlProxy getSurfaceControlProxy() {
- return mSurfaceControlProxy;
- }
- });
+ @Override
+ public LocalDisplayAdapter.SurfaceControlProxy
+ getSurfaceControlProxy() {
+ return mSurfaceControlProxy;
+ }
+ });
}
@Override
@@ -320,7 +324,7 @@
@Override
int setHdrConversionMode(int conversionMode, int preferredHdrOutputType,
- int[] autoHdrTypes) {
+ int[] allowedHdrOutputTypes) {
mHdrConversionMode = conversionMode;
mPreferredHdrOutputType = preferredHdrOutputType;
return Display.HdrCapabilities.HDR_TYPE_INVALID;
@@ -1295,11 +1299,11 @@
.setUniqueId("uniqueId --- mirror display");
assertThrows(SecurityException.class, () -> {
localService.createVirtualDisplay(
- builder.build(),
- mMockAppToken /* callback */,
- null /* virtualDeviceToken */,
- mock(DisplayWindowPolicyController.class),
- PACKAGE_NAME);
+ builder.build(),
+ mMockAppToken /* callback */,
+ null /* virtualDeviceToken */,
+ mock(DisplayWindowPolicyController.class),
+ PACKAGE_NAME);
});
}
@@ -1433,7 +1437,7 @@
// The virtual display should not have FLAG_ALWAYS_UNLOCKED set.
assertEquals(0, (displayManager.getDisplayDeviceInfoInternal(displayId).flags
- & DisplayDeviceInfo.FLAG_ALWAYS_UNLOCKED));
+ & DisplayDeviceInfo.FLAG_ALWAYS_UNLOCKED));
}
/**
@@ -1466,7 +1470,7 @@
// The virtual display should not have FLAG_PRESENTATION set.
assertEquals(0, (displayManager.getDisplayDeviceInfoInternal(displayId).flags
- & DisplayDeviceInfo.FLAG_PRESENTATION));
+ & DisplayDeviceInfo.FLAG_PRESENTATION));
}
@Test
@@ -2358,6 +2362,7 @@
HdrConversionMode.HDR_CONVERSION_FORCE,
Display.HdrCapabilities.HDR_TYPE_DOLBY_VISION);
displayManager.setHdrConversionModeInternal(mode);
+
assertEquals(mode, displayManager.getHdrConversionModeSettingInternal());
assertEquals(mode.getConversionMode(), mHdrConversionMode);
assertEquals(mode.getPreferredHdrOutputType(), mPreferredHdrOutputType);
@@ -2402,6 +2407,86 @@
}
@Test
+ public void testSetAreUserDisabledHdrTypesAllowed_withFalse_whenHdrDisabled_stripsHdrType() {
+ DisplayManagerService displayManager = new DisplayManagerService(
+ mContext, new BasicInjector() {
+ @Override
+ int setHdrConversionMode(int conversionMode, int preferredHdrOutputType,
+ int[] allowedTypes) {
+ mAllowedHdrOutputTypes = allowedTypes;
+ return Display.HdrCapabilities.HDR_TYPE_INVALID;
+ }
+
+ // Overriding this method to capture the allowed HDR type
+ @Override
+ int[] getSupportedHdrOutputTypes() {
+ return new int[]{Display.HdrCapabilities.HDR_TYPE_DOLBY_VISION};
+ }
+ });
+
+ // Setup: no HDR types disabled, userDisabledTypes allowed, system conversion
+ displayManager.setUserDisabledHdrTypesInternal(new int [0]);
+ displayManager.setAreUserDisabledHdrTypesAllowedInternal(true);
+ displayManager.setHdrConversionModeInternal(
+ new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_SYSTEM));
+
+ assertEquals(1, mAllowedHdrOutputTypes.length);
+ assertTrue(Display.HdrCapabilities.HDR_TYPE_DOLBY_VISION == mAllowedHdrOutputTypes[0]);
+
+ // Action: disable Dolby Vision, set userDisabledTypes not allowed
+ displayManager.setUserDisabledHdrTypesInternal(
+ new int [] {Display.HdrCapabilities.HDR_TYPE_DOLBY_VISION});
+ displayManager.setAreUserDisabledHdrTypesAllowedInternal(false);
+
+ assertEquals(0, mAllowedHdrOutputTypes.length);
+ }
+
+ @Test
+ public void testGetEnabledHdrTypesLocked_whenTypesDisabled_stripsDisabledTypes() {
+ DisplayManagerService displayManager = new DisplayManagerService(
+ mContext, new BasicInjector() {
+ @Override
+ int[] getSupportedHdrOutputTypes() {
+ return new int[]{Display.HdrCapabilities.HDR_TYPE_DOLBY_VISION};
+ }
+ });
+
+ displayManager.setUserDisabledHdrTypesInternal(new int [0]);
+ displayManager.setAreUserDisabledHdrTypesAllowedInternal(true);
+ int [] enabledHdrOutputTypes = displayManager.getEnabledHdrOutputTypes();
+ assertEquals(1, enabledHdrOutputTypes.length);
+ assertTrue(Display.HdrCapabilities.HDR_TYPE_DOLBY_VISION == enabledHdrOutputTypes[0]);
+
+ displayManager.setAreUserDisabledHdrTypesAllowedInternal(false);
+ enabledHdrOutputTypes = displayManager.getEnabledHdrOutputTypes();
+ assertEquals(1, enabledHdrOutputTypes.length);
+ assertTrue(Display.HdrCapabilities.HDR_TYPE_DOLBY_VISION == enabledHdrOutputTypes[0]);
+
+ displayManager.setUserDisabledHdrTypesInternal(
+ new int [] {Display.HdrCapabilities.HDR_TYPE_DOLBY_VISION});
+ enabledHdrOutputTypes = displayManager.getEnabledHdrOutputTypes();
+ assertEquals(0, enabledHdrOutputTypes.length);
+ }
+
+ @Test
+ public void testSetHdrConversionModeInternal_isForceSdrIsUpdated() {
+ DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+ LogicalDisplayMapper logicalDisplayMapper = displayManager.getLogicalDisplayMapper();
+ FakeDisplayDevice displayDevice =
+ createFakeDisplayDevice(displayManager, new float[]{60f}, Display.TYPE_EXTERNAL);
+ LogicalDisplay logicalDisplay =
+ logicalDisplayMapper.getDisplayLocked(displayDevice, /* includeDisabled= */ true);
+
+ displayManager.setHdrConversionModeInternal(
+ new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_FORCE, HDR_TYPE_INVALID));
+ assertTrue(logicalDisplay.getDisplayInfoLocked().isForceSdr);
+
+ displayManager.setHdrConversionModeInternal(
+ new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_SYSTEM));
+ assertFalse(logicalDisplay.getDisplayInfoLocked().isForceSdr);
+ }
+
+ @Test
public void testReturnsRefreshRateForDisplayAndSensor_proximitySensorSet() {
DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
DisplayManagerInternal localService = displayManager.new LocalService();
@@ -3505,7 +3590,7 @@
}
private FakeDisplayDevice createFakeDisplayDevice(DisplayManagerService displayManager,
- Display.Mode[] modes) {
+ Display.Mode[] modes) {
FakeDisplayDevice displayDevice = new FakeDisplayDevice();
DisplayDeviceInfo displayDeviceInfo = new DisplayDeviceInfo();
displayDeviceInfo.supportedModes = modes;
@@ -3761,9 +3846,9 @@
public void setUserPreferredDisplayModeLocked(Display.Mode preferredMode) {
for (Display.Mode mode : mDisplayDeviceInfo.supportedModes) {
if (mode.matchesIfValid(
- preferredMode.getPhysicalWidth(),
- preferredMode.getPhysicalHeight(),
- preferredMode.getRefreshRate())) {
+ preferredMode.getPhysicalWidth(),
+ preferredMode.getPhysicalHeight(),
+ preferredMode.getRefreshRate())) {
mPreferredMode = mode;
break;
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
index 5ec5302..f6ad07d 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -62,6 +62,7 @@
import static com.android.server.am.ProcessList.PREVIOUS_APP_ADJ;
import static com.android.server.am.ProcessList.SCHED_GROUP_BACKGROUND;
import static com.android.server.am.ProcessList.SCHED_GROUP_DEFAULT;
+import static com.android.server.am.ProcessList.SCHED_GROUP_FOREGROUND_WINDOW;
import static com.android.server.am.ProcessList.SCHED_GROUP_RESTRICTED;
import static com.android.server.am.ProcessList.SCHED_GROUP_TOP_APP;
import static com.android.server.am.ProcessList.SCHED_GROUP_TOP_APP_BOUND;
@@ -534,6 +535,14 @@
updateOomAdj(app);
assertProcStates(app, PROCESS_STATE_TOP, VISIBLE_APP_ADJ, SCHED_GROUP_TOP_APP);
assertEquals("perceptible-freeform-activity", app.mState.getAdjType());
+
+ doReturn(WindowProcessController.ACTIVITY_STATE_FLAG_IS_VISIBLE
+ | WindowProcessController.ACTIVITY_STATE_FLAG_VISIBLE_MULTI_WINDOW_MODE)
+ .when(wpc).getActivityStateFlags();
+ updateOomAdj(app);
+ assertProcStates(app, PROCESS_STATE_TOP, VISIBLE_APP_ADJ,
+ SCHED_GROUP_FOREGROUND_WINDOW);
+ assertEquals("vis-multi-window-activity", app.mState.getAdjType());
}
@SuppressWarnings("GuardedBy")
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundUserSoundNotifierTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundUserSoundNotifierTest.java
index a82658b..3062d51 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundUserSoundNotifierTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundUserSoundNotifierTest.java
@@ -16,13 +16,19 @@
package com.android.server.pm;
+import static android.media.AudioAttributes.USAGE_ALARM;
+
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+import static org.testng.AssertJUnit.assertEquals;
import android.app.Notification;
import android.app.NotificationManager;
@@ -31,6 +37,9 @@
import android.media.AudioAttributes;
import android.media.AudioFocusInfo;
import android.media.AudioManager;
+import android.media.AudioPlaybackConfiguration;
+import android.media.PlayerProxy;
+import android.media.audiopolicy.AudioPolicy;
import android.os.Build;
import android.os.RemoteException;
import android.os.UserHandle;
@@ -45,6 +54,10 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Stack;
+
@RunWith(JUnit4.class)
public class BackgroundUserSoundNotifierTest {
@@ -63,7 +76,10 @@
MockitoAnnotations.initMocks(this);
mSpiedContext = spy(mRealContext);
mUsersToRemove = new ArraySet<>();
- mUserManager = UserManager.get(mRealContext);
+
+ mUserManager = spy(mSpiedContext.getSystemService(UserManager.class));
+ doReturn(mUserManager)
+ .when(mSpiedContext).getSystemService(UserManager.class);
doReturn(mNotificationManager)
.when(mSpiedContext).getSystemService(NotificationManager.class);
mBackgroundUserSoundNotifier = new BackgroundUserSoundNotifier(mSpiedContext);
@@ -74,12 +90,9 @@
mUsersToRemove.stream().toList().forEach(this::removeUser);
}
@Test
- public void testAlarmOnBackgroundUser_ForegroundUserNotified() throws RemoteException {
- AudioAttributes aa = new AudioAttributes.Builder()
- .setUsage(AudioAttributes.USAGE_ALARM).build();
- UserInfo user = createUser("User",
- UserManager.USER_TYPE_FULL_SECONDARY,
- 0);
+ public void testAlarmOnBackgroundUser_foregroundUserNotified() throws RemoteException {
+ AudioAttributes aa = new AudioAttributes.Builder().setUsage(USAGE_ALARM).build();
+ UserInfo user = createUser("User", UserManager.USER_TYPE_FULL_SECONDARY, 0);
final int fgUserId = mSpiedContext.getUserId();
final int bgUserUid = user.id * 100000;
doReturn(UserHandle.of(fgUserId)).when(mSpiedContext).getUser();
@@ -95,10 +108,9 @@
}
@Test
- public void testMediaOnBackgroundUser_ForegroundUserNotNotified() throws RemoteException {
+ public void testMediaOnBackgroundUser_foregroundUserNotNotified() throws RemoteException {
AudioAttributes aa = new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_MEDIA).build();
- UserInfo user = createUser("User", UserManager.USER_TYPE_FULL_SECONDARY, 0);
final int bgUserUid = mSpiedContext.getUserId() * 100000;
AudioFocusInfo afi = new AudioFocusInfo(aa, bgUserUid, "",
/* packageName= */ "com.android.car.audio", AudioManager.AUDIOFOCUS_GAIN,
@@ -109,9 +121,9 @@
}
@Test
- public void testAlarmOnForegroundUser_ForegroundUserNotNotified() throws RemoteException {
+ public void testAlarmOnForegroundUser_foregroundUserNotNotified() throws RemoteException {
AudioAttributes aa = new AudioAttributes.Builder()
- .setUsage(AudioAttributes.USAGE_ALARM).build();
+ .setUsage(USAGE_ALARM).build();
final int fgUserId = mSpiedContext.getUserId();
final int fgUserUid = fgUserId * 100000;
doReturn(UserHandle.of(fgUserId)).when(mSpiedContext).getUser();
@@ -123,6 +135,109 @@
verifyZeroInteractions(mNotificationManager);
}
+ @Test
+ public void testMuteAlarmSounds() {
+ final int fgUserId = mSpiedContext.getUserId();
+ int bgUserId = fgUserId + 1;
+ int bgUserUid = bgUserId * 100000;
+ mBackgroundUserSoundNotifier.mNotificationClientUid = bgUserUid;
+
+ AudioManager mockAudioManager = mock(AudioManager.class);
+ when(mSpiedContext.getSystemService(AudioManager.class)).thenReturn(mockAudioManager);
+
+ AudioPlaybackConfiguration apc1 = mock(AudioPlaybackConfiguration.class);
+ when(apc1.getClientUid()).thenReturn(bgUserUid);
+ when(apc1.getPlayerProxy()).thenReturn(mock(PlayerProxy.class));
+
+ AudioPlaybackConfiguration apc2 = mock(AudioPlaybackConfiguration.class);
+ when(apc2.getClientUid()).thenReturn(bgUserUid + 1);
+ when(apc2.getPlayerProxy()).thenReturn(mock(PlayerProxy.class));
+
+ List<AudioPlaybackConfiguration> configs = new ArrayList<>();
+ configs.add(apc1);
+ configs.add(apc2);
+ when(mockAudioManager.getActivePlaybackConfigurations()).thenReturn(configs);
+
+ AudioPolicy mockAudioPolicy = mock(AudioPolicy.class);
+
+ AudioAttributes aa = new AudioAttributes.Builder().setUsage(USAGE_ALARM).build();
+ AudioFocusInfo afi = new AudioFocusInfo(aa, bgUserUid, "", /* packageName= */ "",
+ AudioManager.AUDIOFOCUS_GAIN, AudioManager.AUDIOFOCUS_NONE, /* flags= */ 0,
+ Build.VERSION.SDK_INT);
+ Stack<AudioFocusInfo> focusStack = new Stack<>();
+ focusStack.add(afi);
+ doReturn(focusStack).when(mockAudioPolicy).getFocusStack();
+ mBackgroundUserSoundNotifier.mFocusControlAudioPolicy = mockAudioPolicy;
+
+ mBackgroundUserSoundNotifier.muteAlarmSounds(mSpiedContext);
+
+ verify(apc1.getPlayerProxy()).stop();
+ verify(apc2.getPlayerProxy(), never()).stop();
+ }
+
+ @Test
+ public void testOnAudioFocusGrant_alarmOnBackgroundUser_notifiesForegroundUser() {
+ final int fgUserId = mSpiedContext.getUserId();
+ UserInfo bgUser = createUser("Background User", UserManager.USER_TYPE_FULL_SECONDARY, 0);
+ int bgUserUid = bgUser.id * 100000;
+
+ AudioAttributes aa = new AudioAttributes.Builder().setUsage(USAGE_ALARM).build();
+ AudioFocusInfo afi = new AudioFocusInfo(aa, bgUserUid, "", "",
+ AudioManager.AUDIOFOCUS_GAIN, 0, 0, Build.VERSION.SDK_INT);
+
+ mBackgroundUserSoundNotifier.getAudioPolicyFocusListener()
+ .onAudioFocusGrant(afi, AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
+
+ verify(mNotificationManager)
+ .notifyAsUser(eq(BackgroundUserSoundNotifier.class.getSimpleName()),
+ eq(afi.getClientUid()), any(Notification.class),
+ eq(UserHandle.of(fgUserId)));
+ }
+
+
+ @Test
+ public void testCreateNotification_UserSwitcherEnabled_bothActionsAvailable() {
+ String userName = "BgUser";
+
+ doReturn(true).when(mUserManager).isUserSwitcherEnabled();
+ doReturn(UserManager.SWITCHABILITY_STATUS_OK)
+ .when(mUserManager).getUserSwitchability(any());
+
+ Notification notification = mBackgroundUserSoundNotifier.createNotification(userName,
+ mSpiedContext);
+
+ assertEquals("Alarm for BgUser", notification.extras.getString(
+ Notification.EXTRA_TITLE));
+ assertEquals(Notification.CATEGORY_REMINDER, notification.category);
+ assertEquals(Notification.VISIBILITY_PUBLIC, notification.visibility);
+ assertEquals(com.android.internal.R.drawable.ic_audio_alarm,
+ notification.getSmallIcon().getResId());
+
+ assertEquals(2, notification.actions.length);
+ assertEquals(mSpiedContext.getString(
+ com.android.internal.R.string.bg_user_sound_notification_button_mute),
+ notification.actions[0].title);
+ assertEquals(mSpiedContext.getString(
+ com.android.internal.R.string.bg_user_sound_notification_button_switch_user),
+ notification.actions[1].title);
+ }
+
+ @Test
+ public void testCreateNotification_UserSwitcherDisabled_onlyMuteActionAvailable() {
+ String userName = "BgUser";
+
+ doReturn(false).when(mUserManager).isUserSwitcherEnabled();
+ doReturn(UserManager.SWITCHABILITY_STATUS_USER_SWITCH_DISALLOWED)
+ .when(mUserManager).getUserSwitchability(any());
+
+ Notification notification = mBackgroundUserSoundNotifier.createNotification(userName,
+ mSpiedContext);
+
+ assertEquals(1, notification.actions.length);
+ assertEquals(mSpiedContext.getString(
+ com.android.internal.R.string.bg_user_sound_notification_button_mute),
+ notification.actions[0].title);
+ }
private UserInfo createUser(String name, String userType, int flags) {
UserInfo user = mUserManager.createUser(name, userType, flags);
diff --git a/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java b/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
index 44aa868..a55aa23 100644
--- a/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
+++ b/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
@@ -30,7 +30,6 @@
import android.os.FileUtils;
import android.os.ParcelFileDescriptor;
import android.platform.test.annotations.Presubmit;
-import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.system.Os;
@@ -41,8 +40,6 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
-import com.android.text.flags.Flags;
-
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
@@ -1106,7 +1103,6 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void signatureMissingCase_fontFamilyInstalled_fontFamilyInstallLater() {
// Install font families, foo.ttf, bar.ttf.
installTestFontFamilies(1 /* version */);
@@ -1126,7 +1122,6 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void signatureMissingCase_fontFamilyInstalled_fontInstallLater() {
// Install font families, foo.ttf, bar.ttf.
installTestFontFamilies(1);
@@ -1146,7 +1141,6 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void signatureMissingCase_fontFileInstalled_fontFamilyInstallLater() {
// Install font file, foo.ttf and bar.ttf
installTestFontFile(2 /* numFonts */, 1 /* version */);
@@ -1166,7 +1160,6 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void signatureMissingCase_fontFileInstalled_fontFileInstallLater() {
// Install font file, foo.ttf and bar.ttf
installTestFontFile(2 /* numFonts */, 1 /* version */);
@@ -1186,7 +1179,6 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void signatureAllMissingCase_fontFamilyInstalled_fontFamilyInstallLater() {
// Install font families, foo.ttf, bar.ttf.
installTestFontFamilies(1 /* version */);
@@ -1206,7 +1198,6 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void signatureAllMissingCase_fontFamilyInstalled_fontInstallLater() {
// Install font families, foo.ttf, bar.ttf.
installTestFontFamilies(1 /* version */);
@@ -1226,7 +1217,6 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void signatureAllMissingCase_fontFileInstalled_fontFamilyInstallLater() {
// Install font file, foo.ttf
installTestFontFile(1 /* numFonts */, 1 /* version */);
@@ -1246,7 +1236,6 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void signatureAllMissingCase_fontFileInstalled_fontFileInstallLater() {
// Install font file, foo.ttf
installTestFontFile(1 /* numFonts */, 1 /* version */);
@@ -1266,7 +1255,6 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void fontMissingCase_fontFamilyInstalled_fontFamilyInstallLater() {
// Install font families, foo.ttf, bar.ttf.
installTestFontFamilies(1 /* version */);
@@ -1286,7 +1274,6 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void fontMissingCase_fontFamilyInstalled_fontInstallLater() {
// Install font families, foo.ttf, bar.ttf.
installTestFontFamilies(1);
@@ -1306,7 +1293,6 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void fontMissingCase_fontFileInstalled_fontFamilyInstallLater() {
// Install font file, foo.ttf and bar.ttf
installTestFontFile(2 /* numFonts */, 1 /* version */);
@@ -1326,7 +1312,6 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void fontMissingCase_fontFileInstalled_fontFileInstallLater() {
// Install font file, foo.ttf and bar.ttf
installTestFontFile(2 /* numFonts */, 1 /* version */);
@@ -1346,7 +1331,6 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void fontAllMissingCase_fontFamilyInstalled_fontFamilyInstallLater() {
// Install font families, foo.ttf, bar.ttf.
installTestFontFamilies(1 /* version */);
@@ -1366,7 +1350,6 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void fontAllMissingCase_fontFamilyInstalled_fontInstallLater() {
// Install font families, foo.ttf, bar.ttf.
installTestFontFamilies(1 /* version */);
@@ -1386,7 +1369,6 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void fontAllMissingCase_fontFileInstalled_fontFamilyInstallLater() {
// Install font file, foo.ttf
installTestFontFile(1 /* numFonts */, 1 /* version */);
@@ -1406,7 +1388,6 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void fontAllMissingCase_fontFileInstalled_fontFileInstallLater() {
// Install font file, foo.ttf
installTestFontFile(1 /* numFonts */, 1 /* version */);
@@ -1426,7 +1407,6 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void fontDirAllMissingCase_fontFamilyInstalled_fontFamilyInstallLater() {
// Install font families, foo.ttf, bar.ttf.
installTestFontFamilies(1 /* version */);
@@ -1446,7 +1426,6 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void fontDirAllMissingCase_fontFamilyInstalled_fontInstallLater() {
// Install font families, foo.ttf, bar.ttf.
installTestFontFamilies(1 /* version */);
@@ -1466,7 +1445,6 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void fontDirAllMissingCase_fontFileInstalled_fontFamilyInstallLater() {
// Install font file, foo.ttf
installTestFontFile(1 /* numFonts */, 1 /* version */);
@@ -1486,7 +1464,6 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void fontDirAllMissingCase_fontFileInstalled_fontFileInstallLater() {
// Install font file, foo.ttf
installTestFontFile(1 /* numFonts */, 1 /* version */);
@@ -1506,7 +1483,6 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void dirContentAllMissingCase_fontFamilyInstalled_fontFamilyInstallLater() {
// Install font families, foo.ttf, bar.ttf.
installTestFontFamilies(1 /* version */);
@@ -1527,7 +1503,6 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void dirContentAllMissingCase_fontFamilyInstalled_fontInstallLater() {
// Install font families, foo.ttf, bar.ttf.
installTestFontFamilies(1 /* version */);
@@ -1548,7 +1523,6 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void dirContentAllMissingCase_fontFileInstalled_fontFamilyInstallLater() {
// Install font file, foo.ttf
installTestFontFile(1 /* numFonts */, 1 /* version */);
@@ -1569,7 +1543,6 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void dirContentAllMissingCase_fontFileInstalled_fontFileInstallLater() {
// Install font file, foo.ttf
installTestFontFile(1 /* numFonts */, 1 /* version */);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/VibratorHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/VibratorHelperTest.java
index 4d2396c..65b4ac1 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/VibratorHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/VibratorHelperTest.java
@@ -104,6 +104,12 @@
}
@Test
+ public void createVibrationEffectFromSoundUri_opaqueUri() {
+ Uri uri = Uri.parse("a:b#c");
+ assertNull(mVibratorHelper.createVibrationEffectFromSoundUri(uri));
+ }
+
+ @Test
public void createVibrationEffectFromSoundUri_uriWithoutRequiredQueryParameter() {
Uri uri = Settings.System.DEFAULT_NOTIFICATION_URI;
assertNull(mVibratorHelper.createVibrationEffectFromSoundUri(uri));
diff --git a/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewTest.java b/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewTest.java
index 681b7f2..eac2e92 100644
--- a/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewTest.java
+++ b/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewTest.java
@@ -29,7 +29,9 @@
import android.graphics.Color;
import android.graphics.Rect;
import android.graphics.drawable.ColorDrawable;
+import android.hardware.input.InputManager;
import android.testing.TestableContext;
+import android.view.InputDevice;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
@@ -60,13 +62,15 @@
*/
@RunWith(AndroidJUnit4.class)
public class TouchpadDebugViewTest {
- private static final int TOUCHPAD_DEVICE_ID = 6;
+ private static final int TOUCHPAD_DEVICE_ID = 60;
private TouchpadDebugView mTouchpadDebugView;
private WindowManager.LayoutParams mWindowLayoutParams;
@Mock
WindowManager mWindowManager;
+ @Mock
+ InputManager mInputManager;
Rect mWindowBounds;
WindowMetrics mWindowMetrics;
@@ -79,12 +83,21 @@
mTestableContext = new TestableContext(context);
mTestableContext.addMockSystemService(WindowManager.class, mWindowManager);
+ mTestableContext.addMockSystemService(InputManager.class, mInputManager);
mWindowBounds = new Rect(0, 0, 2560, 1600);
mWindowMetrics = new WindowMetrics(mWindowBounds, new WindowInsets(mWindowBounds), 1.0f);
when(mWindowManager.getCurrentWindowMetrics()).thenReturn(mWindowMetrics);
+ InputDevice inputDevice = new InputDevice.Builder()
+ .setId(TOUCHPAD_DEVICE_ID)
+ .setSources(InputDevice.SOURCE_TOUCHPAD | InputDevice.SOURCE_MOUSE)
+ .setName("Test Device " + TOUCHPAD_DEVICE_ID)
+ .build();
+
+ when(mInputManager.getInputDevice(TOUCHPAD_DEVICE_ID)).thenReturn(inputDevice);
+
mTouchpadDebugView = new TouchpadDebugView(mTestableContext, TOUCHPAD_DEVICE_ID,
new TouchpadHardwareProperties.Builder(0f, 0f, 500f,
500f, 45f, 47f, -4f, 5f, (short) 10, true,