diff options
86 files changed, 2420 insertions, 1610 deletions
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java index ee03e4b2ccd1..857154fee453 100644 --- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java +++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java @@ -116,7 +116,6 @@ import android.os.ServiceManager; import android.os.ShellCallback; import android.os.ShellCommand; import android.os.SystemClock; -import android.os.SystemProperties; import android.os.ThreadLocalWorkSource; import android.os.Trace; import android.os.UserHandle; @@ -179,9 +178,6 @@ import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.text.SimpleDateFormat; -import java.time.Instant; -import java.time.zone.ZoneOffsetTransition; -import java.time.zone.ZoneRules; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; @@ -193,7 +189,6 @@ import java.util.Set; import java.util.TimeZone; import java.util.TreeSet; import java.util.concurrent.ThreadLocalRandom; -import java.util.concurrent.TimeUnit; import java.util.function.Predicate; /** @@ -233,13 +228,6 @@ public class AlarmManagerService extends SystemService { private static final long TEMPORARY_QUOTA_DURATION = INTERVAL_DAY; - // System properties read on some device configurations to initialize time properly and - // perform DST transitions at the bootloader level. - private static final String TIMEOFFSET_PROPERTY = "persist.sys.time.offset"; - private static final String DST_TRANSITION_PROPERTY = "persist.sys.time.dst_transition"; - private static final String DST_OFFSET_PROPERTY = "persist.sys.time.dst_offset"; - - private final Intent mBackgroundIntent = new Intent().addFlags(Intent.FLAG_FROM_BACKGROUND); @@ -2127,22 +2115,6 @@ public class AlarmManagerService extends SystemService { // "GMT" if the ID is unrecognized). The parameter ID is used here rather than // newZone.getId(). It will be rejected if it is invalid. timeZoneWasChanged = SystemTimeZone.setTimeZoneId(tzId, confidence, logInfo); - - final int gmtOffset = newZone.getOffset(mInjector.getCurrentTimeMillis()); - SystemProperties.set(TIMEOFFSET_PROPERTY, String.valueOf(gmtOffset)); - - final ZoneRules rules = newZone.toZoneId().getRules(); - final ZoneOffsetTransition transition = rules.nextTransition(Instant.now()); - if (null != transition) { - // Get the offset between the time after the DST transition and before. - final long transitionOffset = TimeUnit.SECONDS.toMillis(( - transition.getOffsetAfter().getTotalSeconds() - - transition.getOffsetBefore().getTotalSeconds())); - // Time when the next DST transition is programmed. - final long nextTransition = TimeUnit.SECONDS.toMillis(transition.toEpochSecond()); - SystemProperties.set(DST_TRANSITION_PROPERTY, String.valueOf(nextTransition)); - SystemProperties.set(DST_OFFSET_PROPERTY, String.valueOf(transitionOffset)); - } } // Clear the default time zone in the system server process. This forces the next call diff --git a/core/java/android/hardware/input/input_framework.aconfig b/core/java/android/hardware/input/input_framework.aconfig index 0cd280002dbf..b4ad050c8b5d 100644 --- a/core/java/android/hardware/input/input_framework.aconfig +++ b/core/java/android/hardware/input/input_framework.aconfig @@ -75,3 +75,13 @@ flag { 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 71c83f20741d..99bd67bd96ff 100644 --- a/core/java/android/os/Vibrator.java +++ b/core/java/android/os/Vibrator.java @@ -497,7 +497,27 @@ public abstract class Vibrator { */ @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/provider/CallLog.java b/core/java/android/provider/CallLog.java index 708c1966be8b..192afb18f28f 100644 --- a/core/java/android/provider/CallLog.java +++ b/core/java/android/provider/CallLog.java @@ -28,6 +28,7 @@ import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.annotation.UserHandleAware; import android.compat.annotation.UnsupportedAppUsage; +import android.content.ComponentName; import android.content.ContentProvider; import android.content.ContentResolver; import android.content.ContentValues; @@ -1624,6 +1625,19 @@ public class CallLog { "is_call_log_phone_account_migration_pending"; /** + * The default maximum number of call log entries stored in the call log provider for each + * {@link PhoneAccountHandle}. + */ + private static final int DEFAULT_MAX_CALL_LOG_SIZE = 500; + + /** + * Expected component name of Telephony phone accounts. + */ + private static final ComponentName TELEPHONY_COMPONENT_NAME = + new ComponentName("com.android.phone", + "com.android.services.telephony.TelephonyConnectionService"); + + /** * Adds a call to the call log. * * @param ci the CallerInfo object to get the target contact from. Can be null @@ -2084,25 +2098,35 @@ public class CallLog { } int numDeleted; - if (values.containsKey(PHONE_ACCOUNT_ID) - && !TextUtils.isEmpty(values.getAsString(PHONE_ACCOUNT_ID)) - && values.containsKey(PHONE_ACCOUNT_COMPONENT_NAME) - && !TextUtils.isEmpty(values.getAsString(PHONE_ACCOUNT_COMPONENT_NAME))) { + final String phoneAccountId = + values.containsKey(PHONE_ACCOUNT_ID) + ? values.getAsString(PHONE_ACCOUNT_ID) : null; + final String phoneAccountComponentName = + values.containsKey(PHONE_ACCOUNT_COMPONENT_NAME) + ? values.getAsString(PHONE_ACCOUNT_COMPONENT_NAME) : null; + int maxCallLogSize = DEFAULT_MAX_CALL_LOG_SIZE; + if (!TextUtils.isEmpty(phoneAccountId) + && !TextUtils.isEmpty(phoneAccountComponentName)) { + if (android.provider.Flags.allowConfigMaximumCallLogEntriesPerSim() + && TELEPHONY_COMPONENT_NAME + .flattenToString().equals(phoneAccountComponentName)) { + maxCallLogSize = context.getResources().getInteger( + com.android.internal.R.integer.config_maximumCallLogEntriesPerSim); + } // Only purge entries for the same phone account. numDeleted = resolver.delete(uri, "_id IN " + "(SELECT _id FROM calls" + " WHERE " + PHONE_ACCOUNT_COMPONENT_NAME + " = ?" + " AND " + PHONE_ACCOUNT_ID + " = ?" + " ORDER BY " + DEFAULT_SORT_ORDER - + " LIMIT -1 OFFSET 500)", new String[] { - values.getAsString(PHONE_ACCOUNT_COMPONENT_NAME), - values.getAsString(PHONE_ACCOUNT_ID) - }); + + " LIMIT -1 OFFSET " + maxCallLogSize + ")", + new String[] { phoneAccountComponentName, phoneAccountId } + ); } else { // No valid phone account specified, so default to the old behavior. numDeleted = resolver.delete(uri, "_id IN " + "(SELECT _id FROM calls ORDER BY " + DEFAULT_SORT_ORDER - + " LIMIT -1 OFFSET 500)", null); + + " LIMIT -1 OFFSET " + maxCallLogSize + ")", null); } Log.i(LOG_TAG, "addEntry: cleaned up " + numDeleted + " old entries"); diff --git a/core/java/android/provider/flags.aconfig b/core/java/android/provider/flags.aconfig index ff98fc4ff7c6..53d0c62ec2c5 100644 --- a/core/java/android/provider/flags.aconfig +++ b/core/java/android/provider/flags.aconfig @@ -30,4 +30,16 @@ flag { namespace: "backstage_power" description: "Add a new settings page for the RUN_BACKUP_JOBS permission." bug: "320563660" -}
\ No newline at end of file +} + +# OWNER = tgunn TARGET=25Q1 +flag { + name: "allow_config_maximum_call_log_entries_per_sim" + is_exported: true + namespace: "telecom" + description: "Allow partners to modify the maximum number of call log size for each sim card." + bug: "352235494" + metadata { + purpose: PURPOSE_FEATURE + } +} diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java index 46b222baecd1..66d08f99516e 100644 --- a/core/java/android/service/wallpaper/WallpaperService.java +++ b/core/java/android/service/wallpaper/WallpaperService.java @@ -404,8 +404,7 @@ public abstract class WallpaperService extends Service { } 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 b07534f5fe52..5d84d17bdb6e 100644 --- a/core/java/android/text/ClientFlags.java +++ b/core/java/android/text/ClientFlags.java @@ -68,11 +68,4 @@ public class ClientFlags { 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 4dca284b8a4d..9e02460d2637 100644 --- a/core/java/android/text/TextFlags.java +++ b/core/java/android/text/TextFlags.java @@ -61,7 +61,6 @@ public final class TextFlags { 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 @@ public final class TextFlags { 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 809ec6321fad..e5ac0e1a8f6e 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 @@ static constexpr bool kDebugProc = false; // 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 @@ jintArray android_os_Process_getPids(JNIEnv* env, jobject clazz, } DIR* dirp = opendir(file8); - env->ReleaseStringUTFChars(file, file8); if(dirp == NULL) { @@ -850,6 +849,7 @@ jintArray android_os_Process_getPids(JNIEnv* env, jobject clazz, 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 @@ jboolean android_os_Process_readProcFile(JNIEnv* env, jobject clazz, 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. - - char readBufferStack[kProcReadStackBufferSize]; - std::unique_ptr<char[]> readBufferHeap; - char* readBuffer = &readBufferStack[0]; - ssize_t readBufferSize = kProcReadStackBufferSize; - ssize_t numberBytesRead; + // with the stack buffer first. We allocate a buffer big enough + // for most files. + + 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; - } - 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; + heapBuf.resize(2 * heapBuf.size()); } - 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/res/res/values/config.xml b/core/res/res/values/config.xml index 8cb7646939ea..2afc30315bd1 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -7100,4 +7100,8 @@ <!-- Whether to enable the private space search illustration and search tile content in "Hide Private Space" settings page. OEM/Partner can explicitly opt to hide the illustration and search tile content. --> <bool name="config_enableSearchTileHideIllustrationInPrivateSpace">true</bool> + + <!-- The maximum number of call log entries for each sim card that can be stored in the call log + provider on the device. --> + <integer name="config_maximumCallLogEntriesPerSim">500</integer> </resources> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 4a425cbe009a..bc8c778c0671 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -5530,6 +5530,8 @@ <java-symbol type="integer" name="config_wallpaperFrameRateCompatibility" /> <java-symbol type="integer" name="config_defaultMinEmergencyGestureTapDurationMillis" /> + <java-symbol type="integer" name="config_maximumCallLogEntriesPerSim" /> + <!-- Back swipe thresholds --> <java-symbol type="dimen" name="navigation_edge_action_progress_threshold" /> <java-symbol type="dimen" name="back_progress_non_linear_factor" /> diff --git a/core/tests/vibrator/src/android/os/VibratorTest.java b/core/tests/vibrator/src/android/os/VibratorTest.java index cfa12bb5b504..6210a00a5940 100644 --- a/core/tests/vibrator/src/android/os/VibratorTest.java +++ b/core/tests/vibrator/src/android/os/VibratorTest.java @@ -222,6 +222,18 @@ public class VibratorTest { } @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 5d4139e4be8c..1fe6ca7dc14f 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -505,6 +505,7 @@ applications that come with the platform <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 b83931fa0615..df95a91d72d7 100644 --- a/graphics/java/android/graphics/Paint.java +++ b/graphics/java/android/graphics/Paint.java @@ -35,7 +35,6 @@ import android.compat.annotation.UnsupportedAppUsage; 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 @@ public class Paint { * @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 @@ public class Paint { * </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 @@ public class Paint { 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 @@ public class Paint { 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 544f0f38f48c..882a8d035e93 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java @@ -565,6 +565,7 @@ class DividerPresenter implements View.OnTouchListener { return true; } + // Only called by onTouch() and mRenderer is already null-checked. @GuardedBy("mLock") private void onStartDragging(@NonNull MotionEvent event) { mVelocityTracker = VelocityTracker.obtain(); @@ -590,6 +591,7 @@ class DividerPresenter implements View.OnTouchListener { }); } + // Only called by onTouch() and mRenderer is already null-checked. @GuardedBy("mLock") private void onDrag(@NonNull MotionEvent event) { if (mVelocityTracker != null) { @@ -660,8 +662,10 @@ class DividerPresenter implements View.OnTouchListener { @GuardedBy("mLock") private void updateDividerPosition(int position) { - mRenderer.setDividerPosition(position); - mRenderer.updateSurface(); + if (mRenderer != null) { + mRenderer.setDividerPosition(position); + mRenderer.updateSurface(); + } } @GuardedBy("mLock") @@ -669,7 +673,10 @@ class DividerPresenter implements View.OnTouchListener { // Veil visibility change should be applied together with the surface boost transaction in // the wct. final SurfaceControl.Transaction t = new SurfaceControl.Transaction(); - mRenderer.hideVeils(t); + + if (mRenderer != null) { + mRenderer.hideVeils(t); + } // Callbacks must be executed on the executor to release mLock and prevent deadlocks. // mDecorSurfaceOwner may change between here and when the callback is executed, @@ -684,8 +691,10 @@ class DividerPresenter implements View.OnTouchListener { } }); }); - mRenderer.mIsDragging = false; - mRenderer.mDragHandle.setPressed(mRenderer.mIsDragging); + if (mRenderer != null) { + mRenderer.mIsDragging = false; + mRenderer.mDragHandle.setPressed(mRenderer.mIsDragging); + } } /** @@ -1090,13 +1099,14 @@ class DividerPresenter implements View.OnTouchListener { @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 +1125,10 @@ class DividerPresenter implements View.OnTouchListener { 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 +1140,6 @@ class DividerPresenter implements View.OnTouchListener { context, displayManager.getDisplay(mProperties.mDisplayId), mWindowlessWindowManager, "DividerContainer"); mDividerLayout = new FrameLayout(context); - mDividerLine = new View(context); update(); } @@ -1220,6 +1232,7 @@ class DividerPresenter implements View.OnTouchListener { 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 +1241,24 @@ class DividerPresenter implements View.OnTouchListener { 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 +1304,6 @@ class DividerPresenter implements View.OnTouchListener { */ 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 +1357,11 @@ class DividerPresenter implements View.OnTouchListener { } @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 +1372,12 @@ class DividerPresenter implements View.OnTouchListener { 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 a52141c5f9d6..ba97c8322c92 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_PINNED; 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 @@ public class PipTransition extends PipTransitionController { ? 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 @@ public class PipTransition extends PipTransitionController { 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/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java index be16207dc2d5..8e21d292f156 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java @@ -738,7 +738,11 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin return; } final PackageManager pm = mContext.getApplicationContext().getPackageManager(); - final ActivityInfo activityInfo = pm.getActivityInfo(baseActivity, 0 /* flags */); + final ActivityInfo activityInfo = pm.getActivityInfo(baseActivity, + // Include uninstalled apps. Despite its name, adding this flag is a workaround + // to #getActivityInfo throwing a NameNotFoundException for installed packages + // when HSUM is enabled. See b/354884302. + PackageManager.MATCH_UNINSTALLED_PACKAGES); final IconProvider provider = new IconProvider(mContext); final Drawable appIconDrawable = provider.getIcon(activityInfo); final BaseIconFactory headerIconFactory = createIconFactory(mContext, diff --git a/libs/hwui/jni/Bitmap.cpp b/libs/hwui/jni/Bitmap.cpp index d4157008ca46..010c4e8dfb3a 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 @@ static void assert_premultiplied(const SkImageInfo& info, bool isPremultiplied) 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 @@ void reinitBitmap(JNIEnv* env, jobject javaBitmap, const SkImageInfo& info, 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 @@ Bitmap& toBitmap(jlong bitmapHandle) { 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 @@ SkImageInfo GraphicsJNI::getBitmapInfo(JNIEnv* env, jobject bitmap, uint32_t* ou 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 @@ static const JNINativeMethod gBitmapMethods[] = { 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 03a2b7526f62..990a2d4708cc 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 @@ public class ZenMode implements Parcelable { 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 04d30ed6f001..0b5187c44821 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 eb9d0ab9d42c..462db340bdaa 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -1206,6 +1206,13 @@ flag { } 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 1ce51afb4fdc..8c38253d6469 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 @@ constructor( 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 650c2d3dd909..fc957545d799 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.Image 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.ColorMatrix 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.IntSize 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 @@ -156,6 +163,8 @@ import com.android.systemui.communal.ui.compose.extensions.observeTaps import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel import com.android.systemui.communal.ui.viewmodel.CommunalViewModel +import com.android.systemui.communal.util.DensityUtils.Companion.adjustedDp +import com.android.systemui.communal.util.DensityUtils.Companion.scalingAdjustment import com.android.systemui.communal.widgets.SmartspaceAppWidgetHostView import com.android.systemui.communal.widgets.WidgetConfigurator import com.android.systemui.res.R @@ -204,13 +213,51 @@ fun CommunalHub( 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 -> @@ -604,11 +651,11 @@ private fun EmptyStateCta( Card( modifier = Modifier.height(hubDimensions.GridHeight).padding(contentPadding), colors = CardDefaults.cardColors(containerColor = Color.Transparent), - border = BorderStroke(3.dp, colors.secondary), - shape = RoundedCornerShape(size = 80.dp) + border = BorderStroke(3.adjustedDp, colors.secondary), + shape = RoundedCornerShape(size = 80.adjustedDp) ) { Column( - modifier = Modifier.fillMaxSize().padding(horizontal = 110.dp), + modifier = Modifier.fillMaxSize().padding(horizontal = 110.adjustedDp), verticalArrangement = Arrangement.spacedBy(Dimensions.Spacing, Alignment.CenterVertically), horizontalAlignment = Alignment.CenterHorizontally, @@ -791,7 +838,7 @@ private fun ToolbarButton( onClick = onClick, colors = ButtonDefaults.outlinedButtonColors( - contentColor = colors.primary, + contentColor = colors.onPrimaryContainer, ), border = BorderStroke(width = 2.0.dp, color = colors.primary), contentPadding = Dimensions.ButtonPadding, @@ -855,22 +902,22 @@ private fun CommunalContent( /** 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 // resize grid items to account for the border. modifier.drawBehind { // 8dp of padding between the widget and the highlight on every side. - val padding = 8.dp.toPx() + val padding = 8.adjustedDp.toPx() drawRoundRect( brush, alpha = alpha, topLeft = Offset(-padding, -padding), size = Size(width = size.width + padding * 2, height = size.height + padding * 2), - cornerRadius = CornerRadius(37.dp.toPx()), - style = Stroke(width = 3.dp.toPx()) + cornerRadius = CornerRadius(37.adjustedDp.toPx()), + style = Stroke(width = 3.adjustedDp.toPx()) ) } ) @@ -890,7 +937,7 @@ private fun CtaTileInViewModeContent( containerColor = colors.primary, contentColor = colors.onPrimary, ), - shape = RoundedCornerShape(68.dp, 34.dp, 68.dp, 34.dp) + shape = RoundedCornerShape(68.adjustedDp, 34.adjustedDp, 68.adjustedDp, 34.adjustedDp) ) { Column( modifier = Modifier.fillMaxSize().padding(vertical = 32.dp, horizontal = 50.dp), @@ -1107,11 +1154,11 @@ fun WidgetConfigureButton( visible = visible, enter = fadeIn(), exit = fadeOut(), - modifier = modifier.padding(16.dp), + modifier = modifier.padding(16.adjustedDp), ) { FilledIconButton( - shape = RoundedCornerShape(16.dp), - modifier = Modifier.size(48.dp), + shape = RoundedCornerShape(16.adjustedDp), + modifier = Modifier.size(48.adjustedDp), colors = IconButtonColors( containerColor = colors.primary, @@ -1124,7 +1171,7 @@ fun WidgetConfigureButton( Icon( imageVector = Icons.Outlined.Edit, contentDescription = stringResource(id = R.string.edit_widget), - modifier = Modifier.padding(12.dp) + modifier = Modifier.padding(12.adjustedDp) ) } } @@ -1187,7 +1234,9 @@ fun PendingWidgetPlaceholder( modifier = modifier.background( MaterialTheme.colorScheme.surfaceVariant, - RoundedCornerShape(dimensionResource(system_app_widget_background_radius)) + RoundedCornerShape( + dimensionResource(system_app_widget_background_radius) * scalingAdjustment + ) ), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally, @@ -1368,11 +1417,11 @@ class Dimensions(val context: Context, val config: Configuration, val density: D val GridTopSpacing: Dp get() { if (config.orientation == Configuration.ORIENTATION_LANDSCAPE) { - return 114.dp + return 114.adjustedDp } else { val windowMetrics = WindowMetricsCalculator.getOrCreate().computeCurrentWindowMetrics(context) - val screenHeight = with(density) { windowMetrics.bounds.height().toDp() } + val screenHeight = with(density) { windowMetrics.bounds.height().adjustedDp } return (screenHeight - CardHeightFull) / 2 } @@ -1381,26 +1430,47 @@ class Dimensions(val context: Context, val config: Configuration, val density: D val GridHeight = CardHeightFull + GridTopSpacing companion object { - val CardHeightFull = 530.dp - val ItemSpacing = 50.dp - val CardHeightHalf = (CardHeightFull - ItemSpacing) / 2 - val CardHeightThird = (CardHeightFull - (2 * ItemSpacing)) / 3 - val CardWidth = 360.dp - val CardOutlineWidth = 3.dp - val Spacing = ItemSpacing / 2 + val CardHeightFull + get() = 530.adjustedDp + + val ItemSpacing + get() = 50.adjustedDp + + val CardHeightHalf + get() = (CardHeightFull - ItemSpacing) / 2 + + val CardHeightThird + get() = (CardHeightFull - (2 * ItemSpacing)) / 3 + + val CardWidth + get() = 360.adjustedDp + + val CardOutlineWidth + get() = 3.adjustedDp + + val Spacing + get() = ItemSpacing / 2 // The sizing/padding of the toolbar in glanceable hub edit mode - val ToolbarPaddingTop = 27.dp - val ToolbarPaddingHorizontal = ItemSpacing - val ToolbarButtonPaddingHorizontal = 24.dp - val ToolbarButtonPaddingVertical = 16.dp + val ToolbarPaddingTop + get() = 27.adjustedDp + + val ToolbarPaddingHorizontal + get() = ItemSpacing + + val ToolbarButtonPaddingHorizontal + get() = 24.adjustedDp + + val ToolbarButtonPaddingVertical + get() = 16.adjustedDp + val ButtonPadding = PaddingValues( vertical = ToolbarButtonPaddingVertical, horizontal = ToolbarButtonPaddingHorizontal, ) - val IconSize = 40.dp - val SlideOffsetY = 30.dp + val IconSize = 40.adjustedDp + val SlideOffsetY = 30.adjustedDp } } 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 86639fa02a92..b40bccb98597 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 @@ constructor( ) { MovableElement( key = IndicationAreaElementKey, - modifier = modifier.shortcutPadding(), + modifier = modifier.indicationAreaPadding(), ) { content { IndicationArea( @@ -210,6 +210,11 @@ constructor( ) .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 000000000000..4850085c4b4e --- /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 7ebc224a00db..0e98b840942b 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.SysuiTestCase; 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 @@ import java.util.Optional; @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 @@ public class BouncerSwipeTouchHandlerTest extends SysuiTestCase { @Mock Region mRegion; + @Mock + CommunalViewModel mCommunalViewModel; + @Captor ArgumentCaptor<Rect> mRectCaptor; @@ -139,9 +146,11 @@ public class BouncerSwipeTouchHandlerTest extends SysuiTestCase { @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 @@ public class BouncerSwipeTouchHandlerTest extends SysuiTestCase { mVelocityTrackerFactory, mLockPatternUtils, mUserTracker, + mCommunalViewModel, mFlingAnimationUtils, mFlingAnimationUtilsClosing, TOUCH_REGION, @@ -201,7 +211,6 @@ public class BouncerSwipeTouchHandlerTest extends SysuiTestCase { 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 7fd9ce20ab92..204d4b09f3ae 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.Flags 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 @@ import org.mockito.kotlin.whenever @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 @@ class ShadeTouchHandlerTest : SysuiTestCase() { fun setup() { mTouchHandler = ShadeTouchHandler( + kosmos.testScope, Optional.of(mCentralSurfaces), mShadeViewController, mDreamManager, + communalViewModel, kosmos.communalSettingsInteractor, TOUCH_HEIGHT ) @@ -75,6 +79,7 @@ class ShadeTouchHandlerTest : SysuiTestCase() { // 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 @@ class ShadeTouchHandlerTest : SysuiTestCase() { // 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 @@ class ShadeTouchHandlerTest : SysuiTestCase() { // 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 @@ class ShadeTouchHandlerTest : SysuiTestCase() { // 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 @@ class ShadeTouchHandlerTest : SysuiTestCase() { // 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 @@ class ShadeTouchHandlerTest : SysuiTestCase() { // 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 @@ class ShadeTouchHandlerTest : SysuiTestCase() { // 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 @@ class ShadeTouchHandlerTest : SysuiTestCase() { } @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 @@ class ShadeTouchHandlerTest : SysuiTestCase() { 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 c28cf348b9fc..a09189efa41b 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 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { 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 86871edf98f5..7a41bc6da176 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 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { 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 857cdce448ed..274880b484cc 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 @@ class InputMethodRepositoryTest : SysuiTestCase() { fun setUp() { MockitoAnnotations.initMocks(this) - whenever(inputMethodManager.getEnabledInputMethodSubtypeList(eq(null), anyBoolean())) - .thenReturn(listOf()) - underTest = InputMethodRepositoryImpl( backgroundDispatcher = kosmos.testDispatcher, @@ -71,10 +68,16 @@ class InputMethodRepositoryTest : SysuiTestCase() { 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 @@ class InputMethodRepositoryTest : SysuiTestCase() { 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 @@ class InputMethodRepositoryTest : SysuiTestCase() { ) ) - 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 @@ class InputMethodRepositoryTest : SysuiTestCase() { testScope.runTest { val displayId = 7 - underTest.showInputMethodPicker(displayId, /* showAuxiliarySubtypes = */ true) + underTest.showInputMethodPicker(displayId, /* showAuxiliarySubtypes= */ true) verify(inputMethodManager) .showInputMethodPickerFromSystem( @@ -118,7 +130,6 @@ class InputMethodRepositoryTest : SysuiTestCase() { } 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 d23ff2a817e9..8e6de2f04279 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 @@ class InputMethodInteractorTest : SysuiTestCase() { 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 61ccd7f27b74..74243208cd98 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 @@ class FromOccludedTransitionInteractorTest : SysuiTestCase() { @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 8810ade1d851..7b87aeb60c13 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 @@ package com.android.systemui.statusbar.notification.collection.coordinator 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 @@ class LockScreenMinimalismCoordinatorTest : SysuiTestCase() { 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 7e9f437c7b6a..3fd9c2160ce2 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.NotificationEntryB 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 @@ import platform.test.runner.parameterized.Parameters @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 keyguardRepository + get() = kosmos.fakeKeyguardRepository + + private val keyguardTransitionRepository + get() = kosmos.fakeKeyguardTransitionRepository + + private val statusBarStateController + get() = kosmos.statusBarStateController - private val headsUpManager: HeadsUpManager = mock() - private val keyguardRepository = FakeKeyguardRepository() - private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository private val notifPipeline: NotifPipeline = mock() - private val statusBarStateController: StatusBarStateController = mock() init { mSetFlagsRule.setFlagsParameterization(flags) @@ -253,7 +268,7 @@ class OriginalUnseenKeyguardCoordinatorTest(flags: FlagsParameterization) : Sysu 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 @@ class OriginalUnseenKeyguardCoordinatorTest(flags: FlagsParameterization) : Sysu 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 @@ class OriginalUnseenKeyguardCoordinatorTest(flags: FlagsParameterization) : Sysu 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 @@ class OriginalUnseenKeyguardCoordinatorTest(flags: FlagsParameterization) : Sysu 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 000000000000..2159b864d2a2 --- /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 8f9da3b2e1e3..9a862fc6a18f 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 @@ class AvalancheControllerTest : SysuiTestCase() { // 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 @@ class AvalancheControllerTest : SysuiTestCase() { // 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 df07b446667a..9005ae3c3d41 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 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase { 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 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase { @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 b91bde4fb417..7a6838a6c9ac 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 @@ class HeadsUpManagerPhoneTest(flags: FlagsParameterization) : BaseHeadsUpManager 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 0f1171774d7f..1342dd05d7f2 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java @@ -287,7 +287,7 @@ public class KeyguardDisplayManager { /** * 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 636bc5b912e5..000000000000 --- 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 000000000000..d5790a44a887 --- /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 baca9594dd2f..000000000000 --- 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 000000000000..06b41de12941 --- /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 a4924d18e0c6..ae21e56eaf4d 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 @@ package com.android.systemui.ambient.touch.dagger 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 @@ interface AmbientTouchModule { 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 b8601ec15132..4be93ccde1d5 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.shared.model.EditModeState 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 @@ abstract class BaseCommunalViewModel( 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 @@ abstract class BaseCommunalViewModel( 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/util/DensityUtils.kt b/packages/SystemUI/src/com/android/systemui/communal/util/DensityUtils.kt new file mode 100644 index 000000000000..57be7b5a0b75 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/util/DensityUtils.kt @@ -0,0 +1,40 @@ +/* + * 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.communal.util + +import android.view.Display +import android.view.WindowManagerGlobal +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp + +/** + * [DensityUtils] helps convert dp defined values to be consistent regardless of the set density. + */ +class DensityUtils { + companion object { + val Int.adjustedDp: Dp + get() = this.dp * scalingAdjustment + + private val windowManagerService = WindowManagerGlobal.getWindowManagerService() + val scalingAdjustment + get() = + windowManagerService?.let { wm -> + wm.getInitialDisplayDensity(Display.DEFAULT_DISPLAY).toFloat() / + wm.getBaseDisplayDensity(Display.DEFAULT_DISPLAY) + } ?: 1F + } +} 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 398576935eed..03ef17b6ec5b 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 @@ constructor( Box( modifier = Modifier.fillMaxSize() - .background(LocalAndroidColorScheme.current.onSecondaryFixed), + .background(LocalAndroidColorScheme.current.surfaceDim), ) { CommunalHub( viewModel = communalViewModel, diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/RoundedCornerEnforcement.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/RoundedCornerEnforcement.kt index abda44be09fa..87aa5e2366ab 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/widgets/RoundedCornerEnforcement.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/RoundedCornerEnforcement.kt @@ -24,6 +24,7 @@ import android.graphics.Rect import android.view.View import android.view.ViewGroup import androidx.core.os.BuildCompat.isAtLeastS +import com.android.systemui.communal.util.DensityUtils import com.android.systemui.res.R import kotlin.math.min @@ -82,7 +83,8 @@ internal object RoundedCornerEnforcement { /** Get the radius of the rounded rectangle defined in the host's resource. */ private fun getOwnedEnforcedRadius(context: Context): Float { val res: Resources = context.resources - return res.getDimension(R.dimen.communal_enforced_rounded_corner_max_radius) + return res.getDimension(R.dimen.communal_enforced_rounded_corner_max_radius) * + DensityUtils.scalingAdjustment } /** diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index d0beb7abc25b..8990505bc5db 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 @@ object Flags { 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 bdc18b322ac0..0e19d8788320 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 @@ package com.android.systemui.inputmethod.data.model * @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 5f316c4495ec..c6fdc32e132a 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 @@ package com.android.systemui.inputmethod.data.repository 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 @@ import kotlinx.coroutines.withContext /** 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 @@ constructor( ) : 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 @@ constructor( } } - 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 @@ constructor( /** * 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 c54aa7f2c6a5..d3ef17859ac5 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 @@ constructor( * 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 d1a84632e0eb..2f41c0b2c1ea 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 @@ import javax.inject.Inject 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 @@ const val UNLOCK_ANIMATION_SURFACE_BEHIND_START_DELAY_MS = 67L * [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 @@ class KeyguardUnlockAnimationController @Inject constructor( 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 @@ class KeyguardUnlockAnimationController @Inject constructor( */ 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 @@ class KeyguardUnlockAnimationController @Inject constructor( * transition, but that's okay! */ override fun setLauncherUnlockController( - activityClass: String, - callback: ILauncherUnlockAnimationController? + activityClass: String, + callback: ILauncherUnlockAnimationController? ) { launcherActivityClass = activityClass launcherUnlockController = callback @@ -274,8 +273,7 @@ class KeyguardUnlockAnimationController @Inject constructor( * 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 @@ class KeyguardUnlockAnimationController @Inject constructor( */ 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 @@ class KeyguardUnlockAnimationController @Inject constructor( * 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 @@ class KeyguardUnlockAnimationController @Inject constructor( * 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 @@ class KeyguardUnlockAnimationController @Inject constructor( 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) - } - override fun onAnimationEnd(animation: Animator) { - Log.d(TAG, "wallpaperCannedUnlockAnimator#onAnimationEnd") - keyguardViewMediator.get().exitKeyguardAndFinishSurfaceBehindRemoteAnimation( - false /* cancelled */) - Trace.asyncTraceEnd(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) + } } - }) + ) } if (fasterUnlockTransition()) { @@ -405,7 +415,9 @@ class KeyguardUnlockAnimationController @Inject constructor( interpolator = Interpolators.LINEAR addUpdateListener { valueAnimator: ValueAnimator -> setWallpaperAppearAmount( - valueAnimator.animatedValue as Float, closingWallpaperTargets) + valueAnimator.animatedValue as Float, + closingWallpaperTargets + ) } } } @@ -418,15 +430,19 @@ class KeyguardUnlockAnimationController @Inject constructor( 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 @@ class KeyguardUnlockAnimationController @Inject constructor( 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 @@ class KeyguardUnlockAnimationController @Inject constructor( 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 @@ class KeyguardUnlockAnimationController @Inject constructor( 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 @@ class KeyguardUnlockAnimationController @Inject constructor( * changed. */ override fun onKeyguardGoingAwayChanged() { - if (keyguardStateController.isKeyguardGoingAway && - !statusBarStateController.leaveOpenOnKeyguardHide()) { + if ( + keyguardStateController.isKeyguardGoingAway && + !statusBarStateController.leaveOpenOnKeyguardHide() + ) { prepareForInWindowLauncherAnimations() } @@ -489,16 +508,22 @@ class KeyguardUnlockAnimationController @Inject constructor( // 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 @@ class KeyguardUnlockAnimationController @Inject constructor( // 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) - - // 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) - } + 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) + + // 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 @@ class KeyguardUnlockAnimationController @Inject constructor( requestedShowSurfaceBehindKeyguard: Boolean ) { if (surfaceTransactionApplier == null) { - surfaceTransactionApplier = SyncRtSurfaceTransactionApplier( - keyguardViewController.viewRootImpl.view) + surfaceTransactionApplier = + SyncRtSurfaceTransactionApplier(keyguardViewController.viewRootImpl.view) } surfaceBehindRemoteAnimationTargets = targets @@ -603,8 +632,10 @@ class KeyguardUnlockAnimationController @Inject constructor( // 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 @@ class KeyguardUnlockAnimationController @Inject constructor( 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 @@ class KeyguardUnlockAnimationController @Inject constructor( } // 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 @@ class KeyguardUnlockAnimationController @Inject constructor( 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 @@ class KeyguardUnlockAnimationController @Inject constructor( } 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 @@ class KeyguardUnlockAnimationController @Inject constructor( * 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 @@ class KeyguardUnlockAnimationController @Inject constructor( 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 @@ class KeyguardUnlockAnimationController @Inject constructor( 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 @@ class KeyguardUnlockAnimationController @Inject constructor( // 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 @@ class KeyguardUnlockAnimationController @Inject constructor( // 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 @@ class KeyguardUnlockAnimationController @Inject constructor( 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 @@ class KeyguardUnlockAnimationController @Inject constructor( } // 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 @@ class KeyguardUnlockAnimationController @Inject constructor( * 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 @@ class KeyguardUnlockAnimationController @Inject constructor( } } 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 @@ class KeyguardUnlockAnimationController @Inject constructor( 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 @@ class KeyguardUnlockAnimationController @Inject constructor( // 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 @@ class KeyguardUnlockAnimationController @Inject constructor( } 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 @@ class KeyguardUnlockAnimationController @Inject constructor( 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 @@ class KeyguardUnlockAnimationController @Inject constructor( 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 @@ class KeyguardUnlockAnimationController @Inject constructor( // 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 @@ class KeyguardUnlockAnimationController @Inject constructor( // 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 @@ class KeyguardUnlockAnimationController @Inject constructor( 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 @@ class KeyguardUnlockAnimationController @Inject constructor( * 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 6c5337418d02..cc4a92cb516c 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 @@ public class MediaProjectionPermissionActivity extends Activity } } - 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 @@ public class MediaProjectionPermissionActivity extends Activity 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 @@ public class MediaProjectionPermissionActivity extends Activity 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 dd2dbf338e40..f8b3ce1ddc53 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 androidx.annotation.WorkerThread 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 @@ constructor( 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 46c586163ce8..a8a78a91097c 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 @@ public class RecordingController 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 b468d0e75a7a..25d1cd17d092 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.lifecycleScope 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 @@ constructor( } 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 a6605f652ff3..a621b2a02c5d 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 @@ package com.android.systemui.statusbar.notification.collection.coordinator 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_ONGOING 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 @@ import kotlinx.coroutines.launch 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 @@ constructor( 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 @@ constructor( // 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 @@ constructor( 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 @@ constructor( 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 0103fffeb725..bfea2ba6b839 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.HeadsUpManager 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.delay 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 @@ import kotlinx.coroutines.yield 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 @@ constructor( // 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 948a3c2f65b0..90a05ef99586 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 @@ constructor( ) } } + + 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 8f2ad40e7130..41b69a733bd3 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 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb 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 a88c6d707da4..a96382672235 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 @@ import javax.inject.Inject 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 @@ constructor( } /** 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 @@ constructor( ) } } - logState("after $fn") + outcome += getStateStr() + headsUpManagerLogger.logAvalancheUpdate(caller, isEnabled, key, outcome) } @VisibleForTesting @@ -169,32 +175,37 @@ constructor( * 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 @@ constructor( 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 @@ constructor( } 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 6ffb162ad0f2..80c595fb638b 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 @@ class HeadsUpManagerLogger @Inject constructor( }) } + 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 f726aae318df..e251ab50e3c7 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.statusbar.policy.KeyguardStateController 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.verify 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 @@ import java.util.function.Predicate 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 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { ) 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 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { // 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 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { ) // 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 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { @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 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { * 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 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { * 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 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { 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 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { 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 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { 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 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { } } - /** - * 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 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { 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 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { 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 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { 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 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { 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 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { 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 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { 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 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { } @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 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { 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 7dd802878674..f884b874cca8 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 android.widget.TextView 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.statusbar.phone.SystemUIDialog 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 @@ class MediaProjectionPermissionDialogDelegateTest : SysuiTestCase() { 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 @@ class MediaProjectionPermissionDialogDelegateTest : SysuiTestCase() { 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 ce1a885098d9..8d84c3e7392e 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 @@ class RecordIssueDialogDelegateTest : SysuiTestCase() { .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 @@ class RecordIssueDialogDelegateTest : SysuiTestCase() { .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 477c50b58519..6b16e78436d4 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.content.Intent; 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.MediaProjectionMetricsLogger; 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 @@ public class RecordingControllerTest extends SysuiTestCase { private FakeFeatureFlags mFeatureFlags; private RecordingController mController; - private TestSystemUIDialogFactory mDialogFactory; private static final int USER_ID = 10; @@ -120,14 +114,6 @@ public class RecordingControllerTest extends SysuiTestCase { 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 @@ public class RecordingControllerTest extends SysuiTestCase { @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 @@ public class RecordingControllerTest extends SysuiTestCase { } @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 @@ public class RecordingControllerTest extends SysuiTestCase { @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 @@ public class RecordingControllerTest extends SysuiTestCase { @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 @@ public class RecordingControllerTest extends SysuiTestCase { /* 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 cc8d7d532bda..11b0bdf3effd 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.SysuiTestCase 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.Mock 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 @@ class ScreenRecordPermissionDialogDelegateTest : SysuiTestCase() { 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 39085295fbac..000000000000 --- 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 1060b62f071f..c36a046532bd 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.GroupExpans 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 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase { @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 31f93b402a75..af5e60e9cd01 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.any; 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.navigationbar.TaskbarDelegate; 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 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { @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 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { mSelectedUserInteractor, () -> mock(KeyguardSurfaceBehindInteractor.class), mock(JavaAdapter.class), - () -> mock(SceneInteractor.class), + () -> mSceneInteractor, mock(StatusBarKeyguardViewManagerInteractor.class), () -> mDeviceEntryInteractor) { @Override @@ -270,21 +273,23 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { } @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 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { mSelectedUserInteractor, () -> mock(KeyguardSurfaceBehindInteractor.class), mock(JavaAdapter.class), - () -> mock(SceneInteractor.class), + () -> mSceneInteractor, mock(StatusBarKeyguardViewManagerInteractor.class), () -> mDeviceEntryInteractor) { @Override @@ -1104,9 +1109,9 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { @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 8e4461dd5b1e..444baa048a03 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 @@ class FakeInputMethodRepository : InputMethodRepository { } 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 06668201a925..8614fc905934 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.data.repository.shadeRepository 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 @@ class KosmosJavaAdapter() { 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 77d97bb7cbe9..933ebf014fa1 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 @@ package com.android.systemui.statusbar.notification.collection.coordinator 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 c1e0419e5609..b19e221d099c 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/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java index f821e00d56ae..69478bbd0d44 100644 --- a/services/core/java/com/android/server/accounts/AccountManagerService.java +++ b/services/core/java/com/android/server/accounts/AccountManagerService.java @@ -4616,6 +4616,9 @@ public class AccountManagerService opPackageName, visibleAccountTypes, includeUserManagedNotVisible); + } catch (SQLiteException e) { + Log.w(TAG, "Could not get accounts for user " + userId, e); + return new Account[]{}; } finally { restoreCallingIdentity(identityToken); } @@ -4703,12 +4706,17 @@ public class AccountManagerService public Account[] getSharedAccountsAsUser(int userId) { userId = handleIncomingUser(userId); - UserAccounts accounts = getUserAccounts(userId); - synchronized (accounts.dbLock) { - List<Account> accountList = accounts.accountsDb.getSharedAccounts(); - Account[] accountArray = new Account[accountList.size()]; - accountList.toArray(accountArray); - return accountArray; + try { + UserAccounts accounts = getUserAccounts(userId); + synchronized (accounts.dbLock) { + List<Account> accountList = accounts.accountsDb.getSharedAccounts(); + Account[] accountArray = new Account[accountList.size()]; + accountList.toArray(accountArray); + return accountArray; + } + } catch (SQLiteException e) { + Log.w(TAG, "Could not get shared accounts for user " + userId, e); + return new Account[]{}; } } diff --git a/services/core/java/com/android/server/am/AppExitInfoTracker.java b/services/core/java/com/android/server/am/AppExitInfoTracker.java index 47b65eb885ab..1f88657e5dbc 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 @@ public final class AppExitInfoTracker { } /** 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 @@ public final class AppExitInfoTracker { 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 @@ public final class AppExitInfoTracker { */ @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 @@ public final class AppExitInfoTracker { immediateLog = true; } } + if (rssKb != null) { + info.setRss(rssKb.longValue()); + } scheduleLogToStatsdLocked(info, immediateLog); } @@ -523,7 +526,7 @@ public final class AppExitInfoTracker { */ @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 @@ public final class AppExitInfoTracker { // 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 @@ public final class AppExitInfoTracker { 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 @@ public final class AppExitInfoTracker { } } - 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 @@ public final class AppExitInfoTracker { // 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 779aabecbb99..726e8275ae2d 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 @@ public final class ProcessList { 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 f6f23d9f01d1..608fb35cea9a 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 @@ public final class ExternalDisplayStatsService { 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 @@ public final class ExternalDisplayStatsService { 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 49934126ab8c..1d1a178ff20b 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_SELECT 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 @@ class KeyboardLayoutManager implements InputManager.InputDeviceListener { 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 6b7b702b9157..5e45b4c2d5af 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 @@ public class PackageArchiver { 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 @@ public class PackageArchiver { /** * 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 @@ public class PackageArchiver { // 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/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index a84598dd73dc..1c14c5dbc43a 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -5522,7 +5522,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { final int procCount = procs.size(); for (int i = 0; i < procCount; i++) { final int procUid = procs.keyAt(i); - if (UserHandle.isApp(procUid) || !UserHandle.isSameUser(procUid, uid)) { + if (!UserHandle.isCore(procUid) || !UserHandle.isSameUser(procUid, uid)) { // Don't use an app process or different user process for system component. continue; } 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 98ba9aee406a..abb354b1414a 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 @@ public class ExternalDisplayStatsServiceTest { } @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 @@ public class ExternalDisplayStatsServiceTest { 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 adcbf5c9d059..194bf4ba73f3 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 @@ public class ApplicationExitInfoTest { 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 @@ public class ApplicationExitInfoTest { null, // subReason 0, // status app2Pss1, // pss - app2Rss1, // rss + lmkd_reported_rss, // rss IMPORTANCE_CACHED, // importance null); // description @@ -499,6 +503,11 @@ public class ApplicationExitInfoTest { 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 @@ public class ApplicationExitInfoTest { 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 d5d284783978..b92af876ed22 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.InputChannel; 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 @@ public class SystemServicesTestRule implements TestRule { // 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 4c531b8f9ee0..a4085e5315a4 100644 --- a/tests/inputmethod/ConcurrentMultiSessionImeTest/Android.bp +++ b/tests/inputmethod/ConcurrentMultiSessionImeTest/Android.bp @@ -23,6 +23,7 @@ android_test { resource_dirs: ["res"], libs: ["android.test.runner"], static_libs: [ + "androidx.core_core", "androidx.test.ext.junit", "androidx.test.rules", "compatibility-device-util-axt", |