Merge "Add trace points for showing panels" into tm-qpr-dev
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index e3bf2d4..f62a487 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -814,8 +814,8 @@
}
/**
- * Activity level {@link android.content.pm.PackageManager.Property PackageManager
- * .Property} for an app to inform the system that the activity can be opted-in or opted-out
+ * Application level {@link android.content.pm.PackageManager.Property PackageManager
+ * .Property} for an app to inform the system that the app can be opted-in or opted-out
* from the compatibility treatment that avoids {@link
* android.app.Activity#setRequestedOrientation} loops. The loop can be trigerred by
* ignoreRequestedOrientation display setting enabled on the device or by the landscape natural
@@ -833,17 +833,17 @@
* <li>Camera compatibility force rotation treatment is active for the package.
* </ul>
*
- * <p>Setting this property to {@code false} informs the system that the activity must be
+ * <p>Setting this property to {@code false} informs the system that the app must be
* opted-out from the compatibility treatment even if the device manufacturer has opted the app
* into the treatment.
*
* <p><b>Syntax:</b>
* <pre>
- * <activity>
+ * <application>
* <property
* android:name="android.window.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION"
* android:value="true|false"/>
- * </activity>
+ * </application>
* </pre>
*
* @hide
@@ -853,8 +853,8 @@
"android.window.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION";
/**
- * Activity level {@link android.content.pm.PackageManager.Property PackageManager
- * .Property} for an app to inform the system that the activity should be excluded from the
+ * Application level {@link android.content.pm.PackageManager.Property PackageManager
+ * .Property} for an app to inform the system that the app should be excluded from the
* camera compatibility force rotation treatment.
*
* <p>The camera compatibility treatment aligns orientations of portrait app window and natural
@@ -879,11 +879,11 @@
*
* <p><b>Syntax:</b>
* <pre>
- * <activity>
+ * <application>
* <property
* android:name="android.window.PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION"
* android:value="true|false"/>
- * </activity>
+ * </application>
* </pre>
*
* @hide
@@ -893,8 +893,8 @@
"android.window.PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION";
/**
- * Activity level {@link android.content.pm.PackageManager.Property PackageManager
- * .Property} for an app to inform the system that the activity should be excluded
+ * Application level {@link android.content.pm.PackageManager.Property PackageManager
+ * .Property} for an app to inform the system that the app should be excluded
* from the activity "refresh" after the camera compatibility force rotation treatment.
*
* <p>The camera compatibility treatment aligns orientations of portrait app window and natural
@@ -926,11 +926,11 @@
*
* <p><b>Syntax:</b>
* <pre>
- * <activity>
+ * <application>
* <property
* android:name="android.window.PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH"
* android:value="true|false"/>
- * </activity>
+ * </application>
* </pre>
*
* @hide
@@ -940,7 +940,7 @@
"android.window.PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH";
/**
- * Activity level {@link android.content.pm.PackageManager.Property PackageManager
+ * Application level {@link android.content.pm.PackageManager.Property PackageManager
* .Property} for an app to inform the system that the activity should be or shouldn't be
* "refreshed" after the camera compatibility force rotation treatment using "paused ->
* resumed" cycle rather than "stopped -> resumed".
@@ -976,11 +976,11 @@
*
* <p><b>Syntax:</b>
* <pre>
- * <activity>
+ * <application>
* <property
* android:name="android.window.PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE"
* android:value="true|false"/>
- * </activity>
+ * </application>
* </pre>
*
* @hide
@@ -990,23 +990,23 @@
"android.window.PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE";
/**
- * Activity level {@link android.content.pm.PackageManager.Property PackageManager
- * .Property} for an app to inform the system that the activity should be excluded from the
+ * Application level {@link android.content.pm.PackageManager.Property PackageManager
+ * .Property} for an app to inform the system that the app should be excluded from the
* compatibility override for orientation set by the device manufacturer.
*
* <p>With this property set to {@code true} or unset, device manufacturers can override
- * orientation for the activity using their discretion to improve display compatibility.
+ * orientation for the app using their discretion to improve display compatibility.
*
* <p>With this property set to {@code false}, device manufactured per-app override for
* orientation won't be applied.
*
* <p><b>Syntax:</b>
* <pre>
- * <activity>
+ * <application>
* <property
* android:name="android.window.PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE"
* android:value="true|false"/>
- * </activity>
+ * </application>
* </pre>
*
* @hide
@@ -1016,8 +1016,8 @@
"android.window.PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE";
/**
- * Activity level {@link android.content.pm.PackageManager.Property PackageManager
- * .Property} for an app to inform the system that the activity should be opted-out from the
+ * Application level {@link android.content.pm.PackageManager.Property PackageManager
+ * .Property} for an app to inform the system that the app should be opted-out from the
* compatibility override that fixes display orientation to landscape natural orientation when
* an activity is fullscreen.
*
@@ -1047,11 +1047,11 @@
*
* <p><b>Syntax:</b>
* <pre>
- * <activity>
+ * <application>
* <property
* android:name="android.window.PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE"
* android:value="true|false"/>
- * </activity>
+ * </application>
* </pre>
*
* @hide
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index b10df60..31903e2 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -315,6 +315,7 @@
<protected-broadcast android:name="android.media.MASTER_BALANCE_CHANGED_ACTION" />
<protected-broadcast android:name="android.media.SCO_AUDIO_STATE_CHANGED" />
<protected-broadcast android:name="android.media.ACTION_SCO_AUDIO_STATE_UPDATED" />
+ <protected-broadcast android:name="com.android.server.audio.action.CHECK_MUSIC_ACTIVE" />
<protected-broadcast android:name="android.intent.action.MEDIA_REMOVED" />
<protected-broadcast android:name="android.intent.action.MEDIA_UNMOUNTED" />
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 73518dc..87ece55 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -984,6 +984,9 @@
<!-- Boolean indicating whether light mode is allowed when DWB is turned on. -->
<bool name="config_displayWhiteBalanceLightModeAllowed">true</bool>
+ <!-- Duration, in milliseconds, of the display white balance animated transitions. -->
+ <integer name="config_displayWhiteBalanceTransitionTime">3000</integer>
+
<!-- Device states where the sensor based rotation values should be reversed around the Z axis
for the default display.
TODO(b/265312193): Remove this workaround when this bug is fixed.-->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index b7621da..a74c787 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3435,6 +3435,7 @@
<java-symbol type="array" name="config_displayWhiteBalanceDisplayPrimaries" />
<java-symbol type="array" name="config_displayWhiteBalanceDisplayNominalWhite" />
<java-symbol type="bool" name="config_displayWhiteBalanceLightModeAllowed" />
+ <java-symbol type="integer" name="config_displayWhiteBalanceTransitionTime" />
<!-- Device states where the sensor based rotation values should be reversed around the Z axis
for the default display.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index 2363092..aaeef19 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -82,7 +82,7 @@
/** Flag for U animation features */
public static boolean IS_U_ANIMATION_ENABLED =
SystemProperties.getInt("persist.wm.debug.predictive_back_anim",
- SETTING_VALUE_OFF) == SETTING_VALUE_ON;
+ SETTING_VALUE_ON) == SETTING_VALUE_ON;
/** Predictive back animation developer option */
private final AtomicBoolean mEnableAnimations = new AtomicBoolean(false);
// TODO (b/241808055) Find a appropriate time to remove during refactor
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
index 2534498..e24c228 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
@@ -126,7 +126,7 @@
private Icon mIcon;
private boolean mIsBubble;
private boolean mIsTextChanged;
- private boolean mIsClearable;
+ private boolean mIsDismissable;
private boolean mShouldSuppressNotificationDot;
private boolean mShouldSuppressNotificationList;
private boolean mShouldSuppressPeek;
@@ -181,7 +181,7 @@
@VisibleForTesting(visibility = PRIVATE)
public Bubble(@NonNull final String key, @NonNull final ShortcutInfo shortcutInfo,
final int desiredHeight, final int desiredHeightResId, @Nullable final String title,
- int taskId, @Nullable final String locus, boolean isClearable, Executor mainExecutor,
+ int taskId, @Nullable final String locus, boolean isDismissable, Executor mainExecutor,
final Bubbles.BubbleMetadataFlagListener listener) {
Objects.requireNonNull(key);
Objects.requireNonNull(shortcutInfo);
@@ -190,7 +190,7 @@
mKey = key;
mGroupKey = null;
mLocusId = locus != null ? new LocusId(locus) : null;
- mIsClearable = isClearable;
+ mIsDismissable = isDismissable;
mFlags = 0;
mUser = shortcutInfo.getUserHandle();
mPackageName = shortcutInfo.getPackage();
@@ -248,8 +248,8 @@
}
@Hide
- public boolean isClearable() {
- return mIsClearable;
+ public boolean isDismissable() {
+ return mIsDismissable;
}
/**
@@ -533,7 +533,7 @@
mDeleteIntent = entry.getBubbleMetadata().getDeleteIntent();
}
- mIsClearable = entry.isClearable();
+ mIsDismissable = entry.isDismissable();
mShouldSuppressNotificationDot = entry.shouldSuppressNotificationDot();
mShouldSuppressNotificationList = entry.shouldSuppressNotificationList();
mShouldSuppressPeek = entry.shouldSuppressPeek();
@@ -612,7 +612,7 @@
* Whether this notification should be shown in the shade.
*/
boolean showInShade() {
- return !shouldSuppressNotification() || !mIsClearable;
+ return !shouldSuppressNotification() || !mIsDismissable;
}
/**
@@ -877,7 +877,7 @@
pw.print(" desiredHeight: "); pw.println(getDesiredHeightString());
pw.print(" suppressNotif: "); pw.println(shouldSuppressNotification());
pw.print(" autoExpand: "); pw.println(shouldAutoExpand());
- pw.print(" isClearable: "); pw.println(mIsClearable);
+ pw.print(" isDismissable: "); pw.println(mIsDismissable);
pw.println(" bubbleMetadataFlagListener null: " + (mBubbleMetadataFlagListener == null));
if (mExpandedView != null) {
mExpandedView.dump(pw);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDataRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDataRepository.kt
index e3aefa5..e37c785 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDataRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDataRepository.kt
@@ -110,7 +110,7 @@
b.title,
b.taskId,
b.locusId?.id,
- b.isClearable
+ b.isDismissable
)
}
}
@@ -206,7 +206,7 @@
entity.title,
entity.taskId,
entity.locus,
- entity.isClearable,
+ entity.isDismissable,
mainExecutor,
bubbleMetadataFlagListener
)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleEntry.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleEntry.java
index 5f42826..afe19c4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleEntry.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleEntry.java
@@ -38,18 +38,18 @@
private StatusBarNotification mSbn;
private Ranking mRanking;
- private boolean mIsClearable;
+ private boolean mIsDismissable;
private boolean mShouldSuppressNotificationDot;
private boolean mShouldSuppressNotificationList;
private boolean mShouldSuppressPeek;
public BubbleEntry(@NonNull StatusBarNotification sbn,
- Ranking ranking, boolean isClearable, boolean shouldSuppressNotificationDot,
+ Ranking ranking, boolean isDismissable, boolean shouldSuppressNotificationDot,
boolean shouldSuppressNotificationList, boolean shouldSuppressPeek) {
mSbn = sbn;
mRanking = ranking;
- mIsClearable = isClearable;
+ mIsDismissable = isDismissable;
mShouldSuppressNotificationDot = shouldSuppressNotificationDot;
mShouldSuppressNotificationList = shouldSuppressNotificationList;
mShouldSuppressPeek = shouldSuppressPeek;
@@ -115,9 +115,9 @@
return mRanking.canBubble();
}
- /** @return true if this notification is clearable. */
- public boolean isClearable() {
- return mIsClearable;
+ /** @return true if this notification can be dismissed. */
+ public boolean isDismissable() {
+ return mIsDismissable;
}
/** @return true if {@link Policy#SUPPRESSED_EFFECT_BADGE} set for this notification. */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubbleEntity.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubbleEntity.kt
index f3abc27..9b2e263 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubbleEntity.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubbleEntity.kt
@@ -28,5 +28,5 @@
val title: String? = null,
val taskId: Int,
val locus: String? = null,
- val isClearable: Boolean = false
+ val isDismissable: Boolean = false
)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubbleXmlHelper.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubbleXmlHelper.kt
index 14c053c..48d8ccf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubbleXmlHelper.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubbleXmlHelper.kt
@@ -43,9 +43,7 @@
private const val ATTR_TITLE = "t"
private const val ATTR_TASK_ID = "tid"
private const val ATTR_LOCUS = "l"
-
-// TODO rename it to dismissable to follow NotificationEntry namings
-private const val ATTR_CLEARABLE = "d"
+private const val ATTR_DISMISSABLE = "d"
/**
* Writes the bubbles in xml format into given output stream.
@@ -87,7 +85,7 @@
bubble.title?.let { serializer.attribute(null, ATTR_TITLE, it) }
serializer.attribute(null, ATTR_TASK_ID, bubble.taskId.toString())
bubble.locus?.let { serializer.attribute(null, ATTR_LOCUS, it) }
- serializer.attribute(null, ATTR_CLEARABLE, bubble.isClearable.toString())
+ serializer.attribute(null, ATTR_DISMISSABLE, bubble.isDismissable.toString())
serializer.endTag(null, TAG_BUBBLE)
} catch (e: IOException) {
throw RuntimeException(e)
@@ -147,7 +145,7 @@
parser.getAttributeWithName(ATTR_TITLE),
parser.getAttributeWithName(ATTR_TASK_ID)?.toInt() ?: INVALID_TASK_ID,
parser.getAttributeWithName(ATTR_LOCUS),
- parser.getAttributeWithName(ATTR_CLEARABLE)?.toBoolean() ?: false
+ parser.getAttributeWithName(ATTR_DISMISSABLE)?.toBoolean() ?: false
)
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubbleXmlHelperTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubbleXmlHelperTest.kt
index c02e2d1..3bfbcd2 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubbleXmlHelperTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubbleXmlHelperTest.kt
@@ -35,7 +35,7 @@
private val user0Bubbles = listOf(
BubbleEntity(0, "com.example.messenger", "shortcut-1", "0k1", 120, 0, null, 1,
- isClearable = true),
+ isDismissable = true),
BubbleEntity(10, "com.example.chat", "alice and bob", "0k2", 0, 16537428, "title", 2,
null),
BubbleEntity(0, "com.example.messenger", "shortcut-2", "0k3", 120, 0, null,
@@ -44,7 +44,7 @@
private val user1Bubbles = listOf(
BubbleEntity(1, "com.example.messenger", "shortcut-1", "1k1", 120, 0, null, 3,
- isClearable = true),
+ isDismissable = true),
BubbleEntity(12, "com.example.chat", "alice and bob", "1k2", 0, 16537428, "title", 4,
null),
BubbleEntity(1, "com.example.messenger", "shortcut-2", "1k3", 120, 0, null,
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/LargeScreenSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/LargeScreenSettings.java
new file mode 100644
index 0000000..bdd4869
--- /dev/null
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/LargeScreenSettings.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.provider.settings.backup;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.util.DisplayMetrics;
+import android.view.WindowManager;
+
+/** Settings that should not be restored when target device is a large screen
+ * i.e. tablets and foldables in unfolded state
+ */
+public class LargeScreenSettings {
+ private static final float LARGE_SCREEN_MIN_DPS = 600;
+ private static final String LARGE_SCREEN_DO_NOT_RESTORE = "accelerometer_rotation";
+
+ /**
+ * Autorotation setting should not be restored when the target device is a large screen.
+ * (b/243489549)
+ */
+ public static boolean doNotRestoreIfLargeScreenSetting(String key, Context context) {
+ return isLargeScreen(context) && LARGE_SCREEN_DO_NOT_RESTORE.equals(key);
+ }
+
+ // copied from systemui/shared/...Utilities.java
+ // since we don't want to add compile time dependency on sys ui package
+ private static boolean isLargeScreen(Context context) {
+ final WindowManager windowManager = context.getSystemService(WindowManager.class);
+ final Rect bounds = windowManager.getCurrentWindowMetrics().getBounds();
+ float smallestWidth = dpiFromPx(Math.min(bounds.width(), bounds.height()),
+ context.getResources().getConfiguration().densityDpi);
+ return smallestWidth >= LARGE_SCREEN_MIN_DPS;
+ }
+
+ private static float dpiFromPx(float size, int densityDpi) {
+ float densityRatio = (float) densityDpi / DisplayMetrics.DENSITY_DEFAULT;
+ return (size / densityRatio);
+ }
+}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
index d3afccc..d0055d7 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
@@ -37,6 +37,7 @@
import android.provider.Settings;
import android.provider.settings.backup.DeviceSpecificSettings;
import android.provider.settings.backup.GlobalSettings;
+import android.provider.settings.backup.LargeScreenSettings;
import android.provider.settings.backup.SecureSettings;
import android.provider.settings.backup.SystemSettings;
import android.provider.settings.validators.GlobalSettingsValidators;
@@ -812,6 +813,12 @@
continue;
}
+ if (LargeScreenSettings.doNotRestoreIfLargeScreenSetting(key, getBaseContext())) {
+ Log.i(TAG, "Skipping restore for setting " + key + " as the target device "
+ + "is a large screen (i.e tablet or foldable in unfolded state)");
+ continue;
+ }
+
String value = null;
boolean hasValueToRestore = false;
if (cachedEntries.indexOfKey(key) >= 0) {
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index b6e006f..e96aead5 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -674,6 +674,7 @@
<intent-filter>
<action android:name="android.telecom.action.SHOW_SWITCH_TO_WORK_PROFILE_FOR_CALL_DIALOG" />
<category android:name="android.intent.category.DEFAULT" />
+ <data android:scheme="tel" />
</intent-filter>
</activity>
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
index 7645dec..d110850 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
@@ -85,7 +85,8 @@
animations = DefaultClockAnimations(dozeFraction, foldFraction)
events.onColorPaletteChanged(resources)
events.onTimeZoneChanged(TimeZone.getDefault())
- events.onTimeTick()
+ smallClock.events.onTimeTick()
+ largeClock.events.onTimeTick()
}
open inner class DefaultClockFaceController(
@@ -109,6 +110,8 @@
override val events =
object : ClockFaceEvents {
+ override fun onTimeTick() = view.refreshTime()
+
override fun onRegionDarknessChanged(isRegionDark: Boolean) {
this@DefaultClockFaceController.isRegionDark = isRegionDark
updateColor()
@@ -169,8 +172,6 @@
}
inner class DefaultClockEvents : ClockEvents {
- override fun onTimeTick() = clocks.forEach { it.refreshTime() }
-
override fun onTimeFormatChanged(is24Hr: Boolean) =
clocks.forEach { it.refreshFormat(is24Hr) }
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
index a2a0709..7727589 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
@@ -66,7 +66,8 @@
events.onColorPaletteChanged(resources)
animations.doze(dozeFraction)
animations.fold(foldFraction)
- events.onTimeTick()
+ smallClock.events.onTimeTick()
+ largeClock.events.onTimeTick()
}
/** Optional method for dumping debug information */
@@ -87,9 +88,6 @@
/** Events that should call when various rendering parameters change */
interface ClockEvents {
- /** Call every time tick */
- fun onTimeTick() {}
-
/** Call whenever timezone changes */
fun onTimeZoneChanged(timeZone: TimeZone) {}
@@ -131,6 +129,13 @@
/** Events that have specific data about the related face */
interface ClockFaceEvents {
+ /** Call every time tick */
+ fun onTimeTick() {}
+
+ /** Expected interval between calls to onTimeTick. Can always reduce to PER_MINUTE in AOD. */
+ val tickRate: ClockTickRate
+ get() = ClockTickRate.PER_MINUTE
+
/** Region Darkness specific to the clock face */
fun onRegionDarknessChanged(isDark: Boolean) {}
@@ -150,6 +155,13 @@
fun onTargetRegionChanged(targetRegion: Rect?) {}
}
+/** Tick rates for clocks */
+enum class ClockTickRate(val value: Int) {
+ PER_MINUTE(2), // Update the clock once per minute.
+ PER_SECOND(1), // Update the clock once per second.
+ PER_FRAME(0), // Update the clock every second.
+}
+
/** Some data about a clock design */
data class ClockMetadata(
val clockId: ClockId,
diff --git a/packages/SystemUI/res/layout/screenshot_detection_notice.xml b/packages/SystemUI/res/layout/screenshot_detection_notice.xml
new file mode 100644
index 0000000..fc936c0
--- /dev/null
+++ b/packages/SystemUI/res/layout/screenshot_detection_notice.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/screenshot_detection_notice"
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ android:padding="12dp"
+ android:visibility="gone">
+
+ <TextView
+ android:id="@+id/screenshot_detection_notice_text"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:lineHeight="24sp"
+ android:textSize="18sp" />
+</FrameLayout>
diff --git a/packages/SystemUI/res/layout/screenshot_static.xml b/packages/SystemUI/res/layout/screenshot_static.xml
index 7e8bc2c..a748e29 100644
--- a/packages/SystemUI/res/layout/screenshot_static.xml
+++ b/packages/SystemUI/res/layout/screenshot_static.xml
@@ -134,7 +134,7 @@
android:orientation="horizontal"
app:layout_constraintGuide_end="0dp" />
- <androidx.constraintlayout.widget.ConstraintLayout
+ <FrameLayout
android:id="@+id/screenshot_message_container"
android:layout_width="0dp"
android:layout_height="wrap_content"
@@ -145,10 +145,12 @@
android:paddingVertical="@dimen/overlay_action_container_padding_vertical"
android:elevation="4dp"
android:background="@drawable/action_chip_container_background"
- android:visibility="invisible"
+ android:visibility="gone"
app:layout_constraintTop_toBottomOf="@id/guideline"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
>
- </androidx.constraintlayout.widget.ConstraintLayout>
+ <include layout="@layout/screenshot_work_profile_first_run" />
+ <include layout="@layout/screenshot_detection_notice" />
+ </FrameLayout>
</com.android.systemui.screenshot.DraggableConstraintLayout>
diff --git a/packages/SystemUI/res/layout/screenshot_work_profile_first_run.xml b/packages/SystemUI/res/layout/screenshot_work_profile_first_run.xml
index c794d91..392d845 100644
--- a/packages/SystemUI/res/layout/screenshot_work_profile_first_run.xml
+++ b/packages/SystemUI/res/layout/screenshot_work_profile_first_run.xml
@@ -1,41 +1,41 @@
<?xml version="1.0" encoding="utf-8"?>
-<merge
+<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto">
+ android:id="@+id/work_profile_first_run"
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ android:paddingStart="16dp"
+ android:paddingEnd="4dp"
+ android:paddingVertical="16dp"
+ android:visibility="gone">
<ImageView
android:id="@+id/screenshot_message_icon"
- android:layout_width="48dp"
- android:layout_height="48dp"
- android:paddingEnd="4dp"
- android:src="@drawable/ic_work_app_badge"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintEnd_toStartOf="@id/screenshot_message_content"
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintBottom_toBottomOf="parent"/>
+ android:layout_width="32dp"
+ android:layout_height="32dp"
+ android:layout_marginEnd="12dp"
+ android:layout_gravity="center_vertical"
+ android:src="@drawable/ic_work_app_badge"/>
<TextView
android:id="@+id/screenshot_message_content"
android:layout_width="0dp"
android:layout_height="wrap_content"
- android:layout_gravity="start"
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintStart_toEndOf="@id/screenshot_message_icon"
- app:layout_constraintEnd_toStartOf="@id/message_dismiss_button"/>
+ android:layout_weight="1"
+ android:layout_gravity="start|center_vertical"
+ android:textSize="18sp"
+ android:textColor="?android:attr/textColorPrimary"
+ android:lineHeight="24sp"
+ />
<FrameLayout
android:id="@+id/message_dismiss_button"
android:layout_width="@dimen/overlay_dismiss_button_tappable_size"
android:layout_height="@dimen/overlay_dismiss_button_tappable_size"
- app:layout_constraintStart_toEndOf="@id/screenshot_message_content"
- app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintBottom_toBottomOf="parent"
android:contentDescription="@string/screenshot_dismiss_work_profile">
<ImageView
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_margin="@dimen/overlay_dismiss_button_margin"
+ android:layout_width="24dp"
+ android:layout_height="24dp"
+ android:layout_gravity="center"
android:src="@drawable/overlay_cancel"/>
</FrameLayout>
-</merge>
+</LinearLayout>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 1826c00..371f001 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -828,4 +828,8 @@
<string name="config_wallpaperPickerPackage" translatable="false">
com.android.wallpaper
</string>
+
+ <!-- Whether the floating rotation button should be on the left/right in the device's natural
+ orientation -->
+ <bool name="floating_rotation_button_position_left">true</bool>
</resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 6cdce03..227b00e 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -240,9 +240,13 @@
<!-- Content description for the right boundary of the screenshot being cropped, with the current position as a percentage. [CHAR LIMIT=NONE] -->
<string name="screenshot_right_boundary_pct">Right boundary <xliff:g id="percent" example="50">%1$d</xliff:g> percent</string>
<!-- Notification displayed when a screenshot is saved in a work profile. [CHAR LIMIT=NONE] -->
- <string name="screenshot_work_profile_notification">Work screenshots are saved in the <xliff:g id="app" example="Work Files">%1$s</xliff:g> app</string>
+ <string name="screenshot_work_profile_notification">Saved in <xliff:g id="app" example="Files">%1$s</xliff:g> in the work profile</string>
<!-- Default name referring to the app on the device that lets the user browse stored files. [CHAR LIMIT=NONE] -->
<string name="screenshot_default_files_app_name">Files</string>
+ <!-- A notice shown to the user to indicate that an app has detected the screenshot that the user has just taken. [CHAR LIMIT=75] -->
+ <string name="screenshot_detected_template"><xliff:g id="appName" example="Google Chrome">%1$s</xliff:g> detected this screenshot.</string>
+ <!-- A notice shown to the user to indicate that multiple apps have detected the screenshot that the user has just taken. [CHAR LIMIT=75] -->
+ <string name="screenshot_detected_multiple_template"><xliff:g id="appName" example="Google Chrome">%1$s</xliff:g> and other open apps detected this screenshot.</string>
<!-- Notification title displayed for screen recording [CHAR LIMIT=50]-->
<string name="screenrecord_name">Screen Recorder</string>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt b/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt
index 196f7f0..c9a25b0 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt
@@ -47,10 +47,6 @@
val resourceId: Int
}
-interface DeviceConfigFlag<T> : Flag<T> {
- val default: T
-}
-
interface SysPropFlag<T> : Flag<T> {
val default: T
}
@@ -80,8 +76,8 @@
private constructor(parcel: Parcel) : this(
id = parcel.readInt(),
- name = parcel.readString(),
- namespace = parcel.readString(),
+ name = parcel.readString() ?: "",
+ namespace = parcel.readString() ?: "",
default = parcel.readBoolean(),
teamfood = parcel.readBoolean(),
overridden = parcel.readBoolean()
@@ -137,21 +133,6 @@
) : ResourceFlag<Boolean>
/**
- * A Flag that can reads its overrides from DeviceConfig.
- *
- * This is generally useful for flags that come from or are used _outside_ of SystemUI.
- *
- * Prefer [UnreleasedFlag] and [ReleasedFlag].
- */
-data class DeviceConfigBooleanFlag constructor(
- override val id: Int,
- override val name: String,
- override val namespace: String,
- override val default: Boolean = false,
- override val teamfood: Boolean = false
-) : DeviceConfigFlag<Boolean>
-
-/**
* A Flag that can reads its overrides from System Properties.
*
* This is generally useful for flags that come from or are used _outside_ of SystemUI.
@@ -186,8 +167,8 @@
private constructor(parcel: Parcel) : this(
id = parcel.readInt(),
- name = parcel.readString(),
- namespace = parcel.readString(),
+ name = parcel.readString() ?: "",
+ namespace = parcel.readString() ?: "",
default = parcel.readString() ?: ""
)
@@ -226,8 +207,8 @@
private constructor(parcel: Parcel) : this(
id = parcel.readInt(),
- name = parcel.readString(),
- namespace = parcel.readString(),
+ name = parcel.readString() ?: "",
+ namespace = parcel.readString() ?: "",
default = parcel.readInt()
)
@@ -266,8 +247,8 @@
private constructor(parcel: Parcel) : this(
id = parcel.readInt(),
- name = parcel.readString(),
- namespace = parcel.readString(),
+ name = parcel.readString() ?: "",
+ namespace = parcel.readString() ?: "",
default = parcel.readLong()
)
@@ -298,8 +279,8 @@
private constructor(parcel: Parcel) : this(
id = parcel.readInt(),
- name = parcel.readString(),
- namespace = parcel.readString(),
+ name = parcel.readString() ?: "",
+ namespace = parcel.readString() ?: "",
default = parcel.readFloat()
)
@@ -338,8 +319,8 @@
private constructor(parcel: Parcel) : this(
id = parcel.readInt(),
- name = parcel.readString(),
- namespace = parcel.readString(),
+ name = parcel.readString() ?: "",
+ namespace = parcel.readString() ?: "",
default = parcel.readDouble()
)
diff --git a/packages/SystemUI/shared/src/com/android/systemui/flags/FlagListenable.kt b/packages/SystemUI/shared/src/com/android/systemui/flags/FlagListenable.kt
index 195ba465..72a4fab 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/flags/FlagListenable.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/flags/FlagListenable.kt
@@ -34,7 +34,7 @@
/** An event representing the change */
interface FlagEvent {
/** the id of the flag which changed */
- val flagId: Int
+ val flagName: String
/** if all listeners alerted invoke this method, the restart will be skipped */
fun requestNoRestart()
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/flags/FlagManager.kt b/packages/SystemUI/shared/src/com/android/systemui/flags/FlagManager.kt
index d85292a..da1641c 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/flags/FlagManager.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/flags/FlagManager.kt
@@ -39,7 +39,7 @@
const val ACTION_GET_FLAGS = "com.android.systemui.action.GET_FLAGS"
const val FLAGS_PERMISSION = "com.android.systemui.permission.FLAGS"
const val ACTION_SYSUI_STARTED = "com.android.systemui.STARTED"
- const val EXTRA_ID = "id"
+ const val EXTRA_NAME = "name"
const val EXTRA_VALUE = "value"
const val EXTRA_FLAGS = "flags"
private const val SETTINGS_PREFIX = "systemui/flags"
@@ -56,7 +56,7 @@
* that the restart be suppressed
*/
var onSettingsChangedAction: Consumer<Boolean>? = null
- var clearCacheAction: Consumer<Int>? = null
+ var clearCacheAction: Consumer<String>? = null
private val listeners: MutableSet<PerFlagListener> = mutableSetOf()
private val settingsObserver: ContentObserver = SettingsObserver()
@@ -96,35 +96,42 @@
* Returns the stored value or null if not set.
* This API is used by TheFlippinApp.
*/
- fun isEnabled(id: Int): Boolean? = readFlagValue(id, BooleanFlagSerializer)
+ fun isEnabled(name: String): Boolean? = readFlagValue(name, BooleanFlagSerializer)
/**
* Sets the value of a boolean flag.
* This API is used by TheFlippinApp.
*/
- fun setFlagValue(id: Int, enabled: Boolean) {
- val intent = createIntent(id)
+ fun setFlagValue(name: String, enabled: Boolean) {
+ val intent = createIntent(name)
intent.putExtra(EXTRA_VALUE, enabled)
context.sendBroadcast(intent)
}
- fun eraseFlag(id: Int) {
- val intent = createIntent(id)
+ fun eraseFlag(name: String) {
+ val intent = createIntent(name)
context.sendBroadcast(intent)
}
/** Returns the stored value or null if not set. */
+ // TODO(b/265188950): Remove method this once ids are fully deprecated.
fun <T> readFlagValue(id: Int, serializer: FlagSerializer<T>): T? {
- val data = settings.getString(idToSettingsKey(id))
+ val data = settings.getStringFromSecure(idToSettingsKey(id))
+ return serializer.fromSettingsData(data)
+ }
+
+ /** Returns the stored value or null if not set. */
+ fun <T> readFlagValue(name: String, serializer: FlagSerializer<T>): T? {
+ val data = settings.getString(nameToSettingsKey(name))
return serializer.fromSettingsData(data)
}
override fun addListener(flag: Flag<*>, listener: FlagListenable.Listener) {
synchronized(listeners) {
val registerNeeded = listeners.isEmpty()
- listeners.add(PerFlagListener(flag.id, listener))
+ listeners.add(PerFlagListener(flag.name, listener))
if (registerNeeded) {
settings.registerContentObserver(SETTINGS_PREFIX, true, settingsObserver)
}
@@ -143,38 +150,38 @@
}
}
- private fun createIntent(id: Int): Intent {
+ private fun createIntent(name: String): Intent {
val intent = Intent(ACTION_SET_FLAG)
intent.setPackage(RECEIVING_PACKAGE)
- intent.putExtra(EXTRA_ID, id)
+ intent.putExtra(EXTRA_NAME, name)
return intent
}
+ // TODO(b/265188950): Remove method this once ids are fully deprecated.
fun idToSettingsKey(id: Int): String {
return "$SETTINGS_PREFIX/$id"
}
+ fun nameToSettingsKey(name: String): String {
+ return "$SETTINGS_PREFIX/$name"
+ }
+
inner class SettingsObserver : ContentObserver(handler) {
override fun onChange(selfChange: Boolean, uri: Uri?) {
if (uri == null) {
return
}
val parts = uri.pathSegments
- val idStr = parts[parts.size - 1]
- val id = try {
- idStr.toInt()
- } catch (e: NumberFormatException) {
- return
- }
- clearCacheAction?.accept(id)
- dispatchListenersAndMaybeRestart(id, onSettingsChangedAction)
+ val name = parts[parts.size - 1]
+ clearCacheAction?.accept(name)
+ dispatchListenersAndMaybeRestart(name, onSettingsChangedAction)
}
}
- fun dispatchListenersAndMaybeRestart(id: Int, restartAction: Consumer<Boolean>?) {
+ fun dispatchListenersAndMaybeRestart(name: String, restartAction: Consumer<Boolean>?) {
val filteredListeners: List<FlagListenable.Listener> = synchronized(listeners) {
- listeners.mapNotNull { if (it.id == id) it.listener else null }
+ listeners.mapNotNull { if (it.name == name) it.listener else null }
}
// If there are no listeners, there's nothing to dispatch to, and nothing to suppress it.
if (filteredListeners.isEmpty()) {
@@ -185,7 +192,7 @@
val suppressRestartList: List<Boolean> = filteredListeners.map { listener ->
var didRequestNoRestart = false
val event = object : FlagListenable.FlagEvent {
- override val flagId = id
+ override val flagName = name
override fun requestNoRestart() {
didRequestNoRestart = true
}
@@ -198,7 +205,7 @@
restartAction?.accept(suppressRestart)
}
- private data class PerFlagListener(val id: Int, val listener: FlagListenable.Listener)
+ private data class PerFlagListener(val name: String, val listener: FlagListenable.Listener)
}
class NoFlagResultsException : Exception(
diff --git a/packages/SystemUI/shared/src/com/android/systemui/flags/FlagSettingsHelper.kt b/packages/SystemUI/shared/src/com/android/systemui/flags/FlagSettingsHelper.kt
index 742bb0b..6beb851 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/flags/FlagSettingsHelper.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/flags/FlagSettingsHelper.kt
@@ -22,7 +22,10 @@
class FlagSettingsHelper(private val contentResolver: ContentResolver) {
- fun getString(key: String): String? = Settings.Secure.getString(contentResolver, key)
+ // TODO(b/265188950): Remove method this once ids are fully deprecated.
+ fun getStringFromSecure(key: String): String? = Settings.Secure.getString(contentResolver, key)
+
+ fun getString(key: String): String? = Settings.Global.getString(contentResolver, key)
fun registerContentObserver(
name: String,
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButton.java b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButton.java
index 857cc462..5d036fb 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButton.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButton.java
@@ -35,6 +35,7 @@
import android.view.animation.AccelerateDecelerateInterpolator;
import android.widget.FrameLayout;
+import androidx.annotation.BoolRes;
import androidx.core.view.OneShotPreDrawListener;
import com.android.systemui.shared.rotation.FloatingRotationButtonPositionCalculator.Position;
@@ -65,6 +66,8 @@
private final int mTaskbarBottomMarginResource;
@DimenRes
private final int mButtonDiameterResource;
+ @BoolRes
+ private final int mFloatingRotationBtnPositionLeftResource;
private AnimatedVectorDrawable mAnimatedDrawable;
private boolean mIsShowing;
@@ -84,7 +87,7 @@
@LayoutRes int layout, @IdRes int keyButtonId, @DimenRes int minMargin,
@DimenRes int roundedContentPadding, @DimenRes int taskbarLeftMargin,
@DimenRes int taskbarBottomMargin, @DimenRes int buttonDiameter,
- @DimenRes int rippleMaxWidth) {
+ @DimenRes int rippleMaxWidth, @BoolRes int floatingRotationBtnPositionLeftResource) {
mWindowManager = context.getSystemService(WindowManager.class);
mKeyButtonContainer = (ViewGroup) LayoutInflater.from(context).inflate(layout, null);
mKeyButtonView = mKeyButtonContainer.findViewById(keyButtonId);
@@ -100,6 +103,7 @@
mTaskbarLeftMarginResource = taskbarLeftMargin;
mTaskbarBottomMarginResource = taskbarBottomMargin;
mButtonDiameterResource = buttonDiameter;
+ mFloatingRotationBtnPositionLeftResource = floatingRotationBtnPositionLeftResource;
updateDimensionResources();
}
@@ -116,8 +120,11 @@
int taskbarMarginBottom =
res.getDimensionPixelSize(mTaskbarBottomMarginResource);
+ boolean floatingRotationButtonPositionLeft =
+ res.getBoolean(mFloatingRotationBtnPositionLeftResource);
+
mPositionCalculator = new FloatingRotationButtonPositionCalculator(defaultMargin,
- taskbarMarginLeft, taskbarMarginBottom);
+ taskbarMarginLeft, taskbarMarginBottom, floatingRotationButtonPositionLeft);
final int diameter = res.getDimensionPixelSize(mButtonDiameterResource);
mContainerSize = diameter + Math.max(defaultMargin, Math.max(taskbarMarginLeft,
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButtonPositionCalculator.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButtonPositionCalculator.kt
index ec3c073..40e43a9 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButtonPositionCalculator.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButtonPositionCalculator.kt
@@ -10,7 +10,8 @@
class FloatingRotationButtonPositionCalculator(
private val defaultMargin: Int,
private val taskbarMarginLeft: Int,
- private val taskbarMarginBottom: Int
+ private val taskbarMarginBottom: Int,
+ private val floatingRotationButtonPositionLeft: Boolean
) {
fun calculatePosition(
@@ -18,7 +19,6 @@
taskbarVisible: Boolean,
taskbarStashed: Boolean
): Position {
-
val isTaskbarSide = currentRotation == Surface.ROTATION_0
|| currentRotation == Surface.ROTATION_90
val useTaskbarMargin = isTaskbarSide && taskbarVisible && !taskbarStashed
@@ -55,11 +55,21 @@
)
private fun resolveGravity(rotation: Int): Int =
- when (rotation) {
- Surface.ROTATION_0 -> Gravity.BOTTOM or Gravity.LEFT
- Surface.ROTATION_90 -> Gravity.BOTTOM or Gravity.RIGHT
- Surface.ROTATION_180 -> Gravity.TOP or Gravity.RIGHT
- Surface.ROTATION_270 -> Gravity.TOP or Gravity.LEFT
- else -> throw IllegalArgumentException("Invalid rotation $rotation")
+ if (floatingRotationButtonPositionLeft) {
+ when (rotation) {
+ Surface.ROTATION_0 -> Gravity.BOTTOM or Gravity.LEFT
+ Surface.ROTATION_90 -> Gravity.BOTTOM or Gravity.RIGHT
+ Surface.ROTATION_180 -> Gravity.TOP or Gravity.RIGHT
+ Surface.ROTATION_270 -> Gravity.TOP or Gravity.LEFT
+ else -> throw IllegalArgumentException("Invalid rotation $rotation")
+ }
+ } else {
+ when (rotation) {
+ Surface.ROTATION_0 -> Gravity.BOTTOM or Gravity.RIGHT
+ Surface.ROTATION_90 -> Gravity.TOP or Gravity.RIGHT
+ Surface.ROTATION_180 -> Gravity.TOP or Gravity.LEFT
+ Surface.ROTATION_270 -> Gravity.BOTTOM or Gravity.LEFT
+ else -> throw IllegalArgumentException("Invalid rotation $rotation")
+ }
}
}
diff --git a/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsFactory.kt b/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsFactory.kt
index 05372fe..31234cf 100644
--- a/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsFactory.kt
+++ b/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsFactory.kt
@@ -35,7 +35,7 @@
teamfood: Boolean = false
): UnreleasedFlag {
val flag = UnreleasedFlag(id = id, name = name, namespace = namespace, teamfood = teamfood)
- FlagsFactory.checkForDupesAndAdd(flag)
+ checkForDupesAndAdd(flag)
return flag
}
@@ -46,7 +46,7 @@
teamfood: Boolean = false
): ReleasedFlag {
val flag = ReleasedFlag(id = id, name = name, namespace = namespace, teamfood = teamfood)
- FlagsFactory.checkForDupesAndAdd(flag)
+ checkForDupesAndAdd(flag)
return flag
}
@@ -65,7 +65,7 @@
resourceId = resourceId,
teamfood = teamfood
)
- FlagsFactory.checkForDupesAndAdd(flag)
+ checkForDupesAndAdd(flag)
return flag
}
@@ -77,18 +77,13 @@
): SysPropBooleanFlag {
val flag =
SysPropBooleanFlag(id = id, name = name, namespace = "systemui", default = default)
- FlagsFactory.checkForDupesAndAdd(flag)
+ checkForDupesAndAdd(flag)
return flag
}
private fun checkForDupesAndAdd(flag: Flag<*>) {
if (flagMap.containsKey(flag.name)) {
- throw IllegalArgumentException("Name {flag.name} is already registered")
- }
- flagMap.forEach {
- if (it.value.id == flag.id) {
- throw IllegalArgumentException("Name {flag.id} is already registered")
- }
+ throw IllegalArgumentException("Name {$flag.name} is already registered")
}
flagMap[flag.name] = flag
}
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index 3a940e9..7a094a7 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -25,6 +25,7 @@
import android.util.TypedValue
import android.view.View
import android.widget.FrameLayout
+import android.view.ViewTreeObserver
import androidx.annotation.VisibleForTesting
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
@@ -42,12 +43,15 @@
import com.android.systemui.log.dagger.KeyguardSmallClockLog
import com.android.systemui.log.dagger.KeyguardLargeClockLog
import com.android.systemui.plugins.ClockController
+import com.android.systemui.plugins.ClockFaceController
+import com.android.systemui.plugins.ClockTickRate
import com.android.systemui.plugins.log.LogBuffer
import com.android.systemui.plugins.log.LogLevel.DEBUG
import com.android.systemui.shared.regionsampling.RegionSampler
import com.android.systemui.statusbar.policy.BatteryController
import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback
import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.util.concurrency.DelayableExecutor
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.DisposableHandle
import kotlinx.coroutines.Job
@@ -72,7 +76,7 @@
private val configurationController: ConfigurationController,
@Main private val resources: Resources,
private val context: Context,
- @Main private val mainExecutor: Executor,
+ @Main private val mainExecutor: DelayableExecutor,
@Background private val bgExecutor: Executor,
@KeyguardSmallClockLog private val smallLogBuffer: LogBuffer?,
@KeyguardLargeClockLog private val largeLogBuffer: LogBuffer?,
@@ -94,6 +98,7 @@
clock?.largeClock?.view?.addOnLayoutChangeListener(mLayoutChangedListener)
}
updateFontSizes()
+ updateTimeListeners()
}
}
@@ -208,6 +213,10 @@
}
var regionSampler: RegionSampler? = null
+ var smallTimeListener: TimeListener? = null
+ var largeTimeListener: TimeListener? = null
+ val shouldTimeListenerRun: Boolean
+ get() = isKeyguardVisible && dozeAmount < DOZE_TICKRATE_THRESHOLD
private var smallClockIsDark = true
private var largeClockIsDark = true
@@ -246,6 +255,9 @@
clock?.animations?.doze(if (isDozing) 1f else 0f)
}
}
+
+ smallTimeListener?.update(shouldTimeListenerRun)
+ largeTimeListener?.update(shouldTimeListenerRun)
}
override fun onTimeFormatChanged(timeFormat: String) {
@@ -285,6 +297,8 @@
}
}
}
+ smallTimeListener?.update(shouldTimeListenerRun)
+ largeTimeListener?.update(shouldTimeListenerRun)
}
fun unregisterListeners() {
@@ -299,6 +313,25 @@
batteryController.removeCallback(batteryCallback)
keyguardUpdateMonitor.removeCallback(keyguardUpdateMonitorCallback)
regionSampler?.stopRegionSampler()
+ smallTimeListener?.stop()
+ largeTimeListener?.stop()
+ }
+
+ private fun updateTimeListeners() {
+ smallTimeListener?.stop()
+ largeTimeListener?.stop()
+
+ smallTimeListener = null
+ largeTimeListener = null
+
+ clock?.smallClock?.let {
+ smallTimeListener = TimeListener(it, mainExecutor)
+ smallTimeListener?.update(shouldTimeListenerRun)
+ }
+ clock?.largeClock?.let {
+ largeTimeListener = TimeListener(it, mainExecutor)
+ largeTimeListener?.update(shouldTimeListenerRun)
+ }
}
private fun updateFontSizes() {
@@ -308,12 +341,18 @@
resources.getDimensionPixelSize(R.dimen.large_clock_text_size).toFloat())
}
+ private fun handleDoze(doze: Float) {
+ dozeAmount = doze
+ clock?.animations?.doze(dozeAmount)
+ smallTimeListener?.update(doze < DOZE_TICKRATE_THRESHOLD)
+ largeTimeListener?.update(doze < DOZE_TICKRATE_THRESHOLD)
+ }
+
@VisibleForTesting
internal fun listenForDozeAmount(scope: CoroutineScope): Job {
return scope.launch {
keyguardInteractor.dozeAmount.collect {
- dozeAmount = it
- clock?.animations?.doze(dozeAmount)
+ handleDoze(it)
}
}
}
@@ -322,8 +361,7 @@
internal fun listenForDozeAmountTransition(scope: CoroutineScope): Job {
return scope.launch {
keyguardTransitionInteractor.dozeAmountTransition.collect {
- dozeAmount = it.value
- clock?.animations?.doze(dozeAmount)
+ handleDoze(it.value)
}
}
}
@@ -338,8 +376,7 @@
keyguardTransitionInteractor.anyStateToAodTransition.filter {
it.transitionState == TransitionState.FINISHED
}.collect {
- dozeAmount = 1f
- clock?.animations?.doze(dozeAmount)
+ handleDoze(1f)
}
}
}
@@ -359,7 +396,54 @@
}
}
+ class TimeListener(val clockFace: ClockFaceController, val executor: DelayableExecutor) {
+ val predrawListener = ViewTreeObserver.OnPreDrawListener {
+ clockFace.events.onTimeTick()
+ true
+ }
+
+ val secondsRunnable = object : Runnable {
+ override fun run() {
+ if (!isRunning) {
+ return
+ }
+
+ executor.executeDelayed(this, 990)
+ clockFace.events.onTimeTick()
+ }
+ }
+
+ var isRunning: Boolean = false
+ private set
+
+ fun start() {
+ if (isRunning) {
+ return
+ }
+
+ isRunning = true
+ when (clockFace.events.tickRate) {
+ ClockTickRate.PER_MINUTE -> {/* Handled by KeyguardClockSwitchController */}
+ ClockTickRate.PER_SECOND -> executor.execute(secondsRunnable)
+ ClockTickRate.PER_FRAME -> {
+ clockFace.view.viewTreeObserver.addOnPreDrawListener(predrawListener)
+ clockFace.view.invalidate()
+ }
+ }
+ }
+
+ fun stop() {
+ if (!isRunning) { return }
+
+ isRunning = false
+ clockFace.view.viewTreeObserver.removeOnPreDrawListener(predrawListener)
+ }
+
+ fun update(shouldRun: Boolean) = if (shouldRun) start() else stop()
+ }
+
companion object {
private val TAG = ClockEventController::class.simpleName!!
+ private val DOZE_TICKRATE_THRESHOLD = 0.99f
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
index 7da27b1..baaef19 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
@@ -103,6 +103,7 @@
@Override
public void reset() {
+ super.reset();
// start fresh
mDismissing = false;
mView.resetPasswordText(false /* animate */, false /* announce */);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index 8684019..a148aa1 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -91,6 +91,7 @@
private ViewGroup mStatusArea;
// If the SMARTSPACE flag is set, keyguard_slice_view is replaced by the following views.
+ private ViewGroup mDateWeatherView;
private View mWeatherView;
private View mSmartspaceView;
@@ -201,7 +202,7 @@
// TODO(b/261757708): add content observer for the Settings toggle and add/remove
// weather according to the Settings.
if (mSmartspaceController.isDateWeatherDecoupled()) {
- addWeatherView(viewIndex);
+ addDateWeatherView(viewIndex);
viewIndex += 1;
}
@@ -239,6 +240,14 @@
void onLocaleListChanged() {
if (mSmartspaceController.isEnabled()) {
+ if (mSmartspaceController.isDateWeatherDecoupled()) {
+ mDateWeatherView.removeView(mWeatherView);
+ int index = mStatusArea.indexOfChild(mDateWeatherView);
+ if (index >= 0) {
+ mStatusArea.removeView(mDateWeatherView);
+ addDateWeatherView(index);
+ }
+ }
int index = mStatusArea.indexOfChild(mSmartspaceView);
if (index >= 0) {
mStatusArea.removeView(mSmartspaceView);
@@ -247,16 +256,28 @@
}
}
- private void addWeatherView(int index) {
- mWeatherView = mSmartspaceController.buildAndConnectWeatherView(mView);
+ private void addDateWeatherView(int index) {
+ mDateWeatherView = (ViewGroup) mSmartspaceController.buildAndConnectDateView(mView);
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
MATCH_PARENT, WRAP_CONTENT);
- mStatusArea.addView(mWeatherView, index, lp);
+ mStatusArea.addView(mDateWeatherView, index, lp);
int startPadding = getContext().getResources().getDimensionPixelSize(
R.dimen.below_clock_padding_start);
int endPadding = getContext().getResources().getDimensionPixelSize(
R.dimen.below_clock_padding_end);
- mWeatherView.setPaddingRelative(startPadding, 0, endPadding, 0);
+ mDateWeatherView.setPaddingRelative(startPadding, 0, endPadding, 0);
+
+ addWeatherView();
+ }
+
+ private void addWeatherView() {
+ LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
+ WRAP_CONTENT, WRAP_CONTENT);
+ mWeatherView = mSmartspaceController.buildAndConnectWeatherView(mView);
+ // Place weather right after the date, before the extras
+ final int index = mDateWeatherView.getChildCount() == 0 ? 0 : 1;
+ mDateWeatherView.addView(mWeatherView, index, lp);
+ mWeatherView.setPaddingRelative(0, 0, 4, 0);
}
private void addSmartspaceView(int index) {
@@ -323,7 +344,8 @@
}
ClockController clock = getClock();
if (clock != null) {
- clock.getEvents().onTimeTick();
+ clock.getSmallClock().getEvents().onTimeTick();
+ clock.getLargeClock().getEvents().onTimeTick();
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardHostView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardHostView.java
index 08e9cf6..2a389b6 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardHostView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardHostView.java
@@ -19,6 +19,7 @@
import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
+import android.view.MotionEvent;
import android.widget.FrameLayout;
/**
@@ -33,7 +34,7 @@
public class KeyguardHostView extends FrameLayout {
protected ViewMediatorCallback mViewMediatorCallback;
-
+ private boolean mIsInteractable;
public KeyguardHostView(Context context) {
this(context, null);
@@ -54,4 +55,24 @@
public void setViewMediatorCallback(ViewMediatorCallback viewMediatorCallback) {
mViewMediatorCallback = viewMediatorCallback;
}
+
+ /** Set true if the view can be interacted with */
+ public void setInteractable(boolean isInteractable) {
+ mIsInteractable = isInteractable;
+ }
+
+ /**
+ * Make sure to disallow touches while transitioning the bouncer, otherwise
+ * it can remain interactable even when barely visible.
+ */
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ return !mIsInteractable;
+ }
+
+ /** True to consume any events that are sent to it */
+ @Override
+ public boolean onTouchEvent(MotionEvent ev) {
+ return true;
+ }
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java
index ea84438..6139403 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java
@@ -527,4 +527,9 @@
mKeyguardSecurityContainerController.updateKeyguardPosition(x);
}
}
+
+ /** Set true if the view can be interacted with */
+ public void setInteractable(boolean isInteractable) {
+ mView.setInteractable(isInteractable);
+ }
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
index d1c9a30..b143c5b 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
@@ -121,6 +121,7 @@
@Override
public void reset() {
+ mMessageAreaController.setMessage("", false);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/ChooserSelector.kt b/packages/SystemUI/src/com/android/systemui/ChooserSelector.kt
index 9ac45b3..227f0ace 100644
--- a/packages/SystemUI/src/com/android/systemui/ChooserSelector.kt
+++ b/packages/SystemUI/src/com/android/systemui/ChooserSelector.kt
@@ -34,7 +34,7 @@
override fun start() {
coroutineScope.launch {
val listener = FlagListenable.Listener { event ->
- if (event.flagId == Flags.CHOOSER_UNBUNDLED.id) {
+ if (event.flagName == Flags.CHOOSER_UNBUNDLED.name) {
launch { updateUnbundledChooserEnabled() }
event.requestNoRestart()
}
diff --git a/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt b/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt
index 3e0fa45..54939fd 100644
--- a/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt
@@ -35,6 +35,7 @@
import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.settingslib.Utils
import com.android.systemui.animation.Interpolators
+import com.android.systemui.log.ScreenDecorationsLogger
import com.android.systemui.plugins.statusbar.StatusBarStateController
import java.util.concurrent.Executor
@@ -47,7 +48,8 @@
pos: Int,
val statusBarStateController: StatusBarStateController,
val keyguardUpdateMonitor: KeyguardUpdateMonitor,
- val mainExecutor: Executor
+ val mainExecutor: Executor,
+ val logger: ScreenDecorationsLogger,
) : ScreenDecorations.DisplayCutoutView(context, pos) {
private var showScanningAnim = false
private val rimPaint = Paint()
@@ -55,6 +57,7 @@
private var rimAnimator: AnimatorSet? = null
private val rimRect = RectF()
private var cameraProtectionColor = Color.BLACK
+
var faceScanningAnimColor = Utils.getColorAttrDefaultColor(context,
R.attr.wallpaperTextColorAccent)
private var cameraProtectionAnimator: ValueAnimator? = null
@@ -175,15 +178,22 @@
}
if (showScanningAnim) {
// Make sure that our measured height encompasses the extra space for the animation
- mTotalBounds.union(mBoundingRect)
+ mTotalBounds.set(mBoundingRect)
mTotalBounds.union(
rimRect.left.toInt(),
rimRect.top.toInt(),
rimRect.right.toInt(),
rimRect.bottom.toInt())
- setMeasuredDimension(
- resolveSizeAndState(mTotalBounds.width(), widthMeasureSpec, 0),
- resolveSizeAndState(mTotalBounds.height(), heightMeasureSpec, 0))
+ val measuredWidth = resolveSizeAndState(mTotalBounds.width(), widthMeasureSpec, 0)
+ val measuredHeight = resolveSizeAndState(mTotalBounds.height(), heightMeasureSpec, 0)
+ logger.boundingRect(rimRect, "onMeasure: Face scanning animation")
+ logger.boundingRect(mBoundingRect, "onMeasure: Display cutout view bounding rect")
+ logger.boundingRect(mTotalBounds, "onMeasure: TotalBounds")
+ logger.onMeasureDimensions(widthMeasureSpec,
+ heightMeasureSpec,
+ measuredWidth,
+ measuredHeight)
+ setMeasuredDimension(measuredWidth, measuredHeight)
} else {
setMeasuredDimension(
resolveSizeAndState(mBoundingRect.width(), widthMeasureSpec, 0),
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index 71f98fa..c1c7f2d 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -64,6 +64,7 @@
import com.android.internal.util.Preconditions;
import com.android.settingslib.Utils;
+import com.android.systemui.biometrics.AuthController;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.decor.CutoutDecorProviderFactory;
@@ -75,6 +76,7 @@
import com.android.systemui.decor.PrivacyDotDecorProviderFactory;
import com.android.systemui.decor.RoundedCornerDecorProviderFactory;
import com.android.systemui.decor.RoundedCornerResDelegate;
+import com.android.systemui.log.ScreenDecorationsLogger;
import com.android.systemui.qs.SettingObserver;
import com.android.systemui.settings.DisplayTracker;
import com.android.systemui.settings.UserTracker;
@@ -120,6 +122,18 @@
R.id.display_cutout_right,
R.id.display_cutout_bottom
};
+ private final ScreenDecorationsLogger mLogger;
+
+ private final AuthController.Callback mAuthControllerCallback = new AuthController.Callback() {
+ @Override
+ public void onFaceSensorLocationChanged() {
+ mLogger.onSensorLocationChanged();
+ if (mExecutor != null) {
+ mExecutor.execute(
+ () -> updateOverlayProviderViews(new Integer[]{mFaceScanningViewId}));
+ }
+ }
+ };
private DisplayTracker mDisplayTracker;
@VisibleForTesting
@@ -153,6 +167,7 @@
private WindowManager mWindowManager;
private int mRotation;
private SettingObserver mColorInversionSetting;
+ @Nullable
private DelayableExecutor mExecutor;
private Handler mHandler;
boolean mPendingConfigChange;
@@ -172,6 +187,7 @@
DisplayCutoutView overlay = (DisplayCutoutView) getOverlayView(
mFaceScanningViewId);
if (overlay != null) {
+ mLogger.cameraProtectionBoundsForScanningOverlay(bounds);
overlay.setProtection(protectionPath, bounds);
overlay.enableShowProtection(true);
updateOverlayWindowVisibilityIfViewExists(
@@ -184,6 +200,7 @@
}
if (mScreenDecorHwcLayer != null) {
+ mLogger.hwcLayerCameraProtectionBounds(bounds);
mScreenDecorHwcLayer.setProtection(protectionPath, bounds);
mScreenDecorHwcLayer.enableShowProtection(true);
return;
@@ -197,11 +214,12 @@
}
++setProtectionCnt;
final DisplayCutoutView dcv = (DisplayCutoutView) view;
+ mLogger.dcvCameraBounds(id, bounds);
dcv.setProtection(protectionPath, bounds);
dcv.enableShowProtection(true);
}
if (setProtectionCnt == 0) {
- Log.e(TAG, "CutoutView not initialized showCameraProtection");
+ mLogger.cutoutViewNotInitialized();
}
}
@@ -307,7 +325,9 @@
PrivacyDotViewController dotViewController,
ThreadFactory threadFactory,
PrivacyDotDecorProviderFactory dotFactory,
- FaceScanningProviderFactory faceScanningFactory) {
+ FaceScanningProviderFactory faceScanningFactory,
+ ScreenDecorationsLogger logger,
+ AuthController authController) {
mContext = context;
mMainExecutor = mainExecutor;
mSecureSettings = secureSettings;
@@ -319,6 +339,8 @@
mDotFactory = dotFactory;
mFaceScanningFactory = faceScanningFactory;
mFaceScanningViewId = com.android.systemui.R.id.face_scanning_anim;
+ mLogger = logger;
+ authController.addCallback(mAuthControllerCallback);
}
@Override
@@ -1306,7 +1328,7 @@
if (showProtection) {
// Make sure that our measured height encompasses the protection
- mTotalBounds.union(mBoundingRect);
+ mTotalBounds.set(mBoundingRect);
mTotalBounds.union((int) protectionRect.left, (int) protectionRect.top,
(int) protectionRect.right, (int) protectionRect.bottom);
setMeasuredDimension(
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt
index d072ec7..addbee9 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt
@@ -33,7 +33,6 @@
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor
import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor
-import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.shade.ShadeExpansionListener
@@ -83,7 +82,6 @@
) {
private val useExpandedOverlay: Boolean =
featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)
- private val isModernBouncerEnabled: Boolean = featureFlags.isEnabled(Flags.MODERN_BOUNCER)
private val isModernAlternateBouncerEnabled: Boolean =
featureFlags.isEnabled(Flags.MODERN_ALTERNATE_BOUNCER)
private var showingUdfpsBouncer = false
@@ -109,12 +107,6 @@
)
}
}
- /**
- * Hidden amount of input (pin/pattern/password) bouncer. This is used
- * [KeyguardBouncerConstants.EXPANSION_VISIBLE] (0f) to
- * [KeyguardBouncerConstants.EXPANSION_HIDDEN] (1f). Only used for the non-modernBouncer.
- */
- private var inputBouncerHiddenAmount = KeyguardBouncerConstants.EXPANSION_HIDDEN
private var inputBouncerExpansion = 0f // only used for modernBouncer
private val stateListener: StatusBarStateController.StateListener =
@@ -253,15 +245,13 @@
}
init {
- if (isModernBouncerEnabled || isModernAlternateBouncerEnabled) {
- view.repeatWhenAttached {
- // repeatOnLifecycle CREATED (as opposed to STARTED) because the Bouncer expansion
- // can make the view not visible; and we still want to listen for events
- // that may make the view visible again.
- repeatOnLifecycle(Lifecycle.State.CREATED) {
- if (isModernBouncerEnabled) listenForBouncerExpansion(this)
- if (isModernAlternateBouncerEnabled) listenForAlternateBouncerVisibility(this)
- }
+ view.repeatWhenAttached {
+ // repeatOnLifecycle CREATED (as opposed to STARTED) because the Bouncer expansion
+ // can make the view not visible; and we still want to listen for events
+ // that may make the view visible again.
+ repeatOnLifecycle(Lifecycle.State.CREATED) {
+ listenForBouncerExpansion(this)
+ if (isModernAlternateBouncerEnabled) listenForAlternateBouncerVisibility(this)
}
}
}
@@ -332,7 +322,6 @@
override fun dump(pw: PrintWriter, args: Array<String>) {
super.dump(pw, args)
- pw.println("isModernBouncerEnabled=$isModernBouncerEnabled")
pw.println("isModernAlternateBouncerEnabled=$isModernAlternateBouncerEnabled")
pw.println("showingUdfpsAltBouncer=$showingUdfpsBouncer")
pw.println(
@@ -352,11 +341,7 @@
pw.println("udfpsRequestedByApp=$udfpsRequested")
pw.println("launchTransitionFadingAway=$launchTransitionFadingAway")
pw.println("lastDozeAmount=$lastDozeAmount")
- if (isModernBouncerEnabled) {
- pw.println("inputBouncerExpansion=$inputBouncerExpansion")
- } else {
- pw.println("inputBouncerHiddenAmount=$inputBouncerHiddenAmount")
- }
+ pw.println("inputBouncerExpansion=$inputBouncerExpansion")
view.dump(pw)
}
@@ -383,7 +368,6 @@
} else {
keyguardUpdateMonitor.requestFaceAuthOnOccludingApp(false)
}
- updateBouncerHiddenAmount()
updateAlpha()
updatePauseAuth()
return true
@@ -424,19 +408,11 @@
}
fun isBouncerExpansionGreaterThan(bouncerExpansionThreshold: Float): Boolean {
- return if (isModernBouncerEnabled) {
- inputBouncerExpansion >= bouncerExpansionThreshold
- } else {
- inputBouncerHiddenAmount < bouncerExpansionThreshold
- }
+ return inputBouncerExpansion >= bouncerExpansionThreshold
}
fun isInputBouncerFullyVisible(): Boolean {
- return if (isModernBouncerEnabled) {
- inputBouncerExpansion == 1f
- } else {
- keyguardViewManager.isBouncerShowing && !alternateBouncerInteractor.isVisibleState()
- }
+ return inputBouncerExpansion == 1f
}
override fun listenForTouchesOutsideView(): Boolean {
@@ -488,11 +464,7 @@
}
private fun getInputBouncerHiddenAmt(): Float {
- return if (isModernBouncerEnabled) {
- 1f - inputBouncerExpansion
- } else {
- inputBouncerHiddenAmount
- }
+ return 1f - inputBouncerExpansion
}
/** Update the scale factor based on the device's resolution. */
@@ -500,19 +472,6 @@
udfpsController.mOverlayParams?.scaleFactor?.let { view.setScaleFactor(it) }
}
- private fun updateBouncerHiddenAmount() {
- if (isModernBouncerEnabled) {
- return
- }
- val altBouncerShowing = alternateBouncerInteractor.isVisibleState()
- if (altBouncerShowing || !keyguardViewManager.primaryBouncerIsOrWillBeShowing()) {
- inputBouncerHiddenAmount = 1f
- } else if (keyguardViewManager.isBouncerShowing) {
- // input bouncer is fully showing
- inputBouncerHiddenAmount = 0f
- }
- }
-
private val legacyAlternateBouncer: LegacyAlternateBouncer =
object : LegacyAlternateBouncer {
override fun showAlternateBouncer(): Boolean {
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
index ef07e99..2dfcf70 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
@@ -31,6 +31,7 @@
import com.android.systemui.globalactions.GlobalActionsComponent
import com.android.systemui.keyboard.KeyboardUI
import com.android.systemui.keyguard.KeyguardViewMediator
+import com.android.systemui.keyguard.data.quickaffordance.MuteQuickAffordanceCoreStartable
import com.android.systemui.log.SessionTracker
import com.android.systemui.media.dialog.MediaOutputSwitcherDialogUI
import com.android.systemui.media.RingtonePlayer
@@ -288,6 +289,14 @@
@ClassKey(StylusUsiPowerStartable::class)
abstract fun bindStylusUsiPowerStartable(sysui: StylusUsiPowerStartable): CoreStartable
+ /** Inject into MuteQuickAffordanceCoreStartable*/
+ @Binds
+ @IntoMap
+ @ClassKey(MuteQuickAffordanceCoreStartable::class)
+ abstract fun bindMuteQuickAffordanceCoreStartable(
+ sysui: MuteQuickAffordanceCoreStartable
+ ): CoreStartable
+
/**Inject into DreamMonitor */
@Binds
@IntoMap
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 9ad7b8c..6274a26 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -218,6 +218,10 @@
abstract BcSmartspaceConfigPlugin optionalBcSmartspaceConfigPlugin();
@BindsOptionalOf
+ @Named(SmartspaceModule.DATE_SMARTSPACE_DATA_PLUGIN)
+ abstract BcSmartspaceDataPlugin optionalDateSmartspaceConfigPlugin();
+
+ @BindsOptionalOf
@Named(SmartspaceModule.WEATHER_SMARTSPACE_DATA_PLUGIN)
abstract BcSmartspaceDataPlugin optionalWeatherSmartspaceConfigPlugin();
diff --git a/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt b/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt
index 976afd4..88c0c50 100644
--- a/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt
@@ -34,6 +34,7 @@
import com.android.systemui.biometrics.AuthController
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.log.ScreenDecorationsLogger
import com.android.systemui.plugins.statusbar.StatusBarStateController
import java.util.concurrent.Executor
import javax.inject.Inject
@@ -45,6 +46,7 @@
private val statusBarStateController: StatusBarStateController,
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
@Main private val mainExecutor: Executor,
+ private val logger: ScreenDecorationsLogger,
) : DecorProviderFactory() {
private val display = context.display
private val displayInfo = DisplayInfo()
@@ -82,7 +84,8 @@
authController,
statusBarStateController,
keyguardUpdateMonitor,
- mainExecutor
+ mainExecutor,
+ logger,
)
)
}
@@ -104,7 +107,8 @@
private val authController: AuthController,
private val statusBarStateController: StatusBarStateController,
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
- private val mainExecutor: Executor
+ private val mainExecutor: Executor,
+ private val logger: ScreenDecorationsLogger,
) : BoundDecorProvider() {
override val viewId: Int = com.android.systemui.R.id.face_scanning_anim
@@ -136,7 +140,8 @@
alignedBound,
statusBarStateController,
keyguardUpdateMonitor,
- mainExecutor
+ mainExecutor,
+ logger,
)
view.id = viewId
view.setColor(tintColor)
@@ -155,8 +160,9 @@
layoutParams.let { lp ->
lp.width = ViewGroup.LayoutParams.MATCH_PARENT
lp.height = ViewGroup.LayoutParams.MATCH_PARENT
+ logger.faceSensorLocation(authController.faceSensorLocation)
authController.faceSensorLocation?.y?.let { faceAuthSensorHeight ->
- val faceScanningHeight = (faceAuthSensorHeight * 2).toInt()
+ val faceScanningHeight = (faceAuthSensorHeight * 2)
when (rotation) {
Surface.ROTATION_0, Surface.ROTATION_180 ->
lp.height = faceScanningHeight
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
index d1a14a1..4bac697 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
@@ -19,7 +19,7 @@
import static com.android.systemui.flags.FlagManager.ACTION_GET_FLAGS;
import static com.android.systemui.flags.FlagManager.ACTION_SET_FLAG;
import static com.android.systemui.flags.FlagManager.EXTRA_FLAGS;
-import static com.android.systemui.flags.FlagManager.EXTRA_ID;
+import static com.android.systemui.flags.FlagManager.EXTRA_NAME;
import static com.android.systemui.flags.FlagManager.EXTRA_VALUE;
import static com.android.systemui.flags.FlagsCommonModule.ALL_FLAGS;
@@ -39,6 +39,7 @@
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.util.settings.GlobalSettings;
import com.android.systemui.util.settings.SecureSettings;
import org.jetbrains.annotations.NotNull;
@@ -72,14 +73,15 @@
private final FlagManager mFlagManager;
private final Context mContext;
+ private final GlobalSettings mGlobalSettings;
private final SecureSettings mSecureSettings;
private final Resources mResources;
private final SystemPropertiesHelper mSystemProperties;
private final ServerFlagReader mServerFlagReader;
- private final Map<Integer, Flag<?>> mAllFlags;
- private final Map<Integer, Boolean> mBooleanFlagCache = new TreeMap<>();
- private final Map<Integer, String> mStringFlagCache = new TreeMap<>();
- private final Map<Integer, Integer> mIntFlagCache = new TreeMap<>();
+ private final Map<String, Flag<?>> mAllFlags;
+ private final Map<String, Boolean> mBooleanFlagCache = new TreeMap<>();
+ private final Map<String, String> mStringFlagCache = new TreeMap<>();
+ private final Map<String, Integer> mIntFlagCache = new TreeMap<>();
private final Restarter mRestarter;
private final ServerFlagReader.ChangeListener mOnPropertiesChanged =
@@ -94,14 +96,16 @@
public FeatureFlagsDebug(
FlagManager flagManager,
Context context,
+ GlobalSettings globalSettings,
SecureSettings secureSettings,
SystemPropertiesHelper systemProperties,
@Main Resources resources,
ServerFlagReader serverFlagReader,
- @Named(ALL_FLAGS) Map<Integer, Flag<?>> allFlags,
+ @Named(ALL_FLAGS) Map<String, Flag<?>> allFlags,
Restarter restarter) {
mFlagManager = flagManager;
mContext = context;
+ mGlobalSettings = globalSettings;
mSecureSettings = secureSettings;
mResources = resources;
mSystemProperties = systemProperties;
@@ -133,96 +137,103 @@
}
private boolean isEnabledInternal(@NotNull BooleanFlag flag) {
- int id = flag.getId();
- if (!mBooleanFlagCache.containsKey(id)) {
- mBooleanFlagCache.put(id,
+ String name = flag.getName();
+ if (!mBooleanFlagCache.containsKey(name)) {
+ mBooleanFlagCache.put(name,
readBooleanFlagInternal(flag, flag.getDefault()));
}
- return mBooleanFlagCache.get(id);
+ return mBooleanFlagCache.get(name);
}
@Override
public boolean isEnabled(@NonNull ResourceBooleanFlag flag) {
- int id = flag.getId();
- if (!mBooleanFlagCache.containsKey(id)) {
- mBooleanFlagCache.put(id,
+ String name = flag.getName();
+ if (!mBooleanFlagCache.containsKey(name)) {
+ mBooleanFlagCache.put(name,
readBooleanFlagInternal(flag, mResources.getBoolean(flag.getResourceId())));
}
- return mBooleanFlagCache.get(id);
+ return mBooleanFlagCache.get(name);
}
@Override
public boolean isEnabled(@NonNull SysPropBooleanFlag flag) {
- int id = flag.getId();
- if (!mBooleanFlagCache.containsKey(id)) {
+ String name = flag.getName();
+ if (!mBooleanFlagCache.containsKey(name)) {
// Use #readFlagValue to get the default. That will allow it to fall through to
// teamfood if need be.
mBooleanFlagCache.put(
- id,
+ name,
mSystemProperties.getBoolean(
flag.getName(),
readBooleanFlagInternal(flag, flag.getDefault())));
}
- return mBooleanFlagCache.get(id);
+ return mBooleanFlagCache.get(name);
}
@NonNull
@Override
public String getString(@NonNull StringFlag flag) {
- int id = flag.getId();
- if (!mStringFlagCache.containsKey(id)) {
- mStringFlagCache.put(id,
- readFlagValueInternal(id, flag.getDefault(), StringFlagSerializer.INSTANCE));
+ String name = flag.getName();
+ if (!mStringFlagCache.containsKey(name)) {
+ mStringFlagCache.put(name,
+ readFlagValueInternal(
+ flag.getId(), name, flag.getDefault(), StringFlagSerializer.INSTANCE));
}
- return mStringFlagCache.get(id);
+ return mStringFlagCache.get(name);
}
@NonNull
@Override
public String getString(@NonNull ResourceStringFlag flag) {
- int id = flag.getId();
- if (!mStringFlagCache.containsKey(id)) {
- mStringFlagCache.put(id,
- readFlagValueInternal(id, mResources.getString(flag.getResourceId()),
+ String name = flag.getName();
+ if (!mStringFlagCache.containsKey(name)) {
+ mStringFlagCache.put(name,
+ readFlagValueInternal(
+ flag.getId(), name, mResources.getString(flag.getResourceId()),
StringFlagSerializer.INSTANCE));
}
- return mStringFlagCache.get(id);
+ return mStringFlagCache.get(name);
}
@NonNull
@Override
public int getInt(@NonNull IntFlag flag) {
- int id = flag.getId();
- if (!mIntFlagCache.containsKey(id)) {
- mIntFlagCache.put(id,
- readFlagValueInternal(id, flag.getDefault(), IntFlagSerializer.INSTANCE));
+ String name = flag.getName();
+ if (!mIntFlagCache.containsKey(name)) {
+ mIntFlagCache.put(name,
+ readFlagValueInternal(
+ flag.getId(), name, flag.getDefault(), IntFlagSerializer.INSTANCE));
}
- return mIntFlagCache.get(id);
+ return mIntFlagCache.get(name);
}
@NonNull
@Override
public int getInt(@NonNull ResourceIntFlag flag) {
- int id = flag.getId();
- if (!mIntFlagCache.containsKey(id)) {
- mIntFlagCache.put(id,
- readFlagValueInternal(id, mResources.getInteger(flag.getResourceId()),
+ String name = flag.getName();
+ if (!mIntFlagCache.containsKey(name)) {
+ mIntFlagCache.put(name,
+ readFlagValueInternal(
+ flag.getId(), name, mResources.getInteger(flag.getResourceId()),
IntFlagSerializer.INSTANCE));
}
- return mIntFlagCache.get(id);
+ return mIntFlagCache.get(name);
}
/** Specific override for Boolean flags that checks against the teamfood list.*/
private boolean readBooleanFlagInternal(Flag<Boolean> flag, boolean defaultValue) {
- Boolean result = readBooleanFlagOverride(flag.getId());
+ Boolean result = readBooleanFlagOverride(flag.getName());
+ if (result == null) {
+ result = readBooleanFlagOverride(flag.getId());
+ }
boolean hasServerOverride = mServerFlagReader.hasOverride(
flag.getNamespace(), flag.getName());
@@ -231,7 +242,7 @@
if (!hasServerOverride
&& !defaultValue
&& result == null
- && flag.getId() != Flags.TEAMFOOD.getId()
+ && !flag.getName().equals(Flags.TEAMFOOD.getName())
&& flag.getTeamfood()) {
return isEnabled(Flags.TEAMFOOD);
}
@@ -244,16 +255,31 @@
return readFlagValueInternal(id, BooleanFlagSerializer.INSTANCE);
}
+ private Boolean readBooleanFlagOverride(String name) {
+ return readFlagValueInternal(name, BooleanFlagSerializer.INSTANCE);
+ }
+
+ // TODO(b/265188950): Remove id from this method once ids are fully deprecated.
@NonNull
private <T> T readFlagValueInternal(
- int id, @NonNull T defaultValue, FlagSerializer<T> serializer) {
+ int id, String name, @NonNull T defaultValue, FlagSerializer<T> serializer) {
requireNonNull(defaultValue, "defaultValue");
- T result = readFlagValueInternal(id, serializer);
- return result == null ? defaultValue : result;
+ T resultForName = readFlagValueInternal(name, serializer);
+ if (resultForName == null) {
+ T resultForId = readFlagValueInternal(id, serializer);
+ if (resultForId == null) {
+ return defaultValue;
+ } else {
+ setFlagValue(name, resultForId, serializer);
+ return resultForId;
+ }
+ }
+ return resultForName;
}
/** Returns the stored value or null if not set. */
+ // TODO(b/265188950): Remove method this once ids are fully deprecated.
@Nullable
private <T> T readFlagValueInternal(int id, FlagSerializer<T> serializer) {
try {
@@ -264,51 +290,71 @@
return null;
}
- private <T> void setFlagValue(int id, @NonNull T value, FlagSerializer<T> serializer) {
+ /** Returns the stored value or null if not set. */
+ @Nullable
+ private <T> T readFlagValueInternal(String name, FlagSerializer<T> serializer) {
+ try {
+ return mFlagManager.readFlagValue(name, serializer);
+ } catch (Exception e) {
+ eraseInternal(name);
+ }
+ return null;
+ }
+
+ private <T> void setFlagValue(String name, @NonNull T value, FlagSerializer<T> serializer) {
requireNonNull(value, "Cannot set a null value");
- T currentValue = readFlagValueInternal(id, serializer);
+ T currentValue = readFlagValueInternal(name, serializer);
if (Objects.equals(currentValue, value)) {
- Log.i(TAG, "Flag id " + id + " is already " + value);
+ Log.i(TAG, "Flag id " + name + " is already " + value);
return;
}
final String data = serializer.toSettingsData(value);
if (data == null) {
- Log.w(TAG, "Failed to set id " + id + " to " + value);
+ Log.w(TAG, "Failed to set id " + name + " to " + value);
return;
}
- mSecureSettings.putStringForUser(mFlagManager.idToSettingsKey(id), data,
+ mGlobalSettings.putStringForUser(mFlagManager.nameToSettingsKey(name), data,
UserHandle.USER_CURRENT);
- Log.i(TAG, "Set id " + id + " to " + value);
- removeFromCache(id);
- mFlagManager.dispatchListenersAndMaybeRestart(id, this::restartSystemUI);
+ Log.i(TAG, "Set id " + name + " to " + value);
+ removeFromCache(name);
+ mFlagManager.dispatchListenersAndMaybeRestart(name, this::restartSystemUI);
}
<T> void eraseFlag(Flag<T> flag) {
if (flag instanceof SysPropFlag) {
mSystemProperties.erase(((SysPropFlag<T>) flag).getName());
- dispatchListenersAndMaybeRestart(flag.getId(), this::restartAndroid);
+ dispatchListenersAndMaybeRestart(flag.getName(), this::restartAndroid);
} else {
- eraseFlag(flag.getId());
+ eraseFlag(flag.getName());
}
}
/** Erase a flag's overridden value if there is one. */
- private void eraseFlag(int id) {
- eraseInternal(id);
- removeFromCache(id);
- dispatchListenersAndMaybeRestart(id, this::restartSystemUI);
+ private void eraseFlag(String name) {
+ eraseInternal(name);
+ removeFromCache(name);
+ dispatchListenersAndMaybeRestart(name, this::restartSystemUI);
}
- private void dispatchListenersAndMaybeRestart(int id, Consumer<Boolean> restartAction) {
- mFlagManager.dispatchListenersAndMaybeRestart(id, restartAction);
+ private void dispatchListenersAndMaybeRestart(String name, Consumer<Boolean> restartAction) {
+ mFlagManager.dispatchListenersAndMaybeRestart(name, restartAction);
}
- /** Works just like {@link #eraseFlag(int)} except that it doesn't restart SystemUI. */
+ /** Works just like {@link #eraseFlag(String)} except that it doesn't restart SystemUI. */
+ // TODO(b/265188950): Remove method this once ids are fully deprecated.
private void eraseInternal(int id) {
- // We can't actually "erase" things from sysprops, but we can set them to empty!
- mSecureSettings.putStringForUser(mFlagManager.idToSettingsKey(id), "",
+ // We can't actually "erase" things from settings, but we can set them to empty!
+ mGlobalSettings.putStringForUser(mFlagManager.idToSettingsKey(id), "",
UserHandle.USER_CURRENT);
- Log.i(TAG, "Erase id " + id);
+ Log.i(TAG, "Erase name " + id);
+ }
+
+ /** Works just like {@link #eraseFlag(String)} except that it doesn't restart SystemUI. */
+ private void eraseInternal(String name) {
+ // We can't actually "erase" things from settings, but we can set them to empty!
+ mGlobalSettings.putStringForUser(mFlagManager.nameToSettingsKey(name), "",
+ UserHandle.USER_CURRENT);
+ Log.i(TAG, "Erase name " + name);
}
@Override
@@ -339,13 +385,13 @@
void setBooleanFlagInternal(Flag<?> flag, boolean value) {
if (flag instanceof BooleanFlag) {
- setFlagValue(flag.getId(), value, BooleanFlagSerializer.INSTANCE);
+ setFlagValue(flag.getName(), value, BooleanFlagSerializer.INSTANCE);
} else if (flag instanceof ResourceBooleanFlag) {
- setFlagValue(flag.getId(), value, BooleanFlagSerializer.INSTANCE);
+ setFlagValue(flag.getName(), value, BooleanFlagSerializer.INSTANCE);
} else if (flag instanceof SysPropBooleanFlag) {
// Store SysProp flags in SystemProperties where they can read by outside parties.
mSystemProperties.setBoolean(((SysPropBooleanFlag) flag).getName(), value);
- dispatchListenersAndMaybeRestart(flag.getId(),
+ dispatchListenersAndMaybeRestart(flag.getName(),
FeatureFlagsDebug.this::restartAndroid);
} else {
throw new IllegalArgumentException("Unknown flag type");
@@ -354,9 +400,9 @@
void setStringFlagInternal(Flag<?> flag, String value) {
if (flag instanceof StringFlag) {
- setFlagValue(flag.getId(), value, StringFlagSerializer.INSTANCE);
+ setFlagValue(flag.getName(), value, StringFlagSerializer.INSTANCE);
} else if (flag instanceof ResourceStringFlag) {
- setFlagValue(flag.getId(), value, StringFlagSerializer.INSTANCE);
+ setFlagValue(flag.getName(), value, StringFlagSerializer.INSTANCE);
} else {
throw new IllegalArgumentException("Unknown flag type");
}
@@ -364,9 +410,9 @@
void setIntFlagInternal(Flag<?> flag, int value) {
if (flag instanceof IntFlag) {
- setFlagValue(flag.getId(), value, IntFlagSerializer.INSTANCE);
+ setFlagValue(flag.getName(), value, IntFlagSerializer.INSTANCE);
} else if (flag instanceof ResourceIntFlag) {
- setFlagValue(flag.getId(), value, IntFlagSerializer.INSTANCE);
+ setFlagValue(flag.getName(), value, IntFlagSerializer.INSTANCE);
} else {
throw new IllegalArgumentException("Unknown flag type");
}
@@ -405,17 +451,17 @@
Log.w(TAG, "No extras");
return;
}
- int id = extras.getInt(EXTRA_ID);
- if (id <= 0) {
- Log.w(TAG, "ID not set or less than or equal to 0: " + id);
+ String name = extras.getString(EXTRA_NAME);
+ if (name == null || name.isEmpty()) {
+ Log.w(TAG, "NAME not set or is empty: " + name);
return;
}
- if (!mAllFlags.containsKey(id)) {
- Log.w(TAG, "Tried to set unknown id: " + id);
+ if (!mAllFlags.containsKey(name)) {
+ Log.w(TAG, "Tried to set unknown name: " + name);
return;
}
- Flag<?> flag = mAllFlags.get(id);
+ Flag<?> flag = mAllFlags.get(name);
if (!extras.containsKey(EXTRA_VALUE)) {
eraseFlag(flag);
@@ -452,13 +498,16 @@
if (f instanceof ReleasedFlag) {
enabled = isEnabled((ReleasedFlag) f);
- overridden = readBooleanFlagOverride(f.getId()) != null;
+ overridden = readBooleanFlagOverride(f.getName()) != null
+ || readBooleanFlagOverride(f.getId()) != null;
} else if (f instanceof UnreleasedFlag) {
enabled = isEnabled((UnreleasedFlag) f);
- overridden = readBooleanFlagOverride(f.getId()) != null;
+ overridden = readBooleanFlagOverride(f.getName()) != null
+ || readBooleanFlagOverride(f.getId()) != null;
} else if (f instanceof ResourceBooleanFlag) {
enabled = isEnabled((ResourceBooleanFlag) f);
- overridden = readBooleanFlagOverride(f.getId()) != null;
+ overridden = readBooleanFlagOverride(f.getName()) != null
+ || readBooleanFlagOverride(f.getId()) != null;
} else if (f instanceof SysPropBooleanFlag) {
// TODO(b/223379190): Teamfood not supported for sysprop flags yet.
enabled = isEnabled((SysPropBooleanFlag) f);
@@ -480,9 +529,9 @@
}
};
- private void removeFromCache(int id) {
- mBooleanFlagCache.remove(id);
- mStringFlagCache.remove(id);
+ private void removeFromCache(String name) {
+ mBooleanFlagCache.remove(name);
+ mStringFlagCache.remove(name);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java
index 8bddacc..7e14237 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java
@@ -21,18 +21,16 @@
import static java.util.Objects.requireNonNull;
import android.content.res.Resources;
-import android.util.SparseArray;
-import android.util.SparseBooleanArray;
import androidx.annotation.NonNull;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.util.DeviceConfigProxy;
import org.jetbrains.annotations.NotNull;
import java.io.PrintWriter;
+import java.util.HashMap;
import java.util.Map;
import javax.inject.Inject;
@@ -50,12 +48,11 @@
private final Resources mResources;
private final SystemPropertiesHelper mSystemProperties;
- private final DeviceConfigProxy mDeviceConfigProxy;
private final ServerFlagReader mServerFlagReader;
private final Restarter mRestarter;
- private final Map<Integer, Flag<?>> mAllFlags;
- SparseBooleanArray mBooleanCache = new SparseBooleanArray();
- SparseArray<String> mStringCache = new SparseArray<>();
+ private final Map<String, Flag<?>> mAllFlags;
+ private final Map<String, Boolean> mBooleanCache = new HashMap<>();
+ private final Map<String, String> mStringCache = new HashMap<>();
private final ServerFlagReader.ChangeListener mOnPropertiesChanged =
new ServerFlagReader.ChangeListener() {
@@ -69,13 +66,11 @@
public FeatureFlagsRelease(
@Main Resources resources,
SystemPropertiesHelper systemProperties,
- DeviceConfigProxy deviceConfigProxy,
ServerFlagReader serverFlagReader,
- @Named(ALL_FLAGS) Map<Integer, Flag<?>> allFlags,
+ @Named(ALL_FLAGS) Map<String, Flag<?>> allFlags,
Restarter restarter) {
mResources = resources;
mSystemProperties = systemProperties;
- mDeviceConfigProxy = deviceConfigProxy;
mServerFlagReader = serverFlagReader;
mAllFlags = allFlags;
mRestarter = restarter;
@@ -106,50 +101,48 @@
@Override
public boolean isEnabled(ResourceBooleanFlag flag) {
- int cacheIndex = mBooleanCache.indexOfKey(flag.getId());
- if (cacheIndex < 0) {
- return isEnabled(flag.getId(), mResources.getBoolean(flag.getResourceId()));
+ if (!mBooleanCache.containsKey(flag.getName())) {
+ return isEnabled(flag.getName(), mResources.getBoolean(flag.getResourceId()));
}
- return mBooleanCache.valueAt(cacheIndex);
+ return mBooleanCache.get(flag.getName());
}
@Override
public boolean isEnabled(SysPropBooleanFlag flag) {
- int cacheIndex = mBooleanCache.indexOfKey(flag.getId());
- if (cacheIndex < 0) {
+ if (!mBooleanCache.containsKey(flag.getName())) {
return isEnabled(
- flag.getId(), mSystemProperties.getBoolean(flag.getName(), flag.getDefault()));
+ flag.getName(),
+ mSystemProperties.getBoolean(flag.getName(), flag.getDefault()));
}
- return mBooleanCache.valueAt(cacheIndex);
+ return mBooleanCache.get(flag.getName());
}
- private boolean isEnabled(int key, boolean defaultValue) {
- mBooleanCache.append(key, defaultValue);
+ private boolean isEnabled(String name, boolean defaultValue) {
+ mBooleanCache.put(name, defaultValue);
return defaultValue;
}
@NonNull
@Override
public String getString(@NonNull StringFlag flag) {
- return getString(flag.getId(), flag.getDefault());
+ return getString(flag.getName(), flag.getDefault());
}
@NonNull
@Override
public String getString(@NonNull ResourceStringFlag flag) {
- int cacheIndex = mStringCache.indexOfKey(flag.getId());
- if (cacheIndex < 0) {
- return getString(flag.getId(),
+ if (!mStringCache.containsKey(flag.getName())) {
+ return getString(flag.getName(),
requireNonNull(mResources.getString(flag.getResourceId())));
}
- return mStringCache.valueAt(cacheIndex);
+ return mStringCache.get(flag.getName());
}
- private String getString(int key, String defaultValue) {
- mStringCache.append(key, defaultValue);
+ private String getString(String name, String defaultValue) {
+ mStringCache.put(name, defaultValue);
return defaultValue;
}
@@ -169,11 +162,17 @@
public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
pw.println("can override: false");
Map<String, Flag<?>> knownFlags = FlagsFactory.INSTANCE.getKnownFlags();
+ pw.println("Booleans: ");
for (Map.Entry<String, Flag<?>> nameToFlag : knownFlags.entrySet()) {
Flag<?> flag = nameToFlag.getValue();
- int id = flag.getId();
+ if (!(flag instanceof BooleanFlag)
+ || !(flag instanceof ResourceBooleanFlag)
+ || !(flag instanceof SysPropBooleanFlag)) {
+ continue;
+ }
+
boolean def = false;
- if (mBooleanCache.indexOfKey(flag.getId()) < 0) {
+ if (!mBooleanCache.containsKey(flag.getName())) {
if (flag instanceof SysPropBooleanFlag) {
SysPropBooleanFlag f = (SysPropBooleanFlag) flag;
def = mSystemProperties.getBoolean(f.getName(), f.getDefault());
@@ -185,15 +184,32 @@
def = f.getDefault();
}
}
- pw.println(" sysui_flag_" + id + ": " + (mBooleanCache.get(id, def)));
+ pw.println(
+ " " + flag.getName() + ": "
+ + (mBooleanCache.getOrDefault(flag.getName(), def)));
}
- int numStrings = mStringCache.size();
- pw.println("Strings: " + numStrings);
- for (int i = 0; i < numStrings; i++) {
- final int id = mStringCache.keyAt(i);
- final String value = mStringCache.valueAt(i);
- final int length = value.length();
- pw.println(" sysui_flag_" + id + ": [length=" + length + "] \"" + value + "\"");
+
+ pw.println("Strings: ");
+ for (Map.Entry<String, Flag<?>> nameToFlag : knownFlags.entrySet()) {
+ Flag<?> flag = nameToFlag.getValue();
+ if (!(flag instanceof StringFlag)
+ || !(flag instanceof ResourceStringFlag)) {
+ continue;
+ }
+
+ String def = "";
+ if (!mBooleanCache.containsKey(flag.getName())) {
+ if (flag instanceof ResourceStringFlag) {
+ ResourceStringFlag f = (ResourceStringFlag) flag;
+ def = mResources.getString(f.getResourceId());
+ } else if (flag instanceof StringFlag) {
+ StringFlag f = (StringFlag) flag;
+ def = f.getDefault();
+ }
+ }
+ String value = mStringCache.getOrDefault(flag.getName(), def);
+ pw.println(
+ " " + flag.getName() + ": [length=" + value.length() + "] \"" + value + "\"");
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagCommand.java b/packages/SystemUI/src/com/android/systemui/flags/FlagCommand.java
index b7fc0e4..daf9429 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FlagCommand.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FlagCommand.java
@@ -39,12 +39,12 @@
private final List<String> mOffCommands = List.of("false", "off", "0", "disable");
private final List<String> mSetCommands = List.of("set", "put");
private final FeatureFlagsDebug mFeatureFlags;
- private final Map<Integer, Flag<?>> mAllFlags;
+ private final Map<String, Flag<?>> mAllFlags;
@Inject
FlagCommand(
FeatureFlagsDebug featureFlags,
- @Named(ALL_FLAGS) Map<Integer, Flag<?>> allFlags
+ @Named(ALL_FLAGS) Map<String, Flag<?>> allFlags
) {
mFeatureFlags = featureFlags;
mAllFlags = allFlags;
@@ -53,30 +53,22 @@
@Override
public void execute(@NonNull PrintWriter pw, @NonNull List<String> args) {
if (args.size() == 0) {
- pw.println("Error: no flag id supplied");
+ pw.println("Error: no flag name supplied");
help(pw);
pw.println();
printKnownFlags(pw);
return;
}
- int id = 0;
- try {
- id = Integer.parseInt(args.get(0));
- if (!mAllFlags.containsKey(id)) {
- pw.println("Unknown flag id: " + id);
- pw.println();
- printKnownFlags(pw);
- return;
- }
- } catch (NumberFormatException e) {
- id = flagNameToId(args.get(0));
- if (id == 0) {
- pw.println("Invalid flag. Must an integer id or flag name: " + args.get(0));
- return;
- }
+ String name = args.get(0);
+ if (!mAllFlags.containsKey(name)) {
+ pw.println("Unknown flag name: " + name);
+ pw.println();
+ printKnownFlags(pw);
+ return;
}
- Flag<?> flag = mAllFlags.get(id);
+
+ Flag<?> flag = mAllFlags.get(name);
String cmd = "";
if (args.size() > 1) {
@@ -117,7 +109,7 @@
return;
}
- pw.println("Flag " + id + " is " + newValue);
+ pw.println("Flag " + name + " is " + newValue);
pw.flush(); // Next command will restart sysui, so flush before we do so.
if (shouldSet) {
mFeatureFlags.setBooleanFlagInternal(flag, newValue);
@@ -136,11 +128,11 @@
return;
}
String value = args.get(2);
- pw.println("Setting Flag " + id + " to " + value);
+ pw.println("Setting Flag " + name + " to " + value);
pw.flush(); // Next command will restart sysui, so flush before we do so.
mFeatureFlags.setStringFlagInternal(flag, args.get(2));
} else {
- pw.println("Flag " + id + " is " + getStringFlag(flag));
+ pw.println("Flag " + name + " is " + getStringFlag(flag));
}
return;
} else if (isIntFlag(flag)) {
@@ -155,11 +147,11 @@
return;
}
int value = Integer.parseInt(args.get(2));
- pw.println("Setting Flag " + id + " to " + value);
+ pw.println("Setting Flag " + name + " to " + value);
pw.flush(); // Next command will restart sysui, so flush before we do so.
mFeatureFlags.setIntFlagInternal(flag, value);
} else {
- pw.println("Flag " + id + " is " + getIntFlag(flag));
+ pw.println("Flag " + name + " is " + getIntFlag(flag));
}
return;
}
@@ -182,8 +174,7 @@
private boolean isBooleanFlag(Flag<?> flag) {
return (flag instanceof BooleanFlag)
|| (flag instanceof ResourceBooleanFlag)
- || (flag instanceof SysPropFlag)
- || (flag instanceof DeviceConfigBooleanFlag);
+ || (flag instanceof SysPropFlag);
}
private boolean isBooleanFlagEnabled(Flag<?> flag) {
@@ -252,15 +243,14 @@
for (int i = 0; i < longestFieldName - "Flag Name".length() + 1; i++) {
pw.print(" ");
}
- pw.println("ID Value");
+ pw.println(" Value");
for (int i = 0; i < longestFieldName; i++) {
pw.print("=");
}
- pw.println(" ==== ========");
+ pw.println(" ========");
for (String fieldName : fields.keySet()) {
Flag<?> flag = fields.get(fieldName);
- int id = flag.getId();
- if (id == 0 || !mAllFlags.containsKey(id)) {
+ if (!mAllFlags.containsKey(flag.getName())) {
continue;
}
pw.print(fieldName);
@@ -268,9 +258,9 @@
for (int i = 0; i < longestFieldName - fieldWidth + 1; i++) {
pw.print(" ");
}
- pw.printf("%-4d ", id);
+ pw.print(" ");
if (isBooleanFlag(flag)) {
- pw.println(isBooleanFlagEnabled(mAllFlags.get(id)));
+ pw.println(isBooleanFlagEnabled(flag));
} else if (isStringFlag(flag)) {
pw.println(getStringFlag(flag));
} else if (isIntFlag(flag)) {
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 12d36bc..9595bc4 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -128,13 +128,6 @@
val LOCKSCREEN_CUSTOM_CLOCKS = unreleasedFlag(207, "lockscreen_custom_clocks", teamfood = true)
/**
- * Flag to enable the usage of the new bouncer data source. This is a refactor of and eventual
- * replacement of KeyguardBouncer.java.
- */
- // TODO(b/254512385): Tracking Bug
- @JvmField val MODERN_BOUNCER = releasedFlag(208, "modern_bouncer")
-
- /**
* Whether the clock on a wide lock screen should use the new "stepping" animation for moving
* the digits when the clock moves.
*/
@@ -345,8 +338,7 @@
// TODO(b/254513168): Tracking Bug
@JvmField val UMO_SURFACE_RIPPLE = unreleasedFlag(907, "umo_surface_ripple")
- @JvmField
- val MEDIA_FALSING_PENALTY = unreleasedFlag(908, "media_falsing_media", teamfood = true)
+ @JvmField val MEDIA_FALSING_PENALTY = releasedFlag(908, "media_falsing_media")
// TODO(b/261734857): Tracking Bug
@JvmField val UMO_TURBULENCE_NOISE = unreleasedFlag(909, "umo_turbulence_noise")
@@ -369,6 +361,9 @@
@JvmField
val MEDIA_RECOMMENDATION_CARD_UPDATE = unreleasedFlag(914, "media_recommendation_card_update")
+ // TODO(b/267007629): Tracking Bug
+ val MEDIA_RESUME_PROGRESS = unreleasedFlag(915, "media_resume_progress")
+
// 1000 - dock
val SIMULATE_DOCK_THROUGH_CHARGING = releasedFlag(1000, "simulate_dock_through_charging")
@@ -471,7 +466,7 @@
@Keep
@JvmField
val WM_ENABLE_PREDICTIVE_BACK_ANIM =
- sysPropBooleanFlag(1201, "persist.wm.debug.predictive_back_anim", default = false)
+ sysPropBooleanFlag(1201, "persist.wm.debug.predictive_back_anim", default = true)
@Keep
@JvmField
@@ -504,7 +499,7 @@
// TODO(b/238475428): Tracking Bug
@JvmField
val WM_SHADE_ANIMATE_BACK_GESTURE =
- unreleasedFlag(1208, "persist.wm.debug.shade_animate_back_gesture", teamfood = true)
+ unreleasedFlag(1208, "persist.wm.debug.shade_animate_back_gesture", teamfood = false)
// TODO(b/265639042): Tracking Bug
@JvmField
@@ -520,6 +515,9 @@
// TODO(b/264916608): Tracking Bug
@JvmField val SCREENSHOT_METADATA = unreleasedFlag(1302, "screenshot_metadata")
+ // TODO(b/266955521): Tracking bug
+ @JvmField val SCREENSHOT_DETECTION = unreleasedFlag(1303, "screenshot_detection")
+
// 1400 - columbus
// TODO(b/254512756): Tracking Bug
val QUICK_TAP_IN_PCC = releasedFlag(1400, "quick_tap_in_pcc")
@@ -528,10 +526,19 @@
val QUICK_TAP_FLOW_FRAMEWORK =
unreleasedFlag(1401, "quick_tap_flow_framework", teamfood = false)
- // 1500 - chooser
+ // 1500 - chooser aka sharesheet
// TODO(b/254512507): Tracking Bug
val CHOOSER_UNBUNDLED = unreleasedFlag(1500, "chooser_unbundled", teamfood = true)
+ // TODO(b/266983432) Tracking Bug
+ val SHARESHEET_CUSTOM_ACTIONS = unreleasedFlag(1501, "sharesheet_custom_actions")
+
+ // TODO(b/266982749) Tracking Bug
+ val SHARESHEET_RESELECTION_ACTION = unreleasedFlag(1502, "sharesheet_reselection_action")
+
+ // TODO(b/266983474) Tracking Bug
+ val SHARESHEET_IMAGE_AND_TEXT_PREVIEW = unreleasedFlag(1503, "sharesheet_image_text_preview")
+
// 1600 - accessibility
@JvmField
val A11Y_FLOATING_MENU_FLING_SPRING_ANIMATIONS =
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagsCommonModule.kt b/packages/SystemUI/src/com/android/systemui/flags/FlagsCommonModule.kt
index 8442230..0054d26 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FlagsCommonModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FlagsCommonModule.kt
@@ -28,8 +28,8 @@
@JvmStatic
@Provides
@Named(ALL_FLAGS)
- fun providesAllFlags(): Map<Int, Flag<*>> {
- return FlagsFactory.knownFlags.map { it.value.id to it.value }.toMap()
+ fun providesAllFlags(): Map<String, Flag<*>> {
+ return FlagsFactory.knownFlags
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/ServerFlagReader.kt b/packages/SystemUI/src/com/android/systemui/flags/ServerFlagReader.kt
index ae05c46..a02b795 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/ServerFlagReader.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/ServerFlagReader.kt
@@ -54,10 +54,11 @@
return
}
+
for ((listener, flags) in listeners) {
propLoop@ for (propName in properties.keyset) {
for (flag in flags) {
- if (propName == getServerOverrideName(flag.id)) {
+ if (propName == getServerOverrideName(flag.id) || propName == flag.name) {
listener.onChange()
break@propLoop
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt
index 76c2430..80675d3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt
@@ -29,6 +29,7 @@
const val DO_NOT_DISTURB = "do_not_disturb"
const val FLASHLIGHT = "flashlight"
const val HOME_CONTROLS = "home"
+ const val MUTE = "mute"
const val QR_CODE_SCANNER = "qr_code_scanner"
const val QUICK_ACCESS_WALLET = "wallet"
const val VIDEO_CAMERA = "video_camera"
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt
index a1cce5c..4556195 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt
@@ -33,12 +33,13 @@
@Provides
@ElementsIntoSet
fun quickAffordanceConfigs(
+ camera: CameraQuickAffordanceConfig,
doNotDisturb: DoNotDisturbQuickAffordanceConfig,
flashlight: FlashlightQuickAffordanceConfig,
home: HomeControlsKeyguardQuickAffordanceConfig,
+ mute: MuteQuickAffordanceConfig,
quickAccessWallet: QuickAccessWalletKeyguardQuickAffordanceConfig,
qrCodeScanner: QrCodeScannerKeyguardQuickAffordanceConfig,
- camera: CameraQuickAffordanceConfig,
videoCamera: VideoCameraQuickAffordanceConfig,
): Set<KeyguardQuickAffordanceConfig> {
return setOf(
@@ -46,6 +47,7 @@
doNotDisturb,
flashlight,
home,
+ mute,
quickAccessWallet,
qrCodeScanner,
videoCamera,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfig.kt
new file mode 100644
index 0000000..d085db9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfig.kt
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.data.quickaffordance
+
+import android.content.Context
+import android.media.AudioManager
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.Observer
+import com.android.systemui.R
+import com.android.systemui.animation.Expandable
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
+import com.android.systemui.settings.UserFileManager
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.util.RingerModeTracker
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.onStart
+import javax.inject.Inject
+
+@SysUISingleton
+class MuteQuickAffordanceConfig @Inject constructor(
+ context: Context,
+ private val userTracker: UserTracker,
+ private val userFileManager: UserFileManager,
+ private val ringerModeTracker: RingerModeTracker,
+ private val audioManager: AudioManager,
+) : KeyguardQuickAffordanceConfig {
+
+ private var previousNonSilentMode: Int = DEFAULT_LAST_NON_SILENT_VALUE
+
+ override val key: String = BuiltInKeyguardQuickAffordanceKeys.MUTE
+
+ override val pickerName: String = context.getString(R.string.volume_ringer_status_silent)
+
+ override val pickerIconResourceId: Int = R.drawable.ic_notifications_silence
+
+ override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState> =
+ ringerModeTracker.ringerModeInternal.asFlow()
+ .onStart { emit(getLastNonSilentRingerMode()) }
+ .distinctUntilChanged()
+ .onEach { mode ->
+ // only remember last non-SILENT ringer mode
+ if (mode != null && mode != AudioManager.RINGER_MODE_SILENT) {
+ previousNonSilentMode = mode
+ }
+ }
+ .map { mode ->
+ val (activationState, contentDescriptionRes) = when {
+ audioManager.isVolumeFixed ->
+ ActivationState.NotSupported to
+ R.string.volume_ringer_hint_mute
+ mode == AudioManager.RINGER_MODE_SILENT ->
+ ActivationState.Active to
+ R.string.volume_ringer_hint_mute
+ else ->
+ ActivationState.Inactive to
+ R.string.volume_ringer_hint_unmute
+ }
+
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(
+ Icon.Resource(
+ R.drawable.ic_notifications_silence,
+ ContentDescription.Resource(contentDescriptionRes),
+ ),
+ activationState,
+ )
+ }
+
+ override fun onTriggered(
+ expandable: Expandable?
+ ): KeyguardQuickAffordanceConfig.OnTriggeredResult {
+ val newRingerMode: Int
+ val currentRingerMode =
+ ringerModeTracker.ringerModeInternal.value ?: DEFAULT_LAST_NON_SILENT_VALUE
+ if (currentRingerMode == AudioManager.RINGER_MODE_SILENT) {
+ newRingerMode = previousNonSilentMode
+ } else {
+ previousNonSilentMode = currentRingerMode
+ newRingerMode = AudioManager.RINGER_MODE_SILENT
+ }
+
+ if (currentRingerMode != newRingerMode) {
+ audioManager.ringerModeInternal = newRingerMode
+ }
+ return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
+ }
+
+ override suspend fun getPickerScreenState(): KeyguardQuickAffordanceConfig.PickerScreenState =
+ if (audioManager.isVolumeFixed) {
+ KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice
+ } else {
+ KeyguardQuickAffordanceConfig.PickerScreenState.Default()
+ }
+
+ /**
+ * Gets the last non-silent ringer mode from shared-preferences if it exists. This is
+ * cached by [MuteQuickAffordanceCoreStartable] while this affordance is selected
+ */
+ private fun getLastNonSilentRingerMode(): Int =
+ userFileManager.getSharedPreferences(
+ MUTE_QUICK_AFFORDANCE_PREFS_FILE_NAME,
+ Context.MODE_PRIVATE,
+ userTracker.userId
+ ).getInt(
+ LAST_NON_SILENT_RINGER_MODE_KEY,
+ ringerModeTracker.ringerModeInternal.value ?: DEFAULT_LAST_NON_SILENT_VALUE
+ )
+
+ private fun <T> LiveData<T>.asFlow(): Flow<T?> =
+ conflatedCallbackFlow {
+ val observer = Observer { value: T -> trySend(value) }
+ observeForever(observer)
+ send(value)
+ awaitClose { removeObserver(observer) }
+ }
+
+ companion object {
+ const val LAST_NON_SILENT_RINGER_MODE_KEY = "key_last_non_silent_ringer_mode"
+ const val MUTE_QUICK_AFFORDANCE_PREFS_FILE_NAME = "quick_affordance_mute_ringer_mode_cache"
+ private const val DEFAULT_LAST_NON_SILENT_VALUE = AudioManager.RINGER_MODE_NORMAL
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceCoreStartable.kt
new file mode 100644
index 0000000..12a6310
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceCoreStartable.kt
@@ -0,0 +1,86 @@
+/*
+ * 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.keyguard.data.quickaffordance
+
+import android.content.Context
+import android.media.AudioManager
+import androidx.lifecycle.Observer
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
+import com.android.systemui.settings.UserFileManager
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.util.RingerModeTracker
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.map
+import javax.inject.Inject
+
+/**
+ * Store previous non-silent Ringer Mode into shared prefs to be used for Mute Lockscreen Shortcut
+ */
+@SysUISingleton
+class MuteQuickAffordanceCoreStartable @Inject constructor(
+ private val featureFlags: FeatureFlags,
+ private val userTracker: UserTracker,
+ private val ringerModeTracker: RingerModeTracker,
+ private val userFileManager: UserFileManager,
+ private val keyguardQuickAffordanceRepository: KeyguardQuickAffordanceRepository,
+ @Application private val coroutineScope: CoroutineScope,
+) : CoreStartable {
+
+ private val observer = Observer(this::updateLastNonSilentRingerMode)
+
+ override fun start() {
+ if (!featureFlags.isEnabled(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES)) return
+
+ // only listen to ringerModeInternal changes when Mute is one of the selected affordances
+ keyguardQuickAffordanceRepository
+ .selections
+ .map { selections ->
+ // determines if Mute is selected in any lockscreen shortcut position
+ val muteSelected: Boolean = selections.values.any { configList ->
+ configList.any { config ->
+ config.key == BuiltInKeyguardQuickAffordanceKeys.MUTE
+ }
+ }
+ if (muteSelected) {
+ ringerModeTracker.ringerModeInternal.observeForever(observer)
+ } else {
+ ringerModeTracker.ringerModeInternal.removeObserver(observer)
+ }
+ }
+ .launchIn(coroutineScope)
+ }
+
+ private fun updateLastNonSilentRingerMode(lastRingerMode: Int) {
+ if (AudioManager.RINGER_MODE_SILENT != lastRingerMode) {
+ userFileManager.getSharedPreferences(
+ MuteQuickAffordanceConfig.MUTE_QUICK_AFFORDANCE_PREFS_FILE_NAME,
+ Context.MODE_PRIVATE,
+ userTracker.userId
+ )
+ .edit()
+ .putInt(MuteQuickAffordanceConfig.LAST_NON_SILENT_RINGER_MODE_KEY, lastRingerMode)
+ .apply()
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt
similarity index 96%
rename from packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricRepository.kt
rename to packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt
index 25d8f40..0af596a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt
@@ -47,12 +47,13 @@
import kotlinx.coroutines.flow.transformLatest
/**
- * Acts as source of truth for biometric features.
+ * Acts as source of truth for biometric authentication related settings like enrollments, device
+ * policy, etc.
*
* Abstracts-away data sources and their schemas so the rest of the app doesn't need to worry about
* upstream changes.
*/
-interface BiometricRepository {
+interface BiometricSettingsRepository {
/** Whether any fingerprints are enrolled for the current user. */
val isFingerprintEnrolled: StateFlow<Boolean>
@@ -68,7 +69,7 @@
}
@SysUISingleton
-class BiometricRepositoryImpl
+class BiometricSettingsRepositoryImpl
@Inject
constructor(
context: Context,
@@ -80,7 +81,7 @@
@Application scope: CoroutineScope,
@Background backgroundDispatcher: CoroutineDispatcher,
@Main looper: Looper,
-) : BiometricRepository {
+) : BiometricSettingsRepository {
/** UserId of the current selected user. */
private val selectedUserId: Flow<Int> =
@@ -88,7 +89,7 @@
override val isFingerprintEnrolled: StateFlow<Boolean> =
selectedUserId
- .flatMapLatest { userId ->
+ .flatMapLatest {
conflatedCallbackFlow {
val callback =
object : AuthController.Callback {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt
index 3e17136..4ac6ac8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt
@@ -41,31 +41,96 @@
*
* Make sure to add newly added flows to the logger.
*/
+interface KeyguardBouncerRepository {
+ /** Values associated with the PrimaryBouncer (pin/pattern/password) input. */
+ val primaryBouncerVisible: StateFlow<Boolean>
+ val primaryBouncerShow: StateFlow<KeyguardBouncerModel?>
+ val primaryBouncerShowingSoon: StateFlow<Boolean>
+ val primaryBouncerHide: StateFlow<Boolean>
+ val primaryBouncerStartingToHide: StateFlow<Boolean>
+ val primaryBouncerStartingDisappearAnimation: StateFlow<Runnable?>
+ /** Determines if we want to instantaneously show the primary bouncer instead of translating. */
+ val primaryBouncerScrimmed: StateFlow<Boolean>
+ /**
+ * Set how much of the notification panel is showing on the screen.
+ * ```
+ * 0f = panel fully hidden = bouncer fully showing
+ * 1f = panel fully showing = bouncer fully hidden
+ * ```
+ */
+ val panelExpansionAmount: StateFlow<Float>
+ val keyguardPosition: StateFlow<Float>
+ val onScreenTurnedOff: StateFlow<Boolean>
+ val isBackButtonEnabled: StateFlow<Boolean?>
+ /** Determines if user is already unlocked */
+ val keyguardAuthenticated: StateFlow<Boolean?>
+ val showMessage: StateFlow<BouncerShowMessageModel?>
+ val resourceUpdateRequests: StateFlow<Boolean>
+ val bouncerPromptReason: Int
+ val bouncerErrorMessage: CharSequence?
+ val isAlternateBouncerVisible: StateFlow<Boolean>
+ val isAlternateBouncerUIAvailable: StateFlow<Boolean>
+ var lastAlternateBouncerVisibleTime: Long
+
+ fun setPrimaryScrimmed(isScrimmed: Boolean)
+
+ fun setPrimaryVisible(isVisible: Boolean)
+
+ fun setPrimaryShow(keyguardBouncerModel: KeyguardBouncerModel?)
+
+ fun setPrimaryShowingSoon(showingSoon: Boolean)
+
+ fun setPrimaryHide(hide: Boolean)
+
+ fun setPrimaryStartingToHide(startingToHide: Boolean)
+
+ fun setPrimaryStartDisappearAnimation(runnable: Runnable?)
+
+ fun setPanelExpansion(panelExpansion: Float)
+
+ fun setKeyguardPosition(keyguardPosition: Float)
+
+ fun setResourceUpdateRequests(willUpdateResources: Boolean)
+
+ fun setShowMessage(bouncerShowMessageModel: BouncerShowMessageModel?)
+
+ fun setKeyguardAuthenticated(keyguardAuthenticated: Boolean?)
+
+ fun setIsBackButtonEnabled(isBackButtonEnabled: Boolean)
+
+ fun setOnScreenTurnedOff(onScreenTurnedOff: Boolean)
+
+ fun setAlternateVisible(isVisible: Boolean)
+
+ fun setAlternateBouncerUIAvailable(isAvailable: Boolean)
+}
+
@SysUISingleton
-class KeyguardBouncerRepository
+class KeyguardBouncerRepositoryImpl
@Inject
constructor(
private val viewMediatorCallback: ViewMediatorCallback,
private val clock: SystemClock,
@Application private val applicationScope: CoroutineScope,
@BouncerLog private val buffer: TableLogBuffer,
-) {
+) : KeyguardBouncerRepository {
/** Values associated with the PrimaryBouncer (pin/pattern/password) input. */
private val _primaryBouncerVisible = MutableStateFlow(false)
- val primaryBouncerVisible = _primaryBouncerVisible.asStateFlow()
+ override val primaryBouncerVisible = _primaryBouncerVisible.asStateFlow()
private val _primaryBouncerShow = MutableStateFlow<KeyguardBouncerModel?>(null)
- val primaryBouncerShow = _primaryBouncerShow.asStateFlow()
+ override val primaryBouncerShow = _primaryBouncerShow.asStateFlow()
private val _primaryBouncerShowingSoon = MutableStateFlow(false)
- val primaryBouncerShowingSoon = _primaryBouncerShowingSoon.asStateFlow()
+ override val primaryBouncerShowingSoon = _primaryBouncerShowingSoon.asStateFlow()
private val _primaryBouncerHide = MutableStateFlow(false)
- val primaryBouncerHide = _primaryBouncerHide.asStateFlow()
+ override val primaryBouncerHide = _primaryBouncerHide.asStateFlow()
private val _primaryBouncerStartingToHide = MutableStateFlow(false)
- val primaryBouncerStartingToHide = _primaryBouncerStartingToHide.asStateFlow()
+ override val primaryBouncerStartingToHide = _primaryBouncerStartingToHide.asStateFlow()
private val _primaryBouncerDisappearAnimation = MutableStateFlow<Runnable?>(null)
- val primaryBouncerStartingDisappearAnimation = _primaryBouncerDisappearAnimation.asStateFlow()
+ override val primaryBouncerStartingDisappearAnimation =
+ _primaryBouncerDisappearAnimation.asStateFlow()
/** Determines if we want to instantaneously show the primary bouncer instead of translating. */
private val _primaryBouncerScrimmed = MutableStateFlow(false)
- val primaryBouncerScrimmed = _primaryBouncerScrimmed.asStateFlow()
+ override val primaryBouncerScrimmed = _primaryBouncerScrimmed.asStateFlow()
/**
* Set how much of the notification panel is showing on the screen.
* ```
@@ -74,23 +139,23 @@
* ```
*/
private val _panelExpansionAmount = MutableStateFlow(EXPANSION_HIDDEN)
- val panelExpansionAmount = _panelExpansionAmount.asStateFlow()
+ override val panelExpansionAmount = _panelExpansionAmount.asStateFlow()
private val _keyguardPosition = MutableStateFlow(0f)
- val keyguardPosition = _keyguardPosition.asStateFlow()
+ override val keyguardPosition = _keyguardPosition.asStateFlow()
private val _onScreenTurnedOff = MutableStateFlow(false)
- val onScreenTurnedOff = _onScreenTurnedOff.asStateFlow()
+ override val onScreenTurnedOff = _onScreenTurnedOff.asStateFlow()
private val _isBackButtonEnabled = MutableStateFlow<Boolean?>(null)
- val isBackButtonEnabled = _isBackButtonEnabled.asStateFlow()
+ override val isBackButtonEnabled = _isBackButtonEnabled.asStateFlow()
private val _keyguardAuthenticated = MutableStateFlow<Boolean?>(null)
/** Determines if user is already unlocked */
- val keyguardAuthenticated = _keyguardAuthenticated.asStateFlow()
+ override val keyguardAuthenticated = _keyguardAuthenticated.asStateFlow()
private val _showMessage = MutableStateFlow<BouncerShowMessageModel?>(null)
- val showMessage = _showMessage.asStateFlow()
+ override val showMessage = _showMessage.asStateFlow()
private val _resourceUpdateRequests = MutableStateFlow(false)
- val resourceUpdateRequests = _resourceUpdateRequests.asStateFlow()
- val bouncerPromptReason: Int
+ override val resourceUpdateRequests = _resourceUpdateRequests.asStateFlow()
+ override val bouncerPromptReason: Int
get() = viewMediatorCallback.bouncerPromptReason
- val bouncerErrorMessage: CharSequence?
+ override val bouncerErrorMessage: CharSequence?
get() = viewMediatorCallback.consumeCustomMessage()
init {
@@ -99,21 +164,21 @@
/** Values associated with the AlternateBouncer */
private val _isAlternateBouncerVisible = MutableStateFlow(false)
- val isAlternateBouncerVisible = _isAlternateBouncerVisible.asStateFlow()
- var lastAlternateBouncerVisibleTime: Long = NOT_VISIBLE
+ override val isAlternateBouncerVisible = _isAlternateBouncerVisible.asStateFlow()
+ override var lastAlternateBouncerVisibleTime: Long = NOT_VISIBLE
private val _isAlternateBouncerUIAvailable = MutableStateFlow<Boolean>(false)
- val isAlternateBouncerUIAvailable: StateFlow<Boolean> =
+ override val isAlternateBouncerUIAvailable: StateFlow<Boolean> =
_isAlternateBouncerUIAvailable.asStateFlow()
- fun setPrimaryScrimmed(isScrimmed: Boolean) {
+ override fun setPrimaryScrimmed(isScrimmed: Boolean) {
_primaryBouncerScrimmed.value = isScrimmed
}
- fun setPrimaryVisible(isVisible: Boolean) {
+ override fun setPrimaryVisible(isVisible: Boolean) {
_primaryBouncerVisible.value = isVisible
}
- fun setAlternateVisible(isVisible: Boolean) {
+ override fun setAlternateVisible(isVisible: Boolean) {
if (isVisible && !_isAlternateBouncerVisible.value) {
lastAlternateBouncerVisibleTime = clock.uptimeMillis()
} else if (!isVisible) {
@@ -122,55 +187,55 @@
_isAlternateBouncerVisible.value = isVisible
}
- fun setAlternateBouncerUIAvailable(isAvailable: Boolean) {
+ override fun setAlternateBouncerUIAvailable(isAvailable: Boolean) {
_isAlternateBouncerUIAvailable.value = isAvailable
}
- fun setPrimaryShow(keyguardBouncerModel: KeyguardBouncerModel?) {
+ override fun setPrimaryShow(keyguardBouncerModel: KeyguardBouncerModel?) {
_primaryBouncerShow.value = keyguardBouncerModel
}
- fun setPrimaryShowingSoon(showingSoon: Boolean) {
+ override fun setPrimaryShowingSoon(showingSoon: Boolean) {
_primaryBouncerShowingSoon.value = showingSoon
}
- fun setPrimaryHide(hide: Boolean) {
+ override fun setPrimaryHide(hide: Boolean) {
_primaryBouncerHide.value = hide
}
- fun setPrimaryStartingToHide(startingToHide: Boolean) {
+ override fun setPrimaryStartingToHide(startingToHide: Boolean) {
_primaryBouncerStartingToHide.value = startingToHide
}
- fun setPrimaryStartDisappearAnimation(runnable: Runnable?) {
+ override fun setPrimaryStartDisappearAnimation(runnable: Runnable?) {
_primaryBouncerDisappearAnimation.value = runnable
}
- fun setPanelExpansion(panelExpansion: Float) {
+ override fun setPanelExpansion(panelExpansion: Float) {
_panelExpansionAmount.value = panelExpansion
}
- fun setKeyguardPosition(keyguardPosition: Float) {
+ override fun setKeyguardPosition(keyguardPosition: Float) {
_keyguardPosition.value = keyguardPosition
}
- fun setResourceUpdateRequests(willUpdateResources: Boolean) {
+ override fun setResourceUpdateRequests(willUpdateResources: Boolean) {
_resourceUpdateRequests.value = willUpdateResources
}
- fun setShowMessage(bouncerShowMessageModel: BouncerShowMessageModel?) {
+ override fun setShowMessage(bouncerShowMessageModel: BouncerShowMessageModel?) {
_showMessage.value = bouncerShowMessageModel
}
- fun setKeyguardAuthenticated(keyguardAuthenticated: Boolean?) {
+ override fun setKeyguardAuthenticated(keyguardAuthenticated: Boolean?) {
_keyguardAuthenticated.value = keyguardAuthenticated
}
- fun setIsBackButtonEnabled(isBackButtonEnabled: Boolean) {
+ override fun setIsBackButtonEnabled(isBackButtonEnabled: Boolean) {
_isBackButtonEnabled.value = isBackButtonEnabled
}
- fun setOnScreenTurnedOff(onScreenTurnedOff: Boolean) {
+ override fun setOnScreenTurnedOff(onScreenTurnedOff: Boolean) {
_onScreenTurnedOff.value = onScreenTurnedOff
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt
index 2b2b9d0..8ece318 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt
@@ -146,7 +146,7 @@
* Returns a snapshot of the [KeyguardQuickAffordanceConfig] instances of the affordances at the
* slot with the given ID. The configs are sorted in descending priority order.
*/
- fun getSelections(slotId: String): List<KeyguardQuickAffordanceConfig> {
+ fun getCurrentSelections(slotId: String): List<KeyguardQuickAffordanceConfig> {
val selections = selectionManager.value.getSelections().getOrDefault(slotId, emptyList())
return configs.filter { selections.contains(it.key) }
}
@@ -155,7 +155,7 @@
* Returns a snapshot of the IDs of the selected affordances, indexed by slot ID. The configs
* are sorted in descending priority order.
*/
- fun getSelections(): Map<String, List<String>> {
+ fun getCurrentSelections(): Map<String, List<String>> {
return selectionManager.value.getSelections()
}
@@ -217,7 +217,7 @@
private inner class Dumpster : Dumpable {
override fun dump(pw: PrintWriter, args: Array<out String>) {
val slotPickerRepresentations = getSlotPickerRepresentations()
- val selectionsBySlotId = getSelections()
+ val selectionsBySlotId = getCurrentSelections()
pw.println("Slots & selections:")
slotPickerRepresentations.forEach { slotPickerRepresentation ->
val slotId = slotPickerRepresentation.id
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
index cc99eb7..4a262f5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
@@ -31,10 +31,16 @@
@Binds
fun lightRevealScrimRepository(impl: LightRevealScrimRepositoryImpl): LightRevealScrimRepository
- @Binds fun biometricRepository(impl: BiometricRepositoryImpl): BiometricRepository
+ @Binds
+ fun biometricSettingsRepository(
+ impl: BiometricSettingsRepositoryImpl
+ ): BiometricSettingsRepository
@Binds
fun deviceEntryFingerprintAuthRepository(
impl: DeviceEntryFingerprintAuthRepositoryImpl
): DeviceEntryFingerprintAuthRepository
+
+ @Binds
+ fun keyguardBouncerRepository(impl: KeyguardBouncerRepositoryImpl): KeyguardBouncerRepository
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractor.kt
index 6020ef8..6452e0e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractor.kt
@@ -20,7 +20,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
-import com.android.systemui.keyguard.data.repository.BiometricRepository
+import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager.LegacyAlternateBouncer
@@ -34,7 +34,7 @@
@Inject
constructor(
private val bouncerRepository: KeyguardBouncerRepository,
- private val biometricRepository: BiometricRepository,
+ private val biometricSettingsRepository: BiometricSettingsRepository,
private val deviceEntryFingerprintAuthRepository: DeviceEntryFingerprintAuthRepository,
private val systemClock: SystemClock,
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
@@ -99,9 +99,9 @@
fun canShowAlternateBouncerForFingerprint(): Boolean {
return if (isModernAlternateBouncerEnabled) {
bouncerRepository.isAlternateBouncerUIAvailable.value &&
- biometricRepository.isFingerprintEnrolled.value &&
- biometricRepository.isStrongBiometricAllowed.value &&
- biometricRepository.isFingerprintEnabledByDevicePolicy.value &&
+ biometricSettingsRepository.isFingerprintEnrolled.value &&
+ biometricSettingsRepository.isStrongBiometricAllowed.value &&
+ biometricSettingsRepository.isFingerprintEnabledByDevicePolicy.value &&
!deviceEntryFingerprintAuthRepository.isLockedOut.value
} else {
legacyAlternateBouncer != null &&
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
index ce61f2f..86f65dde 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
@@ -21,6 +21,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel.Companion.isWakeAndUnlock
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionInfo
import com.android.systemui.keyguard.shared.model.WakefulnessModel.Companion.isWakingOrStartingToWake
@@ -44,6 +45,7 @@
override fun start() {
listenForDozingToLockscreen()
+ listenForDozingToGone()
}
private fun listenForDozingToLockscreen() {
@@ -68,6 +70,28 @@
}
}
+ private fun listenForDozingToGone() {
+ scope.launch {
+ keyguardInteractor.biometricUnlockState
+ .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
+ .collect { (biometricUnlockState, lastStartedTransition) ->
+ if (
+ lastStartedTransition.to == KeyguardState.DOZING &&
+ isWakeAndUnlock(biometricUnlockState)
+ ) {
+ keyguardTransitionRepository.startTransition(
+ TransitionInfo(
+ name,
+ KeyguardState.DOZING,
+ KeyguardState.GONE,
+ getAnimator(),
+ )
+ )
+ }
+ }
+ }
+ }
+
private fun getAnimator(duration: Duration = DEFAULT_DURATION): ValueAnimator {
return ValueAnimator().apply {
setInterpolator(Interpolators.LINEAR)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
index 9ddc575..57c3b31 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
@@ -154,7 +154,7 @@
val slots = repository.get().getSlotPickerRepresentations()
val slot = slots.find { it.id == slotId } ?: return false
val selections =
- repository.get().getSelections().getOrDefault(slotId, emptyList()).toMutableList()
+ repository.get().getCurrentSelections().getOrDefault(slotId, emptyList()).toMutableList()
val alreadySelected = selections.remove(affordanceId)
if (!alreadySelected) {
while (selections.size > 0 && selections.size >= slot.maxSelectedAffordances) {
@@ -193,7 +193,7 @@
if (affordanceId.isNullOrEmpty()) {
return if (
- repository.get().getSelections().getOrDefault(slotId, emptyList()).isEmpty()
+ repository.get().getCurrentSelections().getOrDefault(slotId, emptyList()).isEmpty()
) {
false
} else {
@@ -203,7 +203,7 @@
}
val selections =
- repository.get().getSelections().getOrDefault(slotId, emptyList()).toMutableList()
+ repository.get().getCurrentSelections().getOrDefault(slotId, emptyList()).toMutableList()
return if (selections.remove(affordanceId)) {
repository
.get()
@@ -220,7 +220,7 @@
/** Returns affordance IDs indexed by slot ID, for all known slots. */
suspend fun getSelections(): Map<String, List<KeyguardQuickAffordancePickerRepresentation>> {
val slots = repository.get().getSlotPickerRepresentations()
- val selections = repository.get().getSelections()
+ val selections = repository.get().getCurrentSelections()
val affordanceById =
getAffordancePickerRepresentations().associateBy { affordance -> affordance.id }
return slots.associate { slot ->
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
index ad6dbea..53c80f6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
@@ -19,7 +19,6 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
-import com.android.systemui.keyguard.shared.model.AnimationParams
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
import com.android.systemui.keyguard.shared.model.KeyguardState.BOUNCER
@@ -31,9 +30,6 @@
import com.android.systemui.keyguard.shared.model.TransitionState.STARTED
import com.android.systemui.keyguard.shared.model.TransitionStep
import javax.inject.Inject
-import kotlin.math.max
-import kotlin.math.min
-import kotlin.time.Duration
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
@@ -104,38 +100,4 @@
/* The last completed [KeyguardState] transition */
val finishedKeyguardState: Flow<KeyguardState> =
finishedKeyguardTransitionStep.map { step -> step.to }
-
- /**
- * Transitions will occur over a [totalDuration] with [TransitionStep]s being emitted in the
- * range of [0, 1]. View animations should begin and end within a subset of this range. This
- * function maps the [startTime] and [duration] into [0, 1], when this subset is valid.
- */
- fun transitionStepAnimation(
- flow: Flow<TransitionStep>,
- params: AnimationParams,
- totalDuration: Duration,
- ): Flow<Float> {
- val start = (params.startTime / totalDuration).toFloat()
- val chunks = (totalDuration / params.duration).toFloat()
- var isRunning = false
- return flow
- .map { step ->
- val value = (step.value - start) * chunks
- if (step.transitionState == STARTED) {
- // When starting, make sure to always emit. If a transition is started from the
- // middle, it is possible this animation is being skipped but we need to inform
- // the ViewModels of the last update
- isRunning = true
- max(0f, min(1f, value))
- } else if (isRunning && value >= 1f) {
- // Always send a final value of 1. Because of rounding, [value] may never be
- // exactly 1.
- isRunning = false
- 1f
- } else {
- value
- }
- }
- .filter { value -> value >= 0f && value <= 1f }
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
index a92540d..96bf815 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
@@ -113,6 +113,8 @@
0f
}
}
+ /** Allow for interaction when just about fully visible */
+ val isInteractable: Flow<Boolean> = bouncerExpansion.map { it > 0.9 }
// TODO(b/243685699): Move isScrimmed logic to data layer.
// TODO(b/243695312): Encapsulate all of the show logic for the bouncer.
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt
new file mode 100644
index 0000000..ca1e27c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt
@@ -0,0 +1,106 @@
+/*
+ * 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.keyguard.ui
+
+import android.view.animation.Interpolator
+import com.android.systemui.animation.Interpolators.LINEAR
+import com.android.systemui.keyguard.shared.model.TransitionState.CANCELED
+import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED
+import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
+import com.android.systemui.keyguard.shared.model.TransitionState.STARTED
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import kotlin.math.max
+import kotlin.math.min
+import kotlin.time.Duration
+import kotlin.time.Duration.Companion.milliseconds
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.map
+
+/**
+ * For the given transition params, construct a flow using [createFlow] for the specified portion of
+ * the overall transition.
+ */
+class KeyguardTransitionAnimationFlow(
+ private val transitionDuration: Duration,
+ private val transitionFlow: Flow<TransitionStep>,
+) {
+ /**
+ * Transitions will occur over a [transitionDuration] with [TransitionStep]s being emitted in
+ * the range of [0, 1]. View animations should begin and end within a subset of this range. This
+ * function maps the [startTime] and [duration] into [0, 1], when this subset is valid.
+ */
+ fun createFlow(
+ duration: Duration,
+ onStep: (Float) -> Float,
+ startTime: Duration = 0.milliseconds,
+ onCancel: (() -> Float)? = null,
+ onFinish: (() -> Float)? = null,
+ interpolator: Interpolator = LINEAR,
+ ): Flow<Float> {
+ if (!duration.isPositive()) {
+ throw IllegalArgumentException("duration must be a positive number: $duration")
+ }
+ if ((startTime + duration).compareTo(transitionDuration) > 0) {
+ throw IllegalArgumentException(
+ "startTime($startTime) + duration($duration) must be" +
+ " <= transitionDuration($transitionDuration)"
+ )
+ }
+
+ val start = (startTime / transitionDuration).toFloat()
+ val chunks = (transitionDuration / duration).toFloat()
+ var isComplete = true
+
+ fun stepToValue(step: TransitionStep): Float? {
+ val value = (step.value - start) * chunks
+ return when (step.transitionState) {
+ // When starting, make sure to always emit. If a transition is started from the
+ // middle, it is possible this animation is being skipped but we need to inform
+ // the ViewModels of the last update
+ STARTED -> {
+ isComplete = false
+ max(0f, min(1f, value))
+ }
+ // Always send a final value of 1. Because of rounding, [value] may never be
+ // exactly 1.
+ RUNNING ->
+ if (isComplete) {
+ null
+ } else if (value >= 1f) {
+ isComplete = true
+ 1f
+ } else if (value >= 0f) {
+ value
+ } else {
+ null
+ }
+ else -> null
+ }?.let { onStep(interpolator.getInterpolation(it)) }
+ }
+
+ return transitionFlow
+ .map { step ->
+ when (step.transitionState) {
+ STARTED -> stepToValue(step)
+ RUNNING -> stepToValue(step)
+ CANCELED -> onCancel?.invoke()
+ FINISHED -> onFinish?.invoke()
+ }
+ }
+ .filterNotNull()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
index 5e46c5d..9f09d53 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
@@ -106,13 +106,6 @@
hostViewController.appear(
SystemBarUtils.getStatusBarHeight(view.context)
)
- }
- }
-
- launch {
- viewModel.showWithFullExpansion.collect { model ->
- hostViewController.resetSecurityContainer()
- hostViewController.showPromptReason(model.promptReason)
hostViewController.onResume()
}
}
@@ -161,6 +154,12 @@
}
launch {
+ viewModel.isInteractable.collect { isInteractable ->
+ hostViewController.setInteractable(isInteractable)
+ }
+ }
+
+ launch {
viewModel.isBouncerVisible
.filter { !it }
.collect {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
index 8808574..adde595 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
@@ -177,7 +177,8 @@
val receiver =
object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
- clockController.clock?.events?.onTimeTick()
+ clockController.clock?.smallClock?.events?.onTimeTick()
+ clockController.clock?.largeClock?.events?.onTimeTick()
}
}
broadcastDispatcher.registerReceiver(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt
index 6627865..8d6545a4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt
@@ -21,15 +21,10 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.FromDreamingTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.shared.model.AnimationParams
-import com.android.systemui.keyguard.shared.model.TransitionState.CANCELED
-import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED
+import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.filter
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.merge
/**
* Breaks down DREAMING->LOCKSCREEN transition into discrete steps for corresponding views to
@@ -41,39 +36,46 @@
constructor(
private val interactor: KeyguardTransitionInteractor,
) {
+ private val transitionAnimation =
+ KeyguardTransitionAnimationFlow(
+ transitionDuration = TO_LOCKSCREEN_DURATION,
+ transitionFlow = interactor.dreamingToLockscreenTransition,
+ )
/** Dream overlay y-translation on exit */
fun dreamOverlayTranslationY(translatePx: Int): Flow<Float> {
- return flowForAnimation(DREAM_OVERLAY_TRANSLATION_Y).map { value ->
- EMPHASIZED_ACCELERATE.getInterpolation(value) * translatePx
- }
+ return transitionAnimation.createFlow(
+ duration = 600.milliseconds,
+ onStep = { it * translatePx },
+ interpolator = EMPHASIZED_ACCELERATE,
+ )
}
/** Dream overlay views alpha - fade out */
- val dreamOverlayAlpha: Flow<Float> = flowForAnimation(DREAM_OVERLAY_ALPHA).map { 1f - it }
+ val dreamOverlayAlpha: Flow<Float> =
+ transitionAnimation.createFlow(
+ duration = 250.milliseconds,
+ onStep = { 1f - it },
+ )
/** Lockscreen views y-translation */
fun lockscreenTranslationY(translatePx: Int): Flow<Float> {
- return merge(
- flowForAnimation(LOCKSCREEN_TRANSLATION_Y).map { value ->
- -translatePx + (EMPHASIZED_DECELERATE.getInterpolation(value) * translatePx)
- },
- // On end, reset the translation to 0
- interactor.dreamingToLockscreenTransition
- .filter { it.transitionState == FINISHED || it.transitionState == CANCELED }
- .map { 0f }
+ return transitionAnimation.createFlow(
+ duration = TO_LOCKSCREEN_DURATION,
+ onStep = { value -> -translatePx + value * translatePx },
+ // Reset on cancel or finish
+ onFinish = { 0f },
+ onCancel = { 0f },
+ interpolator = EMPHASIZED_DECELERATE,
)
}
/** Lockscreen views alpha */
- val lockscreenAlpha: Flow<Float> = flowForAnimation(LOCKSCREEN_ALPHA)
-
- private fun flowForAnimation(params: AnimationParams): Flow<Float> {
- return interactor.transitionStepAnimation(
- interactor.dreamingToLockscreenTransition,
- params,
- totalDuration = TO_LOCKSCREEN_DURATION
+ val lockscreenAlpha: Flow<Float> =
+ transitionAnimation.createFlow(
+ startTime = 233.milliseconds,
+ duration = 250.milliseconds,
+ onStep = { it },
)
- }
companion object {
/* Length of time before ending the dream activity, in order to start unoccluding */
@@ -81,11 +83,5 @@
@JvmField
val LOCKSCREEN_ANIMATION_DURATION_MS =
(TO_LOCKSCREEN_DURATION - DREAM_ANIMATION_DURATION).inWholeMilliseconds
-
- val DREAM_OVERLAY_TRANSLATION_Y = AnimationParams(duration = 600.milliseconds)
- val DREAM_OVERLAY_ALPHA = AnimationParams(duration = 250.milliseconds)
- val LOCKSCREEN_TRANSLATION_Y = AnimationParams(duration = TO_LOCKSCREEN_DURATION)
- val LOCKSCREEN_ALPHA =
- AnimationParams(startTime = 233.milliseconds, duration = 250.milliseconds)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModel.kt
index 5a47960..f16827d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModel.kt
@@ -20,15 +20,10 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.FromGoneTransitionInteractor.Companion.TO_DREAMING_DURATION
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.shared.model.AnimationParams
-import com.android.systemui.keyguard.shared.model.TransitionState.CANCELED
-import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED
+import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.filter
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.merge
/** Breaks down GONE->DREAMING transition into discrete steps for corresponding views to consume. */
@SysUISingleton
@@ -38,32 +33,28 @@
private val interactor: KeyguardTransitionInteractor,
) {
+ private val transitionAnimation =
+ KeyguardTransitionAnimationFlow(
+ transitionDuration = TO_DREAMING_DURATION,
+ transitionFlow = interactor.goneToDreamingTransition,
+ )
+
/** Lockscreen views y-translation */
fun lockscreenTranslationY(translatePx: Int): Flow<Float> {
- return merge(
- flowForAnimation(LOCKSCREEN_TRANSLATION_Y).map { value ->
- (EMPHASIZED_ACCELERATE.getInterpolation(value) * translatePx)
- },
- // On end, reset the translation to 0
- interactor.goneToDreamingTransition
- .filter { it.transitionState == FINISHED || it.transitionState == CANCELED }
- .map { 0f }
+ return transitionAnimation.createFlow(
+ duration = 500.milliseconds,
+ onStep = { it * translatePx },
+ // Reset on cancel or finish
+ onFinish = { 0f },
+ onCancel = { 0f },
+ interpolator = EMPHASIZED_ACCELERATE,
)
}
/** Lockscreen views alpha */
- val lockscreenAlpha: Flow<Float> = flowForAnimation(LOCKSCREEN_ALPHA).map { 1f - it }
-
- private fun flowForAnimation(params: AnimationParams): Flow<Float> {
- return interactor.transitionStepAnimation(
- interactor.goneToDreamingTransition,
- params,
- totalDuration = TO_DREAMING_DURATION
+ val lockscreenAlpha: Flow<Float> =
+ transitionAnimation.createFlow(
+ duration = 250.milliseconds,
+ onStep = { 1f - it },
)
- }
-
- companion object {
- val LOCKSCREEN_TRANSLATION_Y = AnimationParams(duration = 500.milliseconds)
- val LOCKSCREEN_ALPHA = AnimationParams(duration = 250.milliseconds)
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt
index c6002d6..b8b3a8e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt
@@ -20,12 +20,10 @@
import com.android.systemui.keyguard.data.BouncerView
import com.android.systemui.keyguard.data.BouncerViewDelegate
import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor
-import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_VISIBLE
import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel
import com.android.systemui.keyguard.shared.model.KeyguardBouncerModel
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
/** Models UI state for the lock screen bouncer; handles user input. */
@@ -41,13 +39,12 @@
/** Observe on bouncer visibility. */
val isBouncerVisible: Flow<Boolean> = interactor.isVisible
+ /** Can the user interact with the view? */
+ val isInteractable: Flow<Boolean> = interactor.isInteractable
+
/** Observe whether bouncer is showing. */
val show: Flow<KeyguardBouncerModel> = interactor.show
- /** Observe visible expansion when bouncer is showing. */
- val showWithFullExpansion: Flow<KeyguardBouncerModel> =
- interactor.show.filter { it.expansionAmount == EXPANSION_VISIBLE }
-
/** Observe whether bouncer is hiding. */
val hide: Flow<Unit> = interactor.hide
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt
index e05adbd..bc9dc4f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt
@@ -20,15 +20,10 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor.Companion.TO_DREAMING_DURATION
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.shared.model.AnimationParams
-import com.android.systemui.keyguard.shared.model.TransitionState.CANCELED
-import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED
+import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.filter
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.merge
/**
* Breaks down LOCKSCREEN->DREAMING transition into discrete steps for corresponding views to
@@ -40,35 +35,32 @@
constructor(
private val interactor: KeyguardTransitionInteractor,
) {
+ private val transitionAnimation =
+ KeyguardTransitionAnimationFlow(
+ transitionDuration = TO_DREAMING_DURATION,
+ transitionFlow = interactor.lockscreenToDreamingTransition,
+ )
/** Lockscreen views y-translation */
fun lockscreenTranslationY(translatePx: Int): Flow<Float> {
- return merge(
- flowForAnimation(LOCKSCREEN_TRANSLATION_Y).map { value ->
- (EMPHASIZED_ACCELERATE.getInterpolation(value) * translatePx)
- },
- // On end, reset the translation to 0
- interactor.lockscreenToDreamingTransition
- .filter { it.transitionState == FINISHED || it.transitionState == CANCELED }
- .map { 0f }
+ return transitionAnimation.createFlow(
+ duration = 500.milliseconds,
+ onStep = { it * translatePx },
+ // Reset on cancel or finish
+ onFinish = { 0f },
+ onCancel = { 0f },
+ interpolator = EMPHASIZED_ACCELERATE,
)
}
/** Lockscreen views alpha */
- val lockscreenAlpha: Flow<Float> = flowForAnimation(LOCKSCREEN_ALPHA).map { 1f - it }
-
- private fun flowForAnimation(params: AnimationParams): Flow<Float> {
- return interactor.transitionStepAnimation(
- interactor.lockscreenToDreamingTransition,
- params,
- totalDuration = TO_DREAMING_DURATION
+ val lockscreenAlpha: Flow<Float> =
+ transitionAnimation.createFlow(
+ duration = 250.milliseconds,
+ onStep = { 1f - it },
)
- }
companion object {
@JvmField val DREAMING_ANIMATION_DURATION_MS = TO_DREAMING_DURATION.inWholeMilliseconds
-
- val LOCKSCREEN_TRANSLATION_Y = AnimationParams(duration = 500.milliseconds)
- val LOCKSCREEN_ALPHA = AnimationParams(duration = 250.milliseconds)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt
index 22d292e..a60665a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt
@@ -20,14 +20,10 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor.Companion.TO_OCCLUDED_DURATION
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.shared.model.AnimationParams
-import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.filter
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.merge
/**
* Breaks down LOCKSCREEN->OCCLUDED transition into discrete steps for corresponding views to
@@ -39,33 +35,28 @@
constructor(
private val interactor: KeyguardTransitionInteractor,
) {
+ private val transitionAnimation =
+ KeyguardTransitionAnimationFlow(
+ transitionDuration = TO_OCCLUDED_DURATION,
+ transitionFlow = interactor.lockscreenToOccludedTransition,
+ )
+
+ /** Lockscreen views alpha */
+ val lockscreenAlpha: Flow<Float> =
+ transitionAnimation.createFlow(
+ duration = 250.milliseconds,
+ onStep = { 1f - it },
+ )
/** Lockscreen views y-translation */
fun lockscreenTranslationY(translatePx: Int): Flow<Float> {
- return merge(
- flowForAnimation(LOCKSCREEN_TRANSLATION_Y).map { value ->
- (EMPHASIZED_ACCELERATE.getInterpolation(value) * translatePx)
- },
- // On end, reset the translation to 0
- interactor.lockscreenToOccludedTransition
- .filter { step -> step.transitionState == TransitionState.FINISHED }
- .map { 0f }
+ return transitionAnimation.createFlow(
+ duration = TO_OCCLUDED_DURATION,
+ onStep = { value -> value * translatePx },
+ // Reset on cancel or finish
+ onFinish = { 0f },
+ onCancel = { 0f },
+ interpolator = EMPHASIZED_ACCELERATE,
)
}
-
- /** Lockscreen views alpha */
- val lockscreenAlpha: Flow<Float> = flowForAnimation(LOCKSCREEN_ALPHA).map { 1f - it }
-
- private fun flowForAnimation(params: AnimationParams): Flow<Float> {
- return interactor.transitionStepAnimation(
- interactor.lockscreenToOccludedTransition,
- params,
- totalDuration = TO_OCCLUDED_DURATION
- )
- }
-
- companion object {
- val LOCKSCREEN_TRANSLATION_Y = AnimationParams(duration = TO_OCCLUDED_DURATION)
- val LOCKSCREEN_ALPHA = AnimationParams(duration = 250.milliseconds)
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt
index e804562..5770f3e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt
@@ -20,11 +20,10 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.FromOccludedTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.shared.model.AnimationParams
+import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.map
/**
* Breaks down OCCLUDED->LOCKSCREEN transition into discrete steps for corresponding views to
@@ -36,28 +35,26 @@
constructor(
private val interactor: KeyguardTransitionInteractor,
) {
+ private val transitionAnimation =
+ KeyguardTransitionAnimationFlow(
+ transitionDuration = TO_LOCKSCREEN_DURATION,
+ transitionFlow = interactor.occludedToLockscreenTransition,
+ )
+
/** Lockscreen views y-translation */
fun lockscreenTranslationY(translatePx: Int): Flow<Float> {
- return flowForAnimation(LOCKSCREEN_TRANSLATION_Y).map { value ->
- -translatePx + (EMPHASIZED_DECELERATE.getInterpolation(value) * translatePx)
- }
- }
-
- /** Lockscreen views alpha */
- val lockscreenAlpha: Flow<Float> = flowForAnimation(LOCKSCREEN_ALPHA)
-
- private fun flowForAnimation(params: AnimationParams): Flow<Float> {
- return interactor.transitionStepAnimation(
- interactor.occludedToLockscreenTransition,
- params,
- totalDuration = TO_LOCKSCREEN_DURATION
+ return transitionAnimation.createFlow(
+ duration = TO_LOCKSCREEN_DURATION,
+ onStep = { value -> -translatePx + value * translatePx },
+ interpolator = EMPHASIZED_DECELERATE,
)
}
- companion object {
- @JvmField val LOCKSCREEN_ANIMATION_DURATION_MS = TO_LOCKSCREEN_DURATION.inWholeMilliseconds
- val LOCKSCREEN_TRANSLATION_Y = AnimationParams(duration = TO_LOCKSCREEN_DURATION)
- val LOCKSCREEN_ALPHA =
- AnimationParams(startTime = 233.milliseconds, duration = 250.milliseconds)
- }
+ /** Lockscreen views alpha */
+ val lockscreenAlpha: Flow<Float> =
+ transitionAnimation.createFlow(
+ startTime = 233.milliseconds,
+ duration = 250.milliseconds,
+ onStep = { it },
+ )
}
diff --git a/packages/SystemUI/src/com/android/systemui/log/ScreenDecorationsLogger.kt b/packages/SystemUI/src/com/android/systemui/log/ScreenDecorationsLogger.kt
new file mode 100644
index 0000000..5acaa46
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/ScreenDecorationsLogger.kt
@@ -0,0 +1,135 @@
+/*
+ * 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.log
+
+import android.graphics.Point
+import android.graphics.Rect
+import android.graphics.RectF
+import androidx.core.graphics.toRectF
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.dagger.ScreenDecorationsLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel.DEBUG
+import com.android.systemui.plugins.log.LogLevel.ERROR
+import javax.inject.Inject
+
+private const val TAG = "ScreenDecorationsLog"
+
+/**
+ * Helper class for logging for [com.android.systemui.ScreenDecorations]
+ *
+ * To enable logcat echoing for an entire buffer:
+ *
+ * ```
+ * adb shell settings put global systemui/buffer/ScreenDecorationsLog <logLevel>
+ *
+ * ```
+ */
+@SysUISingleton
+class ScreenDecorationsLogger
+@Inject
+constructor(
+ @ScreenDecorationsLog private val logBuffer: LogBuffer,
+) {
+ fun cameraProtectionBoundsForScanningOverlay(bounds: Rect) {
+ logBuffer.log(
+ TAG,
+ DEBUG,
+ { str1 = bounds.toShortString() },
+ { "Face scanning overlay present camera protection bounds: $str1" }
+ )
+ }
+
+ fun hwcLayerCameraProtectionBounds(bounds: Rect) {
+ logBuffer.log(
+ TAG,
+ DEBUG,
+ { str1 = bounds.toShortString() },
+ { "Hwc layer present camera protection bounds: $str1" }
+ )
+ }
+
+ fun dcvCameraBounds(id: Int, bounds: Rect) {
+ logBuffer.log(
+ TAG,
+ DEBUG,
+ {
+ str1 = bounds.toShortString()
+ int1 = id
+ },
+ { "DisplayCutoutView id=$int1 present, camera protection bounds: $str1" }
+ )
+ }
+
+ fun cutoutViewNotInitialized() {
+ logBuffer.log(TAG, ERROR, "CutoutView not initialized showCameraProtection")
+ }
+
+ fun boundingRect(boundingRectangle: RectF, context: String) {
+ logBuffer.log(
+ TAG,
+ DEBUG,
+ {
+ str1 = context
+ str2 = boundingRectangle.toShortString()
+ },
+ { "Bounding rect $str1 : $str2" }
+ )
+ }
+
+ fun boundingRect(boundingRectangle: Rect, context: String) {
+ boundingRect(boundingRectangle.toRectF(), context)
+ }
+
+ fun onMeasureDimensions(
+ widthMeasureSpec: Int,
+ heightMeasureSpec: Int,
+ measuredWidth: Int,
+ measuredHeight: Int
+ ) {
+ logBuffer.log(
+ TAG,
+ DEBUG,
+ {
+ long1 = widthMeasureSpec.toLong()
+ long2 = heightMeasureSpec.toLong()
+ int1 = measuredWidth
+ int2 = measuredHeight
+ },
+ {
+ "Face scanning animation: widthMeasureSpec: $long1 measuredWidth: $int1, " +
+ "heightMeasureSpec: $long2 measuredHeight: $int2"
+ }
+ )
+ }
+
+ fun faceSensorLocation(faceSensorLocation: Point?) {
+ logBuffer.log(
+ TAG,
+ DEBUG,
+ {
+ int1 = faceSensorLocation?.y?.times(2) ?: 0
+ str1 = "$faceSensorLocation"
+ },
+ { "Reinflating view: Face sensor location: $str1, faceScanningHeight: $int1" }
+ )
+ }
+
+ fun onSensorLocationChanged() {
+ logBuffer.log(TAG, DEBUG, "AuthControllerCallback in ScreenDecorations triggered")
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index 6c6f7e9..4177480 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -350,6 +350,16 @@
}
/**
+ * Provides a {@link LogBuffer} for use by {@link com.android.systemui.ScreenDecorations}.
+ */
+ @Provides
+ @SysUISingleton
+ @ScreenDecorationsLog
+ public static LogBuffer provideScreenDecorationsLog(LogBufferFactory factory) {
+ return factory.create("ScreenDecorationsLog", 200);
+ }
+
+ /**
* Provides a {@link LogBuffer} for bluetooth-related logs.
*/
@Provides
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/AnimationParams.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/ScreenDecorationsLog.kt
similarity index 60%
rename from packages/SystemUI/src/com/android/systemui/keyguard/shared/model/AnimationParams.kt
rename to packages/SystemUI/src/com/android/systemui/log/dagger/ScreenDecorationsLog.kt
index 67733e9..de2a8b6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/AnimationParams.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/ScreenDecorationsLog.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * 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.
@@ -11,15 +11,15 @@
* 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
+ * limitations under the License.
*/
-package com.android.systemui.keyguard.shared.model
-import kotlin.time.Duration
-import kotlin.time.Duration.Companion.milliseconds
+package com.android.systemui.log.dagger
-/** Animation parameters */
-data class AnimationParams(
- val startTime: Duration = 0.milliseconds,
- val duration: Duration,
-)
+import javax.inject.Qualifier
+
+/** A [com.android.systemui.log.LogBuffer] for ScreenDecorations added by SysUI. */
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class ScreenDecorationsLog
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaData.kt b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaData.kt
index be18cbe..b7a2522 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaData.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaData.kt
@@ -92,6 +92,9 @@
/** Whether explicit indicator exists */
val isExplicit: Boolean = false,
+
+ /** Track progress (0 - 1) to display for players where [resumption] is true */
+ val resumeProgress: Double? = null,
) {
companion object {
/** Media is playing on the local device */
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarViewModel.kt
index bba5f35..a057c9f 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarViewModel.kt
@@ -238,6 +238,24 @@
}
/**
+ * Set the progress to a fixed percentage value that cannot be changed by the user.
+ *
+ * @param percent value between 0 and 1
+ */
+ fun updateStaticProgress(percent: Double) {
+ val position = (percent * 100).toInt()
+ _data =
+ Progress(
+ enabled = true,
+ seekAvailable = false,
+ playing = false,
+ scrubbing = false,
+ elapsedTime = position,
+ duration = 100,
+ )
+ }
+
+ /**
* Puts the seek bar into a resumption state.
*
* This should be called when the media session behind the controller has been destroyed.
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
index b11f628..aba3e98 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
@@ -67,6 +67,7 @@
import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaDataProvider
import com.android.systemui.media.controls.resume.MediaResumeListener
import com.android.systemui.media.controls.util.MediaControllerFactory
+import com.android.systemui.media.controls.util.MediaDataUtils
import com.android.systemui.media.controls.util.MediaFlags
import com.android.systemui.media.controls.util.MediaUiEventLogger
import com.android.systemui.plugins.ActivityStarter
@@ -667,6 +668,11 @@
MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT &&
mediaFlags.isExplicitIndicatorEnabled()
+ val progress =
+ if (mediaFlags.isResumeProgressEnabled()) {
+ MediaDataUtils.getDescriptionProgress(desc.extras)
+ } else null
+
val mediaAction = getResumeMediaAction(resumeAction)
val lastActive = systemClock.elapsedRealtime()
foregroundExecutor.execute {
@@ -697,6 +703,7 @@
instanceId = instanceId,
appUid = appUid,
isExplicit = isExplicit,
+ resumeProgress = progress,
)
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
index 9250a58..d26f239 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
@@ -115,8 +115,6 @@
import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.time.SystemClock;
-import dagger.Lazy;
-
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;
@@ -124,6 +122,7 @@
import javax.inject.Inject;
+import dagger.Lazy;
import kotlin.Triple;
import kotlin.Unit;
@@ -523,8 +522,13 @@
}
// Seek Bar
- final MediaController controller = getController();
- mBackgroundExecutor.execute(() -> mSeekBarViewModel.updateController(controller));
+ if (data.getResumption() && data.getResumeProgress() != null) {
+ double progress = data.getResumeProgress();
+ mSeekBarViewModel.updateStaticProgress(progress);
+ } else {
+ final MediaController controller = getController();
+ mBackgroundExecutor.execute(() -> mSeekBarViewModel.updateController(controller));
+ }
// Show the broadcast dialog button only when the le audio is enabled.
mShowBroadcastDialogButton =
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaDataUtils.java b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaDataUtils.java
index bcfceaa..85282a1 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaDataUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaDataUtils.java
@@ -19,8 +19,12 @@
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
+import android.os.Bundle;
import android.text.TextUtils;
+import androidx.core.math.MathUtils;
+import androidx.media.utils.MediaConstants;
+
/**
* Utility class with common methods for media controls
*/
@@ -50,4 +54,35 @@
: unknownName);
return applicationName;
}
+
+ /**
+ * Check the bundle for extras indicating the progress percentage
+ *
+ * @param extras
+ * @return the progress value between 0-1 inclusive if prsent, otherwise null
+ */
+ public static Double getDescriptionProgress(Bundle extras) {
+ if (!extras.containsKey(MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS)) {
+ return null;
+ }
+
+ int status = extras.getInt(MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS);
+ switch (status) {
+ case MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_NOT_PLAYED:
+ return 0.0;
+ case MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_FULLY_PLAYED:
+ return 1.0;
+ case MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED: {
+ if (extras
+ .containsKey(MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE)) {
+ double percent = extras
+ .getDouble(MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE);
+ return MathUtils.clamp(percent, 0.0, 1.0);
+ } else {
+ return 0.5;
+ }
+ }
+ }
+ return null;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
index 81efa36..a689dc3 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
@@ -55,4 +55,7 @@
/** Check whether we show the updated recommendation card. */
fun isRecommendationCardUpdateEnabled() =
featureFlags.isEnabled(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE)
+
+ /** Check whether to get progress information for resume players */
+ fun isResumeProgressEnabled() = featureFlags.isEnabled(Flags.MEDIA_RESUME_PROGRESS)
}
diff --git a/packages/SystemUI/src/com/android/systemui/motiontool/MotionToolModule.kt b/packages/SystemUI/src/com/android/systemui/motiontool/MotionToolModule.kt
index 1324d2c..c4a1ed4 100644
--- a/packages/SystemUI/src/com/android/systemui/motiontool/MotionToolModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/motiontool/MotionToolModule.kt
@@ -19,7 +19,6 @@
import android.view.WindowManagerGlobal
import com.android.app.motiontool.DdmHandleMotionTool
import com.android.app.motiontool.MotionToolManager
-import com.android.app.viewcapture.ViewCapture
import com.android.systemui.CoreStartable
import dagger.Binds
import dagger.Module
@@ -38,17 +37,12 @@
}
@Provides
- fun provideMotionToolManager(
- viewCapture: ViewCapture,
- windowManagerGlobal: WindowManagerGlobal
- ): MotionToolManager {
- return MotionToolManager.getInstance(viewCapture, windowManagerGlobal)
+ fun provideMotionToolManager(windowManagerGlobal: WindowManagerGlobal): MotionToolManager {
+ return MotionToolManager.getInstance(windowManagerGlobal)
}
@Provides
fun provideWindowManagerGlobal(): WindowManagerGlobal = WindowManagerGlobal.getInstance()
-
- @Provides fun provideViewCapture(): ViewCapture = ViewCapture.getInstance()
}
@Binds
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
index 1a3be8e..63fb499 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
@@ -301,7 +301,8 @@
R.dimen.floating_rotation_button_taskbar_left_margin,
R.dimen.floating_rotation_button_taskbar_bottom_margin,
R.dimen.floating_rotation_button_diameter,
- R.dimen.key_button_ripple_max_width);
+ R.dimen.key_button_ripple_max_width,
+ R.bool.floating_rotation_button_position_left);
mRotationButtonController = new RotationButtonController(mLightContext, mLightIconColor,
mDarkIconColor, R.drawable.ic_sysbar_rotate_button_ccw_start_0,
R.drawable.ic_sysbar_rotate_button_ccw_start_90,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
index 51de522..b155e13 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
@@ -255,17 +255,19 @@
Log.d(TAG, "setWifiIndicators: " + indicators);
}
mWifiInfo.mEnabled = indicators.enabled;
- if (indicators.qsIcon == null) {
- return;
- }
- mWifiInfo.mConnected = indicators.qsIcon.visible;
- mWifiInfo.mWifiSignalIconId = indicators.qsIcon.icon;
- mWifiInfo.mWifiSignalContentDescription = indicators.qsIcon.contentDescription;
- mWifiInfo.mEnabled = indicators.enabled;
mWifiInfo.mSsid = indicators.description;
mWifiInfo.mIsTransient = indicators.isTransient;
mWifiInfo.mStatusLabel = indicators.statusLabel;
- refreshState(mWifiInfo);
+ if (indicators.qsIcon != null) {
+ mWifiInfo.mConnected = indicators.qsIcon.visible;
+ mWifiInfo.mWifiSignalIconId = indicators.qsIcon.icon;
+ mWifiInfo.mWifiSignalContentDescription = indicators.qsIcon.contentDescription;
+ refreshState(mWifiInfo);
+ } else {
+ mWifiInfo.mConnected = false;
+ mWifiInfo.mWifiSignalIconId = 0;
+ mWifiInfo.mWifiSignalContentDescription = null;
+ }
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/MessageContainerController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/MessageContainerController.kt
index 1e531ba..ad66514 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/MessageContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/MessageContainerController.kt
@@ -3,101 +3,133 @@
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.ValueAnimator
-import android.graphics.drawable.Drawable
+import android.os.UserHandle
import android.view.View
import android.view.ViewGroup
import android.view.ViewGroup.MarginLayoutParams
import android.view.ViewTreeObserver
import android.view.animation.AccelerateDecelerateInterpolator
-import android.widget.ImageView
-import android.widget.TextView
import androidx.constraintlayout.widget.Guideline
import com.android.systemui.R
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import javax.inject.Inject
/**
* MessageContainerController controls the display of content in the screenshot message container.
*/
class MessageContainerController
+@Inject
constructor(
- parent: ViewGroup,
+ private val workProfileMessageController: WorkProfileMessageController,
+ private val screenshotDetectionController: ScreenshotDetectionController,
+ private val featureFlags: FeatureFlags,
) {
- private val guideline: Guideline = parent.requireViewById(R.id.guideline)
- private val messageContainer: ViewGroup =
- parent.requireViewById(R.id.screenshot_message_container)
+ private lateinit var container: ViewGroup
+ private lateinit var guideline: Guideline
+ private lateinit var workProfileFirstRunView: ViewGroup
+ private lateinit var detectionNoticeView: ViewGroup
+ private var animateOut: Animator? = null
- /**
- * Show a notification under the screenshot view indicating that a work profile screenshot has
- * been taken and which app can be used to view it.
- *
- * @param appName The name of the app to use to view screenshots
- * @param appIcon Optional icon for the relevant files app
- * @param onDismiss Runnable to be run when the user dismisses this message
- */
- fun showWorkProfileMessage(appName: CharSequence, appIcon: Drawable?, onDismiss: Runnable) {
- // Eventually this container will support multiple notification types, but for now just make
- // sure we don't double inflate.
- if (messageContainer.childCount == 0) {
- View.inflate(
- messageContainer.context,
- R.layout.screenshot_work_profile_first_run,
- messageContainer
- )
+ fun setView(screenshotView: ViewGroup) {
+ container = screenshotView.requireViewById(R.id.screenshot_message_container)
+ guideline = screenshotView.requireViewById(R.id.guideline)
+
+ workProfileFirstRunView = container.requireViewById(R.id.work_profile_first_run)
+ detectionNoticeView = container.requireViewById(R.id.screenshot_detection_notice)
+
+ // Restore to starting state.
+ container.visibility = View.GONE
+ guideline.setGuidelineEnd(0)
+ workProfileFirstRunView.visibility = View.GONE
+ detectionNoticeView.visibility = View.GONE
+ }
+
+ // Minimal implementation for use when Flags.SCREENSHOT_METADATA isn't turned on.
+ fun onScreenshotTaken(userHandle: UserHandle) {
+ if (featureFlags.isEnabled(Flags.SCREENSHOT_WORK_PROFILE_POLICY)) {
+ val workProfileData = workProfileMessageController.onScreenshotTaken(userHandle)
+ if (workProfileData != null) {
+ workProfileFirstRunView.visibility = View.VISIBLE
+ detectionNoticeView.visibility = View.GONE
+
+ workProfileMessageController.populateView(
+ workProfileFirstRunView,
+ workProfileData,
+ this::animateOutMessageContainer
+ )
+ animateInMessageContainer()
+ }
}
- if (appIcon != null) {
- // Replace the default icon if one is provided.
- val imageView: ImageView =
- messageContainer.requireViewById<ImageView>(R.id.screenshot_message_icon)
- imageView.setImageDrawable(appIcon)
+ }
+
+ fun onScreenshotTaken(screenshot: ScreenshotData) {
+ if (featureFlags.isEnabled(Flags.SCREENSHOT_WORK_PROFILE_POLICY)) {
+ val workProfileData =
+ workProfileMessageController.onScreenshotTaken(screenshot.userHandle)
+ var notifiedApps: List<CharSequence> = listOf()
+ if (featureFlags.isEnabled(Flags.SCREENSHOT_DETECTION)) {
+ notifiedApps = screenshotDetectionController.maybeNotifyOfScreenshot(screenshot)
+ }
+
+ // If work profile first run needs to show, bias towards that, otherwise show screenshot
+ // detection notification if needed.
+ if (workProfileData != null) {
+ workProfileFirstRunView.visibility = View.VISIBLE
+ detectionNoticeView.visibility = View.GONE
+ workProfileMessageController.populateView(
+ workProfileFirstRunView,
+ workProfileData,
+ this::animateOutMessageContainer
+ )
+ animateInMessageContainer()
+ } else if (notifiedApps.isNotEmpty()) {
+ detectionNoticeView.visibility = View.VISIBLE
+ workProfileFirstRunView.visibility = View.GONE
+ screenshotDetectionController.populateView(detectionNoticeView, notifiedApps)
+ animateInMessageContainer()
+ }
}
- val messageContent =
- messageContainer.requireViewById<TextView>(R.id.screenshot_message_content)
- messageContent.text =
- messageContainer.context.getString(
- R.string.screenshot_work_profile_notification,
- appName
- )
- messageContainer.requireViewById<View>(R.id.message_dismiss_button).setOnClickListener {
- animateOutMessageContainer()
- onDismiss.run()
- }
+ }
+
+ private fun animateInMessageContainer() {
+ if (container.visibility == View.VISIBLE) return
// Need the container to be fully measured before animating in (to know animation offset
// destination)
- messageContainer.viewTreeObserver.addOnPreDrawListener(
+ container.visibility = View.VISIBLE
+ container.viewTreeObserver.addOnPreDrawListener(
object : ViewTreeObserver.OnPreDrawListener {
override fun onPreDraw(): Boolean {
- messageContainer.viewTreeObserver.removeOnPreDrawListener(this)
- animateInMessageContainer()
+ container.viewTreeObserver.removeOnPreDrawListener(this)
+ getAnimator(true).start()
return false
}
}
)
}
- private fun animateInMessageContainer() {
- if (messageContainer.visibility == View.VISIBLE) return
-
- messageContainer.visibility = View.VISIBLE
- getAnimator(true).start()
- }
-
private fun animateOutMessageContainer() {
- getAnimator(false).apply {
- addListener(
- object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator) {
- super.onAnimationEnd(animation)
- messageContainer.visibility = View.INVISIBLE
+ if (animateOut != null) return
+
+ animateOut =
+ getAnimator(false).apply {
+ addListener(
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ super.onAnimationEnd(animation)
+ container.visibility = View.GONE
+ animateOut = null
+ }
}
- }
- )
- start()
- }
+ )
+ start()
+ }
}
private fun getAnimator(animateIn: Boolean): Animator {
- val params = messageContainer.layoutParams as MarginLayoutParams
- val offset = messageContainer.height + params.topMargin + params.bottomMargin
+ val params = container.layoutParams as MarginLayoutParams
+ val offset = container.height + params.topMargin + params.bottomMargin
val anim = if (animateIn) ValueAnimator.ofFloat(0f, 1f) else ValueAnimator.ofFloat(1f, 0f)
with(anim) {
duration = ScreenshotView.SCREENSHOT_ACTIONS_EXPANSION_DURATION_MS
@@ -105,7 +137,7 @@
addUpdateListener { valueAnimator: ValueAnimator ->
val interpolation = valueAnimator.animatedValue as Float
guideline.setGuidelineEnd((interpolation * offset).toInt())
- messageContainer.alpha = interpolation
+ container.alpha = interpolation
}
}
return anim
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index ab13962..72a8e23 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -282,7 +282,6 @@
private final TimeoutHandler mScreenshotHandler;
private final ActionIntentExecutor mActionExecutor;
private final UserManager mUserManager;
- private final WorkProfileMessageController mWorkProfileMessageController;
private final AssistContentRequester mAssistContentRequester;
private final OnBackInvokedCallback mOnBackInvokedCallback = () -> {
@@ -293,7 +292,7 @@
};
private ScreenshotView mScreenshotView;
- private MessageContainerController mMessageContainerController;
+ private final MessageContainerController mMessageContainerController;
private Bitmap mScreenBitmap;
private SaveImageInBackgroundTask mSaveInBgTask;
private boolean mScreenshotTakenInPortrait;
@@ -332,8 +331,8 @@
ScreenshotNotificationSmartActionsProvider screenshotNotificationSmartActionsProvider,
ActionIntentExecutor actionExecutor,
UserManager userManager,
- WorkProfileMessageController workProfileMessageController,
AssistContentRequester assistContentRequester,
+ MessageContainerController messageContainerController,
DisplayTracker displayTracker
) {
mScreenshotSmartActions = screenshotSmartActions;
@@ -367,7 +366,7 @@
mFlags = flags;
mActionExecutor = actionExecutor;
mUserManager = userManager;
- mWorkProfileMessageController = workProfileMessageController;
+ mMessageContainerController = messageContainerController;
mAssistContentRequester = assistContentRequester;
mAccessibilityManager = AccessibilityManager.getInstance(mContext);
@@ -468,7 +467,11 @@
}
}
- prepareAnimation(screenshot.getScreenBounds(), showFlash);
+ prepareAnimation(screenshot.getScreenBounds(), showFlash, () -> {
+ if (mFlags.isEnabled(SCREENSHOT_WORK_PROFILE_POLICY)) {
+ mMessageContainerController.onScreenshotTaken(screenshot);
+ }
+ });
if (mFlags.isEnabled(SCREENSHOT_WORK_PROFILE_POLICY)) {
mScreenshotView.badgeScreenshot(mContext.getPackageManager().getUserBadgedIcon(
@@ -632,7 +635,9 @@
// Inflate the screenshot layout
mScreenshotView = (ScreenshotView)
LayoutInflater.from(mContext).inflate(R.layout.screenshot, null);
- mMessageContainerController = new MessageContainerController(mScreenshotView);
+ if (mFlags.isEnabled(SCREENSHOT_WORK_PROFILE_POLICY)) {
+ mMessageContainerController.setView(mScreenshotView);
+ }
mScreenshotView.addOnAttachStateChangeListener(
new View.OnAttachStateChangeListener() {
@Override
@@ -782,7 +787,11 @@
enqueueScrollCaptureRequest(owner);
attachWindow();
- prepareAnimation(screenRect, showFlash);
+ prepareAnimation(screenRect, showFlash, () -> {
+ if (mFlags.isEnabled(SCREENSHOT_WORK_PROFILE_POLICY)) {
+ mMessageContainerController.onScreenshotTaken(owner);
+ }
+ });
if (mFlags.isEnabled(SCREENSHOT_WORK_PROFILE_POLICY)) {
mScreenshotView.badgeScreenshot(mContext.getPackageManager().getUserBadgedIcon(
@@ -799,7 +808,8 @@
mScreenshotHandler.cancelTimeout(); // restarted after animation
}
- private void prepareAnimation(Rect screenRect, boolean showFlash) {
+ private void prepareAnimation(Rect screenRect, boolean showFlash,
+ Runnable onAnimationComplete) {
mScreenshotView.getViewTreeObserver().addOnPreDrawListener(
new ViewTreeObserver.OnPreDrawListener() {
@Override
@@ -808,7 +818,7 @@
Log.d(TAG, "onPreDraw: startAnimation");
}
mScreenshotView.getViewTreeObserver().removeOnPreDrawListener(this);
- startAnimation(screenRect, showFlash);
+ startAnimation(screenRect, showFlash, onAnimationComplete);
return true;
}
});
@@ -1089,13 +1099,22 @@
/**
* Starts the animation after taking the screenshot
*/
- private void startAnimation(Rect screenRect, boolean showFlash) {
+ private void startAnimation(Rect screenRect, boolean showFlash, Runnable onAnimationComplete) {
if (mScreenshotAnimation != null && mScreenshotAnimation.isRunning()) {
mScreenshotAnimation.cancel();
}
mScreenshotAnimation =
mScreenshotView.createScreenshotDropInAnimation(screenRect, showFlash);
+ if (onAnimationComplete != null) {
+ mScreenshotAnimation.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ onAnimationComplete.run();
+ }
+ });
+ }
// Play the shutter sound to notify that we've taken a screenshot
playCameraSound();
@@ -1194,10 +1213,6 @@
private void doPostAnimation(ScreenshotController.SavedImageData imageData) {
mScreenshotView.setChipIntents(imageData);
- if (mFlags.isEnabled(SCREENSHOT_WORK_PROFILE_POLICY)) {
- mWorkProfileMessageController.onScreenshotTaken(imageData.owner,
- mMessageContainerController);
- }
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotData.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotData.kt
index c43e4b4..e9be88a 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotData.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotData.kt
@@ -8,6 +8,7 @@
import android.os.UserHandle
import android.view.WindowManager.ScreenshotSource
import android.view.WindowManager.ScreenshotType
+import androidx.annotation.VisibleForTesting
import com.android.internal.util.ScreenshotRequest
/** ScreenshotData represents the current state of a single screenshot being acquired. */
@@ -42,5 +43,10 @@
request.bitmap,
)
}
+
+ @VisibleForTesting
+ fun forTesting(): ScreenshotData {
+ return ScreenshotData(0, 0, null, null, null, 0, Insets.NONE, null)
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotDetectionController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotDetectionController.kt
new file mode 100644
index 0000000..70ea2b5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotDetectionController.kt
@@ -0,0 +1,56 @@
+/*
+ * 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.screenshot
+
+import android.content.pm.PackageManager
+import android.view.IWindowManager
+import android.view.ViewGroup
+import android.widget.TextView
+import com.android.systemui.R
+import javax.inject.Inject
+
+class ScreenshotDetectionController
+@Inject
+constructor(
+ private val windowManager: IWindowManager,
+ private val packageManager: PackageManager,
+) {
+ /**
+ * Notify potentially listening apps of the screenshot. Return a list of the names of the apps
+ * notified.
+ */
+ fun maybeNotifyOfScreenshot(data: ScreenshotData): List<CharSequence> {
+ // TODO: actually ask the window manager once API is available.
+ return listOf()
+ }
+
+ fun populateView(view: ViewGroup, appNames: List<CharSequence>) {
+ assert(appNames.isNotEmpty())
+
+ val textView: TextView = view.requireViewById(R.id.screenshot_detection_notice_text)
+ if (appNames.size == 1) {
+ textView.text =
+ view.resources.getString(R.string.screenshot_detected_template, appNames[0])
+ } else {
+ textView.text =
+ view.resources.getString(
+ R.string.screenshot_detected_multiple_template,
+ appNames[0]
+ )
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/WorkProfileMessageController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/WorkProfileMessageController.kt
index b4a07d4..1b728b8 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/WorkProfileMessageController.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/WorkProfileMessageController.kt
@@ -23,13 +23,16 @@
import android.os.UserHandle
import android.os.UserManager
import android.util.Log
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ImageView
+import android.widget.TextView
import com.android.systemui.R
import javax.inject.Inject
/**
- * Handles all the non-UI portions of the work profile first run:
- * - Track whether the user has already dismissed it.
- * - Load the proper icon and app name.
+ * Handles work profile first run, determining whether a first run UI should be shown and populating
+ * that UI if needed.
*/
class WorkProfileMessageController
@Inject
@@ -40,10 +43,12 @@
) {
/**
- * Determine if a message should be shown to the user, send message details to
- * MessageContainerController if appropriate.
+ * @return a populated WorkProfileFirstRunData object if a work profile first run message should
+ * be shown
*/
- fun onScreenshotTaken(userHandle: UserHandle, messageContainer: MessageContainerController) {
+ fun onScreenshotTaken(userHandle: UserHandle?): WorkProfileFirstRunData? {
+ if (userHandle == null) return null
+
if (userManager.isManagedProfile(userHandle.identifier) && !messageAlreadyDismissed()) {
var badgedIcon: Drawable? = null
var label: CharSequence? = null
@@ -62,12 +67,27 @@
}
// If label wasn't loaded, use a default
- val badgedLabel =
- packageManager.getUserBadgedLabel(label ?: defaultFileAppName(), userHandle)
+ return WorkProfileFirstRunData(label ?: defaultFileAppName(), badgedIcon)
+ }
+ return null
+ }
- messageContainer.showWorkProfileMessage(badgedLabel, badgedIcon) {
- onMessageDismissed()
- }
+ /**
+ * Use the provided WorkProfileFirstRunData to populate the work profile first run UI in the
+ * given view.
+ */
+ fun populateView(view: ViewGroup, data: WorkProfileFirstRunData, animateOut: () -> Unit) {
+ if (data.icon != null) {
+ // Replace the default icon if one is provided.
+ val imageView: ImageView = view.requireViewById<ImageView>(R.id.screenshot_message_icon)
+ imageView.setImageDrawable(data.icon)
+ }
+ val messageContent = view.requireViewById<TextView>(R.id.screenshot_message_content)
+ messageContent.text =
+ view.context.getString(R.string.screenshot_work_profile_notification, data.appName)
+ view.requireViewById<View>(R.id.message_dismiss_button).setOnClickListener {
+ animateOut()
+ onMessageDismissed()
}
}
@@ -91,6 +111,8 @@
private fun defaultFileAppName() = context.getString(R.string.screenshot_default_files_app_name)
+ data class WorkProfileFirstRunData constructor(val appName: CharSequence, val icon: Drawable?)
+
companion object {
const val TAG = "WorkProfileMessageCtrl"
const val SHARED_PREFERENCES_NAME = "com.android.systemui.screenshot"
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index b6f08f8..296c631 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -3151,17 +3151,11 @@
}
// The padding on this area is large enough that we can use a cheaper clipping strategy
mKeyguardStatusViewController.setClipBounds(clipStatusView ? mLastQsClipBounds : null);
- if (!qsVisible && mSplitShadeEnabled) {
- // On the lockscreen when qs isn't visible, we don't want the bounds of the shade to
- // be visible, otherwise you can see the bounds once swiping up to see bouncer
- mScrimController.setNotificationsBounds(0, 0, 0, 0);
- } else {
- // Increase the height of the notifications scrim when not in split shade
- // (e.g. portrait tablet) so the rounded corners are not visible at the bottom,
- // in this case they are rendered off-screen
- final int notificationsScrimBottom = mSplitShadeEnabled ? bottom : bottom + radius;
- mScrimController.setNotificationsBounds(left, top, right, notificationsScrimBottom);
- }
+ // Increase the height of the notifications scrim when not in split shade
+ // (e.g. portrait tablet) so the rounded corners are not visible at the bottom,
+ // in this case they are rendered off-screen
+ final int notificationsScrimBottom = mSplitShadeEnabled ? bottom : bottom + radius;
+ mScrimController.setNotificationsBounds(left, top, right, notificationsScrimBottom);
if (mSplitShadeEnabled) {
mKeyguardStatusBarViewController.setNoTopClipping();
@@ -3222,6 +3216,12 @@
private int calculateQsBottomPosition(float qsExpansionFraction) {
if (mTransitioningToFullShadeProgress > 0.0f) {
return mTransitionToFullShadeQSPosition;
+ } else if (mSplitShadeEnabled) {
+ // in split shade - outside lockscreen transition handled above - we simply jump between
+ // two qs expansion values - either shade is closed and qs expansion is 0 or shade is
+ // open and qs expansion is 1
+ int qsBottomTarget = mQs.getDesiredHeight() + mLargeScreenShadeHeaderHeight;
+ return qsExpansionFraction > 0 ? qsBottomTarget : 0;
} else {
int qsBottomYFrom = (int) getHeaderTranslation() + mQs.getQsMinExpansionHeight();
int expandedTopMargin = mUseLargeScreenShadeHeader ? mLargeScreenShadeHeaderHeight : 0;
@@ -4958,8 +4958,12 @@
beginJankMonitoring();
}
mInitialOffsetOnTouch = expandedHeight;
- mInitialExpandY = newY;
- mInitialExpandX = newX;
+ if (!mTracking || isFullyCollapsed()) {
+ mInitialExpandY = newY;
+ mInitialExpandX = newX;
+ } else {
+ mShadeLog.d("not setting mInitialExpandY in startExpandMotion");
+ }
mInitialTouchFromKeyguard = mKeyguardStateController.isShowing();
if (startTracking) {
mTouchSlopExceeded = true;
@@ -6143,8 +6147,12 @@
+ " false");
return true;
}
- mInitialExpandY = y;
- mInitialExpandX = x;
+ if (!mTracking || isFullyCollapsed()) {
+ mInitialExpandY = y;
+ mInitialExpandX = x;
+ } else {
+ mShadeLog.d("not setting mInitialExpandY in onInterceptTouch");
+ }
mTouchStartedInEmptyArea = !isInContentBounds(x, y);
mTouchSlopExceeded = mTouchSlopExceededBeforeDown;
mMotionAborted = false;
@@ -6333,7 +6341,8 @@
final float x = event.getX(pointerIndex);
final float y = event.getY(pointerIndex);
- if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
+ if (event.getActionMasked() == MotionEvent.ACTION_DOWN
+ || event.getActionMasked() == MotionEvent.ACTION_MOVE) {
mGestureWaitForTouchSlop = shouldGestureWaitForTouchSlop();
mIgnoreXTouchSlop = true;
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index 7ed6e3e..60fa865 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -160,12 +160,10 @@
// This view is not part of the newly inflated expanded status bar.
mBrightnessMirror = mView.findViewById(R.id.brightness_mirror_container);
- if (featureFlags.isEnabled(Flags.MODERN_BOUNCER)) {
- KeyguardBouncerViewBinder.bind(
- mView.findViewById(R.id.keyguard_bouncer_container),
- keyguardBouncerViewModel,
- keyguardBouncerComponentFactory);
- }
+ KeyguardBouncerViewBinder.bind(
+ mView.findViewById(R.id.keyguard_bouncer_container),
+ keyguardBouncerViewModel,
+ keyguardBouncerComponentFactory);
if (featureFlags.isEnabled(Flags.UNOCCLUSION_TRANSITION)) {
collectFlow(mView, keyguardTransitionInteractor.getLockscreenToDreamingTransition(),
diff --git a/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceModule.kt b/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceModule.kt
index 393279b..641131e 100644
--- a/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceModule.kt
@@ -44,6 +44,11 @@
const val DREAM_SMARTSPACE_PRECONDITION = "dream_smartspace_precondition"
/**
+ * The BcSmartspaceDataPlugin for the standalone date (+alarm+dnd).
+ */
+ const val DATE_SMARTSPACE_DATA_PLUGIN = "date_smartspace_data_plugin"
+
+ /**
* The BcSmartspaceDataPlugin for the standalone weather.
*/
const val WEATHER_SMARTSPACE_DATA_PLUGIN = "weather_smartspace_data_plugin"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
index f4ca9cc..6a9761d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
@@ -52,6 +52,7 @@
import com.android.systemui.settings.UserTracker
import com.android.systemui.shared.regionsampling.RegionSampler
import com.android.systemui.shared.regionsampling.UpdateColorCallback
+import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.DATE_SMARTSPACE_DATA_PLUGIN
import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.WEATHER_SMARTSPACE_DATA_PLUGIN
import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.statusbar.policy.ConfigurationController
@@ -84,6 +85,8 @@
@Main private val uiExecutor: Executor,
@Background private val bgExecutor: Executor,
@Main private val handler: Handler,
+ @Named(DATE_SMARTSPACE_DATA_PLUGIN)
+ optionalDatePlugin: Optional<BcSmartspaceDataPlugin>,
@Named(WEATHER_SMARTSPACE_DATA_PLUGIN)
optionalWeatherPlugin: Optional<BcSmartspaceDataPlugin>,
optionalPlugin: Optional<BcSmartspaceDataPlugin>,
@@ -94,6 +97,7 @@
}
private var session: SmartspaceSession? = null
+ private val datePlugin: BcSmartspaceDataPlugin? = optionalDatePlugin.orElse(null)
private val weatherPlugin: BcSmartspaceDataPlugin? = optionalWeatherPlugin.orElse(null)
private val plugin: BcSmartspaceDataPlugin? = optionalPlugin.orElse(null)
private val configPlugin: BcSmartspaceConfigPlugin? = optionalConfigPlugin.orElse(null)
@@ -222,7 +226,7 @@
execution.assertIsMainThread()
return featureFlags.isEnabled(Flags.SMARTSPACE_DATE_WEATHER_DECOUPLED) &&
- weatherPlugin != null
+ datePlugin != null && weatherPlugin != null
}
private fun updateBypassEnabled() {
@@ -231,6 +235,25 @@
}
/**
+ * Constructs the date view and connects it to the smartspace service.
+ */
+ fun buildAndConnectDateView(parent: ViewGroup): View? {
+ execution.assertIsMainThread()
+
+ if (!isEnabled()) {
+ throw RuntimeException("Cannot build view when not enabled")
+ }
+ if (!isDateWeatherDecoupled()) {
+ throw RuntimeException("Cannot build date view when not decoupled")
+ }
+
+ val view = buildView(parent, datePlugin)
+ connectSession()
+
+ return view
+ }
+
+ /**
* Constructs the weather view and connects it to the smartspace service.
*/
fun buildAndConnectWeatherView(parent: ViewGroup): View? {
@@ -308,7 +331,7 @@
}
private fun connectSession() {
- if (weatherPlugin == null && plugin == null) return
+ if (datePlugin == null && weatherPlugin == null && plugin == null) return
if (session != null || smartspaceViews.isEmpty()) {
return
}
@@ -346,6 +369,7 @@
statusBarStateController.addCallback(statusBarStateListener)
bypassController.registerOnBypassStateChangedListener(bypassStateChangedListener)
+ datePlugin?.registerSmartspaceEventNotifier { e -> session?.notifySmartspaceEvent(e) }
weatherPlugin?.registerSmartspaceEventNotifier { e -> session?.notifySmartspaceEvent(e) }
plugin?.registerSmartspaceEventNotifier { e -> session?.notifySmartspaceEvent(e) }
@@ -383,6 +407,8 @@
bypassController.unregisterOnBypassStateChangedListener(bypassStateChangedListener)
session = null
+ datePlugin?.registerSmartspaceEventNotifier(null)
+
weatherPlugin?.registerSmartspaceEventNotifier(null)
weatherPlugin?.onTargetsAvailable(emptyList())
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 9275e2b..a6b71dc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -591,6 +591,7 @@
}
mShowingPublicInitialized = false;
updateNotificationColor();
+ updateLongClickable();
if (mMenuRow != null) {
mMenuRow.onNotificationUpdated(mEntry.getSbn());
mMenuRow.setAppName(mAppName);
@@ -1196,8 +1197,26 @@
return getShowingLayout().getVisibleWrapper();
}
+ private boolean isNotificationRowLongClickable() {
+ if (mLongPressListener == null) {
+ return false;
+ }
+
+ if (!areGutsExposed()) { // guts is not opened
+ return true;
+ }
+
+ // if it is leave behind, it shouldn't be long clickable.
+ return !isGutsLeaveBehind();
+ }
+
+ private void updateLongClickable() {
+ setLongClickable(isNotificationRowLongClickable());
+ }
+
public void setLongPressListener(LongPressListener longPressListener) {
mLongPressListener = longPressListener;
+ updateLongClickable();
}
public void setDragController(ExpandableNotificationRowDragController dragController) {
@@ -2044,11 +2063,13 @@
void onGutsOpened() {
resetTranslation();
updateContentAccessibilityImportanceForGuts(false /* isEnabled */);
+ updateLongClickable();
}
void onGutsClosed() {
updateContentAccessibilityImportanceForGuts(true /* isEnabled */);
mIsSnoozed = false;
+ updateLongClickable();
}
/**
@@ -2947,6 +2968,10 @@
return (mGuts != null && mGuts.isExposed());
}
+ private boolean isGutsLeaveBehind() {
+ return (mGuts != null && mGuts.isLeavebehind());
+ }
+
@Override
public boolean isContentExpandable() {
if (mIsSummaryWithChildren && !shouldShowPublic()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
index 37ff11d..efcbb3c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
@@ -586,7 +586,9 @@
}
final ExpandableNotificationRow row = (ExpandableNotificationRow) view;
- view.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
+ if (view.isLongClickable()) {
+ view.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
+ }
if (row.areGutsExposed()) {
closeAndSaveGuts(false /* removeLeavebehind */, false /* force */,
true /* removeControls */, -1 /* x */, -1 /* y */,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
index 344d233..c1c6c88 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
@@ -23,6 +23,7 @@
import android.view.View;
import android.view.ViewStub;
+import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.LockIconView;
import com.android.systemui.R;
import com.android.systemui.battery.BatteryMeterView;
@@ -67,6 +68,7 @@
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.statusbar.window.StatusBarWindowStateController;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.util.CarrierConfigTracker;
import com.android.systemui.util.settings.SecureSettings;
@@ -299,7 +301,9 @@
OperatorNameViewController.Factory operatorNameViewControllerFactory,
SecureSettings secureSettings,
@Main Executor mainExecutor,
- DumpManager dumpManager
+ DumpManager dumpManager,
+ StatusBarWindowStateController statusBarWindowStateController,
+ KeyguardUpdateMonitor keyguardUpdateMonitor
) {
return new CollapsedStatusBarFragment(statusBarFragmentComponentFactory,
ongoingCallController,
@@ -320,7 +324,9 @@
operatorNameViewControllerFactory,
secureSettings,
mainExecutor,
- dumpManager);
+ dumpManager,
+ statusBarWindowStateController,
+ keyguardUpdateMonitor);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
index 9354c5e..00fd4ef 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
@@ -44,6 +44,7 @@
import androidx.annotation.VisibleForTesting;
+import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.Dumpable;
import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
@@ -72,6 +73,8 @@
import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController;
import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallListener;
import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.statusbar.window.StatusBarWindowStateController;
+import com.android.systemui.statusbar.window.StatusBarWindowStateListener;
import com.android.systemui.util.CarrierConfigTracker;
import com.android.systemui.util.CarrierConfigTracker.CarrierConfigChangedListener;
import com.android.systemui.util.CarrierConfigTracker.DefaultDataSubscriptionChangedListener;
@@ -129,6 +132,8 @@
private final SecureSettings mSecureSettings;
private final Executor mMainExecutor;
private final DumpManager mDumpManager;
+ private final StatusBarWindowStateController mStatusBarWindowStateController;
+ private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private List<String> mBlockedIcons = new ArrayList<>();
private Map<Startable, Startable.State> mStartableStates = new ArrayMap<>();
@@ -164,6 +169,22 @@
}
};
+ /**
+ * Whether we've launched the secure camera over the lockscreen, but haven't yet received a
+ * status bar window state change afterward.
+ *
+ * We wait for this state change (which will tell us whether to show/hide the status bar icons)
+ * so that there is no flickering/jump cutting during the camera launch.
+ */
+ private boolean mWaitingForWindowStateChangeAfterCameraLaunch = false;
+
+ /**
+ * Listener that updates {@link #mWaitingForWindowStateChangeAfterCameraLaunch} when it receives
+ * a new status bar window state.
+ */
+ private final StatusBarWindowStateListener mStatusBarWindowStateListener = state ->
+ mWaitingForWindowStateChangeAfterCameraLaunch = false;
+
@SuppressLint("ValidFragment")
public CollapsedStatusBarFragment(
StatusBarFragmentComponent.Factory statusBarFragmentComponentFactory,
@@ -185,7 +206,9 @@
OperatorNameViewController.Factory operatorNameViewControllerFactory,
SecureSettings secureSettings,
@Main Executor mainExecutor,
- DumpManager dumpManager
+ DumpManager dumpManager,
+ StatusBarWindowStateController statusBarWindowStateController,
+ KeyguardUpdateMonitor keyguardUpdateMonitor
) {
mStatusBarFragmentComponentFactory = statusBarFragmentComponentFactory;
mOngoingCallController = ongoingCallController;
@@ -207,6 +230,20 @@
mSecureSettings = secureSettings;
mMainExecutor = mainExecutor;
mDumpManager = dumpManager;
+ mStatusBarWindowStateController = statusBarWindowStateController;
+ mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mStatusBarWindowStateController.addListener(mStatusBarWindowStateListener);
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ mStatusBarWindowStateController.removeListener(mStatusBarWindowStateListener);
}
@Override
@@ -254,6 +291,11 @@
mCarrierConfigTracker.addDefaultDataSubscriptionChangedListener(mDefaultDataListener);
}
+ @Override
+ public void onCameraLaunchGestureDetected(int source) {
+ mWaitingForWindowStateChangeAfterCameraLaunch = true;
+ }
+
@VisibleForTesting
void updateBlockedIcons() {
mBlockedIcons.clear();
@@ -466,6 +508,27 @@
&& mNotificationPanelViewController.hideStatusBarIconsWhenExpanded()) {
return true;
}
+
+ // When launching the camera over the lockscreen, the icons become visible momentarily
+ // before animating out, since we're not yet aware that the launching camera activity is
+ // fullscreen. Even once the activity finishes launching, it takes a short time before WM
+ // decides that the top app wants to hide the icons and tells us to hide them. To ensure
+ // that this high-visibility animation is smooth, keep the icons hidden during a camera
+ // launch until we receive a window state change which indicates that the activity is done
+ // launching and WM has decided to show/hide the icons. For extra safety (to ensure the
+ // icons don't remain hidden somehow) we double check that the camera is still showing, the
+ // status bar window isn't hidden, and we're still occluded as well, though these checks
+ // are typically unnecessary.
+ final boolean hideIconsForSecureCamera =
+ (mWaitingForWindowStateChangeAfterCameraLaunch ||
+ !mStatusBarWindowStateController.windowIsShowing()) &&
+ mKeyguardUpdateMonitor.isSecureCameraLaunchedOverKeyguard() &&
+ mKeyguardStateController.isOccluded();
+
+ if (hideIconsForSecureCamera) {
+ return true;
+ }
+
return mStatusBarHideIconsForBouncerManager.getShouldHideStatusBarIconsForBouncer();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
index 9ae38e9..0e4a432 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
@@ -28,6 +28,7 @@
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepository
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import com.android.systemui.util.CarrierConfigTracker
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -37,10 +38,12 @@
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.transformLatest
@@ -100,6 +103,7 @@
constructor(
private val mobileConnectionsRepo: MobileConnectionsRepository,
private val carrierConfigTracker: CarrierConfigTracker,
+ private val logger: ConnectivityPipelineLogger,
userSetupRepo: UserSetupRepository,
@Application private val scope: CoroutineScope,
) : MobileIconsInteractor {
@@ -168,6 +172,8 @@
}
}
}
+ .distinctUntilChanged()
+ .onEach { logger.logFilteredSubscriptionsChanged(it) }
override val defaultDataSubId = mobileConnectionsRepo.defaultDataSubId
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt
index 829a5ca..ef75713 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt
@@ -23,6 +23,8 @@
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import java.io.PrintWriter
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -30,7 +32,9 @@
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
@@ -51,13 +55,17 @@
interactor: MobileIconsInteractor,
private val iconController: StatusBarIconController,
private val iconsViewModelFactory: MobileIconsViewModel.Factory,
+ private val logger: ConnectivityPipelineLogger,
@Application private val scope: CoroutineScope,
private val statusBarPipelineFlags: StatusBarPipelineFlags,
) : CoreStartable {
private val mobileSubIds: Flow<List<Int>> =
- interactor.filteredSubscriptions.mapLatest { subscriptions ->
- subscriptions.map { subscriptionModel -> subscriptionModel.subscriptionId }
- }
+ interactor.filteredSubscriptions
+ .mapLatest { subscriptions ->
+ subscriptions.map { subscriptionModel -> subscriptionModel.subscriptionId }
+ }
+ .distinctUntilChanged()
+ .onEach { logger.logUiAdapterSubIdsUpdated(it) }
/**
* We expose the list of tracked subscriptions as a flow of a list of ints, where each int is
@@ -72,6 +80,9 @@
/** In order to keep the logs tame, we will reuse the same top-level mobile icons view model */
val mobileIconsViewModel = iconsViewModelFactory.create(mobileSubIdsState)
+ private var isCollecting: Boolean = false
+ private var lastValue: List<Int>? = null
+
override fun start() {
// Only notify the icon controller if we want to *render* the new icons.
// Note that this flow may still run if
@@ -79,8 +90,18 @@
// get the logging data without rendering.
if (statusBarPipelineFlags.useNewMobileIcons()) {
scope.launch {
- mobileSubIds.collectLatest { iconController.setNewMobileIconSubIds(it) }
+ isCollecting = true
+ mobileSubIds.collectLatest {
+ logger.logUiAdapterSubIdsSentToIconController(it)
+ lastValue = it
+ iconController.setNewMobileIconSubIds(it)
+ }
}
}
}
+
+ override fun dump(pw: PrintWriter, args: Array<out String>) {
+ pw.println("isCollecting=$isCollecting")
+ pw.println("Last values sent to icon controller: $lastValue")
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt
index 491f3a5..7c7ffaf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt
@@ -25,6 +25,7 @@
import com.android.systemui.log.dagger.StatusBarConnectivityLog
import com.android.systemui.plugins.log.LogBuffer
import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.toString
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
@@ -201,6 +202,35 @@
)
}
+ // TODO(b/238425913): We should split this class into mobile-specific and wifi-specific loggers.
+
+ fun logFilteredSubscriptionsChanged(subs: List<SubscriptionModel>) {
+ buffer.log(
+ SB_LOGGING_TAG,
+ LogLevel.INFO,
+ { str1 = subs.toString() },
+ { "Filtered subscriptions updated: $str1" },
+ )
+ }
+
+ fun logUiAdapterSubIdsUpdated(subs: List<Int>) {
+ buffer.log(
+ SB_LOGGING_TAG,
+ LogLevel.INFO,
+ { str1 = subs.toString() },
+ { "Sub IDs in MobileUiAdapter updated internally: $str1" },
+ )
+ }
+
+ fun logUiAdapterSubIdsSentToIconController(subs: List<Int>) {
+ buffer.log(
+ SB_LOGGING_TAG,
+ LogLevel.INFO,
+ { str1 = subs.toString() },
+ { "Sub IDs in MobileUiAdapter being sent to icon controller: $str1" },
+ )
+ }
+
companion object {
const val SB_LOGGING_TAG = "SbConnectivity"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarView.kt
index cc0ec54..b1e2812 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarView.kt
@@ -77,6 +77,17 @@
return binding.getShouldIconBeVisible()
}
+ /** See [StatusBarIconView.getDrawingRect]. */
+ override fun getDrawingRect(outRect: Rect) {
+ super.getDrawingRect(outRect)
+ val translationX = translationX.toInt()
+ val translationY = translationY.toInt()
+ outRect.left += translationX
+ outRect.right += translationX
+ outRect.top += translationY
+ outRect.bottom += translationY
+ }
+
/**
* Initializes this view.
*
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowStateController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowStateController.kt
index 60f6df6..8f424b2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowStateController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowStateController.kt
@@ -61,6 +61,10 @@
listeners.add(listener)
}
+ fun removeListener(listener: StatusBarWindowStateListener) {
+ listeners.remove(listener)
+ }
+
/** Returns true if the window is currently showing. */
fun windowIsShowing() = windowState == WINDOW_STATE_SHOWING
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt
index ec6965a..899b0c2 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt
@@ -80,6 +80,26 @@
)
}
+ /** Logs that there was a failure to animate the view in. */
+ fun logAnimateInFailure() {
+ buffer.log(
+ tag,
+ LogLevel.WARNING,
+ {},
+ { "View's appearance animation failed. Forcing view display manually." },
+ )
+ }
+
+ /** Logs that there was a failure to animate the view out. */
+ fun logAnimateOutFailure() {
+ buffer.log(
+ tag,
+ LogLevel.WARNING,
+ {},
+ { "View's disappearance animation failed." },
+ )
+ }
+
fun logViewHidden(info: T) {
buffer.log(
tag,
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarAnimator.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarAnimator.kt
new file mode 100644
index 0000000..01a81de
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarAnimator.kt
@@ -0,0 +1,83 @@
+/*
+ * 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.temporarydisplay.chipbar
+
+import android.view.View
+import android.view.ViewGroup
+import com.android.systemui.animation.Interpolators
+import com.android.systemui.animation.ViewHierarchyAnimator
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.util.children
+import javax.inject.Inject
+
+/**
+ * A class controlling chipbar animations. Typically delegates to [ViewHierarchyAnimator].
+ *
+ * Used so that animations can be mocked out in tests.
+ */
+@SysUISingleton
+open class ChipbarAnimator @Inject constructor() {
+ /**
+ * Animates [innerView] and its children into view.
+ *
+ * @return true if the animation was successfully started and false if the animation can't be
+ * run for any reason.
+ *
+ * See [ViewHierarchyAnimator.animateAddition].
+ */
+ open fun animateViewIn(innerView: ViewGroup, onAnimationEnd: Runnable): Boolean {
+ return ViewHierarchyAnimator.animateAddition(
+ innerView,
+ ViewHierarchyAnimator.Hotspot.TOP,
+ Interpolators.EMPHASIZED_DECELERATE,
+ duration = ANIMATION_IN_DURATION,
+ includeMargins = true,
+ includeFadeIn = true,
+ onAnimationEnd = onAnimationEnd,
+ )
+ }
+
+ /**
+ * Animates [innerView] and its children out of view.
+ *
+ * @return true if the animation was successfully started and false if the animation can't be
+ * run for any reason.
+ *
+ * See [ViewHierarchyAnimator.animateRemoval].
+ */
+ open fun animateViewOut(innerView: ViewGroup, onAnimationEnd: Runnable): Boolean {
+ return ViewHierarchyAnimator.animateRemoval(
+ innerView,
+ ViewHierarchyAnimator.Hotspot.TOP,
+ Interpolators.EMPHASIZED_ACCELERATE,
+ ANIMATION_OUT_DURATION,
+ includeMargins = true,
+ onAnimationEnd,
+ )
+ }
+
+ /** Force shows this view and all child views. Should be used in case [animateViewIn] fails. */
+ fun forceDisplayView(innerView: View) {
+ innerView.alpha = 1f
+ if (innerView is ViewGroup) {
+ innerView.children.forEach { forceDisplayView(it) }
+ }
+ }
+}
+
+private const val ANIMATION_IN_DURATION = 500L
+private const val ANIMATION_OUT_DURATION = 250L
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
index 46f13cc..696134c 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
@@ -32,8 +32,6 @@
import com.android.internal.widget.CachingIconView
import com.android.systemui.Gefingerpoken
import com.android.systemui.R
-import com.android.systemui.animation.Interpolators
-import com.android.systemui.animation.ViewHierarchyAnimator
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription
import com.android.systemui.common.shared.model.Text.Companion.loadText
@@ -78,6 +76,7 @@
configurationController: ConfigurationController,
dumpManager: DumpManager,
powerManager: PowerManager,
+ private val chipbarAnimator: ChipbarAnimator,
private val falsingManager: FalsingManager,
private val falsingCollector: FalsingCollector,
private val swipeChipbarAwayGestureHandler: SwipeChipbarAwayGestureHandler?,
@@ -206,23 +205,17 @@
}
override fun animateViewIn(view: ViewGroup) {
+ // We can only request focus once the animation finishes.
val onAnimationEnd = Runnable {
maybeGetAccessibilityFocus(view.getTag(INFO_TAG) as ChipbarInfo?, view)
}
- val added =
- ViewHierarchyAnimator.animateAddition(
- view.getInnerView(),
- ViewHierarchyAnimator.Hotspot.TOP,
- Interpolators.EMPHASIZED_DECELERATE,
- duration = ANIMATION_IN_DURATION,
- includeMargins = true,
- includeFadeIn = true,
- // We can only request focus once the animation finishes.
- onAnimationEnd = onAnimationEnd,
- )
- // If the view doesn't get animated, the [onAnimationEnd] runnable won't get run. So, just
- // run it immediately.
- if (!added) {
+ val animatedIn = chipbarAnimator.animateViewIn(view.getInnerView(), onAnimationEnd)
+
+ // If the view doesn't get animated, the [onAnimationEnd] runnable won't get run and the
+ // views would remain un-displayed. So, just force-set/run those items immediately.
+ if (!animatedIn) {
+ logger.logAnimateInFailure()
+ chipbarAnimator.forceDisplayView(view.getInnerView())
onAnimationEnd.run()
}
}
@@ -230,18 +223,11 @@
override fun animateViewOut(view: ViewGroup, removalReason: String?, onAnimationEnd: Runnable) {
val innerView = view.getInnerView()
innerView.accessibilityLiveRegion = ACCESSIBILITY_LIVE_REGION_NONE
- val removed =
- ViewHierarchyAnimator.animateRemoval(
- innerView,
- ViewHierarchyAnimator.Hotspot.TOP,
- Interpolators.EMPHASIZED_ACCELERATE,
- ANIMATION_OUT_DURATION,
- includeMargins = true,
- onAnimationEnd,
- )
+ val removed = chipbarAnimator.animateViewOut(innerView, onAnimationEnd)
// If the view doesn't get animated, the [onAnimationEnd] runnable won't get run. So, just
// run it immediately.
if (!removed) {
+ logger.logAnimateOutFailure()
onAnimationEnd.run()
}
@@ -299,8 +285,6 @@
}
}
-private const val ANIMATION_IN_DURATION = 500L
-private const val ANIMATION_OUT_DURATION = 250L
@IdRes private val INFO_TAG = R.id.tag_chipbar_info
private const val SWIPE_UP_GESTURE_REASON = "SWIPE_UP_GESTURE_DETECTED"
private const val TAG = "ChipbarCoordinator"
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index f29ca4d..3e3a891 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -357,13 +357,22 @@
};
@Inject
- public ThemeOverlayController(Context context, BroadcastDispatcher broadcastDispatcher,
- @Background Handler bgHandler, @Main Executor mainExecutor,
- @Background Executor bgExecutor, ThemeOverlayApplier themeOverlayApplier,
- SecureSettings secureSettings, WallpaperManager wallpaperManager,
- UserManager userManager, DeviceProvisionedController deviceProvisionedController,
- UserTracker userTracker, DumpManager dumpManager, FeatureFlags featureFlags,
- @Main Resources resources, WakefulnessLifecycle wakefulnessLifecycle) {
+ public ThemeOverlayController(
+ Context context,
+ BroadcastDispatcher broadcastDispatcher,
+ @Background Handler bgHandler,
+ @Main Executor mainExecutor,
+ @Background Executor bgExecutor,
+ ThemeOverlayApplier themeOverlayApplier,
+ SecureSettings secureSettings,
+ WallpaperManager wallpaperManager,
+ UserManager userManager,
+ DeviceProvisionedController deviceProvisionedController,
+ UserTracker userTracker,
+ DumpManager dumpManager,
+ FeatureFlags featureFlags,
+ @Main Resources resources,
+ WakefulnessLifecycle wakefulnessLifecycle) {
mContext = context;
mIsMonochromaticEnabled = featureFlags.isEnabled(Flags.MONOCHROMATIC_THEMES);
mIsMonetEnabled = featureFlags.isEnabled(Flags.MONET);
diff --git a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
index 8cb4deb..e5ab473 100644
--- a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
@@ -17,8 +17,11 @@
package com.android.systemui.user.data.repository
+import android.app.IActivityManager
+import android.app.UserSwitchObserver
import android.content.Context
import android.content.pm.UserInfo
+import android.os.IRemoteCallback
import android.os.UserHandle
import android.os.UserManager
import android.provider.Settings
@@ -30,6 +33,8 @@
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR
import com.android.systemui.settings.UserTracker
import com.android.systemui.user.data.model.UserSwitcherSettingsModel
import com.android.systemui.util.settings.GlobalSettings
@@ -68,6 +73,9 @@
/** [UserInfo] of the currently-selected user. */
val selectedUserInfo: Flow<UserInfo>
+ /** Whether user switching is currently in progress. */
+ val userSwitchingInProgress: Flow<Boolean>
+
/** User ID of the last non-guest selected user. */
val lastSelectedNonGuestUserId: Int
@@ -108,6 +116,8 @@
@Background private val backgroundDispatcher: CoroutineDispatcher,
private val globalSettings: GlobalSettings,
private val tracker: UserTracker,
+ private val activityManager: IActivityManager,
+ featureFlags: FeatureFlags,
) : UserRepository {
private val _userSwitcherSettings = MutableStateFlow(runBlocking { getSettings() })
@@ -129,6 +139,10 @@
private var _isGuestUserResetting: Boolean = false
override var isGuestUserResetting: Boolean = _isGuestUserResetting
+ private val _isUserSwitchingInProgress = MutableStateFlow(false)
+ override val userSwitchingInProgress: Flow<Boolean>
+ get() = _isUserSwitchingInProgress
+
override val isGuestUserCreationScheduled = AtomicBoolean()
override val isStatusBarUserChipEnabled: Boolean =
@@ -141,6 +155,9 @@
init {
observeSelectedUser()
observeUserSettings()
+ if (featureFlags.isEnabled(FACE_AUTH_REFACTOR)) {
+ observeUserSwitching()
+ }
}
override fun refreshUsers() {
@@ -166,6 +183,28 @@
return _userSwitcherSettings.value.isSimpleUserSwitcher
}
+ private fun observeUserSwitching() {
+ conflatedCallbackFlow {
+ val callback =
+ object : UserSwitchObserver() {
+ override fun onUserSwitching(newUserId: Int, reply: IRemoteCallback) {
+ trySendWithFailureLogging(true, TAG, "userSwitching started")
+ }
+
+ override fun onUserSwitchComplete(newUserId: Int) {
+ trySendWithFailureLogging(false, TAG, "userSwitching completed")
+ }
+ }
+ activityManager.registerUserSwitchObserver(callback, TAG)
+ trySendWithFailureLogging(false, TAG, "initial value defaulting to false")
+ awaitClose { activityManager.unregisterUserSwitchObserver(callback) }
+ }
+ .onEach { _isUserSwitchingInProgress.value = it }
+ // TODO (b/262838215), Make this stateIn and initialize directly in field declaration
+ // once the flag is launched
+ .launchIn(applicationScope)
+ }
+
private fun observeSelectedUser() {
conflatedCallbackFlow {
fun send() {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
index b9c23d4..43a2017 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
@@ -32,10 +32,12 @@
import com.android.systemui.plugins.ClockEvents
import com.android.systemui.plugins.ClockFaceController
import com.android.systemui.plugins.ClockFaceEvents
+import com.android.systemui.plugins.ClockTickRate
import com.android.systemui.plugins.log.LogBuffer
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.policy.BatteryController
import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.capture
@@ -72,7 +74,7 @@
@Mock private lateinit var animations: ClockAnimations
@Mock private lateinit var events: ClockEvents
@Mock private lateinit var clock: ClockController
- @Mock private lateinit var mainExecutor: Executor
+ @Mock private lateinit var mainExecutor: DelayableExecutor
@Mock private lateinit var bgExecutor: Executor
@Mock private lateinit var featureFlags: FeatureFlags
@Mock private lateinit var smallClockController: ClockFaceController
@@ -97,6 +99,8 @@
whenever(largeClockController.events).thenReturn(largeClockEvents)
whenever(clock.events).thenReturn(events)
whenever(clock.animations).thenReturn(animations)
+ whenever(smallClockEvents.tickRate).thenReturn(ClockTickRate.PER_MINUTE)
+ whenever(largeClockEvents.tickRate).thenReturn(ClockTickRate.PER_MINUTE)
repository = FakeKeyguardRepository()
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
index 1059543..50645e5 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
@@ -150,4 +150,11 @@
getContext().getResources().getString(R.string.kg_prompt_reason_restart_password),
false);
}
+
+
+ @Test
+ public void testReset() {
+ mKeyguardAbsKeyInputViewController.reset();
+ verify(mKeyguardMessageAreaController).setMessage("", false);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
index a4180fd..36b3f89 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
@@ -33,6 +33,7 @@
import android.provider.Settings;
import android.testing.AndroidTestingRunner;
import android.view.View;
+import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
@@ -47,6 +48,7 @@
import com.android.systemui.plugins.ClockController;
import com.android.systemui.plugins.ClockEvents;
import com.android.systemui.plugins.ClockFaceController;
+import com.android.systemui.plugins.ClockFaceEvents;
import com.android.systemui.plugins.log.LogBuffer;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shared.clocks.AnimatableClockView;
@@ -99,6 +101,8 @@
@Mock
private ClockEvents mClockEvents;
@Mock
+ private ClockFaceEvents mClockFaceEvents;
+ @Mock
DumpManager mDumpManager;
@Mock
ClockEventController mClockEventController;
@@ -118,6 +122,11 @@
@Mock
private LogBuffer mLogBuffer;
+ private final View mFakeDateView = (View) (new ViewGroup(mContext) {
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {}
+ });
+ private final View mFakeWeatherView = new View(mContext);
private final View mFakeSmartspaceView = new View(mContext);
private KeyguardClockSwitchController mController;
@@ -145,6 +154,8 @@
when(mLargeClockView.getContext()).thenReturn(getContext());
when(mView.isAttachedToWindow()).thenReturn(true);
+ when(mSmartspaceController.buildAndConnectDateView(any())).thenReturn(mFakeDateView);
+ when(mSmartspaceController.buildAndConnectWeatherView(any())).thenReturn(mFakeWeatherView);
when(mSmartspaceController.buildAndConnectView(any())).thenReturn(mFakeSmartspaceView);
mExecutor = new FakeExecutor(new FakeSystemClock());
mController = new KeyguardClockSwitchController(
@@ -168,6 +179,8 @@
when(mClockController.getLargeClock()).thenReturn(mLargeClockController);
when(mClockController.getSmallClock()).thenReturn(mSmallClockController);
when(mClockController.getEvents()).thenReturn(mClockEvents);
+ when(mSmallClockController.getEvents()).thenReturn(mClockFaceEvents);
+ when(mLargeClockController.getEvents()).thenReturn(mClockFaceEvents);
when(mClockController.getAnimations()).thenReturn(mClockAnimations);
when(mClockRegistry.createCurrentClock()).thenReturn(mClockController);
when(mClockEventController.getClock()).thenReturn(mClockController);
@@ -252,6 +265,19 @@
}
@Test
+ public void onLocaleListChanged_rebuildsSmartspaceViews_whenDecouplingEnabled() {
+ when(mSmartspaceController.isEnabled()).thenReturn(true);
+ when(mSmartspaceController.isDateWeatherDecoupled()).thenReturn(true);
+ mController.init();
+
+ mController.onLocaleListChanged();
+ // Should be called once on initial setup, then once again for locale change
+ verify(mSmartspaceController, times(2)).buildAndConnectDateView(mView);
+ verify(mSmartspaceController, times(2)).buildAndConnectWeatherView(mView);
+ verify(mSmartspaceController, times(2)).buildAndConnectView(mView);
+ }
+
+ @Test
public void testSmartspaceDisabledShowsKeyguardStatusArea() {
when(mSmartspaceController.isEnabled()).thenReturn(false);
mController.init();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ChooserSelectorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/ChooserSelectorTest.kt
index 81d0034..32edf8f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ChooserSelectorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/ChooserSelectorTest.kt
@@ -148,7 +148,7 @@
// Act
whenever(mockFeatureFlags.isEnabled(any<UnreleasedFlag>())).thenReturn(true)
- flagListener.value.onFlagChanged(TestFlagEvent(Flags.CHOOSER_UNBUNDLED.id))
+ flagListener.value.onFlagChanged(TestFlagEvent(Flags.CHOOSER_UNBUNDLED.name))
// Assert
verify(mockPackageManager, times(2)).setComponentEnabledSetting(
@@ -175,7 +175,7 @@
// Act
whenever(mockFeatureFlags.isEnabled(any<UnreleasedFlag>())).thenReturn(false)
- flagListener.value.onFlagChanged(TestFlagEvent(Flags.CHOOSER_UNBUNDLED.id))
+ flagListener.value.onFlagChanged(TestFlagEvent(Flags.CHOOSER_UNBUNDLED.name))
// Assert
verify(mockPackageManager, times(2)).setComponentEnabledSetting(
@@ -198,13 +198,13 @@
// Act
whenever(mockFeatureFlags.isEnabled(any<UnreleasedFlag>())).thenReturn(false)
- flagListener.value.onFlagChanged(TestFlagEvent(Flags.CHOOSER_UNBUNDLED.id + 1))
+ flagListener.value.onFlagChanged(TestFlagEvent("other flag"))
// Assert
verifyZeroInteractions(mockPackageManager)
}
- private class TestFlagEvent(override val flagId: Int) : FlagListenable.FlagEvent {
+ private class TestFlagEvent(override val flagName: String) : FlagListenable.FlagEvent {
override fun requestNoRestart() {}
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
index e918c1c..fc11148 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
@@ -21,6 +21,7 @@
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.systemui.dump.LogBufferHelperKt.logcatLogBuffer;
import static com.google.common.truth.Truth.assertThat;
@@ -88,6 +89,7 @@
import com.android.systemui.decor.PrivacyDotCornerDecorProviderImpl;
import com.android.systemui.decor.PrivacyDotDecorProviderFactory;
import com.android.systemui.decor.RoundedCornerResDelegate;
+import com.android.systemui.log.ScreenDecorationsLogger;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.settings.FakeDisplayTracker;
import com.android.systemui.settings.UserTracker;
@@ -219,11 +221,14 @@
mAuthController,
mStatusBarStateController,
mKeyguardUpdateMonitor,
- mExecutor));
+ mExecutor,
+ new ScreenDecorationsLogger(logcatLogBuffer("TestLogBuffer"))));
mScreenDecorations = spy(new ScreenDecorations(mContext, mExecutor, mSecureSettings,
mTunerService, mUserTracker, mDisplayTracker, mDotViewController, mThreadFactory,
- mPrivacyDotDecorProviderFactory, mFaceScanningProviderFactory) {
+ mPrivacyDotDecorProviderFactory, mFaceScanningProviderFactory,
+ new ScreenDecorationsLogger(logcatLogBuffer("TestLogBuffer")),
+ mAuthController) {
@Override
public void start() {
super.start();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
index fd931b0..41beada 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
@@ -52,18 +52,16 @@
import androidx.test.filters.SmallTest
import com.airbnb.lottie.LottieAnimationView
import com.android.keyguard.KeyguardUpdateMonitor
-import com.android.keyguard.ViewMediatorCallback
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.SysuiTestableContext
import com.android.systemui.dump.DumpManager
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags.MODERN_ALTERNATE_BOUNCER
-import com.android.systemui.keyguard.data.repository.FakeBiometricRepository
+import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
-import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerRepository
import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor
-import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.recents.OverviewProxyService
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.time.FakeSystemClock
@@ -111,7 +109,7 @@
@Captor lateinit var overlayCaptor: ArgumentCaptor<View>
@Captor lateinit var overlayViewParamsCaptor: ArgumentCaptor<WindowManager.LayoutParams>
- private lateinit var keyguardBouncerRepository: KeyguardBouncerRepository
+ private lateinit var keyguardBouncerRepository: FakeKeyguardBouncerRepository
private lateinit var alternateBouncerInteractor: AlternateBouncerInteractor
private val featureFlags = FakeFeatureFlags()
private val executor = FakeExecutor(FakeSystemClock())
@@ -135,17 +133,11 @@
@Before
fun setup() {
featureFlags.set(MODERN_ALTERNATE_BOUNCER, true)
- keyguardBouncerRepository =
- KeyguardBouncerRepository(
- mock(ViewMediatorCallback::class.java),
- FakeSystemClock(),
- TestCoroutineScope(),
- mock(TableLogBuffer::class.java),
- )
+ keyguardBouncerRepository = FakeKeyguardBouncerRepository()
alternateBouncerInteractor =
AlternateBouncerInteractor(
keyguardBouncerRepository,
- FakeBiometricRepository(),
+ FakeBiometricSettingsRepository(),
FakeDeviceEntryFingerprintAuthRepository(),
FakeSystemClock(),
mock(KeyguardUpdateMonitor::class.java),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerBaseTest.java
index 498cc29..dbbc266 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerBaseTest.java
@@ -147,7 +147,6 @@
protected UdfpsKeyguardViewController createUdfpsKeyguardViewController(
boolean useModernBouncer, boolean useExpandedOverlay) {
- mFeatureFlags.set(Flags.MODERN_BOUNCER, useModernBouncer);
mFeatureFlags.set(Flags.MODERN_ALTERNATE_BOUNCER, useModernBouncer);
mFeatureFlags.set(Flags.UDFPS_NEW_TOUCH_DETECTION, useExpandedOverlay);
UdfpsKeyguardViewController controller = new UdfpsKeyguardViewController(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt
index 81a6bc2..c73ff1d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt
@@ -26,9 +26,10 @@
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.keyguard.DismissCallbackRegistry
import com.android.systemui.keyguard.data.BouncerView
-import com.android.systemui.keyguard.data.repository.BiometricRepository
+import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository
+import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepositoryImpl
import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor
import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerCallbackInteractor
import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor
@@ -65,7 +66,7 @@
allowTestableLooperAsMainThread() // repeatWhenAttached requires the main thread
MockitoAnnotations.initMocks(this)
keyguardBouncerRepository =
- KeyguardBouncerRepository(
+ KeyguardBouncerRepositoryImpl(
mock(com.android.keyguard.ViewMediatorCallback::class.java),
FakeSystemClock(),
TestCoroutineScope(),
@@ -91,7 +92,7 @@
mAlternateBouncerInteractor =
AlternateBouncerInteractor(
keyguardBouncerRepository,
- mock(BiometricRepository::class.java),
+ mock(BiometricSettingsRepository::class.java),
mock(DeviceEntryFingerprintAuthRepository::class.java),
mock(SystemClock::class.java),
mock(KeyguardUpdateMonitor::class.java),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FakeFeatureFlagsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FakeFeatureFlagsTest.kt
index 170a70f..35f0f6c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FakeFeatureFlagsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FakeFeatureFlagsTest.kt
@@ -125,7 +125,7 @@
flags.set(unreleasedFlag, false)
flags.set(unreleasedFlag, false)
- listener.verifyInOrder(unreleasedFlag.id, unreleasedFlag.id)
+ listener.verifyInOrder(unreleasedFlag.name, unreleasedFlag.name)
}
@Test
@@ -137,7 +137,7 @@
flags.set(stringFlag, "Test")
flags.set(stringFlag, "Test")
- listener.verifyInOrder(stringFlag.id)
+ listener.verifyInOrder(stringFlag.name)
}
@Test
@@ -149,7 +149,7 @@
flags.removeListener(listener)
flags.set(unreleasedFlag, false)
- listener.verifyInOrder(unreleasedFlag.id)
+ listener.verifyInOrder(unreleasedFlag.name)
}
@Test
@@ -162,7 +162,7 @@
flags.removeListener(listener)
flags.set(stringFlag, "Other")
- listener.verifyInOrder(stringFlag.id)
+ listener.verifyInOrder(stringFlag.name)
}
@Test
@@ -175,7 +175,7 @@
flags.set(releasedFlag, true)
flags.set(unreleasedFlag, true)
- listener.verifyInOrder(releasedFlag.id, unreleasedFlag.id)
+ listener.verifyInOrder(releasedFlag.name, unreleasedFlag.name)
}
@Test
@@ -191,7 +191,7 @@
flags.set(releasedFlag, false)
flags.set(unreleasedFlag, false)
- listener.verifyInOrder(releasedFlag.id, unreleasedFlag.id)
+ listener.verifyInOrder(releasedFlag.name, unreleasedFlag.name)
}
@Test
@@ -204,8 +204,8 @@
flags.set(releasedFlag, true)
- listener1.verifyInOrder(releasedFlag.id)
- listener2.verifyInOrder(releasedFlag.id)
+ listener1.verifyInOrder(releasedFlag.name)
+ listener2.verifyInOrder(releasedFlag.name)
}
@Test
@@ -220,18 +220,18 @@
flags.removeListener(listener2)
flags.set(releasedFlag, false)
- listener1.verifyInOrder(releasedFlag.id, releasedFlag.id)
- listener2.verifyInOrder(releasedFlag.id)
+ listener1.verifyInOrder(releasedFlag.name, releasedFlag.name)
+ listener2.verifyInOrder(releasedFlag.name)
}
class VerifyingListener : FlagListenable.Listener {
- var flagEventIds = mutableListOf<Int>()
+ var flagEventNames = mutableListOf<String>()
override fun onFlagChanged(event: FlagListenable.FlagEvent) {
- flagEventIds.add(event.flagId)
+ flagEventNames.add(event.flagName)
}
- fun verifyInOrder(vararg eventIds: Int) {
- assertThat(flagEventIds).containsExactlyElementsIn(eventIds.asList())
+ fun verifyInOrder(vararg eventNames: String) {
+ assertThat(flagEventNames).containsExactlyElementsIn(eventNames.asList())
}
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt
index 7592cc5..d8bbd04 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt
@@ -23,12 +23,11 @@
import android.content.res.Resources.NotFoundException
import android.test.suitebuilder.annotation.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.statusbar.commandline.CommandRegistry
-import com.android.systemui.util.DeviceConfigProxyFake
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.nullable
import com.android.systemui.util.mockito.withArgCaptor
+import com.android.systemui.util.settings.GlobalSettings
import com.android.systemui.util.settings.SecureSettings
import com.google.common.truth.Truth.assertThat
import org.junit.Assert
@@ -62,21 +61,20 @@
@Mock
private lateinit var mockContext: Context
@Mock
+ private lateinit var globalSettings: GlobalSettings
+ @Mock
private lateinit var secureSettings: SecureSettings
@Mock
private lateinit var systemProperties: SystemPropertiesHelper
@Mock
private lateinit var resources: Resources
@Mock
- private lateinit var commandRegistry: CommandRegistry
- @Mock
private lateinit var restarter: Restarter
- private val flagMap = mutableMapOf<Int, Flag<*>>()
+ private val flagMap = mutableMapOf<String, Flag<*>>()
private lateinit var broadcastReceiver: BroadcastReceiver
- private lateinit var clearCacheAction: Consumer<Int>
+ private lateinit var clearCacheAction: Consumer<String>
private val serverFlagReader = ServerFlagReaderFake()
- private val deviceConfig = DeviceConfigProxyFake()
private val teamfoodableFlagA = UnreleasedFlag(
500, name = "a", namespace = "test", teamfood = true
)
@@ -87,11 +85,13 @@
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
- flagMap.put(teamfoodableFlagA.id, teamfoodableFlagA)
- flagMap.put(teamfoodableFlagB.id, teamfoodableFlagB)
+ flagMap.put(Flags.TEAMFOOD.name, Flags.TEAMFOOD)
+ flagMap.put(teamfoodableFlagA.name, teamfoodableFlagA)
+ flagMap.put(teamfoodableFlagB.name, teamfoodableFlagB)
mFeatureFlagsDebug = FeatureFlagsDebug(
flagManager,
mockContext,
+ globalSettings,
secureSettings,
systemProperties,
resources,
@@ -110,14 +110,14 @@
clearCacheAction = withArgCaptor {
verify(flagManager).clearCacheAction = capture()
}
- whenever(flagManager.idToSettingsKey(any())).thenAnswer { "key-${it.arguments[0]}" }
+ whenever(flagManager.nameToSettingsKey(any())).thenAnswer { "key-${it.arguments[0]}" }
}
@Test
fun readBooleanFlag() {
// Remember that the TEAMFOOD flag is id#1 and has special behavior.
- whenever(flagManager.readFlagValue<Boolean>(eq(3), any())).thenReturn(true)
- whenever(flagManager.readFlagValue<Boolean>(eq(4), any())).thenReturn(false)
+ whenever(flagManager.readFlagValue<Boolean>(eq("3"), any())).thenReturn(true)
+ whenever(flagManager.readFlagValue<Boolean>(eq("4"), any())).thenReturn(false)
assertThat(
mFeatureFlagsDebug.isEnabled(
@@ -141,7 +141,7 @@
mFeatureFlagsDebug.isEnabled(
ReleasedFlag(
4,
- name = "3",
+ name = "4",
namespace = "test"
)
)
@@ -150,7 +150,7 @@
mFeatureFlagsDebug.isEnabled(
UnreleasedFlag(
5,
- name = "4",
+ name = "5",
namespace = "test"
)
)
@@ -159,7 +159,8 @@
@Test
fun teamFoodFlag_False() {
- whenever(flagManager.readFlagValue<Boolean>(eq(1), any())).thenReturn(false)
+ whenever(flagManager.readFlagValue<Boolean>(
+ eq(Flags.TEAMFOOD.name), any())).thenReturn(false)
assertThat(mFeatureFlagsDebug.isEnabled(teamfoodableFlagA)).isFalse()
assertThat(mFeatureFlagsDebug.isEnabled(teamfoodableFlagB)).isTrue()
@@ -170,7 +171,8 @@
@Test
fun teamFoodFlag_True() {
- whenever(flagManager.readFlagValue<Boolean>(eq(1), any())).thenReturn(true)
+ whenever(flagManager.readFlagValue<Boolean>(
+ eq(Flags.TEAMFOOD.name), any())).thenReturn(true)
assertThat(mFeatureFlagsDebug.isEnabled(teamfoodableFlagA)).isTrue()
assertThat(mFeatureFlagsDebug.isEnabled(teamfoodableFlagB)).isTrue()
@@ -181,11 +183,12 @@
@Test
fun teamFoodFlag_Overridden() {
- whenever(flagManager.readFlagValue<Boolean>(eq(teamfoodableFlagA.id), any()))
+ whenever(flagManager.readFlagValue<Boolean>(eq(teamfoodableFlagA.name), any()))
.thenReturn(true)
- whenever(flagManager.readFlagValue<Boolean>(eq(teamfoodableFlagB.id), any()))
+ whenever(flagManager.readFlagValue<Boolean>(eq(teamfoodableFlagB.name), any()))
.thenReturn(false)
- whenever(flagManager.readFlagValue<Boolean>(eq(1), any())).thenReturn(true)
+ whenever(flagManager.readFlagValue<Boolean>(
+ eq(Flags.TEAMFOOD.name), any())).thenReturn(true)
assertThat(mFeatureFlagsDebug.isEnabled(teamfoodableFlagA)).isTrue()
assertThat(mFeatureFlagsDebug.isEnabled(teamfoodableFlagB)).isFalse()
@@ -202,8 +205,8 @@
whenever(resources.getBoolean(1004)).thenAnswer { throw NameNotFoundException() }
whenever(resources.getBoolean(1005)).thenAnswer { throw NameNotFoundException() }
- whenever(flagManager.readFlagValue<Boolean>(eq(3), any())).thenReturn(true)
- whenever(flagManager.readFlagValue<Boolean>(eq(5), any())).thenReturn(false)
+ whenever(flagManager.readFlagValue<Boolean>(eq("3"), any())).thenReturn(true)
+ whenever(flagManager.readFlagValue<Boolean>(eq("5"), any())).thenReturn(false)
assertThat(
mFeatureFlagsDebug.isEnabled(
@@ -255,8 +258,8 @@
@Test
fun readStringFlag() {
- whenever(flagManager.readFlagValue<String>(eq(3), any())).thenReturn("foo")
- whenever(flagManager.readFlagValue<String>(eq(4), any())).thenReturn("bar")
+ whenever(flagManager.readFlagValue<String>(eq("3"), any())).thenReturn("foo")
+ whenever(flagManager.readFlagValue<String>(eq("4"), any())).thenReturn("bar")
assertThat(mFeatureFlagsDebug.getString(StringFlag(1, "1", "test", "biz"))).isEqualTo("biz")
assertThat(mFeatureFlagsDebug.getString(StringFlag(2, "2", "test", "baz"))).isEqualTo("baz")
assertThat(mFeatureFlagsDebug.getString(StringFlag(3, "3", "test", "buz"))).isEqualTo("foo")
@@ -272,9 +275,9 @@
whenever(resources.getString(1005)).thenAnswer { throw NameNotFoundException() }
whenever(resources.getString(1006)).thenAnswer { throw NameNotFoundException() }
- whenever(flagManager.readFlagValue<String>(eq(3), any())).thenReturn("override3")
- whenever(flagManager.readFlagValue<String>(eq(4), any())).thenReturn("override4")
- whenever(flagManager.readFlagValue<String>(eq(6), any())).thenReturn("override6")
+ whenever(flagManager.readFlagValue<String>(eq("3"), any())).thenReturn("override3")
+ whenever(flagManager.readFlagValue<String>(eq("4"), any())).thenReturn("override4")
+ whenever(flagManager.readFlagValue<String>(eq("6"), any())).thenReturn("override6")
assertThat(
mFeatureFlagsDebug.getString(
@@ -322,8 +325,8 @@
@Test
fun readIntFlag() {
- whenever(flagManager.readFlagValue<Int>(eq(3), any())).thenReturn(22)
- whenever(flagManager.readFlagValue<Int>(eq(4), any())).thenReturn(48)
+ whenever(flagManager.readFlagValue<Int>(eq("3"), any())).thenReturn(22)
+ whenever(flagManager.readFlagValue<Int>(eq("4"), any())).thenReturn(48)
assertThat(mFeatureFlagsDebug.getInt(IntFlag(1, "1", "test", 12))).isEqualTo(12)
assertThat(mFeatureFlagsDebug.getInt(IntFlag(2, "2", "test", 93))).isEqualTo(93)
assertThat(mFeatureFlagsDebug.getInt(IntFlag(3, "3", "test", 8))).isEqualTo(22)
@@ -368,12 +371,12 @@
broadcastReceiver.onReceive(mockContext, Intent())
broadcastReceiver.onReceive(mockContext, Intent("invalid action"))
broadcastReceiver.onReceive(mockContext, Intent(FlagManager.ACTION_SET_FLAG))
- setByBroadcast(0, false) // unknown id does nothing
- setByBroadcast(1, "string") // wrong type does nothing
- setByBroadcast(2, 123) // wrong type does nothing
- setByBroadcast(3, false) // wrong type does nothing
- setByBroadcast(4, 123) // wrong type does nothing
- verifyNoMoreInteractions(flagManager, secureSettings)
+ setByBroadcast("0", false) // unknown id does nothing
+ setByBroadcast("1", "string") // wrong type does nothing
+ setByBroadcast("2", 123) // wrong type does nothing
+ setByBroadcast("3", false) // wrong type does nothing
+ setByBroadcast("4", 123) // wrong type does nothing
+ verifyNoMoreInteractions(flagManager, globalSettings)
}
@Test
@@ -383,16 +386,16 @@
// trying to erase an id not in the map does nothing
broadcastReceiver.onReceive(
mockContext,
- Intent(FlagManager.ACTION_SET_FLAG).putExtra(FlagManager.EXTRA_ID, 0)
+ Intent(FlagManager.ACTION_SET_FLAG).putExtra(FlagManager.EXTRA_NAME, "")
)
- verifyNoMoreInteractions(flagManager, secureSettings)
+ verifyNoMoreInteractions(flagManager, globalSettings)
// valid id with no value puts empty string in the setting
broadcastReceiver.onReceive(
mockContext,
- Intent(FlagManager.ACTION_SET_FLAG).putExtra(FlagManager.EXTRA_ID, 1)
+ Intent(FlagManager.ACTION_SET_FLAG).putExtra(FlagManager.EXTRA_NAME, "1")
)
- verifyPutData(1, "", numReads = 0)
+ verifyPutData("1", "", numReads = 0)
}
@Test
@@ -402,51 +405,51 @@
addFlag(ResourceBooleanFlag(3, "3", "test", 1003))
addFlag(ResourceBooleanFlag(4, "4", "test", 1004))
- setByBroadcast(1, false)
- verifyPutData(1, "{\"type\":\"boolean\",\"value\":false}")
+ setByBroadcast("1", false)
+ verifyPutData("1", "{\"type\":\"boolean\",\"value\":false}")
- setByBroadcast(2, true)
- verifyPutData(2, "{\"type\":\"boolean\",\"value\":true}")
+ setByBroadcast("2", true)
+ verifyPutData("2", "{\"type\":\"boolean\",\"value\":true}")
- setByBroadcast(3, false)
- verifyPutData(3, "{\"type\":\"boolean\",\"value\":false}")
+ setByBroadcast("3", false)
+ verifyPutData("3", "{\"type\":\"boolean\",\"value\":false}")
- setByBroadcast(4, true)
- verifyPutData(4, "{\"type\":\"boolean\",\"value\":true}")
+ setByBroadcast("4", true)
+ verifyPutData("4", "{\"type\":\"boolean\",\"value\":true}")
}
@Test
fun setStringFlag() {
- addFlag(StringFlag(1, "flag1", "1", "test"))
+ addFlag(StringFlag(1, "1", "1", "test"))
addFlag(ResourceStringFlag(2, "2", "test", 1002))
- setByBroadcast(1, "override1")
- verifyPutData(1, "{\"type\":\"string\",\"value\":\"override1\"}")
+ setByBroadcast("1", "override1")
+ verifyPutData("1", "{\"type\":\"string\",\"value\":\"override1\"}")
- setByBroadcast(2, "override2")
- verifyPutData(2, "{\"type\":\"string\",\"value\":\"override2\"}")
+ setByBroadcast("2", "override2")
+ verifyPutData("2", "{\"type\":\"string\",\"value\":\"override2\"}")
}
@Test
fun setFlag_ClearsCache() {
val flag1 = addFlag(StringFlag(1, "1", "test", "flag1"))
- whenever(flagManager.readFlagValue<String>(eq(1), any())).thenReturn("original")
+ whenever(flagManager.readFlagValue<String>(eq("1"), any())).thenReturn("original")
// gets the flag & cache it
assertThat(mFeatureFlagsDebug.getString(flag1)).isEqualTo("original")
- verify(flagManager).readFlagValue(eq(1), eq(StringFlagSerializer))
+ verify(flagManager, times(1)).readFlagValue(eq("1"), eq(StringFlagSerializer))
// hit the cache
assertThat(mFeatureFlagsDebug.getString(flag1)).isEqualTo("original")
verifyNoMoreInteractions(flagManager)
// set the flag
- setByBroadcast(1, "new")
- verifyPutData(1, "{\"type\":\"string\",\"value\":\"new\"}", numReads = 2)
- whenever(flagManager.readFlagValue<String>(eq(1), any())).thenReturn("new")
+ setByBroadcast("1", "new")
+ verifyPutData("1", "{\"type\":\"string\",\"value\":\"new\"}", numReads = 2)
+ whenever(flagManager.readFlagValue<String>(eq("1"), any())).thenReturn("new")
assertThat(mFeatureFlagsDebug.getString(flag1)).isEqualTo("new")
- verify(flagManager, times(3)).readFlagValue(eq(1), eq(StringFlagSerializer))
+ verify(flagManager, times(3)).readFlagValue(eq("1"), eq(StringFlagSerializer))
}
@Test
@@ -463,7 +466,6 @@
val flag = UnreleasedFlag(100, name = "100", namespace = "test")
serverFlagReader.setFlagValue(flag.namespace, flag.name, true)
-
assertThat(mFeatureFlagsDebug.isEnabled(flag)).isTrue()
}
@@ -503,26 +505,26 @@
assertThat(dump).contains(" sysui_flag_7: [length=9] \"override7\"\n")
}
- private fun verifyPutData(id: Int, data: String, numReads: Int = 1) {
- inOrder(flagManager, secureSettings).apply {
- verify(flagManager, times(numReads)).readFlagValue(eq(id), any<FlagSerializer<*>>())
- verify(flagManager).idToSettingsKey(eq(id))
- verify(secureSettings).putStringForUser(eq("key-$id"), eq(data), anyInt())
- verify(flagManager).dispatchListenersAndMaybeRestart(eq(id), any())
+ private fun verifyPutData(name: String, data: String, numReads: Int = 1) {
+ inOrder(flagManager, globalSettings).apply {
+ verify(flagManager, times(numReads)).readFlagValue(eq(name), any<FlagSerializer<*>>())
+ verify(flagManager).nameToSettingsKey(eq(name))
+ verify(globalSettings).putStringForUser(eq("key-$name"), eq(data), anyInt())
+ verify(flagManager).dispatchListenersAndMaybeRestart(eq(name), any())
}.verifyNoMoreInteractions()
- verifyNoMoreInteractions(flagManager, secureSettings)
+ verifyNoMoreInteractions(flagManager, globalSettings)
}
- private fun setByBroadcast(id: Int, value: Serializable?) {
+ private fun setByBroadcast(name: String, value: Serializable?) {
val intent = Intent(FlagManager.ACTION_SET_FLAG)
- intent.putExtra(FlagManager.EXTRA_ID, id)
+ intent.putExtra(FlagManager.EXTRA_NAME, name)
intent.putExtra(FlagManager.EXTRA_VALUE, value)
broadcastReceiver.onReceive(mockContext, intent)
}
private fun <F : Flag<*>> addFlag(flag: F): F {
- val old = flagMap.put(flag.id, flag)
- check(old == null) { "Flag ${flag.id} already registered" }
+ val old = flagMap.put(flag.name, flag)
+ check(old == null) { "Flag ${flag.name} already registered" }
return flag
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.kt
index d5b5a4a..4c6028c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.kt
@@ -19,7 +19,6 @@
import android.content.res.Resources
import android.test.suitebuilder.annotation.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.util.DeviceConfigProxyFake
import com.google.common.truth.Truth.assertThat
import org.junit.Assert.assertThrows
import org.junit.Before
@@ -39,9 +38,8 @@
@Mock private lateinit var mResources: Resources
@Mock private lateinit var mSystemProperties: SystemPropertiesHelper
@Mock private lateinit var restarter: Restarter
- private val flagMap = mutableMapOf<Int, Flag<*>>()
+ private val flagMap = mutableMapOf<String, Flag<*>>()
private val serverFlagReader = ServerFlagReaderFake()
- private val deviceConfig = DeviceConfigProxyFake()
@Before
fun setup() {
@@ -49,7 +47,6 @@
mFeatureFlagsRelease = FeatureFlagsRelease(
mResources,
mSystemProperties,
- deviceConfig,
serverFlagReader,
flagMap,
restarter)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FlagCommandTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FlagCommandTest.kt
index fea91c5..28131b5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FlagCommandTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FlagCommandTest.kt
@@ -32,7 +32,7 @@
@Mock private lateinit var featureFlags: FeatureFlagsDebug
@Mock private lateinit var pw: PrintWriter
- private val flagMap = mutableMapOf<Int, Flag<*>>()
+ private val flagMap = mutableMapOf<String, Flag<*>>()
private val flagA = UnreleasedFlag(500, "500", "test")
private val flagB = ReleasedFlag(501, "501", "test")
private val stringFlag = StringFlag(502, "502", "test", "abracadabra")
@@ -53,59 +53,59 @@
(invocation.getArgument(0) as IntFlag).default
}
- flagMap.put(flagA.id, flagA)
- flagMap.put(flagB.id, flagB)
- flagMap.put(stringFlag.id, stringFlag)
- flagMap.put(intFlag.id, intFlag)
+ flagMap.put(flagA.name, flagA)
+ flagMap.put(flagB.name, flagB)
+ flagMap.put(stringFlag.name, stringFlag)
+ flagMap.put(intFlag.name, intFlag)
cmd = FlagCommand(featureFlags, flagMap)
}
@Test
fun readBooleanFlagCommand() {
- cmd.execute(pw, listOf(flagA.id.toString()))
+ cmd.execute(pw, listOf(flagA.name))
Mockito.verify(featureFlags).isEnabled(flagA)
}
@Test
fun readStringFlagCommand() {
- cmd.execute(pw, listOf(stringFlag.id.toString()))
+ cmd.execute(pw, listOf(stringFlag.name))
Mockito.verify(featureFlags).getString(stringFlag)
}
@Test
fun readIntFlag() {
- cmd.execute(pw, listOf(intFlag.id.toString()))
+ cmd.execute(pw, listOf(intFlag.name))
Mockito.verify(featureFlags).getInt(intFlag)
}
@Test
fun setBooleanFlagCommand() {
- cmd.execute(pw, listOf(flagB.id.toString(), "on"))
+ cmd.execute(pw, listOf(flagB.name, "on"))
Mockito.verify(featureFlags).setBooleanFlagInternal(flagB, true)
}
@Test
fun setStringFlagCommand() {
- cmd.execute(pw, listOf(stringFlag.id.toString(), "set", "foobar"))
+ cmd.execute(pw, listOf(stringFlag.name, "set", "foobar"))
Mockito.verify(featureFlags).setStringFlagInternal(stringFlag, "foobar")
}
@Test
fun setIntFlag() {
- cmd.execute(pw, listOf(intFlag.id.toString(), "put", "123"))
+ cmd.execute(pw, listOf(intFlag.name, "put", "123"))
Mockito.verify(featureFlags).setIntFlagInternal(intFlag, 123)
}
@Test
fun toggleBooleanFlagCommand() {
- cmd.execute(pw, listOf(flagB.id.toString(), "toggle"))
+ cmd.execute(pw, listOf(flagB.name, "toggle"))
Mockito.verify(featureFlags).setBooleanFlagInternal(flagB, false)
}
@Test
fun eraseFlagCommand() {
- cmd.execute(pw, listOf(flagA.id.toString(), "erase"))
+ cmd.execute(pw, listOf(flagA.name, "erase"))
Mockito.verify(featureFlags).eraseFlag(flagA)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FlagManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FlagManagerTest.kt
index fca7e96..e679d47 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FlagManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FlagManagerTest.kt
@@ -87,14 +87,14 @@
@Test
fun testObserverClearsCache() {
val listener = mock<FlagListenable.Listener>()
- val clearCacheAction = mock<Consumer<Int>>()
+ val clearCacheAction = mock<Consumer<String>>()
mFlagManager.clearCacheAction = clearCacheAction
mFlagManager.addListener(ReleasedFlag(1, "1", "test"), listener)
val observer = withArgCaptor<ContentObserver> {
verify(mFlagSettingsHelper).registerContentObserver(any(), any(), capture())
}
observer.onChange(false, flagUri(1))
- verify(clearCacheAction).accept(eq(1))
+ verify(clearCacheAction).accept(eq("1"))
}
@Test
@@ -110,14 +110,14 @@
val flagEvent1 = withArgCaptor<FlagListenable.FlagEvent> {
verify(listener1).onFlagChanged(capture())
}
- assertThat(flagEvent1.flagId).isEqualTo(1)
+ assertThat(flagEvent1.flagName).isEqualTo("1")
verifyNoMoreInteractions(listener1, listener10)
observer.onChange(false, flagUri(10))
val flagEvent10 = withArgCaptor<FlagListenable.FlagEvent> {
verify(listener10).onFlagChanged(capture())
}
- assertThat(flagEvent10.flagId).isEqualTo(10)
+ assertThat(flagEvent10.flagName).isEqualTo("10")
verifyNoMoreInteractions(listener1, listener10)
}
@@ -130,18 +130,18 @@
mFlagManager.addListener(ReleasedFlag(1, "1", "test"), listener1)
mFlagManager.addListener(ReleasedFlag(10, "10", "test"), listener10)
- mFlagManager.dispatchListenersAndMaybeRestart(1, null)
+ mFlagManager.dispatchListenersAndMaybeRestart("1", null)
val flagEvent1 = withArgCaptor<FlagListenable.FlagEvent> {
verify(listener1).onFlagChanged(capture())
}
- assertThat(flagEvent1.flagId).isEqualTo(1)
+ assertThat(flagEvent1.flagName).isEqualTo("1")
verifyNoMoreInteractions(listener1, listener10)
- mFlagManager.dispatchListenersAndMaybeRestart(10, null)
+ mFlagManager.dispatchListenersAndMaybeRestart("10", null)
val flagEvent10 = withArgCaptor<FlagListenable.FlagEvent> {
verify(listener10).onFlagChanged(capture())
}
- assertThat(flagEvent10.flagId).isEqualTo(10)
+ assertThat(flagEvent10.flagName).isEqualTo("10")
verifyNoMoreInteractions(listener1, listener10)
}
@@ -151,25 +151,25 @@
mFlagManager.addListener(ReleasedFlag(1, "1", "test"), listener)
mFlagManager.addListener(ReleasedFlag(10, "10", "test"), listener)
- mFlagManager.dispatchListenersAndMaybeRestart(1, null)
+ mFlagManager.dispatchListenersAndMaybeRestart("1", null)
val flagEvent1 = withArgCaptor<FlagListenable.FlagEvent> {
verify(listener).onFlagChanged(capture())
}
- assertThat(flagEvent1.flagId).isEqualTo(1)
+ assertThat(flagEvent1.flagName).isEqualTo("1")
verifyNoMoreInteractions(listener)
- mFlagManager.dispatchListenersAndMaybeRestart(10, null)
+ mFlagManager.dispatchListenersAndMaybeRestart("10", null)
val flagEvent10 = withArgCaptor<FlagListenable.FlagEvent> {
verify(listener, times(2)).onFlagChanged(capture())
}
- assertThat(flagEvent10.flagId).isEqualTo(10)
+ assertThat(flagEvent10.flagName).isEqualTo("10")
verifyNoMoreInteractions(listener)
}
@Test
fun testRestartWithNoListeners() {
val restartAction = mock<Consumer<Boolean>>()
- mFlagManager.dispatchListenersAndMaybeRestart(1, restartAction)
+ mFlagManager.dispatchListenersAndMaybeRestart("1", restartAction)
verify(restartAction).accept(eq(false))
verifyNoMoreInteractions(restartAction)
}
@@ -180,7 +180,7 @@
mFlagManager.addListener(ReleasedFlag(1, "1", "test")) { event ->
event.requestNoRestart()
}
- mFlagManager.dispatchListenersAndMaybeRestart(1, restartAction)
+ mFlagManager.dispatchListenersAndMaybeRestart("1", restartAction)
verify(restartAction).accept(eq(true))
verifyNoMoreInteractions(restartAction)
}
@@ -191,7 +191,7 @@
mFlagManager.addListener(ReleasedFlag(10, "10", "test")) { event ->
event.requestNoRestart()
}
- mFlagManager.dispatchListenersAndMaybeRestart(1, restartAction)
+ mFlagManager.dispatchListenersAndMaybeRestart("1", restartAction)
verify(restartAction).accept(eq(false))
verifyNoMoreInteractions(restartAction)
}
@@ -205,7 +205,7 @@
mFlagManager.addListener(ReleasedFlag(10, "10", "test")) {
// do not request
}
- mFlagManager.dispatchListenersAndMaybeRestart(1, restartAction)
+ mFlagManager.dispatchListenersAndMaybeRestart("1", restartAction)
verify(restartAction).accept(eq(false))
verifyNoMoreInteractions(restartAction)
}
@@ -214,31 +214,31 @@
fun testReadBooleanFlag() {
// test that null string returns null
whenever(mFlagSettingsHelper.getString(any())).thenReturn(null)
- assertThat(mFlagManager.readFlagValue(1, BooleanFlagSerializer)).isNull()
+ assertThat(mFlagManager.readFlagValue("1", BooleanFlagSerializer)).isNull()
// test that empty string returns null
whenever(mFlagSettingsHelper.getString(any())).thenReturn("")
- assertThat(mFlagManager.readFlagValue(1, BooleanFlagSerializer)).isNull()
+ assertThat(mFlagManager.readFlagValue("1", BooleanFlagSerializer)).isNull()
// test false
whenever(mFlagSettingsHelper.getString(any()))
.thenReturn("{\"type\":\"boolean\",\"value\":false}")
- assertThat(mFlagManager.readFlagValue(1, BooleanFlagSerializer)).isFalse()
+ assertThat(mFlagManager.readFlagValue("1", BooleanFlagSerializer)).isFalse()
// test true
whenever(mFlagSettingsHelper.getString(any()))
.thenReturn("{\"type\":\"boolean\",\"value\":true}")
- assertThat(mFlagManager.readFlagValue(1, BooleanFlagSerializer)).isTrue()
+ assertThat(mFlagManager.readFlagValue("1", BooleanFlagSerializer)).isTrue()
// Reading a value of a different type should just return null
whenever(mFlagSettingsHelper.getString(any()))
.thenReturn("{\"type\":\"string\",\"value\":\"foo\"}")
- assertThat(mFlagManager.readFlagValue(1, BooleanFlagSerializer)).isNull()
+ assertThat(mFlagManager.readFlagValue("1", BooleanFlagSerializer)).isNull()
// Reading a value that isn't json should throw an exception
assertThrows(InvalidFlagStorageException::class.java) {
whenever(mFlagSettingsHelper.getString(any())).thenReturn("1")
- mFlagManager.readFlagValue(1, BooleanFlagSerializer)
+ mFlagManager.readFlagValue("1", BooleanFlagSerializer)
}
}
@@ -257,31 +257,31 @@
fun testReadStringFlag() {
// test that null string returns null
whenever(mFlagSettingsHelper.getString(any())).thenReturn(null)
- assertThat(mFlagManager.readFlagValue(1, StringFlagSerializer)).isNull()
+ assertThat(mFlagManager.readFlagValue("1", StringFlagSerializer)).isNull()
// test that empty string returns null
whenever(mFlagSettingsHelper.getString(any())).thenReturn("")
- assertThat(mFlagManager.readFlagValue(1, StringFlagSerializer)).isNull()
+ assertThat(mFlagManager.readFlagValue("1", StringFlagSerializer)).isNull()
// test json with the empty string value returns empty string
whenever(mFlagSettingsHelper.getString(any()))
.thenReturn("{\"type\":\"string\",\"value\":\"\"}")
- assertThat(mFlagManager.readFlagValue(1, StringFlagSerializer)).isEqualTo("")
+ assertThat(mFlagManager.readFlagValue("1", StringFlagSerializer)).isEqualTo("")
// test string with value is returned
whenever(mFlagSettingsHelper.getString(any()))
.thenReturn("{\"type\":\"string\",\"value\":\"foo\"}")
- assertThat(mFlagManager.readFlagValue(1, StringFlagSerializer)).isEqualTo("foo")
+ assertThat(mFlagManager.readFlagValue("1", StringFlagSerializer)).isEqualTo("foo")
// Reading a value of a different type should just return null
whenever(mFlagSettingsHelper.getString(any()))
.thenReturn("{\"type\":\"boolean\",\"value\":false}")
- assertThat(mFlagManager.readFlagValue(1, StringFlagSerializer)).isNull()
+ assertThat(mFlagManager.readFlagValue("1", StringFlagSerializer)).isNull()
// Reading a value that isn't json should throw an exception
assertThrows(InvalidFlagStorageException::class.java) {
whenever(mFlagSettingsHelper.getString(any())).thenReturn("1")
- mFlagManager.readFlagValue(1, StringFlagSerializer)
+ mFlagManager.readFlagValue("1", StringFlagSerializer)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfigTest.kt
new file mode 100644
index 0000000..a3740d8
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfigTest.kt
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.data.quickaffordance
+
+import android.content.Context
+import android.media.AudioManager
+import androidx.lifecycle.LiveData
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.settings.UserFileManager
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.util.RingerModeTracker
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mock
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class MuteQuickAffordanceConfigTest : SysuiTestCase() {
+
+ private lateinit var underTest: MuteQuickAffordanceConfig
+ @Mock
+ private lateinit var ringerModeTracker: RingerModeTracker
+ @Mock
+ private lateinit var audioManager: AudioManager
+ @Mock
+ private lateinit var userTracker: UserTracker
+ @Mock
+ private lateinit var userFileManager: UserFileManager
+
+ private lateinit var testScope: TestScope
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ testScope = TestScope()
+
+ whenever(userTracker.userContext).thenReturn(context)
+ whenever(userFileManager.getSharedPreferences(any(), any(), any()))
+ .thenReturn(context.getSharedPreferences("mutequickaffordancetest", Context.MODE_PRIVATE))
+
+ underTest = MuteQuickAffordanceConfig(
+ context,
+ userTracker,
+ userFileManager,
+ ringerModeTracker,
+ audioManager
+ )
+ }
+
+ @Test
+ fun `picker state - volume fixed - not available`() = testScope.runTest {
+ //given
+ whenever(audioManager.isVolumeFixed).thenReturn(true)
+
+ //when
+ val result = underTest.getPickerScreenState()
+
+ //then
+ assertEquals(KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice, result)
+ }
+
+ @Test
+ fun `picker state - volume not fixed - available`() = testScope.runTest {
+ //given
+ whenever(audioManager.isVolumeFixed).thenReturn(false)
+
+ //when
+ val result = underTest.getPickerScreenState()
+
+ //then
+ assertEquals(KeyguardQuickAffordanceConfig.PickerScreenState.Default(), result)
+ }
+
+ @Test
+ fun `triggered - state was previously NORMAL - currently SILENT - move to previous state`() {
+ //given
+ val ringerModeCapture = argumentCaptor<Int>()
+ val ringerModeInternal = mock<LiveData<Int>>()
+ whenever(ringerModeTracker.ringerModeInternal).thenReturn(ringerModeInternal)
+ whenever(ringerModeInternal.value).thenReturn(AudioManager.RINGER_MODE_NORMAL)
+ underTest.onTriggered(null)
+ whenever(ringerModeInternal.value).thenReturn(AudioManager.RINGER_MODE_SILENT)
+
+ //when
+ val result = underTest.onTriggered(null)
+ verify(audioManager, times(2)).ringerModeInternal = ringerModeCapture.capture()
+
+ //then
+ assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled, result)
+ assertEquals(AudioManager.RINGER_MODE_NORMAL, ringerModeCapture.value)
+ }
+
+ @Test
+ fun `triggered - state is not SILENT - move to SILENT ringer`() {
+ //given
+ val ringerModeCapture = argumentCaptor<Int>()
+ val ringerModeInternal = mock<LiveData<Int>>()
+ whenever(ringerModeTracker.ringerModeInternal).thenReturn(ringerModeInternal)
+ whenever(ringerModeInternal.value).thenReturn(AudioManager.RINGER_MODE_NORMAL)
+
+ //when
+ val result = underTest.onTriggered(null)
+ verify(audioManager).ringerModeInternal = ringerModeCapture.capture()
+
+ //then
+ assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled, result)
+ assertEquals(AudioManager.RINGER_MODE_SILENT, ringerModeCapture.value)
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceCoreStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceCoreStartableTest.kt
new file mode 100644
index 0000000..26601b6
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceCoreStartableTest.kt
@@ -0,0 +1,220 @@
+/*
+ * 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.keyguard.data.quickaffordance
+
+import android.content.Context
+import android.media.AudioManager
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.Observer
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
+import com.android.systemui.settings.UserFileManager
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.util.RingerModeTracker
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.cancelChildren
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Assert.assertEquals
+
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyZeroInteractions
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class MuteQuickAffordanceCoreStartableTest : SysuiTestCase() {
+
+ @Mock
+ private lateinit var featureFlags: FeatureFlags
+ @Mock
+ private lateinit var userTracker: UserTracker
+ @Mock
+ private lateinit var ringerModeTracker: RingerModeTracker
+ @Mock
+ private lateinit var userFileManager: UserFileManager
+ @Mock
+ private lateinit var keyguardQuickAffordanceRepository: KeyguardQuickAffordanceRepository
+
+ private lateinit var testScope: TestScope
+
+ private lateinit var underTest: MuteQuickAffordanceCoreStartable
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ whenever(featureFlags.isEnabled(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES)).thenReturn(true)
+
+ val config: KeyguardQuickAffordanceConfig = mock()
+ whenever(config.key).thenReturn(BuiltInKeyguardQuickAffordanceKeys.MUTE)
+
+ val emission = MutableStateFlow(mapOf("testQuickAffordanceKey" to listOf(config)))
+ whenever(keyguardQuickAffordanceRepository.selections).thenReturn(emission)
+
+ testScope = TestScope()
+
+ underTest = MuteQuickAffordanceCoreStartable(
+ featureFlags,
+ userTracker,
+ ringerModeTracker,
+ userFileManager,
+ keyguardQuickAffordanceRepository,
+ testScope,
+ )
+ }
+
+ @Test
+ fun `feature flag is OFF - do nothing with keyguardQuickAffordanceRepository`() = testScope.runTest {
+ //given
+ whenever(featureFlags.isEnabled(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES)).thenReturn(false)
+
+ //when
+ underTest.start()
+
+ //then
+ verifyZeroInteractions(keyguardQuickAffordanceRepository)
+ coroutineContext.cancelChildren()
+ }
+
+ @Test
+ fun `feature flag is ON - call to keyguardQuickAffordanceRepository`() = testScope.runTest {
+ //given
+ val ringerModeInternal = mock<MutableLiveData<Int>>()
+ whenever(ringerModeTracker.ringerModeInternal).thenReturn(ringerModeInternal)
+
+ //when
+ underTest.start()
+ runCurrent()
+
+ //then
+ verify(keyguardQuickAffordanceRepository).selections
+ coroutineContext.cancelChildren()
+ }
+
+ @Test
+ fun `ringer mode is changed to SILENT - do not save to shared preferences`() = testScope.runTest {
+ //given
+ val ringerModeInternal = mock<MutableLiveData<Int>>()
+ val observerCaptor = argumentCaptor<Observer<Int>>()
+ whenever(ringerModeTracker.ringerModeInternal).thenReturn(ringerModeInternal)
+
+ //when
+ underTest.start()
+ runCurrent()
+ verify(ringerModeInternal).observeForever(observerCaptor.capture())
+ observerCaptor.value.onChanged(AudioManager.RINGER_MODE_SILENT)
+
+ //then
+ verifyZeroInteractions(userFileManager)
+ coroutineContext.cancelChildren()
+ }
+
+ @Test
+ fun `ringerModeInternal changes to something not SILENT - is set in sharedpreferences`() = testScope.runTest {
+ //given
+ val newRingerMode = 99
+ val observerCaptor = argumentCaptor<Observer<Int>>()
+ val ringerModeInternal = mock<MutableLiveData<Int>>()
+ val sharedPrefs = context.getSharedPreferences("quick_affordance_mute_ringer_mode_cache_test", Context.MODE_PRIVATE)
+ whenever(ringerModeTracker.ringerModeInternal).thenReturn(ringerModeInternal)
+ whenever(
+ userFileManager.getSharedPreferences(eq("quick_affordance_mute_ringer_mode_cache"), any(), any())
+ ).thenReturn(sharedPrefs)
+
+ //when
+ underTest.start()
+ runCurrent()
+ verify(ringerModeInternal).observeForever(observerCaptor.capture())
+ observerCaptor.value.onChanged(newRingerMode)
+ val result = sharedPrefs.getInt("key_last_non_silent_ringer_mode", -1)
+
+ //then
+ assertEquals(newRingerMode, result)
+ coroutineContext.cancelChildren()
+ }
+
+ @Test
+ fun `MUTE is in selections - observe ringerModeInternal`() = testScope.runTest {
+ //given
+ val ringerModeInternal = mock<MutableLiveData<Int>>()
+ whenever(ringerModeTracker.ringerModeInternal).thenReturn(ringerModeInternal)
+
+ //when
+ underTest.start()
+ runCurrent()
+
+ //then
+ verify(ringerModeInternal).observeForever(any())
+ coroutineContext.cancelChildren()
+ }
+
+ @Test
+ fun `MUTE is in selections 2x - observe ringerModeInternal`() = testScope.runTest {
+ //given
+ val config: KeyguardQuickAffordanceConfig = mock()
+ whenever(config.key).thenReturn(BuiltInKeyguardQuickAffordanceKeys.MUTE)
+ val emission = MutableStateFlow(mapOf("testKey" to listOf(config), "testkey2" to listOf(config)))
+ whenever(keyguardQuickAffordanceRepository.selections).thenReturn(emission)
+ val ringerModeInternal = mock<MutableLiveData<Int>>()
+ whenever(ringerModeTracker.ringerModeInternal).thenReturn(ringerModeInternal)
+
+ //when
+ underTest.start()
+ runCurrent()
+
+ //then
+ verify(ringerModeInternal).observeForever(any())
+ coroutineContext.cancelChildren()
+ }
+
+ @Test
+ fun `MUTE is not in selections - stop observing ringerModeInternal`() = testScope.runTest {
+ //given
+ val config: KeyguardQuickAffordanceConfig = mock()
+ whenever(config.key).thenReturn("notmutequickaffordance")
+ val emission = MutableStateFlow(mapOf("testKey" to listOf(config)))
+ whenever(keyguardQuickAffordanceRepository.selections).thenReturn(emission)
+ val ringerModeInternal = mock<MutableLiveData<Int>>()
+ whenever(ringerModeTracker.ringerModeInternal).thenReturn(ringerModeInternal)
+
+ //when
+ underTest.start()
+ runCurrent()
+
+ //then
+ verify(ringerModeInternal).removeObserver(any())
+ coroutineContext.cancelChildren()
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt
similarity index 94%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricRepositoryTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt
index a92dd3b..ddd1049 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt
@@ -50,8 +50,8 @@
@SmallTest
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@RunWith(AndroidTestingRunner::class)
-class BiometricRepositoryTest : SysuiTestCase() {
- private lateinit var underTest: BiometricRepository
+class BiometricSettingsRepositoryTest : SysuiTestCase() {
+ private lateinit var underTest: BiometricSettingsRepository
@Mock private lateinit var authController: AuthController
@Mock private lateinit var lockPatternUtils: LockPatternUtils
@@ -71,11 +71,11 @@
userRepository = FakeUserRepository()
}
- private suspend fun createBiometricRepository() {
+ private suspend fun createBiometricSettingsRepository() {
userRepository.setUserInfos(listOf(PRIMARY_USER))
userRepository.setSelectedUserInfo(PRIMARY_USER)
underTest =
- BiometricRepositoryImpl(
+ BiometricSettingsRepositoryImpl(
context = context,
lockPatternUtils = lockPatternUtils,
broadcastDispatcher = fakeBroadcastDispatcher,
@@ -91,7 +91,7 @@
@Test
fun fingerprintEnrollmentChange() =
testScope.runTest {
- createBiometricRepository()
+ createBiometricSettingsRepository()
val fingerprintEnabledByDevicePolicy = collectLastValue(underTest.isFingerprintEnrolled)
runCurrent()
@@ -117,7 +117,7 @@
@Test
fun strongBiometricAllowedChange() =
testScope.runTest {
- createBiometricRepository()
+ createBiometricSettingsRepository()
val strongBiometricAllowed = collectLastValue(underTest.isStrongBiometricAllowed)
runCurrent()
@@ -140,7 +140,7 @@
@Test
fun fingerprintDisabledByDpmChange() =
testScope.runTest {
- createBiometricRepository()
+ createBiometricSettingsRepository()
val fingerprintEnabledByDevicePolicy =
collectLastValue(underTest.isFingerprintEnabledByDevicePolicy)
runCurrent()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepositoryTest.kt
index 969537d2..444a2a7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepositoryTest.kt
@@ -45,7 +45,7 @@
MockitoAnnotations.initMocks(this)
val testCoroutineScope = TestCoroutineScope()
underTest =
- KeyguardBouncerRepository(
+ KeyguardBouncerRepositoryImpl(
viewMediatorCallback,
systemClock,
testCoroutineScope,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt
index b071a02..6099f01 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt
@@ -275,10 +275,10 @@
expected: Map<String, List<KeyguardQuickAffordanceConfig>>,
) {
assertThat(observed).isEqualTo(expected)
- assertThat(underTest.getSelections())
+ assertThat(underTest.getCurrentSelections())
.isEqualTo(expected.mapValues { (_, configs) -> configs.map { it.key } })
expected.forEach { (slotId, configs) ->
- assertThat(underTest.getSelections(slotId)).isEqualTo(configs)
+ assertThat(underTest.getCurrentSelections(slotId)).isEqualTo(configs)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractorTest.kt
index 68fff26..8caf60f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractorTest.kt
@@ -22,9 +22,10 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
-import com.android.systemui.keyguard.data.repository.FakeBiometricRepository
+import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository
+import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepositoryImpl
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.util.time.FakeSystemClock
import com.android.systemui.util.time.SystemClock
@@ -46,7 +47,7 @@
class AlternateBouncerInteractorTest : SysuiTestCase() {
private lateinit var underTest: AlternateBouncerInteractor
private lateinit var bouncerRepository: KeyguardBouncerRepository
- private lateinit var biometricRepository: FakeBiometricRepository
+ private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository
private lateinit var deviceEntryFingerprintAuthRepository:
FakeDeviceEntryFingerprintAuthRepository
@Mock private lateinit var systemClock: SystemClock
@@ -58,19 +59,19 @@
fun setup() {
MockitoAnnotations.initMocks(this)
bouncerRepository =
- KeyguardBouncerRepository(
+ KeyguardBouncerRepositoryImpl(
mock(ViewMediatorCallback::class.java),
FakeSystemClock(),
TestCoroutineScope(),
bouncerLogger,
)
- biometricRepository = FakeBiometricRepository()
+ biometricSettingsRepository = FakeBiometricSettingsRepository()
deviceEntryFingerprintAuthRepository = FakeDeviceEntryFingerprintAuthRepository()
featureFlags = FakeFeatureFlags().apply { this.set(Flags.MODERN_ALTERNATE_BOUNCER, true) }
underTest =
AlternateBouncerInteractor(
bouncerRepository,
- biometricRepository,
+ biometricSettingsRepository,
deviceEntryFingerprintAuthRepository,
systemClock,
keyguardUpdateMonitor,
@@ -95,7 +96,7 @@
@Test
fun canShowAlternateBouncerForFingerprint_noFingerprintsEnrolled() {
givenCanShowAlternateBouncer()
- biometricRepository.setFingerprintEnrolled(false)
+ biometricSettingsRepository.setFingerprintEnrolled(false)
assertFalse(underTest.canShowAlternateBouncerForFingerprint())
}
@@ -103,7 +104,7 @@
@Test
fun canShowAlternateBouncerForFingerprint_strongBiometricNotAllowed() {
givenCanShowAlternateBouncer()
- biometricRepository.setStrongBiometricAllowed(false)
+ biometricSettingsRepository.setStrongBiometricAllowed(false)
assertFalse(underTest.canShowAlternateBouncerForFingerprint())
}
@@ -111,7 +112,7 @@
@Test
fun canShowAlternateBouncerForFingerprint_devicePolicyDoesNotAllowFingerprint() {
givenCanShowAlternateBouncer()
- biometricRepository.setFingerprintEnabledByDevicePolicy(false)
+ biometricSettingsRepository.setFingerprintEnabledByDevicePolicy(false)
assertFalse(underTest.canShowAlternateBouncerForFingerprint())
}
@@ -158,13 +159,13 @@
private fun givenCanShowAlternateBouncer() {
bouncerRepository.setAlternateBouncerUIAvailable(true)
- biometricRepository.setFingerprintEnrolled(true)
- biometricRepository.setStrongBiometricAllowed(true)
- biometricRepository.setFingerprintEnabledByDevicePolicy(true)
+ biometricSettingsRepository.setFingerprintEnrolled(true)
+ biometricSettingsRepository.setStrongBiometricAllowed(true)
+ biometricSettingsRepository.setFingerprintEnabledByDevicePolicy(true)
deviceEntryFingerprintAuthRepository.setLockedOut(false)
}
private fun givenCannotShowAlternateBouncer() {
- biometricRepository.setFingerprintEnrolled(false)
+ biometricSettingsRepository.setFingerprintEnrolled(false)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index 3a871b4..702f3763 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -24,6 +24,7 @@
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepositoryImpl
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
import com.android.systemui.keyguard.shared.model.DozeStateModel
import com.android.systemui.keyguard.shared.model.DozeTransitionModel
import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -437,6 +438,43 @@
}
@Test
+ fun `DOZING to GONE`() =
+ testScope.runTest {
+ // GIVEN a prior transition has run to DOZING
+ runner.startTransition(
+ testScope,
+ TransitionInfo(
+ ownerName = "",
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.DOZING,
+ animator =
+ ValueAnimator().apply {
+ duration = 10
+ interpolator = Interpolators.LINEAR
+ },
+ )
+ )
+ runCurrent()
+ reset(mockTransitionRepository)
+
+ // WHEN biometrics succeeds with wake and unlock mode
+ keyguardRepository.setBiometricUnlockState(BiometricUnlockModel.WAKE_AND_UNLOCK)
+ runCurrent()
+
+ val info =
+ withArgCaptor<TransitionInfo> {
+ verify(mockTransitionRepository).startTransition(capture())
+ }
+ // THEN a transition to DOZING should occur
+ assertThat(info.ownerName).isEqualTo("FromDozingTransitionInteractor")
+ assertThat(info.from).isEqualTo(KeyguardState.DOZING)
+ assertThat(info.to).isEqualTo(KeyguardState.GONE)
+ assertThat(info.animator).isNotNull()
+
+ coroutineContext.cancelChildren()
+ }
+
+ @Test
fun `GONE to DOZING`() =
testScope.runTest {
// GIVEN a device with AOD not available
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt
index 7f48ea1..c5e0252 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt
@@ -68,13 +68,13 @@
@Mock private lateinit var keyguardBypassController: KeyguardBypassController
@Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
private val mainHandler = FakeHandler(Looper.getMainLooper())
- private lateinit var mPrimaryBouncerInteractor: PrimaryBouncerInteractor
+ private lateinit var underTest: PrimaryBouncerInteractor
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
DejankUtils.setImmediate(true)
- mPrimaryBouncerInteractor =
+ underTest =
PrimaryBouncerInteractor(
repository,
bouncerView,
@@ -94,7 +94,7 @@
@Test
fun testShow_isScrimmed() {
- mPrimaryBouncerInteractor.show(true)
+ underTest.show(true)
verify(repository).setOnScreenTurnedOff(false)
verify(repository).setKeyguardAuthenticated(null)
verify(repository).setPrimaryHide(false)
@@ -124,7 +124,7 @@
@Test
fun testHide() {
- mPrimaryBouncerInteractor.hide()
+ underTest.hide()
verify(falsingCollector).onBouncerHidden()
verify(keyguardStateController).notifyBouncerShowing(false)
verify(repository).setPrimaryShowingSoon(false)
@@ -137,7 +137,7 @@
@Test
fun testExpansion() {
`when`(repository.panelExpansionAmount.value).thenReturn(0.5f)
- mPrimaryBouncerInteractor.setPanelExpansion(0.6f)
+ underTest.setPanelExpansion(0.6f)
verify(repository).setPanelExpansion(0.6f)
verify(mPrimaryBouncerCallbackInteractor).dispatchExpansionChanged(0.6f)
}
@@ -146,7 +146,7 @@
fun testExpansion_fullyShown() {
`when`(repository.panelExpansionAmount.value).thenReturn(0.5f)
`when`(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
- mPrimaryBouncerInteractor.setPanelExpansion(EXPANSION_VISIBLE)
+ underTest.setPanelExpansion(EXPANSION_VISIBLE)
verify(falsingCollector).onBouncerShown()
verify(mPrimaryBouncerCallbackInteractor).dispatchFullyShown()
}
@@ -155,7 +155,7 @@
fun testExpansion_fullyHidden() {
`when`(repository.panelExpansionAmount.value).thenReturn(0.5f)
`when`(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
- mPrimaryBouncerInteractor.setPanelExpansion(EXPANSION_HIDDEN)
+ underTest.setPanelExpansion(EXPANSION_HIDDEN)
verify(repository).setPrimaryVisible(false)
verify(repository).setPrimaryShow(null)
verify(repository).setPrimaryHide(true)
@@ -167,7 +167,7 @@
@Test
fun testExpansion_startingToHide() {
`when`(repository.panelExpansionAmount.value).thenReturn(EXPANSION_VISIBLE)
- mPrimaryBouncerInteractor.setPanelExpansion(0.1f)
+ underTest.setPanelExpansion(0.1f)
verify(repository).setPrimaryStartingToHide(true)
verify(mPrimaryBouncerCallbackInteractor).dispatchStartingToHide()
}
@@ -175,7 +175,7 @@
@Test
fun testShowMessage() {
val argCaptor = ArgumentCaptor.forClass(BouncerShowMessageModel::class.java)
- mPrimaryBouncerInteractor.showMessage("abc", null)
+ underTest.showMessage("abc", null)
verify(repository).setShowMessage(argCaptor.capture())
assertThat(argCaptor.value.message).isEqualTo("abc")
}
@@ -184,62 +184,62 @@
fun testDismissAction() {
val onDismissAction = mock(ActivityStarter.OnDismissAction::class.java)
val cancelAction = mock(Runnable::class.java)
- mPrimaryBouncerInteractor.setDismissAction(onDismissAction, cancelAction)
+ underTest.setDismissAction(onDismissAction, cancelAction)
verify(bouncerViewDelegate).setDismissAction(onDismissAction, cancelAction)
}
@Test
fun testUpdateResources() {
- mPrimaryBouncerInteractor.updateResources()
+ underTest.updateResources()
verify(repository).setResourceUpdateRequests(true)
}
@Test
fun testNotifyKeyguardAuthenticated() {
- mPrimaryBouncerInteractor.notifyKeyguardAuthenticated(true)
+ underTest.notifyKeyguardAuthenticated(true)
verify(repository).setKeyguardAuthenticated(true)
}
@Test
fun testNotifyShowedMessage() {
- mPrimaryBouncerInteractor.onMessageShown()
+ underTest.onMessageShown()
verify(repository).setShowMessage(null)
}
@Test
fun testOnScreenTurnedOff() {
- mPrimaryBouncerInteractor.onScreenTurnedOff()
+ underTest.onScreenTurnedOff()
verify(repository).setOnScreenTurnedOff(true)
}
@Test
fun testSetKeyguardPosition() {
- mPrimaryBouncerInteractor.setKeyguardPosition(0f)
+ underTest.setKeyguardPosition(0f)
verify(repository).setKeyguardPosition(0f)
}
@Test
fun testNotifyKeyguardAuthenticatedHandled() {
- mPrimaryBouncerInteractor.notifyKeyguardAuthenticatedHandled()
+ underTest.notifyKeyguardAuthenticatedHandled()
verify(repository).setKeyguardAuthenticated(null)
}
@Test
fun testNotifyUpdatedResources() {
- mPrimaryBouncerInteractor.notifyUpdatedResources()
+ underTest.notifyUpdatedResources()
verify(repository).setResourceUpdateRequests(false)
}
@Test
fun testSetBackButtonEnabled() {
- mPrimaryBouncerInteractor.setBackButtonEnabled(true)
+ underTest.setBackButtonEnabled(true)
verify(repository).setIsBackButtonEnabled(true)
}
@Test
fun testStartDisappearAnimation() {
val runnable = mock(Runnable::class.java)
- mPrimaryBouncerInteractor.startDisappearAnimation(runnable)
+ underTest.startDisappearAnimation(runnable)
verify(repository).setPrimaryStartDisappearAnimation(any(Runnable::class.java))
}
@@ -248,42 +248,42 @@
`when`(repository.primaryBouncerVisible.value).thenReturn(true)
`when`(repository.panelExpansionAmount.value).thenReturn(EXPANSION_VISIBLE)
`when`(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
- assertThat(mPrimaryBouncerInteractor.isFullyShowing()).isTrue()
+ assertThat(underTest.isFullyShowing()).isTrue()
`when`(repository.primaryBouncerVisible.value).thenReturn(false)
- assertThat(mPrimaryBouncerInteractor.isFullyShowing()).isFalse()
+ assertThat(underTest.isFullyShowing()).isFalse()
}
@Test
fun testIsScrimmed() {
`when`(repository.primaryBouncerScrimmed.value).thenReturn(true)
- assertThat(mPrimaryBouncerInteractor.isScrimmed()).isTrue()
+ assertThat(underTest.isScrimmed()).isTrue()
`when`(repository.primaryBouncerScrimmed.value).thenReturn(false)
- assertThat(mPrimaryBouncerInteractor.isScrimmed()).isFalse()
+ assertThat(underTest.isScrimmed()).isFalse()
}
@Test
fun testIsInTransit() {
`when`(repository.primaryBouncerShowingSoon.value).thenReturn(true)
- assertThat(mPrimaryBouncerInteractor.isInTransit()).isTrue()
+ assertThat(underTest.isInTransit()).isTrue()
`when`(repository.primaryBouncerShowingSoon.value).thenReturn(false)
- assertThat(mPrimaryBouncerInteractor.isInTransit()).isFalse()
+ assertThat(underTest.isInTransit()).isFalse()
`when`(repository.panelExpansionAmount.value).thenReturn(0.5f)
- assertThat(mPrimaryBouncerInteractor.isInTransit()).isTrue()
+ assertThat(underTest.isInTransit()).isTrue()
}
@Test
fun testIsAnimatingAway() {
`when`(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(Runnable {})
- assertThat(mPrimaryBouncerInteractor.isAnimatingAway()).isTrue()
+ assertThat(underTest.isAnimatingAway()).isTrue()
`when`(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
- assertThat(mPrimaryBouncerInteractor.isAnimatingAway()).isFalse()
+ assertThat(underTest.isAnimatingAway()).isFalse()
}
@Test
fun testWillDismissWithAction() {
`when`(bouncerViewDelegate.willDismissWithActions()).thenReturn(true)
- assertThat(mPrimaryBouncerInteractor.willDismissWithAction()).isTrue()
+ assertThat(underTest.willDismissWithAction()).isTrue()
`when`(bouncerViewDelegate.willDismissWithActions()).thenReturn(false)
- assertThat(mPrimaryBouncerInteractor.willDismissWithAction()).isFalse()
+ assertThat(underTest.willDismissWithAction()).isFalse()
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt
new file mode 100644
index 0000000..ea7bc91
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import android.os.Looper
+import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardSecurityModel
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.DismissCallbackRegistry
+import com.android.systemui.keyguard.data.BouncerView
+import com.android.systemui.keyguard.data.BouncerViewDelegate
+import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerRepository
+import com.android.systemui.statusbar.phone.KeyguardBypassController
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.utils.os.FakeHandler
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(JUnit4::class)
+class PrimaryBouncerInteractorWithCoroutinesTest : SysuiTestCase() {
+ private lateinit var repository: FakeKeyguardBouncerRepository
+ @Mock private lateinit var bouncerView: BouncerView
+ @Mock private lateinit var bouncerViewDelegate: BouncerViewDelegate
+ @Mock private lateinit var keyguardStateController: KeyguardStateController
+ @Mock private lateinit var keyguardSecurityModel: KeyguardSecurityModel
+ @Mock private lateinit var primaryBouncerCallbackInteractor: PrimaryBouncerCallbackInteractor
+ @Mock private lateinit var falsingCollector: FalsingCollector
+ @Mock private lateinit var dismissCallbackRegistry: DismissCallbackRegistry
+ @Mock private lateinit var keyguardBypassController: KeyguardBypassController
+ @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+ private val mainHandler = FakeHandler(Looper.getMainLooper())
+ private lateinit var underTest: PrimaryBouncerInteractor
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ repository = FakeKeyguardBouncerRepository()
+ underTest =
+ PrimaryBouncerInteractor(
+ repository,
+ bouncerView,
+ mainHandler,
+ keyguardStateController,
+ keyguardSecurityModel,
+ primaryBouncerCallbackInteractor,
+ falsingCollector,
+ dismissCallbackRegistry,
+ keyguardBypassController,
+ keyguardUpdateMonitor,
+ )
+ }
+
+ @Test
+ fun notInteractableWhenExpansionIsBelow90Percent() = runTest {
+ val isInteractable = collectLastValue(underTest.isInteractable)
+
+ repository.setPrimaryVisible(true)
+ repository.setPanelExpansion(0.15f)
+
+ assertThat(isInteractable()).isFalse()
+ }
+
+ @Test
+ fun notInteractableWhenExpansionAbove90PercentButNotVisible() = runTest {
+ val isInteractable = collectLastValue(underTest.isInteractable)
+
+ repository.setPrimaryVisible(false)
+ repository.setPanelExpansion(0.05f)
+
+ assertThat(isInteractable()).isFalse()
+ }
+
+ @Test
+ fun isInteractableWhenExpansionAbove90PercentAndVisible() = runTest {
+ var isInteractable = collectLastValue(underTest.isInteractable)
+
+ repository.setPrimaryVisible(true)
+ repository.setPanelExpansion(0.09f)
+
+ assertThat(isInteractable()).isTrue()
+
+ repository.setPanelExpansion(0.12f)
+ assertThat(isInteractable()).isFalse()
+
+ repository.setPanelExpansion(0f)
+ assertThat(isInteractable()).isTrue()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowTest.kt
new file mode 100644
index 0000000..a5b78b74
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowTest.kt
@@ -0,0 +1,174 @@
+/*
+ * 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.keyguard.ui
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.google.common.truth.Truth.assertThat
+import kotlin.time.Duration.Companion.milliseconds
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@SmallTest
+@RunWith(JUnit4::class)
+class KeyguardTransitionAnimationFlowTest : SysuiTestCase() {
+ private lateinit var underTest: KeyguardTransitionAnimationFlow
+ private lateinit var repository: FakeKeyguardTransitionRepository
+
+ @Before
+ fun setUp() {
+ repository = FakeKeyguardTransitionRepository()
+ underTest =
+ KeyguardTransitionAnimationFlow(
+ 1000.milliseconds,
+ repository.transitions,
+ )
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun zeroDurationThrowsException() = runTest {
+ val flow = underTest.createFlow(duration = 0.milliseconds, onStep = { it })
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun startTimePlusDurationGreaterThanTransitionDurationThrowsException() = runTest {
+ val flow =
+ underTest.createFlow(
+ startTime = 300.milliseconds,
+ duration = 800.milliseconds,
+ onStep = { it }
+ )
+ }
+
+ @Test
+ fun onFinishRunsWhenSpecified() = runTest {
+ val flow =
+ underTest.createFlow(
+ duration = 100.milliseconds,
+ onStep = { it },
+ onFinish = { 10f },
+ )
+ var animationValues = collectLastValue(flow)
+ repository.sendTransitionStep(step(1f, TransitionState.FINISHED))
+ assertThat(animationValues()).isEqualTo(10f)
+ }
+
+ @Test
+ fun onCancelRunsWhenSpecified() = runTest {
+ val flow =
+ underTest.createFlow(
+ duration = 100.milliseconds,
+ onStep = { it },
+ onCancel = { 100f },
+ )
+ var animationValues = collectLastValue(flow)
+ repository.sendTransitionStep(step(0.5f, TransitionState.CANCELED))
+ assertThat(animationValues()).isEqualTo(100f)
+ }
+
+ @Test
+ fun usesStartTime() = runTest {
+ val flow =
+ underTest.createFlow(
+ startTime = 500.milliseconds,
+ duration = 500.milliseconds,
+ onStep = { it },
+ )
+ var animationValues = collectLastValue(flow)
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+ assertThat(animationValues()).isEqualTo(0f)
+
+ // Should not emit a value
+ repository.sendTransitionStep(step(0.1f, TransitionState.RUNNING))
+
+ repository.sendTransitionStep(step(0.5f, TransitionState.RUNNING))
+ assertFloat(animationValues(), 0f)
+ repository.sendTransitionStep(step(0.6f, TransitionState.RUNNING))
+ assertFloat(animationValues(), 0.2f)
+ repository.sendTransitionStep(step(0.8f, TransitionState.RUNNING))
+ assertFloat(animationValues(), 0.6f)
+ repository.sendTransitionStep(step(1f, TransitionState.RUNNING))
+ assertFloat(animationValues(), 1f)
+ }
+
+ @Test
+ fun usesInterpolator() = runTest {
+ val flow =
+ underTest.createFlow(
+ duration = 1000.milliseconds,
+ interpolator = EMPHASIZED_ACCELERATE,
+ onStep = { it },
+ )
+ var animationValues = collectLastValue(flow)
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+ assertFloat(animationValues(), EMPHASIZED_ACCELERATE.getInterpolation(0f))
+ repository.sendTransitionStep(step(0.5f, TransitionState.RUNNING))
+ assertFloat(animationValues(), EMPHASIZED_ACCELERATE.getInterpolation(0.5f))
+ repository.sendTransitionStep(step(0.6f, TransitionState.RUNNING))
+ assertFloat(animationValues(), EMPHASIZED_ACCELERATE.getInterpolation(0.6f))
+ repository.sendTransitionStep(step(0.8f, TransitionState.RUNNING))
+ assertFloat(animationValues(), EMPHASIZED_ACCELERATE.getInterpolation(0.8f))
+ repository.sendTransitionStep(step(1f, TransitionState.RUNNING))
+ assertFloat(animationValues(), EMPHASIZED_ACCELERATE.getInterpolation(1f))
+ }
+
+ @Test
+ fun usesOnStepToDoubleValue() = runTest {
+ val flow =
+ underTest.createFlow(
+ duration = 1000.milliseconds,
+ onStep = { it * 2 },
+ )
+ var animationValues = collectLastValue(flow)
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+ assertFloat(animationValues(), 0f)
+ repository.sendTransitionStep(step(0.3f, TransitionState.RUNNING))
+ assertFloat(animationValues(), 0.6f)
+ repository.sendTransitionStep(step(0.6f, TransitionState.RUNNING))
+ assertFloat(animationValues(), 1.2f)
+ repository.sendTransitionStep(step(0.8f, TransitionState.RUNNING))
+ assertFloat(animationValues(), 1.6f)
+ repository.sendTransitionStep(step(1f, TransitionState.RUNNING))
+ assertFloat(animationValues(), 2f)
+ }
+
+ private fun assertFloat(actual: Float?, expected: Float) {
+ assertThat(actual!!).isWithin(0.01f).of(expected)
+ }
+
+ private fun step(
+ value: Float,
+ state: TransitionState = TransitionState.RUNNING
+ ): TransitionStep {
+ return TransitionStep(
+ from = KeyguardState.GONE,
+ to = KeyguardState.DREAMING,
+ value = value,
+ transitionState = state,
+ ownerName = "GoneToDreamingTransitionViewModelTest"
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt
index 5571663..06e397d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt
@@ -18,19 +18,13 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE
-import com.android.systemui.animation.Interpolators.EMPHASIZED_DECELERATE
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.domain.interactor.FromDreamingTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.shared.model.AnimationParams
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
-import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel.Companion.DREAM_OVERLAY_ALPHA
-import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel.Companion.DREAM_OVERLAY_TRANSLATION_Y
-import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel.Companion.LOCKSCREEN_ALPHA
-import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel.Companion.LOCKSCREEN_TRANSLATION_Y
+import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
+import com.google.common.collect.Range
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
@@ -46,6 +40,7 @@
class DreamingToLockscreenTransitionViewModelTest : SysuiTestCase() {
private lateinit var underTest: DreamingToLockscreenTransitionViewModel
private lateinit var repository: FakeKeyguardTransitionRepository
+ private lateinit var transitionAnimation: KeyguardTransitionAnimationFlow
@Before
fun setUp() {
@@ -63,32 +58,18 @@
val job =
underTest.dreamOverlayTranslationY(pixels).onEach { values.add(it) }.launchIn(this)
+ // Should start running here...
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
repository.sendTransitionStep(step(0f))
repository.sendTransitionStep(step(0.3f))
repository.sendTransitionStep(step(0.5f))
+ repository.sendTransitionStep(step(0.6f))
+ // ...up to here
+ repository.sendTransitionStep(step(0.8f))
repository.sendTransitionStep(step(1f))
- // Only 3 values should be present, since the dream overlay runs for a small fraction
- // of the overall animation time
- assertThat(values.size).isEqualTo(3)
- assertThat(values[0])
- .isEqualTo(
- EMPHASIZED_ACCELERATE.getInterpolation(
- animValue(0f, DREAM_OVERLAY_TRANSLATION_Y)
- ) * pixels
- )
- assertThat(values[1])
- .isEqualTo(
- EMPHASIZED_ACCELERATE.getInterpolation(
- animValue(0.3f, DREAM_OVERLAY_TRANSLATION_Y)
- ) * pixels
- )
- assertThat(values[2])
- .isEqualTo(
- EMPHASIZED_ACCELERATE.getInterpolation(
- animValue(0.5f, DREAM_OVERLAY_TRANSLATION_Y)
- ) * pixels
- )
+ assertThat(values.size).isEqualTo(5)
+ values.forEach { assertThat(it).isIn(Range.closed(0f, 100f)) }
job.cancel()
}
@@ -100,16 +81,18 @@
val job = underTest.dreamOverlayAlpha.onEach { values.add(it) }.launchIn(this)
+ // Should start running here...
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
repository.sendTransitionStep(step(0f))
repository.sendTransitionStep(step(0.1f))
repository.sendTransitionStep(step(0.5f))
+ // ...up to here
repository.sendTransitionStep(step(1f))
// Only two values should be present, since the dream overlay runs for a small fraction
// of the overall animation time
- assertThat(values.size).isEqualTo(2)
- assertThat(values[0]).isEqualTo(1f - animValue(0f, DREAM_OVERLAY_ALPHA))
- assertThat(values[1]).isEqualTo(1f - animValue(0.1f, DREAM_OVERLAY_ALPHA))
+ assertThat(values.size).isEqualTo(4)
+ values.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) }
job.cancel()
}
@@ -121,19 +104,15 @@
val job = underTest.lockscreenAlpha.onEach { values.add(it) }.launchIn(this)
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
repository.sendTransitionStep(step(0f))
repository.sendTransitionStep(step(0.1f))
- // Should start running here...
repository.sendTransitionStep(step(0.2f))
repository.sendTransitionStep(step(0.3f))
- // ...up to here
repository.sendTransitionStep(step(1f))
- // Only two values should be present, since the dream overlay runs for a small fraction
- // of the overall animation time
- assertThat(values.size).isEqualTo(2)
- assertThat(values[0]).isEqualTo(animValue(0.2f, LOCKSCREEN_ALPHA))
- assertThat(values[1]).isEqualTo(animValue(0.3f, LOCKSCREEN_ALPHA))
+ assertThat(values.size).isEqualTo(4)
+ values.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) }
job.cancel()
}
@@ -147,58 +126,27 @@
val job =
underTest.lockscreenTranslationY(pixels).onEach { values.add(it) }.launchIn(this)
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
repository.sendTransitionStep(step(0f))
repository.sendTransitionStep(step(0.3f))
repository.sendTransitionStep(step(0.5f))
repository.sendTransitionStep(step(1f))
- assertThat(values.size).isEqualTo(4)
- assertThat(values[0])
- .isEqualTo(
- -pixels +
- EMPHASIZED_DECELERATE.getInterpolation(
- animValue(0f, LOCKSCREEN_TRANSLATION_Y)
- ) * pixels
- )
- assertThat(values[1])
- .isEqualTo(
- -pixels +
- EMPHASIZED_DECELERATE.getInterpolation(
- animValue(0.3f, LOCKSCREEN_TRANSLATION_Y)
- ) * pixels
- )
- assertThat(values[2])
- .isEqualTo(
- -pixels +
- EMPHASIZED_DECELERATE.getInterpolation(
- animValue(0.5f, LOCKSCREEN_TRANSLATION_Y)
- ) * pixels
- )
- assertThat(values[3])
- .isEqualTo(
- -pixels +
- EMPHASIZED_DECELERATE.getInterpolation(
- animValue(1f, LOCKSCREEN_TRANSLATION_Y)
- ) * pixels
- )
+ assertThat(values.size).isEqualTo(5)
+ values.forEach { assertThat(it).isIn(Range.closed(-100f, 0f)) }
job.cancel()
}
- private fun animValue(stepValue: Float, params: AnimationParams): Float {
- val totalDuration = TO_LOCKSCREEN_DURATION
- val startValue = (params.startTime / totalDuration).toFloat()
-
- val multiplier = (totalDuration / params.duration).toFloat()
- return (stepValue - startValue) * multiplier
- }
-
- private fun step(value: Float): TransitionStep {
+ private fun step(
+ value: Float,
+ state: TransitionState = TransitionState.RUNNING
+ ): TransitionStep {
return TransitionStep(
from = KeyguardState.DREAMING,
to = KeyguardState.LOCKSCREEN,
value = value,
- transitionState = TransitionState.RUNNING,
+ transitionState = state,
ownerName = "DreamingToLockscreenTransitionViewModelTest"
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt
index 7fa204b..14c3b50 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt
@@ -18,16 +18,12 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.domain.interactor.FromGoneTransitionInteractor.Companion.TO_DREAMING_DURATION
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.shared.model.AnimationParams
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
-import com.android.systemui.keyguard.ui.viewmodel.GoneToDreamingTransitionViewModel.Companion.LOCKSCREEN_ALPHA
-import com.android.systemui.keyguard.ui.viewmodel.GoneToDreamingTransitionViewModel.Companion.LOCKSCREEN_TRANSLATION_Y
+import com.google.common.collect.Range
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
@@ -59,20 +55,18 @@
val job = underTest.lockscreenAlpha.onEach { values.add(it) }.launchIn(this)
// Should start running here...
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
repository.sendTransitionStep(step(0f))
repository.sendTransitionStep(step(0.1f))
repository.sendTransitionStep(step(0.2f))
- // ...up to here
repository.sendTransitionStep(step(0.3f))
+ // ...up to here
repository.sendTransitionStep(step(1f))
// Only three values should be present, since the dream overlay runs for a small
- // fraction
- // of the overall animation time
- assertThat(values.size).isEqualTo(3)
- assertThat(values[0]).isEqualTo(1f - animValue(0f, LOCKSCREEN_ALPHA))
- assertThat(values[1]).isEqualTo(1f - animValue(0.1f, LOCKSCREEN_ALPHA))
- assertThat(values[2]).isEqualTo(1f - animValue(0.2f, LOCKSCREEN_ALPHA))
+ // fraction of the overall animation time
+ assertThat(values.size).isEqualTo(5)
+ values.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) }
job.cancel()
}
@@ -87,45 +81,19 @@
underTest.lockscreenTranslationY(pixels).onEach { values.add(it) }.launchIn(this)
// Should start running here...
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
repository.sendTransitionStep(step(0f))
repository.sendTransitionStep(step(0.3f))
repository.sendTransitionStep(step(0.5f))
- // ...up to here
- repository.sendTransitionStep(step(1f))
// And a final reset event on CANCEL
repository.sendTransitionStep(step(0.8f, TransitionState.CANCELED))
- assertThat(values.size).isEqualTo(4)
- assertThat(values[0])
- .isEqualTo(
- EMPHASIZED_ACCELERATE.getInterpolation(
- animValue(0f, LOCKSCREEN_TRANSLATION_Y)
- ) * pixels
- )
- assertThat(values[1])
- .isEqualTo(
- EMPHASIZED_ACCELERATE.getInterpolation(
- animValue(0.3f, LOCKSCREEN_TRANSLATION_Y)
- ) * pixels
- )
- assertThat(values[2])
- .isEqualTo(
- EMPHASIZED_ACCELERATE.getInterpolation(
- animValue(0.5f, LOCKSCREEN_TRANSLATION_Y)
- ) * pixels
- )
- assertThat(values[3]).isEqualTo(0f)
+ assertThat(values.size).isEqualTo(5)
+ values.forEach { assertThat(it).isIn(Range.closed(0f, 100f)) }
+
job.cancel()
}
- private fun animValue(stepValue: Float, params: AnimationParams): Float {
- val totalDuration = TO_DREAMING_DURATION
- val startValue = (params.startTime / totalDuration).toFloat()
-
- val multiplier = (totalDuration / params.duration).toFloat()
- return (stepValue - startValue) * multiplier
- }
-
private fun step(
value: Float,
state: TransitionState = TransitionState.RUNNING
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt
index 539fc2c..ed31dc3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt
@@ -18,16 +18,12 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor.Companion.TO_DREAMING_DURATION
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.shared.model.AnimationParams
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
-import com.android.systemui.keyguard.ui.viewmodel.LockscreenToDreamingTransitionViewModel.Companion.LOCKSCREEN_ALPHA
-import com.android.systemui.keyguard.ui.viewmodel.LockscreenToDreamingTransitionViewModel.Companion.LOCKSCREEN_TRANSLATION_Y
+import com.google.common.collect.Range
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
@@ -59,19 +55,18 @@
val job = underTest.lockscreenAlpha.onEach { values.add(it) }.launchIn(this)
// Should start running here...
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
repository.sendTransitionStep(step(0f))
repository.sendTransitionStep(step(0.1f))
repository.sendTransitionStep(step(0.2f))
- // ...up to here
repository.sendTransitionStep(step(0.3f))
+ // ...up to here
repository.sendTransitionStep(step(1f))
// Only three values should be present, since the dream overlay runs for a small
// fraction of the overall animation time
- assertThat(values.size).isEqualTo(3)
- assertThat(values[0]).isEqualTo(1f - animValue(0f, LOCKSCREEN_ALPHA))
- assertThat(values[1]).isEqualTo(1f - animValue(0.1f, LOCKSCREEN_ALPHA))
- assertThat(values[2]).isEqualTo(1f - animValue(0.2f, LOCKSCREEN_ALPHA))
+ assertThat(values.size).isEqualTo(5)
+ values.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) }
job.cancel()
}
@@ -85,47 +80,22 @@
val job =
underTest.lockscreenTranslationY(pixels).onEach { values.add(it) }.launchIn(this)
- // Should start running here...
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
repository.sendTransitionStep(step(0f))
repository.sendTransitionStep(step(0.3f))
repository.sendTransitionStep(step(0.5f))
- // ...up to here
repository.sendTransitionStep(step(1f))
// And a final reset event on FINISHED
repository.sendTransitionStep(step(1f, TransitionState.FINISHED))
- assertThat(values.size).isEqualTo(4)
- assertThat(values[0])
- .isEqualTo(
- EMPHASIZED_ACCELERATE.getInterpolation(
- animValue(0f, LOCKSCREEN_TRANSLATION_Y)
- ) * pixels
- )
- assertThat(values[1])
- .isEqualTo(
- EMPHASIZED_ACCELERATE.getInterpolation(
- animValue(0.3f, LOCKSCREEN_TRANSLATION_Y)
- ) * pixels
- )
- assertThat(values[2])
- .isEqualTo(
- EMPHASIZED_ACCELERATE.getInterpolation(
- animValue(0.5f, LOCKSCREEN_TRANSLATION_Y)
- ) * pixels
- )
- assertThat(values[3]).isEqualTo(0f)
+ assertThat(values.size).isEqualTo(6)
+ values.forEach { assertThat(it).isIn(Range.closed(0f, 100f)) }
+ // Validate finished value
+ assertThat(values[5]).isEqualTo(0f)
job.cancel()
}
- private fun animValue(stepValue: Float, params: AnimationParams): Float {
- val totalDuration = TO_DREAMING_DURATION
- val startValue = (params.startTime / totalDuration).toFloat()
-
- val multiplier = (totalDuration / params.duration).toFloat()
- return (stepValue - startValue) * multiplier
- }
-
private fun step(
value: Float,
state: TransitionState = TransitionState.RUNNING
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt
index 759345f..458b315 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt
@@ -18,16 +18,12 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor.Companion.TO_OCCLUDED_DURATION
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.shared.model.AnimationParams
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
-import com.android.systemui.keyguard.ui.viewmodel.LockscreenToOccludedTransitionViewModel.Companion.LOCKSCREEN_ALPHA
-import com.android.systemui.keyguard.ui.viewmodel.LockscreenToOccludedTransitionViewModel.Companion.LOCKSCREEN_TRANSLATION_Y
+import com.google.common.collect.Range
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
@@ -59,19 +55,18 @@
val job = underTest.lockscreenAlpha.onEach { values.add(it) }.launchIn(this)
// Should start running here...
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
repository.sendTransitionStep(step(0f))
repository.sendTransitionStep(step(0.1f))
repository.sendTransitionStep(step(0.4f))
- // ...up to here
repository.sendTransitionStep(step(0.7f))
+ // ...up to here
repository.sendTransitionStep(step(1f))
// Only 3 values should be present, since the dream overlay runs for a small fraction
// of the overall animation time
- assertThat(values.size).isEqualTo(3)
- assertThat(values[0]).isEqualTo(1f - animValue(0f, LOCKSCREEN_ALPHA))
- assertThat(values[1]).isEqualTo(1f - animValue(0.1f, LOCKSCREEN_ALPHA))
- assertThat(values[2]).isEqualTo(1f - animValue(0.4f, LOCKSCREEN_ALPHA))
+ assertThat(values.size).isEqualTo(5)
+ values.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) }
job.cancel()
}
@@ -86,54 +81,51 @@
underTest.lockscreenTranslationY(pixels).onEach { values.add(it) }.launchIn(this)
// Should start running here...
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
repository.sendTransitionStep(step(0f))
repository.sendTransitionStep(step(0.3f))
repository.sendTransitionStep(step(0.5f))
repository.sendTransitionStep(step(1f))
// ...up to here
- assertThat(values.size).isEqualTo(4)
- assertThat(values[0])
- .isEqualTo(
- EMPHASIZED_ACCELERATE.getInterpolation(
- animValue(0f, LOCKSCREEN_TRANSLATION_Y)
- ) * pixels
- )
- assertThat(values[1])
- .isEqualTo(
- EMPHASIZED_ACCELERATE.getInterpolation(
- animValue(0.3f, LOCKSCREEN_TRANSLATION_Y)
- ) * pixels
- )
- assertThat(values[2])
- .isEqualTo(
- EMPHASIZED_ACCELERATE.getInterpolation(
- animValue(0.5f, LOCKSCREEN_TRANSLATION_Y)
- ) * pixels
- )
- assertThat(values[3])
- .isEqualTo(
- EMPHASIZED_ACCELERATE.getInterpolation(
- animValue(1f, LOCKSCREEN_TRANSLATION_Y)
- ) * pixels
- )
+ assertThat(values.size).isEqualTo(5)
+ values.forEach { assertThat(it).isIn(Range.closed(0f, 100f)) }
+
job.cancel()
}
- private fun animValue(stepValue: Float, params: AnimationParams): Float {
- val totalDuration = TO_OCCLUDED_DURATION
- val startValue = (params.startTime / totalDuration).toFloat()
+ @Test
+ fun lockscreenTranslationYIsCanceled() =
+ runTest(UnconfinedTestDispatcher()) {
+ val values = mutableListOf<Float>()
- val multiplier = (totalDuration / params.duration).toFloat()
- return (stepValue - startValue) * multiplier
- }
+ val pixels = 100
+ val job =
+ underTest.lockscreenTranslationY(pixels).onEach { values.add(it) }.launchIn(this)
- private fun step(value: Float): TransitionStep {
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+ repository.sendTransitionStep(step(0f))
+ repository.sendTransitionStep(step(0.3f))
+ repository.sendTransitionStep(step(0.3f, TransitionState.CANCELED))
+
+ assertThat(values.size).isEqualTo(4)
+ values.forEach { assertThat(it).isIn(Range.closed(0f, 100f)) }
+
+ // Cancel will reset the translation
+ assertThat(values[3]).isEqualTo(0)
+
+ job.cancel()
+ }
+
+ private fun step(
+ value: Float,
+ state: TransitionState = TransitionState.RUNNING,
+ ): TransitionStep {
return TransitionStep(
from = KeyguardState.LOCKSCREEN,
to = KeyguardState.OCCLUDED,
value = value,
- transitionState = TransitionState.RUNNING,
+ transitionState = state,
ownerName = "LockscreenToOccludedTransitionViewModelTest"
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt
index 98d292d..a36214e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt
@@ -18,16 +18,12 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.animation.Interpolators.EMPHASIZED_DECELERATE
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.domain.interactor.FromOccludedTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.shared.model.AnimationParams
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
-import com.android.systemui.keyguard.ui.viewmodel.OccludedToLockscreenTransitionViewModel.Companion.LOCKSCREEN_ALPHA
-import com.android.systemui.keyguard.ui.viewmodel.OccludedToLockscreenTransitionViewModel.Companion.LOCKSCREEN_TRANSLATION_Y
+import com.google.common.collect.Range
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
@@ -58,21 +54,19 @@
val job = underTest.lockscreenAlpha.onEach { values.add(it) }.launchIn(this)
- repository.sendTransitionStep(step(0f))
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+ repository.sendTransitionStep(step(0.1f))
// Should start running here...
repository.sendTransitionStep(step(0.3f))
repository.sendTransitionStep(step(0.4f))
repository.sendTransitionStep(step(0.5f))
- // ...up to here
repository.sendTransitionStep(step(0.6f))
+ // ...up to here
+ repository.sendTransitionStep(step(0.8f))
repository.sendTransitionStep(step(1f))
- // Only two values should be present, since the dream overlay runs for a small fraction
- // of the overall animation time
- assertThat(values.size).isEqualTo(3)
- assertThat(values[0]).isEqualTo(animValue(0.3f, LOCKSCREEN_ALPHA))
- assertThat(values[1]).isEqualTo(animValue(0.4f, LOCKSCREEN_ALPHA))
- assertThat(values[2]).isEqualTo(animValue(0.5f, LOCKSCREEN_ALPHA))
+ assertThat(values.size).isEqualTo(5)
+ values.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) }
job.cancel()
}
@@ -86,58 +80,27 @@
val job =
underTest.lockscreenTranslationY(pixels).onEach { values.add(it) }.launchIn(this)
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
repository.sendTransitionStep(step(0f))
repository.sendTransitionStep(step(0.3f))
repository.sendTransitionStep(step(0.5f))
repository.sendTransitionStep(step(1f))
- assertThat(values.size).isEqualTo(4)
- assertThat(values[0])
- .isEqualTo(
- -pixels +
- EMPHASIZED_DECELERATE.getInterpolation(
- animValue(0f, LOCKSCREEN_TRANSLATION_Y)
- ) * pixels
- )
- assertThat(values[1])
- .isEqualTo(
- -pixels +
- EMPHASIZED_DECELERATE.getInterpolation(
- animValue(0.3f, LOCKSCREEN_TRANSLATION_Y)
- ) * pixels
- )
- assertThat(values[2])
- .isEqualTo(
- -pixels +
- EMPHASIZED_DECELERATE.getInterpolation(
- animValue(0.5f, LOCKSCREEN_TRANSLATION_Y)
- ) * pixels
- )
- assertThat(values[3])
- .isEqualTo(
- -pixels +
- EMPHASIZED_DECELERATE.getInterpolation(
- animValue(1f, LOCKSCREEN_TRANSLATION_Y)
- ) * pixels
- )
+ assertThat(values.size).isEqualTo(5)
+ values.forEach { assertThat(it).isIn(Range.closed(-100f, 0f)) }
job.cancel()
}
- private fun animValue(stepValue: Float, params: AnimationParams): Float {
- val totalDuration = TO_LOCKSCREEN_DURATION
- val startValue = (params.startTime / totalDuration).toFloat()
-
- val multiplier = (totalDuration / params.duration).toFloat()
- return (stepValue - startValue) * multiplier
- }
-
- private fun step(value: Float): TransitionStep {
+ private fun step(
+ value: Float,
+ state: TransitionState = TransitionState.RUNNING
+ ): TransitionStep {
return TransitionStep(
from = KeyguardState.OCCLUDED,
to = KeyguardState.LOCKSCREEN,
value = value,
- transitionState = TransitionState.RUNNING,
+ transitionState = state,
ownerName = "OccludedToLockscreenTransitionViewModelTest"
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataCombineLatestTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataCombineLatestTest.java
index c0639f3..0a5b124 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataCombineLatestTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataCombineLatestTest.java
@@ -79,7 +79,7 @@
USER_ID, true, APP, null, ARTIST, TITLE, null,
new ArrayList<>(), new ArrayList<>(), null, PACKAGE, null, null, null, true, null,
MediaData.PLAYBACK_LOCAL, false, KEY, false, false, false, 0L,
- InstanceId.fakeInstanceId(-1), -1, false);
+ InstanceId.fakeInstanceId(-1), -1, false, null);
mDeviceData = new MediaDeviceData(true, null, DEVICE_NAME, null, false);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt
index 1ac6695..53cc78f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt
@@ -644,27 +644,8 @@
build()
}
val currentTime = clock.elapsedRealtime()
- mediaDataManager.addResumptionControls(
- USER_ID,
- desc,
- Runnable {},
- session.sessionToken,
- APP_NAME,
- pendingIntent,
- PACKAGE_NAME
- )
- assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
- assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
- // THEN the media data indicates that it is for resumption
- verify(listener)
- .onMediaDataLoaded(
- eq(PACKAGE_NAME),
- eq(null),
- capture(mediaDataCaptor),
- eq(true),
- eq(0),
- eq(false)
- )
+ addResumeControlAndLoad(desc)
+
val data = mediaDataCaptor.value
assertThat(data.resumption).isTrue()
assertThat(data.song).isEqualTo(SESSION_TITLE)
@@ -690,27 +671,8 @@
build()
}
val currentTime = clock.elapsedRealtime()
- mediaDataManager.addResumptionControls(
- USER_ID,
- desc,
- Runnable {},
- session.sessionToken,
- APP_NAME,
- pendingIntent,
- PACKAGE_NAME
- )
- assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
- assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
- // THEN the media data indicates that it is for resumption
- verify(listener)
- .onMediaDataLoaded(
- eq(PACKAGE_NAME),
- eq(null),
- capture(mediaDataCaptor),
- eq(true),
- eq(0),
- eq(false)
- )
+ addResumeControlAndLoad(desc)
+
val data = mediaDataCaptor.value
assertThat(data.resumption).isTrue()
assertThat(data.song).isEqualTo(SESSION_TITLE)
@@ -723,6 +685,84 @@
}
@Test
+ fun testAddResumptionControls_hasPartialProgress() {
+ whenever(mediaFlags.isResumeProgressEnabled()).thenReturn(true)
+
+ // WHEN resumption controls are added with partial progress
+ val progress = 0.5
+ val extras =
+ Bundle().apply {
+ putInt(
+ MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS,
+ MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED
+ )
+ putDouble(MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE, progress)
+ }
+ val desc =
+ MediaDescription.Builder().run {
+ setTitle(SESSION_TITLE)
+ setExtras(extras)
+ build()
+ }
+ addResumeControlAndLoad(desc)
+
+ val data = mediaDataCaptor.value
+ assertThat(data.resumption).isTrue()
+ assertThat(data.resumeProgress).isEqualTo(progress)
+ }
+
+ @Test
+ fun testAddResumptionControls_hasNotPlayedProgress() {
+ whenever(mediaFlags.isResumeProgressEnabled()).thenReturn(true)
+
+ // WHEN resumption controls are added that have not been played
+ val extras =
+ Bundle().apply {
+ putInt(
+ MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS,
+ MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_NOT_PLAYED
+ )
+ }
+ val desc =
+ MediaDescription.Builder().run {
+ setTitle(SESSION_TITLE)
+ setExtras(extras)
+ build()
+ }
+ addResumeControlAndLoad(desc)
+
+ val data = mediaDataCaptor.value
+ assertThat(data.resumption).isTrue()
+ assertThat(data.resumeProgress).isEqualTo(0)
+ }
+
+ @Test
+ fun testAddResumptionControls_hasFullProgress() {
+ whenever(mediaFlags.isResumeProgressEnabled()).thenReturn(true)
+
+ // WHEN resumption controls are added with progress info
+ val extras =
+ Bundle().apply {
+ putInt(
+ MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS,
+ MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_FULLY_PLAYED
+ )
+ }
+ val desc =
+ MediaDescription.Builder().run {
+ setTitle(SESSION_TITLE)
+ setExtras(extras)
+ build()
+ }
+ addResumeControlAndLoad(desc)
+
+ // THEN the media data includes the progress
+ val data = mediaDataCaptor.value
+ assertThat(data.resumption).isTrue()
+ assertThat(data.resumeProgress).isEqualTo(1)
+ }
+
+ @Test
fun testResumptionDisabled_dismissesResumeControls() {
// WHEN there are resume controls and resumption is switched off
val desc =
@@ -730,26 +770,8 @@
setTitle(SESSION_TITLE)
build()
}
- mediaDataManager.addResumptionControls(
- USER_ID,
- desc,
- Runnable {},
- session.sessionToken,
- APP_NAME,
- pendingIntent,
- PACKAGE_NAME
- )
- assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
- assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
- verify(listener)
- .onMediaDataLoaded(
- eq(PACKAGE_NAME),
- eq(null),
- capture(mediaDataCaptor),
- eq(true),
- eq(0),
- eq(false)
- )
+ addResumeControlAndLoad(desc)
+
val data = mediaDataCaptor.value
mediaDataManager.setMediaResumptionEnabled(false)
@@ -1690,4 +1712,29 @@
stateBuilder.setState(PlaybackState.STATE_PAUSED, 0, 1.0f)
whenever(controller.playbackState).thenReturn(stateBuilder.build())
}
+
+ /** Helper function to add a resumption control and capture the resulting MediaData */
+ private fun addResumeControlAndLoad(desc: MediaDescription) {
+ mediaDataManager.addResumptionControls(
+ USER_ID,
+ desc,
+ Runnable {},
+ session.sessionToken,
+ APP_NAME,
+ pendingIntent,
+ PACKAGE_NAME
+ )
+ assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
+ assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+
+ verify(listener)
+ .onMediaDataLoaded(
+ eq(PACKAGE_NAME),
+ eq(null),
+ capture(mediaDataCaptor),
+ eq(true),
+ eq(0),
+ eq(false)
+ )
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
index ce22b19..26b9204 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
@@ -906,6 +906,17 @@
}
@Test
+ fun bind_resumeState_withProgress() {
+ val progress = 0.5
+ val state = mediaData.copy(resumption = true, resumeProgress = progress)
+
+ player.attachPlayer(viewHolder)
+ player.bindPlayer(state, PACKAGE)
+
+ verify(seekBarViewModel).updateStaticProgress(progress)
+ }
+
+ @Test
fun bindNotificationActions() {
val icon = context.getDrawable(android.R.drawable.ic_media_play)
val bg = context.getDrawable(R.drawable.qs_media_round_button_background)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt
index c63ca3d..ef10e40 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt
@@ -45,6 +45,7 @@
import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.temporarydisplay.TemporaryViewDisplayController
+import com.android.systemui.temporarydisplay.chipbar.ChipbarAnimator
import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
import com.android.systemui.temporarydisplay.chipbar.ChipbarLogger
import com.android.systemui.temporarydisplay.chipbar.SwipeChipbarAwayGestureHandler
@@ -145,6 +146,7 @@
configurationController,
dumpManager,
powerManager,
+ ChipbarAnimator(),
falsingManager,
falsingCollector,
swipeHandler,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/FloatingRotationButtonPositionCalculatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/FloatingRotationButtonPositionCalculatorTest.kt
index 36e02cb..bc67df6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/FloatingRotationButtonPositionCalculatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/FloatingRotationButtonPositionCalculatorTest.kt
@@ -16,40 +16,50 @@
internal class FloatingRotationButtonPositionCalculatorTest(private val testCase: TestCase)
: SysuiTestCase() {
- private val calculator = FloatingRotationButtonPositionCalculator(
- MARGIN_DEFAULT, MARGIN_TASKBAR_LEFT, MARGIN_TASKBAR_BOTTOM
- )
-
@Test
fun calculatePosition() {
- val position = calculator.calculatePosition(
+ val position = testCase.calculator.calculatePosition(
testCase.rotation,
testCase.taskbarVisible,
testCase.taskbarStashed
)
-
assertThat(position).isEqualTo(testCase.expectedPosition)
}
internal class TestCase(
+ val calculator: FloatingRotationButtonPositionCalculator,
val rotation: Int,
val taskbarVisible: Boolean,
val taskbarStashed: Boolean,
val expectedPosition: Position
) {
override fun toString(): String =
- "when rotation = $rotation, " +
+ "when calculator = $calculator, " +
+ "rotation = $rotation, " +
"taskbarVisible = $taskbarVisible, " +
"taskbarStashed = $taskbarStashed - " +
"expected $expectedPosition"
}
companion object {
+ private const val MARGIN_DEFAULT = 10
+ private const val MARGIN_TASKBAR_LEFT = 20
+ private const val MARGIN_TASKBAR_BOTTOM = 30
+
+ private val posLeftCalculator = FloatingRotationButtonPositionCalculator(
+ MARGIN_DEFAULT, MARGIN_TASKBAR_LEFT, MARGIN_TASKBAR_BOTTOM, true
+ )
+ private val posRightCalculator = FloatingRotationButtonPositionCalculator(
+ MARGIN_DEFAULT, MARGIN_TASKBAR_LEFT, MARGIN_TASKBAR_BOTTOM, false
+ )
+
@Parameterized.Parameters(name = "{0}")
@JvmStatic
fun getParams(): Collection<TestCase> =
listOf(
+ // Position left
TestCase(
+ calculator = posLeftCalculator,
rotation = Surface.ROTATION_0,
taskbarVisible = false,
taskbarStashed = false,
@@ -60,6 +70,7 @@
)
),
TestCase(
+ calculator = posLeftCalculator,
rotation = Surface.ROTATION_90,
taskbarVisible = false,
taskbarStashed = false,
@@ -70,6 +81,7 @@
)
),
TestCase(
+ calculator = posLeftCalculator,
rotation = Surface.ROTATION_180,
taskbarVisible = false,
taskbarStashed = false,
@@ -80,6 +92,7 @@
)
),
TestCase(
+ calculator = posLeftCalculator,
rotation = Surface.ROTATION_270,
taskbarVisible = false,
taskbarStashed = false,
@@ -90,6 +103,7 @@
)
),
TestCase(
+ calculator = posLeftCalculator,
rotation = Surface.ROTATION_0,
taskbarVisible = true,
taskbarStashed = false,
@@ -100,6 +114,7 @@
)
),
TestCase(
+ calculator = posLeftCalculator,
rotation = Surface.ROTATION_0,
taskbarVisible = true,
taskbarStashed = true,
@@ -110,6 +125,7 @@
)
),
TestCase(
+ calculator = posLeftCalculator,
rotation = Surface.ROTATION_90,
taskbarVisible = true,
taskbarStashed = false,
@@ -118,11 +134,86 @@
translationX = -MARGIN_TASKBAR_LEFT,
translationY = -MARGIN_TASKBAR_BOTTOM
)
+ ),
+
+ // Position right
+ TestCase(
+ calculator = posRightCalculator,
+ rotation = Surface.ROTATION_0,
+ taskbarVisible = false,
+ taskbarStashed = false,
+ expectedPosition = Position(
+ gravity = Gravity.BOTTOM or Gravity.RIGHT,
+ translationX = -MARGIN_DEFAULT,
+ translationY = -MARGIN_DEFAULT
+ )
+ ),
+ TestCase(
+ calculator = posRightCalculator,
+ rotation = Surface.ROTATION_90,
+ taskbarVisible = false,
+ taskbarStashed = false,
+ expectedPosition = Position(
+ gravity = Gravity.TOP or Gravity.RIGHT,
+ translationX = -MARGIN_DEFAULT,
+ translationY = MARGIN_DEFAULT
+ )
+ ),
+ TestCase(
+ calculator = posRightCalculator,
+ rotation = Surface.ROTATION_180,
+ taskbarVisible = false,
+ taskbarStashed = false,
+ expectedPosition = Position(
+ gravity = Gravity.TOP or Gravity.LEFT,
+ translationX = MARGIN_DEFAULT,
+ translationY = MARGIN_DEFAULT
+ )
+ ),
+ TestCase(
+ calculator = posRightCalculator,
+ rotation = Surface.ROTATION_270,
+ taskbarVisible = false,
+ taskbarStashed = false,
+ expectedPosition = Position(
+ gravity = Gravity.BOTTOM or Gravity.LEFT,
+ translationX = MARGIN_DEFAULT,
+ translationY = -MARGIN_DEFAULT
+ )
+ ),
+ TestCase(
+ calculator = posRightCalculator,
+ rotation = Surface.ROTATION_0,
+ taskbarVisible = true,
+ taskbarStashed = false,
+ expectedPosition = Position(
+ gravity = Gravity.BOTTOM or Gravity.RIGHT,
+ translationX = -MARGIN_TASKBAR_LEFT,
+ translationY = -MARGIN_TASKBAR_BOTTOM
+ )
+ ),
+ TestCase(
+ calculator = posRightCalculator,
+ rotation = Surface.ROTATION_0,
+ taskbarVisible = true,
+ taskbarStashed = true,
+ expectedPosition = Position(
+ gravity = Gravity.BOTTOM or Gravity.RIGHT,
+ translationX = -MARGIN_DEFAULT,
+ translationY = -MARGIN_DEFAULT
+ )
+ ),
+ TestCase(
+ calculator = posRightCalculator,
+ rotation = Surface.ROTATION_90,
+ taskbarVisible = true,
+ taskbarStashed = false,
+ expectedPosition = Position(
+ gravity = Gravity.TOP or Gravity.RIGHT,
+ translationX = -MARGIN_TASKBAR_LEFT,
+ translationY = MARGIN_TASKBAR_BOTTOM
+ )
)
)
-
- private const val MARGIN_DEFAULT = 10
- private const val MARGIN_TASKBAR_LEFT = 20
- private const val MARGIN_TASKBAR_BOTTOM = 30
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileTest.java
index 80c39cf..addca9d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileTest.java
@@ -37,10 +37,12 @@
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.qs.QSTileHost;
import com.android.systemui.qs.logging.QSLogger;
+import com.android.systemui.qs.tileimpl.QSTileImpl;
import com.android.systemui.qs.tiles.dialog.InternetDialogFactory;
import com.android.systemui.statusbar.connectivity.AccessPointController;
import com.android.systemui.statusbar.connectivity.IconState;
import com.android.systemui.statusbar.connectivity.NetworkController;
+import com.android.systemui.statusbar.connectivity.WifiIndicators;
import org.junit.Before;
import org.junit.Test;
@@ -135,4 +137,24 @@
assertThat(mTile.getState().secondaryLabel)
.isNotEqualTo(mContext.getString(R.string.status_bar_airplane));
}
+
+ @Test
+ public void setIsAirplaneMode_APM_enabled_after_wifi_disconnected() {
+ WifiIndicators wifiIndicators = new WifiIndicators(
+ /* enabled= */ true,
+ /* statusIcon= */ null,
+ /* qsIcon= */ null,
+ /* activityIn= */ false,
+ /* activityOut= */ false,
+ /* description= */ null,
+ /* isTransient= */ false,
+ /* statusLabel= */ null
+ );
+ mTile.mSignalCallback.setWifiIndicators(wifiIndicators);
+ IconState state = new IconState(true, 0, "");
+ mTile.mSignalCallback.setIsAirplaneMode(state);
+ mTestableLooper.processAllMessages();
+ assertThat(mTile.getState().icon).isEqualTo(
+ QSTileImpl.ResourceIcon.get(R.drawable.ic_qs_no_internet_unavailable));
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/MessageContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/MessageContainerControllerTest.kt
new file mode 100644
index 0000000..9f0a803
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/MessageContainerControllerTest.kt
@@ -0,0 +1,143 @@
+package com.android.systemui.screenshot
+
+import android.graphics.drawable.Drawable
+import android.os.UserHandle
+import android.testing.AndroidTestingRunner
+import android.view.View
+import android.view.ViewGroup
+import android.widget.FrameLayout
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.constraintlayout.widget.Guideline
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.whenever
+import junit.framework.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class MessageContainerControllerTest : SysuiTestCase() {
+ lateinit var messageContainer: MessageContainerController
+
+ @Mock lateinit var workProfileMessageController: WorkProfileMessageController
+
+ @Mock lateinit var screenshotDetectionController: ScreenshotDetectionController
+
+ @Mock lateinit var icon: Drawable
+
+ lateinit var workProfileFirstRunView: ViewGroup
+ lateinit var detectionNoticeView: ViewGroup
+ lateinit var container: FrameLayout
+
+ var featureFlags = FakeFeatureFlags()
+ lateinit var screenshotView: ViewGroup
+
+ val userHandle = UserHandle.of(5)
+ val screenshotData = ScreenshotData.forTesting()
+
+ val appName = "app name"
+ lateinit var workProfileData: WorkProfileMessageController.WorkProfileFirstRunData
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ messageContainer =
+ MessageContainerController(
+ workProfileMessageController,
+ screenshotDetectionController,
+ featureFlags
+ )
+ screenshotView = ConstraintLayout(mContext)
+ workProfileData = WorkProfileMessageController.WorkProfileFirstRunData(appName, icon)
+
+ val guideline = Guideline(mContext)
+ guideline.id = com.android.systemui.R.id.guideline
+ screenshotView.addView(guideline)
+
+ container = FrameLayout(mContext)
+ container.id = com.android.systemui.R.id.screenshot_message_container
+ screenshotView.addView(container)
+
+ workProfileFirstRunView = FrameLayout(mContext)
+ workProfileFirstRunView.id = com.android.systemui.R.id.work_profile_first_run
+ container.addView(workProfileFirstRunView)
+
+ detectionNoticeView = FrameLayout(mContext)
+ detectionNoticeView.id = com.android.systemui.R.id.screenshot_detection_notice
+ container.addView(detectionNoticeView)
+
+ messageContainer.setView(screenshotView)
+
+ screenshotData.userHandle = userHandle
+ }
+
+ @Test
+ fun testOnScreenshotTakenUserHandle_noWorkProfileFirstRun() {
+ featureFlags.set(Flags.SCREENSHOT_WORK_PROFILE_POLICY, true)
+ // (just being explicit here)
+ whenever(workProfileMessageController.onScreenshotTaken(eq(userHandle))).thenReturn(null)
+
+ messageContainer.onScreenshotTaken(userHandle)
+
+ verify(workProfileMessageController, never()).populateView(any(), any(), any())
+ }
+
+ @Test
+ fun testOnScreenshotTakenUserHandle_noWorkProfileFlag() {
+ featureFlags.set(Flags.SCREENSHOT_WORK_PROFILE_POLICY, false)
+
+ messageContainer.onScreenshotTaken(userHandle)
+
+ verify(workProfileMessageController, never()).onScreenshotTaken(any())
+ verify(workProfileMessageController, never()).populateView(any(), any(), any())
+ }
+
+ @Test
+ fun testOnScreenshotTakenUserHandle_withWorkProfileFirstRun() {
+ featureFlags.set(Flags.SCREENSHOT_WORK_PROFILE_POLICY, true)
+ whenever(workProfileMessageController.onScreenshotTaken(eq(userHandle)))
+ .thenReturn(workProfileData)
+ messageContainer.onScreenshotTaken(userHandle)
+
+ verify(workProfileMessageController)
+ .populateView(eq(workProfileFirstRunView), eq(workProfileData), any())
+ assertEquals(View.VISIBLE, workProfileFirstRunView.visibility)
+ assertEquals(View.GONE, detectionNoticeView.visibility)
+ }
+
+ @Test
+ fun testOnScreenshotTakenScreenshotData_flagsOff() {
+ featureFlags.set(Flags.SCREENSHOT_WORK_PROFILE_POLICY, false)
+ featureFlags.set(Flags.SCREENSHOT_DETECTION, false)
+
+ messageContainer.onScreenshotTaken(screenshotData)
+
+ verify(workProfileMessageController, never()).onScreenshotTaken(any())
+ verify(screenshotDetectionController, never()).maybeNotifyOfScreenshot(any())
+
+ assertEquals(View.GONE, container.visibility)
+ }
+
+ @Test
+ fun testOnScreenshotTakenScreenshotData_nothingToShow() {
+ featureFlags.set(Flags.SCREENSHOT_WORK_PROFILE_POLICY, true)
+ featureFlags.set(Flags.SCREENSHOT_DETECTION, true)
+
+ messageContainer.onScreenshotTaken(screenshotData)
+
+ verify(workProfileMessageController, never()).populateView(any(), any(), any())
+ verify(screenshotDetectionController, never()).populateView(any(), any())
+
+ assertEquals(View.GONE, container.visibility)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/WorkProfileMessageControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/WorkProfileMessageControllerTest.java
index e8905ab..3440f91 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/WorkProfileMessageControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/WorkProfileMessageControllerTest.java
@@ -16,13 +16,11 @@
package com.android.systemui.screenshot;
-import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.ArgumentMatchers.nullable;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.ComponentName;
@@ -33,28 +31,35 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.testing.AndroidTestingRunner;
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
import androidx.test.filters.SmallTest;
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
import com.android.systemui.util.FakeSharedPreferences;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
import org.mockito.ArgumentMatchers;
-import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import kotlin.Unit;
+
@SmallTest
@RunWith(AndroidTestingRunner.class)
-public class WorkProfileMessageControllerTest {
+public class WorkProfileMessageControllerTest extends SysuiTestCase {
private static final String DEFAULT_LABEL = "default label";
- private static final String BADGED_DEFAULT_LABEL = "badged default label";
private static final String APP_LABEL = "app label";
- private static final String BADGED_APP_LABEL = "badged app label";
private static final UserHandle NON_WORK_USER = UserHandle.of(0);
private static final UserHandle WORK_USER = UserHandle.of(10);
@@ -63,17 +68,13 @@
@Mock
private PackageManager mPackageManager;
@Mock
- private Context mContext;
- @Mock
- private MessageContainerController mMessageDisplay;
+ private Context mMockContext;
@Mock
private Drawable mActivityIcon;
@Mock
private Drawable mBadgedActivityIcon;
@Mock
private ActivityInfo mActivityInfo;
- @Captor
- private ArgumentCaptor<Runnable> mRunnableArgumentCaptor;
private FakeSharedPreferences mSharedPreferences = new FakeSharedPreferences();
@@ -84,14 +85,10 @@
MockitoAnnotations.initMocks(this);
when(mUserManager.isManagedProfile(eq(WORK_USER.getIdentifier()))).thenReturn(true);
- when(mContext.getSharedPreferences(
+ when(mMockContext.getSharedPreferences(
eq(WorkProfileMessageController.SHARED_PREFERENCES_NAME),
eq(Context.MODE_PRIVATE))).thenReturn(mSharedPreferences);
- when(mContext.getString(ArgumentMatchers.anyInt())).thenReturn(DEFAULT_LABEL);
- when(mPackageManager.getUserBadgedLabel(eq(DEFAULT_LABEL), any()))
- .thenReturn(BADGED_DEFAULT_LABEL);
- when(mPackageManager.getUserBadgedLabel(eq(APP_LABEL), any()))
- .thenReturn(BADGED_APP_LABEL);
+ when(mMockContext.getString(ArgumentMatchers.anyInt())).thenReturn(DEFAULT_LABEL);
when(mPackageManager.getActivityIcon(any(ComponentName.class)))
.thenReturn(mActivityIcon);
when(mPackageManager.getUserBadgedIcon(
@@ -103,16 +100,13 @@
mSharedPreferences.edit().putBoolean(
WorkProfileMessageController.PREFERENCE_KEY, false).apply();
- mMessageController = new WorkProfileMessageController(mContext, mUserManager,
+ mMessageController = new WorkProfileMessageController(mMockContext, mUserManager,
mPackageManager);
}
@Test
public void testOnScreenshotTaken_notManaged() {
- mMessageController.onScreenshotTaken(NON_WORK_USER, mMessageDisplay);
-
- verify(mMessageDisplay, never())
- .showWorkProfileMessage(any(), nullable(Drawable.class), any());
+ assertNull(mMessageController.onScreenshotTaken(NON_WORK_USER));
}
@Test
@@ -120,10 +114,7 @@
mSharedPreferences.edit().putBoolean(
WorkProfileMessageController.PREFERENCE_KEY, true).apply();
- mMessageController.onScreenshotTaken(WORK_USER, mMessageDisplay);
-
- verify(mMessageDisplay, never())
- .showWorkProfileMessage(any(), nullable(Drawable.class), any());
+ assertNull(mMessageController.onScreenshotTaken(WORK_USER));
}
@Test
@@ -133,28 +124,45 @@
any(PackageManager.ComponentInfoFlags.class))).thenThrow(
new PackageManager.NameNotFoundException());
- mMessageController.onScreenshotTaken(WORK_USER, mMessageDisplay);
+ WorkProfileMessageController.WorkProfileFirstRunData data =
+ mMessageController.onScreenshotTaken(WORK_USER);
- verify(mMessageDisplay).showWorkProfileMessage(
- eq(BADGED_DEFAULT_LABEL), eq(null), any());
+ assertEquals(DEFAULT_LABEL, data.getAppName());
+ assertNull(data.getIcon());
}
@Test
public void testOnScreenshotTaken() {
- mMessageController.onScreenshotTaken(WORK_USER, mMessageDisplay);
+ WorkProfileMessageController.WorkProfileFirstRunData data =
+ mMessageController.onScreenshotTaken(WORK_USER);
- verify(mMessageDisplay).showWorkProfileMessage(
- eq(BADGED_APP_LABEL), eq(mBadgedActivityIcon), mRunnableArgumentCaptor.capture());
+ assertEquals(APP_LABEL, data.getAppName());
+ assertEquals(mBadgedActivityIcon, data.getIcon());
+ }
- // Dismiss hasn't been tapped, preference untouched.
- assertFalse(
- mSharedPreferences.getBoolean(WorkProfileMessageController.PREFERENCE_KEY, false));
+ @Test
+ public void testPopulateView() throws InterruptedException {
+ ViewGroup layout = (ViewGroup) LayoutInflater.from(mContext).inflate(
+ R.layout.screenshot_work_profile_first_run, null);
+ WorkProfileMessageController.WorkProfileFirstRunData data =
+ new WorkProfileMessageController.WorkProfileFirstRunData(APP_LABEL,
+ mBadgedActivityIcon);
+ final CountDownLatch countdown = new CountDownLatch(1);
+ mMessageController.populateView(layout, data, () -> {
+ countdown.countDown();
+ return Unit.INSTANCE;
+ });
- mRunnableArgumentCaptor.getValue().run();
+ ImageView image = layout.findViewById(R.id.screenshot_message_icon);
+ assertEquals(mBadgedActivityIcon, image.getDrawable());
+ TextView text = layout.findViewById(R.id.screenshot_message_content);
+ // The app name is used in a template, but at least validate that it was inserted.
+ assertTrue(text.getText().toString().contains(APP_LABEL));
- // After dismiss has been tapped, the setting should be updated.
- assertTrue(
- mSharedPreferences.getBoolean(WorkProfileMessageController.PREFERENCE_KEY, false));
+ // Validate that clicking the dismiss button calls back properly.
+ assertEquals(1, countdown.getCount());
+ layout.findViewById(R.id.message_dismiss_button).callOnClick();
+ countdown.await(1000, TimeUnit.MILLISECONDS);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
index 4c76825..e5d5e3b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -45,13 +45,16 @@
import com.android.systemui.statusbar.phone.PhoneStatusBarViewController
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
import com.android.systemui.statusbar.window.StatusBarWindowStateController
+import com.android.systemui.util.mockito.any
import com.google.common.truth.Truth.assertThat
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.Mockito.anyFloat
+import org.mockito.Mockito.mock
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when` as whenever
@@ -102,7 +105,6 @@
@Mock
private lateinit var alternateBouncerInteractor: AlternateBouncerInteractor
@Mock lateinit var keyguardBouncerComponentFactory: KeyguardBouncerComponent.Factory
- @Mock lateinit var keyguardBouncerContainer: ViewGroup
@Mock lateinit var keyguardBouncerComponent: KeyguardBouncerComponent
@Mock lateinit var keyguardHostViewController: KeyguardHostViewController
@Mock lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
@@ -116,6 +118,12 @@
fun setUp() {
MockitoAnnotations.initMocks(this)
whenever(view.bottom).thenReturn(VIEW_BOTTOM)
+ whenever(view.findViewById<ViewGroup>(R.id.keyguard_bouncer_container))
+ .thenReturn(mock(ViewGroup::class.java))
+ whenever(keyguardBouncerComponentFactory.create(any(ViewGroup::class.java)))
+ .thenReturn(keyguardBouncerComponent)
+ whenever(keyguardBouncerComponent.keyguardHostViewController)
+ .thenReturn(keyguardHostViewController)
underTest = NotificationShadeWindowViewController(
lockscreenShadeTransitionController,
FalsingCollectorFake(),
@@ -275,6 +283,7 @@
@Test
fun testGetBouncerContainer() {
+ Mockito.clearInvocations(view)
underTest.bouncerContainer
verify(view).findViewById<ViewGroup>(R.id.keyguard_bouncer_container)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java
index d435624..5cc3ef1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java
@@ -29,9 +29,11 @@
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.MotionEvent;
+import android.view.ViewGroup;
import androidx.test.filters.SmallTest;
+import com.android.keyguard.KeyguardHostViewController;
import com.android.keyguard.LockIconViewController;
import com.android.keyguard.dagger.KeyguardBouncerComponent;
import com.android.systemui.R;
@@ -94,6 +96,8 @@
@Mock private FeatureFlags mFeatureFlags;
@Mock private KeyguardBouncerViewModel mKeyguardBouncerViewModel;
@Mock private KeyguardBouncerComponent.Factory mKeyguardBouncerComponentFactory;
+ @Mock private KeyguardBouncerComponent mKeyguardBouncerComponent;
+ @Mock private KeyguardHostViewController mKeyguardHostViewController;
@Mock private NotificationInsetsController mNotificationInsetsController;
@Mock private AlternateBouncerInteractor mAlternateBouncerInteractor;
@Mock private KeyguardTransitionInteractor mKeyguardTransitionInteractor;
@@ -110,6 +114,12 @@
when(mView.findViewById(R.id.notification_stack_scroller))
.thenReturn(mNotificationStackScrollLayout);
+ when(mView.findViewById(R.id.keyguard_bouncer_container)).thenReturn(mock(ViewGroup.class));
+ when(mKeyguardBouncerComponentFactory.create(any(ViewGroup.class))).thenReturn(
+ mKeyguardBouncerComponent);
+ when(mKeyguardBouncerComponent.getKeyguardHostViewController()).thenReturn(
+ mKeyguardHostViewController);
+
when(mStatusBarStateController.isDozing()).thenReturn(false);
mDependency.injectTestDependency(ShadeController.class, mShadeController);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt
index a7588dd..cd2efc0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt
@@ -114,7 +114,8 @@
@Test
fun defaultClock_events_onTimeTick() {
val clock = provider.createClock(DEFAULT_CLOCK_ID)
- clock.events.onTimeTick()
+ clock.smallClock.events.onTimeTick()
+ clock.largeClock.events.onTimeTick()
verify(mockSmallClockView).refreshTime()
verify(mockLargeClockView).refreshTime()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
index 2423f13..0a576de 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
@@ -113,6 +113,9 @@
private lateinit var handler: Handler
@Mock
+ private lateinit var datePlugin: BcSmartspaceDataPlugin
+
+ @Mock
private lateinit var weatherPlugin: BcSmartspaceDataPlugin
@Mock
@@ -155,6 +158,7 @@
KeyguardBypassController.OnBypassStateChangedListener
private lateinit var deviceProvisionedListener: DeviceProvisionedListener
+ private lateinit var dateSmartspaceView: SmartspaceView
private lateinit var weatherSmartspaceView: SmartspaceView
private lateinit var smartspaceView: SmartspaceView
@@ -190,6 +194,8 @@
`when`(secureSettings.getUriFor(NOTIF_ON_LOCKSCREEN_SETTING))
.thenReturn(fakeNotifOnLockscreenSettingUri)
`when`(smartspaceManager.createSmartspaceSession(any())).thenReturn(smartspaceSession)
+ `when`(datePlugin.getView(any())).thenReturn(
+ createDateSmartspaceView(), createDateSmartspaceView())
`when`(weatherPlugin.getView(any())).thenReturn(
createWeatherSmartspaceView(), createWeatherSmartspaceView())
`when`(plugin.getView(any())).thenReturn(createSmartspaceView(), createSmartspaceView())
@@ -221,6 +227,7 @@
executor,
bgExecutor,
handler,
+ Optional.of(datePlugin),
Optional.of(weatherPlugin),
Optional.of(plugin),
Optional.of(configPlugin),
@@ -275,7 +282,8 @@
// THEN the listener is registered to the underlying plugin
verify(plugin).registerListener(controllerListener)
- // The listener is registered only for the plugin, not the weather plugin.
+ // The listener is registered only for the plugin, not the date, or weather plugin.
+ verify(datePlugin, never()).registerListener(any())
verify(weatherPlugin, never()).registerListener(any())
}
@@ -289,7 +297,8 @@
// THEN the listener is subsequently registered
verify(plugin).registerListener(controllerListener)
- // The listener is registered only for the plugin, not the weather plugin.
+ // The listener is registered only for the plugin, not the date, or the weather plugin.
+ verify(datePlugin, never()).registerListener(any())
verify(weatherPlugin, never()).registerListener(any())
}
@@ -308,6 +317,7 @@
verify(plugin).registerSmartspaceEventNotifier(null)
verify(weatherPlugin).onTargetsAvailable(emptyList())
verify(weatherPlugin).registerSmartspaceEventNotifier(null)
+ verify(datePlugin).registerSmartspaceEventNotifier(null)
}
@Test
@@ -357,6 +367,7 @@
configChangeListener.onThemeChanged()
// We update the new text color to match the wallpaper color
+ verify(dateSmartspaceView).setPrimaryTextColor(anyInt())
verify(weatherSmartspaceView).setPrimaryTextColor(anyInt())
verify(smartspaceView).setPrimaryTextColor(anyInt())
}
@@ -384,6 +395,7 @@
statusBarStateListener.onDozeAmountChanged(0.1f, 0.7f)
// We pass that along to the view
+ verify(dateSmartspaceView).setDozeAmount(0.7f)
verify(weatherSmartspaceView).setDozeAmount(0.7f)
verify(smartspaceView).setDozeAmount(0.7f)
}
@@ -502,6 +514,8 @@
verify(plugin).onTargetsAvailable(eq(listOf(targets[0], targets[1], targets[2])))
// No filtering is applied for the weather plugin
verify(weatherPlugin).onTargetsAvailable(eq(targets))
+ // No targets needed for the date plugin
+ verify(datePlugin, never()).onTargetsAvailable(any())
}
@Test
@@ -633,6 +647,18 @@
private fun connectSession() {
if (controller.isDateWeatherDecoupled()) {
+ val dateView = controller.buildAndConnectDateView(fakeParent)
+ dateSmartspaceView = dateView as SmartspaceView
+ fakeParent.addView(dateView)
+ controller.stateChangeListener.onViewAttachedToWindow(dateView)
+
+ verify(dateSmartspaceView).setUiSurface(
+ BcSmartspaceDataPlugin.UI_SURFACE_LOCK_SCREEN_AOD)
+ verify(dateSmartspaceView).registerDataProvider(datePlugin)
+
+ verify(dateSmartspaceView).setPrimaryTextColor(anyInt())
+ verify(dateSmartspaceView).setDozeAmount(0.5f)
+
val weatherView = controller.buildAndConnectWeatherView(fakeParent)
weatherSmartspaceView = weatherView as SmartspaceView
fakeParent.addView(weatherView)
@@ -686,6 +712,7 @@
verify(smartspaceView).setDozeAmount(0.5f)
if (controller.isDateWeatherDecoupled()) {
+ clearInvocations(dateSmartspaceView)
clearInvocations(weatherSmartspaceView)
}
clearInvocations(smartspaceView)
@@ -734,7 +761,38 @@
).thenReturn(if (value) 1 else 0)
}
- // Separate function for the weather view, which doesn't implement all functions in interface.
+ // Separate function for the date view, which implements a specific subset of all functions.
+ private fun createDateSmartspaceView(): SmartspaceView {
+ return spy(object : View(context), SmartspaceView {
+ override fun registerDataProvider(plugin: BcSmartspaceDataPlugin?) {
+ }
+
+ override fun setPrimaryTextColor(color: Int) {
+ }
+
+ override fun setIsDreaming(isDreaming: Boolean) {
+ }
+
+ override fun setUiSurface(uiSurface: String) {
+ }
+
+ override fun setDozeAmount(amount: Float) {
+ }
+
+ override fun setIntentStarter(intentStarter: BcSmartspaceDataPlugin.IntentStarter?) {
+ }
+
+ override fun setFalsingManager(falsingManager: FalsingManager?) {
+ }
+
+ override fun setDnd(image: Drawable?, description: String?) {
+ }
+
+ override fun setNextAlarm(image: Drawable?, description: String?) {
+ }
+ })
+ }
+ // Separate function for the weather view, which implements a specific subset of all functions.
private fun createWeatherSmartspaceView(): SmartspaceView {
return spy(object : View(context), SmartspaceView {
override fun registerDataProvider(plugin: BcSmartspaceDataPlugin?) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
index 4559a23..9e23d54 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
@@ -82,19 +82,14 @@
import java.util.Arrays;
import java.util.List;
+import java.util.function.Consumer;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@RunWithLooper
public class ExpandableNotificationRowTest extends SysuiTestCase {
- private ExpandableNotificationRow mGroupRow;
- private ExpandableNotificationRow mNotifRow;
- private ExpandableNotificationRow mPublicRow;
-
private NotificationTestHelper mNotificationTestHelper;
- boolean mHeadsUpAnimatingAway = false;
-
@Rule public MockitoRule mockito = MockitoJUnit.rule();
@Before
@@ -105,112 +100,108 @@
mDependency,
TestableLooper.get(this));
mNotificationTestHelper.setDefaultInflationFlags(FLAG_CONTENT_VIEW_ALL);
+
FakeFeatureFlags fakeFeatureFlags = new FakeFeatureFlags();
fakeFeatureFlags.set(Flags.NOTIFICATION_ANIMATE_BIG_PICTURE, true);
mNotificationTestHelper.setFeatureFlags(fakeFeatureFlags);
- // create a standard private notification row
- Notification normalNotif = mNotificationTestHelper.createNotification();
- normalNotif.publicVersion = null;
- mNotifRow = mNotificationTestHelper.createRow(normalNotif);
+ }
+
+ @Test
+ public void testUpdateBackgroundColors_isRecursive() throws Exception {
+ ExpandableNotificationRow group = mNotificationTestHelper.createGroup();
+ group.setTintColor(Color.RED);
+ group.getChildNotificationAt(0).setTintColor(Color.GREEN);
+ group.getChildNotificationAt(1).setTintColor(Color.BLUE);
+
+ assertThat(group.getCurrentBackgroundTint()).isEqualTo(Color.RED);
+ assertThat(group.getChildNotificationAt(0).getCurrentBackgroundTint())
+ .isEqualTo(Color.GREEN);
+ assertThat(group.getChildNotificationAt(1).getCurrentBackgroundTint())
+ .isEqualTo(Color.BLUE);
+
+ group.updateBackgroundColors();
+
+ int resetTint = group.getCurrentBackgroundTint();
+ assertThat(resetTint).isNotEqualTo(Color.RED);
+ assertThat(group.getChildNotificationAt(0).getCurrentBackgroundTint())
+ .isEqualTo(resetTint);
+ assertThat(group.getChildNotificationAt(1).getCurrentBackgroundTint())
+ .isEqualTo(resetTint);
+ }
+
+ @Test
+ public void testSetSensitiveOnNotifRowNotifiesOfHeightChange() throws Exception {
+ // GIVEN a sensitive notification row that's currently redacted
+ ExpandableNotificationRow row = mNotificationTestHelper.createRow();
+ measureAndLayout(row);
+ row.setHideSensitiveForIntrinsicHeight(true);
+ row.setSensitive(true, true);
+ assertThat(row.getShowingLayout()).isSameInstanceAs(row.getPublicLayout());
+ assertThat(row.getIntrinsicHeight()).isGreaterThan(0);
+
+ // GIVEN that the row has a height change listener
+ OnHeightChangedListener listener = mock(OnHeightChangedListener.class);
+ row.setOnHeightChangedListener(listener);
+
+ // WHEN the row is set to no longer be sensitive
+ row.setSensitive(false, true);
+
+ // VERIFY that the height change listener is invoked
+ assertThat(row.getShowingLayout()).isSameInstanceAs(row.getPrivateLayout());
+ assertThat(row.getIntrinsicHeight()).isGreaterThan(0);
+ verify(listener).onHeightChanged(eq(row), eq(false));
+ }
+
+ @Test
+ public void testSetSensitiveOnGroupRowNotifiesOfHeightChange() throws Exception {
+ // GIVEN a sensitive group row that's currently redacted
+ ExpandableNotificationRow group = mNotificationTestHelper.createGroup();
+ measureAndLayout(group);
+ group.setHideSensitiveForIntrinsicHeight(true);
+ group.setSensitive(true, true);
+ assertThat(group.getShowingLayout()).isSameInstanceAs(group.getPublicLayout());
+ assertThat(group.getIntrinsicHeight()).isGreaterThan(0);
+
+ // GIVEN that the row has a height change listener
+ OnHeightChangedListener listener = mock(OnHeightChangedListener.class);
+ group.setOnHeightChangedListener(listener);
+
+ // WHEN the row is set to no longer be sensitive
+ group.setSensitive(false, true);
+
+ // VERIFY that the height change listener is invoked
+ assertThat(group.getShowingLayout()).isSameInstanceAs(group.getPrivateLayout());
+ assertThat(group.getIntrinsicHeight()).isGreaterThan(0);
+ verify(listener).onHeightChanged(eq(group), eq(false));
+ }
+
+ @Test
+ public void testSetSensitiveOnPublicRowDoesNotNotifyOfHeightChange() throws Exception {
// create a notification row whose public version is identical
Notification publicNotif = mNotificationTestHelper.createNotification();
publicNotif.publicVersion = mNotificationTestHelper.createNotification();
- mPublicRow = mNotificationTestHelper.createRow(publicNotif);
- // create a group row
- mGroupRow = mNotificationTestHelper.createGroup();
- mGroupRow.setHeadsUpAnimatingAwayListener(
- animatingAway -> mHeadsUpAnimatingAway = animatingAway);
+ ExpandableNotificationRow publicRow = mNotificationTestHelper.createRow(publicNotif);
- }
-
- @Test
- public void testUpdateBackgroundColors_isRecursive() {
- mGroupRow.setTintColor(Color.RED);
- mGroupRow.getChildNotificationAt(0).setTintColor(Color.GREEN);
- mGroupRow.getChildNotificationAt(1).setTintColor(Color.BLUE);
-
- assertThat(mGroupRow.getCurrentBackgroundTint()).isEqualTo(Color.RED);
- assertThat(mGroupRow.getChildNotificationAt(0).getCurrentBackgroundTint())
- .isEqualTo(Color.GREEN);
- assertThat(mGroupRow.getChildNotificationAt(1).getCurrentBackgroundTint())
- .isEqualTo(Color.BLUE);
-
- mGroupRow.updateBackgroundColors();
-
- int resetTint = mGroupRow.getCurrentBackgroundTint();
- assertThat(resetTint).isNotEqualTo(Color.RED);
- assertThat(mGroupRow.getChildNotificationAt(0).getCurrentBackgroundTint())
- .isEqualTo(resetTint);
- assertThat(mGroupRow.getChildNotificationAt(1).getCurrentBackgroundTint())
- .isEqualTo(resetTint);
- }
-
- @Test
- public void testSetSensitiveOnNotifRowNotifiesOfHeightChange() throws InterruptedException {
- // GIVEN a sensitive notification row that's currently redacted
- measureAndLayout(mNotifRow);
- mNotifRow.setHideSensitiveForIntrinsicHeight(true);
- mNotifRow.setSensitive(true, true);
- assertThat(mNotifRow.getShowingLayout()).isSameInstanceAs(mNotifRow.getPublicLayout());
- assertThat(mNotifRow.getIntrinsicHeight()).isGreaterThan(0);
-
- // GIVEN that the row has a height change listener
- OnHeightChangedListener listener = mock(OnHeightChangedListener.class);
- mNotifRow.setOnHeightChangedListener(listener);
-
- // WHEN the row is set to no longer be sensitive
- mNotifRow.setSensitive(false, true);
-
- // VERIFY that the height change listener is invoked
- assertThat(mNotifRow.getShowingLayout()).isSameInstanceAs(mNotifRow.getPrivateLayout());
- assertThat(mNotifRow.getIntrinsicHeight()).isGreaterThan(0);
- verify(listener).onHeightChanged(eq(mNotifRow), eq(false));
- }
-
- @Test
- public void testSetSensitiveOnGroupRowNotifiesOfHeightChange() {
- // GIVEN a sensitive group row that's currently redacted
- measureAndLayout(mGroupRow);
- mGroupRow.setHideSensitiveForIntrinsicHeight(true);
- mGroupRow.setSensitive(true, true);
- assertThat(mGroupRow.getShowingLayout()).isSameInstanceAs(mGroupRow.getPublicLayout());
- assertThat(mGroupRow.getIntrinsicHeight()).isGreaterThan(0);
-
- // GIVEN that the row has a height change listener
- OnHeightChangedListener listener = mock(OnHeightChangedListener.class);
- mGroupRow.setOnHeightChangedListener(listener);
-
- // WHEN the row is set to no longer be sensitive
- mGroupRow.setSensitive(false, true);
-
- // VERIFY that the height change listener is invoked
- assertThat(mGroupRow.getShowingLayout()).isSameInstanceAs(mGroupRow.getPrivateLayout());
- assertThat(mGroupRow.getIntrinsicHeight()).isGreaterThan(0);
- verify(listener).onHeightChanged(eq(mGroupRow), eq(false));
- }
-
- @Test
- public void testSetSensitiveOnPublicRowDoesNotNotifyOfHeightChange() {
// GIVEN a sensitive public row that's currently redacted
- measureAndLayout(mPublicRow);
- mPublicRow.setHideSensitiveForIntrinsicHeight(true);
- mPublicRow.setSensitive(true, true);
- assertThat(mPublicRow.getShowingLayout()).isSameInstanceAs(mPublicRow.getPublicLayout());
- assertThat(mPublicRow.getIntrinsicHeight()).isGreaterThan(0);
+ measureAndLayout(publicRow);
+ publicRow.setHideSensitiveForIntrinsicHeight(true);
+ publicRow.setSensitive(true, true);
+ assertThat(publicRow.getShowingLayout()).isSameInstanceAs(publicRow.getPublicLayout());
+ assertThat(publicRow.getIntrinsicHeight()).isGreaterThan(0);
// GIVEN that the row has a height change listener
OnHeightChangedListener listener = mock(OnHeightChangedListener.class);
- mPublicRow.setOnHeightChangedListener(listener);
+ publicRow.setOnHeightChangedListener(listener);
// WHEN the row is set to no longer be sensitive
- mPublicRow.setSensitive(false, true);
+ publicRow.setSensitive(false, true);
// VERIFY that the height change listener is not invoked, because the height didn't change
- assertThat(mPublicRow.getShowingLayout()).isSameInstanceAs(mPublicRow.getPrivateLayout());
- assertThat(mPublicRow.getIntrinsicHeight()).isGreaterThan(0);
- assertThat(mPublicRow.getPrivateLayout().getMinHeight())
- .isEqualTo(mPublicRow.getPublicLayout().getMinHeight());
- verify(listener, never()).onHeightChanged(eq(mPublicRow), eq(false));
+ assertThat(publicRow.getShowingLayout()).isSameInstanceAs(publicRow.getPrivateLayout());
+ assertThat(publicRow.getIntrinsicHeight()).isGreaterThan(0);
+ assertThat(publicRow.getPrivateLayout().getMinHeight())
+ .isEqualTo(publicRow.getPublicLayout().getMinHeight());
+ verify(listener, never()).onHeightChanged(eq(publicRow), eq(false));
}
private void measureAndLayout(ExpandableNotificationRow row) {
@@ -227,36 +218,43 @@
}
@Test
- public void testGroupSummaryNotShowingIconWhenPublic() {
- mGroupRow.setSensitive(true, true);
- mGroupRow.setHideSensitiveForIntrinsicHeight(true);
- assertTrue(mGroupRow.isSummaryWithChildren());
- assertFalse(mGroupRow.isShowingIcon());
+ public void testGroupSummaryNotShowingIconWhenPublic() throws Exception {
+ ExpandableNotificationRow group = mNotificationTestHelper.createGroup();
+
+ group.setSensitive(true, true);
+ group.setHideSensitiveForIntrinsicHeight(true);
+ assertTrue(group.isSummaryWithChildren());
+ assertFalse(group.isShowingIcon());
}
@Test
- public void testNotificationHeaderVisibleWhenAnimating() {
- mGroupRow.setSensitive(true, true);
- mGroupRow.setHideSensitive(true, false, 0, 0);
- mGroupRow.setHideSensitive(false, true, 0, 0);
- assertEquals(View.VISIBLE, mGroupRow.getChildrenContainer().getVisibleWrapper()
+ public void testNotificationHeaderVisibleWhenAnimating() throws Exception {
+ ExpandableNotificationRow group = mNotificationTestHelper.createGroup();
+
+ group.setSensitive(true, true);
+ group.setHideSensitive(true, false, 0, 0);
+ group.setHideSensitive(false, true, 0, 0);
+ assertEquals(View.VISIBLE, group.getChildrenContainer().getVisibleWrapper()
.getNotificationHeader().getVisibility());
}
@Test
- public void testUserLockedResetEvenWhenNoChildren() {
- mGroupRow.setUserLocked(true);
- mGroupRow.setUserLocked(false);
+ public void testUserLockedResetEvenWhenNoChildren() throws Exception {
+ ExpandableNotificationRow group = mNotificationTestHelper.createGroup();
+
+ group.setUserLocked(true);
+ group.setUserLocked(false);
assertFalse("The childrencontainer should not be userlocked but is, the state "
- + "seems out of sync.", mGroupRow.getChildrenContainer().isUserLocked());
+ + "seems out of sync.", group.getChildrenContainer().isUserLocked());
}
@Test
- public void testReinflatedOnDensityChange() {
+ public void testReinflatedOnDensityChange() throws Exception {
+ ExpandableNotificationRow row = mNotificationTestHelper.createRow();
NotificationChildrenContainer mockContainer = mock(NotificationChildrenContainer.class);
- mNotifRow.setChildrenContainer(mockContainer);
+ row.setChildrenContainer(mockContainer);
- mNotifRow.onDensityOrFontScaleChanged();
+ row.onDensityOrFontScaleChanged();
verify(mockContainer).reInflateViews(any(), any());
}
@@ -299,64 +297,73 @@
@Test
public void testAboveShelfChangedListenerCalledWhenGoingBelow() throws Exception {
ExpandableNotificationRow row = mNotificationTestHelper.createRow();
- row.setHeadsUp(true);
AboveShelfChangedListener listener = mock(AboveShelfChangedListener.class);
row.setAboveShelfChangedListener(listener);
+ Mockito.reset(listener);
+ row.setHeadsUp(true);
row.setAboveShelf(false);
verify(listener).onAboveShelfStateChanged(false);
}
@Test
public void testClickSound() throws Exception {
- assertTrue("Should play sounds by default.", mGroupRow.isSoundEffectsEnabled());
+ ExpandableNotificationRow group = mNotificationTestHelper.createGroup();
+
+ assertTrue("Should play sounds by default.", group.isSoundEffectsEnabled());
StatusBarStateController mock = mNotificationTestHelper.getStatusBarStateController();
when(mock.isDozing()).thenReturn(true);
- mGroupRow.setSecureStateProvider(()-> false);
+ group.setSecureStateProvider(()-> false);
assertFalse("Shouldn't play sounds when dark and trusted.",
- mGroupRow.isSoundEffectsEnabled());
- mGroupRow.setSecureStateProvider(()-> true);
+ group.isSoundEffectsEnabled());
+ group.setSecureStateProvider(()-> true);
assertTrue("Should always play sounds when not trusted.",
- mGroupRow.isSoundEffectsEnabled());
+ group.isSoundEffectsEnabled());
}
@Test
- public void testSetDismissed_longPressListenerRemoved() {
+ public void testSetDismissed_longPressListenerRemoved() throws Exception {
+ ExpandableNotificationRow group = mNotificationTestHelper.createGroup();
+
ExpandableNotificationRow.LongPressListener listener =
mock(ExpandableNotificationRow.LongPressListener.class);
- mGroupRow.setLongPressListener(listener);
- mGroupRow.doLongClickCallback(0,0);
- verify(listener, times(1)).onLongPress(eq(mGroupRow), eq(0), eq(0),
+ group.setLongPressListener(listener);
+ group.doLongClickCallback(0, 0);
+ verify(listener, times(1)).onLongPress(eq(group), eq(0), eq(0),
any(NotificationMenuRowPlugin.MenuItem.class));
reset(listener);
- mGroupRow.dismiss(true);
- mGroupRow.doLongClickCallback(0,0);
- verify(listener, times(0)).onLongPress(eq(mGroupRow), eq(0), eq(0),
+ group.dismiss(true);
+ group.doLongClickCallback(0, 0);
+ verify(listener, times(0)).onLongPress(eq(group), eq(0), eq(0),
any(NotificationMenuRowPlugin.MenuItem.class));
}
@Test
- public void testFeedback_noHeader() {
+ public void testFeedback_noHeader() throws Exception {
+ ExpandableNotificationRow groupRow = mNotificationTestHelper.createGroup();
+
// public notification is custom layout - no header
- mGroupRow.setSensitive(true, true);
- mGroupRow.setOnFeedbackClickListener(null);
- mGroupRow.setFeedbackIcon(null);
+ groupRow.setSensitive(true, true);
+ groupRow.setOnFeedbackClickListener(null);
+ groupRow.setFeedbackIcon(null);
}
@Test
- public void testFeedback_header() {
+ public void testFeedback_header() throws Exception {
+ ExpandableNotificationRow group = mNotificationTestHelper.createGroup();
+
NotificationContentView publicLayout = mock(NotificationContentView.class);
- mGroupRow.setPublicLayout(publicLayout);
+ group.setPublicLayout(publicLayout);
NotificationContentView privateLayout = mock(NotificationContentView.class);
- mGroupRow.setPrivateLayout(privateLayout);
+ group.setPrivateLayout(privateLayout);
NotificationChildrenContainer mockContainer = mock(NotificationChildrenContainer.class);
when(mockContainer.getNotificationChildCount()).thenReturn(1);
- mGroupRow.setChildrenContainer(mockContainer);
+ group.setChildrenContainer(mockContainer);
final boolean show = true;
final FeedbackIcon icon = new FeedbackIcon(
R.drawable.ic_feedback_alerted, R.string.notification_feedback_indicator_alerted);
- mGroupRow.setFeedbackIcon(icon);
+ group.setFeedbackIcon(icon);
verify(mockContainer, times(1)).setFeedbackIcon(icon);
verify(privateLayout, times(1)).setFeedbackIcon(icon);
@@ -364,43 +371,60 @@
}
@Test
- public void testFeedbackOnClick() {
+ public void testFeedbackOnClick() throws Exception {
+ ExpandableNotificationRow group = mNotificationTestHelper.createGroup();
+
ExpandableNotificationRow.CoordinateOnClickListener l = mock(
ExpandableNotificationRow.CoordinateOnClickListener.class);
View view = mock(View.class);
- mGroupRow.setOnFeedbackClickListener(l);
+ group.setOnFeedbackClickListener(l);
- mGroupRow.getFeedbackOnClickListener().onClick(view);
+ group.getFeedbackOnClickListener().onClick(view);
verify(l, times(1)).onClick(any(), anyInt(), anyInt(), any());
}
@Test
- public void testHeadsUpAnimatingAwayListener() {
- mGroupRow.setHeadsUpAnimatingAway(true);
- Assert.assertEquals(true, mHeadsUpAnimatingAway);
- mGroupRow.setHeadsUpAnimatingAway(false);
- Assert.assertEquals(false, mHeadsUpAnimatingAway);
+ public void testHeadsUpAnimatingAwayListener() throws Exception {
+ ExpandableNotificationRow group = mNotificationTestHelper.createGroup();
+ Consumer<Boolean> headsUpListener = mock(Consumer.class);
+ AboveShelfChangedListener aboveShelfChangedListener = mock(AboveShelfChangedListener.class);
+ group.setHeadsUpAnimatingAwayListener(headsUpListener);
+ group.setAboveShelfChangedListener(aboveShelfChangedListener);
+
+ group.setHeadsUpAnimatingAway(true);
+ verify(headsUpListener).accept(true);
+ verify(aboveShelfChangedListener).onAboveShelfStateChanged(true);
+
+ group.setHeadsUpAnimatingAway(false);
+ verify(headsUpListener).accept(false);
+ verify(aboveShelfChangedListener).onAboveShelfStateChanged(false);
}
@Test
- public void testIsBlockingHelperShowing_isCorrectlyUpdated() {
- mGroupRow.setBlockingHelperShowing(true);
- assertTrue(mGroupRow.isBlockingHelperShowing());
+ public void testIsBlockingHelperShowing_isCorrectlyUpdated() throws Exception {
+ ExpandableNotificationRow group = mNotificationTestHelper.createGroup();
- mGroupRow.setBlockingHelperShowing(false);
- assertFalse(mGroupRow.isBlockingHelperShowing());
+ group.setBlockingHelperShowing(true);
+ assertTrue(group.isBlockingHelperShowing());
+
+ group.setBlockingHelperShowing(false);
+ assertFalse(group.isBlockingHelperShowing());
}
@Test
- public void testGetNumUniqueChildren_defaultChannel() {
- assertEquals(1, mGroupRow.getNumUniqueChannels());
+ public void testGetNumUniqueChildren_defaultChannel() throws Exception {
+ ExpandableNotificationRow groupRow = mNotificationTestHelper.createGroup();
+
+ assertEquals(1, groupRow.getNumUniqueChannels());
}
@Test
- public void testGetNumUniqueChildren_multiChannel() {
+ public void testGetNumUniqueChildren_multiChannel() throws Exception {
+ ExpandableNotificationRow group = mNotificationTestHelper.createGroup();
+
List<ExpandableNotificationRow> childRows =
- mGroupRow.getChildrenContainer().getAttachedChildren();
+ group.getChildrenContainer().getAttachedChildren();
// Give each child a unique channel id/name.
int i = 0;
for (ExpandableNotificationRow childRow : childRows) {
@@ -412,25 +436,29 @@
i++;
}
- assertEquals(3, mGroupRow.getNumUniqueChannels());
+ assertEquals(3, group.getNumUniqueChannels());
}
@Test
public void testIconScrollXAfterTranslationAndReset() throws Exception {
- mGroupRow.setDismissUsingRowTranslationX(false);
- mGroupRow.setTranslation(50);
- assertEquals(50, -mGroupRow.getEntry().getIcons().getShelfIcon().getScrollX());
+ ExpandableNotificationRow group = mNotificationTestHelper.createGroup();
- mGroupRow.resetTranslation();
- assertEquals(0, mGroupRow.getEntry().getIcons().getShelfIcon().getScrollX());
+ group.setDismissUsingRowTranslationX(false);
+ group.setTranslation(50);
+ assertEquals(50, -group.getEntry().getIcons().getShelfIcon().getScrollX());
+
+ group.resetTranslation();
+ assertEquals(0, group.getEntry().getIcons().getShelfIcon().getScrollX());
}
@Test
- public void testIsExpanded_userExpanded() {
- mGroupRow.setExpandable(true);
- Assert.assertFalse(mGroupRow.isExpanded());
- mGroupRow.setUserExpanded(true);
- Assert.assertTrue(mGroupRow.isExpanded());
+ public void testIsExpanded_userExpanded() throws Exception {
+ ExpandableNotificationRow group = mNotificationTestHelper.createGroup();
+
+ group.setExpandable(true);
+ Assert.assertFalse(group.isExpanded());
+ group.setUserExpanded(true);
+ Assert.assertTrue(group.isExpanded());
}
@Test
@@ -549,72 +577,80 @@
}
@Test
- public void applyRoundnessAndInv_should_be_immediately_applied_on_childrenContainer_legacy() {
- mGroupRow.useRoundnessSourceTypes(false);
- Assert.assertEquals(0f, mGroupRow.getBottomRoundness(), 0.001f);
- Assert.assertEquals(0f, mGroupRow.getChildrenContainer().getBottomRoundness(), 0.001f);
+ public void applyRoundnessAndInv_should_be_immediately_applied_on_childrenContainer_legacy()
+ throws Exception {
+ ExpandableNotificationRow group = mNotificationTestHelper.createGroup();
+ group.useRoundnessSourceTypes(false);
+ Assert.assertEquals(0f, group.getBottomRoundness(), 0.001f);
+ Assert.assertEquals(0f, group.getChildrenContainer().getBottomRoundness(), 0.001f);
- mGroupRow.requestBottomRoundness(1f, SourceType.from(""), false);
+ group.requestBottomRoundness(1f, SourceType.from(""), false);
- Assert.assertEquals(1f, mGroupRow.getBottomRoundness(), 0.001f);
- Assert.assertEquals(1f, mGroupRow.getChildrenContainer().getBottomRoundness(), 0.001f);
+ Assert.assertEquals(1f, group.getBottomRoundness(), 0.001f);
+ Assert.assertEquals(1f, group.getChildrenContainer().getBottomRoundness(), 0.001f);
}
@Test
- public void applyRoundnessAndInvalidate_should_be_immediately_applied_on_childrenContainer() {
- mGroupRow.useRoundnessSourceTypes(true);
- Assert.assertEquals(0f, mGroupRow.getBottomRoundness(), 0.001f);
- Assert.assertEquals(0f, mGroupRow.getChildrenContainer().getBottomRoundness(), 0.001f);
+ public void applyRoundnessAndInvalidate_should_be_immediately_applied_on_childrenContainer()
+ throws Exception {
+ ExpandableNotificationRow group = mNotificationTestHelper.createGroup();
+ group.useRoundnessSourceTypes(true);
+ Assert.assertEquals(0f, group.getBottomRoundness(), 0.001f);
+ Assert.assertEquals(0f, group.getChildrenContainer().getBottomRoundness(), 0.001f);
- mGroupRow.requestBottomRoundness(1f, SourceType.from(""), false);
+ group.requestBottomRoundness(1f, SourceType.from(""), false);
- Assert.assertEquals(1f, mGroupRow.getBottomRoundness(), 0.001f);
- Assert.assertEquals(1f, mGroupRow.getChildrenContainer().getBottomRoundness(), 0.001f);
+ Assert.assertEquals(1f, group.getBottomRoundness(), 0.001f);
+ Assert.assertEquals(1f, group.getChildrenContainer().getBottomRoundness(), 0.001f);
}
@Test
public void testSetContentAnimationRunning_Run() throws Exception {
// Create views for the notification row.
+ ExpandableNotificationRow row = mNotificationTestHelper.createRow();
NotificationContentView publicLayout = mock(NotificationContentView.class);
- mNotifRow.setPublicLayout(publicLayout);
+ row.setPublicLayout(publicLayout);
NotificationContentView privateLayout = mock(NotificationContentView.class);
- mNotifRow.setPrivateLayout(privateLayout);
+ row.setPrivateLayout(privateLayout);
- mNotifRow.setAnimationRunning(true);
+ row.setAnimationRunning(true);
verify(publicLayout, times(1)).setContentAnimationRunning(true);
verify(privateLayout, times(1)).setContentAnimationRunning(true);
}
@Test
- public void testSetContentAnimationRunning_Stop() {
+ public void testSetContentAnimationRunning_Stop() throws Exception {
// Create views for the notification row.
+ ExpandableNotificationRow row = mNotificationTestHelper.createRow();
NotificationContentView publicLayout = mock(NotificationContentView.class);
- mNotifRow.setPublicLayout(publicLayout);
+ row.setPublicLayout(publicLayout);
NotificationContentView privateLayout = mock(NotificationContentView.class);
- mNotifRow.setPrivateLayout(privateLayout);
+ row.setPrivateLayout(privateLayout);
- mNotifRow.setAnimationRunning(false);
+ row.setAnimationRunning(false);
verify(publicLayout, times(1)).setContentAnimationRunning(false);
verify(privateLayout, times(1)).setContentAnimationRunning(false);
}
@Test
- public void testSetContentAnimationRunningInGroupChild_Run() {
- // Creates parent views on mGroupRow.
+ public void testSetContentAnimationRunningInGroupChild_Run() throws Exception {
+ // Creates parent views on groupRow.
+ ExpandableNotificationRow groupRow = mNotificationTestHelper.createGroup();
NotificationContentView publicParentLayout = mock(NotificationContentView.class);
- mGroupRow.setPublicLayout(publicParentLayout);
+ groupRow.setPublicLayout(publicParentLayout);
NotificationContentView privateParentLayout = mock(NotificationContentView.class);
- mGroupRow.setPrivateLayout(privateParentLayout);
+ groupRow.setPrivateLayout(privateParentLayout);
- // Create child views on mNotifRow.
+ // Create child views on row.
+ ExpandableNotificationRow row = mNotificationTestHelper.createRow();
NotificationContentView publicChildLayout = mock(NotificationContentView.class);
- mNotifRow.setPublicLayout(publicChildLayout);
+ row.setPublicLayout(publicChildLayout);
NotificationContentView privateChildLayout = mock(NotificationContentView.class);
- mNotifRow.setPrivateLayout(privateChildLayout);
- when(mNotifRow.isGroupExpanded()).thenReturn(true);
- setMockChildrenContainer(mGroupRow, mNotifRow);
+ row.setPrivateLayout(privateChildLayout);
+ when(row.isGroupExpanded()).thenReturn(true);
+ setMockChildrenContainer(groupRow, row);
- mGroupRow.setAnimationRunning(true);
+ groupRow.setAnimationRunning(true);
verify(publicParentLayout, times(1)).setContentAnimationRunning(true);
verify(privateParentLayout, times(1)).setContentAnimationRunning(true);
// The child layouts should be started too.
@@ -624,23 +660,25 @@
@Test
- public void testSetIconAnimationRunningGroup_Run() {
+ public void testSetIconAnimationRunningGroup_Run() throws Exception {
// Create views for a group row.
+ ExpandableNotificationRow group = mNotificationTestHelper.createGroup();
+ ExpandableNotificationRow child = mNotificationTestHelper.createRow();
NotificationContentView publicParentLayout = mock(NotificationContentView.class);
- mGroupRow.setPublicLayout(publicParentLayout);
+ group.setPublicLayout(publicParentLayout);
NotificationContentView privateParentLayout = mock(NotificationContentView.class);
- mGroupRow.setPrivateLayout(privateParentLayout);
- when(mGroupRow.isGroupExpanded()).thenReturn(true);
+ group.setPrivateLayout(privateParentLayout);
+ when(group.isGroupExpanded()).thenReturn(true);
- // Sets up mNotifRow as a child ExpandableNotificationRow.
+ // Add the child to the group.
NotificationContentView publicChildLayout = mock(NotificationContentView.class);
- mNotifRow.setPublicLayout(publicChildLayout);
+ child.setPublicLayout(publicChildLayout);
NotificationContentView privateChildLayout = mock(NotificationContentView.class);
- mNotifRow.setPrivateLayout(privateChildLayout);
- when(mNotifRow.isGroupExpanded()).thenReturn(true);
+ child.setPrivateLayout(privateChildLayout);
+ when(child.isGroupExpanded()).thenReturn(true);
NotificationChildrenContainer mockContainer =
- setMockChildrenContainer(mGroupRow, mNotifRow);
+ setMockChildrenContainer(group, child);
// Mock the children view wrappers, and give them each an icon.
NotificationViewWrapper mockViewWrapper = mock(NotificationViewWrapper.class);
@@ -663,7 +701,7 @@
AnimatedVectorDrawable lowPriVectorDrawable = mock(AnimatedVectorDrawable.class);
setDrawableIconsInImageView(mockLowPriorityIcon, lowPriDrawable, lowPriVectorDrawable);
- mGroupRow.setAnimationRunning(true);
+ group.setAnimationRunning(true);
verify(drawable, times(1)).start();
verify(vectorDrawable, times(1)).start();
verify(lowPriDrawable, times(1)).start();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
index e4fc4d5..aca9c56 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
@@ -40,7 +40,6 @@
import android.content.Intent;
import android.content.pm.LauncherApps;
import android.graphics.drawable.Icon;
-import android.os.Handler;
import android.os.UserHandle;
import android.service.notification.StatusBarNotification;
import android.testing.TestableLooper;
@@ -49,7 +48,6 @@
import android.widget.RemoteViews;
import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.UiEventLogger;
import com.android.systemui.TestableDependency;
import com.android.systemui.classifier.FalsingCollectorFake;
import com.android.systemui.classifier.FalsingManagerFake;
@@ -57,7 +55,6 @@
import com.android.systemui.media.controls.util.MediaFeatureFlag;
import com.android.systemui.media.dialog.MediaOutputDialogFactory;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.shade.ShadeExpansionStateManager;
import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.NotificationShadeWindowController;
@@ -68,7 +65,6 @@
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
-import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider;
import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
import com.android.systemui.statusbar.notification.icon.IconBuilder;
@@ -77,11 +73,8 @@
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.ExpandableNotificationRowLogger;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.OnExpandClickListener;
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
-import com.android.systemui.statusbar.phone.ConfigurationControllerImpl;
import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
-import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
-import com.android.systemui.statusbar.policy.HeadsUpManagerLogger;
import com.android.systemui.statusbar.policy.InflatedSmartReplyState;
import com.android.systemui.statusbar.policy.InflatedSmartReplyViewHolder;
import com.android.systemui.statusbar.policy.SmartReplyConstants;
@@ -121,12 +114,12 @@
private final GroupMembershipManager mGroupMembershipManager;
private final GroupExpansionManager mGroupExpansionManager;
private ExpandableNotificationRow mRow;
- private HeadsUpManagerPhone mHeadsUpManager;
+ private final HeadsUpManagerPhone mHeadsUpManager;
private final NotifBindPipeline mBindPipeline;
private final NotifCollectionListener mBindPipelineEntryListener;
private final RowContentBindStage mBindStage;
private final IconManager mIconManager;
- private StatusBarStateController mStatusBarStateController;
+ private final StatusBarStateController mStatusBarStateController;
private final PeopleNotificationIdentifier mPeopleNotificationIdentifier;
public final OnUserInteractionCallback mOnUserInteractionCallback;
public final Runnable mFutureDismissalRunnable;
@@ -146,21 +139,7 @@
mStatusBarStateController = mock(StatusBarStateController.class);
mGroupMembershipManager = mock(GroupMembershipManager.class);
mGroupExpansionManager = mock(GroupExpansionManager.class);
- mHeadsUpManager = new HeadsUpManagerPhone(
- mContext,
- mock(HeadsUpManagerLogger.class),
- mStatusBarStateController,
- mock(KeyguardBypassController.class),
- mock(GroupMembershipManager.class),
- mock(VisualStabilityProvider.class),
- mock(ConfigurationControllerImpl.class),
- new Handler(mTestLooper.getLooper()),
- mock(AccessibilityManagerWrapper.class),
- mock(UiEventLogger.class),
- mock(ShadeExpansionStateManager.class)
- );
- mHeadsUpManager.mHandler.removeCallbacksAndMessages(null);
- mHeadsUpManager.mHandler = new Handler(mTestLooper.getLooper());
+ mHeadsUpManager = mock(HeadsUpManagerPhone.class);
mIconManager = new IconManager(
mock(CommonNotifCollection.class),
mock(LauncherApps.class),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
index f230b87..07e8d3c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
@@ -23,10 +23,12 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -45,6 +47,7 @@
import androidx.test.filters.SmallTest;
+import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.R;
import com.android.systemui.SysuiBaseFragmentTest;
import com.android.systemui.dump.DumpManager;
@@ -67,6 +70,8 @@
import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent;
import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.statusbar.window.StatusBarWindowStateController;
+import com.android.systemui.statusbar.window.StatusBarWindowStateListener;
import com.android.systemui.util.CarrierConfigTracker;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.settings.SecureSettings;
@@ -79,6 +84,9 @@
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
+import java.util.ArrayList;
+import java.util.List;
+
@RunWith(AndroidTestingRunner.class)
@RunWithLooper(setAsMainLooper = true)
@SmallTest
@@ -118,6 +126,12 @@
private StatusBarHideIconsForBouncerManager mStatusBarHideIconsForBouncerManager;
@Mock
private DumpManager mDumpManager;
+ @Mock
+ private StatusBarWindowStateController mStatusBarWindowStateController;
+ @Mock
+ private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+
+ private List<StatusBarWindowStateListener> mStatusBarWindowStateListeners = new ArrayList<>();
public CollapsedStatusBarFragmentTest() {
super(CollapsedStatusBarFragment.class);
@@ -127,6 +141,14 @@
public void setup() {
injectLeakCheckedDependencies(ALL_SUPPORTED_CLASSES);
mDependency.injectMockDependency(DarkIconDispatcher.class);
+
+ // Keep the window state listeners so we can dispatch to them to test the status bar
+ // fragment's response.
+ doAnswer(invocation -> {
+ mStatusBarWindowStateListeners.add(invocation.getArgument(0));
+ return null;
+ }).when(mStatusBarWindowStateController).addListener(
+ any(StatusBarWindowStateListener.class));
}
@Test
@@ -414,6 +436,27 @@
assertFalse(contains);
}
+ @Test
+ public void testStatusBarIcons_hiddenThroughoutCameraLaunch() {
+ final CollapsedStatusBarFragment fragment = resumeAndGetFragment();
+
+ mockSecureCameraLaunch(fragment, true /* launched */);
+
+ // Status icons should be invisible or gone, but certainly not VISIBLE.
+ assertNotEquals(View.VISIBLE, getEndSideContentView().getVisibility());
+ assertNotEquals(View.VISIBLE, getClockView().getVisibility());
+
+ mockSecureCameraLaunchFinished();
+
+ assertNotEquals(View.VISIBLE, getEndSideContentView().getVisibility());
+ assertNotEquals(View.VISIBLE, getClockView().getVisibility());
+
+ mockSecureCameraLaunch(fragment, false /* launched */);
+
+ assertEquals(View.VISIBLE, getEndSideContentView().getVisibility());
+ assertEquals(View.VISIBLE, getClockView().getVisibility());
+ }
+
@Override
protected Fragment instantiate(Context context, String className, Bundle arguments) {
MockitoAnnotations.initMocks(this);
@@ -455,7 +498,9 @@
mOperatorNameViewControllerFactory,
mSecureSettings,
mExecutor,
- mDumpManager);
+ mDumpManager,
+ mStatusBarWindowStateController,
+ mKeyguardUpdateMonitor);
}
private void setUpDaggerComponent() {
@@ -478,6 +523,35 @@
mNotificationAreaInner);
}
+ /**
+ * Configure mocks to return values consistent with the secure camera animating itself launched
+ * over the keyguard.
+ */
+ private void mockSecureCameraLaunch(CollapsedStatusBarFragment fragment, boolean launched) {
+ when(mKeyguardUpdateMonitor.isSecureCameraLaunchedOverKeyguard()).thenReturn(launched);
+ when(mKeyguardStateController.isOccluded()).thenReturn(launched);
+
+ if (launched) {
+ fragment.onCameraLaunchGestureDetected(0 /* source */);
+ } else {
+ for (StatusBarWindowStateListener listener : mStatusBarWindowStateListeners) {
+ listener.onStatusBarWindowStateChanged(StatusBarManager.WINDOW_STATE_SHOWING);
+ }
+ }
+
+ fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
+ }
+
+ /**
+ * Configure mocks to return values consistent with the secure camera showing over the keyguard
+ * with its launch animation finished.
+ */
+ private void mockSecureCameraLaunchFinished() {
+ for (StatusBarWindowStateListener listener : mStatusBarWindowStateListeners) {
+ listener.onStatusBarWindowStateChanged(StatusBarManager.WINDOW_STATE_HIDDEN);
+ }
+ }
+
private CollapsedStatusBarFragment resumeAndGetFragment() {
mFragments.dispatchResume();
processAllMessages();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
index 1b62d5c..bd24922 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
@@ -28,6 +28,7 @@
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
import com.android.systemui.util.CarrierConfigTracker
+import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
@@ -77,6 +78,7 @@
MobileIconsInteractorImpl(
connectionsRepository,
carrierConfigTracker,
+ logger = mock(),
userSetupRepository,
testScope.backgroundScope,
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarViewTest.kt
index 3fe6983..e4c8fd0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarViewTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.pipeline.shared.ui.view
+import android.graphics.Rect
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
import androidx.test.filters.SmallTest
@@ -118,6 +119,22 @@
assertThat(view.isIconVisible).isEqualTo(false)
}
+ @Test
+ fun getDrawingRect_takesTranslationIntoAccount() {
+ val view = createAndInitView()
+
+ view.translationX = 50f
+ view.translationY = 60f
+
+ val drawingRect = Rect()
+ view.getDrawingRect(drawingRect)
+
+ assertThat(drawingRect.left).isEqualTo(view.left + 50)
+ assertThat(drawingRect.right).isEqualTo(view.right + 50)
+ assertThat(drawingRect.top).isEqualTo(view.top + 60)
+ assertThat(drawingRect.bottom).isEqualTo(view.bottom + 60)
+ }
+
private fun createAndInitView(): ModernStatusBarView {
val view = ModernStatusBarView(context, null)
binding = TestBinding()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt
index dd04ac4..fc7436a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt
@@ -79,6 +79,7 @@
@Mock private lateinit var viewUtil: ViewUtil
@Mock private lateinit var vibratorHelper: VibratorHelper
@Mock private lateinit var swipeGestureHandler: SwipeChipbarAwayGestureHandler
+ private lateinit var chipbarAnimator: TestChipbarAnimator
private lateinit var fakeWakeLockBuilder: WakeLockFake.Builder
private lateinit var fakeWakeLock: WakeLockFake
private lateinit var fakeClock: FakeSystemClock
@@ -98,6 +99,7 @@
fakeWakeLockBuilder.setWakeLock(fakeWakeLock)
uiEventLoggerFake = UiEventLoggerFake()
+ chipbarAnimator = TestChipbarAnimator()
underTest =
ChipbarCoordinator(
@@ -109,6 +111,7 @@
configurationController,
dumpManager,
powerManager,
+ chipbarAnimator,
falsingManager,
falsingCollector,
swipeGestureHandler,
@@ -371,6 +374,26 @@
verify(vibratorHelper).vibrate(VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK))
}
+ /** Regression test for b/266119467. */
+ @Test
+ fun displayView_animationFailure_viewsStillBecomeVisible() {
+ chipbarAnimator.allowAnimation = false
+
+ underTest.displayView(
+ createChipbarInfo(
+ Icon.Resource(R.id.check_box, null),
+ Text.Loaded("text"),
+ endItem = ChipbarEndItem.Loading,
+ )
+ )
+
+ val view = getChipbarView()
+ assertThat(view.getInnerView().alpha).isEqualTo(1f)
+ assertThat(view.getStartIconView().alpha).isEqualTo(1f)
+ assertThat(view.getLoadingIcon().alpha).isEqualTo(1f)
+ assertThat(view.getChipTextView().alpha).isEqualTo(1f)
+ }
+
@Test
fun updateView_viewUpdated() {
// First, display a view
@@ -453,6 +476,25 @@
verify(windowManager).removeView(chipbarView)
}
+ /** Regression test for b/266209420. */
+ @Test
+ fun removeView_animationFailure_viewStillRemoved() {
+ chipbarAnimator.allowAnimation = false
+
+ underTest.displayView(
+ createChipbarInfo(
+ Icon.Resource(R.drawable.ic_cake, contentDescription = null),
+ Text.Loaded("title text"),
+ endItem = ChipbarEndItem.Error,
+ ),
+ )
+ val chipbarView = getChipbarView()
+
+ underTest.removeView(DEVICE_ID, "test reason")
+
+ verify(windowManager).removeView(chipbarView)
+ }
+
@Test
fun swipeToDismiss_false_neverListensForGesture() {
underTest.displayView(
@@ -560,8 +602,9 @@
private fun ViewGroup.getStartIconView() = this.requireViewById<ImageView>(R.id.start_icon)
- private fun ViewGroup.getChipText(): String =
- (this.requireViewById<TextView>(R.id.text)).text as String
+ private fun ViewGroup.getChipTextView() = this.requireViewById<TextView>(R.id.text)
+
+ private fun ViewGroup.getChipText(): String = this.getChipTextView().text as String
private fun ViewGroup.getLoadingIcon(): View = this.requireViewById(R.id.loading)
@@ -574,6 +617,25 @@
verify(windowManager).addView(viewCaptor.capture(), any())
return viewCaptor.value as ViewGroup
}
+
+ /** Test class that lets us disallow animations. */
+ inner class TestChipbarAnimator : ChipbarAnimator() {
+ var allowAnimation: Boolean = true
+
+ override fun animateViewIn(innerView: ViewGroup, onAnimationEnd: Runnable): Boolean {
+ if (!allowAnimation) {
+ return false
+ }
+ return super.animateViewIn(innerView, onAnimationEnd)
+ }
+
+ override fun animateViewOut(innerView: ViewGroup, onAnimationEnd: Runnable): Boolean {
+ if (!allowAnimation) {
+ return false
+ }
+ return super.animateViewOut(innerView, onAnimationEnd)
+ }
+ }
}
private const val TIMEOUT = 10000
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
index 034c618..ccf378a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
@@ -17,12 +17,17 @@
package com.android.systemui.user.data.repository
+import android.app.IActivityManager
+import android.app.UserSwitchObserver
import android.content.pm.UserInfo
+import android.os.IRemoteCallback
import android.os.UserHandle
import android.os.UserManager
import android.provider.Settings
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR
import com.android.systemui.settings.FakeUserTracker
import com.android.systemui.user.data.model.UserSwitcherSettingsModel
import com.android.systemui.util.settings.FakeSettings
@@ -39,7 +44,14 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
import org.mockito.Mock
+import org.mockito.Mockito.any
+import org.mockito.Mockito.anyString
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
@@ -48,6 +60,8 @@
class UserRepositoryImplTest : SysuiTestCase() {
@Mock private lateinit var manager: UserManager
+ @Mock private lateinit var activityManager: IActivityManager
+ @Captor private lateinit var userSwitchObserver: ArgumentCaptor<UserSwitchObserver>
private lateinit var underTest: UserRepositoryImpl
@@ -214,6 +228,34 @@
assertThat(selectedUserInfo?.id).isEqualTo(1)
}
+ @Test
+ fun userSwitchingInProgress_registersOnlyOneUserSwitchObserver() = runSelfCancelingTest {
+ underTest = create(this)
+
+ underTest.userSwitchingInProgress.launchIn(this)
+ underTest.userSwitchingInProgress.launchIn(this)
+ underTest.userSwitchingInProgress.launchIn(this)
+
+ verify(activityManager, times(1)).registerUserSwitchObserver(any(), anyString())
+ }
+
+ @Test
+ fun userSwitchingInProgress_propagatesStateFromActivityManager() = runSelfCancelingTest {
+ underTest = create(this)
+ verify(activityManager)
+ .registerUserSwitchObserver(userSwitchObserver.capture(), anyString())
+
+ userSwitchObserver.value.onUserSwitching(0, mock(IRemoteCallback::class.java))
+
+ var mostRecentSwitchingValue = false
+ underTest.userSwitchingInProgress.onEach { mostRecentSwitchingValue = it }.launchIn(this)
+
+ assertThat(mostRecentSwitchingValue).isTrue()
+
+ userSwitchObserver.value.onUserSwitchComplete(0)
+ assertThat(mostRecentSwitchingValue).isFalse()
+ }
+
private fun createUserInfo(
id: Int,
isGuest: Boolean,
@@ -280,6 +322,8 @@
}
private fun create(scope: CoroutineScope = TestCoroutineScope()): UserRepositoryImpl {
+ val featureFlags = FakeFeatureFlags()
+ featureFlags.set(FACE_AUTH_REFACTOR, true)
return UserRepositoryImpl(
appContext = context,
manager = manager,
@@ -288,6 +332,8 @@
backgroundDispatcher = IMMEDIATE,
globalSettings = globalSettings,
tracker = tracker,
+ activityManager = activityManager,
+ featureFlags = featureFlags,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt
index 6c82cef..b94f816e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt
@@ -38,12 +38,6 @@
}
}
- fun set(flag: DeviceConfigBooleanFlag, value: Boolean) {
- if (booleanFlags.put(flag.id, value)?.let { value != it } != false) {
- notifyFlagChanged(flag)
- }
- }
-
fun set(flag: ResourceBooleanFlag, value: Boolean) {
if (booleanFlags.put(flag.id, value)?.let { value != it } != false) {
notifyFlagChanged(flag)
@@ -73,7 +67,7 @@
listeners.forEach { listener ->
listener.onFlagChanged(
object : FlagListenable.FlagEvent {
- override val flagId = flag.id
+ override val flagName = flag.name
override fun requestNoRestart() {}
}
)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt
similarity index 95%
rename from packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricRepository.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt
index f3e52de..044679d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt
@@ -21,7 +21,7 @@
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
-class FakeBiometricRepository : BiometricRepository {
+class FakeBiometricSettingsRepository : BiometricSettingsRepository {
private val _isFingerprintEnrolled = MutableStateFlow<Boolean>(false)
override val isFingerprintEnrolled: StateFlow<Boolean> = _isFingerprintEnrolled.asStateFlow()
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardBouncerRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardBouncerRepository.kt
new file mode 100644
index 0000000..d0383e9
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardBouncerRepository.kt
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.data.repository
+
+import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN
+import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel
+import com.android.systemui.keyguard.shared.model.KeyguardBouncerModel
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+/** Fake implementation of [KeyguardRepository] */
+class FakeKeyguardBouncerRepository : KeyguardBouncerRepository {
+ private val _primaryBouncerVisible = MutableStateFlow(false)
+ override val primaryBouncerVisible = _primaryBouncerVisible.asStateFlow()
+ private val _primaryBouncerShow = MutableStateFlow<KeyguardBouncerModel?>(null)
+ override val primaryBouncerShow = _primaryBouncerShow.asStateFlow()
+ private val _primaryBouncerShowingSoon = MutableStateFlow(false)
+ override val primaryBouncerShowingSoon = _primaryBouncerShowingSoon.asStateFlow()
+ private val _primaryBouncerHide = MutableStateFlow(false)
+ override val primaryBouncerHide = _primaryBouncerHide.asStateFlow()
+ private val _primaryBouncerStartingToHide = MutableStateFlow(false)
+ override val primaryBouncerStartingToHide = _primaryBouncerStartingToHide.asStateFlow()
+ private val _primaryBouncerDisappearAnimation = MutableStateFlow<Runnable?>(null)
+ override val primaryBouncerStartingDisappearAnimation =
+ _primaryBouncerDisappearAnimation.asStateFlow()
+ private val _primaryBouncerScrimmed = MutableStateFlow(false)
+ override val primaryBouncerScrimmed = _primaryBouncerScrimmed.asStateFlow()
+ private val _panelExpansionAmount = MutableStateFlow(EXPANSION_HIDDEN)
+ override val panelExpansionAmount = _panelExpansionAmount.asStateFlow()
+ private val _keyguardPosition = MutableStateFlow(0f)
+ override val keyguardPosition = _keyguardPosition.asStateFlow()
+ private val _onScreenTurnedOff = MutableStateFlow(false)
+ override val onScreenTurnedOff = _onScreenTurnedOff.asStateFlow()
+ private val _isBackButtonEnabled = MutableStateFlow<Boolean?>(null)
+ override val isBackButtonEnabled = _isBackButtonEnabled.asStateFlow()
+ private val _keyguardAuthenticated = MutableStateFlow<Boolean?>(null)
+ override val keyguardAuthenticated = _keyguardAuthenticated.asStateFlow()
+ private val _showMessage = MutableStateFlow<BouncerShowMessageModel?>(null)
+ override val showMessage = _showMessage.asStateFlow()
+ private val _resourceUpdateRequests = MutableStateFlow(false)
+ override val resourceUpdateRequests = _resourceUpdateRequests.asStateFlow()
+ override val bouncerPromptReason = 0
+ override val bouncerErrorMessage: CharSequence? = null
+ private val _isAlternateBouncerVisible = MutableStateFlow(false)
+ override val isAlternateBouncerVisible = _isAlternateBouncerVisible.asStateFlow()
+ override var lastAlternateBouncerVisibleTime: Long = 0L
+ private val _isAlternateBouncerUIAvailable = MutableStateFlow<Boolean>(false)
+ override val isAlternateBouncerUIAvailable = _isAlternateBouncerUIAvailable.asStateFlow()
+
+ override fun setPrimaryScrimmed(isScrimmed: Boolean) {
+ _primaryBouncerScrimmed.value = isScrimmed
+ }
+
+ override fun setPrimaryVisible(isVisible: Boolean) {
+ _primaryBouncerVisible.value = isVisible
+ }
+
+ override fun setAlternateVisible(isVisible: Boolean) {
+ _isAlternateBouncerVisible.value = isVisible
+ }
+
+ override fun setAlternateBouncerUIAvailable(isAvailable: Boolean) {
+ _isAlternateBouncerUIAvailable.value = isAvailable
+ }
+
+ override fun setPrimaryShow(keyguardBouncerModel: KeyguardBouncerModel?) {
+ _primaryBouncerShow.value = keyguardBouncerModel
+ }
+
+ override fun setPrimaryShowingSoon(showingSoon: Boolean) {
+ _primaryBouncerShowingSoon.value = showingSoon
+ }
+
+ override fun setPrimaryHide(hide: Boolean) {
+ _primaryBouncerHide.value = hide
+ }
+
+ override fun setPrimaryStartingToHide(startingToHide: Boolean) {
+ _primaryBouncerStartingToHide.value = startingToHide
+ }
+
+ override fun setPrimaryStartDisappearAnimation(runnable: Runnable?) {
+ _primaryBouncerDisappearAnimation.value = runnable
+ }
+
+ override fun setPanelExpansion(panelExpansion: Float) {
+ _panelExpansionAmount.value = panelExpansion
+ }
+
+ override fun setKeyguardPosition(keyguardPosition: Float) {
+ _keyguardPosition.value = keyguardPosition
+ }
+
+ override fun setResourceUpdateRequests(willUpdateResources: Boolean) {
+ _resourceUpdateRequests.value = willUpdateResources
+ }
+
+ override fun setShowMessage(bouncerShowMessageModel: BouncerShowMessageModel?) {
+ _showMessage.value = bouncerShowMessageModel
+ }
+
+ override fun setKeyguardAuthenticated(keyguardAuthenticated: Boolean?) {
+ _keyguardAuthenticated.value = keyguardAuthenticated
+ }
+
+ override fun setIsBackButtonEnabled(isBackButtonEnabled: Boolean) {
+ _isBackButtonEnabled.value = isBackButtonEnabled
+ }
+
+ override fun setOnScreenTurnedOff(onScreenTurnedOff: Boolean) {
+ _onScreenTurnedOff.value = onScreenTurnedOff
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt
index ea5a302..1a8e244 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt
@@ -39,6 +39,10 @@
private val _selectedUserInfo = MutableStateFlow<UserInfo?>(null)
override val selectedUserInfo: Flow<UserInfo> = _selectedUserInfo.filterNotNull()
+ private val _userSwitchingInProgress = MutableStateFlow(false)
+ override val userSwitchingInProgress: Flow<Boolean>
+ get() = _userSwitchingInProgress
+
override var lastSelectedNonGuestUserId: Int = UserHandle.USER_SYSTEM
private var _isGuestUserAutoCreated: Boolean = false
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index dfb2467..fa3a3bf 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -2194,19 +2194,19 @@
AudioSystem.STREAM_ASSISTANT : AudioSystem.STREAM_MUSIC;
if (mIsSingleVolume) {
- mStreamVolumeAlias = STREAM_VOLUME_ALIAS_TELEVISION;
+ mStreamVolumeAlias = STREAM_VOLUME_ALIAS_TELEVISION.clone();
dtmfStreamAlias = AudioSystem.STREAM_MUSIC;
} else if (mUseVolumeGroupAliases) {
- mStreamVolumeAlias = STREAM_VOLUME_ALIAS_NONE;
+ mStreamVolumeAlias = STREAM_VOLUME_ALIAS_NONE.clone();
dtmfStreamAlias = AudioSystem.STREAM_DTMF;
} else {
switch (mPlatformType) {
case AudioSystem.PLATFORM_VOICE:
- mStreamVolumeAlias = STREAM_VOLUME_ALIAS_VOICE;
+ mStreamVolumeAlias = STREAM_VOLUME_ALIAS_VOICE.clone();
dtmfStreamAlias = AudioSystem.STREAM_RING;
break;
default:
- mStreamVolumeAlias = STREAM_VOLUME_ALIAS_DEFAULT;
+ mStreamVolumeAlias = STREAM_VOLUME_ALIAS_DEFAULT.clone();
dtmfStreamAlias = AudioSystem.STREAM_MUSIC;
}
if (!mNotifAliasRing) {
@@ -10211,7 +10211,7 @@
private static final int CHECK_MODE_FOR_UID_PERIOD_MS = 6000;
private static final String ACTION_CHECK_MUSIC_ACTIVE =
- AudioService.class.getSimpleName() + ".CHECK_MUSIC_ACTIVE";
+ "com.android.server.audio.action.CHECK_MUSIC_ACTIVE";
private static final int REQUEST_CODE_CHECK_MUSIC_ACTIVE = 1;
private int safeMediaVolumeIndex(int device) {
diff --git a/services/core/java/com/android/server/audio/AudioSystemAdapter.java b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
index 2588371..a17b4bf 100644
--- a/services/core/java/com/android/server/audio/AudioSystemAdapter.java
+++ b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
@@ -29,8 +29,12 @@
import com.android.internal.annotations.GuardedBy;
import java.io.PrintWriter;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@@ -60,8 +64,12 @@
private String[] mMethodNames = {"getDevicesForAttributes"};
private static final boolean USE_CACHE_FOR_GETDEVICES = true;
+ private static final Object sDeviceCacheLock = new Object();
+ @GuardedBy("sDeviceCacheLock")
private ConcurrentHashMap<Pair<AudioAttributes, Boolean>, ArrayList<AudioDeviceAttributes>>
mDevicesForAttrCache;
+ @GuardedBy("sDeviceCacheLock")
+ private long mDevicesForAttributesCacheClearTimeMs = System.currentTimeMillis();
private int[] mMethodCacheHit;
private static final Object sRoutingListenerLock = new Object();
@GuardedBy("sRoutingListenerLock")
@@ -147,9 +155,11 @@
AudioSystem.setRoutingCallback(sSingletonDefaultAdapter);
AudioSystem.setVolumeRangeInitRequestCallback(sSingletonDefaultAdapter);
if (USE_CACHE_FOR_GETDEVICES) {
- sSingletonDefaultAdapter.mDevicesForAttrCache =
- new ConcurrentHashMap<>(AudioSystem.getNumStreamTypes());
- sSingletonDefaultAdapter.mMethodCacheHit = new int[NB_MEASUREMENTS];
+ synchronized (sDeviceCacheLock) {
+ sSingletonDefaultAdapter.mDevicesForAttrCache =
+ new ConcurrentHashMap<>(AudioSystem.getNumStreamTypes());
+ sSingletonDefaultAdapter.mMethodCacheHit = new int[NB_MEASUREMENTS];
+ }
}
if (ENABLE_GETDEVICES_STATS) {
sSingletonDefaultAdapter.mMethodCallCounter = new int[NB_MEASUREMENTS];
@@ -163,8 +173,9 @@
if (DEBUG_CACHE) {
Log.d(TAG, "---- clearing cache ----------");
}
- if (mDevicesForAttrCache != null) {
- synchronized (mDevicesForAttrCache) {
+ synchronized (sDeviceCacheLock) {
+ if (mDevicesForAttrCache != null) {
+ mDevicesForAttributesCacheClearTimeMs = System.currentTimeMillis();
mDevicesForAttrCache.clear();
}
}
@@ -193,7 +204,7 @@
if (USE_CACHE_FOR_GETDEVICES) {
ArrayList<AudioDeviceAttributes> res;
final Pair<AudioAttributes, Boolean> key = new Pair(attributes, forVolume);
- synchronized (mDevicesForAttrCache) {
+ synchronized (sDeviceCacheLock) {
res = mDevicesForAttrCache.get(key);
if (res == null) {
// result from AudioSystem guaranteed non-null, but could be invalid
@@ -508,23 +519,31 @@
*/
public void dump(PrintWriter pw) {
pw.println("\nAudioSystemAdapter:");
- pw.println(" mDevicesForAttrCache:");
- if (mDevicesForAttrCache != null) {
- for (Map.Entry<Pair<AudioAttributes, Boolean>, ArrayList<AudioDeviceAttributes>>
- entry : mDevicesForAttrCache.entrySet()) {
- final AudioAttributes attributes = entry.getKey().first;
- try {
- final int stream = attributes.getVolumeControlStream();
- pw.println("\t" + attributes + " forVolume: " + entry.getKey().second
- + " stream: "
- + AudioSystem.STREAM_NAMES[stream] + "(" + stream + ")");
- for (AudioDeviceAttributes devAttr : entry.getValue()) {
- pw.println("\t\t" + devAttr);
+ final DateTimeFormatter formatter = DateTimeFormatter
+ .ofPattern("MM-dd HH:mm:ss:SSS")
+ .withLocale(Locale.US)
+ .withZone(ZoneId.systemDefault());
+ synchronized (sDeviceCacheLock) {
+ pw.println(" last cache clear time: " + formatter.format(
+ Instant.ofEpochMilli(mDevicesForAttributesCacheClearTimeMs)));
+ pw.println(" mDevicesForAttrCache:");
+ if (mDevicesForAttrCache != null) {
+ for (Map.Entry<Pair<AudioAttributes, Boolean>, ArrayList<AudioDeviceAttributes>>
+ entry : mDevicesForAttrCache.entrySet()) {
+ final AudioAttributes attributes = entry.getKey().first;
+ try {
+ final int stream = attributes.getVolumeControlStream();
+ pw.println("\t" + attributes + " forVolume: " + entry.getKey().second
+ + " stream: "
+ + AudioSystem.STREAM_NAMES[stream] + "(" + stream + ")");
+ for (AudioDeviceAttributes devAttr : entry.getValue()) {
+ pw.println("\t\t" + devAttr);
+ }
+ } catch (IllegalArgumentException e) {
+ // dump could fail if attributes do not map to a stream.
+ pw.println("\t dump failed for attributes: " + attributes);
+ Log.e(TAG, "dump failed", e);
}
- } catch (IllegalArgumentException e) {
- // dump could fail if attributes do not map to a stream.
- pw.println("\t dump failed for attributes: " + attributes);
- Log.e(TAG, "dump failed", e);
}
}
}
diff --git a/services/core/java/com/android/server/display/color/ColorDisplayService.java b/services/core/java/com/android/server/display/color/ColorDisplayService.java
index 017b96c..2276715 100644
--- a/services/core/java/com/android/server/display/color/ColorDisplayService.java
+++ b/services/core/java/com/android/server/display/color/ColorDisplayService.java
@@ -107,11 +107,6 @@
Matrix.setIdentityM(MATRIX_IDENTITY, 0);
}
- /**
- * The transition time, in milliseconds, for Night Display to turn on/off.
- */
- private static final long TRANSITION_DURATION = 3000L;
-
private static final int MSG_USER_CHANGED = 0;
private static final int MSG_SET_UP = 1;
private static final int MSG_APPLY_NIGHT_DISPLAY_IMMEDIATE = 2;
@@ -661,7 +656,7 @@
TintValueAnimator valueAnimator = TintValueAnimator.ofMatrix(COLOR_MATRIX_EVALUATOR,
from == null ? MATRIX_IDENTITY : from, to);
tintController.setAnimator(valueAnimator);
- valueAnimator.setDuration(TRANSITION_DURATION);
+ valueAnimator.setDuration(tintController.getTransitionDurationMilliseconds());
valueAnimator.setInterpolator(AnimationUtils.loadInterpolator(
getContext(), android.R.interpolator.fast_out_slow_in));
valueAnimator.addUpdateListener((ValueAnimator animator) -> {
diff --git a/services/core/java/com/android/server/display/color/DisplayWhiteBalanceTintController.java b/services/core/java/com/android/server/display/color/DisplayWhiteBalanceTintController.java
index 93a78c1..f27ccc7 100644
--- a/services/core/java/com/android/server/display/color/DisplayWhiteBalanceTintController.java
+++ b/services/core/java/com/android/server/display/color/DisplayWhiteBalanceTintController.java
@@ -59,7 +59,8 @@
private float[] mCurrentColorTemperatureXYZ;
@VisibleForTesting
boolean mSetUp = false;
- private float[] mMatrixDisplayWhiteBalance = new float[16];
+ private final float[] mMatrixDisplayWhiteBalance = new float[16];
+ private long mTransitionDuration;
private Boolean mIsAvailable;
// This feature becomes disallowed if the device is in an unsupported strong/light state.
private boolean mIsAllowed = true;
@@ -119,6 +120,9 @@
final int colorTemperature = res.getInteger(
R.integer.config_displayWhiteBalanceColorTemperatureDefault);
+ mTransitionDuration = res.getInteger(
+ R.integer.config_displayWhiteBalanceTransitionTime);
+
synchronized (mLock) {
mDisplayColorSpaceRGB = displayColorSpaceRGB;
mDisplayNominalWhiteXYZ = displayNominalWhiteXYZ;
@@ -232,6 +236,11 @@
}
@Override
+ public long getTransitionDurationMilliseconds() {
+ return mTransitionDuration;
+ }
+
+ @Override
public void dump(PrintWriter pw) {
synchronized (mLock) {
pw.println(" mSetUp = " + mSetUp);
diff --git a/services/core/java/com/android/server/display/color/TintController.java b/services/core/java/com/android/server/display/color/TintController.java
index 422dd32..c53ac06 100644
--- a/services/core/java/com/android/server/display/color/TintController.java
+++ b/services/core/java/com/android/server/display/color/TintController.java
@@ -16,7 +16,6 @@
package com.android.server.display.color;
-import android.animation.ValueAnimator;
import android.content.Context;
import android.util.Slog;
@@ -24,6 +23,11 @@
abstract class TintController {
+ /**
+ * The default transition time, in milliseconds, for color transforms to turn on/off.
+ */
+ private static final long TRANSITION_DURATION = 3000L;
+
private ColorDisplayService.TintValueAnimator mAnimator;
private Boolean mIsActivated;
@@ -66,6 +70,10 @@
return mIsActivated == null;
}
+ public long getTransitionDurationMilliseconds() {
+ return TRANSITION_DURATION;
+ }
+
/**
* Dump debug information.
*/
diff --git a/services/core/java/com/android/server/dreams/DreamController.java b/services/core/java/com/android/server/dreams/DreamController.java
index db9deb1..f87a146 100644
--- a/services/core/java/com/android/server/dreams/DreamController.java
+++ b/services/core/java/com/android/server/dreams/DreamController.java
@@ -244,7 +244,10 @@
}
mListener.onDreamStopped(dream.mToken);
+ } else if (dream.mCanDoze && !mCurrentDream.mCanDoze) {
+ mListener.stopDozing(dream.mToken);
}
+
} finally {
Trace.traceEnd(Trace.TRACE_TAG_POWER);
}
@@ -289,6 +292,7 @@
*/
public interface Listener {
void onDreamStopped(Binder token);
+ void stopDozing(Binder token);
}
private final class DreamRecord implements DeathRecipient, ServiceConnection {
diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java
index bb1e393..148b80e 100644
--- a/services/core/java/com/android/server/dreams/DreamManagerService.java
+++ b/services/core/java/com/android/server/dreams/DreamManagerService.java
@@ -499,7 +499,12 @@
}
synchronized (mLock) {
- if (mCurrentDream != null && mCurrentDream.token == token && mCurrentDream.isDozing) {
+ if (mCurrentDream == null) {
+ return;
+ }
+
+ final boolean sameDream = mCurrentDream.token == token;
+ if ((sameDream && mCurrentDream.isDozing) || (!sameDream && !mCurrentDream.isDozing)) {
mCurrentDream.isDozing = false;
mDozeWakeLock.release();
mPowerManagerInternal.setDozeOverrideFromDreamManager(
@@ -765,6 +770,11 @@
}
}
}
+
+ @Override
+ public void stopDozing(Binder token) {
+ stopDozingInternal(token);
+ }
};
private final ContentObserver mDozeEnabledObserver = new ContentObserver(null) {
diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java
index 39acaee..25fefad 100644
--- a/services/core/java/com/android/server/notification/ManagedServices.java
+++ b/services/core/java/com/android/server/notification/ManagedServices.java
@@ -132,6 +132,7 @@
// contains connections to all connected services, including app services
// and system services
+ @GuardedBy("mMutex")
private final ArrayList<ManagedServiceInfo> mServices = new ArrayList<>();
/**
* The services that have been bound by us. If the service is also connected, it will also
@@ -157,7 +158,8 @@
// List of approved packages or components (by user, then by primary/secondary) that are
// allowed to be bound as managed services. A package or component appearing in this list does
// not mean that we are currently bound to said package/component.
- protected ArrayMap<Integer, ArrayMap<Boolean, ArraySet<String>>> mApproved = new ArrayMap<>();
+ protected final ArrayMap<Integer, ArrayMap<Boolean, ArraySet<String>>> mApproved =
+ new ArrayMap<>();
// List of packages or components (by user) that are configured to be enabled/disabled
// explicitly by the user
@@ -316,6 +318,7 @@
return changes;
}
+ @GuardedBy("mApproved")
private boolean clearUserSetFlagLocked(ComponentName component, int userId) {
String approvedValue = getApprovedValue(component.flattenToString());
ArraySet<String> userSet = mUserSetServices.get(userId);
@@ -376,8 +379,8 @@
pw.println(" " + cmpt);
}
- pw.println(" Live " + getCaption() + "s (" + mServices.size() + "):");
synchronized (mMutex) {
+ pw.println(" Live " + getCaption() + "s (" + mServices.size() + "):");
for (ManagedServiceInfo info : mServices) {
if (filter != null && !filter.matches(info.component)) continue;
pw.println(" " + info.component
@@ -1011,10 +1014,12 @@
return null;
}
final IBinder token = service.asBinder();
- final int N = mServices.size();
- for (int i = 0; i < N; i++) {
- final ManagedServiceInfo info = mServices.get(i);
- if (info.service.asBinder() == token) return info;
+ synchronized (mMutex) {
+ final int nServices = mServices.size();
+ for (int i = 0; i < nServices; i++) {
+ final ManagedServiceInfo info = mServices.get(i);
+ if (info.service.asBinder() == token) return info;
+ }
}
return null;
}
@@ -1488,10 +1493,12 @@
}
}
+ @GuardedBy("mMutex")
private void registerServiceLocked(final ComponentName name, final int userid) {
registerServiceLocked(name, userid, false /* isSystem */);
}
+ @GuardedBy("mMutex")
private void registerServiceLocked(final ComponentName name, final int userid,
final boolean isSystem) {
if (DEBUG) Slog.v(TAG, "registerService: " + name + " u=" + userid);
@@ -1622,6 +1629,7 @@
}
}
+ @GuardedBy("mMutex")
private void unregisterServiceLocked(ComponentName name, int userid) {
final int N = mServices.size();
for (int i = N - 1; i >= 0; i--) {
@@ -1656,6 +1664,7 @@
return serviceInfo;
}
+ @GuardedBy("mMutex")
private ManagedServiceInfo removeServiceLocked(int i) {
final ManagedServiceInfo info = mServices.remove(i);
onServiceRemovedLocked(info);
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 5767730..aede04b 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -1710,12 +1710,9 @@
return ROTATION_UNDEFINED;
}
if (activityOrientation == ActivityInfo.SCREEN_ORIENTATION_BEHIND) {
- // TODO(b/266280737): Use ActivityRecord#canDefineOrientationForActivitiesAbove
final ActivityRecord nextCandidate = getActivity(
- a -> a.getOverrideOrientation() != SCREEN_ORIENTATION_UNSET
- && a.getOverrideOrientation()
- != ActivityInfo.SCREEN_ORIENTATION_BEHIND,
- r, false /* includeBoundary */, true /* traverseTopToBottom */);
+ a -> a.canDefineOrientationForActivitiesAbove() /* callback */,
+ r /* boundary */, false /* includeBoundary */, true /* traverseTopToBottom */);
if (nextCandidate != null) {
r = nextCandidate;
}
diff --git a/services/core/java/com/android/server/wm/LetterboxConfiguration.java b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
index 800fe09..b64420a 100644
--- a/services/core/java/com/android/server/wm/LetterboxConfiguration.java
+++ b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
@@ -18,6 +18,7 @@
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.wm.LetterboxConfigurationDeviceConfig.KEY_ALLOW_IGNORE_ORIENTATION_REQUEST;
import static com.android.server.wm.LetterboxConfigurationDeviceConfig.KEY_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY;
import android.annotation.IntDef;
@@ -311,12 +312,23 @@
mDeviceConfig.updateFlagActiveStatus(
/* isActive */ mIsDisplayRotationImmersiveAppCompatPolicyEnabled,
/* key */ KEY_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY);
+ mDeviceConfig.updateFlagActiveStatus(
+ /* isActive */ true,
+ /* key */ KEY_ALLOW_IGNORE_ORIENTATION_REQUEST);
mLetterboxConfigurationPersister = letterboxConfigurationPersister;
mLetterboxConfigurationPersister.start();
}
/**
+ * Whether enabling ignoreOrientationRequest is allowed on the device. This value is controlled
+ * via {@link android.provider.DeviceConfig}.
+ */
+ boolean isIgnoreOrientationRequestAllowed() {
+ return mDeviceConfig.getFlag(KEY_ALLOW_IGNORE_ORIENTATION_REQUEST);
+ }
+
+ /**
* Overrides the aspect ratio of letterbox for fixed orientation. If given value is <= {@link
* #MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO}, both it and a value of {@link
* com.android.internal.R.dimen.config_fixedOrientationLetterboxAspectRatio} will be ignored and
diff --git a/services/core/java/com/android/server/wm/LetterboxConfigurationDeviceConfig.java b/services/core/java/com/android/server/wm/LetterboxConfigurationDeviceConfig.java
index cf123a1..3f067e3 100644
--- a/services/core/java/com/android/server/wm/LetterboxConfigurationDeviceConfig.java
+++ b/services/core/java/com/android/server/wm/LetterboxConfigurationDeviceConfig.java
@@ -38,10 +38,16 @@
private static final boolean DEFAULT_VALUE_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY =
true;
+ static final String KEY_ALLOW_IGNORE_ORIENTATION_REQUEST =
+ "allow_ignore_orientation_request";
+ private static final boolean DEFAULT_VALUE_ALLOW_IGNORE_ORIENTATION_REQUEST = true;
+
@VisibleForTesting
static final Map<String, Boolean> sKeyToDefaultValueMap = Map.of(
KEY_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY,
- DEFAULT_VALUE_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY
+ DEFAULT_VALUE_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY,
+ KEY_ALLOW_IGNORE_ORIENTATION_REQUEST,
+ DEFAULT_VALUE_ALLOW_IGNORE_ORIENTATION_REQUEST
);
// Whether enabling rotation compat policy for immersive apps that prevents auto rotation
@@ -52,6 +58,10 @@
private boolean mIsDisplayRotationImmersiveAppCompatPolicyEnabled =
DEFAULT_VALUE_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY;
+ // Whether enabling ignoreOrientationRequest is allowed on the device.
+ private boolean mIsAllowIgnoreOrientationRequest =
+ DEFAULT_VALUE_ALLOW_IGNORE_ORIENTATION_REQUEST;
+
// Set of active device configs that need to be updated in
// DeviceConfig.OnPropertiesChangedListener#onPropertiesChanged.
private final ArraySet<String> mActiveDeviceConfigsSet = new ArraySet<>();
@@ -93,6 +103,8 @@
switch (key) {
case KEY_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY:
return mIsDisplayRotationImmersiveAppCompatPolicyEnabled;
+ case KEY_ALLOW_IGNORE_ORIENTATION_REQUEST:
+ return mIsAllowIgnoreOrientationRequest;
default:
throw new AssertionError("Unexpected flag name: " + key);
}
@@ -108,6 +120,10 @@
mIsDisplayRotationImmersiveAppCompatPolicyEnabled =
getDeviceConfig(key, defaultValue);
break;
+ case KEY_ALLOW_IGNORE_ORIENTATION_REQUEST:
+ mIsAllowIgnoreOrientationRequest =
+ getDeviceConfig(key, defaultValue);
+ break;
default:
throw new AssertionError("Unexpected flag name: " + key);
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 5065014..2911890 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -4170,7 +4170,8 @@
* <p>Note: this assumes that {@link #mGlobalLock} is held by the caller.
*/
boolean isIgnoreOrientationRequestDisabled() {
- return mIsIgnoreOrientationRequestDisabled;
+ return mIsIgnoreOrientationRequestDisabled
+ || !mLetterboxConfiguration.isIgnoreOrientationRequestAllowed();
}
@Override
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index ae03fbb..45dacbb 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -2387,7 +2387,11 @@
// IME parent may failed to attach to the app during rotating the screen.
// See DisplayContent#shouldImeAttachedToApp, DisplayContent#isImeControlledByApp
if (windowConfigChanged) {
- getDisplayContent().updateImeControlTarget();
+ // If the window was the IME layering target, updates the IME surface parent in case
+ // the IME surface may be wrongly positioned when the window configuration affects the
+ // IME surface association. (e.g. Attach IME surface on the display instead of the
+ // app when the app bounds being letterboxed.)
+ mDisplayContent.updateImeControlTarget(isImeLayeringTarget() /* updateImeParent */);
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index 0bcee92..2be193b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -2313,6 +2313,29 @@
}
@Test
+ public void testDisplayIgnoreOrientationRequest_disabledViaDeviceConfig_orientationRespected() {
+ // Set up a display in landscape
+ setUpDisplaySizeWithApp(2800, 1400);
+
+ final ActivityRecord activity = buildActivityRecord(/* supportsSizeChanges= */ false,
+ RESIZE_MODE_UNRESIZEABLE, SCREEN_ORIENTATION_PORTRAIT);
+ activity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+
+ spyOn(activity.mWmService.mLetterboxConfiguration);
+ doReturn(true).when(activity.mWmService.mLetterboxConfiguration)
+ .isIgnoreOrientationRequestAllowed();
+
+ // Display should not be rotated.
+ assertEquals(SCREEN_ORIENTATION_UNSPECIFIED, activity.mDisplayContent.getOrientation());
+
+ doReturn(false).when(activity.mWmService.mLetterboxConfiguration)
+ .isIgnoreOrientationRequestAllowed();
+
+ // Display should be rotated.
+ assertEquals(SCREEN_ORIENTATION_PORTRAIT, activity.mDisplayContent.getOrientation());
+ }
+
+ @Test
public void testSandboxDisplayApis_unresizableAppNotSandboxed() {
// Set up a display in landscape with an unresizable app.
setUpDisplaySizeWithApp(2500, 1000);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index fd3776f..514aec1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -1138,7 +1138,9 @@
spyOn(app.getDisplayContent());
app.mActivityRecord.getRootTask().setWindowingMode(WINDOWING_MODE_FULLSCREEN);
- verify(app.getDisplayContent()).updateImeControlTarget();
+ // Expect updateImeParent will be invoked when the configuration of the IME control
+ // target has changed.
+ verify(app.getDisplayContent()).updateImeControlTarget(eq(true) /* updateImeParent */);
assertEquals(mAppWindow, mDisplayContent.getImeTarget(IME_TARGET_CONTROL).getWindow());
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt
index aacc17a4..3361502 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt
@@ -113,4 +113,18 @@
}
return false
}
+
+ fun toggleFixPortraitOrientation(wmHelper: WindowManagerStateHelper) {
+ val button = uiDevice.wait(Until.findObject(By.res(getPackage(),
+ "toggle_fixed_portrait_btn")), FIND_TIMEOUT)
+ require(button != null) {
+ "Button not found, this usually happens when the device " +
+ "was left in an unknown state (e.g. Screen turned off)"
+ }
+ button.click()
+ mInstrumentation.waitForIdleSync()
+ // Ensure app relaunching transition finish and the IME has shown
+ wmHelper.waitForAppTransitionIdle()
+ wmHelper.waitImeShown()
+ }
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToFixedPortraitAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToFixedPortraitAppTest.kt
new file mode 100644
index 0000000..3b3bce6
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToFixedPortraitAppTest.kt
@@ -0,0 +1,129 @@
+/*
+ * 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.server.wm.flicker.ime
+
+import android.app.Instrumentation
+import android.platform.test.annotations.Postsubmit
+import android.view.Surface
+import android.view.WindowManagerPolicyConstants
+import androidx.test.filters.RequiresDevice
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.FlickerBuilderProvider
+import com.android.server.wm.flicker.FlickerParametersRunnerFactory
+import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.FlickerTestParameterFactory
+import com.android.server.wm.flicker.annotation.Group2
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper
+import com.android.server.wm.flicker.helpers.WindowUtils
+import com.android.server.wm.flicker.traces.region.RegionSubject
+import com.android.server.wm.traces.common.FlickerComponentName
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test IME window shown on the app with fixing portrait orientation.
+ * To run this test: `atest FlickerTests:OpenImeWindowToFixedPortraitAppTest`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Group2
+class OpenImeWindowToFixedPortraitAppTest (private val testSpec: FlickerTestParameter) {
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val testApp = ImeAppAutoFocusHelper(instrumentation, testSpec.startRotation)
+
+ @FlickerBuilderProvider
+ fun buildFlicker(): FlickerBuilder {
+ return FlickerBuilder(instrumentation).apply {
+ setup {
+ eachRun {
+ testApp.launchViaIntent(wmHelper)
+ testApp.openIME(device, wmHelper)
+ // Enable letterbox when the app calls setRequestedOrientation
+ device.executeShellCommand("cmd window set-ignore-orientation-request true")
+ }
+ }
+ transitions {
+ testApp.toggleFixPortraitOrientation(wmHelper)
+ }
+ teardown {
+ eachRun {
+ testApp.exit()
+ device.executeShellCommand("cmd window set-ignore-orientation-request false")
+ }
+ }
+ }
+ }
+
+ @Postsubmit
+ @Test
+ fun imeLayerVisibleStart() {
+ testSpec.assertLayersStart {
+ this.isVisible(FlickerComponentName.IME)
+ }
+ }
+
+ @Postsubmit
+ @Test
+ fun imeLayerExistsEnd() {
+ testSpec.assertLayersEnd {
+ this.isVisible(FlickerComponentName.IME)
+ }
+ }
+
+ @Postsubmit
+ @Test
+ fun imeLayerVisibleRegionKeepsTheSame() {
+ var imeLayerVisibleRegionBeforeTransition: RegionSubject? = null
+ testSpec.assertLayersStart {
+ imeLayerVisibleRegionBeforeTransition = this.visibleRegion(FlickerComponentName.IME)
+ }
+ testSpec.assertLayersEnd {
+ this.visibleRegion(FlickerComponentName.IME)
+ .coversExactly(imeLayerVisibleRegionBeforeTransition!!.region)
+ }
+ }
+
+ @Postsubmit
+ @Test
+ fun appWindowWithLetterboxCoversExactlyOnScreen() {
+ val displayBounds = WindowUtils.getDisplayBounds(testSpec.startRotation)
+ testSpec.assertLayersEnd {
+ this.visibleRegion(testApp.component, FlickerComponentName.LETTERBOX)
+ .coversExactly(displayBounds)
+ }
+ }
+
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<FlickerTestParameter> {
+ return FlickerTestParameterFactory.getInstance()
+ .getConfigNonRotationTests(
+ supportedRotations = listOf(Surface.ROTATION_90, Surface.ROTATION_270),
+ supportedNavigationModes = listOf(
+ WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY,
+ WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY
+ )
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
index b8ef195..efd80f2 100644
--- a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
@@ -45,7 +45,7 @@
android:theme="@style/CutoutShortEdges"
android:taskAffinity="com.android.server.wm.flicker.testapp.ImeActivityAutoFocus"
android:windowSoftInputMode="stateVisible"
- android:configChanges="orientation|screenSize"
+ android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
android:label="ImeAppAutoFocus"
android:exported="true">
<intent-filter>
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_ime.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_ime.xml
index baaf707..e71fe80 100644
--- a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_ime.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_ime.xml
@@ -26,14 +26,27 @@
android:layout_width="match_parent"
android:imeOptions="flagNoExtractUi"
android:inputType="text"/>
- <Button
- android:id="@+id/finish_activity_btn"
+ <LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:text="Finish activity" />
- <Button
- android:id="@+id/start_dialog_themed_activity_btn"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:text="Start dialog themed activity" />
+ android:layout_height="match_parent"
+ android:orientation="horizontal">
+ <Button
+ android:id="@+id/finish_activity_btn"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Finish activity" />
+ <Button
+ android:id="@+id/start_dialog_themed_activity_btn"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Dialog activity" />
+ <ToggleButton
+ android:id="@+id/toggle_fixed_portrait_btn"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textOn="Portrait (On)"
+ android:textOff="Portrait (Off)"
+ />
+ </LinearLayout>
</LinearLayout>
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeActivityAutoFocus.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeActivityAutoFocus.java
index bb200f1..7ee8deb 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeActivityAutoFocus.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeActivityAutoFocus.java
@@ -16,21 +16,29 @@
package com.android.server.wm.flicker.testapp;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+
import android.content.Intent;
import android.widget.Button;
import android.widget.EditText;
+import android.widget.ToggleButton;
public class ImeActivityAutoFocus extends ImeActivity {
-
@Override
protected void onStart() {
super.onStart();
- EditText editTextField = findViewById(R.id.plain_text_input);
- editTextField.requestFocus();
-
Button startThemedActivityButton = findViewById(R.id.start_dialog_themed_activity_btn);
startThemedActivityButton.setOnClickListener(
button -> startActivity(new Intent(this, DialogThemedActivity.class)));
+
+ ToggleButton toggleFixedPortraitButton = findViewById(R.id.toggle_fixed_portrait_btn);
+ toggleFixedPortraitButton.setOnCheckedChangeListener(
+ (button, isChecked) -> setRequestedOrientation(
+ isChecked ? SCREEN_ORIENTATION_PORTRAIT : SCREEN_ORIENTATION_UNSPECIFIED));
+
+ EditText editTextField = findViewById(R.id.plain_text_input);
+ editTextField.requestFocus();
}
}