Merge "Ignore SQL exceptions in getAccountsAsUserForPackage and syncSharedAccounts." into main
diff --git a/core/java/android/hardware/input/input_framework.aconfig b/core/java/android/hardware/input/input_framework.aconfig
index 0cd2800..b4ad050 100644
--- a/core/java/android/hardware/input/input_framework.aconfig
+++ b/core/java/android/hardware/input/input_framework.aconfig
@@ -75,3 +75,13 @@
description: "Enables a developer overlay that displays raw touchpad input data and gesture recognition status in real-time."
bug: "286551975"
}
+
+flag {
+ namespace: "input_native"
+ name: "keyboard_layout_manager_multi_user_ime_setup"
+ description: "Update KeyboardLayoutManager to work correctly with multi-user IME setup"
+ bug: "354333072"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/core/java/android/os/Vibrator.java b/core/java/android/os/Vibrator.java
index 71c83f2..99bd67b 100644
--- a/core/java/android/os/Vibrator.java
+++ b/core/java/android/os/Vibrator.java
@@ -497,7 +497,27 @@
*/
@RequiresPermission(android.Manifest.permission.VIBRATE)
public void vibrate(@NonNull VibrationEffect vibe, @NonNull VibrationAttributes attributes) {
- vibrate(Process.myUid(), mPackageName, vibe, null, attributes);
+ vibrate(vibe, attributes, null);
+ }
+
+ /**
+ * Vibrate with a given effect.
+ *
+ * <p>The app should be in the foreground for the vibration to happen. Background apps should
+ * specify a ringtone, notification or alarm usage in order to vibrate.</p>
+ *
+ * @param vibe {@link VibrationEffect} describing the vibration to be performed.
+ * @param attributes {@link VibrationAttributes} corresponding to the vibration. For example,
+ * specify {@link VibrationAttributes#USAGE_ALARM} for alarm vibrations or
+ * {@link VibrationAttributes#USAGE_RINGTONE} for vibrations associated with
+ * incoming calls.
+ * @param reason the reason for this vibration, used for debugging purposes.
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.VIBRATE)
+ public void vibrate(@NonNull VibrationEffect vibe,
+ @NonNull VibrationAttributes attributes, @NonNull String reason) {
+ vibrate(Process.myUid(), mPackageName, vibe, reason, attributes);
}
/**
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 46b222b..66d08f9 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -404,8 +404,7 @@
}
private void prepareToDraw() {
- if (mDisplayState == Display.STATE_DOZE
- || mDisplayState == Display.STATE_DOZE_SUSPEND) {
+ if (mDisplayState == Display.STATE_DOZE) {
try {
mSession.pokeDrawLock(mWindow);
} catch (RemoteException e) {
diff --git a/core/java/android/text/ClientFlags.java b/core/java/android/text/ClientFlags.java
index b07534f..5d84d17 100644
--- a/core/java/android/text/ClientFlags.java
+++ b/core/java/android/text/ClientFlags.java
@@ -68,11 +68,4 @@
public static boolean fixMisalignedContextMenu() {
return TextFlags.isFeatureEnabled(Flags.FLAG_FIX_MISALIGNED_CONTEXT_MENU);
}
-
- /**
- * @see Flags#clearFontVariationSettings()
- */
- public static boolean clearFontVariationSettings() {
- return TextFlags.isFeatureEnabled(Flags.FLAG_CLEAR_FONT_VARIATION_SETTINGS);
- }
}
diff --git a/core/java/android/text/TextFlags.java b/core/java/android/text/TextFlags.java
index 4dca284..9e02460 100644
--- a/core/java/android/text/TextFlags.java
+++ b/core/java/android/text/TextFlags.java
@@ -61,7 +61,6 @@
Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE,
Flags.FLAG_ICU_BIDI_MIGRATION,
Flags.FLAG_FIX_MISALIGNED_CONTEXT_MENU,
- Flags.FLAG_CLEAR_FONT_VARIATION_SETTINGS,
};
/**
@@ -76,7 +75,6 @@
Flags.fixLineHeightForLocale(),
Flags.icuBidiMigration(),
Flags.fixMisalignedContextMenu(),
- Flags.clearFontVariationSettings(),
};
/**
diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp
index 809ec63..e5ac0e1 100644
--- a/core/jni/android_util_Process.cpp
+++ b/core/jni/android_util_Process.cpp
@@ -33,6 +33,7 @@
#include <algorithm>
#include <array>
+#include <cstring>
#include <limits>
#include <memory>
#include <string>
@@ -50,7 +51,6 @@
#include <inttypes.h>
#include <pwd.h>
#include <signal.h>
-#include <string.h>
#include <sys/epoll.h>
#include <sys/errno.h>
#include <sys/pidfd.h>
@@ -73,13 +73,13 @@
// readProcFile() are reading files under this threshold, e.g.,
// /proc/pid/stat. /proc/pid/time_in_state ends up being about 520
// bytes, so use 1024 for the stack to provide a bit of slack.
-static constexpr ssize_t kProcReadStackBufferSize = 1024;
+static constexpr size_t kProcReadStackBufferSize = 1024;
// The other files we read from proc tend to be a bit larger (e.g.,
// /proc/stat is about 3kB), so once we exhaust the stack buffer,
// retry with a relatively large heap-allocated buffer. We double
// this size and retry until the whole file fits.
-static constexpr ssize_t kProcReadMinHeapBufferSize = 4096;
+static constexpr size_t kProcReadMinHeapBufferSize = 4096;
#if GUARD_THREAD_PRIORITY
Mutex gKeyCreateMutex;
@@ -817,7 +817,6 @@
}
DIR* dirp = opendir(file8);
-
env->ReleaseStringUTFChars(file, file8);
if(dirp == NULL) {
@@ -850,6 +849,7 @@
jintArray newArray = env->NewIntArray(newCount);
if (newArray == NULL) {
closedir(dirp);
+ if (curData) env->ReleaseIntArrayElements(lastArray, curData, 0);
jniThrowException(env, "java/lang/OutOfMemoryError", NULL);
return NULL;
}
@@ -1046,78 +1046,71 @@
return JNI_FALSE;
}
- const char* file8 = env->GetStringUTFChars(file, NULL);
- if (file8 == NULL) {
+ auto releaser = [&](const char* jniStr) { env->ReleaseStringUTFChars(file, jniStr); };
+ std::unique_ptr<const char[], decltype(releaser)> file8(env->GetStringUTFChars(file, NULL),
+ releaser);
+ if (!file8) {
jniThrowException(env, "java/lang/OutOfMemoryError", NULL);
return JNI_FALSE;
}
- ::android::base::unique_fd fd(open(file8, O_RDONLY | O_CLOEXEC));
+ ::android::base::unique_fd fd(open(file8.get(), O_RDONLY | O_CLOEXEC));
if (!fd.ok()) {
if (kDebugProc) {
- ALOGW("Unable to open process file: %s\n", file8);
+ ALOGW("Unable to open process file: %s\n", file8.get());
}
- env->ReleaseStringUTFChars(file, file8);
return JNI_FALSE;
}
- env->ReleaseStringUTFChars(file, file8);
// Most proc files we read are small, so we go through the loop
- // with the stack buffer firstly. We allocate a buffer big
- // enough for the whole file.
+ // with the stack buffer first. We allocate a buffer big enough
+ // for most files.
- char readBufferStack[kProcReadStackBufferSize];
- std::unique_ptr<char[]> readBufferHeap;
- char* readBuffer = &readBufferStack[0];
- ssize_t readBufferSize = kProcReadStackBufferSize;
- ssize_t numberBytesRead;
+ char stackBuf[kProcReadStackBufferSize];
+ std::vector<char> heapBuf;
+ char* buf = stackBuf;
+
+ size_t remaining = sizeof(stackBuf);
off_t offset = 0;
- for (;;) {
- ssize_t requestedBufferSize = readBufferSize - offset;
- // By using pread, we can avoid an lseek to rewind the FD
- // before retry, saving a system call.
- numberBytesRead =
- TEMP_FAILURE_RETRY(pread(fd, readBuffer + offset, requestedBufferSize, offset));
- if (numberBytesRead < 0) {
+ ssize_t numBytesRead;
+
+ do {
+ numBytesRead = TEMP_FAILURE_RETRY(pread(fd, buf + offset, remaining, offset));
+ if (numBytesRead < 0) {
if (kDebugProc) {
ALOGW("Unable to read process file err: %s file: %s fd=%d\n",
- strerror_r(errno, &readBufferStack[0], sizeof(readBufferStack)), file8,
- fd.get());
+ strerror_r(errno, stackBuf, sizeof(stackBuf)), file8.get(), fd.get());
}
return JNI_FALSE;
}
- if (numberBytesRead == 0) {
- // End of file.
- numberBytesRead = offset;
- break;
- }
- if (numberBytesRead < requestedBufferSize) {
- // Read less bytes than requested, it's not an error per pread(2).
- offset += numberBytesRead;
- } else {
- // Buffer is fully used, try to grow it.
- if (readBufferSize > std::numeric_limits<ssize_t>::max() / 2) {
- if (kDebugProc) {
- ALOGW("Proc file too big: %s fd=%d\n", file8, fd.get());
+
+ offset += numBytesRead;
+ remaining -= numBytesRead;
+
+ if (numBytesRead && !remaining) {
+ if (buf == stackBuf) {
+ heapBuf.resize(kProcReadMinHeapBufferSize);
+ static_assert(kProcReadMinHeapBufferSize > sizeof(stackBuf));
+ std::memcpy(heapBuf.data(), stackBuf, sizeof(stackBuf));
+ } else {
+ constexpr size_t MAX_READABLE_PROCFILE_SIZE = 64 << 20;
+ if (heapBuf.size() >= MAX_READABLE_PROCFILE_SIZE) {
+ if (kDebugProc) {
+ ALOGW("Proc file too big: %s fd=%d size=%zu\n",
+ file8.get(), fd.get(), heapBuf.size());
+ }
+ return JNI_FALSE;
}
- return JNI_FALSE;
+ heapBuf.resize(2 * heapBuf.size());
}
- readBufferSize = std::max(readBufferSize * 2, kProcReadMinHeapBufferSize);
- readBufferHeap.reset(); // Free address space before getting more.
- readBufferHeap = std::make_unique<char[]>(readBufferSize);
- if (!readBufferHeap) {
- jniThrowException(env, "java/lang/OutOfMemoryError", NULL);
- return JNI_FALSE;
- }
- readBuffer = readBufferHeap.get();
- offset = 0;
+ buf = heapBuf.data();
+ remaining = heapBuf.size() - offset;
}
- }
+ } while (numBytesRead != 0);
// parseProcLineArray below modifies the buffer while parsing!
return android_os_Process_parseProcLineArray(
- env, clazz, readBuffer, 0, numberBytesRead,
- format, outStrings, outLongs, outFloats);
+ env, clazz, buf, 0, offset, format, outStrings, outLongs, outFloats);
}
void android_os_Process_setApplicationObject(JNIEnv* env, jobject clazz,
diff --git a/core/tests/vibrator/src/android/os/VibratorTest.java b/core/tests/vibrator/src/android/os/VibratorTest.java
index cfa12bb..6210a00 100644
--- a/core/tests/vibrator/src/android/os/VibratorTest.java
+++ b/core/tests/vibrator/src/android/os/VibratorTest.java
@@ -222,6 +222,18 @@
}
@Test
+ public void vibrate_withVibrationAttributesAndReason_usesGivenAttributesAndReason() {
+ VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
+ VibrationAttributes attributes = new VibrationAttributes.Builder().setUsage(
+ VibrationAttributes.USAGE_TOUCH).build();
+ String reason = "reason";
+
+ mVibratorSpy.vibrate(effect, attributes, reason);
+
+ verify(mVibratorSpy).vibrate(anyInt(), anyString(), eq(effect), eq(reason), eq(attributes));
+ }
+
+ @Test
public void vibrate_withAudioAttributes_createsVibrationAttributesWithSameUsage() {
VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
AudioAttributes audioAttributes = new AudioAttributes.Builder().setUsage(
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 5d4139e..1fe6ca7 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -505,6 +505,7 @@
<permission name="android.permission.RENOUNCE_PERMISSIONS" />
<permission name="android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS" />
<permission name="android.permission.GET_PROCESS_STATE_AND_OOM_SCORE" />
+ <permission name="android.permission.READ_DROPBOX_DATA" />
<permission name="android.permission.READ_LOGS" />
<permission name="android.permission.BRIGHTNESS_SLIDER_USAGE" />
<permission name="android.permission.ACCESS_AMBIENT_LIGHT_STATS" />
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index b83931f..df95a91 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -35,7 +35,6 @@
import android.graphics.fonts.FontVariationAxis;
import android.os.Build;
import android.os.LocaleList;
-import android.text.ClientFlags;
import android.text.GraphicsOperations;
import android.text.SpannableString;
import android.text.SpannedString;
@@ -1541,21 +1540,8 @@
* @return typeface
*/
public Typeface setTypeface(Typeface typeface) {
- return setTypefaceInternal(typeface, true);
- }
-
- private Typeface setTypefaceInternal(Typeface typeface, boolean clearFontVariationSettings) {
final long typefaceNative = typeface == null ? 0 : typeface.native_instance;
nSetTypeface(mNativePaint, typefaceNative);
-
- if (ClientFlags.clearFontVariationSettings()) {
- if (clearFontVariationSettings && !Objects.equals(mTypeface, typeface)) {
- // We cannot call setFontVariationSetting with empty string or null because it calls
- // setTypeface method. To avoid recursive setTypeface call, manually resetting
- // mFontVariationSettings.
- mFontVariationSettings = null;
- }
- }
mTypeface = typeface;
return typeface;
}
@@ -2051,14 +2037,6 @@
* </li>
* </ul>
*
- * Note: This method replaces the Typeface previously set to this instance.
- * Until API {@link Build.VERSION_CODES.VANILLA_ICE_CREAM}, any caller of
- * {@link #setTypeface(Typeface)} should call this method with empty settings, then call
- * {@link #setTypeface(Typeface)}, then call this method with preferred variation settings.
- * The device API more than {@link Build.VERSION_CODES.VANILLA_ICE_CREAM}, the
- * {@link #setTypeface(Typeface)} method clears font variation settings. So caller of
- * {@link #setTypeface(Typeface)} should call this method again for applying variation settings.
- *
* @param fontVariationSettings font variation settings. You can pass null or empty string as
* no variation settings.
*
@@ -2081,8 +2059,8 @@
if (settings == null || settings.length() == 0) {
mFontVariationSettings = null;
- setTypefaceInternal(Typeface.createFromTypefaceWithVariation(mTypeface,
- Collections.emptyList()), false);
+ setTypeface(Typeface.createFromTypefaceWithVariation(mTypeface,
+ Collections.emptyList()));
return true;
}
@@ -2100,8 +2078,7 @@
return false;
}
mFontVariationSettings = settings;
- setTypefaceInternal(Typeface.createFromTypefaceWithVariation(targetTypeface, filteredAxes),
- false);
+ setTypeface(Typeface.createFromTypefaceWithVariation(targetTypeface, filteredAxes));
return true;
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java
index 544f0f3..3fc409a 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java
@@ -1090,13 +1090,14 @@
@NonNull
private final SurfaceControl mDividerSurface;
@NonNull
+ private final SurfaceControl mDividerLineSurface;
+ @NonNull
private final WindowlessWindowManager mWindowlessWindowManager;
@NonNull
private final SurfaceControlViewHost mViewHost;
@NonNull
private final FrameLayout mDividerLayout;
- @NonNull
- private final View mDividerLine;
+ @Nullable
private View mDragHandle;
@NonNull
private final View.OnTouchListener mListener;
@@ -1115,7 +1116,10 @@
mProperties = properties;
mListener = listener;
- mDividerSurface = createChildSurface("DividerSurface", true /* visible */);
+ mDividerSurface = createChildSurface(
+ mProperties.mDecorSurface, "DividerSurface", true /* visible */);
+ mDividerLineSurface = createChildSurface(
+ mDividerSurface, "DividerLineSurface", true /* visible */);
mWindowlessWindowManager = new WindowlessWindowManager(
mProperties.mConfiguration,
mDividerSurface,
@@ -1127,7 +1131,6 @@
context, displayManager.getDisplay(mProperties.mDisplayId),
mWindowlessWindowManager, "DividerContainer");
mDividerLayout = new FrameLayout(context);
- mDividerLine = new View(context);
update();
}
@@ -1220,6 +1223,7 @@
dividerSurfacePosition = mDividerPosition;
}
+ // Update the divider surface position relative to the decor surface
if (mProperties.mIsVerticalSplit) {
t.setPosition(mDividerSurface, dividerSurfacePosition, 0.0f);
t.setWindowCrop(mDividerSurface, mDividerSurfaceWidthPx, taskBounds.height());
@@ -1228,10 +1232,24 @@
t.setWindowCrop(mDividerSurface, taskBounds.width(), mDividerSurfaceWidthPx);
}
- // Update divider line position in the surface
+ // Update divider line surface position relative to the divider surface
final int offset = mDividerPosition - dividerSurfacePosition;
- mDividerLine.setX(mProperties.mIsVerticalSplit ? offset : 0);
- mDividerLine.setY(mProperties.mIsVerticalSplit ? 0 : offset);
+ if (mProperties.mIsVerticalSplit) {
+ t.setPosition(mDividerLineSurface, offset, 0);
+ t.setWindowCrop(mDividerLineSurface,
+ mProperties.mDividerWidthPx, taskBounds.height());
+ } else {
+ t.setPosition(mDividerLineSurface, 0, offset);
+ t.setWindowCrop(mDividerLineSurface,
+ taskBounds.width(), mProperties.mDividerWidthPx);
+ }
+
+ // Update divider line surface visibility and color.
+ // If a container is fully expanded, the divider line is invisible unless dragging.
+ final boolean isDividerLineVisible = !mProperties.mIsDraggableExpandType || mIsDragging;
+ t.setVisibility(mDividerLineSurface, isDividerLineVisible);
+ t.setColor(mDividerLineSurface, colorToFloatArray(
+ Color.valueOf(mProperties.mDividerAttributes.getDividerColor())));
if (mIsDragging) {
updateVeils(t);
@@ -1277,21 +1295,6 @@
*/
private void updateDivider(@NonNull SurfaceControl.Transaction t) {
mDividerLayout.removeAllViews();
- mDividerLayout.addView(mDividerLine);
- if (mProperties.mIsDraggableExpandType && !mIsDragging) {
- // If a container is fully expanded, the divider overlays on the expanded container.
- mDividerLine.setBackgroundColor(Color.TRANSPARENT);
- } else {
- mDividerLine.setBackgroundColor(mProperties.mDividerAttributes.getDividerColor());
- }
- final Rect taskBounds = mProperties.mConfiguration.windowConfiguration.getBounds();
- mDividerLine.setLayoutParams(
- mProperties.mIsVerticalSplit
- ? new FrameLayout.LayoutParams(
- mProperties.mDividerWidthPx, taskBounds.height())
- : new FrameLayout.LayoutParams(
- taskBounds.width(), mProperties.mDividerWidthPx)
- );
if (mProperties.mDividerAttributes.getDividerType()
== DividerAttributes.DIVIDER_TYPE_DRAGGABLE) {
createVeils();
@@ -1345,10 +1348,11 @@
}
@NonNull
- private SurfaceControl createChildSurface(@NonNull String name, boolean visible) {
+ private SurfaceControl createChildSurface(
+ @NonNull SurfaceControl parent, @NonNull String name, boolean visible) {
final Rect bounds = mProperties.mConfiguration.windowConfiguration.getBounds();
return new SurfaceControl.Builder()
- .setParent(mProperties.mDecorSurface)
+ .setParent(parent)
.setName(name)
.setHidden(!visible)
.setCallsite("DividerManager.createChildSurface")
@@ -1359,10 +1363,12 @@
private void createVeils() {
if (mPrimaryVeil == null) {
- mPrimaryVeil = createChildSurface("DividerPrimaryVeil", false /* visible */);
+ mPrimaryVeil = createChildSurface(
+ mProperties.mDecorSurface, "DividerPrimaryVeil", false /* visible */);
}
if (mSecondaryVeil == null) {
- mSecondaryVeil = createChildSurface("DividerSecondaryVeil", false /* visible */);
+ mSecondaryVeil = createChildSurface(
+ mProperties.mDecorSurface, "DividerSecondaryVeil", false /* visible */);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index a52141c5..ba97c832 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -21,6 +21,7 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.util.RotationUtils.deltaRotation;
import static android.util.RotationUtils.rotateBounds;
+import static android.view.Surface.ROTATION_0;
import static android.view.Surface.ROTATION_270;
import static android.view.Surface.ROTATION_90;
import static android.view.WindowManager.TRANSIT_CHANGE;
@@ -1183,10 +1184,15 @@
? pipTaskInfo.configuration.windowConfiguration.getBounds()
: mPipOrganizer.mAppBounds;
+ // Populate the final surface control transactions from PipTransitionAnimator,
+ // display cutout insets is handled in the swipe pip to home animator, empty it out here
+ // to avoid flicker.
+ final Rect savedDisplayCutoutInsets = new Rect(pipTaskInfo.displayCutoutInsets);
+ pipTaskInfo.displayCutoutInsets.setEmpty();
final PipAnimationController.PipTransitionAnimator animator =
mPipAnimationController.getAnimator(pipTaskInfo, leash, sourceBounds, sourceBounds,
destinationBounds, sourceHintRect, TRANSITION_DIRECTION_TO_PIP,
- 0 /* startingAngle */, 0 /* rotationDelta */)
+ 0 /* startingAngle */, ROTATION_0 /* rotationDelta */)
.setPipTransactionHandler(mTransactionConsumer)
.setTransitionDirection(TRANSITION_DIRECTION_TO_PIP);
// The start state is the end state for swipe-auto-pip.
@@ -1194,6 +1200,7 @@
animator.applySurfaceControlTransaction(leash, startTransaction,
PipAnimationController.FRACTION_END);
startTransaction.apply();
+ pipTaskInfo.displayCutoutInsets.set(savedDisplayCutoutInsets);
mPipBoundsState.setBounds(destinationBounds);
final SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
diff --git a/libs/hwui/jni/Bitmap.cpp b/libs/hwui/jni/Bitmap.cpp
index d415700..010c4e8 100644
--- a/libs/hwui/jni/Bitmap.cpp
+++ b/libs/hwui/jni/Bitmap.cpp
@@ -33,9 +33,6 @@
#define DEBUG_PARCEL 0
static jclass gBitmap_class;
-static jfieldID gBitmap_nativePtr;
-static jmethodID gBitmap_constructorMethodID;
-static jmethodID gBitmap_reinitMethodID;
namespace android {
@@ -183,6 +180,9 @@
void reinitBitmap(JNIEnv* env, jobject javaBitmap, const SkImageInfo& info,
bool isPremultiplied)
{
+ static jmethodID gBitmap_reinitMethodID =
+ GetMethodIDOrDie(env, gBitmap_class, "reinit", "(IIZ)V");
+
// The caller needs to have already set the alpha type properly, so the
// native SkBitmap stays in sync with the Java Bitmap.
assert_premultiplied(info, isPremultiplied);
@@ -194,6 +194,10 @@
jobject createBitmap(JNIEnv* env, Bitmap* bitmap,
int bitmapCreateFlags, jbyteArray ninePatchChunk, jobject ninePatchInsets,
int density) {
+ static jmethodID gBitmap_constructorMethodID =
+ GetMethodIDOrDie(env, gBitmap_class,
+ "<init>", "(JIIIZ[BLandroid/graphics/NinePatch$InsetStruct;Z)V");
+
bool isMutable = bitmapCreateFlags & kBitmapCreateFlag_Mutable;
bool isPremultiplied = bitmapCreateFlags & kBitmapCreateFlag_Premultiplied;
// The caller needs to have already set the alpha type properly, so the
@@ -232,11 +236,17 @@
using namespace android;
using namespace android::bitmap;
+static inline jlong getNativePtr(JNIEnv* env, jobject bitmap) {
+ static jfieldID gBitmap_nativePtr =
+ GetFieldIDOrDie(env, gBitmap_class, "mNativePtr", "J");
+ return env->GetLongField(bitmap, gBitmap_nativePtr);
+}
+
Bitmap* GraphicsJNI::getNativeBitmap(JNIEnv* env, jobject bitmap) {
SkASSERT(env);
SkASSERT(bitmap);
SkASSERT(env->IsInstanceOf(bitmap, gBitmap_class));
- jlong bitmapHandle = env->GetLongField(bitmap, gBitmap_nativePtr);
+ jlong bitmapHandle = getNativePtr(env, bitmap);
LocalScopedBitmap localBitmap(bitmapHandle);
return localBitmap.valid() ? &localBitmap->bitmap() : nullptr;
}
@@ -246,7 +256,7 @@
SkASSERT(env);
SkASSERT(bitmap);
SkASSERT(env->IsInstanceOf(bitmap, gBitmap_class));
- jlong bitmapHandle = env->GetLongField(bitmap, gBitmap_nativePtr);
+ jlong bitmapHandle = getNativePtr(env, bitmap);
LocalScopedBitmap localBitmap(bitmapHandle);
if (outRowBytes) {
*outRowBytes = localBitmap->rowBytes();
@@ -1269,9 +1279,6 @@
int register_android_graphics_Bitmap(JNIEnv* env)
{
gBitmap_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/graphics/Bitmap"));
- gBitmap_nativePtr = GetFieldIDOrDie(env, gBitmap_class, "mNativePtr", "J");
- gBitmap_constructorMethodID = GetMethodIDOrDie(env, gBitmap_class, "<init>", "(JIIIZ[BLandroid/graphics/NinePatch$InsetStruct;Z)V");
- gBitmap_reinitMethodID = GetMethodIDOrDie(env, gBitmap_class, "reinit", "(IIZ)V");
uirenderer::HardwareBufferHelpers::init();
return android::RegisterMethodsOrDie(env, "android/graphics/Bitmap", gBitmapMethods,
NELEM(gBitmapMethods));
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java
index 03a2b75..990a2d4 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java
@@ -298,6 +298,10 @@
return mIsManualDnd;
}
+ public boolean isEnabled() {
+ return mRule.isEnabled();
+ }
+
public boolean isActive() {
return mStatus == Status.ENABLED_AND_ACTIVE;
}
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 04d30ed..0b5187c 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -65,6 +65,7 @@
<uses-permission android:name="com.android.voicemail.permission.ADD_VOICEMAIL" />
<uses-permission android:name="android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS" />
<uses-permission android:name="android.permission.GET_PROCESS_STATE_AND_OOM_SCORE" />
+ <uses-permission android:name="android.permission.READ_DROPBOX_DATA" />
<uses-permission android:name="android.permission.READ_LOGS" />
<uses-permission android:name="android.permission.BRIGHTNESS_SLIDER_USAGE" />
<uses-permission android:name="android.permission.ACCESS_AMBIENT_LIGHT_STATS" />
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index eb9d0ab..462db34 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -1206,6 +1206,13 @@
}
flag {
+ name: "hubmode_fullscreen_vertical_swipe"
+ namespace: "systemui"
+ description: "Enables fullscreen vertical swiping in hub mode to bring up and down the bouncer and shade"
+ bug: "340177049"
+}
+
+flag {
namespace: "systemui"
name: "remove_update_listener_in_qs_icon_view_impl"
description: "Remove update listeners in QsIconViewImpl class to avoid memory leak."
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt
index 1ce51af..8c38253 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt
@@ -98,13 +98,7 @@
bottom = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Bottom],
)
- val bottomAreaPlaceable =
- bottomAreaMeasurable.measure(
- noMinConstraints.copy(
- maxHeight =
- (constraints.maxHeight - lockIconBounds.bottom).coerceAtLeast(0)
- )
- )
+ val bottomAreaPlaceable = bottomAreaMeasurable.measure(noMinConstraints)
val communalGridPlaceable =
communalGridMeasurable.measure(
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index 650c2d3..ce28e80 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -42,6 +42,7 @@
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.focusable
+import androidx.compose.foundation.gestures.awaitFirstDown
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
@@ -112,6 +113,11 @@
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
+import androidx.compose.ui.input.nestedscroll.NestedScrollSource
+import androidx.compose.ui.input.nestedscroll.nestedScroll
+import androidx.compose.ui.input.pointer.changedToUp
+import androidx.compose.ui.input.pointer.changedToUpIgnoreConsumed
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.layout.LayoutCoordinates
import androidx.compose.ui.layout.boundsInWindow
@@ -138,6 +144,7 @@
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.times
+import androidx.compose.ui.util.fastAll
import androidx.compose.ui.viewinterop.AndroidView
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.window.layout.WindowMetricsCalculator
@@ -204,13 +211,51 @@
ScrollOnUpdatedLiveContentEffect(communalContent, gridState)
}
+ val nestedScrollConnection = remember {
+ object : NestedScrollConnection {
+ override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
+ // Begin tracking nested scrolling
+ viewModel.onNestedScrolling()
+ return super.onPreScroll(available, source)
+ }
+ }
+ }
+
Box(
modifier =
modifier
.semantics { testTagsAsResourceId = true }
.testTag(COMMUNAL_HUB_TEST_TAG)
.fillMaxSize()
+ .nestedScroll(nestedScrollConnection)
.pointerInput(gridState, contentOffset, contentListState) {
+ awaitPointerEventScope {
+ while (true) {
+ var event = awaitFirstDown(requireUnconsumed = false)
+ // Reset touch on first event.
+ viewModel.onResetTouchState()
+
+ // Process down event in case it's consumed immediately
+ if (event.isConsumed) {
+ viewModel.onHubTouchConsumed()
+ }
+
+ do {
+ var event = awaitPointerEvent()
+ for (change in event.changes) {
+ if (change.isConsumed) {
+ // Signal touch consumption on any consumed event.
+ viewModel.onHubTouchConsumed()
+ }
+ }
+ } while (
+ !event.changes.fastAll {
+ it.changedToUp() || it.changedToUpIgnoreConsumed()
+ }
+ )
+ }
+ }
+
// If not in edit mode, don't allow selecting items.
if (!viewModel.isEditMode) return@pointerInput
observeTaps { offset ->
@@ -791,7 +836,7 @@
onClick = onClick,
colors =
ButtonDefaults.outlinedButtonColors(
- contentColor = colors.primary,
+ contentColor = colors.onPrimaryContainer,
),
border = BorderStroke(width = 2.0.dp, color = colors.primary),
contentPadding = Dimensions.ButtonPadding,
@@ -855,7 +900,7 @@
/** Creates an empty card used to highlight a particular spot on the grid. */
@Composable
fun HighlightedItem(modifier: Modifier = Modifier, alpha: Float = 1.0f) {
- val brush = SolidColor(LocalAndroidColorScheme.current.primaryFixed)
+ val brush = SolidColor(LocalAndroidColorScheme.current.primary)
Box(
modifier =
// drawBehind lets us draw outside the bounds of the widgets so that we don't need to
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt
index 86639fa..b40bccb 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt
@@ -98,7 +98,7 @@
) {
MovableElement(
key = IndicationAreaElementKey,
- modifier = modifier.shortcutPadding(),
+ modifier = modifier.indicationAreaPadding(),
) {
content {
IndicationArea(
@@ -210,6 +210,11 @@
)
.padding(bottom = dimensionResource(R.dimen.keyguard_affordance_vertical_offset))
}
+
+ @Composable
+ private fun Modifier.indicationAreaPadding(): Modifier {
+ return this.padding(bottom = dimensionResource(R.dimen.keyguard_indication_margin_bottom))
+ }
}
private val StartButtonElementKey = ElementKey("StartButton")
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerFullscreenSwipeTouchHandlerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerFullscreenSwipeTouchHandlerTest.java
new file mode 100644
index 0000000..4850085
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerFullscreenSwipeTouchHandlerTest.java
@@ -0,0 +1,246 @@
+/*
+ * 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.ambient.touch;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.animation.ValueAnimator;
+import android.content.pm.UserInfo;
+import android.graphics.Rect;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.view.GestureDetector.OnGestureListener;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.logging.UiEventLogger;
+import com.android.internal.widget.LockPatternUtils;
+import com.android.systemui.Flags;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.ambient.touch.scrim.ScrimController;
+import com.android.systemui.ambient.touch.scrim.ScrimManager;
+import com.android.systemui.communal.ui.viewmodel.CommunalViewModel;
+import com.android.systemui.kosmos.KosmosJavaAdapter;
+import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.settings.FakeUserTracker;
+import com.android.systemui.shared.system.InputChannelCompat;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
+import com.android.systemui.statusbar.phone.CentralSurfaces;
+import com.android.wm.shell.animation.FlingAnimationUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Collections;
+import java.util.Optional;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+@EnableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE)
+@DisableFlags(Flags.FLAG_COMMUNAL_BOUNCER_DO_NOT_MODIFY_PLUGIN_OPEN)
+public class BouncerFullscreenSwipeTouchHandlerTest extends SysuiTestCase {
+ private KosmosJavaAdapter mKosmos;
+
+ @Mock
+ CentralSurfaces mCentralSurfaces;
+
+ @Mock
+ ScrimManager mScrimManager;
+
+ @Mock
+ ScrimController mScrimController;
+
+ @Mock
+ NotificationShadeWindowController mNotificationShadeWindowController;
+
+ @Mock
+ FlingAnimationUtils mFlingAnimationUtils;
+
+ @Mock
+ FlingAnimationUtils mFlingAnimationUtilsClosing;
+
+ @Mock
+ TouchHandler.TouchSession mTouchSession;
+
+ BouncerSwipeTouchHandler mTouchHandler;
+
+ @Mock
+ BouncerSwipeTouchHandler.ValueAnimatorCreator mValueAnimatorCreator;
+
+ @Mock
+ ValueAnimator mValueAnimator;
+
+ @Mock
+ BouncerSwipeTouchHandler.VelocityTrackerFactory mVelocityTrackerFactory;
+
+ @Mock
+ VelocityTracker mVelocityTracker;
+
+ @Mock
+ UiEventLogger mUiEventLogger;
+
+ @Mock
+ LockPatternUtils mLockPatternUtils;
+
+ @Mock
+ ActivityStarter mActivityStarter;
+
+ @Mock
+ CommunalViewModel mCommunalViewModel;
+
+ FakeUserTracker mUserTracker;
+
+ private static final float TOUCH_REGION = .3f;
+ private static final float MIN_BOUNCER_HEIGHT = .05f;
+
+ private static final Rect SCREEN_BOUNDS = new Rect(0, 0, 1024, 100);
+ private static final UserInfo CURRENT_USER_INFO = new UserInfo(
+ 10,
+ /* name= */ "user10",
+ /* flags= */ 0
+ );
+
+ @Before
+ public void setup() {
+ mKosmos = new KosmosJavaAdapter(this);
+ MockitoAnnotations.initMocks(this);
+ mUserTracker = new FakeUserTracker();
+ mTouchHandler = new BouncerSwipeTouchHandler(
+ mKosmos.getTestScope(),
+ mScrimManager,
+ Optional.of(mCentralSurfaces),
+ mNotificationShadeWindowController,
+ mValueAnimatorCreator,
+ mVelocityTrackerFactory,
+ mLockPatternUtils,
+ mUserTracker,
+ mCommunalViewModel,
+ mFlingAnimationUtils,
+ mFlingAnimationUtilsClosing,
+ TOUCH_REGION,
+ MIN_BOUNCER_HEIGHT,
+ mUiEventLogger,
+ mActivityStarter);
+
+ when(mScrimManager.getCurrentController()).thenReturn(mScrimController);
+ when(mValueAnimatorCreator.create(anyFloat(), anyFloat())).thenReturn(mValueAnimator);
+ when(mVelocityTrackerFactory.obtain()).thenReturn(mVelocityTracker);
+ when(mFlingAnimationUtils.getMinVelocityPxPerSecond()).thenReturn(Float.MAX_VALUE);
+ when(mTouchSession.getBounds()).thenReturn(SCREEN_BOUNDS);
+ when(mLockPatternUtils.isSecure(CURRENT_USER_INFO.id)).thenReturn(true);
+
+ mUserTracker.set(Collections.singletonList(CURRENT_USER_INFO), 0);
+ }
+
+ /**
+ * Ensures expansion does not happen for full vertical swipes when touch is not available.
+ */
+ @Test
+ public void testFullSwipe_notInitiatedWhenNotAvailable() {
+ mTouchHandler.onGlanceableTouchAvailable(false);
+ mTouchHandler.onSessionStart(mTouchSession);
+ ArgumentCaptor<OnGestureListener> gestureListenerCaptor =
+ ArgumentCaptor.forClass(OnGestureListener.class);
+ verify(mTouchSession).registerGestureListener(gestureListenerCaptor.capture());
+
+ // A touch within range at the bottom of the screen should trigger listening
+ assertThat(gestureListenerCaptor.getValue()
+ .onScroll(Mockito.mock(MotionEvent.class),
+ Mockito.mock(MotionEvent.class),
+ 1,
+ 2)).isFalse();
+ }
+
+ /**
+ * Ensures expansion only happens for full vertical swipes when touch is available.
+ */
+ @Test
+ public void testFullSwipe_initiatedWhenAvailable() {
+ mTouchHandler.onGlanceableTouchAvailable(true);
+ mTouchHandler.onSessionStart(mTouchSession);
+ ArgumentCaptor<OnGestureListener> gestureListenerCaptor =
+ ArgumentCaptor.forClass(OnGestureListener.class);
+ verify(mTouchSession).registerGestureListener(gestureListenerCaptor.capture());
+
+ // A touch within range at the bottom of the screen should trigger listening
+ assertThat(gestureListenerCaptor.getValue()
+ .onScroll(Mockito.mock(MotionEvent.class),
+ Mockito.mock(MotionEvent.class),
+ 1,
+ 2)).isTrue();
+ }
+
+ @Test
+ public void testFullSwipe_motionUpResetsTouchState() {
+ mTouchHandler.onGlanceableTouchAvailable(true);
+ mTouchHandler.onSessionStart(mTouchSession);
+ ArgumentCaptor<OnGestureListener> gestureListenerCaptor =
+ ArgumentCaptor.forClass(OnGestureListener.class);
+ ArgumentCaptor<InputChannelCompat.InputEventListener> inputListenerCaptor =
+ ArgumentCaptor.forClass(InputChannelCompat.InputEventListener.class);
+ verify(mTouchSession).registerGestureListener(gestureListenerCaptor.capture());
+ verify(mTouchSession).registerInputListener(inputListenerCaptor.capture());
+
+ // A touch within range at the bottom of the screen should trigger listening
+ assertThat(gestureListenerCaptor.getValue()
+ .onScroll(Mockito.mock(MotionEvent.class),
+ Mockito.mock(MotionEvent.class),
+ 1,
+ 2)).isTrue();
+
+ MotionEvent upEvent = Mockito.mock(MotionEvent.class);
+ when(upEvent.getAction()).thenReturn(MotionEvent.ACTION_UP);
+ inputListenerCaptor.getValue().onInputEvent(upEvent);
+ verify(mCommunalViewModel).onResetTouchState();
+ }
+
+ @Test
+ public void testFullSwipe_motionCancelResetsTouchState() {
+ mTouchHandler.onGlanceableTouchAvailable(true);
+ mTouchHandler.onSessionStart(mTouchSession);
+ ArgumentCaptor<OnGestureListener> gestureListenerCaptor =
+ ArgumentCaptor.forClass(OnGestureListener.class);
+ ArgumentCaptor<InputChannelCompat.InputEventListener> inputListenerCaptor =
+ ArgumentCaptor.forClass(InputChannelCompat.InputEventListener.class);
+ verify(mTouchSession).registerGestureListener(gestureListenerCaptor.capture());
+ verify(mTouchSession).registerInputListener(inputListenerCaptor.capture());
+
+ // A touch within range at the bottom of the screen should trigger listening
+ assertThat(gestureListenerCaptor.getValue()
+ .onScroll(Mockito.mock(MotionEvent.class),
+ Mockito.mock(MotionEvent.class),
+ 1,
+ 2)).isTrue();
+
+ MotionEvent upEvent = Mockito.mock(MotionEvent.class);
+ when(upEvent.getAction()).thenReturn(MotionEvent.ACTION_CANCEL);
+ inputListenerCaptor.getValue().onInputEvent(upEvent);
+ verify(mCommunalViewModel).onResetTouchState();
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandlerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandlerTest.java
index 7ebc224..0e98b84 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandlerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandlerTest.java
@@ -50,6 +50,8 @@
import com.android.systemui.ambient.touch.scrim.ScrimController;
import com.android.systemui.ambient.touch.scrim.ScrimManager;
import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants;
+import com.android.systemui.communal.ui.viewmodel.CommunalViewModel;
+import com.android.systemui.kosmos.KosmosJavaAdapter;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.settings.FakeUserTracker;
import com.android.systemui.shade.ShadeExpansionChangeEvent;
@@ -72,7 +74,9 @@
@SmallTest
@RunWith(AndroidJUnit4.class)
+@DisableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE)
public class BouncerSwipeTouchHandlerTest extends SysuiTestCase {
+ private KosmosJavaAdapter mKosmos;
@Mock
CentralSurfaces mCentralSurfaces;
@@ -120,6 +124,9 @@
@Mock
Region mRegion;
+ @Mock
+ CommunalViewModel mCommunalViewModel;
+
@Captor
ArgumentCaptor<Rect> mRectCaptor;
@@ -139,9 +146,11 @@
@Before
public void setup() {
+ mKosmos = new KosmosJavaAdapter(this);
MockitoAnnotations.initMocks(this);
mUserTracker = new FakeUserTracker();
mTouchHandler = new BouncerSwipeTouchHandler(
+ mKosmos.getTestScope(),
mScrimManager,
Optional.of(mCentralSurfaces),
mNotificationShadeWindowController,
@@ -149,6 +158,7 @@
mVelocityTrackerFactory,
mLockPatternUtils,
mUserTracker,
+ mCommunalViewModel,
mFlingAnimationUtils,
mFlingAnimationUtilsClosing,
TOUCH_REGION,
@@ -201,7 +211,6 @@
2)).isTrue();
}
-
/**
* Ensures expansion only happens when touch down happens in valid part of the screen.
*/
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.kt
index 7fd9ce2..204d4b0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.kt
@@ -26,8 +26,10 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.ambient.touch.TouchHandler.TouchSession
import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
+import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.flags.Flags.COMMUNAL_SERVICE_ENABLED
import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.kosmos.testScope
import com.android.systemui.shade.ShadeViewController
import com.android.systemui.shared.system.InputChannelCompat
import com.android.systemui.statusbar.phone.CentralSurfaces
@@ -50,11 +52,11 @@
@RunWith(AndroidJUnit4::class)
class ShadeTouchHandlerTest : SysuiTestCase() {
private var kosmos = testKosmos()
-
private var mCentralSurfaces = mock<CentralSurfaces>()
private var mShadeViewController = mock<ShadeViewController>()
private var mDreamManager = mock<DreamManager>()
private var mTouchSession = mock<TouchSession>()
+ private var communalViewModel = mock<CommunalViewModel>()
private lateinit var mTouchHandler: ShadeTouchHandler
@@ -65,9 +67,11 @@
fun setup() {
mTouchHandler =
ShadeTouchHandler(
+ kosmos.testScope,
Optional.of(mCentralSurfaces),
mShadeViewController,
mDreamManager,
+ communalViewModel,
kosmos.communalSettingsInteractor,
TOUCH_HEIGHT
)
@@ -75,6 +79,7 @@
// Verifies that a swipe down in the gesture region is captured by the shade touch handler.
@Test
+ @DisableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE)
fun testSwipeDown_captured() {
val captured = swipe(Direction.DOWN)
Truth.assertThat(captured).isTrue()
@@ -82,6 +87,7 @@
// Verifies that a swipe in the upward direction is not captured.
@Test
+ @DisableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE)
fun testSwipeUp_notCaptured() {
val captured = swipe(Direction.UP)
@@ -91,6 +97,7 @@
// Verifies that a swipe down forwards captured touches to central surfaces for handling.
@Test
+ @DisableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE)
@EnableFlags(Flags.FLAG_COMMUNAL_HUB)
fun testSwipeDown_communalEnabled_sentToCentralSurfaces() {
kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, true)
@@ -103,7 +110,7 @@
// Verifies that a swipe down forwards captured touches to the shade view for handling.
@Test
- @DisableFlags(Flags.FLAG_COMMUNAL_HUB)
+ @DisableFlags(Flags.FLAG_COMMUNAL_HUB, Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE)
fun testSwipeDown_communalDisabled_sentToShadeView() {
swipe(Direction.DOWN)
@@ -114,6 +121,7 @@
// Verifies that a swipe down while dreaming forwards captured touches to the shade view for
// handling.
@Test
+ @DisableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE)
fun testSwipeDown_dreaming_sentToShadeView() {
whenever(mDreamManager.isDreaming).thenReturn(true)
swipe(Direction.DOWN)
@@ -124,6 +132,7 @@
// Verifies that a swipe up is not forwarded to central surfaces.
@Test
+ @DisableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE)
@EnableFlags(Flags.FLAG_COMMUNAL_HUB)
fun testSwipeUp_communalEnabled_touchesNotSent() {
kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, true)
@@ -137,7 +146,7 @@
// Verifies that a swipe up is not forwarded to the shade view.
@Test
- @DisableFlags(Flags.FLAG_COMMUNAL_HUB)
+ @DisableFlags(Flags.FLAG_COMMUNAL_HUB, Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE)
fun testSwipeUp_communalDisabled_touchesNotSent() {
swipe(Direction.UP)
@@ -147,6 +156,7 @@
}
@Test
+ @DisableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE)
fun testCancelMotionEvent_popsTouchSession() {
swipe(Direction.DOWN)
val event = MotionEvent.obtain(0, 0, MotionEvent.ACTION_CANCEL, 0f, 0f, 0)
@@ -154,6 +164,60 @@
verify(mTouchSession).pop()
}
+ @Test
+ @EnableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE)
+ fun testFullVerticalSwipe_initiatedWhenAvailable() {
+ // Indicate touches are available
+ mTouchHandler.onGlanceableTouchAvailable(true)
+
+ // Verify swipe is handled
+ val captured = swipe(Direction.DOWN)
+ Truth.assertThat(captured).isTrue()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE)
+ fun testFullVerticalSwipe_notInitiatedWhenNotAvailable() {
+ // Indicate touches aren't available
+ mTouchHandler.onGlanceableTouchAvailable(false)
+
+ // Verify swipe is not handled
+ val captured = swipe(Direction.DOWN)
+ Truth.assertThat(captured).isFalse()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE)
+ fun testFullVerticalSwipe_resetsTouchStateOnUp() {
+ // Indicate touches are available
+ mTouchHandler.onGlanceableTouchAvailable(true)
+
+ // Verify swipe is handled
+ swipe(Direction.DOWN)
+
+ val upEvent: MotionEvent = mock()
+ whenever(upEvent.action).thenReturn(MotionEvent.ACTION_UP)
+ mInputListenerCaptor.lastValue.onInputEvent(upEvent)
+
+ verify(communalViewModel).onResetTouchState()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE)
+ fun testFullVerticalSwipe_resetsTouchStateOnCancel() {
+ // Indicate touches are available
+ mTouchHandler.onGlanceableTouchAvailable(true)
+
+ // Verify swipe is handled
+ swipe(Direction.DOWN)
+
+ val upEvent: MotionEvent = mock()
+ whenever(upEvent.action).thenReturn(MotionEvent.ACTION_CANCEL)
+ mInputListenerCaptor.lastValue.onInputEvent(upEvent)
+
+ verify(communalViewModel).onResetTouchState()
+ }
+
/**
* Simulates a swipe in the given direction and returns true if the touch was intercepted by the
* touch handler's gesture listener.
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
index c28cf34..a09189e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
@@ -372,6 +372,7 @@
nonAuxiliarySubtypes: Int,
): InputMethodModel {
return InputMethodModel(
+ userId = UUID.randomUUID().mostSignificantBits.toInt(),
imeId = UUID.randomUUID().toString(),
subtypes =
List(auxiliarySubtypes + nonAuxiliarySubtypes) {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
index 86871ed..7a41bc6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
@@ -756,6 +756,17 @@
verify(metricsLogger).logTapWidget("test_pkg/test_cls", rank = 10)
}
+ @Test
+ fun glanceableTouchAvailable_availableWhenNestedScrollingWithoutConsumption() =
+ testScope.runTest {
+ val touchAvailable by collectLastValue(underTest.glanceableTouchAvailable)
+ assertThat(touchAvailable).isTrue()
+ underTest.onHubTouchConsumed()
+ assertThat(touchAvailable).isFalse()
+ underTest.onNestedScrolling()
+ assertThat(touchAvailable).isTrue()
+ }
+
private suspend fun setIsMainUser(isMainUser: Boolean) {
val user = if (isMainUser) MAIN_USER_INFO else SECONDARY_USER_INFO
with(userRepository) {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/inputmethod/data/repository/InputMethodRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/inputmethod/data/repository/InputMethodRepositoryTest.kt
index 857cdce..274880b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/inputmethod/data/repository/InputMethodRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/inputmethod/data/repository/InputMethodRepositoryTest.kt
@@ -56,9 +56,6 @@
fun setUp() {
MockitoAnnotations.initMocks(this)
- whenever(inputMethodManager.getEnabledInputMethodSubtypeList(eq(null), anyBoolean()))
- .thenReturn(listOf())
-
underTest =
InputMethodRepositoryImpl(
backgroundDispatcher = kosmos.testDispatcher,
@@ -71,10 +68,16 @@
testScope.runTest {
whenever(inputMethodManager.getEnabledInputMethodListAsUser(eq(USER_HANDLE)))
.thenReturn(listOf())
- whenever(inputMethodManager.getEnabledInputMethodSubtypeList(any(), anyBoolean()))
+ whenever(
+ inputMethodManager.getEnabledInputMethodSubtypeListAsUser(
+ any(),
+ anyBoolean(),
+ eq(USER_HANDLE)
+ )
+ )
.thenReturn(listOf())
- assertThat(underTest.enabledInputMethods(USER_ID, fetchSubtypes = true).count())
+ assertThat(underTest.enabledInputMethods(USER_HANDLE, fetchSubtypes = true).count())
.isEqualTo(0)
}
@@ -83,11 +86,20 @@
testScope.runTest {
val subtypeId = 123
val isAuxiliary = true
+ val selectedImiId = "imiId"
+ val selectedImi = mock<InputMethodInfo>()
+ whenever(selectedImi.id).thenReturn(selectedImiId)
+ whenever(inputMethodManager.getCurrentInputMethodInfoAsUser(eq(USER_HANDLE)))
+ .thenReturn(selectedImi)
whenever(inputMethodManager.getEnabledInputMethodListAsUser(eq(USER_HANDLE)))
- .thenReturn(listOf(mock<InputMethodInfo>()))
- whenever(inputMethodManager.getEnabledInputMethodSubtypeList(any(), anyBoolean()))
- .thenReturn(listOf())
- whenever(inputMethodManager.getEnabledInputMethodSubtypeList(eq(null), anyBoolean()))
+ .thenReturn(listOf(selectedImi))
+ whenever(
+ inputMethodManager.getEnabledInputMethodSubtypeListAsUser(
+ eq(selectedImiId),
+ anyBoolean(),
+ eq(USER_HANDLE)
+ )
+ )
.thenReturn(
listOf(
InputMethodSubtype.InputMethodSubtypeBuilder()
@@ -97,7 +109,7 @@
)
)
- val result = underTest.selectedInputMethodSubtypes()
+ val result = underTest.selectedInputMethodSubtypes(USER_HANDLE)
assertThat(result).hasSize(1)
assertThat(result.first().subtypeId).isEqualTo(subtypeId)
assertThat(result.first().isAuxiliary).isEqualTo(isAuxiliary)
@@ -108,7 +120,7 @@
testScope.runTest {
val displayId = 7
- underTest.showInputMethodPicker(displayId, /* showAuxiliarySubtypes = */ true)
+ underTest.showInputMethodPicker(displayId, /* showAuxiliarySubtypes= */ true)
verify(inputMethodManager)
.showInputMethodPickerFromSystem(
@@ -118,7 +130,6 @@
}
companion object {
- private const val USER_ID = 100
- private val USER_HANDLE = UserHandle.of(USER_ID)
+ private val USER_HANDLE = UserHandle.of(100)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/inputmethod/domain/interactor/InputMethodInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/inputmethod/domain/interactor/InputMethodInteractorTest.kt
index d23ff2a..8e6de2f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/inputmethod/domain/interactor/InputMethodInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/inputmethod/domain/interactor/InputMethodInteractorTest.kt
@@ -143,6 +143,7 @@
nonAuxiliarySubtypes: Int,
): InputMethodModel {
return InputMethodModel(
+ userId = UUID.randomUUID().mostSignificantBits.toInt(),
imeId = UUID.randomUUID().toString(),
subtypes =
List(auxiliarySubtypes + nonAuxiliarySubtypes) {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractorTest.kt
index 61ccd7f..7424320 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractorTest.kt
@@ -32,11 +32,13 @@
package com.android.systemui.keyguard.domain.interactor
+import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.systemui.Flags
+import com.android.systemui.Flags.FLAG_COMMUNAL_SCENE_KTF_REFACTOR
import com.android.systemui.SysuiTestCase
import com.android.systemui.communal.data.repository.fakeCommunalSceneRepository
import com.android.systemui.communal.shared.model.CommunalScenes
@@ -108,6 +110,7 @@
@Test
@EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
fun testShowWhenLockedActivity_noLongerOnTop_transitionsToGlanceableHub_ifIdleOnCommunal() =
testScope.runTest {
kosmos.fakeCommunalSceneRepository.setTransitionState(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinatorTest.kt
index 8810ade..7b87aeb 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinatorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinatorTest.kt
@@ -20,7 +20,6 @@
import android.app.Notification
import android.app.NotificationManager.IMPORTANCE_DEFAULT
import android.app.NotificationManager.IMPORTANCE_LOW
-import android.os.UserHandle
import android.platform.test.annotations.EnableFlags
import android.provider.Settings
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -475,20 +474,6 @@
val collectionListener: NotifCollectionListener =
argumentCaptor { verify(notifPipeline).addCollectionListener(capture()) }.lastValue
-
- var showOnlyUnseenNotifsOnKeyguardSetting: Boolean
- get() =
- fakeSettings.getIntForUser(
- Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
- UserHandle.USER_CURRENT,
- ) == 1
- set(value) {
- fakeSettings.putIntForUser(
- Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
- if (value) 1 else 2,
- UserHandle.USER_CURRENT,
- )
- }
}
companion object {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinatorTest.kt
index 7e9f437..3fd9c21 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinatorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinatorTest.kt
@@ -18,21 +18,23 @@
package com.android.systemui.statusbar.notification.collection.coordinator
import android.app.Notification
-import android.os.UserHandle
import android.platform.test.flag.junit.FlagsParameterization
import android.provider.Settings
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.dump.DumpManager
+import com.android.systemui.dump.dumpManager
import com.android.systemui.flags.andSceneContainer
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.keyguardRepository
+import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionStep
-import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
import com.android.systemui.log.logcatLogBuffer
-import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.plugins.statusbar.statusBarStateController
import com.android.systemui.scene.data.repository.Idle
import com.android.systemui.scene.data.repository.setTransition
import com.android.systemui.scene.domain.interactor.sceneInteractor
@@ -43,12 +45,15 @@
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
-import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor
+import com.android.systemui.statusbar.notification.domain.interactor.lockScreenShowOnlyUnseenNotificationsSetting
+import com.android.systemui.statusbar.notification.domain.interactor.seenNotificationsInteractor
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
-import com.android.systemui.statusbar.policy.HeadsUpManager
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener
+import com.android.systemui.statusbar.policy.headsUpManager
+import com.android.systemui.testKosmos
import com.android.systemui.util.settings.FakeSettings
+import com.android.systemui.util.settings.fakeSettings
import com.google.common.truth.Truth.assertThat
import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.CoroutineScope
@@ -73,13 +78,23 @@
@RunWith(ParameterizedAndroidJunit4::class)
class OriginalUnseenKeyguardCoordinatorTest(flags: FlagsParameterization) : SysuiTestCase() {
- private val kosmos = Kosmos()
+ private val kosmos =
+ testKosmos().apply {
+ testDispatcher = UnconfinedTestDispatcher()
+ statusBarStateController = mock()
+ fakeSettings.putInt(Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS, 1)
+ }
- private val headsUpManager: HeadsUpManager = mock()
- private val keyguardRepository = FakeKeyguardRepository()
- private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
+ private val keyguardRepository
+ get() = kosmos.fakeKeyguardRepository
+
+ private val keyguardTransitionRepository
+ get() = kosmos.fakeKeyguardTransitionRepository
+
+ private val statusBarStateController
+ get() = kosmos.statusBarStateController
+
private val notifPipeline: NotifPipeline = mock()
- private val statusBarStateController: StatusBarStateController = mock()
init {
mSetFlagsRule.setFlagsParameterization(flags)
@@ -253,7 +268,7 @@
collectionListener.onEntryAdded(fakeEntry)
// GIVEN: The setting for filtering unseen notifications is disabled
- showOnlyUnseenNotifsOnKeyguardSetting = false
+ kosmos.lockScreenShowOnlyUnseenNotificationsSetting = false
// GIVEN: The pipeline has registered the unseen filter for invalidation
val invalidationListener: Pluggable.PluggableListener<NotifFilter> = mock()
@@ -267,7 +282,7 @@
assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isFalse()
// WHEN: The secure setting is changed
- showOnlyUnseenNotifsOnKeyguardSetting = true
+ kosmos.lockScreenShowOnlyUnseenNotificationsSetting = true
// THEN: The pipeline is invalidated
verify(invalidationListener).onPluggableInvalidated(same(unseenFilter), any())
@@ -608,35 +623,25 @@
private fun runKeyguardCoordinatorTest(
testBlock: suspend KeyguardCoordinatorTestScope.() -> Unit
) {
- val testDispatcher = UnconfinedTestDispatcher()
- val testScope = TestScope(testDispatcher)
- val fakeSettings =
- FakeSettings().apply {
- putInt(Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS, 1)
- }
- val seenNotificationsInteractor =
- SeenNotificationsInteractor(ActiveNotificationListRepository())
val keyguardCoordinator =
OriginalUnseenKeyguardCoordinator(
- testDispatcher,
- mock<DumpManager>(),
- headsUpManager,
- keyguardRepository,
- kosmos.keyguardTransitionInteractor,
- KeyguardCoordinatorLogger(logcatLogBuffer()),
- testScope.backgroundScope,
- fakeSettings,
- seenNotificationsInteractor,
- statusBarStateController,
+ dumpManager = kosmos.dumpManager,
+ headsUpManager = kosmos.headsUpManager,
+ keyguardRepository = kosmos.keyguardRepository,
+ keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor,
+ logger = KeyguardCoordinatorLogger(logcatLogBuffer()),
+ scope = kosmos.testScope.backgroundScope,
+ seenNotificationsInteractor = kosmos.seenNotificationsInteractor,
+ statusBarStateController = kosmos.statusBarStateController,
sceneInteractor = kosmos.sceneInteractor,
)
keyguardCoordinator.attach(notifPipeline)
- testScope.runTest {
+ kosmos.testScope.runTest {
KeyguardCoordinatorTestScope(
keyguardCoordinator,
- testScope,
- seenNotificationsInteractor,
- fakeSettings,
+ kosmos.testScope,
+ kosmos.seenNotificationsInteractor,
+ kosmos.fakeSettings,
)
.testBlock()
}
@@ -658,21 +663,8 @@
argumentCaptor { verify(notifPipeline).addCollectionListener(capture()) }.lastValue
val onHeadsUpChangedListener: OnHeadsUpChangedListener
- get() = argumentCaptor { verify(headsUpManager).addListener(capture()) }.lastValue
-
- var showOnlyUnseenNotifsOnKeyguardSetting: Boolean
get() =
- fakeSettings.getIntForUser(
- Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
- UserHandle.USER_CURRENT,
- ) == 1
- set(value) {
- fakeSettings.putIntForUser(
- Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
- if (value) 1 else 2,
- UserHandle.USER_CURRENT,
- )
- }
+ argumentCaptor { verify(kosmos.headsUpManager).addListener(capture()) }.lastValue
}
companion object {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractorTest.kt
new file mode 100644
index 0000000..2159b86
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractorTest.kt
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ *
+ */
+
+package com.android.systemui.statusbar.notification.domain.interactor
+
+import android.platform.test.annotations.EnableFlags
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
+import com.android.systemui.statusbar.notification.shared.NotificationMinimalismPrototype
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class SeenNotificationsInteractorTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+ private val underTest
+ get() = kosmos.seenNotificationsInteractor
+
+ @Test
+ fun testNoFilteredOutSeenNotifications() = runTest {
+ val hasFilteredOutSeenNotifications by
+ collectLastValue(underTest.hasFilteredOutSeenNotifications)
+
+ underTest.setHasFilteredOutSeenNotifications(false)
+
+ assertThat(hasFilteredOutSeenNotifications).isFalse()
+ }
+
+ @Test
+ fun testHasFilteredOutSeenNotifications() = runTest {
+ val hasFilteredOutSeenNotifications by
+ collectLastValue(underTest.hasFilteredOutSeenNotifications)
+
+ underTest.setHasFilteredOutSeenNotifications(true)
+
+ assertThat(hasFilteredOutSeenNotifications).isTrue()
+ }
+
+ @Test
+ @EnableFlags(NotificationMinimalismPrototype.FLAG_NAME)
+ fun topOngoingAndUnseenNotification() = runTest {
+ val entry1 = NotificationEntryBuilder().setTag("entry1").build()
+ val entry2 = NotificationEntryBuilder().setTag("entry2").build()
+
+ underTest.setTopOngoingNotification(null)
+ underTest.setTopUnseenNotification(null)
+
+ assertThat(underTest.isTopOngoingNotification(entry1)).isFalse()
+ assertThat(underTest.isTopOngoingNotification(entry2)).isFalse()
+ assertThat(underTest.isTopUnseenNotification(entry1)).isFalse()
+ assertThat(underTest.isTopUnseenNotification(entry2)).isFalse()
+
+ underTest.setTopOngoingNotification(entry1)
+ underTest.setTopUnseenNotification(entry2)
+
+ assertThat(underTest.isTopOngoingNotification(entry1)).isTrue()
+ assertThat(underTest.isTopOngoingNotification(entry2)).isFalse()
+ assertThat(underTest.isTopUnseenNotification(entry1)).isFalse()
+ assertThat(underTest.isTopUnseenNotification(entry2)).isTrue()
+ }
+
+ fun testShowOnlyUnseenNotifsOnKeyguardSetting() = runTest {
+ val settingEnabled by
+ collectLastValue(underTest.isLockScreenShowOnlyUnseenNotificationsEnabled())
+
+ kosmos.lockScreenShowOnlyUnseenNotificationsSetting = false
+ testScheduler.runCurrent()
+ assertThat(settingEnabled).isFalse()
+
+ kosmos.lockScreenShowOnlyUnseenNotificationsSetting = true
+ testScheduler.runCurrent()
+ assertThat(settingEnabled).isTrue()
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt
index 8f9da3b..9a862fc 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt
@@ -60,6 +60,7 @@
// For creating TestableHeadsUpManager
@Mock private val mAccessibilityMgr: AccessibilityManagerWrapper? = null
private val mUiEventLoggerFake = UiEventLoggerFake()
+ @Mock private lateinit var mHeadsUpManagerLogger: HeadsUpManagerLogger
@Mock private lateinit var mBgHandler: Handler
@@ -82,7 +83,8 @@
// Initialize AvalancheController and TestableHeadsUpManager during setUp instead of
// declaration, where mocks are null
- mAvalancheController = AvalancheController(dumpManager, mUiEventLoggerFake, mBgHandler)
+ mAvalancheController = AvalancheController(dumpManager, mUiEventLoggerFake,
+ mHeadsUpManagerLogger, mBgHandler)
testableHeadsUpManager =
TestableHeadsUpManager(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java
index df07b44..9005ae3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java
@@ -81,6 +81,7 @@
static final int TEST_A11Y_AUTO_DISMISS_TIME = 1_000;
private UiEventLoggerFake mUiEventLoggerFake = new UiEventLoggerFake();
+
private final HeadsUpManagerLogger mLogger = spy(new HeadsUpManagerLogger(logcatLogBuffer()));
@Mock private Handler mBgHandler;
@Mock private DumpManager dumpManager;
@@ -149,7 +150,8 @@
@Override
public void SysuiSetup() throws Exception {
super.SysuiSetup();
- mAvalancheController = new AvalancheController(dumpManager, mUiEventLoggerFake, mBgHandler);
+ mAvalancheController = new AvalancheController(dumpManager, mUiEventLoggerFake, mLogger,
+ mBgHandler);
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.kt
index b91bde4..7a6838a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.kt
@@ -179,7 +179,8 @@
mContext
.getOrCreateTestableResources()
.addOverride(R.integer.ambient_notification_extension_time, 500)
- mAvalancheController = AvalancheController(dumpManager, mUiEventLogger, mBgHandler)
+ mAvalancheController = AvalancheController(dumpManager, mUiEventLogger,
+ mHeadsUpManagerLogger, mBgHandler)
}
@Test
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
index 0f11717..1342dd0 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
@@ -287,7 +287,7 @@
/**
* Helper used to receive device state info from {@link DeviceStateManager}.
*/
- static class DeviceStateHelper implements DeviceStateManager.DeviceStateCallback {
+ public static class DeviceStateHelper implements DeviceStateManager.DeviceStateCallback {
@Nullable
private final DisplayAddress.Physical mRearDisplayPhysicalAddress;
diff --git a/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.java b/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.java
deleted file mode 100644
index 636bc5b..0000000
--- a/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.java
+++ /dev/null
@@ -1,395 +0,0 @@
-/*
- * 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.ambient.touch;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ValueAnimator;
-import android.graphics.Rect;
-import android.graphics.Region;
-import android.util.Log;
-import android.view.GestureDetector;
-import android.view.InputEvent;
-import android.view.MotionEvent;
-import android.view.VelocityTracker;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.VisibleForTesting;
-
-import com.android.internal.logging.UiEvent;
-import com.android.internal.logging.UiEventLogger;
-import com.android.internal.widget.LockPatternUtils;
-import com.android.systemui.Flags;
-import com.android.systemui.ambient.touch.dagger.BouncerSwipeModule;
-import com.android.systemui.ambient.touch.scrim.ScrimController;
-import com.android.systemui.ambient.touch.scrim.ScrimManager;
-import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants;
-import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.settings.UserTracker;
-import com.android.systemui.shade.ShadeExpansionChangeEvent;
-import com.android.systemui.statusbar.NotificationShadeWindowController;
-import com.android.systemui.statusbar.phone.CentralSurfaces;
-import com.android.wm.shell.animation.FlingAnimationUtils;
-
-import java.util.Optional;
-
-import javax.inject.Inject;
-import javax.inject.Named;
-
-/**
- * Monitor for tracking touches on the DreamOverlay to bring up the bouncer.
- */
-public class BouncerSwipeTouchHandler implements TouchHandler {
- /**
- * An interface for creating ValueAnimators.
- */
- public interface ValueAnimatorCreator {
- /**
- * Creates {@link ValueAnimator}.
- */
- ValueAnimator create(float start, float finish);
- }
-
- /**
- * An interface for obtaining VelocityTrackers.
- */
- public interface VelocityTrackerFactory {
- /**
- * Obtains {@link VelocityTracker}.
- */
- VelocityTracker obtain();
- }
-
- public static final float FLING_PERCENTAGE_THRESHOLD = 0.5f;
-
- private static final String TAG = "BouncerSwipeTouchHandler";
- private final NotificationShadeWindowController mNotificationShadeWindowController;
- private final LockPatternUtils mLockPatternUtils;
- private final UserTracker mUserTracker;
- private final float mBouncerZoneScreenPercentage;
- private final float mMinBouncerZoneScreenPercentage;
-
- private final ScrimManager mScrimManager;
- private ScrimController mCurrentScrimController;
- private float mCurrentExpansion;
- private final Optional<CentralSurfaces> mCentralSurfaces;
-
- private VelocityTracker mVelocityTracker;
-
- private final FlingAnimationUtils mFlingAnimationUtils;
- private final FlingAnimationUtils mFlingAnimationUtilsClosing;
-
- private Boolean mCapture;
- private Boolean mExpanded;
-
- private TouchSession mTouchSession;
-
- private final ValueAnimatorCreator mValueAnimatorCreator;
-
- private final VelocityTrackerFactory mVelocityTrackerFactory;
-
- private final UiEventLogger mUiEventLogger;
-
- private final ActivityStarter mActivityStarter;
-
- private final ScrimManager.Callback mScrimManagerCallback = new ScrimManager.Callback() {
- @Override
- public void onScrimControllerChanged(ScrimController controller) {
- if (mCurrentScrimController != null) {
- mCurrentScrimController.reset();
- }
-
- mCurrentScrimController = controller;
- }
- };
-
- private final GestureDetector.OnGestureListener mOnGestureListener =
- new GestureDetector.SimpleOnGestureListener() {
- @Override
- public boolean onScroll(MotionEvent e1, @NonNull MotionEvent e2, float distanceX,
- float distanceY) {
- if (mCapture == null) {
- if (Flags.dreamOverlayBouncerSwipeDirectionFiltering()) {
- mCapture = Math.abs(distanceY) > Math.abs(distanceX)
- && distanceY > 0;
- } else {
- // If the user scrolling favors a vertical direction, begin capturing
- // scrolls.
- mCapture = Math.abs(distanceY) > Math.abs(distanceX);
- }
- if (mCapture) {
- // reset expanding
- mExpanded = false;
- // Since the user is dragging the bouncer up, set scrimmed to false.
- mCurrentScrimController.show();
- }
- }
-
- if (!mCapture) {
- return false;
- }
-
- // Don't set expansion for downward scroll.
- if (e1.getY() < e2.getY()) {
- return true;
- }
-
- if (!mCentralSurfaces.isPresent()) {
- return true;
- }
-
- // If scrolling up and keyguard is not locked, dismiss both keyguard and the
- // dream since there's no bouncer to show.
- if (e1.getY() > e2.getY()
- && !mLockPatternUtils.isSecure(mUserTracker.getUserId())) {
- mActivityStarter.executeRunnableDismissingKeyguard(
- () -> mCentralSurfaces.get().awakenDreams(),
- /* cancelAction= */ null,
- /* dismissShade= */ true,
- /* afterKeyguardGone= */ true,
- /* deferred= */ false);
- return true;
- }
-
- // For consistency, we adopt the expansion definition found in the
- // PanelViewController. In this case, expansion refers to the view above the
- // bouncer. As that view's expansion shrinks, the bouncer appears. The bouncer
- // is fully hidden at full expansion (1) and fully visible when fully collapsed
- // (0).
- final float screenTravelPercentage = Math.abs(e1.getY() - e2.getY())
- / mTouchSession.getBounds().height();
- setPanelExpansion(1 - screenTravelPercentage);
- return true;
- }
- };
-
- private void setPanelExpansion(float expansion) {
- mCurrentExpansion = expansion;
- ShadeExpansionChangeEvent event =
- new ShadeExpansionChangeEvent(
- /* fraction= */ mCurrentExpansion,
- /* expanded= */ mExpanded,
- /* tracking= */ true);
- mCurrentScrimController.expand(event);
- }
-
-
- @VisibleForTesting
- public enum DreamEvent implements UiEventLogger.UiEventEnum {
- @UiEvent(doc = "The screensaver has been swiped up.")
- DREAM_SWIPED(988),
-
- @UiEvent(doc = "The bouncer has become fully visible over dream.")
- DREAM_BOUNCER_FULLY_VISIBLE(1056);
-
- private final int mId;
-
- DreamEvent(int id) {
- mId = id;
- }
-
- @Override
- public int getId() {
- return mId;
- }
- }
-
- @Inject
- public BouncerSwipeTouchHandler(
- ScrimManager scrimManager,
- Optional<CentralSurfaces> centralSurfaces,
- NotificationShadeWindowController notificationShadeWindowController,
- ValueAnimatorCreator valueAnimatorCreator,
- VelocityTrackerFactory velocityTrackerFactory,
- LockPatternUtils lockPatternUtils,
- UserTracker userTracker,
- @Named(BouncerSwipeModule.SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_OPENING)
- FlingAnimationUtils flingAnimationUtils,
- @Named(BouncerSwipeModule.SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_CLOSING)
- FlingAnimationUtils flingAnimationUtilsClosing,
- @Named(BouncerSwipeModule.SWIPE_TO_BOUNCER_START_REGION) float swipeRegionPercentage,
- @Named(BouncerSwipeModule.MIN_BOUNCER_ZONE_SCREEN_PERCENTAGE) float minRegionPercentage,
- UiEventLogger uiEventLogger,
- ActivityStarter activityStarter) {
- mCentralSurfaces = centralSurfaces;
- mScrimManager = scrimManager;
- mNotificationShadeWindowController = notificationShadeWindowController;
- mLockPatternUtils = lockPatternUtils;
- mUserTracker = userTracker;
- mBouncerZoneScreenPercentage = swipeRegionPercentage;
- mMinBouncerZoneScreenPercentage = minRegionPercentage;
- mFlingAnimationUtils = flingAnimationUtils;
- mFlingAnimationUtilsClosing = flingAnimationUtilsClosing;
- mValueAnimatorCreator = valueAnimatorCreator;
- mVelocityTrackerFactory = velocityTrackerFactory;
- mUiEventLogger = uiEventLogger;
- mActivityStarter = activityStarter;
- }
-
- @Override
- public void getTouchInitiationRegion(Rect bounds, Region region, Rect exclusionRect) {
- final int width = bounds.width();
- final int height = bounds.height();
- final int minAllowableBottom = Math.round(height * (1 - mMinBouncerZoneScreenPercentage));
-
- final Rect normalRegion = new Rect(0,
- Math.round(height * (1 - mBouncerZoneScreenPercentage)),
- width, height);
-
- if (exclusionRect != null) {
- int lowestBottom = Math.min(Math.max(0, exclusionRect.bottom), minAllowableBottom);
- normalRegion.top = Math.max(normalRegion.top, lowestBottom);
- }
- region.union(normalRegion);
- }
-
-
- @Override
- public void onSessionStart(TouchSession session) {
- mVelocityTracker = mVelocityTrackerFactory.obtain();
- mTouchSession = session;
- mVelocityTracker.clear();
-
- if (!Flags.communalBouncerDoNotModifyPluginOpen()) {
- mNotificationShadeWindowController.setForcePluginOpen(true, this);
- }
-
- mScrimManager.addCallback(mScrimManagerCallback);
- mCurrentScrimController = mScrimManager.getCurrentController();
-
- session.registerCallback(() -> {
- if (mVelocityTracker != null) {
- mVelocityTracker.recycle();
- mVelocityTracker = null;
- }
- mScrimManager.removeCallback(mScrimManagerCallback);
- mCapture = null;
- mTouchSession = null;
-
- if (!Flags.communalBouncerDoNotModifyPluginOpen()) {
- mNotificationShadeWindowController.setForcePluginOpen(false, this);
- }
- });
-
- session.registerGestureListener(mOnGestureListener);
- session.registerInputListener(ev -> onMotionEvent(ev));
-
- }
-
- private void onMotionEvent(InputEvent event) {
- if (!(event instanceof MotionEvent)) {
- Log.e(TAG, "non MotionEvent received:" + event);
- return;
- }
-
- final MotionEvent motionEvent = (MotionEvent) event;
-
- switch (motionEvent.getAction()) {
- case MotionEvent.ACTION_CANCEL:
- case MotionEvent.ACTION_UP:
- mTouchSession.pop();
- // If we are not capturing any input, there is no need to consider animating to
- // finish transition.
- if (mCapture == null || !mCapture) {
- break;
- }
-
- // We must capture the resulting velocities as resetMonitor() will clear these
- // values.
- mVelocityTracker.computeCurrentVelocity(1000);
- final float verticalVelocity = mVelocityTracker.getYVelocity();
- final float horizontalVelocity = mVelocityTracker.getXVelocity();
-
- final float velocityVector =
- (float) Math.hypot(horizontalVelocity, verticalVelocity);
-
- mExpanded = !flingRevealsOverlay(verticalVelocity, velocityVector);
- final float expansion = mExpanded
- ? KeyguardBouncerConstants.EXPANSION_VISIBLE
- : KeyguardBouncerConstants.EXPANSION_HIDDEN;
-
- // Log the swiping up to show Bouncer event.
- if (expansion == KeyguardBouncerConstants.EXPANSION_VISIBLE) {
- mUiEventLogger.log(DreamEvent.DREAM_SWIPED);
- }
-
- flingToExpansion(verticalVelocity, expansion);
- break;
- default:
- mVelocityTracker.addMovement(motionEvent);
- break;
- }
- }
-
- private ValueAnimator createExpansionAnimator(float targetExpansion) {
- final ValueAnimator animator =
- mValueAnimatorCreator.create(mCurrentExpansion, targetExpansion);
- animator.addUpdateListener(
- animation -> {
- float expansionFraction = (float) animation.getAnimatedValue();
- setPanelExpansion(expansionFraction);
- });
- if (targetExpansion == KeyguardBouncerConstants.EXPANSION_VISIBLE) {
- animator.addListener(
- new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- mUiEventLogger.log(DreamEvent.DREAM_BOUNCER_FULLY_VISIBLE);
- }
- });
- }
- return animator;
- }
-
- protected boolean flingRevealsOverlay(float velocity, float velocityVector) {
- // Fully expand the space above the bouncer, if the user has expanded the bouncer less
- // than halfway or final velocity was positive, indicating a downward direction.
- if (Math.abs(velocityVector) < mFlingAnimationUtils.getMinVelocityPxPerSecond()) {
- return mCurrentExpansion > FLING_PERCENTAGE_THRESHOLD;
- } else {
- return velocity > 0;
- }
- }
-
- protected void flingToExpansion(float velocity, float expansion) {
- if (!mCentralSurfaces.isPresent()) {
- return;
- }
-
- // Don't set expansion if the user doesn't have a pin/password set.
- if (!mLockPatternUtils.isSecure(mUserTracker.getUserId())) {
- return;
- }
-
- // The animation utils deal in pixel units, rather than expansion height.
- final float viewHeight = mTouchSession.getBounds().height();
- final float currentHeight = viewHeight * mCurrentExpansion;
- final float targetHeight = viewHeight * expansion;
- final ValueAnimator animator = createExpansionAnimator(expansion);
- if (expansion == KeyguardBouncerConstants.EXPANSION_HIDDEN) {
- // Hides the bouncer, i.e., fully expands the space above the bouncer.
- mFlingAnimationUtilsClosing.apply(animator, currentHeight, targetHeight, velocity,
- viewHeight);
- } else {
- // Shows the bouncer, i.e., fully collapses the space above the bouncer.
- mFlingAnimationUtils.apply(
- animator, currentHeight, targetHeight, velocity, viewHeight);
- }
-
- animator.start();
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.kt b/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.kt
new file mode 100644
index 0000000..d5790a4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.kt
@@ -0,0 +1,371 @@
+/*
+ * 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.ambient.touch
+
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.ValueAnimator
+import android.graphics.Rect
+import android.graphics.Region
+import android.util.Log
+import android.view.GestureDetector
+import android.view.GestureDetector.SimpleOnGestureListener
+import android.view.InputEvent
+import android.view.MotionEvent
+import android.view.VelocityTracker
+import androidx.annotation.VisibleForTesting
+import com.android.internal.logging.UiEvent
+import com.android.internal.logging.UiEventLogger
+import com.android.internal.widget.LockPatternUtils
+import com.android.systemui.Flags
+import com.android.systemui.ambient.touch.TouchHandler.TouchSession
+import com.android.systemui.ambient.touch.dagger.BouncerSwipeModule
+import com.android.systemui.ambient.touch.scrim.ScrimController
+import com.android.systemui.ambient.touch.scrim.ScrimManager
+import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants
+import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.shade.ShadeExpansionChangeEvent
+import com.android.systemui.statusbar.NotificationShadeWindowController
+import com.android.systemui.statusbar.phone.CentralSurfaces
+import com.android.wm.shell.animation.FlingAnimationUtils
+import java.util.Optional
+import javax.inject.Inject
+import javax.inject.Named
+import kotlin.math.abs
+import kotlin.math.hypot
+import kotlin.math.max
+import kotlin.math.min
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+
+/** Monitor for tracking touches on the DreamOverlay to bring up the bouncer. */
+class BouncerSwipeTouchHandler
+@Inject
+constructor(
+ scope: CoroutineScope,
+ private val scrimManager: ScrimManager,
+ private val centralSurfaces: Optional<CentralSurfaces>,
+ private val notificationShadeWindowController: NotificationShadeWindowController,
+ private val valueAnimatorCreator: ValueAnimatorCreator,
+ private val velocityTrackerFactory: VelocityTrackerFactory,
+ private val lockPatternUtils: LockPatternUtils,
+ private val userTracker: UserTracker,
+ private val communalViewModel: CommunalViewModel,
+ @param:Named(BouncerSwipeModule.SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_OPENING)
+ private val flingAnimationUtils: FlingAnimationUtils,
+ @param:Named(BouncerSwipeModule.SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_CLOSING)
+ private val flingAnimationUtilsClosing: FlingAnimationUtils,
+ @param:Named(BouncerSwipeModule.SWIPE_TO_BOUNCER_START_REGION)
+ private val bouncerZoneScreenPercentage: Float,
+ @param:Named(BouncerSwipeModule.MIN_BOUNCER_ZONE_SCREEN_PERCENTAGE)
+ private val minBouncerZoneScreenPercentage: Float,
+ private val uiEventLogger: UiEventLogger,
+ private val activityStarter: ActivityStarter
+) : TouchHandler {
+ /** An interface for creating ValueAnimators. */
+ interface ValueAnimatorCreator {
+ /** Creates [ValueAnimator]. */
+ fun create(start: Float, finish: Float): ValueAnimator
+ }
+
+ /** An interface for obtaining VelocityTrackers. */
+ interface VelocityTrackerFactory {
+ /** Obtains [VelocityTracker]. */
+ fun obtain(): VelocityTracker?
+ }
+
+ private var currentScrimController: ScrimController? = null
+ private var currentExpansion = 0f
+ private var velocityTracker: VelocityTracker? = null
+ private var capture: Boolean? = null
+ private var expanded: Boolean = false
+ private var touchSession: TouchSession? = null
+ private val scrimManagerCallback =
+ ScrimManager.Callback { controller ->
+ currentScrimController?.reset()
+
+ currentScrimController = controller
+ }
+
+ /** Determines whether the touch handler should process touches in fullscreen swiping mode */
+ private var touchAvailable = false
+
+ private val onGestureListener: GestureDetector.OnGestureListener =
+ object : SimpleOnGestureListener() {
+ override fun onScroll(
+ e1: MotionEvent?,
+ e2: MotionEvent,
+ distanceX: Float,
+ distanceY: Float
+ ): Boolean {
+ if (capture == null) {
+ capture =
+ if (Flags.dreamOverlayBouncerSwipeDirectionFiltering()) {
+ (abs(distanceY.toDouble()) > abs(distanceX.toDouble()) &&
+ distanceY > 0) &&
+ if (Flags.hubmodeFullscreenVerticalSwipe()) touchAvailable else true
+ } else {
+ // If the user scrolling favors a vertical direction, begin capturing
+ // scrolls.
+ abs(distanceY.toDouble()) > abs(distanceX.toDouble())
+ }
+ if (capture == true) {
+ // reset expanding
+ expanded = false
+ // Since the user is dragging the bouncer up, set scrimmed to false.
+ currentScrimController?.show()
+ }
+ }
+ if (capture != true) {
+ return false
+ }
+
+ if (!centralSurfaces.isPresent) {
+ return true
+ }
+
+ e1?.apply outer@{
+ // Don't set expansion for downward scroll.
+ if (y < e2.y) {
+ return true
+ }
+
+ // If scrolling up and keyguard is not locked, dismiss both keyguard and the
+ // dream since there's no bouncer to show.
+ if (y > e2.y && !lockPatternUtils.isSecure(userTracker.userId)) {
+ activityStarter.executeRunnableDismissingKeyguard(
+ { centralSurfaces.get().awakenDreams() },
+ /* cancelAction= */ null,
+ /* dismissShade= */ true,
+ /* afterKeyguardGone= */ true,
+ /* deferred= */ false
+ )
+ return true
+ }
+
+ // For consistency, we adopt the expansion definition found in the
+ // PanelViewController. In this case, expansion refers to the view above the
+ // bouncer. As that view's expansion shrinks, the bouncer appears. The bouncer
+ // is fully hidden at full expansion (1) and fully visible when fully collapsed
+ // (0).
+ touchSession?.apply {
+ val screenTravelPercentage =
+ (abs((this@outer.y - e2.y).toDouble()) / getBounds().height()).toFloat()
+ setPanelExpansion(1 - screenTravelPercentage)
+ }
+ }
+
+ return true
+ }
+ }
+
+ init {
+ if (Flags.hubmodeFullscreenVerticalSwipe()) {
+ scope.launch {
+ communalViewModel.glanceableTouchAvailable.collect {
+ onGlanceableTouchAvailable(it)
+ }
+ }
+ }
+ }
+
+ @VisibleForTesting
+ fun onGlanceableTouchAvailable(available: Boolean) {
+ touchAvailable = available
+ }
+
+ private fun setPanelExpansion(expansion: Float) {
+ currentExpansion = expansion
+ val event =
+ ShadeExpansionChangeEvent(
+ /* fraction= */ currentExpansion,
+ /* expanded= */ expanded,
+ /* tracking= */ true
+ )
+ currentScrimController?.expand(event)
+ }
+
+ @VisibleForTesting
+ enum class DreamEvent(private val mId: Int) : UiEventLogger.UiEventEnum {
+ @UiEvent(doc = "The screensaver has been swiped up.") DREAM_SWIPED(988),
+ @UiEvent(doc = "The bouncer has become fully visible over dream.")
+ DREAM_BOUNCER_FULLY_VISIBLE(1056);
+
+ override fun getId(): Int {
+ return mId
+ }
+ }
+
+ override fun getTouchInitiationRegion(bounds: Rect, region: Region, exclusionRect: Rect?) {
+ val width = bounds.width()
+ val height = bounds.height()
+ val minAllowableBottom = Math.round(height * (1 - minBouncerZoneScreenPercentage))
+ val normalRegion =
+ Rect(0, Math.round(height * (1 - bouncerZoneScreenPercentage)), width, height)
+
+ if (Flags.hubmodeFullscreenVerticalSwipe()) {
+ region.op(bounds, Region.Op.UNION)
+ exclusionRect?.apply { region.op(this, Region.Op.DIFFERENCE) }
+ }
+
+ if (exclusionRect != null) {
+ val lowestBottom =
+ min(max(0.0, exclusionRect.bottom.toDouble()), minAllowableBottom.toDouble())
+ .toInt()
+ normalRegion.top = max(normalRegion.top.toDouble(), lowestBottom.toDouble()).toInt()
+ }
+ region.union(normalRegion)
+ }
+
+ override fun onSessionStart(session: TouchSession) {
+ velocityTracker = velocityTrackerFactory.obtain()
+ touchSession = session
+ velocityTracker?.apply { clear() }
+ if (!Flags.communalBouncerDoNotModifyPluginOpen()) {
+ notificationShadeWindowController.setForcePluginOpen(true, this)
+ }
+ scrimManager.addCallback(scrimManagerCallback)
+ currentScrimController = scrimManager.currentController
+ session.registerCallback {
+ velocityTracker?.apply { recycle() }
+ velocityTracker = null
+
+ scrimManager.removeCallback(scrimManagerCallback)
+ capture = null
+ touchSession = null
+ if (!Flags.communalBouncerDoNotModifyPluginOpen()) {
+ notificationShadeWindowController.setForcePluginOpen(false, this)
+ }
+ }
+ session.registerGestureListener(onGestureListener)
+ session.registerInputListener { ev: InputEvent -> onMotionEvent(ev) }
+ }
+
+ private fun onMotionEvent(event: InputEvent) {
+ if (event !is MotionEvent) {
+ Log.e(TAG, "non MotionEvent received:$event")
+ return
+ }
+ val motionEvent = event
+ when (motionEvent.action) {
+ MotionEvent.ACTION_CANCEL,
+ MotionEvent.ACTION_UP -> {
+ if (Flags.hubmodeFullscreenVerticalSwipe() && capture == true) {
+ communalViewModel.onResetTouchState()
+ }
+ touchSession?.apply { pop() }
+ // If we are not capturing any input, there is no need to consider animating to
+ // finish transition.
+ if (capture == null || !capture!!) {
+ return
+ }
+
+ // We must capture the resulting velocities as resetMonitor() will clear these
+ // values.
+ velocityTracker!!.computeCurrentVelocity(1000)
+ val verticalVelocity = velocityTracker!!.yVelocity
+ val horizontalVelocity = velocityTracker!!.xVelocity
+ val velocityVector =
+ hypot(horizontalVelocity.toDouble(), verticalVelocity.toDouble()).toFloat()
+ expanded = !flingRevealsOverlay(verticalVelocity, velocityVector)
+ val expansion =
+ if (expanded!!) KeyguardBouncerConstants.EXPANSION_VISIBLE
+ else KeyguardBouncerConstants.EXPANSION_HIDDEN
+
+ // Log the swiping up to show Bouncer event.
+ if (expansion == KeyguardBouncerConstants.EXPANSION_VISIBLE) {
+ uiEventLogger.log(DreamEvent.DREAM_SWIPED)
+ }
+ flingToExpansion(verticalVelocity, expansion)
+ }
+ else -> velocityTracker!!.addMovement(motionEvent)
+ }
+ }
+
+ private fun createExpansionAnimator(targetExpansion: Float): ValueAnimator {
+ val animator = valueAnimatorCreator.create(currentExpansion, targetExpansion)
+ animator.addUpdateListener { animation: ValueAnimator ->
+ val expansionFraction = animation.animatedValue as Float
+ setPanelExpansion(expansionFraction)
+ }
+ if (targetExpansion == KeyguardBouncerConstants.EXPANSION_VISIBLE) {
+ animator.addListener(
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ uiEventLogger.log(DreamEvent.DREAM_BOUNCER_FULLY_VISIBLE)
+ }
+ }
+ )
+ }
+ return animator
+ }
+
+ protected fun flingRevealsOverlay(velocity: Float, velocityVector: Float): Boolean {
+ // Fully expand the space above the bouncer, if the user has expanded the bouncer less
+ // than halfway or final velocity was positive, indicating a downward direction.
+ return if (abs(velocityVector.toDouble()) < flingAnimationUtils.minVelocityPxPerSecond) {
+ currentExpansion > FLING_PERCENTAGE_THRESHOLD
+ } else {
+ velocity > 0
+ }
+ }
+
+ protected fun flingToExpansion(velocity: Float, expansion: Float) {
+ if (!centralSurfaces.isPresent) {
+ return
+ }
+
+ // Don't set expansion if the user doesn't have a pin/password set.
+ if (!lockPatternUtils.isSecure(userTracker.userId)) {
+ return
+ }
+
+ touchSession?.apply {
+ // The animation utils deal in pixel units, rather than expansion height.
+ val viewHeight = getBounds().height().toFloat()
+ val currentHeight = viewHeight * currentExpansion
+ val targetHeight = viewHeight * expansion
+ val animator = createExpansionAnimator(expansion)
+ if (expansion == KeyguardBouncerConstants.EXPANSION_HIDDEN) {
+ // Hides the bouncer, i.e., fully expands the space above the bouncer.
+ flingAnimationUtilsClosing.apply(
+ animator,
+ currentHeight,
+ targetHeight,
+ velocity,
+ viewHeight
+ )
+ } else {
+ // Shows the bouncer, i.e., fully collapses the space above the bouncer.
+ flingAnimationUtils.apply(
+ animator,
+ currentHeight,
+ targetHeight,
+ velocity,
+ viewHeight
+ )
+ }
+ animator.start()
+ }
+ }
+
+ companion object {
+ const val FLING_PERCENTAGE_THRESHOLD = 0.5f
+ private const val TAG = "BouncerSwipeTouchHandler"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/ambient/touch/ShadeTouchHandler.java b/packages/SystemUI/src/com/android/systemui/ambient/touch/ShadeTouchHandler.java
deleted file mode 100644
index baca959..0000000
--- a/packages/SystemUI/src/com/android/systemui/ambient/touch/ShadeTouchHandler.java
+++ /dev/null
@@ -1,132 +0,0 @@
-/*
- * 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.ambient.touch;
-
-import static com.android.systemui.ambient.touch.dagger.ShadeModule.NOTIFICATION_SHADE_GESTURE_INITIATION_HEIGHT;
-
-import android.app.DreamManager;
-import android.graphics.Rect;
-import android.graphics.Region;
-import android.view.GestureDetector;
-import android.view.MotionEvent;
-
-import androidx.annotation.NonNull;
-
-import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor;
-import com.android.systemui.shade.ShadeViewController;
-import com.android.systemui.statusbar.phone.CentralSurfaces;
-
-import java.util.Optional;
-
-import javax.inject.Inject;
-import javax.inject.Named;
-
-/**
- * {@link ShadeTouchHandler} is responsible for handling swipe down gestures over dream
- * to bring down the shade.
- */
-public class ShadeTouchHandler implements TouchHandler {
- private final Optional<CentralSurfaces> mSurfaces;
- private final ShadeViewController mShadeViewController;
- private final DreamManager mDreamManager;
- private final int mInitiationHeight;
- private final CommunalSettingsInteractor
- mCommunalSettingsInteractor;
-
- /**
- * Tracks whether or not we are capturing a given touch. Will be null before and after a touch.
- */
- private Boolean mCapture;
-
- @Inject
- ShadeTouchHandler(Optional<CentralSurfaces> centralSurfaces,
- ShadeViewController shadeViewController,
- DreamManager dreamManager,
- CommunalSettingsInteractor communalSettingsInteractor,
- @Named(NOTIFICATION_SHADE_GESTURE_INITIATION_HEIGHT) int initiationHeight) {
- mSurfaces = centralSurfaces;
- mShadeViewController = shadeViewController;
- mDreamManager = dreamManager;
- mCommunalSettingsInteractor = communalSettingsInteractor;
- mInitiationHeight = initiationHeight;
- }
-
- @Override
- public void onSessionStart(TouchSession session) {
- if (mSurfaces.isEmpty()) {
- session.pop();
- return;
- }
-
- session.registerCallback(() -> mCapture = null);
-
- session.registerInputListener(ev -> {
- if (ev instanceof MotionEvent) {
- if (mCapture != null && mCapture) {
- sendTouchEvent((MotionEvent) ev);
- }
- if (((MotionEvent) ev).getAction() == MotionEvent.ACTION_UP
- || ((MotionEvent) ev).getAction() == MotionEvent.ACTION_CANCEL) {
- session.pop();
- }
- }
- });
-
- session.registerGestureListener(new GestureDetector.SimpleOnGestureListener() {
- @Override
- public boolean onScroll(MotionEvent e1, @NonNull MotionEvent e2, float distanceX,
- float distanceY) {
- if (mCapture == null) {
- // Only capture swipes that are going downwards.
- mCapture = Math.abs(distanceY) > Math.abs(distanceX) && distanceY < 0;
- if (mCapture) {
- // Send the initial touches over, as the input listener has already
- // processed these touches.
- sendTouchEvent(e1);
- sendTouchEvent(e2);
- }
- }
- return mCapture;
- }
-
- @Override
- public boolean onFling(MotionEvent e1, @NonNull MotionEvent e2, float velocityX,
- float velocityY) {
- return mCapture;
- }
- });
- }
-
- private void sendTouchEvent(MotionEvent event) {
- if (mCommunalSettingsInteractor.isCommunalFlagEnabled() && !mDreamManager.isDreaming()) {
- // Send touches to central surfaces only when on the glanceable hub while not dreaming.
- // While sending touches where while dreaming will open the shade, the shade
- // while closing if opened then closed in the same gesture.
- mSurfaces.get().handleExternalShadeWindowTouch(event);
- } else {
- // Send touches to the shade view when dreaming.
- mShadeViewController.handleExternalTouch(event);
- }
- }
-
- @Override
- public void getTouchInitiationRegion(Rect bounds, Region region, Rect exclusionRect) {
- final Rect outBounds = new Rect(bounds);
- outBounds.inset(0, 0, 0, outBounds.height() - mInitiationHeight);
- region.op(outBounds, Region.Op.UNION);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/ambient/touch/ShadeTouchHandler.kt b/packages/SystemUI/src/com/android/systemui/ambient/touch/ShadeTouchHandler.kt
new file mode 100644
index 0000000..06b41de
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/ShadeTouchHandler.kt
@@ -0,0 +1,157 @@
+/*
+ * 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.ambient.touch
+
+import android.app.DreamManager
+import android.graphics.Rect
+import android.graphics.Region
+import android.view.GestureDetector.SimpleOnGestureListener
+import android.view.InputEvent
+import android.view.MotionEvent
+import androidx.annotation.VisibleForTesting
+import com.android.systemui.Flags
+import com.android.systemui.ambient.touch.TouchHandler.TouchSession
+import com.android.systemui.ambient.touch.dagger.ShadeModule
+import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
+import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
+import com.android.systemui.shade.ShadeViewController
+import com.android.systemui.statusbar.phone.CentralSurfaces
+import java.util.Optional
+import javax.inject.Inject
+import javax.inject.Named
+import kotlin.math.abs
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+
+/**
+ * [ShadeTouchHandler] is responsible for handling swipe down gestures over dream to bring down the
+ * shade.
+ */
+class ShadeTouchHandler
+@Inject
+constructor(
+ scope: CoroutineScope,
+ private val surfaces: Optional<CentralSurfaces>,
+ private val shadeViewController: ShadeViewController,
+ private val dreamManager: DreamManager,
+ private val communalViewModel: CommunalViewModel,
+ private val communalSettingsInteractor: CommunalSettingsInteractor,
+ @param:Named(ShadeModule.NOTIFICATION_SHADE_GESTURE_INITIATION_HEIGHT)
+ private val initiationHeight: Int
+) : TouchHandler {
+ /**
+ * Tracks whether or not we are capturing a given touch. Will be null before and after a touch.
+ */
+ private var capture: Boolean? = null
+
+ /** Determines whether the touch handler should process touches in fullscreen swiping mode */
+ private var touchAvailable = false
+
+ init {
+ if (Flags.hubmodeFullscreenVerticalSwipe()) {
+ scope.launch {
+ communalViewModel.glanceableTouchAvailable.collect {
+ onGlanceableTouchAvailable(it)
+ }
+ }
+ }
+ }
+
+ @VisibleForTesting
+ fun onGlanceableTouchAvailable(available: Boolean) {
+ touchAvailable = available
+ }
+
+ override fun onSessionStart(session: TouchSession) {
+ if (surfaces.isEmpty) {
+ session.pop()
+ return
+ }
+ session.registerCallback { capture = null }
+ session.registerInputListener { ev: InputEvent? ->
+ if (ev is MotionEvent) {
+ if (capture == true) {
+ sendTouchEvent(ev)
+ }
+ if (ev.action == MotionEvent.ACTION_UP || ev.action == MotionEvent.ACTION_CANCEL) {
+ if (capture == true) {
+ communalViewModel.onResetTouchState()
+ }
+ session.pop()
+ }
+ }
+ }
+ session.registerGestureListener(
+ object : SimpleOnGestureListener() {
+ override fun onScroll(
+ e1: MotionEvent?,
+ e2: MotionEvent,
+ distanceX: Float,
+ distanceY: Float
+ ): Boolean {
+ if (capture == null) {
+ // Only capture swipes that are going downwards.
+ capture =
+ abs(distanceY.toDouble()) > abs(distanceX.toDouble()) &&
+ distanceY < 0 &&
+ if (Flags.hubmodeFullscreenVerticalSwipe()) touchAvailable else true
+ if (capture == true) {
+ // Send the initial touches over, as the input listener has already
+ // processed these touches.
+ e1?.apply { sendTouchEvent(this) }
+ sendTouchEvent(e2)
+ }
+ }
+ return capture == true
+ }
+
+ override fun onFling(
+ e1: MotionEvent?,
+ e2: MotionEvent,
+ velocityX: Float,
+ velocityY: Float
+ ): Boolean {
+ return capture == true
+ }
+ }
+ )
+ }
+
+ private fun sendTouchEvent(event: MotionEvent) {
+ if (communalSettingsInteractor.isCommunalFlagEnabled() && !dreamManager.isDreaming) {
+ // Send touches to central surfaces only when on the glanceable hub while not dreaming.
+ // While sending touches where while dreaming will open the shade, the shade
+ // while closing if opened then closed in the same gesture.
+ surfaces.get().handleExternalShadeWindowTouch(event)
+ } else {
+ // Send touches to the shade view when dreaming.
+ shadeViewController.handleExternalTouch(event)
+ }
+ }
+
+ override fun getTouchInitiationRegion(bounds: Rect, region: Region, exclusionRect: Rect?) {
+ // If fullscreen swipe, use entire space minus exclusion region
+ if (Flags.hubmodeFullscreenVerticalSwipe()) {
+ region.op(bounds, Region.Op.UNION)
+
+ exclusionRect?.apply { region.op(this, Region.Op.DIFFERENCE) }
+ }
+
+ val outBounds = Rect(bounds)
+ outBounds.inset(0, 0, 0, outBounds.height() - initiationHeight)
+ region.op(outBounds, Region.Op.UNION)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/ambient/touch/dagger/AmbientTouchModule.kt b/packages/SystemUI/src/com/android/systemui/ambient/touch/dagger/AmbientTouchModule.kt
index a4924d1..ae21e56 100644
--- a/packages/SystemUI/src/com/android/systemui/ambient/touch/dagger/AmbientTouchModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/dagger/AmbientTouchModule.kt
@@ -17,12 +17,14 @@
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.coroutineScope
import com.android.systemui.ambient.dagger.AmbientModule
import com.android.systemui.ambient.touch.TouchHandler
import dagger.Module
import dagger.Provides
import dagger.multibindings.ElementsIntoSet
import javax.inject.Named
+import kotlinx.coroutines.CoroutineScope
@Module
interface AmbientTouchModule {
@@ -33,6 +35,12 @@
return lifecycleOwner.lifecycle
}
+ @JvmStatic
+ @Provides
+ fun providesLifecycleScope(lifecycle: Lifecycle): CoroutineScope {
+ return lifecycle.coroutineScope
+ }
+
@Provides
@ElementsIntoSet
fun providesDreamTouchHandlers(
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
index b8601ec..4be93cc 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
@@ -29,9 +29,12 @@
import com.android.systemui.communal.widgets.WidgetConfigurator
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.media.controls.ui.view.MediaHost
+import com.android.systemui.util.kotlin.BooleanFlowOperators.anyOf
+import com.android.systemui.util.kotlin.BooleanFlowOperators.not
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.flowOf
/** The base view model for the communal hub. */
@@ -57,6 +60,26 @@
val selectedKey: StateFlow<String?>
get() = _selectedKey
+ private val _isTouchConsumed: MutableStateFlow<Boolean> = MutableStateFlow(false)
+
+ /** Whether an element inside the lazy grid is actively consuming touches */
+ val isTouchConsumed: Flow<Boolean> = _isTouchConsumed.asStateFlow()
+
+ private val _isNestedScrolling: MutableStateFlow<Boolean> = MutableStateFlow(false)
+
+ /** Whether the lazy grid is reporting scrolling within itself */
+ val isNestedScrolling: Flow<Boolean> = _isNestedScrolling.asStateFlow()
+
+ /**
+ * Whether touch is available to be consumed by a touch handler. Touch is available during
+ * nested scrolling as lazy grid reports this for all scroll directions that it detects. In the
+ * case that there is consumed scrolling on a nested element, such as an AndroidView, no nested
+ * scrolling will be reported. It is up to the flow consumer to determine whether the nested
+ * scroll can be applied. In the communal case, this would be identifying the scroll as
+ * vertical, which the lazy horizontal grid does not handle.
+ */
+ val glanceableTouchAvailable: Flow<Boolean> = anyOf(not(isTouchConsumed), isNestedScrolling)
+
/** Accessibility delegate to be set on CommunalAppWidgetHostView. */
open val widgetAccessibilityDelegate: View.AccessibilityDelegate? = null
@@ -200,4 +223,28 @@
fun setSelectedKey(key: String?) {
_selectedKey.value = key
}
+
+ /** Invoked once touches inside the lazy grid are consumed */
+ fun onHubTouchConsumed() {
+ if (_isTouchConsumed.value) {
+ return
+ }
+
+ _isTouchConsumed.value = true
+ }
+
+ /** Invoked when nested scrolling begins on the lazy grid */
+ fun onNestedScrolling() {
+ if (_isNestedScrolling.value) {
+ return
+ }
+
+ _isNestedScrolling.value = true
+ }
+
+ /** Resets nested scroll and touch consumption state */
+ fun onResetTouchState() {
+ _isTouchConsumed.value = false
+ _isNestedScrolling.value = false
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
index 3985769..03ef17b 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
@@ -128,7 +128,7 @@
Box(
modifier =
Modifier.fillMaxSize()
- .background(LocalAndroidColorScheme.current.onSecondaryFixed),
+ .background(LocalAndroidColorScheme.current.surfaceDim),
) {
CommunalHub(
viewModel = communalViewModel,
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index d0beb7a..8990505 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -292,15 +292,6 @@
val WM_ENABLE_SHELL_TRANSITIONS =
sysPropBooleanFlag("persist.wm.debug.shell_transit", default = true)
- // TODO(b/254513207): Tracking Bug
- @Keep
- @JvmField
- val WM_ENABLE_PARTIAL_SCREEN_SHARING =
- releasedFlag(
- name = "enable_record_task_content",
- namespace = DeviceConfig.NAMESPACE_WINDOW_MANAGER,
- )
-
// TODO(b/256873975): Tracking Bug
@JvmField
@Keep
diff --git a/packages/SystemUI/src/com/android/systemui/inputmethod/data/model/InputMethodModel.kt b/packages/SystemUI/src/com/android/systemui/inputmethod/data/model/InputMethodModel.kt
index bdc18b3..0e19d87 100644
--- a/packages/SystemUI/src/com/android/systemui/inputmethod/data/model/InputMethodModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputmethod/data/model/InputMethodModel.kt
@@ -22,6 +22,8 @@
* @see android.view.inputmethod.InputMethodInfo
*/
data class InputMethodModel(
+ /** A unique ID for the user associated with this input method. */
+ val userId: Int,
/** A unique ID for this input method. */
val imeId: String,
/** The subtypes of this IME (may be empty). */
diff --git a/packages/SystemUI/src/com/android/systemui/inputmethod/data/repository/InputMethodRepository.kt b/packages/SystemUI/src/com/android/systemui/inputmethod/data/repository/InputMethodRepository.kt
index 5f316c4..c6fdc32 100644
--- a/packages/SystemUI/src/com/android/systemui/inputmethod/data/repository/InputMethodRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputmethod/data/repository/InputMethodRepository.kt
@@ -18,7 +18,6 @@
import android.annotation.SuppressLint
import android.os.UserHandle
-import android.view.inputmethod.InputMethodInfo
import android.view.inputmethod.InputMethodManager
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
@@ -34,18 +33,27 @@
/** Provides access to input-method related application state in the bouncer. */
interface InputMethodRepository {
+
/**
* Creates and returns a new `Flow` of installed input methods that are enabled for the
* specified user.
*
+ * @param user The user to query.
* @param fetchSubtypes Whether to fetch the IME Subtypes as well (requires an additional IPC
* call for each IME, avoid if not needed).
* @see InputMethodManager.getEnabledInputMethodListAsUser
*/
- suspend fun enabledInputMethods(userId: Int, fetchSubtypes: Boolean): Flow<InputMethodModel>
+ suspend fun enabledInputMethods(
+ user: UserHandle,
+ fetchSubtypes: Boolean,
+ ): Flow<InputMethodModel>
- /** Returns enabled subtypes for the currently selected input method. */
- suspend fun selectedInputMethodSubtypes(): List<InputMethodModel.Subtype>
+ /**
+ * Returns enabled subtypes for the currently selected input method.
+ *
+ * @param user The user to query.
+ */
+ suspend fun selectedInputMethodSubtypes(user: UserHandle): List<InputMethodModel.Subtype>
/**
* Shows the system's input method picker dialog.
@@ -67,20 +75,22 @@
) : InputMethodRepository {
override suspend fun enabledInputMethods(
- userId: Int,
+ user: UserHandle,
fetchSubtypes: Boolean
): Flow<InputMethodModel> {
return withContext(backgroundDispatcher) {
- inputMethodManager.getEnabledInputMethodListAsUser(UserHandle.of(userId))
+ inputMethodManager.getEnabledInputMethodListAsUser(user)
}
.asFlow()
.map { inputMethodInfo ->
InputMethodModel(
+ userId = user.identifier,
imeId = inputMethodInfo.id,
subtypes =
if (fetchSubtypes) {
enabledInputMethodSubtypes(
- inputMethodInfo,
+ user = user,
+ imeId = inputMethodInfo.id,
allowsImplicitlyEnabledSubtypes = true
)
} else {
@@ -90,11 +100,19 @@
}
}
- override suspend fun selectedInputMethodSubtypes(): List<InputMethodModel.Subtype> {
- return enabledInputMethodSubtypes(
- inputMethodInfo = null, // Fetch subtypes for the currently-selected IME.
- allowsImplicitlyEnabledSubtypes = false
- )
+ override suspend fun selectedInputMethodSubtypes(
+ user: UserHandle,
+ ): List<InputMethodModel.Subtype> {
+ val selectedIme = inputMethodManager.getCurrentInputMethodInfoAsUser(user)
+ return if (selectedIme == null) {
+ emptyList()
+ } else {
+ enabledInputMethodSubtypes(
+ user = user,
+ imeId = selectedIme.id,
+ allowsImplicitlyEnabledSubtypes = false
+ )
+ }
}
@SuppressLint("MissingPermission")
@@ -107,21 +125,23 @@
/**
* Returns a list of enabled input method subtypes for the specified input method info.
*
- * @param inputMethodInfo The [InputMethodInfo] whose subtypes list will be returned. If `null`,
- * returns enabled subtypes for the currently selected [InputMethodInfo].
+ * @param user The user to query.
+ * @param imeId The ID of the input method whose subtypes list will be returned.
* @param allowsImplicitlyEnabledSubtypes Whether to allow to return the implicitly enabled
* subtypes. If an input method info doesn't have enabled subtypes, the framework will
* implicitly enable subtypes according to the current system language.
- * @see InputMethodManager.getEnabledInputMethodSubtypeList
+ * @see InputMethodManager.getEnabledInputMethodSubtypeListAsUser
*/
private suspend fun enabledInputMethodSubtypes(
- inputMethodInfo: InputMethodInfo?,
+ user: UserHandle,
+ imeId: String,
allowsImplicitlyEnabledSubtypes: Boolean
): List<InputMethodModel.Subtype> {
return withContext(backgroundDispatcher) {
- inputMethodManager.getEnabledInputMethodSubtypeList(
- inputMethodInfo,
- allowsImplicitlyEnabledSubtypes
+ inputMethodManager.getEnabledInputMethodSubtypeListAsUser(
+ imeId,
+ allowsImplicitlyEnabledSubtypes,
+ user
)
}
.map {
diff --git a/packages/SystemUI/src/com/android/systemui/inputmethod/domain/interactor/InputMethodInteractor.kt b/packages/SystemUI/src/com/android/systemui/inputmethod/domain/interactor/InputMethodInteractor.kt
index c54aa7f..d3ef178 100644
--- a/packages/SystemUI/src/com/android/systemui/inputmethod/domain/interactor/InputMethodInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputmethod/domain/interactor/InputMethodInteractor.kt
@@ -16,6 +16,7 @@
package com.android.systemui.inputmethod.domain.interactor
+import android.os.UserHandle
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.inputmethod.data.repository.InputMethodRepository
import javax.inject.Inject
@@ -36,14 +37,16 @@
* Method adapted from `com.android.inputmethod.latin.Utils`.
*/
suspend fun hasMultipleEnabledImesOrSubtypes(userId: Int): Boolean {
+ val user = UserHandle.of(userId)
// Count IMEs that either have no subtypes, or have at least one non-auxiliary subtype.
val matchingInputMethods =
repository
- .enabledInputMethods(userId, fetchSubtypes = true)
+ .enabledInputMethods(user, fetchSubtypes = true)
.filter { ime -> ime.subtypes.isEmpty() || ime.subtypes.any { !it.isAuxiliary } }
.take(2) // Short-circuit if we find at least 2 matching IMEs.
- return matchingInputMethods.count() > 1 || repository.selectedInputMethodSubtypes().size > 1
+ return matchingInputMethods.count() > 1 ||
+ repository.selectedInputMethodSubtypes(user).size > 1
}
/** Shows the system's input method picker dialog. */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
index d1a8463..2f41c0b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
@@ -62,21 +62,21 @@
const val TAG = "KeyguardUnlock"
/**
- * Starting scale factor for the app/launcher surface behind the keyguard, when it's animating
- * in during keyguard exit.
+ * Starting scale factor for the app/launcher surface behind the keyguard, when it's animating in
+ * during keyguard exit.
*/
const val SURFACE_BEHIND_START_SCALE_FACTOR = 0.95f
/**
- * How much to translate the surface behind the keyguard at the beginning of the exit animation,
- * in terms of percentage of the surface's height.
+ * How much to translate the surface behind the keyguard at the beginning of the exit animation, in
+ * terms of percentage of the surface's height.
*/
const val SURFACE_BEHIND_START_TRANSLATION_Y = 0.05f
/**
- * Y coordinate of the pivot point for the scale effect on the surface behind the keyguard. This
- * is expressed as percentage of the surface's height, so 0.66f means the surface will scale up
- * from the point at (width / 2, height * 0.66).
+ * Y coordinate of the pivot point for the scale effect on the surface behind the keyguard. This is
+ * expressed as percentage of the surface's height, so 0.66f means the surface will scale up from
+ * the point at (width / 2, height * 0.66).
*/
const val SURFACE_BEHIND_SCALE_PIVOT_Y = 0.66f
@@ -155,19 +155,20 @@
* [notifyStartSurfaceBehindRemoteAnimation] by [KeyguardViewMediator].
*/
@SysUISingleton
-class KeyguardUnlockAnimationController @Inject constructor(
- private val windowManager: WindowManager,
- @Main private val resources: Resources,
- private val keyguardStateController: KeyguardStateController,
- private val
- keyguardViewMediator: Lazy<KeyguardViewMediator>,
- private val keyguardViewController: KeyguardViewController,
- private val featureFlags: FeatureFlags,
- private val biometricUnlockControllerLazy: Lazy<BiometricUnlockController>,
- private val statusBarStateController: SysuiStatusBarStateController,
- private val notificationShadeWindowController: NotificationShadeWindowController,
- private val powerManager: PowerManager,
- private val wallpaperManager: WallpaperManager,
+open class KeyguardUnlockAnimationController
+@Inject
+constructor(
+ private val windowManager: WindowManager,
+ @Main private val resources: Resources,
+ private val keyguardStateController: KeyguardStateController,
+ private val keyguardViewMediator: Lazy<KeyguardViewMediator>,
+ private val keyguardViewController: KeyguardViewController,
+ private val featureFlags: FeatureFlags,
+ private val biometricUnlockControllerLazy: Lazy<BiometricUnlockController>,
+ private val statusBarStateController: SysuiStatusBarStateController,
+ private val notificationShadeWindowController: NotificationShadeWindowController,
+ private val powerManager: PowerManager,
+ private val wallpaperManager: WallpaperManager,
) : KeyguardStateController.Callback, ISysuiUnlockAnimationController.Stub() {
interface KeyguardUnlockAnimationListener {
@@ -221,8 +222,8 @@
var playingCannedUnlockAnimation = false
/**
- * Whether we reached the swipe gesture threshold to dismiss keyguard, or restore it, once
- * and should ignore any future changes to the dismiss amount before the animation finishes.
+ * Whether we reached the swipe gesture threshold to dismiss keyguard, or restore it, once and
+ * should ignore any future changes to the dismiss amount before the animation finishes.
*/
var dismissAmountThresholdsReached = false
@@ -235,9 +236,7 @@
*/
private var launcherUnlockController: ILauncherUnlockAnimationController? = null
- /**
- * Fully qualified class name of the launcher activity
- */
+ /** Fully qualified class name of the launcher activity */
private var launcherActivityClass: String? = null
private val listeners = ArrayList<KeyguardUnlockAnimationListener>()
@@ -248,8 +247,8 @@
* transition, but that's okay!
*/
override fun setLauncherUnlockController(
- activityClass: String,
- callback: ILauncherUnlockAnimationController?
+ activityClass: String,
+ callback: ILauncherUnlockAnimationController?
) {
launcherActivityClass = activityClass
launcherUnlockController = callback
@@ -274,8 +273,7 @@
* If we're unlocking via biometrics, PIN entry, or from clicking a notification, a canned
* animation is started in [playCannedUnlockAnimation].
*/
- @VisibleForTesting
- var surfaceTransactionApplier: SyncRtSurfaceTransactionApplier? = null
+ @VisibleForTesting var surfaceTransactionApplier: SyncRtSurfaceTransactionApplier? = null
private var surfaceBehindRemoteAnimationTargets: Array<RemoteAnimationTarget>? = null
private var openingWallpaperTargets: Array<RemoteAnimationTarget>? = null
private var closingWallpaperTargets: Array<RemoteAnimationTarget>? = null
@@ -291,8 +289,7 @@
*/
private var surfaceBehindAlpha = 1f
- @VisibleForTesting
- var surfaceBehindAlphaAnimator = ValueAnimator.ofFloat(0f, 1f)
+ @VisibleForTesting var surfaceBehindAlphaAnimator = ValueAnimator.ofFloat(0f, 1f)
var wallpaperCannedUnlockAnimator = ValueAnimator.ofFloat(0f, 1f)
@@ -310,8 +307,7 @@
* Animator that animates in the surface behind the keyguard. This is used to play a canned
* animation on the surface, if we're not doing a swipe gesture.
*/
- @VisibleForTesting
- val surfaceBehindEntryAnimator = ValueAnimator.ofFloat(0f, 1f)
+ @VisibleForTesting val surfaceBehindEntryAnimator = ValueAnimator.ofFloat(0f, 1f)
/** Rounded corner radius to apply to the surface behind the keyguard. */
private var roundedCornerRadius = 0f
@@ -322,8 +318,7 @@
* window like any other app. This can be true while [willUnlockWithSmartspaceTransition] is
* false, if the smartspace is not available or was not ready in time.
*/
- @VisibleForTesting
- var willUnlockWithInWindowLauncherAnimations: Boolean = false
+ @VisibleForTesting var willUnlockWithInWindowLauncherAnimations: Boolean = false
/**
* Whether we called [ILauncherUnlockAnimationController.prepareForUnlock], but have not yet
@@ -353,49 +348,64 @@
surfaceBehindAlpha = valueAnimator.animatedValue as Float
updateSurfaceBehindAppearAmount()
}
- addListener(object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator) {
- // If we animated the surface alpha to 0f, it means we cancelled a swipe to
- // dismiss. In this case, we should ask the KeyguardViewMediator to end the
- // remote animation to hide the surface behind the keyguard, but should *not*
- // call onKeyguardExitRemoteAnimationFinished since that will hide the keyguard
- // and unlock the device as well as hiding the surface.
- if (surfaceBehindAlpha == 0f) {
- Log.d(TAG, "surfaceBehindAlphaAnimator#onAnimationEnd")
- surfaceBehindRemoteAnimationTargets = null
- openingWallpaperTargets = null
- closingWallpaperTargets = null
- keyguardViewMediator.get().finishSurfaceBehindRemoteAnimation(
- false /* cancelled */)
- } else {
- Log.d(TAG, "skip finishSurfaceBehindRemoteAnimation" +
- " surfaceBehindAlpha=$surfaceBehindAlpha")
+ addListener(
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ // If we animated the surface alpha to 0f, it means we cancelled a swipe to
+ // dismiss. In this case, we should ask the KeyguardViewMediator to end the
+ // remote animation to hide the surface behind the keyguard, but should
+ // *not* call onKeyguardExitRemoteAnimationFinished since that will hide the
+ // keyguard and unlock the device as well as hiding the surface.
+ if (surfaceBehindAlpha == 0f) {
+ Log.d(TAG, "surfaceBehindAlphaAnimator#onAnimationEnd")
+ surfaceBehindRemoteAnimationTargets = null
+ openingWallpaperTargets = null
+ closingWallpaperTargets = null
+ keyguardViewMediator
+ .get()
+ .finishSurfaceBehindRemoteAnimation(false /* cancelled */)
+ } else {
+ Log.d(
+ TAG,
+ "skip finishSurfaceBehindRemoteAnimation" +
+ " surfaceBehindAlpha=$surfaceBehindAlpha"
+ )
+ }
}
}
- })
+ )
}
with(wallpaperCannedUnlockAnimator) {
- duration = if (fasterUnlockTransition()) UNLOCK_ANIMATION_DURATION_MS
- else LAUNCHER_ICONS_ANIMATION_DURATION_MS
- interpolator = if (fasterUnlockTransition()) Interpolators.LINEAR
- else Interpolators.ALPHA_OUT
+ duration =
+ if (fasterUnlockTransition()) UNLOCK_ANIMATION_DURATION_MS
+ else LAUNCHER_ICONS_ANIMATION_DURATION_MS
+ interpolator =
+ if (fasterUnlockTransition()) Interpolators.LINEAR else Interpolators.ALPHA_OUT
addUpdateListener { valueAnimator: ValueAnimator ->
setWallpaperAppearAmount(
- valueAnimator.animatedValue as Float, openingWallpaperTargets)
+ valueAnimator.animatedValue as Float,
+ openingWallpaperTargets
+ )
}
- addListener(object : AnimatorListenerAdapter() {
- override fun onAnimationStart(animation: Animator) {
- super.onAnimationStart(animation)
- Trace.asyncTraceBegin(Trace.TRACE_TAG_APP, "WallpaperAlphaAnimation", 0)
+ addListener(
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationStart(animation: Animator) {
+ super.onAnimationStart(animation)
+ Trace.asyncTraceBegin(Trace.TRACE_TAG_APP, "WallpaperAlphaAnimation", 0)
+ }
+
+ override fun onAnimationEnd(animation: Animator) {
+ Log.d(TAG, "wallpaperCannedUnlockAnimator#onAnimationEnd")
+ keyguardViewMediator
+ .get()
+ .exitKeyguardAndFinishSurfaceBehindRemoteAnimation(
+ false /* cancelled */
+ )
+ Trace.asyncTraceEnd(Trace.TRACE_TAG_APP, "WallpaperAlphaAnimation", 0)
+ }
}
- override fun onAnimationEnd(animation: Animator) {
- Log.d(TAG, "wallpaperCannedUnlockAnimator#onAnimationEnd")
- keyguardViewMediator.get().exitKeyguardAndFinishSurfaceBehindRemoteAnimation(
- false /* cancelled */)
- Trace.asyncTraceEnd(Trace.TRACE_TAG_APP, "WallpaperAlphaAnimation", 0)
- }
- })
+ )
}
if (fasterUnlockTransition()) {
@@ -405,7 +415,9 @@
interpolator = Interpolators.LINEAR
addUpdateListener { valueAnimator: ValueAnimator ->
setWallpaperAppearAmount(
- valueAnimator.animatedValue as Float, closingWallpaperTargets)
+ valueAnimator.animatedValue as Float,
+ closingWallpaperTargets
+ )
}
}
}
@@ -418,15 +430,19 @@
surfaceBehindAlpha = valueAnimator.animatedValue as Float
setSurfaceBehindAppearAmount(valueAnimator.animatedValue as Float)
}
- addListener(object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator) {
- Log.d(TAG, "surfaceBehindEntryAnimator#onAnimationEnd")
- playingCannedUnlockAnimation = false
- keyguardViewMediator.get().exitKeyguardAndFinishSurfaceBehindRemoteAnimation(
- false /* cancelled */
- )
+ addListener(
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ Log.d(TAG, "surfaceBehindEntryAnimator#onAnimationEnd")
+ playingCannedUnlockAnimation = false
+ keyguardViewMediator
+ .get()
+ .exitKeyguardAndFinishSurfaceBehindRemoteAnimation(
+ false /* cancelled */
+ )
+ }
}
- })
+ )
}
// Listen for changes in the dismiss amount.
@@ -436,9 +452,7 @@
resources.getDimensionPixelSize(R.dimen.rounded_corner_radius).toFloat()
}
- /**
- * Add a listener to be notified of various stages of the unlock animation.
- */
+ /** Add a listener to be notified of various stages of the unlock animation. */
fun addKeyguardUnlockAnimationListener(listener: KeyguardUnlockAnimationListener) {
listeners.add(listener)
}
@@ -454,11 +468,11 @@
fun canPerformInWindowLauncherAnimations(): Boolean {
// TODO(b/278086361): Refactor in-window animations.
return !KeyguardWmStateRefactor.isEnabled &&
- isSupportedLauncherUnderneath() &&
- // If the launcher is underneath, but we're about to launch an activity, don't do
- // the animations since they won't be visible.
- !notificationShadeWindowController.isLaunchingActivity &&
- launcherUnlockController != null
+ isSupportedLauncherUnderneath() &&
+ // If the launcher is underneath, but we're about to launch an activity, don't do
+ // the animations since they won't be visible.
+ !notificationShadeWindowController.isLaunchingActivity &&
+ launcherUnlockController != null
}
/**
@@ -469,8 +483,11 @@
private fun logInWindowAnimationConditions() {
Log.wtf(TAG, "canPerformInWindowLauncherAnimations expected all of these to be true: ")
Log.wtf(TAG, " isNexusLauncherUnderneath: ${isSupportedLauncherUnderneath()}")
- Log.wtf(TAG, " !notificationShadeWindowController.isLaunchingActivity: " +
- "${!notificationShadeWindowController.isLaunchingActivity}")
+ Log.wtf(
+ TAG,
+ " !notificationShadeWindowController.isLaunchingActivity: " +
+ "${!notificationShadeWindowController.isLaunchingActivity}"
+ )
Log.wtf(TAG, " launcherUnlockController != null: ${launcherUnlockController != null}")
Log.wtf(TAG, " !isFoldable(context): ${!isFoldable(resources)}")
}
@@ -480,8 +497,10 @@
* changed.
*/
override fun onKeyguardGoingAwayChanged() {
- if (keyguardStateController.isKeyguardGoingAway &&
- !statusBarStateController.leaveOpenOnKeyguardHide()) {
+ if (
+ keyguardStateController.isKeyguardGoingAway &&
+ !statusBarStateController.leaveOpenOnKeyguardHide()
+ ) {
prepareForInWindowLauncherAnimations()
}
@@ -489,16 +508,22 @@
// make sure that we've left the launcher at 100% unlocked. This is a fail-safe to prevent
// against "tiny launcher" and similar states where the launcher is left in the prepared to
// animate state.
- if (!keyguardStateController.isKeyguardGoingAway &&
- willUnlockWithInWindowLauncherAnimations) {
+ if (
+ !keyguardStateController.isKeyguardGoingAway && willUnlockWithInWindowLauncherAnimations
+ ) {
try {
- launcherUnlockController?.setUnlockAmount(1f,
- biometricUnlockControllerLazy.get().isWakeAndUnlock /* forceIfAnimating */)
+ launcherUnlockController?.setUnlockAmount(
+ 1f,
+ biometricUnlockControllerLazy.get().isWakeAndUnlock /* forceIfAnimating */
+ )
} catch (e: DeadObjectException) {
- Log.e(TAG, "launcherUnlockAnimationController was dead, but non-null in " +
+ Log.e(
+ TAG,
+ "launcherUnlockAnimationController was dead, but non-null in " +
"onKeyguardGoingAwayChanged(). Catching exception as this should mean " +
"Launcher is in the process of being destroyed, but the IPC to System UI " +
- "telling us hasn't arrived yet.")
+ "telling us hasn't arrived yet."
+ )
}
}
}
@@ -525,22 +550,26 @@
// Grab the bounds of our lockscreen smartspace and send them to launcher so they can
// position their smartspace there initially, then animate it to its resting position.
if (willUnlockWithSmartspaceTransition) {
- lockscreenSmartspaceBounds = Rect().apply {
- lockscreenSmartspace!!.getBoundsOnScreen(this)
+ lockscreenSmartspaceBounds =
+ Rect().apply {
+ lockscreenSmartspace!!.getBoundsOnScreen(this)
- // The smartspace container on the lockscreen has left and top padding to align it
- // with other lockscreen content. This padding is inside the bounds on screen, so
- // add it to those bounds so that the padding-less launcher smartspace is properly
- // aligned.
- offset(lockscreenSmartspace!!.paddingLeft, lockscreenSmartspace!!.paddingTop)
+ // The smartspace container on the lockscreen has left and top padding to align
+ // it with other lockscreen content. This padding is inside the bounds on
+ // screen, so add it to those bounds so that the padding-less launcher
+ // smartspace is properly aligned.
+ offset(lockscreenSmartspace!!.paddingLeft, lockscreenSmartspace!!.paddingTop)
- // Also offset by the current card's top padding, if it has any. This allows us to
- // align the tops of the lockscreen/launcher smartspace cards. Some cards, such as
- // the three-line date/weather/alarm card, only have three lines on lockscreen but
- // two on launcher.
- offset(0, (lockscreenSmartspace
- as? BcSmartspaceDataPlugin.SmartspaceView)?.currentCardTopPadding ?: 0)
- }
+ // Also offset by the current card's top padding, if it has any. This allows us
+ // to align the tops of the lockscreen/launcher smartspace cards. Some cards,
+ // such as the three-line date/weather/alarm card, only have three lines on
+ // lockscreen but two on launcher.
+ offset(
+ 0,
+ (lockscreenSmartspace as? BcSmartspaceDataPlugin.SmartspaceView)
+ ?.currentCardTopPadding ?: 0
+ )
+ }
}
// Currently selected lockscreen smartspace page, or -1 if it's not available.
@@ -583,8 +612,8 @@
requestedShowSurfaceBehindKeyguard: Boolean
) {
if (surfaceTransactionApplier == null) {
- surfaceTransactionApplier = SyncRtSurfaceTransactionApplier(
- keyguardViewController.viewRootImpl.view)
+ surfaceTransactionApplier =
+ SyncRtSurfaceTransactionApplier(keyguardViewController.viewRootImpl.view)
}
surfaceBehindRemoteAnimationTargets = targets
@@ -603,8 +632,10 @@
// surface behind the keyguard to finish unlocking.
if (keyguardStateController.isFlingingToDismissKeyguard) {
playCannedUnlockAnimation()
- } else if (keyguardStateController.isDismissingFromSwipe &&
- willUnlockWithInWindowLauncherAnimations) {
+ } else if (
+ keyguardStateController.isDismissingFromSwipe &&
+ willUnlockWithInWindowLauncherAnimations
+ ) {
// If we're swiping to unlock to the Launcher, and can play in-window animations,
// make the launcher surface fully visible and play the in-window unlock animation
// on the launcher icons. System UI will remain locked, using the swipe-to-unlock
@@ -615,19 +646,23 @@
try {
launcherUnlockController?.playUnlockAnimation(
- true,
- unlockAnimationDurationMs() + cannedUnlockStartDelayMs(),
- 0 /* startDelay */)
+ true,
+ unlockAnimationDurationMs() + cannedUnlockStartDelayMs(),
+ 0 /* startDelay */
+ )
} catch (e: DeadObjectException) {
// Hello! If you are here investigating a bug where Launcher is blank (no icons)
// then the below assumption about Launcher's destruction was incorrect. This
// would mean prepareToUnlock was called (blanking Launcher in preparation for
// the beginning of the unlock animation), but then somehow we were unable to
// call playUnlockAnimation to animate the icons back in.
- Log.e(TAG, "launcherUnlockAnimationController was dead, but non-null. " +
+ Log.e(
+ TAG,
+ "launcherUnlockAnimationController was dead, but non-null. " +
"Catching exception as this should mean Launcher is in the process " +
"of being destroyed, but the IPC to System UI telling us hasn't " +
- "arrived yet.")
+ "arrived yet."
+ )
}
launcherPreparedForUnlock = false
@@ -643,15 +678,18 @@
}
// Notify if waking from AOD only
- val isWakeAndUnlockNotFromDream = biometricUnlockControllerLazy.get().isWakeAndUnlock &&
- biometricUnlockControllerLazy.get().mode != MODE_WAKE_AND_UNLOCK_FROM_DREAM
+ val isWakeAndUnlockNotFromDream =
+ biometricUnlockControllerLazy.get().isWakeAndUnlock &&
+ biometricUnlockControllerLazy.get().mode != MODE_WAKE_AND_UNLOCK_FROM_DREAM
listeners.forEach {
it.onUnlockAnimationStarted(
playingCannedUnlockAnimation /* playingCannedAnimation */,
isWakeAndUnlockNotFromDream /* isWakeAndUnlockNotFromDream */,
cannedUnlockStartDelayMs() /* unlockStartDelay */,
- LAUNCHER_ICONS_ANIMATION_DURATION_MS /* unlockAnimationDuration */) }
+ LAUNCHER_ICONS_ANIMATION_DURATION_MS /* unlockAnimationDuration */
+ )
+ }
// Finish the keyguard remote animation if the dismiss amount has crossed the threshold.
// Check it here in case there is no more change to the dismiss amount after the last change
@@ -685,8 +723,9 @@
biometricUnlockControllerLazy.get().isWakeAndUnlock -> {
Log.d(TAG, "playCannedUnlockAnimation, isWakeAndUnlock")
setSurfaceBehindAppearAmount(1f)
- keyguardViewMediator.get().exitKeyguardAndFinishSurfaceBehindRemoteAnimation(
- false /* cancelled */)
+ keyguardViewMediator
+ .get()
+ .exitKeyguardAndFinishSurfaceBehindRemoteAnimation(false /* cancelled */)
}
// Otherwise, we're doing a normal full-window unlock. Start this animator, which will
@@ -698,8 +737,11 @@
}
if (launcherPreparedForUnlock && !willUnlockWithInWindowLauncherAnimations) {
- Log.wtf(TAG, "Launcher is prepared for unlock, so we should have started the " +
- "in-window animation, however we apparently did not.")
+ Log.wtf(
+ TAG,
+ "Launcher is prepared for unlock, so we should have started the " +
+ "in-window animation, however we apparently did not."
+ )
logInWindowAnimationConditions()
}
}
@@ -708,7 +750,6 @@
* Unlock to the launcher, using in-window animations, and the smartspace shared element
* transition if possible.
*/
-
@VisibleForTesting
fun unlockToLauncherWithInWindowAnimations() {
surfaceBehindAlpha = 1f
@@ -717,26 +758,32 @@
try {
// Begin the animation, waiting for the shade to animate out.
launcherUnlockController?.playUnlockAnimation(
- true /* unlocked */,
- LAUNCHER_ICONS_ANIMATION_DURATION_MS /* duration */,
- cannedUnlockStartDelayMs() /* startDelay */)
+ true /* unlocked */,
+ LAUNCHER_ICONS_ANIMATION_DURATION_MS /* duration */,
+ cannedUnlockStartDelayMs() /* startDelay */
+ )
} catch (e: DeadObjectException) {
// Hello! If you are here investigating a bug where Launcher is blank (no icons)
// then the below assumption about Launcher's destruction was incorrect. This
// would mean prepareToUnlock was called (blanking Launcher in preparation for
// the beginning of the unlock animation), but then somehow we were unable to
// call playUnlockAnimation to animate the icons back in.
- Log.e(TAG, "launcherUnlockAnimationController was dead, but non-null. " +
+ Log.e(
+ TAG,
+ "launcherUnlockAnimationController was dead, but non-null. " +
"Catching exception as this should mean Launcher is in the process " +
"of being destroyed, but the IPC to System UI telling us hasn't " +
- "arrived yet.")
+ "arrived yet."
+ )
}
launcherPreparedForUnlock = false
// Now that the Launcher surface (with its smartspace positioned identically to ours) is
// visible, hide our smartspace.
- if (lockscreenSmartspace?.visibility == View.VISIBLE) {
+ if (
+ shouldPerformSmartspaceTransition() && lockscreenSmartspace?.visibility == View.VISIBLE
+ ) {
lockscreenSmartspace?.visibility = View.INVISIBLE
}
@@ -747,22 +794,31 @@
fadeOutWallpaper()
}
- handler.postDelayed({
- if (keyguardViewMediator.get().isShowingAndNotOccluded &&
- !keyguardStateController.isKeyguardGoingAway) {
- Log.e(TAG, "Finish keyguard exit animation delayed Runnable ran, but we are " +
- "showing and not going away.")
- return@postDelayed
- }
+ handler.postDelayed(
+ {
+ if (
+ keyguardViewMediator.get().isShowingAndNotOccluded &&
+ !keyguardStateController.isKeyguardGoingAway
+ ) {
+ Log.e(
+ TAG,
+ "Finish keyguard exit animation delayed Runnable ran, but we are " +
+ "showing and not going away."
+ )
+ return@postDelayed
+ }
- if (openingWallpaperTargets?.isNotEmpty() == true) {
- fadeInWallpaper()
- hideKeyguardViewAfterRemoteAnimation()
- } else {
- keyguardViewMediator.get().exitKeyguardAndFinishSurfaceBehindRemoteAnimation(
- false /* cancelled */)
- }
- }, cannedUnlockStartDelayMs())
+ if (openingWallpaperTargets?.isNotEmpty() == true) {
+ fadeInWallpaper()
+ hideKeyguardViewAfterRemoteAnimation()
+ } else {
+ keyguardViewMediator
+ .get()
+ .exitKeyguardAndFinishSurfaceBehindRemoteAnimation(false /* cancelled */)
+ }
+ },
+ cannedUnlockStartDelayMs()
+ )
}
/**
@@ -784,12 +840,14 @@
// interaction tight.
if (keyguardStateController.isFlingingToDismissKeyguard) {
setSurfaceBehindAppearAmount(keyguardStateController.dismissAmount)
- } else if (keyguardStateController.isDismissingFromSwipe ||
- keyguardStateController.isSnappingKeyguardBackAfterSwipe) {
+ } else if (
+ keyguardStateController.isDismissingFromSwipe ||
+ keyguardStateController.isSnappingKeyguardBackAfterSwipe
+ ) {
val totalSwipeDistanceToDismiss =
- (DISMISS_AMOUNT_EXIT_KEYGUARD_THRESHOLD - DISMISS_AMOUNT_SHOW_SURFACE_THRESHOLD)
+ (DISMISS_AMOUNT_EXIT_KEYGUARD_THRESHOLD - DISMISS_AMOUNT_SHOW_SURFACE_THRESHOLD)
val swipedDistanceSoFar: Float =
- keyguardStateController.dismissAmount - DISMISS_AMOUNT_SHOW_SURFACE_THRESHOLD
+ keyguardStateController.dismissAmount - DISMISS_AMOUNT_SHOW_SURFACE_THRESHOLD
val progress = swipedDistanceSoFar / totalSwipeDistanceToDismiss
setSurfaceBehindAppearAmount(progress)
}
@@ -801,10 +859,13 @@
// If the surface is visible or it's about to be, start updating its appearance to
// reflect the new dismiss amount.
- if ((keyguardViewMediator.get().requestedShowSurfaceBehindKeyguard() ||
- keyguardViewMediator.get()
+ if (
+ (keyguardViewMediator.get().requestedShowSurfaceBehindKeyguard() ||
+ keyguardViewMediator
+ .get()
.isAnimatingBetweenKeyguardAndSurfaceBehindOrWillBe) &&
- !playingCannedUnlockAnimation) {
+ !playingCannedUnlockAnimation
+ ) {
updateSurfaceBehindAppearAmount()
}
}
@@ -838,11 +899,15 @@
val dismissAmount = keyguardStateController.dismissAmount
- if (dismissAmount >= DISMISS_AMOUNT_SHOW_SURFACE_THRESHOLD &&
- !keyguardViewMediator.get().requestedShowSurfaceBehindKeyguard()) {
+ if (
+ dismissAmount >= DISMISS_AMOUNT_SHOW_SURFACE_THRESHOLD &&
+ !keyguardViewMediator.get().requestedShowSurfaceBehindKeyguard()
+ ) {
keyguardViewMediator.get().showSurfaceBehindKeyguard()
- } else if (dismissAmount < DISMISS_AMOUNT_SHOW_SURFACE_THRESHOLD &&
- keyguardViewMediator.get().requestedShowSurfaceBehindKeyguard()) {
+ } else if (
+ dismissAmount < DISMISS_AMOUNT_SHOW_SURFACE_THRESHOLD &&
+ keyguardViewMediator.get().requestedShowSurfaceBehindKeyguard()
+ ) {
// We're no longer past the threshold but we are showing the surface. Animate it
// out.
keyguardViewMediator.get().hideSurfaceBehindKeyguard()
@@ -868,22 +933,27 @@
}
// no-op if animation is not requested yet.
- if (!keyguardViewMediator.get().requestedShowSurfaceBehindKeyguard() ||
- !keyguardViewMediator.get().isAnimatingBetweenKeyguardAndSurfaceBehindOrWillBe) {
+ if (
+ !keyguardViewMediator.get().requestedShowSurfaceBehindKeyguard() ||
+ !keyguardViewMediator.get().isAnimatingBetweenKeyguardAndSurfaceBehindOrWillBe
+ ) {
return
}
val dismissAmount = keyguardStateController.dismissAmount
- if (dismissAmount >= 1f ||
+ if (
+ dismissAmount >= 1f ||
(keyguardStateController.isDismissingFromSwipe &&
- // Don't hide if we're flinging during a swipe, since we need to finish
- // animating it out. This will be called again after the fling ends.
- !keyguardStateController.isFlingingToDismissKeyguardDuringSwipeGesture &&
- dismissAmount >= DISMISS_AMOUNT_EXIT_KEYGUARD_THRESHOLD)) {
+ // Don't hide if we're flinging during a swipe, since we need to finish
+ // animating it out. This will be called again after the fling ends.
+ !keyguardStateController.isFlingingToDismissKeyguardDuringSwipeGesture &&
+ dismissAmount >= DISMISS_AMOUNT_EXIT_KEYGUARD_THRESHOLD)
+ ) {
setSurfaceBehindAppearAmount(1f)
dismissAmountThresholdsReached = true
- keyguardViewMediator.get().exitKeyguardAndFinishSurfaceBehindRemoteAnimation(
- false /* cancelled */)
+ keyguardViewMediator
+ .get()
+ .exitKeyguardAndFinishSurfaceBehindRemoteAnimation(false /* cancelled */)
}
}
@@ -894,51 +964,56 @@
* wallpapers, this transitions between the two wallpapers
*/
fun setSurfaceBehindAppearAmount(amount: Float, wallpapers: Boolean = true) {
- val animationAlpha = when {
- // If we're snapping the keyguard back, immediately begin fading it out.
- keyguardStateController.isSnappingKeyguardBackAfterSwipe -> amount
- // If the screen has turned back off, the unlock animation is going to be cancelled,
- // so set the surface alpha to 0f so it's no longer visible.
- !powerManager.isInteractive -> 0f
- else -> surfaceBehindAlpha
- }
+ val animationAlpha =
+ when {
+ // If we're snapping the keyguard back, immediately begin fading it out.
+ keyguardStateController.isSnappingKeyguardBackAfterSwipe -> amount
+ // If the screen has turned back off, the unlock animation is going to be cancelled,
+ // so set the surface alpha to 0f so it's no longer visible.
+ !powerManager.isInteractive -> 0f
+ else -> surfaceBehindAlpha
+ }
surfaceBehindRemoteAnimationTargets?.forEach { surfaceBehindRemoteAnimationTarget ->
if (!KeyguardWmStateRefactor.isEnabled) {
val surfaceHeight: Int =
- surfaceBehindRemoteAnimationTarget.screenSpaceBounds.height()
+ surfaceBehindRemoteAnimationTarget.screenSpaceBounds.height()
- var scaleFactor = (SURFACE_BEHIND_START_SCALE_FACTOR +
- (1f - SURFACE_BEHIND_START_SCALE_FACTOR) *
- MathUtils.clamp(amount, 0f, 1f))
+ var scaleFactor =
+ (SURFACE_BEHIND_START_SCALE_FACTOR +
+ (1f - SURFACE_BEHIND_START_SCALE_FACTOR) * MathUtils.clamp(amount, 0f, 1f))
// If we're dismissing via swipe to the Launcher, we'll play in-window scale
// animations, so don't also scale the window.
- if (keyguardStateController.isDismissingFromSwipe &&
- willUnlockWithInWindowLauncherAnimations) {
+ if (
+ keyguardStateController.isDismissingFromSwipe &&
+ willUnlockWithInWindowLauncherAnimations
+ ) {
scaleFactor = 1f
}
// Translate up from the bottom.
surfaceBehindMatrix.setTranslate(
- surfaceBehindRemoteAnimationTarget.screenSpaceBounds.left.toFloat(),
- surfaceBehindRemoteAnimationTarget.screenSpaceBounds.top.toFloat() +
- surfaceHeight * SURFACE_BEHIND_START_TRANSLATION_Y * (1f - amount)
+ surfaceBehindRemoteAnimationTarget.screenSpaceBounds.left.toFloat(),
+ surfaceBehindRemoteAnimationTarget.screenSpaceBounds.top.toFloat() +
+ surfaceHeight * SURFACE_BEHIND_START_TRANSLATION_Y * (1f - amount)
)
// Scale up from a point at the center-bottom of the surface.
surfaceBehindMatrix.postScale(
- scaleFactor,
- scaleFactor,
- keyguardViewController.viewRootImpl.width / 2f,
- surfaceHeight * SURFACE_BEHIND_SCALE_PIVOT_Y
+ scaleFactor,
+ scaleFactor,
+ keyguardViewController.viewRootImpl.width / 2f,
+ surfaceHeight * SURFACE_BEHIND_SCALE_PIVOT_Y
)
// SyncRtSurfaceTransactionApplier cannot apply transaction when the target view is
// unable to draw
val sc: SurfaceControl? = surfaceBehindRemoteAnimationTarget.leash
- if (keyguardViewController.viewRootImpl.view?.visibility != View.VISIBLE &&
- sc?.isValid == true) {
+ if (
+ keyguardViewController.viewRootImpl.view?.visibility != View.VISIBLE &&
+ sc?.isValid == true
+ ) {
with(SurfaceControl.Transaction()) {
setMatrix(sc, surfaceBehindMatrix, tmpFloat)
setCornerRadius(sc, roundedCornerRadius)
@@ -947,12 +1022,13 @@
}
} else {
applyParamsToSurface(
- SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(
- surfaceBehindRemoteAnimationTarget.leash)
- .withMatrix(surfaceBehindMatrix)
- .withCornerRadius(roundedCornerRadius)
- .withAlpha(animationAlpha)
- .build()
+ SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(
+ surfaceBehindRemoteAnimationTarget.leash
+ )
+ .withMatrix(surfaceBehindMatrix)
+ .withCornerRadius(roundedCornerRadius)
+ .withAlpha(animationAlpha)
+ .build()
)
}
}
@@ -969,8 +1045,8 @@
val fadeOutStart = LOCK_WALLPAPER_FADE_OUT_START_DELAY / total
val fadeOutEnd = fadeOutStart + LOCK_WALLPAPER_FADE_OUT_DURATION / total
- val fadeOutAmount = ((amount - fadeOutStart) / (fadeOutEnd - fadeOutStart))
- .coerceIn(0f, 1f)
+ val fadeOutAmount =
+ ((amount - fadeOutStart) / (fadeOutEnd - fadeOutStart)).coerceIn(0f, 1f)
setWallpaperAppearAmount(fadeInAmount, openingWallpaperTargets)
setWallpaperAppearAmount(1 - fadeOutAmount, closingWallpaperTargets)
@@ -984,18 +1060,19 @@
// SyncRtSurfaceTransactionApplier cannot apply transaction when the target view is
// unable to draw
val sc: SurfaceControl? = wallpaper.leash
- if (keyguardViewController.viewRootImpl.view?.visibility != View.VISIBLE &&
- sc?.isValid == true) {
+ if (
+ keyguardViewController.viewRootImpl.view?.visibility != View.VISIBLE &&
+ sc?.isValid == true
+ ) {
with(SurfaceControl.Transaction()) {
setAlpha(sc, animationAlpha)
apply()
}
} else {
applyParamsToSurface(
- SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(
- wallpaper.leash)
- .withAlpha(animationAlpha)
- .build()
+ SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(wallpaper.leash)
+ .withAlpha(animationAlpha)
+ .build()
)
}
}
@@ -1019,9 +1096,9 @@
}
if (!showKeyguard) {
- // Make sure we made the surface behind fully visible, just in case. It should already be
- // fully visible. The exit animation is finished, and we should not hold the leash anymore,
- // so forcing it to 1f.
+ // Make sure we made the surface behind fully visible, just in case. It should already
+ // be fully visible. The exit animation is finished, and we should not hold the leash
+ // anymore, so forcing it to 1f.
surfaceBehindAlpha = 1f
setSurfaceBehindAppearAmount(1f)
@@ -1061,13 +1138,16 @@
if (!KeyguardWmStateRefactor.isEnabled) {
keyguardViewController.hide(
- surfaceBehindRemoteAnimationStartTime,
- 0 /* fadeOutDuration */
+ surfaceBehindRemoteAnimationStartTime,
+ 0 /* fadeOutDuration */
)
}
} else {
- Log.i(TAG, "#hideKeyguardViewAfterRemoteAnimation called when keyguard view is not " +
- "showing. Ignoring...")
+ Log.i(
+ TAG,
+ "#hideKeyguardViewAfterRemoteAnimation called when keyguard view is not " +
+ "showing. Ignoring..."
+ )
}
}
@@ -1099,7 +1179,8 @@
surfaceBehindAlphaAnimator.reverse()
}
- private fun shouldPerformSmartspaceTransition(): Boolean {
+ /** Note: declared open for ease of testing */
+ open fun shouldPerformSmartspaceTransition(): Boolean {
// Feature is disabled, so we don't want to.
if (!featureFlags.isEnabled(Flags.SMARTSPACE_SHARED_ELEMENT_TRANSITION_ENABLED)) {
return false
@@ -1107,9 +1188,11 @@
// If our controllers are null, or we haven't received a smartspace state from Launcher yet,
// we will not be doing any smartspace transitions today.
- if (launcherUnlockController == null ||
- lockscreenSmartspace == null ||
- launcherSmartspaceState == null) {
+ if (
+ launcherUnlockController == null ||
+ lockscreenSmartspace == null ||
+ launcherSmartspaceState == null
+ ) {
return false
}
@@ -1135,8 +1218,10 @@
// element transition is if we're doing a biometric unlock. Otherwise, it means the bouncer
// is showing, and you can't see the lockscreen smartspace, so a shared element transition
// would not make sense.
- if (!keyguardStateController.canDismissLockScreen() &&
- !biometricUnlockControllerLazy.get().isBiometricUnlock) {
+ if (
+ !keyguardStateController.canDismissLockScreen() &&
+ !biometricUnlockControllerLazy.get().isBiometricUnlock
+ ) {
return false
}
@@ -1175,9 +1260,7 @@
return willUnlockWithSmartspaceTransition
}
- /**
- * Whether the RemoteAnimation on the app/launcher surface behind the keyguard is 'running'.
- */
+ /** Whether the RemoteAnimation on the app/launcher surface behind the keyguard is 'running'. */
fun isAnimatingBetweenKeyguardAndSurfaceBehind(): Boolean {
return keyguardViewMediator.get().isAnimatingBetweenKeyguardAndSurfaceBehind
}
@@ -1196,39 +1279,38 @@
* in-window/shared element transitions!
*/
fun isSupportedLauncherUnderneath(): Boolean {
- return launcherActivityClass?.let { ActivityManagerWrapper.getInstance()
- .runningTask?.topActivity?.className?.equals(it) }
- ?: false
+ return launcherActivityClass?.let {
+ ActivityManagerWrapper.getInstance().runningTask?.topActivity?.className?.equals(it)
+ } ?: false
}
/**
- * Temporary method for b/298186160
- * TODO (b/298186160) replace references with the constant itself when flag is removed
+ * Temporary method for b/298186160 TODO (b/298186160) replace references with the constant
+ * itself when flag is removed
*/
private fun cannedUnlockStartDelayMs(): Long {
return if (fasterUnlockTransition()) CANNED_UNLOCK_START_DELAY
- else LEGACY_CANNED_UNLOCK_START_DELAY
+ else LEGACY_CANNED_UNLOCK_START_DELAY
}
/**
- * Temporary method for b/298186160
- * TODO (b/298186160) replace references with the constant itself when flag is removed
+ * Temporary method for b/298186160 TODO (b/298186160) replace references with the constant
+ * itself when flag is removed
*/
private fun unlockAnimationDurationMs(): Long {
return if (fasterUnlockTransition()) UNLOCK_ANIMATION_DURATION_MS
- else LEGACY_UNLOCK_ANIMATION_DURATION_MS
+ else LEGACY_UNLOCK_ANIMATION_DURATION_MS
}
/**
- * Temporary method for b/298186160
- * TODO (b/298186160) replace references with the constant itself when flag is removed
+ * Temporary method for b/298186160 TODO (b/298186160) replace references with the constant
+ * itself when flag is removed
*/
private fun surfaceBehindFadeOutStartDelayMs(): Long {
return if (fasterUnlockTransition()) UNLOCK_ANIMATION_SURFACE_BEHIND_START_DELAY_MS
- else LEGACY_UNLOCK_ANIMATION_SURFACE_BEHIND_START_DELAY_MS
+ else LEGACY_UNLOCK_ANIMATION_SURFACE_BEHIND_START_DELAY_MS
}
-
companion object {
fun isFoldable(resources: Resources): Boolean {
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
index 6c53374..cc4a92c 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
@@ -187,70 +187,40 @@
}
}
- CharSequence dialogText = null;
- CharSequence dialogTitle = null;
-
final String appName = extractAppName(aInfo, packageManager);
final boolean hasCastingCapabilities =
Utils.isHeadlessRemoteDisplayProvider(packageManager, mPackageName);
- if (hasCastingCapabilities) {
- dialogText = getString(R.string.media_projection_sys_service_dialog_warning);
- dialogTitle = getString(R.string.media_projection_sys_service_dialog_title);
- } else {
- String actionText = getString(R.string.media_projection_dialog_warning, appName);
- SpannableString message = new SpannableString(actionText);
-
- int appNameIndex = actionText.indexOf(appName);
- if (appNameIndex >= 0) {
- message.setSpan(new StyleSpan(Typeface.BOLD),
- appNameIndex, appNameIndex + appName.length(), 0);
- }
- dialogText = message;
- dialogTitle = getString(R.string.media_projection_dialog_title, appName);
- }
-
// Using application context for the dialog, instead of the activity context, so we get
// the correct screen width when in split screen.
Context dialogContext = getApplicationContext();
- if (isPartialScreenSharingEnabled()) {
- final boolean overrideDisableSingleAppOption =
- CompatChanges.isChangeEnabled(
- OVERRIDE_DISABLE_MEDIA_PROJECTION_SINGLE_APP_OPTION,
- mPackageName, getHostUserHandle());
- MediaProjectionPermissionDialogDelegate delegate =
- new MediaProjectionPermissionDialogDelegate(
- dialogContext,
- getMediaProjectionConfig(),
- dialog -> {
- ScreenShareOption selectedOption =
- dialog.getSelectedScreenShareOption();
- grantMediaProjectionPermission(selectedOption.getMode());
- },
- () -> finish(RECORD_CANCEL, /* projection= */ null),
- hasCastingCapabilities,
- appName,
- overrideDisableSingleAppOption,
- mUid,
- mMediaProjectionMetricsLogger);
- mDialog =
- new AlertDialogWithDelegate(
- dialogContext, R.style.Theme_SystemUI_Dialog, delegate);
- } else {
- AlertDialog.Builder dialogBuilder =
- new AlertDialog.Builder(dialogContext, R.style.Theme_SystemUI_Dialog)
- .setTitle(dialogTitle)
- .setIcon(R.drawable.ic_media_projection_permission)
- .setMessage(dialogText)
- .setPositiveButton(R.string.media_projection_action_text, this)
- .setNeutralButton(android.R.string.cancel, this);
- mDialog = dialogBuilder.create();
- }
+ final boolean overrideDisableSingleAppOption =
+ CompatChanges.isChangeEnabled(
+ OVERRIDE_DISABLE_MEDIA_PROJECTION_SINGLE_APP_OPTION,
+ mPackageName, getHostUserHandle());
+ MediaProjectionPermissionDialogDelegate delegate =
+ new MediaProjectionPermissionDialogDelegate(
+ dialogContext,
+ getMediaProjectionConfig(),
+ dialog -> {
+ ScreenShareOption selectedOption =
+ dialog.getSelectedScreenShareOption();
+ grantMediaProjectionPermission(selectedOption.getMode());
+ },
+ () -> finish(RECORD_CANCEL, /* projection= */ null),
+ hasCastingCapabilities,
+ appName,
+ overrideDisableSingleAppOption,
+ mUid,
+ mMediaProjectionMetricsLogger);
+ mDialog =
+ new AlertDialogWithDelegate(
+ dialogContext, R.style.Theme_SystemUI_Dialog, delegate);
if (savedInstanceState == null) {
mMediaProjectionMetricsLogger.notifyProjectionInitiated(
mUid,
- appName == null
+ hasCastingCapabilities
? SessionCreationSource.CAST
: SessionCreationSource.APP);
}
@@ -366,7 +336,7 @@
setResult(RESULT_OK, intent);
finish(RECORD_CONTENT_DISPLAY, projection);
}
- if (isPartialScreenSharingEnabled() && screenShareMode == SINGLE_APP) {
+ if (screenShareMode == SINGLE_APP) {
IMediaProjection projection = MediaProjectionServiceHelper.createOrReuseProjection(
mUid, mPackageName, mReviewGrantedConsentRequired);
final Intent intent = new Intent(this,
@@ -437,8 +407,4 @@
return intent.getParcelableExtra(
MediaProjectionManager.EXTRA_MEDIA_PROJECTION_CONFIG);
}
-
- private boolean isPartialScreenSharingEnabled() {
- return mFeatureFlags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING);
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
index dd2dbf3..f8b3ce1 100644
--- a/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
@@ -35,7 +35,6 @@
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.flags.FeatureFlagsClassic
-import com.android.systemui.flags.Flags
import com.android.systemui.flags.Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES
import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger
import com.android.systemui.mediaprojection.SessionCreationSource
@@ -154,10 +153,7 @@
SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER
)
- if (
- flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING) &&
- !state.hasUserApprovedScreenRecording
- ) {
+ if (!state.hasUserApprovedScreenRecording) {
mainExecutor.execute {
ScreenCapturePermissionDialogDelegate(factory, state).createDialog().apply {
setOnCancelListener { screenRecordSwitch.isChecked = false }
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
index 46c5861..a8a78a9 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
@@ -171,11 +171,8 @@
mMediaProjectionMetricsLogger.notifyProjectionInitiated(
getHostUid(), SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER);
- return (flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)
- ? mScreenRecordPermissionDialogDelegateFactory
- .create(this, getHostUserHandle(), getHostUid(), onStartRecordingClicked)
- : mScreenRecordDialogFactory
- .create(this, onStartRecordingClicked))
+ return mScreenRecordPermissionDialogDelegateFactory
+ .create(this, getHostUserHandle(), getHostUid(), onStartRecordingClicked)
.createDialog();
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
index b468d0e..25d1cd1 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
@@ -39,6 +39,7 @@
import androidx.lifecycle.repeatOnLifecycle
import com.android.compose.theme.PlatformTheme
import com.android.internal.annotations.VisibleForTesting
+import com.android.systemui.Flags
import com.android.systemui.Flags.glanceableHubBackGesture
import com.android.systemui.ambient.touch.TouchMonitor
import com.android.systemui.ambient.touch.dagger.AmbientTouchComponent
@@ -294,24 +295,37 @@
}
containerView.systemGestureExclusionRects =
- listOf(
- // Only allow swipe up to bouncer and swipe down to shade in the very
- // top/bottom to avoid conflicting with widgets in the hub grid.
- Rect(
- insets.left,
- topEdgeSwipeRegionWidth,
- containerView.right - insets.right,
- containerView.bottom - bottomEdgeSwipeRegionWidth
- ),
- // Disable back gestures on the left side of the screen, to avoid
- // conflicting with scene transitions.
- Rect(
- 0,
- 0,
- insets.right,
- containerView.bottom,
+ if (Flags.hubmodeFullscreenVerticalSwipe()) {
+ listOf(
+ // Disable back gestures on the left side of the screen, to avoid
+ // conflicting with scene transitions.
+ Rect(
+ 0,
+ 0,
+ insets.right,
+ containerView.bottom,
+ )
)
- )
+ } else {
+ listOf(
+ // Only allow swipe up to bouncer and swipe down to shade in the very
+ // top/bottom to avoid conflicting with widgets in the hub grid.
+ Rect(
+ insets.left,
+ topEdgeSwipeRegionWidth,
+ containerView.right - insets.right,
+ containerView.bottom - bottomEdgeSwipeRegionWidth
+ ),
+ // Disable back gestures on the left side of the screen, to avoid
+ // conflicting with scene transitions.
+ Rect(
+ 0,
+ 0,
+ insets.right,
+ containerView.bottom,
+ )
+ )
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinator.kt
index a6605f6..a621b2a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinator.kt
@@ -18,12 +18,9 @@
import android.annotation.SuppressLint
import android.app.NotificationManager
-import android.os.UserHandle
-import android.provider.Settings
import androidx.annotation.VisibleForTesting
import com.android.systemui.Dumpable
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.shade.domain.interactor.ShadeInteractor
@@ -43,23 +40,16 @@
import com.android.systemui.statusbar.notification.stack.BUCKET_TOP_UNSEEN
import com.android.systemui.util.asIndenting
import com.android.systemui.util.printCollection
-import com.android.systemui.util.settings.SecureSettings
-import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
import java.io.PrintWriter
import javax.inject.Inject
import kotlin.time.Duration.Companion.seconds
-import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.collectLatest
-import kotlinx.coroutines.flow.conflate
-import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.launch
/**
@@ -73,12 +63,10 @@
class LockScreenMinimalismCoordinator
@Inject
constructor(
- @Background private val bgDispatcher: CoroutineDispatcher,
private val dumpManager: DumpManager,
private val headsUpInteractor: HeadsUpNotificationInteractor,
private val logger: LockScreenMinimalismCoordinatorLogger,
@Application private val scope: CoroutineScope,
- private val secureSettings: SecureSettings,
private val seenNotificationsInteractor: SeenNotificationsInteractor,
private val statusBarStateController: StatusBarStateController,
private val shadeInteractor: ShadeInteractor,
@@ -147,29 +135,7 @@
if (NotificationMinimalismPrototype.isEnabled) {
return flowOf(true)
}
- return secureSettings
- // emit whenever the setting has changed
- .observerFlow(
- UserHandle.USER_ALL,
- Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
- )
- // perform a query immediately
- .onStart { emit(Unit) }
- // for each change, lookup the new value
- .map {
- secureSettings.getIntForUser(
- name = Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
- def = 0,
- userHandle = UserHandle.USER_CURRENT,
- ) == 1
- }
- // don't emit anything if nothing has changed
- .distinctUntilChanged()
- // perform lookups on the bg thread pool
- .flowOn(bgDispatcher)
- // only track the most recent emission, if events are happening faster than they can be
- // consumed
- .conflate()
+ return seenNotificationsInteractor.isLockScreenShowOnlyUnseenNotificationsEnabled()
}
private suspend fun trackUnseenFilterSettingChanges() {
@@ -177,6 +143,7 @@
// update local field and invalidate if necessary
if (isSettingEnabled != unseenFilterEnabled) {
unseenFilterEnabled = isSettingEnabled
+ unseenNotifications.clear()
unseenNotifPromoter.invalidateList("unseen setting changed")
}
// if the setting is enabled, then start tracking and filtering unseen notifications
@@ -190,21 +157,21 @@
private val collectionListener =
object : NotifCollectionListener {
override fun onEntryAdded(entry: NotificationEntry) {
- if (!isShadeVisible) {
+ if (unseenFilterEnabled && !isShadeVisible) {
logger.logUnseenAdded(entry.key)
unseenNotifications.add(entry)
}
}
override fun onEntryUpdated(entry: NotificationEntry) {
- if (!isShadeVisible) {
+ if (unseenFilterEnabled && !isShadeVisible) {
logger.logUnseenUpdated(entry.key)
unseenNotifications.add(entry)
}
}
override fun onEntryRemoved(entry: NotificationEntry, reason: Int) {
- if (unseenNotifications.remove(entry)) {
+ if (unseenFilterEnabled && unseenNotifications.remove(entry)) {
logger.logUnseenRemoved(entry.key)
}
}
@@ -212,6 +179,7 @@
private fun pickOutTopUnseenNotifs(list: List<ListEntry>) {
if (NotificationMinimalismPrototype.isUnexpectedlyInLegacyMode()) return
+ if (!unseenFilterEnabled) return
// Only ever elevate a top unseen notification on keyguard, not even locked shade
if (statusBarStateController.state != StatusBarState.KEYGUARD) {
seenNotificationsInteractor.setTopOngoingNotification(null)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinator.kt
index 0103fff..bfea2ba 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinator.kt
@@ -17,12 +17,9 @@
package com.android.systemui.statusbar.notification.collection.coordinator
import android.annotation.SuppressLint
-import android.os.UserHandle
-import android.provider.Settings
import androidx.annotation.VisibleForTesting
import com.android.systemui.Dumpable
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dump.DumpManager
import com.android.systemui.keyguard.data.repository.KeyguardRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
@@ -43,12 +40,9 @@
import com.android.systemui.statusbar.policy.headsUpEvents
import com.android.systemui.util.asIndenting
import com.android.systemui.util.indentIfPossible
-import com.android.systemui.util.settings.SecureSettings
-import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
import java.io.PrintWriter
import javax.inject.Inject
import kotlin.time.Duration.Companion.seconds
-import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.coroutineScope
@@ -56,13 +50,10 @@
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.collectLatest
-import kotlinx.coroutines.flow.conflate
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.launch
import kotlinx.coroutines.yield
@@ -79,14 +70,12 @@
class OriginalUnseenKeyguardCoordinator
@Inject
constructor(
- @Background private val bgDispatcher: CoroutineDispatcher,
private val dumpManager: DumpManager,
private val headsUpManager: HeadsUpManager,
private val keyguardRepository: KeyguardRepository,
private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
private val logger: KeyguardCoordinatorLogger,
@Application private val scope: CoroutineScope,
- private val secureSettings: SecureSettings,
private val seenNotificationsInteractor: SeenNotificationsInteractor,
private val statusBarStateController: StatusBarStateController,
private val sceneInteractor: SceneInteractor,
@@ -268,29 +257,7 @@
// TODO(b/330387368): should this really just be turned off? If so, hide the setting.
return flowOf(false)
}
- return secureSettings
- // emit whenever the setting has changed
- .observerFlow(
- UserHandle.USER_ALL,
- Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
- )
- // perform a query immediately
- .onStart { emit(Unit) }
- // for each change, lookup the new value
- .map {
- secureSettings.getIntForUser(
- name = Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
- def = 0,
- userHandle = UserHandle.USER_CURRENT,
- ) == 1
- }
- // don't emit anything if nothing has changed
- .distinctUntilChanged()
- // perform lookups on the bg thread pool
- .flowOn(bgDispatcher)
- // only track the most recent emission, if events are happening faster than they can be
- // consumed
- .conflate()
+ return seenNotificationsInteractor.isLockScreenShowOnlyUnseenNotificationsEnabled()
}
private suspend fun trackUnseenFilterSettingChanges() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractor.kt
index 948a3c2..90a05ef 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractor.kt
@@ -16,21 +16,35 @@
package com.android.systemui.statusbar.notification.domain.interactor
+import android.os.UserHandle
+import android.provider.Settings
import android.util.IndentingPrintWriter
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
import com.android.systemui.statusbar.notification.shared.NotificationMinimalismPrototype
import com.android.systemui.util.printSection
+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.flow.Flow
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.conflate
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
/** Interactor for business logic associated with the notification stack. */
@SysUISingleton
class SeenNotificationsInteractor
@Inject
constructor(
+ @Background private val bgDispatcher: CoroutineDispatcher,
private val notificationListRepository: ActiveNotificationListRepository,
+ private val secureSettings: SecureSettings,
) {
/** Are any already-seen notifications currently filtered out of the shade? */
val hasFilteredOutSeenNotifications: StateFlow<Boolean> =
@@ -81,4 +95,29 @@
)
}
}
+
+ fun isLockScreenShowOnlyUnseenNotificationsEnabled(): Flow<Boolean> =
+ secureSettings
+ // emit whenever the setting has changed
+ .observerFlow(
+ UserHandle.USER_ALL,
+ Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
+ )
+ // perform a query immediately
+ .onStart { emit(Unit) }
+ // for each change, lookup the new value
+ .map {
+ secureSettings.getIntForUser(
+ name = Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
+ def = 0,
+ userHandle = UserHandle.USER_CURRENT,
+ ) == 1
+ }
+ // don't emit anything if nothing has changed
+ .distinctUntilChanged()
+ // perform lookups on the bg thread pool
+ .flowOn(bgDispatcher)
+ // only track the most recent emission, if events are happening faster than they can be
+ // consumed
+ .conflate()
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 8f2ad40..41b69a7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -802,7 +802,10 @@
hideAlternateBouncer(false);
if (mKeyguardStateController.isShowing() && !isBouncerShowing()) {
if (SceneContainerFlag.isEnabled()) {
- mDeviceEntryInteractorLazy.get().attemptDeviceEntry();
+ mSceneInteractorLazy.get().changeScene(
+ Scenes.Bouncer,
+ "primary bouncer requested"
+ );
} else {
mPrimaryBouncerInteractor.show(scrimmed);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
index a88c6d7..a963826 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
@@ -38,9 +38,10 @@
class AvalancheController
@Inject
constructor(
- dumpManager: DumpManager,
- private val uiEventLogger: UiEventLogger,
- @Background private val bgHandler: Handler
+ dumpManager: DumpManager,
+ private val uiEventLogger: UiEventLogger,
+ private val headsUpManagerLogger: HeadsUpManagerLogger,
+ @Background private val bgHandler: Handler
) : Dumpable {
private val tag = "AvalancheController"
@@ -109,32 +110,36 @@
}
/** Run or delay Runnable for given HeadsUpEntry */
- fun update(entry: HeadsUpEntry?, runnable: Runnable?, label: String) {
+ fun update(entry: HeadsUpEntry?, runnable: Runnable?, caller: String) {
+ val isEnabled = isEnabled()
+ val key = getKey(entry)
+
if (runnable == null) {
- log { "Runnable is NULL, stop update." }
+ headsUpManagerLogger.logAvalancheUpdate(caller, isEnabled, key, "Runnable NULL, stop")
return
}
- if (!isEnabled()) {
+ if (!isEnabled) {
+ headsUpManagerLogger.logAvalancheUpdate(caller, isEnabled, key,
+ "NOT ENABLED, run runnable")
runnable.run()
return
}
- log { "\n " }
- val fn = "$label => AvalancheController.update ${getKey(entry)}"
if (entry == null) {
- log { "Entry is NULL, stop update." }
+ headsUpManagerLogger.logAvalancheUpdate(caller, isEnabled, key, "Entry NULL, stop")
return
}
if (debug) {
- debugRunnableLabelMap[runnable] = label
+ debugRunnableLabelMap[runnable] = caller
}
+ var outcome = ""
if (isShowing(entry)) {
- log { "\n$fn => update showing" }
+ outcome = "update showing"
runnable.run()
} else if (entry in nextMap) {
- log { "\n$fn => update next" }
+ outcome = "update next"
nextMap[entry]?.add(runnable)
} else if (headsUpEntryShowing == null) {
- log { "\n$fn => showNow" }
+ outcome = "show now"
showNow(entry, arrayListOf(runnable))
} else {
// Clean up invalid state when entry is in list but not map and vice versa
@@ -156,7 +161,8 @@
)
}
}
- logState("after $fn")
+ outcome += getStateStr()
+ headsUpManagerLogger.logAvalancheUpdate(caller, isEnabled, key, outcome)
}
@VisibleForTesting
@@ -169,32 +175,37 @@
* Run or ignore Runnable for given HeadsUpEntry. If entry was never shown, ignore and delete
* all Runnables associated with that entry.
*/
- fun delete(entry: HeadsUpEntry?, runnable: Runnable?, label: String) {
+ fun delete(entry: HeadsUpEntry?, runnable: Runnable?, caller: String) {
+ val isEnabled = isEnabled()
+ val key = getKey(entry)
+
if (runnable == null) {
- log { "Runnable is NULL, stop delete." }
+ headsUpManagerLogger.logAvalancheDelete(caller, isEnabled, key, "Runnable NULL, stop")
return
}
- if (!isEnabled()) {
+ if (!isEnabled) {
+ headsUpManagerLogger.logAvalancheDelete(caller, isEnabled, key,
+ "NOT ENABLED, run runnable")
runnable.run()
return
}
- log { "\n " }
- val fn = "$label => AvalancheController.delete " + getKey(entry)
if (entry == null) {
- log { "$fn => entry NULL, running runnable" }
+ headsUpManagerLogger.logAvalancheDelete(caller, isEnabled, key,
+ "Entry NULL, run runnable")
runnable.run()
return
}
+ var outcome = ""
if (entry in nextMap) {
- log { "$fn => remove from next" }
+ outcome = "remove from next"
if (entry in nextMap) nextMap.remove(entry)
if (entry in nextList) nextList.remove(entry)
uiEventLogger.log(ThrottleEvent.AVALANCHE_THROTTLING_HUN_REMOVED)
} else if (entry in debugDropSet) {
- log { "$fn => remove from dropset" }
+ outcome = "remove from dropset"
debugDropSet.remove(entry)
} else if (isShowing(entry)) {
- log { "$fn => remove showing ${getKey(entry)}" }
+ outcome = "remove showing"
previousHunKey = getKey(headsUpEntryShowing)
// Show the next HUN before removing this one, so that we don't tell listeners
// onHeadsUpPinnedModeChanged, which causes
@@ -203,10 +214,10 @@
showNext()
runnable.run()
} else {
- log { "$fn => run runnable for untracked shown ${getKey(entry)}" }
+ outcome = "run runnable for untracked shown"
runnable.run()
}
- logState("after $fn")
+ headsUpManagerLogger.logAvalancheDelete(caller, isEnabled(), getKey(entry), outcome)
}
/**
@@ -384,23 +395,12 @@
}
private fun getStateStr(): String {
- return "SHOWING: [${getKey(headsUpEntryShowing)}]" +
- "\nPREVIOUS: [$previousHunKey]" +
- "\nNEXT LIST: $nextListStr" +
- "\nNEXT MAP: $nextMapStr" +
- "\nDROPPED: $dropSetStr" +
- "\nENABLED: $enableAtRuntime"
- }
-
- private fun logState(reason: String) {
- log {
- "\n================================================================================="
- }
- log { "STATE $reason" }
- log { getStateStr() }
- log {
- "=================================================================================\n"
- }
+ return "\navalanche state:" +
+ "\n\tshowing: [${getKey(headsUpEntryShowing)}]" +
+ "\n\tprevious: [$previousHunKey]" +
+ "\n\tnext list: $nextListStr" +
+ "\n\tnext map: $nextMapStr" +
+ "\n\tdropped: $dropSetStr"
}
private val dropSetStr: String
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt
index 6ffb162..80c595f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt
@@ -66,6 +66,30 @@
})
}
+ fun logAvalancheUpdate(caller: String, isEnabled: Boolean, notifEntryKey: String,
+ outcome: String) {
+ buffer.log(TAG, INFO, {
+ str1 = caller
+ str2 = notifEntryKey
+ str3 = outcome
+ bool1 = isEnabled
+ }, {
+ "$str1\n\t=> AC[isEnabled:$bool1] update: $str2\n\t=> $str3"
+ })
+ }
+
+ fun logAvalancheDelete(caller: String, isEnabled: Boolean, notifEntryKey: String,
+ outcome: String) {
+ buffer.log(TAG, INFO, {
+ str1 = caller
+ str2 = notifEntryKey
+ str3 = outcome
+ bool1 = isEnabled
+ }, {
+ "$str1\n\t=> AC[isEnabled:$bool1] delete: $str2\n\t=> $str3"
+ })
+ }
+
fun logShowNotification(entry: NotificationEntry) {
buffer.log(TAG, INFO, {
str1 = entry.logKey
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt
index f726aae..e251ab5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt
@@ -29,6 +29,7 @@
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argThat
import com.android.systemui.util.mockito.whenever
+import java.util.function.Predicate
import junit.framework.Assert.assertEquals
import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertTrue
@@ -46,7 +47,6 @@
import org.mockito.Mockito.verifyNoMoreInteractions
import org.mockito.MockitoAnnotations
import org.mockito.kotlin.clearInvocations
-import java.util.function.Predicate
@RunWith(AndroidJUnit4::class)
@RunWithLooper
@@ -54,70 +54,134 @@
class KeyguardUnlockAnimationControllerTest : SysuiTestCase() {
private lateinit var keyguardUnlockAnimationController: KeyguardUnlockAnimationController
- @Mock
- private lateinit var windowManager: WindowManager
- @Mock
- private lateinit var keyguardViewMediator: KeyguardViewMediator
- @Mock
- private lateinit var keyguardStateController: KeyguardStateController
- @Mock
- private lateinit var keyguardViewController: KeyguardViewController
- @Mock
- private lateinit var featureFlags: FeatureFlags
- @Mock
- private lateinit var biometricUnlockController: BiometricUnlockController
- @Mock
- private lateinit var surfaceTransactionApplier: SyncRtSurfaceTransactionApplier
- @Mock
- private lateinit var statusBarStateController: SysuiStatusBarStateController
- @Mock
- private lateinit var notificationShadeWindowController: NotificationShadeWindowController
- @Mock
- private lateinit var powerManager: PowerManager
- @Mock
- private lateinit var wallpaperManager: WallpaperManager
+ @Mock private lateinit var windowManager: WindowManager
+ @Mock private lateinit var keyguardViewMediator: KeyguardViewMediator
+ @Mock private lateinit var keyguardStateController: KeyguardStateController
+ @Mock private lateinit var keyguardViewController: KeyguardViewController
+ @Mock private lateinit var featureFlags: FeatureFlags
+ @Mock private lateinit var biometricUnlockController: BiometricUnlockController
+ @Mock private lateinit var surfaceTransactionApplier: SyncRtSurfaceTransactionApplier
+ @Mock private lateinit var statusBarStateController: SysuiStatusBarStateController
+ @Mock private lateinit var notificationShadeWindowController: NotificationShadeWindowController
+ @Mock private lateinit var powerManager: PowerManager
+ @Mock private lateinit var wallpaperManager: WallpaperManager
@Mock
private lateinit var launcherUnlockAnimationController: ILauncherUnlockAnimationController.Stub
private var surfaceControl1 = mock(SurfaceControl::class.java)
- private var remoteTarget1 = RemoteAnimationTarget(
- 0 /* taskId */, 0, surfaceControl1, false, Rect(), Rect(), 0, Point(), Rect(), Rect(),
- mock(WindowConfiguration::class.java), false, surfaceControl1, Rect(),
- mock(ActivityManager.RunningTaskInfo::class.java), false)
+ private var remoteTarget1 =
+ RemoteAnimationTarget(
+ 0 /* taskId */,
+ 0,
+ surfaceControl1,
+ false,
+ Rect(),
+ Rect(),
+ 0,
+ Point(),
+ Rect(),
+ Rect(),
+ mock(WindowConfiguration::class.java),
+ false,
+ surfaceControl1,
+ Rect(),
+ mock(ActivityManager.RunningTaskInfo::class.java),
+ false
+ )
private var surfaceControl2 = mock(SurfaceControl::class.java)
- private var remoteTarget2 = RemoteAnimationTarget(
- 1 /* taskId */, 0, surfaceControl2, false, Rect(), Rect(), 0, Point(), Rect(), Rect(),
- mock(WindowConfiguration::class.java), false, surfaceControl2, Rect(),
- mock(ActivityManager.RunningTaskInfo::class.java), false)
+ private var remoteTarget2 =
+ RemoteAnimationTarget(
+ 1 /* taskId */,
+ 0,
+ surfaceControl2,
+ false,
+ Rect(),
+ Rect(),
+ 0,
+ Point(),
+ Rect(),
+ Rect(),
+ mock(WindowConfiguration::class.java),
+ false,
+ surfaceControl2,
+ Rect(),
+ mock(ActivityManager.RunningTaskInfo::class.java),
+ false
+ )
private lateinit var remoteAnimationTargets: Array<RemoteAnimationTarget>
private var surfaceControlWp = mock(SurfaceControl::class.java)
- private var wallpaperTarget = RemoteAnimationTarget(
- 2 /* taskId */, 0, surfaceControlWp, false, Rect(), Rect(), 0, Point(), Rect(), Rect(),
- mock(WindowConfiguration::class.java), false, surfaceControlWp, Rect(),
- mock(ActivityManager.RunningTaskInfo::class.java), false)
+ private var wallpaperTarget =
+ RemoteAnimationTarget(
+ 2 /* taskId */,
+ 0,
+ surfaceControlWp,
+ false,
+ Rect(),
+ Rect(),
+ 0,
+ Point(),
+ Rect(),
+ Rect(),
+ mock(WindowConfiguration::class.java),
+ false,
+ surfaceControlWp,
+ Rect(),
+ mock(ActivityManager.RunningTaskInfo::class.java),
+ false
+ )
private lateinit var wallpaperTargets: Array<RemoteAnimationTarget>
private var surfaceControlLockWp = mock(SurfaceControl::class.java)
- private var lockWallpaperTarget = RemoteAnimationTarget(
- 3 /* taskId */, 0, surfaceControlLockWp, false, Rect(), Rect(), 0, Point(), Rect(),
- Rect(), mock(WindowConfiguration::class.java), false, surfaceControlLockWp,
- Rect(), mock(ActivityManager.RunningTaskInfo::class.java), false)
+ private var lockWallpaperTarget =
+ RemoteAnimationTarget(
+ 3 /* taskId */,
+ 0,
+ surfaceControlLockWp,
+ false,
+ Rect(),
+ Rect(),
+ 0,
+ Point(),
+ Rect(),
+ Rect(),
+ mock(WindowConfiguration::class.java),
+ false,
+ surfaceControlLockWp,
+ Rect(),
+ mock(ActivityManager.RunningTaskInfo::class.java),
+ false
+ )
private lateinit var lockWallpaperTargets: Array<RemoteAnimationTarget>
+ private var shouldPerformSmartspaceTransition = false
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- keyguardUnlockAnimationController = KeyguardUnlockAnimationController(
- windowManager, context.resources,
- keyguardStateController, { keyguardViewMediator }, keyguardViewController,
- featureFlags, { biometricUnlockController }, statusBarStateController,
- notificationShadeWindowController, powerManager, wallpaperManager
- )
+ keyguardUnlockAnimationController =
+ object :
+ KeyguardUnlockAnimationController(
+ windowManager,
+ context.resources,
+ keyguardStateController,
+ { keyguardViewMediator },
+ keyguardViewController,
+ featureFlags,
+ { biometricUnlockController },
+ statusBarStateController,
+ notificationShadeWindowController,
+ powerManager,
+ wallpaperManager
+ ) {
+ override fun shouldPerformSmartspaceTransition(): Boolean =
+ shouldPerformSmartspaceTransition
+ }
keyguardUnlockAnimationController.setLauncherUnlockController(
- "", launcherUnlockAnimationController)
+ "",
+ launcherUnlockAnimationController
+ )
whenever(keyguardViewController.viewRootImpl).thenReturn(mock(ViewRootImpl::class.java))
whenever(powerManager.isInteractive).thenReturn(true)
@@ -159,8 +223,8 @@
)
val captorSb = ArgThatCaptor<SyncRtSurfaceTransactionApplier.SurfaceParams>()
- verify(surfaceTransactionApplier, times(1)).scheduleApply(
- captorSb.capture { sp -> sp.surface == surfaceControl1 })
+ verify(surfaceTransactionApplier, times(1))
+ .scheduleApply(captorSb.capture { sp -> sp.surface == surfaceControl1 })
val params = captorSb.getLastValue()
@@ -171,15 +235,13 @@
// Also expect we've immediately asked the keyguard view mediator to finish the remote
// animation.
- verify(keyguardViewMediator, times(1)).exitKeyguardAndFinishSurfaceBehindRemoteAnimation(
- false /* cancelled */)
+ verify(keyguardViewMediator, times(1))
+ .exitKeyguardAndFinishSurfaceBehindRemoteAnimation(false /* cancelled */)
verifyNoMoreInteractions(surfaceTransactionApplier)
}
- /**
- * If we are not wake and unlocking, we expect the unlock animation to play normally.
- */
+ /** If we are not wake and unlocking, we expect the unlock animation to play normally. */
@Test
fun surfaceAnimation_ifNotWakeAndUnlocking() {
whenever(biometricUnlockController.isWakeAndUnlock).thenReturn(false)
@@ -193,18 +255,18 @@
)
// Since the animation is running, we should not have finished the remote animation.
- verify(keyguardViewMediator, times(0)).exitKeyguardAndFinishSurfaceBehindRemoteAnimation(
- false /* cancelled */)
+ verify(keyguardViewMediator, times(0))
+ .exitKeyguardAndFinishSurfaceBehindRemoteAnimation(false /* cancelled */)
}
@Test
fun onWakeAndUnlock_notifiesListenerWithTrue() {
whenever(biometricUnlockController.isWakeAndUnlock).thenReturn(true)
- whenever(biometricUnlockController.mode).thenReturn(
- BiometricUnlockController.MODE_WAKE_AND_UNLOCK)
+ whenever(biometricUnlockController.mode)
+ .thenReturn(BiometricUnlockController.MODE_WAKE_AND_UNLOCK)
- val listener = mock(
- KeyguardUnlockAnimationController.KeyguardUnlockAnimationListener::class.java)
+ val listener =
+ mock(KeyguardUnlockAnimationController.KeyguardUnlockAnimationListener::class.java)
keyguardUnlockAnimationController.addKeyguardUnlockAnimationListener(listener)
keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation(
@@ -221,11 +283,11 @@
@Test
fun onWakeAndUnlockFromDream_notifiesListenerWithFalse() {
whenever(biometricUnlockController.isWakeAndUnlock).thenReturn(true)
- whenever(biometricUnlockController.mode).thenReturn(
- BiometricUnlockController.MODE_WAKE_AND_UNLOCK_FROM_DREAM)
+ whenever(biometricUnlockController.mode)
+ .thenReturn(BiometricUnlockController.MODE_WAKE_AND_UNLOCK_FROM_DREAM)
- val listener = mock(
- KeyguardUnlockAnimationController.KeyguardUnlockAnimationListener::class.java)
+ val listener =
+ mock(KeyguardUnlockAnimationController.KeyguardUnlockAnimationListener::class.java)
keyguardUnlockAnimationController.addKeyguardUnlockAnimationListener(listener)
keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation(
@@ -269,8 +331,8 @@
* keyguard. This means this was a swipe to dismiss gesture but the user flung the keyguard and
* lifted their finger while we were requesting the surface be made visible.
*
- * In this case, we should verify that we are playing the canned unlock animation and not
- * simply fading in the surface.
+ * In this case, we should verify that we are playing the canned unlock animation and not simply
+ * fading in the surface.
*/
@Test
fun playCannedUnlockAnimation_ifRequestedShowSurface_andFlinging() {
@@ -293,8 +355,8 @@
* ever happened and we're just playing the simple canned animation (happens via UDFPS unlock,
* long press on the lock icon, etc).
*
- * In this case, we should verify that we are playing the canned unlock animation and not
- * simply fading in the surface.
+ * In this case, we should verify that we are playing the canned unlock animation and not simply
+ * fading in the surface.
*/
@Test
fun playCannedUnlockAnimation_ifDidNotRequestShowSurface() {
@@ -332,11 +394,11 @@
keyguardUnlockAnimationController.willUnlockWithInWindowLauncherAnimations = true
keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation(
- remoteAnimationTargets,
- wallpaperTargets,
- arrayOf(),
- 0 /* startTime */,
- false /* requestedShowSurfaceBehindKeyguard */
+ remoteAnimationTargets,
+ wallpaperTargets,
+ arrayOf(),
+ 0 /* startTime */,
+ false /* requestedShowSurfaceBehindKeyguard */
)
assertTrue(keyguardUnlockAnimationController.isPlayingCannedUnlockAnimation())
@@ -353,11 +415,11 @@
var lastFadeOutAlpha = -1f
keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation(
- arrayOf(remoteTarget1, remoteTarget2),
- wallpaperTargets,
- lockWallpaperTargets,
- 0 /* startTime */,
- false /* requestedShowSurfaceBehindKeyguard */
+ arrayOf(remoteTarget1, remoteTarget2),
+ wallpaperTargets,
+ lockWallpaperTargets,
+ 0 /* startTime */,
+ false /* requestedShowSurfaceBehindKeyguard */
)
for (i in 0..10) {
@@ -367,19 +429,22 @@
keyguardUnlockAnimationController.setSurfaceBehindAppearAmount(amount)
val captorSb = ArgThatCaptor<SyncRtSurfaceTransactionApplier.SurfaceParams>()
- verify(surfaceTransactionApplier, times(2)).scheduleApply(
+ verify(surfaceTransactionApplier, times(2))
+ .scheduleApply(
captorSb.capture { sp ->
- sp.surface == surfaceControlWp || sp.surface == surfaceControlLockWp })
+ sp.surface == surfaceControlWp || sp.surface == surfaceControlLockWp
+ }
+ )
val fadeInAlpha = captorSb.getLastValue { it.surface == surfaceControlWp }.alpha
val fadeOutAlpha = captorSb.getLastValue { it.surface == surfaceControlLockWp }.alpha
if (amount == 0f) {
- assertTrue (fadeInAlpha == 0f)
- assertTrue (fadeOutAlpha == 1f)
+ assertTrue(fadeInAlpha == 0f)
+ assertTrue(fadeOutAlpha == 1f)
} else if (amount == 1f) {
- assertTrue (fadeInAlpha == 1f)
- assertTrue (fadeOutAlpha == 0f)
+ assertTrue(fadeInAlpha == 1f)
+ assertTrue(fadeOutAlpha == 0f)
} else {
assertTrue(fadeInAlpha >= lastFadeInAlpha)
assertTrue(fadeOutAlpha <= lastFadeOutAlpha)
@@ -389,18 +454,16 @@
}
}
- /**
- * If we are not wake and unlocking, we expect the unlock animation to play normally.
- */
+ /** If we are not wake and unlocking, we expect the unlock animation to play normally. */
@Test
@DisableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
fun surfaceAnimation_multipleTargets() {
keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation(
- arrayOf(remoteTarget1, remoteTarget2),
- wallpaperTargets,
- arrayOf(),
- 0 /* startTime */,
- false /* requestedShowSurfaceBehindKeyguard */
+ arrayOf(remoteTarget1, remoteTarget2),
+ wallpaperTargets,
+ arrayOf(),
+ 0 /* startTime */,
+ false /* requestedShowSurfaceBehindKeyguard */
)
// Cancel the animator so we can verify only the setSurfaceBehind call below.
@@ -412,12 +475,18 @@
keyguardUnlockAnimationController.setSurfaceBehindAppearAmount(0.5f)
val captorSb = ArgThatCaptor<SyncRtSurfaceTransactionApplier.SurfaceParams>()
- verify(surfaceTransactionApplier, times(2)).scheduleApply(captorSb
- .capture { sp -> sp.surface == surfaceControl1 || sp.surface == surfaceControl2 })
+ verify(surfaceTransactionApplier, times(2))
+ .scheduleApply(
+ captorSb.capture { sp ->
+ sp.surface == surfaceControl1 || sp.surface == surfaceControl2
+ }
+ )
val captorWp = ArgThatCaptor<SyncRtSurfaceTransactionApplier.SurfaceParams>()
- verify(surfaceTransactionApplier, times(1).description(
- "WallpaperSurface was expected to receive scheduleApply once"
- )).scheduleApply(captorWp.capture { sp -> sp.surface == surfaceControlWp})
+ verify(
+ surfaceTransactionApplier,
+ times(1).description("WallpaperSurface was expected to receive scheduleApply once")
+ )
+ .scheduleApply(captorWp.capture { sp -> sp.surface == surfaceControlWp })
val allParams = captorSb.getAllValues()
@@ -432,8 +501,8 @@
assertTrue(remainingTargets.isEmpty())
// Since the animation is running, we should not have finished the remote animation.
- verify(keyguardViewMediator, times(0)).exitKeyguardAndFinishSurfaceBehindRemoteAnimation(
- false /* cancelled */)
+ verify(keyguardViewMediator, times(0))
+ .exitKeyguardAndFinishSurfaceBehindRemoteAnimation(false /* cancelled */)
}
@Test
@@ -442,11 +511,11 @@
whenever(powerManager.isInteractive).thenReturn(false)
keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation(
- remoteAnimationTargets,
- wallpaperTargets,
- arrayOf(),
- 0 /* startTime */,
- false /* requestedShowSurfaceBehindKeyguard */
+ remoteAnimationTargets,
+ wallpaperTargets,
+ arrayOf(),
+ 0 /* startTime */,
+ false /* requestedShowSurfaceBehindKeyguard */
)
// Cancel the animator so we can verify only the setSurfaceBehind call below.
@@ -457,12 +526,14 @@
keyguardUnlockAnimationController.setWallpaperAppearAmount(1f, wallpaperTargets)
val captorSb = ArgThatCaptor<SyncRtSurfaceTransactionApplier.SurfaceParams>()
- verify(surfaceTransactionApplier, times(1)).scheduleApply(
- captorSb.capture { sp -> sp.surface == surfaceControl1})
+ verify(surfaceTransactionApplier, times(1))
+ .scheduleApply(captorSb.capture { sp -> sp.surface == surfaceControl1 })
val captorWp = ArgThatCaptor<SyncRtSurfaceTransactionApplier.SurfaceParams>()
- verify(surfaceTransactionApplier, atLeastOnce().description("Wallpaper surface has not " +
- "received scheduleApply")).scheduleApply(
- captorWp.capture { sp -> sp.surface == surfaceControlWp })
+ verify(
+ surfaceTransactionApplier,
+ atLeastOnce().description("Wallpaper surface has not " + "received scheduleApply")
+ )
+ .scheduleApply(captorWp.capture { sp -> sp.surface == surfaceControlWp })
val params = captorSb.getLastValue()
@@ -479,11 +550,11 @@
whenever(powerManager.isInteractive).thenReturn(true)
keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation(
- remoteAnimationTargets,
- wallpaperTargets,
- arrayOf(),
- 0 /* startTime */,
- false /* requestedShowSurfaceBehindKeyguard */
+ remoteAnimationTargets,
+ wallpaperTargets,
+ arrayOf(),
+ 0 /* startTime */,
+ false /* requestedShowSurfaceBehindKeyguard */
)
// Stop the animator - we just want to test whether the override is not applied.
@@ -494,24 +565,31 @@
keyguardUnlockAnimationController.setWallpaperAppearAmount(1f, wallpaperTargets)
val captorSb = ArgThatCaptor<SyncRtSurfaceTransactionApplier.SurfaceParams>()
- verify(surfaceTransactionApplier, times(1)).scheduleApply(
- captorSb.capture { sp -> sp.surface == surfaceControl1 })
+ verify(surfaceTransactionApplier, times(1))
+ .scheduleApply(captorSb.capture { sp -> sp.surface == surfaceControl1 })
val captorWp = ArgThatCaptor<SyncRtSurfaceTransactionApplier.SurfaceParams>()
- verify(surfaceTransactionApplier, atLeastOnce().description("Wallpaper surface has not " +
- "received scheduleApply")).scheduleApply(
- captorWp.capture { sp -> sp.surface == surfaceControlWp })
+ verify(
+ surfaceTransactionApplier,
+ atLeastOnce().description("Wallpaper surface has not " + "received scheduleApply")
+ )
+ .scheduleApply(captorWp.capture { sp -> sp.surface == surfaceControlWp })
val params = captorSb.getLastValue()
assertEquals(1f, params.alpha)
assertTrue(params.matrix.isIdentity)
- assertEquals("Wallpaper surface was expected to have opacity 1",
- 1f, captorWp.getLastValue().alpha)
+ assertEquals(
+ "Wallpaper surface was expected to have opacity 1",
+ 1f,
+ captorWp.getLastValue().alpha
+ )
verifyNoMoreInteractions(surfaceTransactionApplier)
}
@Test
- fun unlockToLauncherWithInWindowAnimations_ssViewIsVisible() {
+ fun unlockToLauncherWithInWindowAnimations_ssViewInVisible_whenPerformSSTransition() {
+ shouldPerformSmartspaceTransition = true
+
val mockLockscreenSmartspaceView = mock(View::class.java)
whenever(mockLockscreenSmartspaceView.visibility).thenReturn(View.VISIBLE)
keyguardUnlockAnimationController.lockscreenSmartspace = mockLockscreenSmartspaceView
@@ -522,6 +600,19 @@
}
@Test
+ fun unlockToLauncherWithInWindowAnimations_ssViewVisible_whenNotPerformSSTransition() {
+ shouldPerformSmartspaceTransition = false
+
+ val mockLockscreenSmartspaceView = mock(View::class.java)
+ whenever(mockLockscreenSmartspaceView.visibility).thenReturn(View.VISIBLE)
+ keyguardUnlockAnimationController.lockscreenSmartspace = mockLockscreenSmartspaceView
+
+ keyguardUnlockAnimationController.unlockToLauncherWithInWindowAnimations()
+
+ verify(mockLockscreenSmartspaceView, never()).visibility = View.INVISIBLE
+ }
+
+ @Test
fun unlockToLauncherWithInWindowAnimations_ssViewIsInvisible() {
val mockLockscreenSmartspaceView = mock(View::class.java)
whenever(mockLockscreenSmartspaceView.visibility).thenReturn(View.INVISIBLE)
@@ -591,7 +682,7 @@
private var allArgs: MutableList<T> = mutableListOf()
fun capture(predicate: Predicate<T>): T {
- return argThat{x: T ->
+ return argThat { x: T ->
if (predicate.test(x)) {
allArgs.add(x)
return@argThat true
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt
index 7dd8028..f884b87 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt
@@ -25,8 +25,6 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.flags.FeatureFlagsClassic
-import com.android.systemui.flags.Flags
import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger
import com.android.systemui.res.R
import com.android.systemui.statusbar.phone.AlertDialogWithDelegate
@@ -34,10 +32,8 @@
import com.android.systemui.util.mockito.mock
import junit.framework.Assert.assertEquals
import org.junit.After
-import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.Mockito.`when` as whenever
@SmallTest
@RunWith(AndroidJUnit4::class)
@@ -46,7 +42,6 @@
private lateinit var dialog: AlertDialog
- private val flags = mock<FeatureFlagsClassic>()
private val appName = "Test App"
private val resIdSingleApp = R.string.screen_share_permission_dialog_option_single_app
@@ -54,11 +49,6 @@
private val resIdSingleAppDisabled =
R.string.media_projection_entry_app_permission_dialog_single_app_disabled
- @Before
- fun setUp() {
- whenever(flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)).thenReturn(true)
- }
-
@After
fun teardown() {
if (::dialog.isInitialized) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
index ce1a885..8d84c3e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
@@ -177,7 +177,6 @@
.thenReturn(false)
whenever(devicePolicyResolver.isScreenCaptureCompletelyDisabled(any<UserHandle>()))
.thenReturn(false)
- whenever(flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)).thenReturn(true)
whenever(state.hasUserApprovedScreenRecording).thenReturn(false)
val screenRecordSwitch = dialog.requireViewById<Switch>(R.id.screenrecord_switch)
@@ -200,7 +199,6 @@
.thenReturn(false)
whenever(devicePolicyResolver.isScreenCaptureCompletelyDisabled(any<UserHandle>()))
.thenReturn(false)
- whenever(flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)).thenReturn(false)
val screenRecordSwitch = dialog.requireViewById<Switch>(R.id.screenrecord_switch)
screenRecordSwitch.isChecked = true
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
index 477c50b..6b16e78 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
@@ -39,10 +39,8 @@
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
-import androidx.annotation.Nullable;
import androidx.test.filters.SmallTest;
-import com.android.systemui.Dependency;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.DialogTransitionAnimator;
import com.android.systemui.broadcast.BroadcastDispatcher;
@@ -52,12 +50,9 @@
import com.android.systemui.mediaprojection.SessionCreationSource;
import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver;
import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialogDelegate;
-import com.android.systemui.model.SysUiState;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.settings.UserTracker;
-import com.android.systemui.statusbar.phone.DialogDelegate;
import com.android.systemui.statusbar.phone.SystemUIDialog;
-import com.android.systemui.statusbar.phone.SystemUIDialogManager;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;
@@ -110,7 +105,6 @@
private FakeFeatureFlags mFeatureFlags;
private RecordingController mController;
- private TestSystemUIDialogFactory mDialogFactory;
private static final int USER_ID = 10;
@@ -120,14 +114,6 @@
Context spiedContext = spy(mContext);
when(spiedContext.getUserId()).thenReturn(TEST_USER_ID);
- mDialogFactory = new TestSystemUIDialogFactory(
- mContext,
- Dependency.get(SystemUIDialogManager.class),
- Dependency.get(SysUiState.class),
- Dependency.get(BroadcastDispatcher.class),
- Dependency.get(DialogTransitionAnimator.class)
- );
-
mFeatureFlags = new FakeFeatureFlags();
when(mScreenCaptureDisabledDialogDelegate.createSysUIDialog())
.thenReturn(mScreenCaptureDisabledDialog);
@@ -251,7 +237,6 @@
@Test
public void testPoliciesFlagDisabled_screenCapturingNotAllowed_returnsNullDevicePolicyDialog() {
- mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING, true);
mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES, false);
when(mDevicePolicyResolver.isScreenCaptureCompletelyDisabled((any()))).thenReturn(true);
@@ -269,19 +254,7 @@
}
@Test
- public void testPartialScreenSharingDisabled_returnsLegacyDialog() {
- mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING, false);
- mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES, false);
-
- Dialog dialog = mController.createScreenRecordDialog(mContext, mFeatureFlags,
- mDialogTransitionAnimator, mActivityStarter, /* onStartRecordingClicked= */ null);
-
- assertThat(dialog).isEqualTo(mScreenRecordSystemUIDialog);
- }
-
- @Test
public void testPoliciesFlagEnabled_screenCapturingNotAllowed_returnsDevicePolicyDialog() {
- mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING, true);
mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES, true);
when(mDevicePolicyResolver.isScreenCaptureCompletelyDisabled((any()))).thenReturn(true);
@@ -293,7 +266,6 @@
@Test
public void testPoliciesFlagEnabled_screenCapturingAllowed_returnsNullDevicePolicyDialog() {
- mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING, true);
mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES, true);
when(mDevicePolicyResolver.isScreenCaptureCompletelyDisabled((any()))).thenReturn(false);
@@ -312,7 +284,6 @@
@Test
public void testPoliciesFlagEnabled_screenCapturingAllowed_logsProjectionInitiated() {
- mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING, true);
mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES, true);
when(mDevicePolicyResolver.isScreenCaptureCompletelyDisabled((any()))).thenReturn(false);
@@ -324,32 +295,4 @@
/* hostUid= */ myUid(),
SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER);
}
-
- private static class TestSystemUIDialogFactory extends SystemUIDialog.Factory {
-
- @Nullable private DialogDelegate<SystemUIDialog> mLastDelegate;
- @Nullable private SystemUIDialog mLastCreatedDialog;
-
- TestSystemUIDialogFactory(
- Context context,
- SystemUIDialogManager systemUIDialogManager,
- SysUiState sysUiState,
- BroadcastDispatcher broadcastDispatcher,
- DialogTransitionAnimator dialogTransitionAnimator) {
- super(
- context,
- systemUIDialogManager,
- sysUiState,
- broadcastDispatcher,
- dialogTransitionAnimator);
- }
-
- @Override
- public SystemUIDialog create(SystemUIDialog.Delegate delegate) {
- SystemUIDialog dialog = super.create(delegate);
- mLastDelegate = delegate;
- mLastCreatedDialog = dialog;
- return dialog;
- }
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt
index cc8d7d5..11b0bdf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt
@@ -28,7 +28,6 @@
import com.android.systemui.animation.DialogTransitionAnimator
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger
import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorActivity
import com.android.systemui.mediaprojection.permission.ENTIRE_SCREEN
@@ -51,7 +50,6 @@
import org.mockito.Mockito.eq
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
@SmallTest
@@ -72,8 +70,6 @@
fun setUp() {
MockitoAnnotations.initMocks(this)
- whenever(flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)).thenReturn(true)
-
val systemUIDialogFactory =
SystemUIDialog.Factory(
context,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractorTest.kt
deleted file mode 100644
index 3908529..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractorTest.kt
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
- * except in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
- *
- */
-
-package com.android.systemui.statusbar.notification.domain.interactor
-
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.test.runTest
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(AndroidJUnit4::class)
-@SmallTest
-class SeenNotificationsInteractorTest : SysuiTestCase() {
-
- private val repository = ActiveNotificationListRepository()
- private val underTest = SeenNotificationsInteractor(repository)
-
- @Test
- fun testNoFilteredOutSeenNotifications() = runTest {
- val hasFilteredOutSeenNotifications by
- collectLastValue(underTest.hasFilteredOutSeenNotifications)
-
- underTest.setHasFilteredOutSeenNotifications(false)
-
- assertThat(hasFilteredOutSeenNotifications).isFalse()
- }
-
- @Test
- fun testHasFilteredOutSeenNotifications() = runTest {
- val hasFilteredOutSeenNotifications by
- collectLastValue(underTest.hasFilteredOutSeenNotifications)
-
- underTest.setHasFilteredOutSeenNotifications(true)
-
- assertThat(hasFilteredOutSeenNotifications).isTrue()
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
index 1060b62..c36a046 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
@@ -91,7 +91,6 @@
import com.android.systemui.statusbar.notification.collection.render.NotifStats;
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController;
-import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository;
import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor;
import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor;
import com.android.systemui.statusbar.notification.init.NotificationsController;
@@ -188,12 +187,8 @@
@Captor
private ArgumentCaptor<StatusBarStateController.StateListener> mStateListenerArgumentCaptor;
-
- private final ActiveNotificationListRepository mActiveNotificationsRepository =
- new ActiveNotificationListRepository();
-
private final SeenNotificationsInteractor mSeenNotificationsInteractor =
- new SeenNotificationsInteractor(mActiveNotificationsRepository);
+ mKosmos.getSeenNotificationsInteractor();
private NotificationStackScrollLayoutController mController;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index 31f93b4..af5e60e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -27,6 +27,7 @@
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.mock;
@@ -94,6 +95,7 @@
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
import com.android.systemui.scene.domain.interactor.SceneInteractor;
+import com.android.systemui.scene.shared.model.Scenes;
import com.android.systemui.shade.NotificationShadeWindowView;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.shade.ShadeExpansionChangeEvent;
@@ -166,6 +168,7 @@
@Mock private StatusBarKeyguardViewManager.KeyguardViewManagerCallback mCallback;
@Mock private SelectedUserInteractor mSelectedUserInteractor;
@Mock private DeviceEntryInteractor mDeviceEntryInteractor;
+ @Mock private SceneInteractor mSceneInteractor;
private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
private PrimaryBouncerCallbackInteractor.PrimaryBouncerExpansionCallback
@@ -233,7 +236,7 @@
mSelectedUserInteractor,
() -> mock(KeyguardSurfaceBehindInteractor.class),
mock(JavaAdapter.class),
- () -> mock(SceneInteractor.class),
+ () -> mSceneInteractor,
mock(StatusBarKeyguardViewManagerInteractor.class),
() -> mDeviceEntryInteractor) {
@Override
@@ -270,21 +273,23 @@
}
@Test
- public void showBouncer_onlyWhenShowing() {
+ public void showPrimaryBouncer_onlyWhenShowing() {
mStatusBarKeyguardViewManager.hide(0 /* startTime */, 0 /* fadeoutDuration */);
mStatusBarKeyguardViewManager.showPrimaryBouncer(true /* scrimmed */);
verify(mPrimaryBouncerInteractor, never()).show(anyBoolean());
verify(mDeviceEntryInteractor, never()).attemptDeviceEntry();
+ verify(mSceneInteractor, never()).changeScene(any(), any());
}
@Test
- public void showBouncer_notWhenBouncerAlreadyShowing() {
+ public void showPrimaryBouncer_notWhenBouncerAlreadyShowing() {
mStatusBarKeyguardViewManager.hide(0 /* startTime */, 0 /* fadeoutDuration */);
when(mKeyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(
KeyguardSecurityModel.SecurityMode.Password);
mStatusBarKeyguardViewManager.showPrimaryBouncer(true /* scrimmed */);
verify(mPrimaryBouncerInteractor, never()).show(anyBoolean());
verify(mDeviceEntryInteractor, never()).attemptDeviceEntry();
+ verify(mSceneInteractor, never()).changeScene(any(), any());
}
@Test
@@ -753,7 +758,7 @@
mSelectedUserInteractor,
() -> mock(KeyguardSurfaceBehindInteractor.class),
mock(JavaAdapter.class),
- () -> mock(SceneInteractor.class),
+ () -> mSceneInteractor,
mock(StatusBarKeyguardViewManagerInteractor.class),
() -> mDeviceEntryInteractor) {
@Override
@@ -1104,9 +1109,9 @@
@Test
@EnableSceneContainer
- public void showPrimaryBouncer_attemptDeviceEntry() {
+ public void showPrimaryBouncer() {
mStatusBarKeyguardViewManager.showPrimaryBouncer(false);
- verify(mDeviceEntryInteractor).attemptDeviceEntry();
+ verify(mSceneInteractor).changeScene(eq(Scenes.Bouncer), anyString());
}
@Test
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/inputmethod/data/repository/FakeInputMethodRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/inputmethod/data/repository/FakeInputMethodRepository.kt
index 8e4461d..444baa0 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/inputmethod/data/repository/FakeInputMethodRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/inputmethod/data/repository/FakeInputMethodRepository.kt
@@ -16,6 +16,7 @@
package com.android.systemui.inputmethod.data.repository
+import android.os.UserHandle
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.inputmethod.data.model.InputMethodModel
import kotlinx.coroutines.flow.Flow
@@ -40,14 +41,15 @@
}
override suspend fun enabledInputMethods(
- userId: Int,
- fetchSubtypes: Boolean,
+ user: UserHandle,
+ fetchSubtypes: Boolean
): Flow<InputMethodModel> {
- return usersToEnabledInputMethods[userId] ?: flowOf()
+ return usersToEnabledInputMethods[user.identifier] ?: flowOf()
}
- override suspend fun selectedInputMethodSubtypes(): List<InputMethodModel.Subtype> =
- selectedInputMethodSubtypes
+ override suspend fun selectedInputMethodSubtypes(
+ user: UserHandle,
+ ): List<InputMethodModel.Subtype> = selectedInputMethodSubtypes
override suspend fun showInputMethodPicker(displayId: Int, showAuxiliarySubtypes: Boolean) {
inputMethodPickerShownDisplayId = displayId
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
index 0666820..8614fc9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
@@ -60,6 +60,7 @@
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.shade.shadeController
import com.android.systemui.statusbar.chips.ui.viewmodel.ongoingActivityChipsViewModel
+import com.android.systemui.statusbar.notification.domain.interactor.seenNotificationsInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.headsUpNotificationInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor
import com.android.systemui.statusbar.phone.scrimController
@@ -98,6 +99,7 @@
val communalRepository by lazy { kosmos.fakeCommunalSceneRepository }
val communalTransitionViewModel by lazy { kosmos.communalTransitionViewModel }
val headsUpNotificationInteractor by lazy { kosmos.headsUpNotificationInteractor }
+ val seenNotificationsInteractor by lazy { kosmos.seenNotificationsInteractor }
val keyguardRepository by lazy { kosmos.fakeKeyguardRepository }
val keyguardBouncerRepository by lazy { kosmos.fakeKeyguardBouncerRepository }
val keyguardInteractor by lazy { kosmos.keyguardInteractor }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinatorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinatorKosmos.kt
index 77d97bb..933ebf0 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinatorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinatorKosmos.kt
@@ -18,23 +18,19 @@
import com.android.systemui.dump.dumpManager
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.plugins.statusbar.statusBarStateController
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.statusbar.notification.domain.interactor.seenNotificationsInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.headsUpNotificationInteractor
-import com.android.systemui.util.settings.fakeSettings
var Kosmos.lockScreenMinimalismCoordinator by
Kosmos.Fixture {
LockScreenMinimalismCoordinator(
- bgDispatcher = testDispatcher,
dumpManager = dumpManager,
headsUpInteractor = headsUpNotificationInteractor,
logger = lockScreenMinimalismCoordinatorLogger,
scope = testScope.backgroundScope,
- secureSettings = fakeSettings,
seenNotificationsInteractor = seenNotificationsInteractor,
statusBarStateController = statusBarStateController,
shadeInteractor = shadeInteractor,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractorKosmos.kt
index c1e0419..b19e221 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractorKosmos.kt
@@ -16,12 +16,32 @@
package com.android.systemui.statusbar.notification.domain.interactor
+import android.os.UserHandle
+import android.provider.Settings
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
+import com.android.systemui.util.settings.fakeSettings
val Kosmos.seenNotificationsInteractor by Fixture {
SeenNotificationsInteractor(
+ bgDispatcher = testDispatcher,
notificationListRepository = activeNotificationListRepository,
+ secureSettings = fakeSettings,
)
}
+
+var Kosmos.lockScreenShowOnlyUnseenNotificationsSetting: Boolean
+ get() =
+ fakeSettings.getIntForUser(
+ Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
+ UserHandle.USER_CURRENT,
+ ) == 1
+ set(value) {
+ fakeSettings.putIntForUser(
+ Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
+ if (value) 1 else 2,
+ UserHandle.USER_CURRENT,
+ )
+ }
diff --git a/services/core/java/com/android/server/am/AppExitInfoTracker.java b/services/core/java/com/android/server/am/AppExitInfoTracker.java
index 47b65eb..1f88657 100644
--- a/services/core/java/com/android/server/am/AppExitInfoTracker.java
+++ b/services/core/java/com/android/server/am/AppExitInfoTracker.java
@@ -353,8 +353,8 @@
}
/** Called when there is a low memory kill */
- void scheduleNoteLmkdProcKilled(final int pid, final int uid) {
- mKillHandler.obtainMessage(KillHandler.MSG_LMKD_PROC_KILLED, pid, uid)
+ void scheduleNoteLmkdProcKilled(final int pid, final int uid, final int rssKb) {
+ mKillHandler.obtainMessage(KillHandler.MSG_LMKD_PROC_KILLED, pid, uid, Long.valueOf(rssKb))
.sendToTarget();
}
@@ -401,9 +401,9 @@
if (lmkd != null) {
updateExistingExitInfoRecordLocked(info, null,
- ApplicationExitInfo.REASON_LOW_MEMORY);
+ ApplicationExitInfo.REASON_LOW_MEMORY, (Long) lmkd.second);
} else if (zygote != null) {
- updateExistingExitInfoRecordLocked(info, (Integer) zygote.second, null);
+ updateExistingExitInfoRecordLocked(info, (Integer) zygote.second, null, null);
} else {
scheduleLogToStatsdLocked(info, false);
}
@@ -486,7 +486,7 @@
*/
@GuardedBy("mLock")
private void updateExistingExitInfoRecordLocked(ApplicationExitInfo info,
- Integer status, Integer reason) {
+ Integer status, Integer reason, Long rssKb) {
if (info == null || !isFresh(info.getTimestamp())) {
// if the record is way outdated, don't update it then (because of potential pid reuse)
return;
@@ -513,6 +513,9 @@
immediateLog = true;
}
}
+ if (rssKb != null) {
+ info.setRss(rssKb.longValue());
+ }
scheduleLogToStatsdLocked(info, immediateLog);
}
@@ -523,7 +526,7 @@
*/
@GuardedBy("mLock")
private boolean updateExitInfoIfNecessaryLocked(
- int pid, int uid, Integer status, Integer reason) {
+ int pid, int uid, Integer status, Integer reason, Long rssKb) {
Integer k = mIsolatedUidRecords.getUidByIsolatedUid(uid);
if (k != null) {
uid = k;
@@ -552,7 +555,7 @@
// always be the first one we se as `getExitInfosLocked()` returns them sorted
// by most-recent-first.
isModified[0] = true;
- updateExistingExitInfoRecordLocked(info, status, reason);
+ updateExistingExitInfoRecordLocked(info, status, reason, rssKb);
return FOREACH_ACTION_STOP_ITERATION;
}
return FOREACH_ACTION_NONE;
@@ -1668,11 +1671,11 @@
switch (msg.what) {
case MSG_LMKD_PROC_KILLED:
mAppExitInfoSourceLmkd.onProcDied(msg.arg1 /* pid */, msg.arg2 /* uid */,
- null /* status */);
+ null /* status */, (Long) msg.obj /* rss_kb */);
break;
case MSG_CHILD_PROC_DIED:
mAppExitInfoSourceZygote.onProcDied(msg.arg1 /* pid */, msg.arg2 /* uid */,
- (Integer) msg.obj /* status */);
+ (Integer) msg.obj /* status */, null /* rss_kb */);
break;
case MSG_PROC_DIED: {
ApplicationExitInfo raw = (ApplicationExitInfo) msg.obj;
@@ -1833,7 +1836,7 @@
}
}
- void onProcDied(final int pid, final int uid, final Integer status) {
+ void onProcDied(final int pid, final int uid, final Integer status, final Long rssKb) {
if (DEBUG_PROCESSES) {
Slog.i(TAG, mTag + ": proc died: pid=" + pid + " uid=" + uid
+ ", status=" + status);
@@ -1846,8 +1849,12 @@
// Unlikely but possible: the record has been created
// Let's update it if we could find a ApplicationExitInfo record
synchronized (mLock) {
- if (!updateExitInfoIfNecessaryLocked(pid, uid, status, mPresetReason)) {
- addLocked(pid, uid, status);
+ if (!updateExitInfoIfNecessaryLocked(pid, uid, status, mPresetReason, rssKb)) {
+ if (rssKb != null) {
+ addLocked(pid, uid, rssKb); // lmkd
+ } else {
+ addLocked(pid, uid, status); // zygote
+ }
}
// Notify any interesed party regarding the lmkd kills
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 779aabe..726e827 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -951,12 +951,14 @@
try {
switch (inputData.readInt()) {
case LMK_PROCKILL:
- if (receivedLen != 12) {
+ if (receivedLen != 16) {
return false;
}
final int pid = inputData.readInt();
final int uid = inputData.readInt();
- mAppExitInfoTracker.scheduleNoteLmkdProcKilled(pid, uid);
+ final int rssKb = inputData.readInt();
+ mAppExitInfoTracker.scheduleNoteLmkdProcKilled(pid, uid,
+ rssKb);
return true;
case LMK_KILL_OCCURRED:
if (receivedLen
diff --git a/services/core/java/com/android/server/display/ExternalDisplayStatsService.java b/services/core/java/com/android/server/display/ExternalDisplayStatsService.java
index f6f23d9..608fb35 100644
--- a/services/core/java/com/android/server/display/ExternalDisplayStatsService.java
+++ b/services/core/java/com/android/server/display/ExternalDisplayStatsService.java
@@ -518,18 +518,24 @@
private void logExternalDisplayIdleStarted() {
synchronized (mExternalDisplayStates) {
for (var i = 0; i < mExternalDisplayStates.size(); i++) {
- mInjector.writeLog(FrameworkStatsLog.EXTERNAL_DISPLAY_STATE_CHANGED,
- KEYGUARD, i + 1, mIsExternalDisplayUsedForAudio);
- if (DEBUG) {
- final int displayId = mExternalDisplayStates.keyAt(i);
- final int state = mExternalDisplayStates.get(displayId, DISCONNECTED_STATE);
- Slog.d(TAG, "logExternalDisplayIdleStarted"
- + " displayId=" + displayId
- + " currentState=" + state
- + " countOfExternalDisplays=" + (i + 1)
- + " state=" + KEYGUARD
- + " mIsExternalDisplayUsedForAudio="
- + mIsExternalDisplayUsedForAudio);
+ final int displayId = mExternalDisplayStates.keyAt(i);
+ final int state = mExternalDisplayStates.get(displayId, DISCONNECTED_STATE);
+ // Don't try to stop "connected" session by keyguard event.
+ // There is no purpose to measure how long keyguard is shown while external
+ // display is connected but not used for mirroring or extended display.
+ // Therefore there no need to log this event.
+ if (state != DISCONNECTED_STATE && state != CONNECTED_STATE) {
+ mInjector.writeLog(FrameworkStatsLog.EXTERNAL_DISPLAY_STATE_CHANGED,
+ KEYGUARD, i + 1, mIsExternalDisplayUsedForAudio);
+ if (DEBUG) {
+ Slog.d(TAG, "logExternalDisplayIdleStarted"
+ + " displayId=" + displayId
+ + " currentState=" + state
+ + " countOfExternalDisplays=" + (i + 1)
+ + " state=" + KEYGUARD
+ + " mIsExternalDisplayUsedForAudio="
+ + mIsExternalDisplayUsedForAudio);
+ }
}
}
}
@@ -540,7 +546,11 @@
for (var i = 0; i < mExternalDisplayStates.size(); i++) {
final int displayId = mExternalDisplayStates.keyAt(i);
final int state = mExternalDisplayStates.get(displayId, DISCONNECTED_STATE);
- if (state == DISCONNECTED_STATE) {
+ // No need to restart "connected" session after keyguard is stopped.
+ // This is because the connection is continuous even if keyguard is shown.
+ // In case in the future keyguard needs to be measured also while display
+ // is not used, then a 'keyguard finished' event needs to be emitted in this case.
+ if (state == DISCONNECTED_STATE || state == CONNECTED_STATE) {
return;
}
mInjector.writeLog(FrameworkStatsLog.EXTERNAL_DISPLAY_STATE_CHANGED,
diff --git a/services/core/java/com/android/server/input/KeyboardLayoutManager.java b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
index 4993412..1d1a178 100644
--- a/services/core/java/com/android/server/input/KeyboardLayoutManager.java
+++ b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
@@ -22,6 +22,8 @@
import static android.hardware.input.KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD;
import static android.hardware.input.KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_DEFAULT;
+import static com.android.hardware.input.Flags.keyboardLayoutManagerMultiUserImeSetup;
+
import android.annotation.AnyThread;
import android.annotation.MainThread;
import android.annotation.NonNull;
@@ -1066,9 +1068,15 @@
for (InputMethodInfo imeInfo :
inputMethodManagerInternal.getEnabledInputMethodListAsUser(
userId)) {
- for (InputMethodSubtype imeSubtype :
- inputMethodManager.getEnabledInputMethodSubtypeList(
- imeInfo, true /* allowsImplicitlyEnabledSubtypes */)) {
+ final List<InputMethodSubtype> imeSubtypes;
+ if (keyboardLayoutManagerMultiUserImeSetup()) {
+ imeSubtypes = inputMethodManagerInternal.getEnabledInputMethodSubtypeListAsUser(
+ imeInfo.getId(), true /* allowsImplicitlyEnabledSubtypes */, userId);
+ } else {
+ imeSubtypes = inputMethodManager.getEnabledInputMethodSubtypeList(imeInfo,
+ true /* allowsImplicitlyEnabledSubtypes */);
+ }
+ for (InputMethodSubtype imeSubtype : imeSubtypes) {
if (!imeSubtype.isSuitableForPhysicalKeyboardLayoutMapping()) {
continue;
}
diff --git a/services/core/java/com/android/server/pm/PackageArchiver.java b/services/core/java/com/android/server/pm/PackageArchiver.java
index 6b7b702..5e45b4c 100644
--- a/services/core/java/com/android/server/pm/PackageArchiver.java
+++ b/services/core/java/com/android/server/pm/PackageArchiver.java
@@ -869,18 +869,25 @@
private int createDraftSession(String packageName, String installerPackage,
String callerPackageName,
IntentSender statusReceiver, int userId) throws IOException {
+ Computer snapshot = mPm.snapshotComputer();
PackageInstaller.SessionParams sessionParams = new PackageInstaller.SessionParams(
PackageInstaller.SessionParams.MODE_FULL_INSTALL);
sessionParams.setAppPackageName(packageName);
sessionParams.setAppLabel(
mContext.getString(com.android.internal.R.string.unarchival_session_app_label));
- sessionParams.setAppIcon(
- getArchivedAppIcon(packageName, UserHandle.of(userId), callerPackageName));
+ // The draft session's app icon is based on the current launcher's icon overlay appops mode
+ String launcherPackageName = getCurrentLauncherPackageName(userId);
+ int launcherUid = launcherPackageName != null
+ ? snapshot.getPackageUid(launcherPackageName, 0, userId)
+ : Process.SYSTEM_UID;
+ sessionParams.setAppIcon(getArchivedAppIcon(packageName, UserHandle.of(userId),
+ isOverlayEnabled(launcherUid,
+ launcherPackageName == null ? callerPackageName : launcherPackageName)));
// To make sure SessionInfo::isUnarchival returns true for draft sessions,
// INSTALL_UNARCHIVE is also set.
sessionParams.installFlags = (INSTALL_UNARCHIVE_DRAFT | INSTALL_UNARCHIVE);
- int installerUid = mPm.snapshotComputer().getPackageUid(installerPackage, 0, userId);
+ int installerUid = snapshot.getPackageUid(installerPackage, 0, userId);
// Handles case of repeated unarchival calls for the same package.
int existingSessionId = mPm.mInstallerService.getExistingDraftSessionId(installerUid,
sessionParams,
@@ -926,12 +933,27 @@
/**
* Returns the icon of an archived app. This is the icon of the main activity of the app.
*
- * <p> The icon is returned without any treatment/overlay. In the rare case the app had multiple
- * launcher activities, only one of the icons is returned arbitrarily.
+ * <p> In the rare case the app had multiple launcher activities, only one of the icons is
+ * returned arbitrarily.
+ *
+ * <p> By default, the icon will be overlay'd with a cloud icon on top. A launcher app can
+ * disable the cloud overlay via the
+ * {@link LauncherApps.ArchiveCompatibilityParams#setEnableIconOverlay(boolean)} API.
+ * The default launcher's cloud overlay mode determines the cloud overlay status returned by
+ * any other callers. That is, if the current launcher has the cloud overlay disabled, any other
+ * app that fetches the app icon will also get an icon that has the cloud overlay disabled.
+ * This is to prevent style mismatch caused by icons that are fetched by different callers.
*/
@Nullable
public Bitmap getArchivedAppIcon(@NonNull String packageName, @NonNull UserHandle user,
String callingPackageName) {
+ return getArchivedAppIcon(packageName, user,
+ isOverlayEnabled(Binder.getCallingUid(), callingPackageName));
+ }
+
+ @Nullable
+ private Bitmap getArchivedAppIcon(@NonNull String packageName, @NonNull UserHandle user,
+ boolean isOverlayEnabled) {
Objects.requireNonNull(packageName);
Objects.requireNonNull(user);
@@ -955,14 +977,19 @@
// In the rare case the archived app defined more than two launcher activities, we choose
// the first one arbitrarily.
Bitmap icon = decodeIcon(archiveState.getActivityInfos().get(0));
- if (icon != null && getAppOpsManager().checkOp(
- AppOpsManager.OP_ARCHIVE_ICON_OVERLAY, callingUid, callingPackageName)
- == MODE_ALLOWED) {
+
+ if (icon != null && isOverlayEnabled) {
icon = includeCloudOverlay(icon);
}
return icon;
}
+ private boolean isOverlayEnabled(int callingUid, String packageName) {
+ return getAppOpsManager().checkOp(
+ AppOpsManager.OP_ARCHIVE_ICON_OVERLAY, callingUid, packageName)
+ == MODE_ALLOWED;
+ }
+
/**
* This method first checks the ArchiveState for the provided userId and then tries to fallback
* to other users if the current user is not archived.
diff --git a/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayStatsServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayStatsServiceTest.java
index 98ba9ae..abb354b 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayStatsServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayStatsServiceTest.java
@@ -151,9 +151,10 @@
}
@Test
- public void testDisplayInteractivityChanges(
+ public void testDisplayInteractivityChangesWhileMirroring(
@TestParameter final boolean isExternalDisplayUsedForAudio) {
mExternalDisplayStatsService.onDisplayConnected(mMockedLogicalDisplay);
+ mExternalDisplayStatsService.onDisplayAdded(EXTERNAL_DISPLAY_ID);
mHandler.flush();
assertThat(mInteractivityReceiver).isNotNull();
@@ -180,12 +181,38 @@
mInteractivityReceiver.onReceive(null, null);
assertThat(mExternalDisplayStatsService.isInteractiveExternalDisplays()).isTrue();
verify(mMockedInjector).writeLog(FrameworkStatsLog.EXTERNAL_DISPLAY_STATE_CHANGED,
- FrameworkStatsLog.EXTERNAL_DISPLAY_STATE_CHANGED__STATE__CONNECTED,
+ FrameworkStatsLog.EXTERNAL_DISPLAY_STATE_CHANGED__STATE__MIRRORING,
/*numberOfDisplays=*/ 1,
isExternalDisplayUsedForAudio);
}
@Test
+ public void testDisplayInteractivityChangesWhileConnected() {
+ mExternalDisplayStatsService.onDisplayConnected(mMockedLogicalDisplay);
+ mHandler.flush();
+ assertThat(mInteractivityReceiver).isNotNull();
+ clearInvocations(mMockedInjector);
+
+ // Default is 'interactive', so no log should be written.
+ mInteractivityReceiver.onReceive(null, null);
+ assertThat(mExternalDisplayStatsService.isInteractiveExternalDisplays()).isTrue();
+ verify(mMockedInjector, never()).writeLog(anyInt(), anyInt(), anyInt(), anyBoolean());
+
+ // Change to non-interactive should not produce log
+ when(mMockedInjector.isInteractive(eq(EXTERNAL_DISPLAY_ID))).thenReturn(false);
+ mInteractivityReceiver.onReceive(null, null);
+ assertThat(mExternalDisplayStatsService.isInteractiveExternalDisplays()).isFalse();
+ verify(mMockedInjector, never()).writeLog(anyInt(), anyInt(), anyInt(), anyBoolean());
+ clearInvocations(mMockedInjector);
+
+ // Change back to interactive should not produce log
+ when(mMockedInjector.isInteractive(eq(EXTERNAL_DISPLAY_ID))).thenReturn(true);
+ mInteractivityReceiver.onReceive(null, null);
+ assertThat(mExternalDisplayStatsService.isInteractiveExternalDisplays()).isTrue();
+ verify(mMockedInjector, never()).writeLog(anyInt(), anyInt(), anyInt(), anyBoolean());
+ }
+
+ @Test
public void testAudioPlaybackChanges() {
mExternalDisplayStatsService.onDisplayConnected(mMockedLogicalDisplay);
mHandler.flush();
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java
index adcbf5c..194bf4b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java
@@ -442,20 +442,24 @@
IMPORTANCE_FOREGROUND_SERVICE, // importance
null); // description
- // Case 4: Create a process from another package with kill from lmkd
+ /*
+ * Case 4: Create a process from another package with kill from lmkd
+ * We expect LMKD's reported RSS to be the process' last seen RSS.
+ */
final int app2UidUser2 = 1010234;
final int app2PidUser2 = 12348;
final long app2Pss1 = 54321;
final long app2Rss1 = 65432;
+ final long lmkd_reported_rss = 43215;
final String app2ProcessName = "com.android.test.stub2:process";
final String app2PackageName = "com.android.test.stub2";
sleep(1);
final long now4 = System.currentTimeMillis();
- doReturn(new Pair<Long, Object>(now4, Integer.valueOf(0)))
+ doReturn(null)
.when(mAppExitInfoTracker.mAppExitInfoSourceZygote)
.remove(anyInt(), anyInt());
- doReturn(new Pair<Long, Object>(now4, null))
+ doReturn(new Pair<Long, Object>(now4, Long.valueOf(lmkd_reported_rss)))
.when(mAppExitInfoTracker.mAppExitInfoSourceLmkd)
.remove(anyInt(), anyInt());
@@ -490,7 +494,7 @@
null, // subReason
0, // status
app2Pss1, // pss
- app2Rss1, // rss
+ lmkd_reported_rss, // rss
IMPORTANCE_CACHED, // importance
null); // description
@@ -499,6 +503,11 @@
mAppExitInfoTracker.getExitInfo(null, app2UidUser2, 0, 0, list);
assertEquals(1, list.size());
+ info = list.get(0);
+
+ // Verify the AppExitInfo has the LMKD reported RSS
+ assertEquals(lmkd_reported_rss, info.getRss());
+
// Case 5: App native crash
final int app3UidUser2 = 1010345;
final int app3PidUser2 = 12349;
@@ -599,7 +608,7 @@
null, // subReason
0, // status
app2Pss1, // pss
- app2Rss1, // rss
+ lmkd_reported_rss, // rss
IMPORTANCE_CACHED, // importance
null); // description
diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
index d5d2847..b92af87 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
@@ -77,6 +77,7 @@
import android.view.SurfaceControl;
import com.android.dx.mockito.inline.extended.StaticMockitoSession;
+import com.android.internal.os.BackgroundThread;
import com.android.server.AnimationThread;
import com.android.server.DisplayThread;
import com.android.server.LocalServices;
@@ -553,6 +554,9 @@
// This is a different handler object than the wm.mAnimationHandler above.
waitHandlerIdle(AnimationThread.getHandler());
waitHandlerIdle(SurfaceAnimationThread.getHandler());
+ // Some binder calls are posted to BackgroundThread.getHandler(), we should wait for them
+ // to finish to run next test.
+ waitHandlerIdle(BackgroundThread.getHandler());
}
static void waitHandlerIdle(Handler handler) {
diff --git a/tests/inputmethod/ConcurrentMultiSessionImeTest/Android.bp b/tests/inputmethod/ConcurrentMultiSessionImeTest/Android.bp
index 4c531b8..a4085e5 100644
--- a/tests/inputmethod/ConcurrentMultiSessionImeTest/Android.bp
+++ b/tests/inputmethod/ConcurrentMultiSessionImeTest/Android.bp
@@ -23,6 +23,7 @@
resource_dirs: ["res"],
libs: ["android.test.runner"],
static_libs: [
+ "androidx.core_core",
"androidx.test.ext.junit",
"androidx.test.rules",
"compatibility-device-util-axt",