Merge "Make Glanceable Hub dimensions consistent between density changes." into main
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
index ee03e4b..857154f 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -116,7 +116,6 @@
import android.os.ShellCallback;
import android.os.ShellCommand;
import android.os.SystemClock;
-import android.os.SystemProperties;
import android.os.ThreadLocalWorkSource;
import android.os.Trace;
import android.os.UserHandle;
@@ -179,9 +178,6 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.text.SimpleDateFormat;
-import java.time.Instant;
-import java.time.zone.ZoneOffsetTransition;
-import java.time.zone.ZoneRules;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
@@ -193,7 +189,6 @@
import java.util.TimeZone;
import java.util.TreeSet;
import java.util.concurrent.ThreadLocalRandom;
-import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;
/**
@@ -233,13 +228,6 @@
private static final long TEMPORARY_QUOTA_DURATION = INTERVAL_DAY;
- // System properties read on some device configurations to initialize time properly and
- // perform DST transitions at the bootloader level.
- private static final String TIMEOFFSET_PROPERTY = "persist.sys.time.offset";
- private static final String DST_TRANSITION_PROPERTY = "persist.sys.time.dst_transition";
- private static final String DST_OFFSET_PROPERTY = "persist.sys.time.dst_offset";
-
-
private final Intent mBackgroundIntent
= new Intent().addFlags(Intent.FLAG_FROM_BACKGROUND);
@@ -2127,22 +2115,6 @@
// "GMT" if the ID is unrecognized). The parameter ID is used here rather than
// newZone.getId(). It will be rejected if it is invalid.
timeZoneWasChanged = SystemTimeZone.setTimeZoneId(tzId, confidence, logInfo);
-
- final int gmtOffset = newZone.getOffset(mInjector.getCurrentTimeMillis());
- SystemProperties.set(TIMEOFFSET_PROPERTY, String.valueOf(gmtOffset));
-
- final ZoneRules rules = newZone.toZoneId().getRules();
- final ZoneOffsetTransition transition = rules.nextTransition(Instant.now());
- if (null != transition) {
- // Get the offset between the time after the DST transition and before.
- final long transitionOffset = TimeUnit.SECONDS.toMillis((
- transition.getOffsetAfter().getTotalSeconds()
- - transition.getOffsetBefore().getTotalSeconds()));
- // Time when the next DST transition is programmed.
- final long nextTransition = TimeUnit.SECONDS.toMillis(transition.toEpochSecond());
- SystemProperties.set(DST_TRANSITION_PROPERTY, String.valueOf(nextTransition));
- SystemProperties.set(DST_OFFSET_PROPERTY, String.valueOf(transitionOffset));
- }
}
// Clear the default time zone in the system server process. This forces the next call
diff --git a/core/java/android/hardware/SensorEvent.java b/core/java/android/hardware/SensorEvent.java
index 6113932..e8d7e1e 100644
--- a/core/java/android/hardware/SensorEvent.java
+++ b/core/java/android/hardware/SensorEvent.java
@@ -600,7 +600,7 @@
*
* <p>
* This sensor must be able to detect and report an on-body to off-body
- * transition within 1 second of the device being removed from the body,
+ * transition within 3 seconds of the device being removed from the body,
* and must be able to detect and report an off-body to on-body transition
* within 5 seconds of the device being put back onto the body.
* </p>
diff --git a/core/java/android/hardware/input/input_framework.aconfig b/core/java/android/hardware/input/input_framework.aconfig
index 0cd2800..b4ad050 100644
--- a/core/java/android/hardware/input/input_framework.aconfig
+++ b/core/java/android/hardware/input/input_framework.aconfig
@@ -75,3 +75,13 @@
description: "Enables a developer overlay that displays raw touchpad input data and gesture recognition status in real-time."
bug: "286551975"
}
+
+flag {
+ namespace: "input_native"
+ name: "keyboard_layout_manager_multi_user_ime_setup"
+ description: "Update KeyboardLayoutManager to work correctly with multi-user IME setup"
+ bug: "354333072"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/core/java/android/os/Vibrator.java b/core/java/android/os/Vibrator.java
index 71c83f2..99bd67b 100644
--- a/core/java/android/os/Vibrator.java
+++ b/core/java/android/os/Vibrator.java
@@ -497,7 +497,27 @@
*/
@RequiresPermission(android.Manifest.permission.VIBRATE)
public void vibrate(@NonNull VibrationEffect vibe, @NonNull VibrationAttributes attributes) {
- vibrate(Process.myUid(), mPackageName, vibe, null, attributes);
+ vibrate(vibe, attributes, null);
+ }
+
+ /**
+ * Vibrate with a given effect.
+ *
+ * <p>The app should be in the foreground for the vibration to happen. Background apps should
+ * specify a ringtone, notification or alarm usage in order to vibrate.</p>
+ *
+ * @param vibe {@link VibrationEffect} describing the vibration to be performed.
+ * @param attributes {@link VibrationAttributes} corresponding to the vibration. For example,
+ * specify {@link VibrationAttributes#USAGE_ALARM} for alarm vibrations or
+ * {@link VibrationAttributes#USAGE_RINGTONE} for vibrations associated with
+ * incoming calls.
+ * @param reason the reason for this vibration, used for debugging purposes.
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.VIBRATE)
+ public void vibrate(@NonNull VibrationEffect vibe,
+ @NonNull VibrationAttributes attributes, @NonNull String reason) {
+ vibrate(Process.myUid(), mPackageName, vibe, reason, attributes);
}
/**
diff --git a/core/java/android/provider/CallLog.java b/core/java/android/provider/CallLog.java
index 708c196..192afb1 100644
--- a/core/java/android/provider/CallLog.java
+++ b/core/java/android/provider/CallLog.java
@@ -28,6 +28,7 @@
import android.annotation.SystemApi;
import android.annotation.UserHandleAware;
import android.compat.annotation.UnsupportedAppUsage;
+import android.content.ComponentName;
import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.ContentValues;
@@ -1624,6 +1625,19 @@
"is_call_log_phone_account_migration_pending";
/**
+ * The default maximum number of call log entries stored in the call log provider for each
+ * {@link PhoneAccountHandle}.
+ */
+ private static final int DEFAULT_MAX_CALL_LOG_SIZE = 500;
+
+ /**
+ * Expected component name of Telephony phone accounts.
+ */
+ private static final ComponentName TELEPHONY_COMPONENT_NAME =
+ new ComponentName("com.android.phone",
+ "com.android.services.telephony.TelephonyConnectionService");
+
+ /**
* Adds a call to the call log.
*
* @param ci the CallerInfo object to get the target contact from. Can be null
@@ -2084,25 +2098,35 @@
}
int numDeleted;
- if (values.containsKey(PHONE_ACCOUNT_ID)
- && !TextUtils.isEmpty(values.getAsString(PHONE_ACCOUNT_ID))
- && values.containsKey(PHONE_ACCOUNT_COMPONENT_NAME)
- && !TextUtils.isEmpty(values.getAsString(PHONE_ACCOUNT_COMPONENT_NAME))) {
+ final String phoneAccountId =
+ values.containsKey(PHONE_ACCOUNT_ID)
+ ? values.getAsString(PHONE_ACCOUNT_ID) : null;
+ final String phoneAccountComponentName =
+ values.containsKey(PHONE_ACCOUNT_COMPONENT_NAME)
+ ? values.getAsString(PHONE_ACCOUNT_COMPONENT_NAME) : null;
+ int maxCallLogSize = DEFAULT_MAX_CALL_LOG_SIZE;
+ if (!TextUtils.isEmpty(phoneAccountId)
+ && !TextUtils.isEmpty(phoneAccountComponentName)) {
+ if (android.provider.Flags.allowConfigMaximumCallLogEntriesPerSim()
+ && TELEPHONY_COMPONENT_NAME
+ .flattenToString().equals(phoneAccountComponentName)) {
+ maxCallLogSize = context.getResources().getInteger(
+ com.android.internal.R.integer.config_maximumCallLogEntriesPerSim);
+ }
// Only purge entries for the same phone account.
numDeleted = resolver.delete(uri, "_id IN "
+ "(SELECT _id FROM calls"
+ " WHERE " + PHONE_ACCOUNT_COMPONENT_NAME + " = ?"
+ " AND " + PHONE_ACCOUNT_ID + " = ?"
+ " ORDER BY " + DEFAULT_SORT_ORDER
- + " LIMIT -1 OFFSET 500)", new String[] {
- values.getAsString(PHONE_ACCOUNT_COMPONENT_NAME),
- values.getAsString(PHONE_ACCOUNT_ID)
- });
+ + " LIMIT -1 OFFSET " + maxCallLogSize + ")",
+ new String[] { phoneAccountComponentName, phoneAccountId }
+ );
} else {
// No valid phone account specified, so default to the old behavior.
numDeleted = resolver.delete(uri, "_id IN "
+ "(SELECT _id FROM calls ORDER BY " + DEFAULT_SORT_ORDER
- + " LIMIT -1 OFFSET 500)", null);
+ + " LIMIT -1 OFFSET " + maxCallLogSize + ")", null);
}
Log.i(LOG_TAG, "addEntry: cleaned up " + numDeleted + " old entries");
diff --git a/core/java/android/provider/flags.aconfig b/core/java/android/provider/flags.aconfig
index ff98fc4..53d0c62 100644
--- a/core/java/android/provider/flags.aconfig
+++ b/core/java/android/provider/flags.aconfig
@@ -30,4 +30,16 @@
namespace: "backstage_power"
description: "Add a new settings page for the RUN_BACKUP_JOBS permission."
bug: "320563660"
-}
\ No newline at end of file
+}
+
+# OWNER = tgunn TARGET=25Q1
+flag {
+ name: "allow_config_maximum_call_log_entries_per_sim"
+ is_exported: true
+ namespace: "telecom"
+ description: "Allow partners to modify the maximum number of call log size for each sim card."
+ bug: "352235494"
+ metadata {
+ purpose: PURPOSE_FEATURE
+ }
+}
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 46b222b..66d08f9 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -404,8 +404,7 @@
}
private void prepareToDraw() {
- if (mDisplayState == Display.STATE_DOZE
- || mDisplayState == Display.STATE_DOZE_SUSPEND) {
+ if (mDisplayState == Display.STATE_DOZE) {
try {
mSession.pokeDrawLock(mWindow);
} catch (RemoteException e) {
diff --git a/core/java/android/text/ClientFlags.java b/core/java/android/text/ClientFlags.java
index b07534f..5d84d17 100644
--- a/core/java/android/text/ClientFlags.java
+++ b/core/java/android/text/ClientFlags.java
@@ -68,11 +68,4 @@
public static boolean fixMisalignedContextMenu() {
return TextFlags.isFeatureEnabled(Flags.FLAG_FIX_MISALIGNED_CONTEXT_MENU);
}
-
- /**
- * @see Flags#clearFontVariationSettings()
- */
- public static boolean clearFontVariationSettings() {
- return TextFlags.isFeatureEnabled(Flags.FLAG_CLEAR_FONT_VARIATION_SETTINGS);
- }
}
diff --git a/core/java/android/text/TextFlags.java b/core/java/android/text/TextFlags.java
index 4dca284..9e02460 100644
--- a/core/java/android/text/TextFlags.java
+++ b/core/java/android/text/TextFlags.java
@@ -61,7 +61,6 @@
Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE,
Flags.FLAG_ICU_BIDI_MIGRATION,
Flags.FLAG_FIX_MISALIGNED_CONTEXT_MENU,
- Flags.FLAG_CLEAR_FONT_VARIATION_SETTINGS,
};
/**
@@ -76,7 +75,6 @@
Flags.fixLineHeightForLocale(),
Flags.icuBidiMigration(),
Flags.fixMisalignedContextMenu(),
- Flags.clearFontVariationSettings(),
};
/**
diff --git a/core/java/android/view/InsetsSource.java b/core/java/android/view/InsetsSource.java
index 5c10db1..b796e0b 100644
--- a/core/java/android/view/InsetsSource.java
+++ b/core/java/android/view/InsetsSource.java
@@ -104,12 +104,23 @@
*/
public static final int FLAG_ANIMATE_RESIZING = 1 << 3;
+ /**
+ * Controls whether the {@link WindowInsets.Type#captionBar()} insets provided by this source
+ * should always be forcibly consumed. Unlike with {@link #FLAG_FORCE_CONSUMING}, when this
+ * flag is used the caption bar will be consumed even when the bar is requested to be visible.
+ *
+ * Note: this flag does not take effect when the window applies
+ * {@link WindowInsetsController.Appearance#APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND}.
+ */
+ public static final int FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR = 1 << 4;
+
@Retention(RetentionPolicy.SOURCE)
@IntDef(flag = true, prefix = "FLAG_", value = {
FLAG_SUPPRESS_SCRIM,
FLAG_INSETS_ROUNDED_CORNER,
FLAG_FORCE_CONSUMING,
FLAG_ANIMATE_RESIZING,
+ FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR
})
public @interface Flags {}
@@ -555,6 +566,9 @@
if ((flags & FLAG_ANIMATE_RESIZING) != 0) {
joiner.add("ANIMATE_RESIZING");
}
+ if ((flags & FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR) != 0) {
+ joiner.add("FORCE_CONSUMING_OPAQUE_CAPTION_BAR");
+ }
return joiner.toString();
}
diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java
index bbd9acf..6b4340a 100644
--- a/core/java/android/view/InsetsState.java
+++ b/core/java/android/view/InsetsState.java
@@ -19,6 +19,7 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.util.SequenceUtils.getInitSeq;
import static android.view.InsetsSource.FLAG_FORCE_CONSUMING;
+import static android.view.InsetsSource.FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR;
import static android.view.InsetsSource.FLAG_INSETS_ROUNDED_CORNER;
import static android.view.InsetsStateProto.DISPLAY_CUTOUT;
import static android.view.InsetsStateProto.DISPLAY_FRAME;
@@ -54,6 +55,7 @@
import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.window.flags.Flags;
import java.io.PrintWriter;
import java.util.Objects;
@@ -131,18 +133,25 @@
final Rect relativeFrame = new Rect(frame);
final Rect relativeFrameMax = new Rect(frame);
@InsetsType int forceConsumingTypes = 0;
+ boolean forceConsumingOpaqueCaptionBar = false;
@InsetsType int suppressScrimTypes = 0;
final Rect[][] typeBoundingRectsMap = new Rect[Type.SIZE][];
final Rect[][] typeMaxBoundingRectsMap = new Rect[Type.SIZE][];
for (int i = mSources.size() - 1; i >= 0; i--) {
final InsetsSource source = mSources.valueAt(i);
final @InsetsType int type = source.getType();
+ final @InsetsSource.Flags int flags = source.getFlags();
- if ((source.getFlags() & InsetsSource.FLAG_FORCE_CONSUMING) != 0) {
+ if ((flags & InsetsSource.FLAG_FORCE_CONSUMING) != 0) {
forceConsumingTypes |= type;
}
- if ((source.getFlags() & InsetsSource.FLAG_SUPPRESS_SCRIM) != 0) {
+ if (Flags.enableCaptionCompatInsetForceConsumptionAlways()
+ && (flags & FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR) != 0) {
+ forceConsumingOpaqueCaptionBar = true;
+ }
+
+ if ((flags & InsetsSource.FLAG_SUPPRESS_SCRIM) != 0) {
suppressScrimTypes |= type;
}
@@ -177,7 +186,8 @@
}
return new WindowInsets(typeInsetsMap, typeMaxInsetsMap, typeVisibilityMap, isScreenRound,
- forceConsumingTypes, suppressScrimTypes, calculateRelativeCutout(frame),
+ forceConsumingTypes, forceConsumingOpaqueCaptionBar, suppressScrimTypes,
+ calculateRelativeCutout(frame),
calculateRelativeRoundedCorners(frame),
calculateRelativePrivacyIndicatorBounds(frame),
calculateRelativeDisplayShape(frame),
diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java
index 987c8c8..e3ea6b22 100644
--- a/core/java/android/view/WindowInsets.java
+++ b/core/java/android/view/WindowInsets.java
@@ -97,6 +97,7 @@
private final int mFrameHeight;
private final @InsetsType int mForceConsumingTypes;
+ private final boolean mForceConsumingOpaqueCaptionBar;
private final @InsetsType int mSuppressScrimTypes;
private final boolean mSystemWindowInsetsConsumed;
private final boolean mStableInsetsConsumed;
@@ -123,7 +124,7 @@
static {
CONSUMED = new WindowInsets(createCompatTypeMap(null), createCompatTypeMap(null),
- createCompatVisibilityMap(createCompatTypeMap(null)), false, 0, 0, null,
+ createCompatVisibilityMap(createCompatTypeMap(null)), false, 0, false, 0, null,
null, null, null, systemBars(), false, null, null, 0, 0);
}
@@ -144,6 +145,7 @@
boolean[] typeVisibilityMap,
boolean isRound,
@InsetsType int forceConsumingTypes,
+ boolean forceConsumingOpaqueCaptionBar,
@InsetsType int suppressScrimTypes,
DisplayCutout displayCutout,
RoundedCorners roundedCorners,
@@ -166,6 +168,7 @@
mTypeVisibilityMap = typeVisibilityMap;
mIsRound = isRound;
mForceConsumingTypes = forceConsumingTypes;
+ mForceConsumingOpaqueCaptionBar = forceConsumingOpaqueCaptionBar;
mSuppressScrimTypes = suppressScrimTypes;
mCompatInsetsTypes = compatInsetsTypes;
mCompatIgnoreVisibility = compatIgnoreVisibility;
@@ -196,7 +199,9 @@
this(src.mSystemWindowInsetsConsumed ? null : src.mTypeInsetsMap,
src.mStableInsetsConsumed ? null : src.mTypeMaxInsetsMap,
src.mTypeVisibilityMap, src.mIsRound,
- src.mForceConsumingTypes, src.mSuppressScrimTypes,
+ src.mForceConsumingTypes,
+ src.mForceConsumingOpaqueCaptionBar,
+ src.mSuppressScrimTypes,
displayCutoutCopyConstructorArgument(src),
src.mRoundedCorners,
src.mPrivacyIndicatorBounds,
@@ -257,7 +262,7 @@
/** @hide */
@UnsupportedAppUsage
public WindowInsets(Rect systemWindowInsets) {
- this(createCompatTypeMap(systemWindowInsets), null, new boolean[SIZE], false, 0, 0,
+ this(createCompatTypeMap(systemWindowInsets), null, new boolean[SIZE], false, 0, false, 0,
null, null, null, null, systemBars(), false /* compatIgnoreVisibility */,
new Rect[SIZE][], null, 0, 0);
}
@@ -675,10 +680,10 @@
return new WindowInsets(mSystemWindowInsetsConsumed ? null : mTypeInsetsMap,
mStableInsetsConsumed ? null : mTypeMaxInsetsMap,
mTypeVisibilityMap,
- mIsRound, mForceConsumingTypes, mSuppressScrimTypes,
- null /* displayCutout */, mRoundedCorners, mPrivacyIndicatorBounds, mDisplayShape,
- mCompatInsetsTypes, mCompatIgnoreVisibility,
- mSystemWindowInsetsConsumed ? null : mTypeBoundingRectsMap,
+ mIsRound, mForceConsumingTypes, mForceConsumingOpaqueCaptionBar,
+ mSuppressScrimTypes, null /* displayCutout */, mRoundedCorners,
+ mPrivacyIndicatorBounds, mDisplayShape, mCompatInsetsTypes,
+ mCompatIgnoreVisibility, mSystemWindowInsetsConsumed ? null : mTypeBoundingRectsMap,
mStableInsetsConsumed ? null : mTypeMaxBoundingRectsMap,
mFrameWidth, mFrameHeight);
}
@@ -729,7 +734,8 @@
public WindowInsets consumeSystemWindowInsets() {
return new WindowInsets(null, null,
mTypeVisibilityMap,
- mIsRound, mForceConsumingTypes, mSuppressScrimTypes,
+ mIsRound, mForceConsumingTypes, mForceConsumingOpaqueCaptionBar,
+ mSuppressScrimTypes,
// If the system window insets types contain displayCutout, we should also consume
// it.
(mCompatInsetsTypes & displayCutout()) != 0
@@ -1024,6 +1030,13 @@
/**
* @hide
*/
+ public boolean isForceConsumingOpaqueCaptionBar() {
+ return mForceConsumingOpaqueCaptionBar;
+ }
+
+ /**
+ * @hide
+ */
public @InsetsType int getSuppressScrimTypes() {
return mSuppressScrimTypes;
}
@@ -1058,6 +1071,8 @@
result.append("\n ");
result.append("forceConsumingTypes=" + Type.toString(mForceConsumingTypes));
result.append("\n ");
+ result.append("forceConsumingOpaqueCaptionBar=" + mForceConsumingOpaqueCaptionBar);
+ result.append("\n ");
result.append("suppressScrimTypes=" + Type.toString(mSuppressScrimTypes));
result.append("\n ");
result.append("compatInsetsTypes=" + Type.toString(mCompatInsetsTypes));
@@ -1180,7 +1195,8 @@
? null
: insetInsets(mTypeMaxInsetsMap, left, top, right, bottom),
mTypeVisibilityMap,
- mIsRound, mForceConsumingTypes, mSuppressScrimTypes,
+ mIsRound, mForceConsumingTypes, mForceConsumingOpaqueCaptionBar,
+ mSuppressScrimTypes,
mDisplayCutoutConsumed
? null
: mDisplayCutout == null
@@ -1214,6 +1230,7 @@
return mIsRound == that.mIsRound
&& mForceConsumingTypes == that.mForceConsumingTypes
+ && mForceConsumingOpaqueCaptionBar == that.mForceConsumingOpaqueCaptionBar
&& mSuppressScrimTypes == that.mSuppressScrimTypes
&& mSystemWindowInsetsConsumed == that.mSystemWindowInsetsConsumed
&& mStableInsetsConsumed == that.mStableInsetsConsumed
@@ -1235,9 +1252,9 @@
public int hashCode() {
return Objects.hash(Arrays.hashCode(mTypeInsetsMap), Arrays.hashCode(mTypeMaxInsetsMap),
Arrays.hashCode(mTypeVisibilityMap), mIsRound, mDisplayCutout, mRoundedCorners,
- mForceConsumingTypes, mSuppressScrimTypes, mSystemWindowInsetsConsumed,
- mStableInsetsConsumed, mDisplayCutoutConsumed, mPrivacyIndicatorBounds,
- mDisplayShape, Arrays.deepHashCode(mTypeBoundingRectsMap),
+ mForceConsumingTypes, mForceConsumingOpaqueCaptionBar, mSuppressScrimTypes,
+ mSystemWindowInsetsConsumed, mStableInsetsConsumed, mDisplayCutoutConsumed,
+ mPrivacyIndicatorBounds, mDisplayShape, Arrays.deepHashCode(mTypeBoundingRectsMap),
Arrays.deepHashCode(mTypeMaxBoundingRectsMap), mFrameWidth, mFrameHeight);
}
@@ -1367,6 +1384,7 @@
private boolean mIsRound;
private @InsetsType int mForceConsumingTypes;
+ private boolean mForceConsumingOpaqueCaptionBar;
private @InsetsType int mSuppressScrimTypes;
private PrivacyIndicatorBounds mPrivacyIndicatorBounds = new PrivacyIndicatorBounds();
@@ -1399,6 +1417,7 @@
mRoundedCorners = insets.mRoundedCorners;
mIsRound = insets.mIsRound;
mForceConsumingTypes = insets.mForceConsumingTypes;
+ mForceConsumingOpaqueCaptionBar = insets.mForceConsumingOpaqueCaptionBar;
mSuppressScrimTypes = insets.mSuppressScrimTypes;
mPrivacyIndicatorBounds = insets.mPrivacyIndicatorBounds;
mDisplayShape = insets.mDisplayShape;
@@ -1687,6 +1706,13 @@
/** @hide */
@NonNull
+ public Builder setForceConsumingOpaqueCaptionBar(boolean forceConsumingOpaqueCaptionBar) {
+ mForceConsumingOpaqueCaptionBar = forceConsumingOpaqueCaptionBar;
+ return this;
+ }
+
+ /** @hide */
+ @NonNull
public Builder setSuppressScrimTypes(@InsetsType int suppressScrimTypes) {
mSuppressScrimTypes = suppressScrimTypes;
return this;
@@ -1765,9 +1791,9 @@
public WindowInsets build() {
return new WindowInsets(mSystemInsetsConsumed ? null : mTypeInsetsMap,
mStableInsetsConsumed ? null : mTypeMaxInsetsMap, mTypeVisibilityMap,
- mIsRound, mForceConsumingTypes, mSuppressScrimTypes, mDisplayCutout,
- mRoundedCorners, mPrivacyIndicatorBounds, mDisplayShape, systemBars(),
- false /* compatIgnoreVisibility */,
+ mIsRound, mForceConsumingTypes, mForceConsumingOpaqueCaptionBar,
+ mSuppressScrimTypes, mDisplayCutout, mRoundedCorners, mPrivacyIndicatorBounds,
+ mDisplayShape, systemBars(), false /* compatIgnoreVisibility */,
mSystemInsetsConsumed ? null : mTypeBoundingRectsMap,
mStableInsetsConsumed ? null : mTypeMaxBoundingRectsMap,
mFrameWidth, mFrameHeight);
diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java
index 87e22ed..4828393 100644
--- a/core/java/com/android/internal/policy/DecorView.java
+++ b/core/java/com/android/internal/policy/DecorView.java
@@ -29,6 +29,7 @@
import static android.view.WindowInsetsController.APPEARANCE_FORCE_LIGHT_NAVIGATION_BARS;
import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
+import static android.view.WindowInsetsController.APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND;
import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
import static android.view.WindowManager.LayoutParams.FLAG_FULLSCREEN;
import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
@@ -36,6 +37,7 @@
import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION;
import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
+import static android.view.flags.Flags.customizableWindowHeaders;
import static com.android.internal.policy.PhoneWindow.FEATURE_OPTIONS_PANEL;
@@ -226,6 +228,7 @@
private boolean mLastHasLeftStableInset = false;
private int mLastWindowFlags = 0;
private @InsetsType int mLastForceConsumingTypes = 0;
+ private boolean mLastForceConsumingOpaqueCaptionBar = false;
private @InsetsType int mLastSuppressScrimTypes = 0;
private int mRootScrollY = 0;
@@ -1068,8 +1071,12 @@
WindowManager.LayoutParams attrs = mWindow.getAttributes();
int sysUiVisibility = attrs.systemUiVisibility | getWindowSystemUiVisibility();
+ final ViewRootImpl viewRoot = getViewRootImpl();
final WindowInsetsController controller = getWindowInsetsController();
final @InsetsType int requestedVisibleTypes = controller.getRequestedVisibleTypes();
+ final @Appearance int appearance = viewRoot != null
+ ? viewRoot.mWindowAttributes.insetsFlags.appearance
+ : controller.getSystemBarsAppearance();
// IME is an exceptional floating window that requires color view.
final boolean isImeWindow =
@@ -1080,13 +1087,9 @@
& FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0;
mLastWindowFlags = attrs.flags;
- final ViewRootImpl viewRoot = getViewRootImpl();
- final @Appearance int appearance = viewRoot != null
- ? viewRoot.mWindowAttributes.insetsFlags.appearance
- : controller.getSystemBarsAppearance();
-
if (insets != null) {
mLastForceConsumingTypes = insets.getForceConsumingTypes();
+ mLastForceConsumingOpaqueCaptionBar = insets.isForceConsumingOpaqueCaptionBar();
final boolean clearsCompatInsets = clearsCompatInsets(attrs.type, attrs.flags,
getResources().getConfiguration().windowConfiguration.getActivityType(),
@@ -1209,16 +1212,20 @@
final boolean hideCaptionBar = fullscreen
|| (requestedVisibleTypes & WindowInsets.Type.captionBar()) == 0;
- final boolean consumingCaptionBar =
- ((mLastForceConsumingTypes & WindowInsets.Type.captionBar()) != 0
+ final boolean consumingCaptionBar = Flags.enableCaptionCompatInsetForceConsumption()
+ && ((mLastForceConsumingTypes & WindowInsets.Type.captionBar()) != 0
&& hideCaptionBar);
- final int consumedTop;
- if (Flags.enableCaptionCompatInsetForceConsumption()) {
- consumedTop = (consumingStatusBar || consumingCaptionBar) ? mLastTopInset : 0;
- } else {
- consumedTop = consumingStatusBar ? mLastTopInset : 0;
- }
+ final boolean isOpaqueCaptionBar = customizableWindowHeaders()
+ && (appearance & APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND) == 0;
+ final boolean consumingOpaqueCaptionBar =
+ Flags.enableCaptionCompatInsetForceConsumptionAlways()
+ && mLastForceConsumingOpaqueCaptionBar
+ && isOpaqueCaptionBar;
+
+ final int consumedTop =
+ (consumingStatusBar || consumingCaptionBar || consumingOpaqueCaptionBar)
+ ? mLastTopInset : 0;
int consumedRight = consumingNavBar ? mLastRightInset : 0;
int consumedBottom = consumingNavBar ? mLastBottomInset : 0;
int consumedLeft = consumingNavBar ? mLastLeftInset : 0;
diff --git a/core/java/com/android/internal/policy/SystemBarUtils.java b/core/java/com/android/internal/policy/SystemBarUtils.java
index efa3697..4ed15fa 100644
--- a/core/java/com/android/internal/policy/SystemBarUtils.java
+++ b/core/java/com/android/internal/policy/SystemBarUtils.java
@@ -92,4 +92,11 @@
// Equals to status bar height if status bar height is bigger.
return Math.max(defaultSize, statusBarHeight);
}
+
+ /**
+ * Gets the taskbar frame height.
+ */
+ public static int getTaskbarHeight(Resources res) {
+ return res.getDimensionPixelSize(R.dimen.taskbar_frame_height);
+ }
}
diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp
index 809ec63..e5ac0e1 100644
--- a/core/jni/android_util_Process.cpp
+++ b/core/jni/android_util_Process.cpp
@@ -33,6 +33,7 @@
#include <algorithm>
#include <array>
+#include <cstring>
#include <limits>
#include <memory>
#include <string>
@@ -50,7 +51,6 @@
#include <inttypes.h>
#include <pwd.h>
#include <signal.h>
-#include <string.h>
#include <sys/epoll.h>
#include <sys/errno.h>
#include <sys/pidfd.h>
@@ -73,13 +73,13 @@
// readProcFile() are reading files under this threshold, e.g.,
// /proc/pid/stat. /proc/pid/time_in_state ends up being about 520
// bytes, so use 1024 for the stack to provide a bit of slack.
-static constexpr ssize_t kProcReadStackBufferSize = 1024;
+static constexpr size_t kProcReadStackBufferSize = 1024;
// The other files we read from proc tend to be a bit larger (e.g.,
// /proc/stat is about 3kB), so once we exhaust the stack buffer,
// retry with a relatively large heap-allocated buffer. We double
// this size and retry until the whole file fits.
-static constexpr ssize_t kProcReadMinHeapBufferSize = 4096;
+static constexpr size_t kProcReadMinHeapBufferSize = 4096;
#if GUARD_THREAD_PRIORITY
Mutex gKeyCreateMutex;
@@ -817,7 +817,6 @@
}
DIR* dirp = opendir(file8);
-
env->ReleaseStringUTFChars(file, file8);
if(dirp == NULL) {
@@ -850,6 +849,7 @@
jintArray newArray = env->NewIntArray(newCount);
if (newArray == NULL) {
closedir(dirp);
+ if (curData) env->ReleaseIntArrayElements(lastArray, curData, 0);
jniThrowException(env, "java/lang/OutOfMemoryError", NULL);
return NULL;
}
@@ -1046,78 +1046,71 @@
return JNI_FALSE;
}
- const char* file8 = env->GetStringUTFChars(file, NULL);
- if (file8 == NULL) {
+ auto releaser = [&](const char* jniStr) { env->ReleaseStringUTFChars(file, jniStr); };
+ std::unique_ptr<const char[], decltype(releaser)> file8(env->GetStringUTFChars(file, NULL),
+ releaser);
+ if (!file8) {
jniThrowException(env, "java/lang/OutOfMemoryError", NULL);
return JNI_FALSE;
}
- ::android::base::unique_fd fd(open(file8, O_RDONLY | O_CLOEXEC));
+ ::android::base::unique_fd fd(open(file8.get(), O_RDONLY | O_CLOEXEC));
if (!fd.ok()) {
if (kDebugProc) {
- ALOGW("Unable to open process file: %s\n", file8);
+ ALOGW("Unable to open process file: %s\n", file8.get());
}
- env->ReleaseStringUTFChars(file, file8);
return JNI_FALSE;
}
- env->ReleaseStringUTFChars(file, file8);
// Most proc files we read are small, so we go through the loop
- // with the stack buffer firstly. We allocate a buffer big
- // enough for the whole file.
+ // with the stack buffer first. We allocate a buffer big enough
+ // for most files.
- char readBufferStack[kProcReadStackBufferSize];
- std::unique_ptr<char[]> readBufferHeap;
- char* readBuffer = &readBufferStack[0];
- ssize_t readBufferSize = kProcReadStackBufferSize;
- ssize_t numberBytesRead;
+ char stackBuf[kProcReadStackBufferSize];
+ std::vector<char> heapBuf;
+ char* buf = stackBuf;
+
+ size_t remaining = sizeof(stackBuf);
off_t offset = 0;
- for (;;) {
- ssize_t requestedBufferSize = readBufferSize - offset;
- // By using pread, we can avoid an lseek to rewind the FD
- // before retry, saving a system call.
- numberBytesRead =
- TEMP_FAILURE_RETRY(pread(fd, readBuffer + offset, requestedBufferSize, offset));
- if (numberBytesRead < 0) {
+ ssize_t numBytesRead;
+
+ do {
+ numBytesRead = TEMP_FAILURE_RETRY(pread(fd, buf + offset, remaining, offset));
+ if (numBytesRead < 0) {
if (kDebugProc) {
ALOGW("Unable to read process file err: %s file: %s fd=%d\n",
- strerror_r(errno, &readBufferStack[0], sizeof(readBufferStack)), file8,
- fd.get());
+ strerror_r(errno, stackBuf, sizeof(stackBuf)), file8.get(), fd.get());
}
return JNI_FALSE;
}
- if (numberBytesRead == 0) {
- // End of file.
- numberBytesRead = offset;
- break;
- }
- if (numberBytesRead < requestedBufferSize) {
- // Read less bytes than requested, it's not an error per pread(2).
- offset += numberBytesRead;
- } else {
- // Buffer is fully used, try to grow it.
- if (readBufferSize > std::numeric_limits<ssize_t>::max() / 2) {
- if (kDebugProc) {
- ALOGW("Proc file too big: %s fd=%d\n", file8, fd.get());
+
+ offset += numBytesRead;
+ remaining -= numBytesRead;
+
+ if (numBytesRead && !remaining) {
+ if (buf == stackBuf) {
+ heapBuf.resize(kProcReadMinHeapBufferSize);
+ static_assert(kProcReadMinHeapBufferSize > sizeof(stackBuf));
+ std::memcpy(heapBuf.data(), stackBuf, sizeof(stackBuf));
+ } else {
+ constexpr size_t MAX_READABLE_PROCFILE_SIZE = 64 << 20;
+ if (heapBuf.size() >= MAX_READABLE_PROCFILE_SIZE) {
+ if (kDebugProc) {
+ ALOGW("Proc file too big: %s fd=%d size=%zu\n",
+ file8.get(), fd.get(), heapBuf.size());
+ }
+ return JNI_FALSE;
}
- return JNI_FALSE;
+ heapBuf.resize(2 * heapBuf.size());
}
- readBufferSize = std::max(readBufferSize * 2, kProcReadMinHeapBufferSize);
- readBufferHeap.reset(); // Free address space before getting more.
- readBufferHeap = std::make_unique<char[]>(readBufferSize);
- if (!readBufferHeap) {
- jniThrowException(env, "java/lang/OutOfMemoryError", NULL);
- return JNI_FALSE;
- }
- readBuffer = readBufferHeap.get();
- offset = 0;
+ buf = heapBuf.data();
+ remaining = heapBuf.size() - offset;
}
- }
+ } while (numBytesRead != 0);
// parseProcLineArray below modifies the buffer while parsing!
return android_os_Process_parseProcLineArray(
- env, clazz, readBuffer, 0, numberBytesRead,
- format, outStrings, outLongs, outFloats);
+ env, clazz, buf, 0, offset, format, outStrings, outLongs, outFloats);
}
void android_os_Process_setApplicationObject(JNIEnv* env, jobject clazz,
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 8cb7646..2afc303 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -7100,4 +7100,8 @@
<!-- Whether to enable the private space search illustration and search tile content in "Hide Private Space" settings page.
OEM/Partner can explicitly opt to hide the illustration and search tile content. -->
<bool name="config_enableSearchTileHideIllustrationInPrivateSpace">true</bool>
+
+ <!-- The maximum number of call log entries for each sim card that can be stored in the call log
+ provider on the device. -->
+ <integer name="config_maximumCallLogEntriesPerSim">500</integer>
</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 4a425cb..bc8c778 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -5530,6 +5530,8 @@
<java-symbol type="integer" name="config_wallpaperFrameRateCompatibility" />
<java-symbol type="integer" name="config_defaultMinEmergencyGestureTapDurationMillis" />
+ <java-symbol type="integer" name="config_maximumCallLogEntriesPerSim" />
+
<!-- Back swipe thresholds -->
<java-symbol type="dimen" name="navigation_edge_action_progress_threshold" />
<java-symbol type="dimen" name="back_progress_non_linear_factor" />
diff --git a/core/tests/coretests/src/android/view/InsetsStateTest.java b/core/tests/coretests/src/android/view/InsetsStateTest.java
index 16bd20a..75749c7 100644
--- a/core/tests/coretests/src/android/view/InsetsStateTest.java
+++ b/core/tests/coretests/src/android/view/InsetsStateTest.java
@@ -19,6 +19,7 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.view.InsetsSource.FLAG_FORCE_CONSUMING;
+import static android.view.InsetsSource.FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR;
import static android.view.InsetsSource.ID_IME;
import static android.view.InsetsSource.SIDE_BOTTOM;
import static android.view.InsetsSource.SIDE_TOP;
@@ -54,12 +55,17 @@
import android.graphics.Insets;
import android.graphics.Rect;
import android.os.Parcel;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.util.SparseIntArray;
import android.view.WindowInsets.Type;
import androidx.test.runner.AndroidJUnit4;
+import com.android.window.flags.Flags;
+
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -78,6 +84,9 @@
@RunWith(AndroidJUnit4.class)
public class InsetsStateTest {
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
private static final int ID_STATUS_BAR = InsetsSource.createId(
null /* owner */, 0 /* index */, statusBars());
private static final int ID_NAVIGATION_BAR = InsetsSource.createId(
@@ -854,4 +863,19 @@
);
}
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION_ALWAYS)
+ public void testCalculateInsets_forceConsumingCaptionBar() {
+ mState.getOrCreateSource(ID_CAPTION_BAR, captionBar())
+ .setFrame(new Rect(0, 0, 100, 100))
+ .setVisible(true)
+ .setFlags(FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR);
+
+ final WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 1000, 1000), null, false,
+ SOFT_INPUT_ADJUST_RESIZE, 0, 0, TYPE_APPLICATION, ACTIVITY_TYPE_UNDEFINED,
+ new SparseIntArray());
+
+ assertTrue(insets.isForceConsumingOpaqueCaptionBar());
+ }
}
diff --git a/core/tests/coretests/src/android/view/WindowInsetsTest.java b/core/tests/coretests/src/android/view/WindowInsetsTest.java
index ba1204b..c86045d 100644
--- a/core/tests/coretests/src/android/view/WindowInsetsTest.java
+++ b/core/tests/coretests/src/android/view/WindowInsetsTest.java
@@ -43,14 +43,14 @@
@Test
public void systemWindowInsets_afterConsuming_isConsumed() {
assertTrue(new WindowInsets(WindowInsets.createCompatTypeMap(new Rect(1, 2, 3, 4)), null,
- null, false, 0, 0, null, null, null, null,
+ null, false, 0, false, 0, null, null, null, null,
WindowInsets.Type.systemBars(), false, null, null, 0, 0)
.consumeSystemWindowInsets().isConsumed());
}
@Test
public void multiNullConstructor_isConsumed() {
- assertTrue(new WindowInsets(null, null, null, false, 0, 0, null, null, null, null,
+ assertTrue(new WindowInsets(null, null, null, false, 0, false, 0, null, null, null, null,
WindowInsets.Type.systemBars(), false, null, null, 0, 0).isConsumed());
}
@@ -67,7 +67,7 @@
WindowInsets.assignCompatInsets(maxInsets, new Rect(0, 10, 0, 0));
WindowInsets.assignCompatInsets(insets, new Rect(0, 0, 0, 0));
WindowInsets windowInsets = new WindowInsets(insets, maxInsets, visible, false, 0,
- 0, null, null, null, DisplayShape.NONE, systemBars(),
+ false, 0, null, null, null, DisplayShape.NONE, systemBars(),
true /* compatIgnoreVisibility */, null, null, 0, 0);
assertEquals(Insets.of(0, 10, 0, 0), windowInsets.getSystemWindowInsets());
}
diff --git a/core/tests/vibrator/src/android/os/VibratorTest.java b/core/tests/vibrator/src/android/os/VibratorTest.java
index cfa12bb..6210a00 100644
--- a/core/tests/vibrator/src/android/os/VibratorTest.java
+++ b/core/tests/vibrator/src/android/os/VibratorTest.java
@@ -222,6 +222,18 @@
}
@Test
+ public void vibrate_withVibrationAttributesAndReason_usesGivenAttributesAndReason() {
+ VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
+ VibrationAttributes attributes = new VibrationAttributes.Builder().setUsage(
+ VibrationAttributes.USAGE_TOUCH).build();
+ String reason = "reason";
+
+ mVibratorSpy.vibrate(effect, attributes, reason);
+
+ verify(mVibratorSpy).vibrate(anyInt(), anyString(), eq(effect), eq(reason), eq(attributes));
+ }
+
+ @Test
public void vibrate_withAudioAttributes_createsVibrationAttributesWithSameUsage() {
VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
AudioAttributes audioAttributes = new AudioAttributes.Builder().setUsage(
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 5d4139e..1fe6ca7 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -505,6 +505,7 @@
<permission name="android.permission.RENOUNCE_PERMISSIONS" />
<permission name="android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS" />
<permission name="android.permission.GET_PROCESS_STATE_AND_OOM_SCORE" />
+ <permission name="android.permission.READ_DROPBOX_DATA" />
<permission name="android.permission.READ_LOGS" />
<permission name="android.permission.BRIGHTNESS_SLIDER_USAGE" />
<permission name="android.permission.ACCESS_AMBIENT_LIGHT_STATS" />
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index b83931f..df95a91 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -35,7 +35,6 @@
import android.graphics.fonts.FontVariationAxis;
import android.os.Build;
import android.os.LocaleList;
-import android.text.ClientFlags;
import android.text.GraphicsOperations;
import android.text.SpannableString;
import android.text.SpannedString;
@@ -1541,21 +1540,8 @@
* @return typeface
*/
public Typeface setTypeface(Typeface typeface) {
- return setTypefaceInternal(typeface, true);
- }
-
- private Typeface setTypefaceInternal(Typeface typeface, boolean clearFontVariationSettings) {
final long typefaceNative = typeface == null ? 0 : typeface.native_instance;
nSetTypeface(mNativePaint, typefaceNative);
-
- if (ClientFlags.clearFontVariationSettings()) {
- if (clearFontVariationSettings && !Objects.equals(mTypeface, typeface)) {
- // We cannot call setFontVariationSetting with empty string or null because it calls
- // setTypeface method. To avoid recursive setTypeface call, manually resetting
- // mFontVariationSettings.
- mFontVariationSettings = null;
- }
- }
mTypeface = typeface;
return typeface;
}
@@ -2051,14 +2037,6 @@
* </li>
* </ul>
*
- * Note: This method replaces the Typeface previously set to this instance.
- * Until API {@link Build.VERSION_CODES.VANILLA_ICE_CREAM}, any caller of
- * {@link #setTypeface(Typeface)} should call this method with empty settings, then call
- * {@link #setTypeface(Typeface)}, then call this method with preferred variation settings.
- * The device API more than {@link Build.VERSION_CODES.VANILLA_ICE_CREAM}, the
- * {@link #setTypeface(Typeface)} method clears font variation settings. So caller of
- * {@link #setTypeface(Typeface)} should call this method again for applying variation settings.
- *
* @param fontVariationSettings font variation settings. You can pass null or empty string as
* no variation settings.
*
@@ -2081,8 +2059,8 @@
if (settings == null || settings.length() == 0) {
mFontVariationSettings = null;
- setTypefaceInternal(Typeface.createFromTypefaceWithVariation(mTypeface,
- Collections.emptyList()), false);
+ setTypeface(Typeface.createFromTypefaceWithVariation(mTypeface,
+ Collections.emptyList()));
return true;
}
@@ -2100,8 +2078,7 @@
return false;
}
mFontVariationSettings = settings;
- setTypefaceInternal(Typeface.createFromTypefaceWithVariation(targetTypeface, filteredAxes),
- false);
+ setTypeface(Typeface.createFromTypefaceWithVariation(targetTypeface, filteredAxes));
return true;
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java
index 544f0f3..882a8d0 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java
@@ -565,6 +565,7 @@
return true;
}
+ // Only called by onTouch() and mRenderer is already null-checked.
@GuardedBy("mLock")
private void onStartDragging(@NonNull MotionEvent event) {
mVelocityTracker = VelocityTracker.obtain();
@@ -590,6 +591,7 @@
});
}
+ // Only called by onTouch() and mRenderer is already null-checked.
@GuardedBy("mLock")
private void onDrag(@NonNull MotionEvent event) {
if (mVelocityTracker != null) {
@@ -660,8 +662,10 @@
@GuardedBy("mLock")
private void updateDividerPosition(int position) {
- mRenderer.setDividerPosition(position);
- mRenderer.updateSurface();
+ if (mRenderer != null) {
+ mRenderer.setDividerPosition(position);
+ mRenderer.updateSurface();
+ }
}
@GuardedBy("mLock")
@@ -669,7 +673,10 @@
// Veil visibility change should be applied together with the surface boost transaction in
// the wct.
final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
- mRenderer.hideVeils(t);
+
+ if (mRenderer != null) {
+ mRenderer.hideVeils(t);
+ }
// Callbacks must be executed on the executor to release mLock and prevent deadlocks.
// mDecorSurfaceOwner may change between here and when the callback is executed,
@@ -684,8 +691,10 @@
}
});
});
- mRenderer.mIsDragging = false;
- mRenderer.mDragHandle.setPressed(mRenderer.mIsDragging);
+ if (mRenderer != null) {
+ mRenderer.mIsDragging = false;
+ mRenderer.mDragHandle.setPressed(mRenderer.mIsDragging);
+ }
}
/**
@@ -1090,13 +1099,14 @@
@NonNull
private final SurfaceControl mDividerSurface;
@NonNull
+ private final SurfaceControl mDividerLineSurface;
+ @NonNull
private final WindowlessWindowManager mWindowlessWindowManager;
@NonNull
private final SurfaceControlViewHost mViewHost;
@NonNull
private final FrameLayout mDividerLayout;
- @NonNull
- private final View mDividerLine;
+ @Nullable
private View mDragHandle;
@NonNull
private final View.OnTouchListener mListener;
@@ -1115,7 +1125,10 @@
mProperties = properties;
mListener = listener;
- mDividerSurface = createChildSurface("DividerSurface", true /* visible */);
+ mDividerSurface = createChildSurface(
+ mProperties.mDecorSurface, "DividerSurface", true /* visible */);
+ mDividerLineSurface = createChildSurface(
+ mDividerSurface, "DividerLineSurface", true /* visible */);
mWindowlessWindowManager = new WindowlessWindowManager(
mProperties.mConfiguration,
mDividerSurface,
@@ -1127,7 +1140,6 @@
context, displayManager.getDisplay(mProperties.mDisplayId),
mWindowlessWindowManager, "DividerContainer");
mDividerLayout = new FrameLayout(context);
- mDividerLine = new View(context);
update();
}
@@ -1220,6 +1232,7 @@
dividerSurfacePosition = mDividerPosition;
}
+ // Update the divider surface position relative to the decor surface
if (mProperties.mIsVerticalSplit) {
t.setPosition(mDividerSurface, dividerSurfacePosition, 0.0f);
t.setWindowCrop(mDividerSurface, mDividerSurfaceWidthPx, taskBounds.height());
@@ -1228,10 +1241,24 @@
t.setWindowCrop(mDividerSurface, taskBounds.width(), mDividerSurfaceWidthPx);
}
- // Update divider line position in the surface
+ // Update divider line surface position relative to the divider surface
final int offset = mDividerPosition - dividerSurfacePosition;
- mDividerLine.setX(mProperties.mIsVerticalSplit ? offset : 0);
- mDividerLine.setY(mProperties.mIsVerticalSplit ? 0 : offset);
+ if (mProperties.mIsVerticalSplit) {
+ t.setPosition(mDividerLineSurface, offset, 0);
+ t.setWindowCrop(mDividerLineSurface,
+ mProperties.mDividerWidthPx, taskBounds.height());
+ } else {
+ t.setPosition(mDividerLineSurface, 0, offset);
+ t.setWindowCrop(mDividerLineSurface,
+ taskBounds.width(), mProperties.mDividerWidthPx);
+ }
+
+ // Update divider line surface visibility and color.
+ // If a container is fully expanded, the divider line is invisible unless dragging.
+ final boolean isDividerLineVisible = !mProperties.mIsDraggableExpandType || mIsDragging;
+ t.setVisibility(mDividerLineSurface, isDividerLineVisible);
+ t.setColor(mDividerLineSurface, colorToFloatArray(
+ Color.valueOf(mProperties.mDividerAttributes.getDividerColor())));
if (mIsDragging) {
updateVeils(t);
@@ -1277,21 +1304,6 @@
*/
private void updateDivider(@NonNull SurfaceControl.Transaction t) {
mDividerLayout.removeAllViews();
- mDividerLayout.addView(mDividerLine);
- if (mProperties.mIsDraggableExpandType && !mIsDragging) {
- // If a container is fully expanded, the divider overlays on the expanded container.
- mDividerLine.setBackgroundColor(Color.TRANSPARENT);
- } else {
- mDividerLine.setBackgroundColor(mProperties.mDividerAttributes.getDividerColor());
- }
- final Rect taskBounds = mProperties.mConfiguration.windowConfiguration.getBounds();
- mDividerLine.setLayoutParams(
- mProperties.mIsVerticalSplit
- ? new FrameLayout.LayoutParams(
- mProperties.mDividerWidthPx, taskBounds.height())
- : new FrameLayout.LayoutParams(
- taskBounds.width(), mProperties.mDividerWidthPx)
- );
if (mProperties.mDividerAttributes.getDividerType()
== DividerAttributes.DIVIDER_TYPE_DRAGGABLE) {
createVeils();
@@ -1345,10 +1357,11 @@
}
@NonNull
- private SurfaceControl createChildSurface(@NonNull String name, boolean visible) {
+ private SurfaceControl createChildSurface(
+ @NonNull SurfaceControl parent, @NonNull String name, boolean visible) {
final Rect bounds = mProperties.mConfiguration.windowConfiguration.getBounds();
return new SurfaceControl.Builder()
- .setParent(mProperties.mDecorSurface)
+ .setParent(parent)
.setName(name)
.setHidden(!visible)
.setCallsite("DividerManager.createChildSurface")
@@ -1359,10 +1372,12 @@
private void createVeils() {
if (mPrimaryVeil == null) {
- mPrimaryVeil = createChildSurface("DividerPrimaryVeil", false /* visible */);
+ mPrimaryVeil = createChildSurface(
+ mProperties.mDecorSurface, "DividerPrimaryVeil", false /* visible */);
}
if (mSecondaryVeil == null) {
- mSecondaryVeil = createChildSurface("DividerSecondaryVeil", false /* visible */);
+ mSecondaryVeil = createChildSurface(
+ mProperties.mDecorSurface, "DividerSecondaryVeil", false /* visible */);
}
}
diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml
index 4e7cfb6..8669af3 100644
--- a/libs/WindowManager/Shell/res/values/strings.xml
+++ b/libs/WindowManager/Shell/res/values/strings.xml
@@ -263,7 +263,7 @@
<!-- Accessibility text for the caption back button [CHAR LIMIT=NONE] -->
<string name="back_button_text">Back</string>
<!-- Accessibility text for the caption handle [CHAR LIMIT=NONE] -->
- <string name="handle_text">Handle</string>
+ <string name="handle_text">App handle</string>
<!-- Accessibility text for the handle menu app icon [CHAR LIMIT=NONE] -->
<string name="app_icon_text">App Icon</string>
<!-- Accessibility text for the handle fullscreen button [CHAR LIMIT=NONE] -->
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java
index d54a6b0..c91567d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java
@@ -80,6 +80,7 @@
outline.setPath(mPath);
}
});
+ setContentDescription(getResources().getString(R.string.handle_text));
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java
index 86cec02..84e32a2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java
@@ -81,6 +81,7 @@
private boolean mHasNavigationBar = false;
private boolean mHasStatusBar = false;
private int mNavBarFrameHeight = 0;
+ private int mTaskbarFrameHeight = 0;
private boolean mAllowSeamlessRotationDespiteNavBarMoving = false;
private boolean mNavigationBarCanMove = false;
private boolean mReverseDefaultRotation = false;
@@ -119,6 +120,7 @@
&& mNavigationBarCanMove == other.mNavigationBarCanMove
&& mReverseDefaultRotation == other.mReverseDefaultRotation
&& mNavBarFrameHeight == other.mNavBarFrameHeight
+ && mTaskbarFrameHeight == other.mTaskbarFrameHeight
&& Objects.equals(mInsetsState, other.mInsetsState);
}
@@ -126,7 +128,7 @@
public int hashCode() {
return Objects.hash(mUiMode, mWidth, mHeight, mCutout, mRotation, mDensityDpi,
mNonDecorInsets, mStableInsets, mHasNavigationBar, mHasStatusBar,
- mNavBarFrameHeight, mAllowSeamlessRotationDespiteNavBarMoving,
+ mNavBarFrameHeight, mTaskbarFrameHeight, mAllowSeamlessRotationDespiteNavBarMoving,
mNavigationBarCanMove, mReverseDefaultRotation, mInsetsState);
}
@@ -176,6 +178,7 @@
mNavigationBarCanMove = dl.mNavigationBarCanMove;
mReverseDefaultRotation = dl.mReverseDefaultRotation;
mNavBarFrameHeight = dl.mNavBarFrameHeight;
+ mTaskbarFrameHeight = dl.mTaskbarFrameHeight;
mNonDecorInsets.set(dl.mNonDecorInsets);
mStableInsets.set(dl.mStableInsets);
mInsetsState.set(dl.mInsetsState, true /* copySources */);
@@ -214,7 +217,8 @@
if (mHasStatusBar) {
convertNonDecorInsetsToStableInsets(res, mStableInsets, mCutout, mHasStatusBar);
}
- mNavBarFrameHeight = getNavigationBarFrameHeight(res, mWidth > mHeight);
+ mNavBarFrameHeight = getNavigationBarFrameHeight(res, /* landscape */ mWidth > mHeight);
+ mTaskbarFrameHeight = SystemBarUtils.getTaskbarHeight(res);
}
/**
@@ -321,6 +325,17 @@
outBounds.inset(mStableInsets);
}
+ /** Predicts the calculated stable bounds when in Desktop Mode. */
+ public void getStableBoundsForDesktopMode(Rect outBounds) {
+ getStableBounds(outBounds);
+
+ if (mNavBarFrameHeight != mTaskbarFrameHeight) {
+ // Currently not in pinned taskbar mode, exclude taskbar insets instead of current
+ // navigation insets from bounds.
+ outBounds.bottom = mHeight - mTaskbarFrameHeight;
+ }
+ }
+
/**
* Gets navigation bar position for this layout
* @return Navigation bar position for this layout.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index a52141c5..ba97c832 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -21,6 +21,7 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.util.RotationUtils.deltaRotation;
import static android.util.RotationUtils.rotateBounds;
+import static android.view.Surface.ROTATION_0;
import static android.view.Surface.ROTATION_270;
import static android.view.Surface.ROTATION_90;
import static android.view.WindowManager.TRANSIT_CHANGE;
@@ -1183,10 +1184,15 @@
? pipTaskInfo.configuration.windowConfiguration.getBounds()
: mPipOrganizer.mAppBounds;
+ // Populate the final surface control transactions from PipTransitionAnimator,
+ // display cutout insets is handled in the swipe pip to home animator, empty it out here
+ // to avoid flicker.
+ final Rect savedDisplayCutoutInsets = new Rect(pipTaskInfo.displayCutoutInsets);
+ pipTaskInfo.displayCutoutInsets.setEmpty();
final PipAnimationController.PipTransitionAnimator animator =
mPipAnimationController.getAnimator(pipTaskInfo, leash, sourceBounds, sourceBounds,
destinationBounds, sourceHintRect, TRANSITION_DIRECTION_TO_PIP,
- 0 /* startingAngle */, 0 /* rotationDelta */)
+ 0 /* startingAngle */, ROTATION_0 /* rotationDelta */)
.setPipTransactionHandler(mTransactionConsumer)
.setTransitionDirection(TRANSITION_DIRECTION_TO_PIP);
// The start state is the end state for swipe-auto-pip.
@@ -1194,6 +1200,7 @@
animator.applySurfaceControlTransaction(leash, startTransaction,
PipAnimationController.FRACTION_END);
startTransaction.apply();
+ pipTaskInfo.displayCutoutInsets.set(savedDisplayCutoutInsets);
mPipBoundsState.setBounds(destinationBounds);
final SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index 9a7ce67..be16207 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -19,6 +19,8 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.windowingModeToString;
+import static android.view.InsetsSource.FLAG_FORCE_CONSUMING;
+import static android.view.InsetsSource.FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR;
import static android.view.MotionEvent.ACTION_CANCEL;
import static android.view.MotionEvent.ACTION_DOWN;
import static android.view.MotionEvent.ACTION_UP;
@@ -593,6 +595,17 @@
// through to the windows below so that the app can respond to input events on
// their custom content.
relayoutParams.mInputFeatures |= WindowManager.LayoutParams.INPUT_FEATURE_SPY;
+ } else {
+ if (Flags.enableCaptionCompatInsetForceConsumption()) {
+ // Force-consume the caption bar insets when the app tries to hide the caption.
+ // This improves app compatibility of immersive apps.
+ relayoutParams.mInsetSourceFlags |= FLAG_FORCE_CONSUMING;
+ }
+ }
+ if (Flags.enableCaptionCompatInsetForceConsumptionAlways()) {
+ // Always force-consume the caption bar insets for maximum app compatibility,
+ // including non-immersive apps that just don't handle caption insets properly.
+ relayoutParams.mInsetSourceFlags |= FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR;
}
// Report occluding elements as bounding rects to the insets system so that apps can
// draw in the empty space in the center:
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index a691f59..5f00aa2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -19,7 +19,6 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.content.res.Configuration.DENSITY_DPI_UNDEFINED;
-import static android.view.InsetsSource.FLAG_FORCE_CONSUMING;
import static android.view.WindowInsets.Type.captionBar;
import static android.view.WindowInsets.Type.mandatorySystemGestures;
import static android.view.WindowInsets.Type.statusBars;
@@ -54,7 +53,6 @@
import android.window.WindowContainerTransaction;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.window.flags.Flags;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
@@ -375,7 +373,8 @@
}
final WindowDecorationInsets newInsets = new WindowDecorationInsets(
- mTaskInfo.token, mOwner, captionInsetsRect, boundingRects);
+ mTaskInfo.token, mOwner, captionInsetsRect, boundingRects,
+ params.mInsetSourceFlags);
if (!newInsets.equals(mWindowDecorationInsets)) {
// Add or update this caption as an insets source.
mWindowDecorationInsets = newInsets;
@@ -660,7 +659,7 @@
final int captionHeight = loadDimensionPixelSize(mContext.getResources(), captionHeightId);
final Rect captionInsets = new Rect(0, 0, 0, captionHeight);
final WindowDecorationInsets newInsets = new WindowDecorationInsets(mTaskInfo.token,
- mOwner, captionInsets, null /* boundingRets */);
+ mOwner, captionInsets, null /* boundingRets */, 0 /* flags */);
if (!newInsets.equals(mWindowDecorationInsets)) {
mWindowDecorationInsets = newInsets;
mWindowDecorationInsets.addOrUpdate(wct);
@@ -674,6 +673,7 @@
int mCaptionWidthId;
final List<OccludingCaptionElement> mOccludingCaptionElements = new ArrayList<>();
int mInputFeatures;
+ @InsetsSource.Flags int mInsetSourceFlags;
int mShadowRadiusId;
int mCornerRadius;
@@ -689,6 +689,7 @@
mCaptionWidthId = Resources.ID_NULL;
mOccludingCaptionElements.clear();
mInputFeatures = 0;
+ mInsetSourceFlags = 0;
mShadowRadiusId = Resources.ID_NULL;
mCornerRadius = 0;
@@ -753,20 +754,20 @@
private final Binder mOwner;
private final Rect mFrame;
private final Rect[] mBoundingRects;
+ private final @InsetsSource.Flags int mFlags;
private WindowDecorationInsets(WindowContainerToken token, Binder owner, Rect frame,
- Rect[] boundingRects) {
+ Rect[] boundingRects, @InsetsSource.Flags int flags) {
mToken = token;
mOwner = owner;
mFrame = frame;
mBoundingRects = boundingRects;
+ mFlags = flags;
}
void addOrUpdate(WindowContainerTransaction wct) {
- final @InsetsSource.Flags int captionSourceFlags =
- Flags.enableCaptionCompatInsetForceConsumption() ? FLAG_FORCE_CONSUMING : 0;
wct.addInsetsSource(mToken, mOwner, INDEX, captionBar(), mFrame, mBoundingRects,
- captionSourceFlags);
+ mFlags);
wct.addInsetsSource(mToken, mOwner, INDEX, mandatorySystemGestures(), mFrame,
mBoundingRects, 0 /* flags */);
}
@@ -782,12 +783,13 @@
if (!(o instanceof WindowDecoration.WindowDecorationInsets that)) return false;
return Objects.equals(mToken, that.mToken) && Objects.equals(mOwner,
that.mOwner) && Objects.equals(mFrame, that.mFrame)
- && Objects.deepEquals(mBoundingRects, that.mBoundingRects);
+ && Objects.deepEquals(mBoundingRects, that.mBoundingRects)
+ && mFlags == that.mFlags;
}
@Override
public int hashCode() {
- return Objects.hash(mToken, mOwner, mFrame, Arrays.hashCode(mBoundingRects));
+ return Objects.hash(mToken, mOwner, mFrame, Arrays.hashCode(mBoundingRects), mFlags);
}
}
-}
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
index 4b069f9..9c3016e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
@@ -20,6 +20,8 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
+import static android.view.InsetsSource.FLAG_FORCE_CONSUMING;
+import static android.view.InsetsSource.FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR;
import static android.view.WindowInsetsController.APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
@@ -411,6 +413,80 @@
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION)
+ public void updateRelayoutParams_defaultHeader_addsForceConsumingFlag() {
+ final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
+ taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
+ taskInfo.taskDescription.setTopOpaqueSystemBarsAppearance(0);
+ final RelayoutParams relayoutParams = new RelayoutParams();
+
+ DesktopModeWindowDecoration.updateRelayoutParams(
+ relayoutParams,
+ mTestableContext,
+ taskInfo,
+ /* applyStartTransactionOnDraw= */ true,
+ /* shouldSetTaskPositionAndCrop */ false);
+
+ assertThat((relayoutParams.mInsetSourceFlags & FLAG_FORCE_CONSUMING) != 0).isTrue();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION)
+ public void updateRelayoutParams_customHeader_noForceConsumptionFlag() {
+ final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
+ taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
+ taskInfo.taskDescription.setTopOpaqueSystemBarsAppearance(
+ APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND);
+ final RelayoutParams relayoutParams = new RelayoutParams();
+
+ DesktopModeWindowDecoration.updateRelayoutParams(
+ relayoutParams,
+ mTestableContext,
+ taskInfo,
+ /* applyStartTransactionOnDraw= */ true,
+ /* shouldSetTaskPositionAndCrop */ false);
+
+ assertThat((relayoutParams.mInsetSourceFlags & FLAG_FORCE_CONSUMING) == 0).isTrue();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION_ALWAYS)
+ public void updateRelayoutParams_header_addsForceConsumingCaptionBar() {
+ final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
+ taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
+ final RelayoutParams relayoutParams = new RelayoutParams();
+
+ DesktopModeWindowDecoration.updateRelayoutParams(
+ relayoutParams,
+ mTestableContext,
+ taskInfo,
+ /* applyStartTransactionOnDraw= */ true,
+ /* shouldSetTaskPositionAndCrop */ false);
+
+ assertThat(
+ (relayoutParams.mInsetSourceFlags & FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR) != 0)
+ .isTrue();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION_ALWAYS)
+ public void updateRelayoutParams_handle_skipsForceConsumingCaptionBar() {
+ final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
+ taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+ final RelayoutParams relayoutParams = new RelayoutParams();
+
+ DesktopModeWindowDecoration.updateRelayoutParams(
+ relayoutParams,
+ mTestableContext,
+ taskInfo,
+ /* applyStartTransactionOnDraw= */ true,
+ /* shouldSetTaskPositionAndCrop */ false);
+
+ assertThat(
+ (relayoutParams.mInsetSourceFlags & FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR) == 0)
+ .isTrue();
+ }
+
@DisableFlags(Flags.FLAG_ENABLE_ADDITIONAL_WINDOWS_ABOVE_STATUS_BAR)
public void relayout_fullscreenTask_appliesTransactionImmediately() {
final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
index 2d1bf14..ca6e03c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
@@ -20,6 +20,7 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
import static android.view.InsetsSource.FLAG_FORCE_CONSUMING;
+import static android.view.InsetsSource.FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR;
import static android.view.WindowInsets.Type.captionBar;
import static android.view.WindowInsets.Type.mandatorySystemGestures;
import static android.view.WindowInsets.Type.statusBars;
@@ -56,7 +57,6 @@
import android.graphics.Color;
import android.graphics.Point;
import android.graphics.Rect;
-import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
import android.testing.AndroidTestingRunner;
import android.util.DisplayMetrics;
@@ -75,7 +75,6 @@
import androidx.test.filters.SmallTest;
import com.android.dx.mockito.inline.extended.StaticMockitoSession;
-import com.android.window.flags.Flags;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestRunningTaskInfoBuilder;
@@ -781,8 +780,7 @@
}
@Test
- @EnableFlags(Flags.FLAG_ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION)
- public void testRelayout_captionInsetForceConsume() {
+ public void testRelayout_captionInsetSourceFlags() {
final Display defaultDisplay = mock(Display.class);
doReturn(defaultDisplay).when(mMockDisplayController)
.getDisplay(Display.DEFAULT_DISPLAY);
@@ -794,11 +792,14 @@
final ActivityManager.RunningTaskInfo taskInfo =
builder.setToken(token).setBounds(new Rect(0, 0, 1000, 1000)).build();
final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
+ mRelayoutParams.mInsetSourceFlags =
+ FLAG_FORCE_CONSUMING | FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR;
windowDecor.relayout(taskInfo);
- // Caption inset source should be force-consuming.
+ // Caption inset source should add params' flags.
verify(mMockWindowContainerTransaction).addInsetsSource(eq(token), any(),
- eq(0) /* index */, eq(captionBar()), any(), any(), eq(FLAG_FORCE_CONSUMING));
+ eq(0) /* index */, eq(captionBar()), any(), any(),
+ eq(FLAG_FORCE_CONSUMING | FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR));
}
@Test
diff --git a/libs/hwui/jni/Bitmap.cpp b/libs/hwui/jni/Bitmap.cpp
index d415700..010c4e8 100644
--- a/libs/hwui/jni/Bitmap.cpp
+++ b/libs/hwui/jni/Bitmap.cpp
@@ -33,9 +33,6 @@
#define DEBUG_PARCEL 0
static jclass gBitmap_class;
-static jfieldID gBitmap_nativePtr;
-static jmethodID gBitmap_constructorMethodID;
-static jmethodID gBitmap_reinitMethodID;
namespace android {
@@ -183,6 +180,9 @@
void reinitBitmap(JNIEnv* env, jobject javaBitmap, const SkImageInfo& info,
bool isPremultiplied)
{
+ static jmethodID gBitmap_reinitMethodID =
+ GetMethodIDOrDie(env, gBitmap_class, "reinit", "(IIZ)V");
+
// The caller needs to have already set the alpha type properly, so the
// native SkBitmap stays in sync with the Java Bitmap.
assert_premultiplied(info, isPremultiplied);
@@ -194,6 +194,10 @@
jobject createBitmap(JNIEnv* env, Bitmap* bitmap,
int bitmapCreateFlags, jbyteArray ninePatchChunk, jobject ninePatchInsets,
int density) {
+ static jmethodID gBitmap_constructorMethodID =
+ GetMethodIDOrDie(env, gBitmap_class,
+ "<init>", "(JIIIZ[BLandroid/graphics/NinePatch$InsetStruct;Z)V");
+
bool isMutable = bitmapCreateFlags & kBitmapCreateFlag_Mutable;
bool isPremultiplied = bitmapCreateFlags & kBitmapCreateFlag_Premultiplied;
// The caller needs to have already set the alpha type properly, so the
@@ -232,11 +236,17 @@
using namespace android;
using namespace android::bitmap;
+static inline jlong getNativePtr(JNIEnv* env, jobject bitmap) {
+ static jfieldID gBitmap_nativePtr =
+ GetFieldIDOrDie(env, gBitmap_class, "mNativePtr", "J");
+ return env->GetLongField(bitmap, gBitmap_nativePtr);
+}
+
Bitmap* GraphicsJNI::getNativeBitmap(JNIEnv* env, jobject bitmap) {
SkASSERT(env);
SkASSERT(bitmap);
SkASSERT(env->IsInstanceOf(bitmap, gBitmap_class));
- jlong bitmapHandle = env->GetLongField(bitmap, gBitmap_nativePtr);
+ jlong bitmapHandle = getNativePtr(env, bitmap);
LocalScopedBitmap localBitmap(bitmapHandle);
return localBitmap.valid() ? &localBitmap->bitmap() : nullptr;
}
@@ -246,7 +256,7 @@
SkASSERT(env);
SkASSERT(bitmap);
SkASSERT(env->IsInstanceOf(bitmap, gBitmap_class));
- jlong bitmapHandle = env->GetLongField(bitmap, gBitmap_nativePtr);
+ jlong bitmapHandle = getNativePtr(env, bitmap);
LocalScopedBitmap localBitmap(bitmapHandle);
if (outRowBytes) {
*outRowBytes = localBitmap->rowBytes();
@@ -1269,9 +1279,6 @@
int register_android_graphics_Bitmap(JNIEnv* env)
{
gBitmap_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/graphics/Bitmap"));
- gBitmap_nativePtr = GetFieldIDOrDie(env, gBitmap_class, "mNativePtr", "J");
- gBitmap_constructorMethodID = GetMethodIDOrDie(env, gBitmap_class, "<init>", "(JIIIZ[BLandroid/graphics/NinePatch$InsetStruct;Z)V");
- gBitmap_reinitMethodID = GetMethodIDOrDie(env, gBitmap_class, "reinit", "(IIZ)V");
uirenderer::HardwareBufferHelpers::init();
return android::RegisterMethodsOrDie(env, "android/graphics/Bitmap", gBitmapMethods,
NELEM(gBitmapMethods));
diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
index 8acaf3b..e575dae 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -5263,6 +5263,8 @@
* main thread.)
*/
public void setCallback(@Nullable /* MediaCodec. */ Callback cb, @Nullable Handler handler) {
+ boolean setCallbackStallFlag =
+ GetFlag(() -> android.media.codec.Flags.setCallbackStall());
if (cb != null) {
synchronized (mListenerLock) {
EventHandler newHandler = getEventHandlerOn(handler, mCallbackHandler);
@@ -5270,7 +5272,7 @@
// even if we were to extend this to be callable dynamically, it must
// be called when codec is flushed, so no messages are pending.
if (newHandler != mCallbackHandler) {
- if (android.media.codec.Flags.setCallbackStall()) {
+ if (setCallbackStallFlag) {
logAndRun(
"[new handler] removeMessages(SET_CALLBACK)",
() -> {
@@ -5289,7 +5291,7 @@
}
}
} else if (mCallbackHandler != null) {
- if (android.media.codec.Flags.setCallbackStall()) {
+ if (setCallbackStallFlag) {
logAndRun(
"[null handler] removeMessages(SET_CALLBACK)",
() -> {
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/modes/EnableZenModeDialog.java b/packages/SettingsLib/src/com/android/settingslib/notification/modes/EnableZenModeDialog.java
index 054c849..c48694c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/modes/EnableZenModeDialog.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/modes/EnableZenModeDialog.java
@@ -159,9 +159,9 @@
});
if (mCancelIsNeutral) {
- builder.setNeutralButton(R.string.cancel, null);
+ builder.setNeutralButton(android.R.string.cancel, null);
} else {
- builder.setNegativeButton(R.string.cancel, null);
+ builder.setNegativeButton(android.R.string.cancel, null);
}
View contentView = getContentView();
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java
index 03a2b75..990a2d4 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java
@@ -298,6 +298,10 @@
return mIsManualDnd;
}
+ public boolean isEnabled() {
+ return mRule.isEnabled();
+ }
+
public boolean isActive() {
return mStatus == Status.ENABLED_AND_ACTIVE;
}
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 04d30ed..0b5187c 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -65,6 +65,7 @@
<uses-permission android:name="com.android.voicemail.permission.ADD_VOICEMAIL" />
<uses-permission android:name="android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS" />
<uses-permission android:name="android.permission.GET_PROCESS_STATE_AND_OOM_SCORE" />
+ <uses-permission android:name="android.permission.READ_DROPBOX_DATA" />
<uses-permission android:name="android.permission.READ_LOGS" />
<uses-permission android:name="android.permission.BRIGHTNESS_SLIDER_USAGE" />
<uses-permission android:name="android.permission.ACCESS_AMBIENT_LIGHT_STATS" />
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index eb9d0ab..462db34 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -1206,6 +1206,13 @@
}
flag {
+ name: "hubmode_fullscreen_vertical_swipe"
+ namespace: "systemui"
+ description: "Enables fullscreen vertical swiping in hub mode to bring up and down the bouncer and shade"
+ bug: "340177049"
+}
+
+flag {
namespace: "systemui"
name: "remove_update_listener_in_qs_icon_view_impl"
description: "Remove update listeners in QsIconViewImpl class to avoid memory leak."
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
index f73b6cd..9fd30b4 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
@@ -33,7 +33,7 @@
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.ui.composable.ComposableScene
import javax.inject.Inject
-import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.Flow
object Bouncer {
object Elements {
@@ -56,7 +56,7 @@
) : ComposableScene {
override val key = Scenes.Bouncer
- override val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
+ override val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
viewModel.destinationScenes
@Composable
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt
index 1ce51af..8c38253 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt
@@ -98,13 +98,7 @@
bottom = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Bottom],
)
- val bottomAreaPlaceable =
- bottomAreaMeasurable.measure(
- noMinConstraints.copy(
- maxHeight =
- (constraints.maxHeight - lockIconBounds.bottom).coerceAtLeast(0)
- )
- )
+ val bottomAreaPlaceable = bottomAreaMeasurable.measure(noMinConstraints)
val communalGridPlaceable =
communalGridMeasurable.measure(
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index b63b235..fc95754 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -42,6 +42,7 @@
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.focusable
+import androidx.compose.foundation.gestures.awaitFirstDown
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
@@ -112,6 +113,11 @@
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
+import androidx.compose.ui.input.nestedscroll.NestedScrollSource
+import androidx.compose.ui.input.nestedscroll.nestedScroll
+import androidx.compose.ui.input.pointer.changedToUp
+import androidx.compose.ui.input.pointer.changedToUpIgnoreConsumed
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.layout.LayoutCoordinates
import androidx.compose.ui.layout.boundsInWindow
@@ -138,6 +144,7 @@
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.times
+import androidx.compose.ui.util.fastAll
import androidx.compose.ui.viewinterop.AndroidView
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.window.layout.WindowMetricsCalculator
@@ -206,13 +213,51 @@
ScrollOnUpdatedLiveContentEffect(communalContent, gridState)
}
+ val nestedScrollConnection = remember {
+ object : NestedScrollConnection {
+ override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
+ // Begin tracking nested scrolling
+ viewModel.onNestedScrolling()
+ return super.onPreScroll(available, source)
+ }
+ }
+ }
+
Box(
modifier =
modifier
.semantics { testTagsAsResourceId = true }
.testTag(COMMUNAL_HUB_TEST_TAG)
.fillMaxSize()
+ .nestedScroll(nestedScrollConnection)
.pointerInput(gridState, contentOffset, contentListState) {
+ awaitPointerEventScope {
+ while (true) {
+ var event = awaitFirstDown(requireUnconsumed = false)
+ // Reset touch on first event.
+ viewModel.onResetTouchState()
+
+ // Process down event in case it's consumed immediately
+ if (event.isConsumed) {
+ viewModel.onHubTouchConsumed()
+ }
+
+ do {
+ var event = awaitPointerEvent()
+ for (change in event.changes) {
+ if (change.isConsumed) {
+ // Signal touch consumption on any consumed event.
+ viewModel.onHubTouchConsumed()
+ }
+ }
+ } while (
+ !event.changes.fastAll {
+ it.changedToUp() || it.changedToUpIgnoreConsumed()
+ }
+ )
+ }
+ }
+
// If not in edit mode, don't allow selecting items.
if (!viewModel.isEditMode) return@pointerInput
observeTaps { offset ->
@@ -793,7 +838,7 @@
onClick = onClick,
colors =
ButtonDefaults.outlinedButtonColors(
- contentColor = colors.primary,
+ contentColor = colors.onPrimaryContainer,
),
border = BorderStroke(width = 2.0.dp, color = colors.primary),
contentPadding = Dimensions.ButtonPadding,
@@ -857,7 +902,7 @@
/** Creates an empty card used to highlight a particular spot on the grid. */
@Composable
fun HighlightedItem(modifier: Modifier = Modifier, alpha: Float = 1.0f) {
- val brush = SolidColor(LocalAndroidColorScheme.current.primaryFixed)
+ val brush = SolidColor(LocalAndroidColorScheme.current.primary)
Box(
modifier =
// drawBehind lets us draw outside the bounds of the widgets so that we don't need to
@@ -995,6 +1040,11 @@
modifier =
modifier
.then(selectableModifier)
+ .thenIf(!viewModel.isEditMode && !model.inQuietMode) {
+ Modifier.pointerInput(Unit) {
+ observeTaps { viewModel.onTapWidget(model.componentName, model.priority) }
+ }
+ }
.thenIf(!viewModel.isEditMode && model.inQuietMode) {
Modifier.pointerInput(Unit) {
// consume tap to prevent the child view from triggering interactions with
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt
index 94018bb..e0fc340 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt
@@ -30,8 +30,8 @@
import com.android.systemui.scene.ui.composable.ComposableScene
import com.android.systemui.statusbar.phone.SystemUIDialogFactory
import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
/** The communal scene shows glanceable hub when the device is locked and docked. */
@@ -45,7 +45,7 @@
) : ComposableScene {
override val key = Scenes.Communal
- override val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
+ override val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
MutableStateFlow<Map<UserAction, UserActionResult>>(
mapOf(
Swipe(SwipeDirection.Right) to UserActionResult(Scenes.Lockscreen),
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
index c241f9c..b077e18 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
@@ -30,7 +30,7 @@
import com.android.systemui.scene.ui.composable.ComposableScene
import dagger.Lazy
import javax.inject.Inject
-import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.Flow
/** The lock screen scene shows when the device is locked. */
@SysUISingleton
@@ -42,7 +42,7 @@
) : ComposableScene {
override val key = Scenes.Lockscreen
- override val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
+ override val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
viewModel.destinationScenes
@Composable
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
index 7400711..c01039d 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
@@ -94,7 +94,7 @@
with(topAreaSection) {
DefaultClockLayout(
modifier =
- Modifier.graphicsLayer {
+ Modifier.fillMaxWidth(0.5f).graphicsLayer {
translationX = unfoldTranslations.start
}
)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt
index 86639fa..b40bccb 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt
@@ -98,7 +98,7 @@
) {
MovableElement(
key = IndicationAreaElementKey,
- modifier = modifier.shortcutPadding(),
+ modifier = modifier.indicationAreaPadding(),
) {
content {
IndicationArea(
@@ -210,6 +210,11 @@
)
.padding(bottom = dimensionResource(R.dimen.keyguard_affordance_vertical_offset))
}
+
+ @Composable
+ private fun Modifier.indicationAreaPadding(): Modifier {
+ return this.padding(bottom = dimensionResource(R.dimen.keyguard_indication_margin_bottom))
+ }
}
private val StartButtonElementKey = ElementKey("StartButton")
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeScene.kt
index db98bc8f..7159def 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeScene.kt
@@ -44,7 +44,7 @@
import dagger.Lazy
import java.util.Optional
import javax.inject.Inject
-import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.Flow
@SysUISingleton
class NotificationsShadeScene
@@ -64,7 +64,7 @@
override val key = Scenes.NotificationsShade
- override val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
+ override val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
sceneViewModel.destinationScenes
@Composable
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
index 3cf8e70..2800eee 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
@@ -69,6 +69,8 @@
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.animation.scene.SceneScope
import com.android.compose.animation.scene.TransitionState
+import com.android.compose.animation.scene.UserAction
+import com.android.compose.animation.scene.UserActionResult
import com.android.compose.animation.scene.animateSceneDpAsState
import com.android.compose.animation.scene.animateSceneFloatAsState
import com.android.compose.modifiers.thenIf
@@ -79,7 +81,6 @@
import com.android.systemui.common.ui.compose.windowinsets.LocalRawScreenHeight
import com.android.systemui.compose.modifiers.sysuiResTag
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.media.controls.ui.composable.MediaCarousel
import com.android.systemui.media.controls.ui.controller.MediaCarouselController
import com.android.systemui.media.controls.ui.view.MediaHost
@@ -109,16 +110,13 @@
import javax.inject.Inject
import javax.inject.Named
import kotlin.math.roundToInt
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.flow.Flow
/** The Quick Settings (AKA "QS") scene shows the quick setting tiles. */
@SysUISingleton
class QuickSettingsScene
@Inject
constructor(
- @Application private val applicationScope: CoroutineScope,
private val shadeSession: SaveableSession,
private val notificationStackScrollView: Lazy<NotificationScrollView>,
private val viewModel: QuickSettingsSceneViewModel,
@@ -131,12 +129,8 @@
) : ComposableScene {
override val key = Scenes.QuickSettings
- override val destinationScenes =
- viewModel.destinationScenes.stateIn(
- scope = applicationScope,
- started = SharingStarted.WhileSubscribed(),
- initialValue = emptyMap(),
- )
+ override val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
+ viewModel.destinationScenes
@Composable
override fun SceneScope.Content(
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeScene.kt
index 422c9f6..f6d1283 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeScene.kt
@@ -60,7 +60,7 @@
import dagger.Lazy
import java.util.Optional
import javax.inject.Inject
-import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.Flow
@SysUISingleton
class QuickSettingsShadeScene
@@ -76,7 +76,7 @@
override val key = Scenes.QuickSettingsShade
- override val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
+ override val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
viewModel.destinationScenes
@Composable
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
index a44041a..a9ddf84 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
@@ -36,7 +36,7 @@
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
import dagger.Lazy
import javax.inject.Inject
-import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.Flow
/**
* "Gone" is not a real scene but rather the absence of scenes when we want to skip showing any
@@ -52,7 +52,7 @@
) : ComposableScene {
override val key = Scenes.Gone
- override val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
+ override val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
viewModel.destinationScenes
@Composable
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
index 9c9e6c6..d5874d1 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
@@ -14,8 +14,6 @@
* limitations under the License.
*/
-@file:OptIn(ExperimentalComposeUiApi::class)
-
package com.android.systemui.scene.ui.composable
import androidx.compose.foundation.layout.Box
@@ -23,14 +21,13 @@
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
-import androidx.compose.runtime.getValue
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.mutableStateMapOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
-import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
-import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.animation.scene.MutableSceneTransitionLayoutState
import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.SceneTransitionLayout
@@ -40,6 +37,7 @@
import com.android.systemui.ribbon.ui.composable.BottomRightCornerRibbon
import com.android.systemui.scene.shared.model.SceneDataSourceDelegator
import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
+import kotlinx.coroutines.flow.collectLatest
/**
* Renders a container of a collection of "scenes" that the user can switch between using certain
@@ -62,19 +60,20 @@
fun SceneContainer(
viewModel: SceneContainerViewModel,
sceneByKey: Map<SceneKey, ComposableScene>,
+ initialSceneKey: SceneKey,
dataSourceDelegator: SceneDataSourceDelegator,
modifier: Modifier = Modifier,
) {
val coroutineScope = rememberCoroutineScope()
- val currentSceneKey: SceneKey by viewModel.currentScene.collectAsStateWithLifecycle()
val state: MutableSceneTransitionLayoutState = remember {
MutableSceneTransitionLayoutState(
- initialScene = currentSceneKey,
+ initialScene = initialSceneKey,
canChangeScene = { toScene -> viewModel.canChangeScene(toScene) },
transitions = SceneContainerTransitions,
enableInterruptions = false,
)
}
+ val currentSceneKey = state.transitionState.currentScene
DisposableEffect(state) {
val dataSource = SceneTransitionLayoutDataSource(state, coroutineScope)
@@ -87,19 +86,32 @@
onDispose { viewModel.setTransitionState(null) }
}
- val userActionsBySceneKey: Map<SceneKey, Map<UserAction, UserActionResult>> =
- sceneByKey.values.associate { scene ->
- val userActions by scene.destinationScenes.collectAsStateWithLifecycle(emptyMap())
- val resolvedUserActions = viewModel.resolveSceneFamilies(userActions)
- scene.key to resolvedUserActions
+ val userActionsBySceneKey: MutableMap<SceneKey, Map<UserAction, UserActionResult>> = remember {
+ mutableStateMapOf()
+ }
+ LaunchedEffect(currentSceneKey) {
+ try {
+ sceneByKey[currentSceneKey]?.destinationScenes?.collectLatest { userActions ->
+ userActionsBySceneKey[currentSceneKey] = viewModel.resolveSceneFamilies(userActions)
+ }
+ } finally {
+ userActionsBySceneKey[currentSceneKey] = emptyMap()
}
+ }
Box(
modifier = Modifier.fillMaxSize(),
) {
SceneTransitionLayout(state = state, modifier = modifier.fillMaxSize()) {
sceneByKey.forEach { (sceneKey, composableScene) ->
- scene(key = sceneKey, userActions = checkNotNull(userActionsBySceneKey[sceneKey])) {
+ scene(
+ key = sceneKey,
+ userActions = userActionsBySceneKey.getOrDefault(sceneKey, emptyMap())
+ ) {
+ // Activate the scene.
+ LaunchedEffect(composableScene) { composableScene.activate() }
+
+ // Render the scene.
with(composableScene) {
this@scene.Content(
modifier = Modifier.element(sceneKey.rootElementKey).fillMaxSize(),
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
index 09414a9..21e3431 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
@@ -109,7 +109,7 @@
import javax.inject.Inject
import javax.inject.Named
import kotlin.math.roundToInt
-import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.Flow
object Shade {
object Elements {
@@ -153,7 +153,11 @@
override val key = Scenes.Shade
- override val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
+ override suspend fun activate() {
+ viewModel.activate()
+ }
+
+ override val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
viewModel.destinationScenes
@Composable
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerFullscreenSwipeTouchHandlerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerFullscreenSwipeTouchHandlerTest.java
new file mode 100644
index 0000000..4850085
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerFullscreenSwipeTouchHandlerTest.java
@@ -0,0 +1,246 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.ambient.touch;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.animation.ValueAnimator;
+import android.content.pm.UserInfo;
+import android.graphics.Rect;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.view.GestureDetector.OnGestureListener;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.logging.UiEventLogger;
+import com.android.internal.widget.LockPatternUtils;
+import com.android.systemui.Flags;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.ambient.touch.scrim.ScrimController;
+import com.android.systemui.ambient.touch.scrim.ScrimManager;
+import com.android.systemui.communal.ui.viewmodel.CommunalViewModel;
+import com.android.systemui.kosmos.KosmosJavaAdapter;
+import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.settings.FakeUserTracker;
+import com.android.systemui.shared.system.InputChannelCompat;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
+import com.android.systemui.statusbar.phone.CentralSurfaces;
+import com.android.wm.shell.animation.FlingAnimationUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Collections;
+import java.util.Optional;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+@EnableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE)
+@DisableFlags(Flags.FLAG_COMMUNAL_BOUNCER_DO_NOT_MODIFY_PLUGIN_OPEN)
+public class BouncerFullscreenSwipeTouchHandlerTest extends SysuiTestCase {
+ private KosmosJavaAdapter mKosmos;
+
+ @Mock
+ CentralSurfaces mCentralSurfaces;
+
+ @Mock
+ ScrimManager mScrimManager;
+
+ @Mock
+ ScrimController mScrimController;
+
+ @Mock
+ NotificationShadeWindowController mNotificationShadeWindowController;
+
+ @Mock
+ FlingAnimationUtils mFlingAnimationUtils;
+
+ @Mock
+ FlingAnimationUtils mFlingAnimationUtilsClosing;
+
+ @Mock
+ TouchHandler.TouchSession mTouchSession;
+
+ BouncerSwipeTouchHandler mTouchHandler;
+
+ @Mock
+ BouncerSwipeTouchHandler.ValueAnimatorCreator mValueAnimatorCreator;
+
+ @Mock
+ ValueAnimator mValueAnimator;
+
+ @Mock
+ BouncerSwipeTouchHandler.VelocityTrackerFactory mVelocityTrackerFactory;
+
+ @Mock
+ VelocityTracker mVelocityTracker;
+
+ @Mock
+ UiEventLogger mUiEventLogger;
+
+ @Mock
+ LockPatternUtils mLockPatternUtils;
+
+ @Mock
+ ActivityStarter mActivityStarter;
+
+ @Mock
+ CommunalViewModel mCommunalViewModel;
+
+ FakeUserTracker mUserTracker;
+
+ private static final float TOUCH_REGION = .3f;
+ private static final float MIN_BOUNCER_HEIGHT = .05f;
+
+ private static final Rect SCREEN_BOUNDS = new Rect(0, 0, 1024, 100);
+ private static final UserInfo CURRENT_USER_INFO = new UserInfo(
+ 10,
+ /* name= */ "user10",
+ /* flags= */ 0
+ );
+
+ @Before
+ public void setup() {
+ mKosmos = new KosmosJavaAdapter(this);
+ MockitoAnnotations.initMocks(this);
+ mUserTracker = new FakeUserTracker();
+ mTouchHandler = new BouncerSwipeTouchHandler(
+ mKosmos.getTestScope(),
+ mScrimManager,
+ Optional.of(mCentralSurfaces),
+ mNotificationShadeWindowController,
+ mValueAnimatorCreator,
+ mVelocityTrackerFactory,
+ mLockPatternUtils,
+ mUserTracker,
+ mCommunalViewModel,
+ mFlingAnimationUtils,
+ mFlingAnimationUtilsClosing,
+ TOUCH_REGION,
+ MIN_BOUNCER_HEIGHT,
+ mUiEventLogger,
+ mActivityStarter);
+
+ when(mScrimManager.getCurrentController()).thenReturn(mScrimController);
+ when(mValueAnimatorCreator.create(anyFloat(), anyFloat())).thenReturn(mValueAnimator);
+ when(mVelocityTrackerFactory.obtain()).thenReturn(mVelocityTracker);
+ when(mFlingAnimationUtils.getMinVelocityPxPerSecond()).thenReturn(Float.MAX_VALUE);
+ when(mTouchSession.getBounds()).thenReturn(SCREEN_BOUNDS);
+ when(mLockPatternUtils.isSecure(CURRENT_USER_INFO.id)).thenReturn(true);
+
+ mUserTracker.set(Collections.singletonList(CURRENT_USER_INFO), 0);
+ }
+
+ /**
+ * Ensures expansion does not happen for full vertical swipes when touch is not available.
+ */
+ @Test
+ public void testFullSwipe_notInitiatedWhenNotAvailable() {
+ mTouchHandler.onGlanceableTouchAvailable(false);
+ mTouchHandler.onSessionStart(mTouchSession);
+ ArgumentCaptor<OnGestureListener> gestureListenerCaptor =
+ ArgumentCaptor.forClass(OnGestureListener.class);
+ verify(mTouchSession).registerGestureListener(gestureListenerCaptor.capture());
+
+ // A touch within range at the bottom of the screen should trigger listening
+ assertThat(gestureListenerCaptor.getValue()
+ .onScroll(Mockito.mock(MotionEvent.class),
+ Mockito.mock(MotionEvent.class),
+ 1,
+ 2)).isFalse();
+ }
+
+ /**
+ * Ensures expansion only happens for full vertical swipes when touch is available.
+ */
+ @Test
+ public void testFullSwipe_initiatedWhenAvailable() {
+ mTouchHandler.onGlanceableTouchAvailable(true);
+ mTouchHandler.onSessionStart(mTouchSession);
+ ArgumentCaptor<OnGestureListener> gestureListenerCaptor =
+ ArgumentCaptor.forClass(OnGestureListener.class);
+ verify(mTouchSession).registerGestureListener(gestureListenerCaptor.capture());
+
+ // A touch within range at the bottom of the screen should trigger listening
+ assertThat(gestureListenerCaptor.getValue()
+ .onScroll(Mockito.mock(MotionEvent.class),
+ Mockito.mock(MotionEvent.class),
+ 1,
+ 2)).isTrue();
+ }
+
+ @Test
+ public void testFullSwipe_motionUpResetsTouchState() {
+ mTouchHandler.onGlanceableTouchAvailable(true);
+ mTouchHandler.onSessionStart(mTouchSession);
+ ArgumentCaptor<OnGestureListener> gestureListenerCaptor =
+ ArgumentCaptor.forClass(OnGestureListener.class);
+ ArgumentCaptor<InputChannelCompat.InputEventListener> inputListenerCaptor =
+ ArgumentCaptor.forClass(InputChannelCompat.InputEventListener.class);
+ verify(mTouchSession).registerGestureListener(gestureListenerCaptor.capture());
+ verify(mTouchSession).registerInputListener(inputListenerCaptor.capture());
+
+ // A touch within range at the bottom of the screen should trigger listening
+ assertThat(gestureListenerCaptor.getValue()
+ .onScroll(Mockito.mock(MotionEvent.class),
+ Mockito.mock(MotionEvent.class),
+ 1,
+ 2)).isTrue();
+
+ MotionEvent upEvent = Mockito.mock(MotionEvent.class);
+ when(upEvent.getAction()).thenReturn(MotionEvent.ACTION_UP);
+ inputListenerCaptor.getValue().onInputEvent(upEvent);
+ verify(mCommunalViewModel).onResetTouchState();
+ }
+
+ @Test
+ public void testFullSwipe_motionCancelResetsTouchState() {
+ mTouchHandler.onGlanceableTouchAvailable(true);
+ mTouchHandler.onSessionStart(mTouchSession);
+ ArgumentCaptor<OnGestureListener> gestureListenerCaptor =
+ ArgumentCaptor.forClass(OnGestureListener.class);
+ ArgumentCaptor<InputChannelCompat.InputEventListener> inputListenerCaptor =
+ ArgumentCaptor.forClass(InputChannelCompat.InputEventListener.class);
+ verify(mTouchSession).registerGestureListener(gestureListenerCaptor.capture());
+ verify(mTouchSession).registerInputListener(inputListenerCaptor.capture());
+
+ // A touch within range at the bottom of the screen should trigger listening
+ assertThat(gestureListenerCaptor.getValue()
+ .onScroll(Mockito.mock(MotionEvent.class),
+ Mockito.mock(MotionEvent.class),
+ 1,
+ 2)).isTrue();
+
+ MotionEvent upEvent = Mockito.mock(MotionEvent.class);
+ when(upEvent.getAction()).thenReturn(MotionEvent.ACTION_CANCEL);
+ inputListenerCaptor.getValue().onInputEvent(upEvent);
+ verify(mCommunalViewModel).onResetTouchState();
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandlerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandlerTest.java
index 7ebc224..0e98b84 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandlerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandlerTest.java
@@ -50,6 +50,8 @@
import com.android.systemui.ambient.touch.scrim.ScrimController;
import com.android.systemui.ambient.touch.scrim.ScrimManager;
import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants;
+import com.android.systemui.communal.ui.viewmodel.CommunalViewModel;
+import com.android.systemui.kosmos.KosmosJavaAdapter;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.settings.FakeUserTracker;
import com.android.systemui.shade.ShadeExpansionChangeEvent;
@@ -72,7 +74,9 @@
@SmallTest
@RunWith(AndroidJUnit4.class)
+@DisableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE)
public class BouncerSwipeTouchHandlerTest extends SysuiTestCase {
+ private KosmosJavaAdapter mKosmos;
@Mock
CentralSurfaces mCentralSurfaces;
@@ -120,6 +124,9 @@
@Mock
Region mRegion;
+ @Mock
+ CommunalViewModel mCommunalViewModel;
+
@Captor
ArgumentCaptor<Rect> mRectCaptor;
@@ -139,9 +146,11 @@
@Before
public void setup() {
+ mKosmos = new KosmosJavaAdapter(this);
MockitoAnnotations.initMocks(this);
mUserTracker = new FakeUserTracker();
mTouchHandler = new BouncerSwipeTouchHandler(
+ mKosmos.getTestScope(),
mScrimManager,
Optional.of(mCentralSurfaces),
mNotificationShadeWindowController,
@@ -149,6 +158,7 @@
mVelocityTrackerFactory,
mLockPatternUtils,
mUserTracker,
+ mCommunalViewModel,
mFlingAnimationUtils,
mFlingAnimationUtilsClosing,
TOUCH_REGION,
@@ -201,7 +211,6 @@
2)).isTrue();
}
-
/**
* Ensures expansion only happens when touch down happens in valid part of the screen.
*/
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.kt
index 7fd9ce2..204d4b0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.kt
@@ -26,8 +26,10 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.ambient.touch.TouchHandler.TouchSession
import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
+import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.flags.Flags.COMMUNAL_SERVICE_ENABLED
import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.kosmos.testScope
import com.android.systemui.shade.ShadeViewController
import com.android.systemui.shared.system.InputChannelCompat
import com.android.systemui.statusbar.phone.CentralSurfaces
@@ -50,11 +52,11 @@
@RunWith(AndroidJUnit4::class)
class ShadeTouchHandlerTest : SysuiTestCase() {
private var kosmos = testKosmos()
-
private var mCentralSurfaces = mock<CentralSurfaces>()
private var mShadeViewController = mock<ShadeViewController>()
private var mDreamManager = mock<DreamManager>()
private var mTouchSession = mock<TouchSession>()
+ private var communalViewModel = mock<CommunalViewModel>()
private lateinit var mTouchHandler: ShadeTouchHandler
@@ -65,9 +67,11 @@
fun setup() {
mTouchHandler =
ShadeTouchHandler(
+ kosmos.testScope,
Optional.of(mCentralSurfaces),
mShadeViewController,
mDreamManager,
+ communalViewModel,
kosmos.communalSettingsInteractor,
TOUCH_HEIGHT
)
@@ -75,6 +79,7 @@
// Verifies that a swipe down in the gesture region is captured by the shade touch handler.
@Test
+ @DisableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE)
fun testSwipeDown_captured() {
val captured = swipe(Direction.DOWN)
Truth.assertThat(captured).isTrue()
@@ -82,6 +87,7 @@
// Verifies that a swipe in the upward direction is not captured.
@Test
+ @DisableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE)
fun testSwipeUp_notCaptured() {
val captured = swipe(Direction.UP)
@@ -91,6 +97,7 @@
// Verifies that a swipe down forwards captured touches to central surfaces for handling.
@Test
+ @DisableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE)
@EnableFlags(Flags.FLAG_COMMUNAL_HUB)
fun testSwipeDown_communalEnabled_sentToCentralSurfaces() {
kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, true)
@@ -103,7 +110,7 @@
// Verifies that a swipe down forwards captured touches to the shade view for handling.
@Test
- @DisableFlags(Flags.FLAG_COMMUNAL_HUB)
+ @DisableFlags(Flags.FLAG_COMMUNAL_HUB, Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE)
fun testSwipeDown_communalDisabled_sentToShadeView() {
swipe(Direction.DOWN)
@@ -114,6 +121,7 @@
// Verifies that a swipe down while dreaming forwards captured touches to the shade view for
// handling.
@Test
+ @DisableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE)
fun testSwipeDown_dreaming_sentToShadeView() {
whenever(mDreamManager.isDreaming).thenReturn(true)
swipe(Direction.DOWN)
@@ -124,6 +132,7 @@
// Verifies that a swipe up is not forwarded to central surfaces.
@Test
+ @DisableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE)
@EnableFlags(Flags.FLAG_COMMUNAL_HUB)
fun testSwipeUp_communalEnabled_touchesNotSent() {
kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, true)
@@ -137,7 +146,7 @@
// Verifies that a swipe up is not forwarded to the shade view.
@Test
- @DisableFlags(Flags.FLAG_COMMUNAL_HUB)
+ @DisableFlags(Flags.FLAG_COMMUNAL_HUB, Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE)
fun testSwipeUp_communalDisabled_touchesNotSent() {
swipe(Direction.UP)
@@ -147,6 +156,7 @@
}
@Test
+ @DisableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE)
fun testCancelMotionEvent_popsTouchSession() {
swipe(Direction.DOWN)
val event = MotionEvent.obtain(0, 0, MotionEvent.ACTION_CANCEL, 0f, 0f, 0)
@@ -154,6 +164,60 @@
verify(mTouchSession).pop()
}
+ @Test
+ @EnableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE)
+ fun testFullVerticalSwipe_initiatedWhenAvailable() {
+ // Indicate touches are available
+ mTouchHandler.onGlanceableTouchAvailable(true)
+
+ // Verify swipe is handled
+ val captured = swipe(Direction.DOWN)
+ Truth.assertThat(captured).isTrue()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE)
+ fun testFullVerticalSwipe_notInitiatedWhenNotAvailable() {
+ // Indicate touches aren't available
+ mTouchHandler.onGlanceableTouchAvailable(false)
+
+ // Verify swipe is not handled
+ val captured = swipe(Direction.DOWN)
+ Truth.assertThat(captured).isFalse()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE)
+ fun testFullVerticalSwipe_resetsTouchStateOnUp() {
+ // Indicate touches are available
+ mTouchHandler.onGlanceableTouchAvailable(true)
+
+ // Verify swipe is handled
+ swipe(Direction.DOWN)
+
+ val upEvent: MotionEvent = mock()
+ whenever(upEvent.action).thenReturn(MotionEvent.ACTION_UP)
+ mInputListenerCaptor.lastValue.onInputEvent(upEvent)
+
+ verify(communalViewModel).onResetTouchState()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE)
+ fun testFullVerticalSwipe_resetsTouchStateOnCancel() {
+ // Indicate touches are available
+ mTouchHandler.onGlanceableTouchAvailable(true)
+
+ // Verify swipe is handled
+ swipe(Direction.DOWN)
+
+ val upEvent: MotionEvent = mock()
+ whenever(upEvent.action).thenReturn(MotionEvent.ACTION_CANCEL)
+ mInputListenerCaptor.lastValue.onInputEvent(upEvent)
+
+ verify(communalViewModel).onResetTouchState()
+ }
+
/**
* Simulates a swipe in the given direction and returns true if the touch was intercepted by the
* touch handler's gesture listener.
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
index c28cf34..a09189e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
@@ -372,6 +372,7 @@
nonAuxiliarySubtypes: Int,
): InputMethodModel {
return InputMethodModel(
+ userId = UUID.randomUUID().mostSignificantBits.toInt(),
imeId = UUID.randomUUID().toString(),
subtypes =
List(auxiliarySubtypes + nonAuxiliarySubtypes) {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/log/CommunalLoggerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/log/CommunalLoggerStartableTest.kt
index def8698..28ad269 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/log/CommunalLoggerStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/log/CommunalLoggerStartableTest.kt
@@ -22,10 +22,13 @@
import com.android.compose.animation.scene.SceneKey
import com.android.internal.logging.UiEventLogger
import com.android.systemui.SysuiTestCase
-import com.android.systemui.communal.domain.interactor.CommunalInteractor
-import com.android.systemui.communal.domain.interactor.communalInteractor
+import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
+import com.android.systemui.communal.domain.interactor.communalSceneInteractor
import com.android.systemui.communal.shared.log.CommunalUiEvent
import com.android.systemui.communal.shared.model.CommunalScenes
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.kosmos.testScope
import com.android.systemui.testKosmos
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -53,18 +56,21 @@
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
- private lateinit var communalInteractor: CommunalInteractor
+ private lateinit var communalSceneInteractor: CommunalSceneInteractor
+ private lateinit var keyguardRepository: FakeKeyguardRepository
private lateinit var underTest: CommunalLoggerStartable
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- communalInteractor = kosmos.communalInteractor
+ communalSceneInteractor = kosmos.communalSceneInteractor
+ keyguardRepository = kosmos.fakeKeyguardRepository
underTest =
CommunalLoggerStartable(
testScope.backgroundScope,
- communalInteractor,
+ communalSceneInteractor,
+ kosmos.keyguardInteractor,
uiEventLogger,
)
underTest.start()
@@ -73,10 +79,13 @@
@Test
fun transitionStateLogging_enterCommunalHub() =
testScope.runTest {
+ // Not dreaming
+ keyguardRepository.setDreamingWithOverlay(false)
+
// Transition state is default (non-communal)
val transitionState =
MutableStateFlow<ObservableTransitionState>(idle(CommunalScenes.Default))
- communalInteractor.setTransitionState(transitionState)
+ communalSceneInteractor.setTransitionState(transitionState)
runCurrent()
// Verify nothing is logged from the default state
@@ -99,12 +108,15 @@
}
@Test
- fun transitionStateLogging_enterCommunalHub_canceled() =
+ fun transitionStateLogging_cancelEnteringCommunalHub() =
testScope.runTest {
+ // Not dreaming
+ keyguardRepository.setDreamingWithOverlay(false)
+
// Transition state is default (non-communal)
val transitionState =
MutableStateFlow<ObservableTransitionState>(idle(CommunalScenes.Default))
- communalInteractor.setTransitionState(transitionState)
+ communalSceneInteractor.setTransitionState(transitionState)
runCurrent()
// Verify nothing is logged from the default state
@@ -132,10 +144,13 @@
@Test
fun transitionStateLogging_exitCommunalHub() =
testScope.runTest {
+ // Not dreaming
+ keyguardRepository.setDreamingWithOverlay(false)
+
// Transition state is communal
val transitionState =
MutableStateFlow<ObservableTransitionState>(idle(CommunalScenes.Communal))
- communalInteractor.setTransitionState(transitionState)
+ communalSceneInteractor.setTransitionState(transitionState)
runCurrent()
// Verify SHOWN is logged when it's the default state
@@ -158,12 +173,15 @@
}
@Test
- fun transitionStateLogging_exitCommunalHub_canceled() =
+ fun transitionStateLogging_cancelExitingCommunalHub() =
testScope.runTest {
+ // Not dreaming
+ keyguardRepository.setDreamingWithOverlay(false)
+
// Transition state is communal
val transitionState =
MutableStateFlow<ObservableTransitionState>(idle(CommunalScenes.Communal))
- communalInteractor.setTransitionState(transitionState)
+ communalSceneInteractor.setTransitionState(transitionState)
runCurrent()
// Clear the initial SHOWN event from the logger
@@ -188,6 +206,136 @@
verify(uiEventLogger, never()).log(CommunalUiEvent.COMMUNAL_HUB_GONE)
}
+ @Test
+ fun transitionStateLogging_dreaming_enterCommunalHub() =
+ testScope.runTest {
+ // Dreaming
+ keyguardRepository.setDreamingWithOverlay(true)
+
+ // Transition state is default (non-communal)
+ val transitionState =
+ MutableStateFlow<ObservableTransitionState>(idle(CommunalScenes.Default))
+ communalSceneInteractor.setTransitionState(transitionState)
+ runCurrent()
+
+ // Verify nothing is logged from the default state
+ verify(uiEventLogger, never()).log(any())
+
+ // Start transition to communal
+ transitionState.value = transition(to = CommunalScenes.Communal)
+ runCurrent()
+
+ // Verify UiEvent logged
+ verify(uiEventLogger).log(CommunalUiEvent.DREAM_TO_COMMUNAL_HUB_SWIPE_START)
+
+ // Finish transition to communal
+ transitionState.value = idle(CommunalScenes.Communal)
+ runCurrent()
+
+ // Verify UiEvent logged
+ verify(uiEventLogger).log(CommunalUiEvent.DREAM_TO_COMMUNAL_HUB_SWIPE_FINISH)
+ verify(uiEventLogger).log(CommunalUiEvent.COMMUNAL_HUB_SHOWN)
+ }
+
+ @Test
+ fun transitionStateLogging_dreaming_cancelEnteringCommunalHub() =
+ testScope.runTest {
+ // Dreaming
+ keyguardRepository.setDreamingWithOverlay(true)
+
+ // Transition state is default (non-communal)
+ val transitionState =
+ MutableStateFlow<ObservableTransitionState>(idle(CommunalScenes.Default))
+ communalSceneInteractor.setTransitionState(transitionState)
+ runCurrent()
+
+ // Verify nothing is logged from the default state
+ verify(uiEventLogger, never()).log(any())
+
+ // Start transition to communal
+ transitionState.value = transition(to = CommunalScenes.Communal)
+ runCurrent()
+
+ // Verify UiEvent logged
+ verify(uiEventLogger).log(CommunalUiEvent.DREAM_TO_COMMUNAL_HUB_SWIPE_START)
+
+ // Cancel the transition
+ transitionState.value = idle(CommunalScenes.Default)
+ runCurrent()
+
+ // Verify UiEvent logged
+ verify(uiEventLogger).log(CommunalUiEvent.DREAM_TO_COMMUNAL_HUB_SWIPE_CANCEL)
+
+ // Verify neither SHOWN nor GONE is logged
+ verify(uiEventLogger, never()).log(CommunalUiEvent.COMMUNAL_HUB_SHOWN)
+ verify(uiEventLogger, never()).log(CommunalUiEvent.COMMUNAL_HUB_GONE)
+ }
+
+ @Test
+ fun transitionStateLogging_dreaming_exitCommunalHub() =
+ testScope.runTest {
+ // Dreaming
+ keyguardRepository.setDreamingWithOverlay(true)
+
+ // Transition state is communal
+ val transitionState =
+ MutableStateFlow<ObservableTransitionState>(idle(CommunalScenes.Communal))
+ communalSceneInteractor.setTransitionState(transitionState)
+ runCurrent()
+
+ // Verify SHOWN is logged when it's the default state
+ verify(uiEventLogger).log(CommunalUiEvent.COMMUNAL_HUB_SHOWN)
+
+ // Start transition from communal
+ transitionState.value = transition(from = CommunalScenes.Communal)
+ runCurrent()
+
+ // Verify UiEvent logged
+ verify(uiEventLogger).log(CommunalUiEvent.COMMUNAL_HUB_TO_DREAM_SWIPE_START)
+
+ // Finish transition to communal
+ transitionState.value = idle(CommunalScenes.Default)
+ runCurrent()
+
+ // Verify UiEvent logged
+ verify(uiEventLogger).log(CommunalUiEvent.COMMUNAL_HUB_TO_DREAM_SWIPE_FINISH)
+ verify(uiEventLogger).log(CommunalUiEvent.COMMUNAL_HUB_GONE)
+ }
+
+ @Test
+ fun transitionStateLogging_dreaming_cancelExitingCommunalHub() =
+ testScope.runTest {
+ // Dreaming
+ keyguardRepository.setDreamingWithOverlay(true)
+
+ // Transition state is communal
+ val transitionState =
+ MutableStateFlow<ObservableTransitionState>(idle(CommunalScenes.Communal))
+ communalSceneInteractor.setTransitionState(transitionState)
+ runCurrent()
+
+ // Clear the initial SHOWN event from the logger
+ clearInvocations(uiEventLogger)
+
+ // Start transition from communal
+ transitionState.value = transition(from = CommunalScenes.Communal)
+ runCurrent()
+
+ // Verify UiEvent logged
+ verify(uiEventLogger).log(CommunalUiEvent.COMMUNAL_HUB_TO_DREAM_SWIPE_START)
+
+ // Cancel the transition
+ transitionState.value = idle(CommunalScenes.Communal)
+ runCurrent()
+
+ // Verify UiEvent logged
+ verify(uiEventLogger).log(CommunalUiEvent.COMMUNAL_HUB_TO_DREAM_SWIPE_CANCEL)
+
+ // Verify neither SHOWN nor GONE is logged
+ verify(uiEventLogger, never()).log(CommunalUiEvent.COMMUNAL_HUB_SHOWN)
+ verify(uiEventLogger, never()).log(CommunalUiEvent.COMMUNAL_HUB_GONE)
+ }
+
private fun transition(
from: SceneKey = CommunalScenes.Default,
to: SceneKey = CommunalScenes.Default,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/log/CommunalMetricsLoggerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/log/CommunalMetricsLoggerTest.kt
index 537ca03..82918a5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/log/CommunalMetricsLoggerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/log/CommunalMetricsLoggerTest.kt
@@ -94,6 +94,30 @@
}
@Test
+ fun logTapWidget_componentNotLoggable_doNotLog() {
+ underTest.logTapWidget(
+ componentName = "com.yellow.package/my_test_widget",
+ rank = 2,
+ )
+ verify(statsLogProxy, never())
+ .writeCommunalHubWidgetEventReported(anyInt(), any(), anyInt())
+ }
+
+ @Test
+ fun logTapWidget_componentLoggable_logRemoveEvent() {
+ underTest.logTapWidget(
+ componentName = "com.red.package/my_test_widget",
+ rank = 2,
+ )
+ verify(statsLogProxy)
+ .writeCommunalHubWidgetEventReported(
+ SysUiStatsLog.COMMUNAL_HUB_WIDGET_EVENT_REPORTED__ACTION__TAP,
+ "com.red.package/my_test_widget",
+ 2,
+ )
+ }
+
+ @Test
fun logWidgetsSnapshot_logOnlyLoggableComponents() {
val statsEvents = mutableListOf<StatsEvent>()
underTest.logWidgetsSnapshot(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
index d862a21..7a41bc6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.communal.view.viewmodel
+import android.content.ComponentName
import android.content.pm.UserInfo
import android.platform.test.flag.junit.FlagsParameterization
import android.provider.Settings
@@ -41,6 +42,7 @@
import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
import com.android.systemui.communal.domain.interactor.communalTutorialInteractor
import com.android.systemui.communal.domain.model.CommunalContentModel
+import com.android.systemui.communal.shared.log.CommunalMetricsLogger
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel.Companion.POPUP_AUTO_HIDE_TIMEOUT_MS
@@ -107,6 +109,7 @@
@RunWith(ParameterizedAndroidJunit4::class)
class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
@Mock private lateinit var mediaHost: MediaHost
+ @Mock private lateinit var metricsLogger: CommunalMetricsLogger
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
@@ -170,7 +173,8 @@
kosmos.communalTutorialInteractor,
kosmos.shadeInteractor,
mediaHost,
- logcatLogBuffer("CommunalViewModelTest")
+ logcatLogBuffer("CommunalViewModelTest"),
+ metricsLogger,
)
}
@@ -746,6 +750,23 @@
verify(communalInteractor).setScrollPosition(eq(index), eq(offset))
}
+ @Test
+ fun onTapWidget_logEvent() {
+ underTest.onTapWidget(ComponentName("test_pkg", "test_cls"), priority = 10)
+ verify(metricsLogger).logTapWidget("test_pkg/test_cls", rank = 10)
+ }
+
+ @Test
+ fun glanceableTouchAvailable_availableWhenNestedScrollingWithoutConsumption() =
+ testScope.runTest {
+ val touchAvailable by collectLastValue(underTest.glanceableTouchAvailable)
+ assertThat(touchAvailable).isTrue()
+ underTest.onHubTouchConsumed()
+ assertThat(touchAvailable).isFalse()
+ underTest.onNestedScrolling()
+ assertThat(touchAvailable).isTrue()
+ }
+
private suspend fun setIsMainUser(isMainUser: Boolean) {
val user = if (isMainUser) MAIN_USER_INFO else SECONDARY_USER_INFO
with(userRepository) {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
index 60c9bb0..4c24ce2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
@@ -54,6 +54,7 @@
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.communal.domain.interactor.communalInteractor
import com.android.systemui.communal.domain.interactor.setCommunalAvailable
+import com.android.systemui.communal.shared.log.CommunalUiEvent
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.complication.ComplicationHostViewController
import com.android.systemui.complication.ComplicationLayoutEngine
@@ -660,6 +661,7 @@
verify(mDreamOverlayCallback).onRedirectWake(true)
client.onWakeRequested()
verify(mCommunalInteractor).changeScene(eq(CommunalScenes.Communal), isNull())
+ verify(mUiEventLogger).log(CommunalUiEvent.DREAM_TO_COMMUNAL_HUB_DREAM_AWAKE_START)
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/inputmethod/data/repository/InputMethodRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/inputmethod/data/repository/InputMethodRepositoryTest.kt
index 857cdce..274880b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/inputmethod/data/repository/InputMethodRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/inputmethod/data/repository/InputMethodRepositoryTest.kt
@@ -56,9 +56,6 @@
fun setUp() {
MockitoAnnotations.initMocks(this)
- whenever(inputMethodManager.getEnabledInputMethodSubtypeList(eq(null), anyBoolean()))
- .thenReturn(listOf())
-
underTest =
InputMethodRepositoryImpl(
backgroundDispatcher = kosmos.testDispatcher,
@@ -71,10 +68,16 @@
testScope.runTest {
whenever(inputMethodManager.getEnabledInputMethodListAsUser(eq(USER_HANDLE)))
.thenReturn(listOf())
- whenever(inputMethodManager.getEnabledInputMethodSubtypeList(any(), anyBoolean()))
+ whenever(
+ inputMethodManager.getEnabledInputMethodSubtypeListAsUser(
+ any(),
+ anyBoolean(),
+ eq(USER_HANDLE)
+ )
+ )
.thenReturn(listOf())
- assertThat(underTest.enabledInputMethods(USER_ID, fetchSubtypes = true).count())
+ assertThat(underTest.enabledInputMethods(USER_HANDLE, fetchSubtypes = true).count())
.isEqualTo(0)
}
@@ -83,11 +86,20 @@
testScope.runTest {
val subtypeId = 123
val isAuxiliary = true
+ val selectedImiId = "imiId"
+ val selectedImi = mock<InputMethodInfo>()
+ whenever(selectedImi.id).thenReturn(selectedImiId)
+ whenever(inputMethodManager.getCurrentInputMethodInfoAsUser(eq(USER_HANDLE)))
+ .thenReturn(selectedImi)
whenever(inputMethodManager.getEnabledInputMethodListAsUser(eq(USER_HANDLE)))
- .thenReturn(listOf(mock<InputMethodInfo>()))
- whenever(inputMethodManager.getEnabledInputMethodSubtypeList(any(), anyBoolean()))
- .thenReturn(listOf())
- whenever(inputMethodManager.getEnabledInputMethodSubtypeList(eq(null), anyBoolean()))
+ .thenReturn(listOf(selectedImi))
+ whenever(
+ inputMethodManager.getEnabledInputMethodSubtypeListAsUser(
+ eq(selectedImiId),
+ anyBoolean(),
+ eq(USER_HANDLE)
+ )
+ )
.thenReturn(
listOf(
InputMethodSubtype.InputMethodSubtypeBuilder()
@@ -97,7 +109,7 @@
)
)
- val result = underTest.selectedInputMethodSubtypes()
+ val result = underTest.selectedInputMethodSubtypes(USER_HANDLE)
assertThat(result).hasSize(1)
assertThat(result.first().subtypeId).isEqualTo(subtypeId)
assertThat(result.first().isAuxiliary).isEqualTo(isAuxiliary)
@@ -108,7 +120,7 @@
testScope.runTest {
val displayId = 7
- underTest.showInputMethodPicker(displayId, /* showAuxiliarySubtypes = */ true)
+ underTest.showInputMethodPicker(displayId, /* showAuxiliarySubtypes= */ true)
verify(inputMethodManager)
.showInputMethodPickerFromSystem(
@@ -118,7 +130,6 @@
}
companion object {
- private const val USER_ID = 100
- private val USER_HANDLE = UserHandle.of(USER_ID)
+ private val USER_HANDLE = UserHandle.of(100)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/inputmethod/domain/interactor/InputMethodInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/inputmethod/domain/interactor/InputMethodInteractorTest.kt
index d23ff2a..8e6de2f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/inputmethod/domain/interactor/InputMethodInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/inputmethod/domain/interactor/InputMethodInteractorTest.kt
@@ -143,6 +143,7 @@
nonAuxiliarySubtypes: Int,
): InputMethodModel {
return InputMethodModel(
+ userId = UUID.randomUUID().mostSignificantBits.toInt(),
imeId = UUID.randomUUID().toString(),
subtypes =
List(auxiliarySubtypes + nonAuxiliarySubtypes) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractorTest.kt
similarity index 96%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractorTest.kt
index 65aa825..7424320 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractorTest.kt
@@ -32,11 +32,13 @@
package com.android.systemui.keyguard.domain.interactor
+import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.systemui.Flags
+import com.android.systemui.Flags.FLAG_COMMUNAL_SCENE_KTF_REFACTOR
import com.android.systemui.SysuiTestCase
import com.android.systemui.communal.data.repository.fakeCommunalSceneRepository
import com.android.systemui.communal.shared.model.CommunalScenes
@@ -49,13 +51,13 @@
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.testKosmos
-import kotlin.test.Test
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
+import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.reset
import org.mockito.Mockito.spy
@@ -108,6 +110,7 @@
@Test
@EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
fun testShowWhenLockedActivity_noLongerOnTop_transitionsToGlanceableHub_ifIdleOnCommunal() =
testScope.runTest {
kosmos.fakeCommunalSceneRepository.setTransitionState(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
index 3db9ef1..bca83f0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
@@ -257,7 +257,6 @@
private fun createLockscreenSceneViewModel(): LockscreenSceneViewModel {
return LockscreenSceneViewModel(
- applicationScope = testScope.backgroundScope,
deviceEntryInteractor = kosmos.deviceEntryInteractor,
communalInteractor = kosmos.communalInteractor,
touchHandling =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorTest.kt
index a67e7c6..d5c9102 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorTest.kt
@@ -21,68 +21,64 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.animation.Expandable
+import com.android.systemui.animation.DialogTransitionAnimator
+import com.android.systemui.qs.tiles.base.actions.FakeQSTileIntentUserInputHandler
import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandlerSubject
-import com.android.systemui.qs.tiles.base.actions.qsTileIntentUserInputHandler
import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx
import com.android.systemui.qs.tiles.impl.modes.domain.model.ModesTileModel
-import com.android.systemui.statusbar.policy.ui.dialog.mockModesDialogDelegate
-import com.android.systemui.testKosmos
-import com.google.common.truth.Truth.assertThat
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.statusbar.policy.ui.dialog.ModesDialogDelegate
+import com.google.common.truth.Truth
+import kotlin.coroutines.EmptyCoroutineContext
import kotlinx.coroutines.test.runTest
+import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.kotlin.eq
-import org.mockito.kotlin.mock
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
@SmallTest
@RunWith(AndroidJUnit4::class)
@EnableFlags(android.app.Flags.FLAG_MODES_UI)
class ModesTileUserActionInteractorTest : SysuiTestCase() {
- private val kosmos = testKosmos()
- private val inputHandler = kosmos.qsTileIntentUserInputHandler
- private val mockDialogDelegate = kosmos.mockModesDialogDelegate
+ private val inputHandler = FakeQSTileIntentUserInputHandler()
- private val underTest =
- ModesTileUserActionInteractor(
- inputHandler,
- mockDialogDelegate,
- )
+ @Mock private lateinit var dialogTransitionAnimator: DialogTransitionAnimator
+ @Mock private lateinit var dialogDelegate: ModesDialogDelegate
+ @Mock private lateinit var mockDialog: SystemUIDialog
- @Test
- fun handleClick_active() = runTest {
- val expandable = mock<Expandable>()
- underTest.handleInput(
- QSTileInputTestKtx.click(data = ModesTileModel(true), expandable = expandable))
+ private lateinit var underTest: ModesTileUserActionInteractor
- verify(mockDialogDelegate).showDialog(eq(expandable))
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+
+ whenever(dialogDelegate.createDialog()).thenReturn(mockDialog)
+
+ underTest =
+ ModesTileUserActionInteractor(
+ EmptyCoroutineContext,
+ inputHandler,
+ dialogTransitionAnimator,
+ dialogDelegate,
+ )
}
@Test
- fun handleClick_inactive() = runTest {
- val expandable = mock<Expandable>()
- underTest.handleInput(
- QSTileInputTestKtx.click(data = ModesTileModel(false), expandable = expandable))
+ fun handleClick() = runTest {
+ underTest.handleInput(QSTileInputTestKtx.click(ModesTileModel(false)))
- verify(mockDialogDelegate).showDialog(eq(expandable))
+ verify(mockDialog).show()
}
@Test
- fun handleLongClick_active() = runTest {
- underTest.handleInput(QSTileInputTestKtx.longClick(ModesTileModel(true)))
-
- QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput {
- assertThat(it.intent.action).isEqualTo(Settings.ACTION_ZEN_MODE_SETTINGS)
- }
- }
-
- @Test
- fun handleLongClick_inactive() = runTest {
+ fun handleLongClick() = runTest {
underTest.handleInput(QSTileInputTestKtx.longClick(ModesTileModel(false)))
QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput {
- assertThat(it.intent.action).isEqualTo(Settings.ACTION_ZEN_MODE_SETTINGS)
+ Truth.assertThat(it.intent.action).isEqualTo(Settings.ACTION_ZEN_MODE_SETTINGS)
}
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
index 9249621..3ca802e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
@@ -93,7 +93,6 @@
sceneContainerStartable.start()
underTest =
QuickSettingsSceneViewModel(
- applicationScope = testScope.backgroundScope,
brightnessMirrorViewModel = kosmos.brightnessMirrorViewModel,
shadeHeaderViewModel = kosmos.shadeHeaderViewModel,
qsSceneAdapter = qsFlexiglassAdapter,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
index 39b3662..228d61a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -143,7 +143,6 @@
private val lockscreenSceneViewModel by lazy {
LockscreenSceneViewModel(
- applicationScope = testScope.backgroundScope,
deviceEntryInteractor = deviceEntryInteractor,
communalInteractor = communalInteractor,
touchHandling =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModelTest.kt
index 0ab6a82..a4992e2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModelTest.kt
@@ -53,7 +53,6 @@
fun setUp() {
underTest =
GoneSceneViewModel(
- applicationScope = testScope.backgroundScope,
shadeInteractor = kosmos.shadeInteractor,
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
index da22c6d..343b6bd 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
@@ -25,6 +25,7 @@
import com.android.compose.animation.scene.Swipe
import com.android.compose.animation.scene.SwipeDirection
import com.android.systemui.SysuiTestCase
+import com.android.systemui.activatable.activateIn
import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
@@ -59,6 +60,7 @@
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
+import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -78,6 +80,11 @@
private val underTest: ShadeSceneViewModel by lazy { kosmos.shadeSceneViewModel }
+ @Before
+ fun setUp() {
+ underTest.activateIn(testScope)
+ }
+
@Test
fun upTransitionSceneKey_deviceLocked_lockScreen() =
testScope.runTest {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinatorTest.kt
index 8810ade..7b87aeb 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinatorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinatorTest.kt
@@ -20,7 +20,6 @@
import android.app.Notification
import android.app.NotificationManager.IMPORTANCE_DEFAULT
import android.app.NotificationManager.IMPORTANCE_LOW
-import android.os.UserHandle
import android.platform.test.annotations.EnableFlags
import android.provider.Settings
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -475,20 +474,6 @@
val collectionListener: NotifCollectionListener =
argumentCaptor { verify(notifPipeline).addCollectionListener(capture()) }.lastValue
-
- var showOnlyUnseenNotifsOnKeyguardSetting: Boolean
- get() =
- fakeSettings.getIntForUser(
- Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
- UserHandle.USER_CURRENT,
- ) == 1
- set(value) {
- fakeSettings.putIntForUser(
- Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
- if (value) 1 else 2,
- UserHandle.USER_CURRENT,
- )
- }
}
companion object {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinatorTest.kt
index 7e9f437..3fd9c21 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinatorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinatorTest.kt
@@ -18,21 +18,23 @@
package com.android.systemui.statusbar.notification.collection.coordinator
import android.app.Notification
-import android.os.UserHandle
import android.platform.test.flag.junit.FlagsParameterization
import android.provider.Settings
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.dump.DumpManager
+import com.android.systemui.dump.dumpManager
import com.android.systemui.flags.andSceneContainer
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.keyguardRepository
+import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionStep
-import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
import com.android.systemui.log.logcatLogBuffer
-import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.plugins.statusbar.statusBarStateController
import com.android.systemui.scene.data.repository.Idle
import com.android.systemui.scene.data.repository.setTransition
import com.android.systemui.scene.domain.interactor.sceneInteractor
@@ -43,12 +45,15 @@
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
-import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor
+import com.android.systemui.statusbar.notification.domain.interactor.lockScreenShowOnlyUnseenNotificationsSetting
+import com.android.systemui.statusbar.notification.domain.interactor.seenNotificationsInteractor
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
-import com.android.systemui.statusbar.policy.HeadsUpManager
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener
+import com.android.systemui.statusbar.policy.headsUpManager
+import com.android.systemui.testKosmos
import com.android.systemui.util.settings.FakeSettings
+import com.android.systemui.util.settings.fakeSettings
import com.google.common.truth.Truth.assertThat
import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.CoroutineScope
@@ -73,13 +78,23 @@
@RunWith(ParameterizedAndroidJunit4::class)
class OriginalUnseenKeyguardCoordinatorTest(flags: FlagsParameterization) : SysuiTestCase() {
- private val kosmos = Kosmos()
+ private val kosmos =
+ testKosmos().apply {
+ testDispatcher = UnconfinedTestDispatcher()
+ statusBarStateController = mock()
+ fakeSettings.putInt(Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS, 1)
+ }
- private val headsUpManager: HeadsUpManager = mock()
- private val keyguardRepository = FakeKeyguardRepository()
- private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
+ private val keyguardRepository
+ get() = kosmos.fakeKeyguardRepository
+
+ private val keyguardTransitionRepository
+ get() = kosmos.fakeKeyguardTransitionRepository
+
+ private val statusBarStateController
+ get() = kosmos.statusBarStateController
+
private val notifPipeline: NotifPipeline = mock()
- private val statusBarStateController: StatusBarStateController = mock()
init {
mSetFlagsRule.setFlagsParameterization(flags)
@@ -253,7 +268,7 @@
collectionListener.onEntryAdded(fakeEntry)
// GIVEN: The setting for filtering unseen notifications is disabled
- showOnlyUnseenNotifsOnKeyguardSetting = false
+ kosmos.lockScreenShowOnlyUnseenNotificationsSetting = false
// GIVEN: The pipeline has registered the unseen filter for invalidation
val invalidationListener: Pluggable.PluggableListener<NotifFilter> = mock()
@@ -267,7 +282,7 @@
assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isFalse()
// WHEN: The secure setting is changed
- showOnlyUnseenNotifsOnKeyguardSetting = true
+ kosmos.lockScreenShowOnlyUnseenNotificationsSetting = true
// THEN: The pipeline is invalidated
verify(invalidationListener).onPluggableInvalidated(same(unseenFilter), any())
@@ -608,35 +623,25 @@
private fun runKeyguardCoordinatorTest(
testBlock: suspend KeyguardCoordinatorTestScope.() -> Unit
) {
- val testDispatcher = UnconfinedTestDispatcher()
- val testScope = TestScope(testDispatcher)
- val fakeSettings =
- FakeSettings().apply {
- putInt(Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS, 1)
- }
- val seenNotificationsInteractor =
- SeenNotificationsInteractor(ActiveNotificationListRepository())
val keyguardCoordinator =
OriginalUnseenKeyguardCoordinator(
- testDispatcher,
- mock<DumpManager>(),
- headsUpManager,
- keyguardRepository,
- kosmos.keyguardTransitionInteractor,
- KeyguardCoordinatorLogger(logcatLogBuffer()),
- testScope.backgroundScope,
- fakeSettings,
- seenNotificationsInteractor,
- statusBarStateController,
+ dumpManager = kosmos.dumpManager,
+ headsUpManager = kosmos.headsUpManager,
+ keyguardRepository = kosmos.keyguardRepository,
+ keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor,
+ logger = KeyguardCoordinatorLogger(logcatLogBuffer()),
+ scope = kosmos.testScope.backgroundScope,
+ seenNotificationsInteractor = kosmos.seenNotificationsInteractor,
+ statusBarStateController = kosmos.statusBarStateController,
sceneInteractor = kosmos.sceneInteractor,
)
keyguardCoordinator.attach(notifPipeline)
- testScope.runTest {
+ kosmos.testScope.runTest {
KeyguardCoordinatorTestScope(
keyguardCoordinator,
- testScope,
- seenNotificationsInteractor,
- fakeSettings,
+ kosmos.testScope,
+ kosmos.seenNotificationsInteractor,
+ kosmos.fakeSettings,
)
.testBlock()
}
@@ -658,21 +663,8 @@
argumentCaptor { verify(notifPipeline).addCollectionListener(capture()) }.lastValue
val onHeadsUpChangedListener: OnHeadsUpChangedListener
- get() = argumentCaptor { verify(headsUpManager).addListener(capture()) }.lastValue
-
- var showOnlyUnseenNotifsOnKeyguardSetting: Boolean
get() =
- fakeSettings.getIntForUser(
- Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
- UserHandle.USER_CURRENT,
- ) == 1
- set(value) {
- fakeSettings.putIntForUser(
- Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
- if (value) 1 else 2,
- UserHandle.USER_CURRENT,
- )
- }
+ argumentCaptor { verify(kosmos.headsUpManager).addListener(capture()) }.lastValue
}
companion object {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractorTest.kt
new file mode 100644
index 0000000..2159b86
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractorTest.kt
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ *
+ */
+
+package com.android.systemui.statusbar.notification.domain.interactor
+
+import android.platform.test.annotations.EnableFlags
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
+import com.android.systemui.statusbar.notification.shared.NotificationMinimalismPrototype
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class SeenNotificationsInteractorTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+ private val underTest
+ get() = kosmos.seenNotificationsInteractor
+
+ @Test
+ fun testNoFilteredOutSeenNotifications() = runTest {
+ val hasFilteredOutSeenNotifications by
+ collectLastValue(underTest.hasFilteredOutSeenNotifications)
+
+ underTest.setHasFilteredOutSeenNotifications(false)
+
+ assertThat(hasFilteredOutSeenNotifications).isFalse()
+ }
+
+ @Test
+ fun testHasFilteredOutSeenNotifications() = runTest {
+ val hasFilteredOutSeenNotifications by
+ collectLastValue(underTest.hasFilteredOutSeenNotifications)
+
+ underTest.setHasFilteredOutSeenNotifications(true)
+
+ assertThat(hasFilteredOutSeenNotifications).isTrue()
+ }
+
+ @Test
+ @EnableFlags(NotificationMinimalismPrototype.FLAG_NAME)
+ fun topOngoingAndUnseenNotification() = runTest {
+ val entry1 = NotificationEntryBuilder().setTag("entry1").build()
+ val entry2 = NotificationEntryBuilder().setTag("entry2").build()
+
+ underTest.setTopOngoingNotification(null)
+ underTest.setTopUnseenNotification(null)
+
+ assertThat(underTest.isTopOngoingNotification(entry1)).isFalse()
+ assertThat(underTest.isTopOngoingNotification(entry2)).isFalse()
+ assertThat(underTest.isTopUnseenNotification(entry1)).isFalse()
+ assertThat(underTest.isTopUnseenNotification(entry2)).isFalse()
+
+ underTest.setTopOngoingNotification(entry1)
+ underTest.setTopUnseenNotification(entry2)
+
+ assertThat(underTest.isTopOngoingNotification(entry1)).isTrue()
+ assertThat(underTest.isTopOngoingNotification(entry2)).isFalse()
+ assertThat(underTest.isTopUnseenNotification(entry1)).isFalse()
+ assertThat(underTest.isTopUnseenNotification(entry2)).isTrue()
+ }
+
+ fun testShowOnlyUnseenNotifsOnKeyguardSetting() = runTest {
+ val settingEnabled by
+ collectLastValue(underTest.isLockScreenShowOnlyUnseenNotificationsEnabled())
+
+ kosmos.lockScreenShowOnlyUnseenNotificationsSetting = false
+ testScheduler.runCurrent()
+ assertThat(settingEnabled).isFalse()
+
+ kosmos.lockScreenShowOnlyUnseenNotificationsSetting = true
+ testScheduler.runCurrent()
+ assertThat(settingEnabled).isTrue()
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt
index 8f9da3b..9a862fc 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt
@@ -60,6 +60,7 @@
// For creating TestableHeadsUpManager
@Mock private val mAccessibilityMgr: AccessibilityManagerWrapper? = null
private val mUiEventLoggerFake = UiEventLoggerFake()
+ @Mock private lateinit var mHeadsUpManagerLogger: HeadsUpManagerLogger
@Mock private lateinit var mBgHandler: Handler
@@ -82,7 +83,8 @@
// Initialize AvalancheController and TestableHeadsUpManager during setUp instead of
// declaration, where mocks are null
- mAvalancheController = AvalancheController(dumpManager, mUiEventLoggerFake, mBgHandler)
+ mAvalancheController = AvalancheController(dumpManager, mUiEventLoggerFake,
+ mHeadsUpManagerLogger, mBgHandler)
testableHeadsUpManager =
TestableHeadsUpManager(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java
index df07b44..9005ae3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java
@@ -81,6 +81,7 @@
static final int TEST_A11Y_AUTO_DISMISS_TIME = 1_000;
private UiEventLoggerFake mUiEventLoggerFake = new UiEventLoggerFake();
+
private final HeadsUpManagerLogger mLogger = spy(new HeadsUpManagerLogger(logcatLogBuffer()));
@Mock private Handler mBgHandler;
@Mock private DumpManager dumpManager;
@@ -149,7 +150,8 @@
@Override
public void SysuiSetup() throws Exception {
super.SysuiSetup();
- mAvalancheController = new AvalancheController(dumpManager, mUiEventLoggerFake, mBgHandler);
+ mAvalancheController = new AvalancheController(dumpManager, mUiEventLoggerFake, mLogger,
+ mBgHandler);
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.kt
index b91bde4..7a6838a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.kt
@@ -179,7 +179,8 @@
mContext
.getOrCreateTestableResources()
.addOverride(R.integer.ambient_notification_extension_time, 500)
- mAvalancheController = AvalancheController(dumpManager, mUiEventLogger, mBgHandler)
+ mAvalancheController = AvalancheController(dumpManager, mUiEventLogger,
+ mHeadsUpManagerLogger, mBgHandler)
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateTest.kt
deleted file mode 100644
index bf0a39b..0000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateTest.kt
+++ /dev/null
@@ -1,124 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-@file:OptIn(ExperimentalCoroutinesApi::class)
-
-package com.android.systemui.statusbar.policy.ui.dialog
-
-import android.app.Dialog
-import android.content.Intent
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.animation.ActivityTransitionAnimator
-import com.android.systemui.animation.mockActivityTransitionAnimatorController
-import com.android.systemui.animation.mockDialogTransitionAnimator
-import com.android.systemui.kosmos.applicationCoroutineScope
-import com.android.systemui.kosmos.mainCoroutineContext
-import com.android.systemui.kosmos.testScope
-import com.android.systemui.plugins.activityStarter
-import com.android.systemui.runOnMainThreadAndWaitForIdleSync
-import com.android.systemui.statusbar.phone.SystemUIDialog
-import com.android.systemui.statusbar.phone.systemUIDialogFactory
-import com.android.systemui.statusbar.policy.ui.dialog.viewmodel.modesDialogViewModel
-import com.android.systemui.testKosmos
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.test.runCurrent
-import kotlinx.coroutines.test.runTest
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.kotlin.any
-import org.mockito.kotlin.eq
-import org.mockito.kotlin.mock
-import org.mockito.kotlin.verify
-import org.mockito.kotlin.whenever
-
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class ModesDialogDelegateTest : SysuiTestCase() {
- private val kosmos = testKosmos()
- private val testScope = kosmos.testScope
- private val activityStarter = kosmos.activityStarter
- private val mockDialogTransitionAnimator = kosmos.mockDialogTransitionAnimator
- private val mockAnimationController = kosmos.mockActivityTransitionAnimatorController
- private lateinit var underTest: ModesDialogDelegate
-
- @Before
- fun setup() {
- whenever(
- mockDialogTransitionAnimator.createActivityTransitionController(
- any<SystemUIDialog>(),
- eq(null)
- )
- )
- .thenReturn(mockAnimationController)
-
- underTest =
- ModesDialogDelegate(
- kosmos.systemUIDialogFactory,
- mockDialogTransitionAnimator,
- activityStarter,
- { kosmos.modesDialogViewModel },
- kosmos.mainCoroutineContext,
- )
- }
-
- @Test
- fun launchFromDialog_whenDialogNotOpen() {
- val intent: Intent = mock()
-
- runOnMainThreadAndWaitForIdleSync { underTest.launchFromDialog(intent) }
-
- verify(activityStarter)
- .startActivity(eq(intent), eq(true), eq<ActivityTransitionAnimator.Controller?>(null))
- }
-
- @Test
- fun launchFromDialog_whenDialogOpen() =
- testScope.runTest {
- val intent: Intent = mock()
- lateinit var dialog: Dialog
-
- runOnMainThreadAndWaitForIdleSync {
- kosmos.applicationCoroutineScope.launch { dialog = underTest.showDialog() }
- runCurrent()
- underTest.launchFromDialog(intent)
- }
-
- verify(mockDialogTransitionAnimator)
- .createActivityTransitionController(any<Dialog>(), eq(null))
- verify(activityStarter).startActivity(eq(intent), eq(true), eq(mockAnimationController))
-
- runOnMainThreadAndWaitForIdleSync { dialog.dismiss() }
- }
-
- @Test
- fun dismiss_clearsDialogReference() {
- val dialog = runOnMainThreadAndWaitForIdleSync { underTest.createDialog() }
-
- assertThat(underTest.currentDialog).isEqualTo(dialog)
-
- runOnMainThreadAndWaitForIdleSync {
- dialog.show()
- dialog.dismiss()
- }
-
- assertThat(underTest.currentDialog).isNull()
- }
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt
index 349b62e..fdfc7f1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt
@@ -18,8 +18,6 @@
package com.android.systemui.statusbar.policy.ui.dialog.viewmodel
-import android.content.Intent
-import android.provider.Settings
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.settingslib.notification.modes.TestModeBuilder
@@ -29,7 +27,6 @@
import com.android.systemui.kosmos.testScope
import com.android.systemui.statusbar.policy.data.repository.fakeZenModeRepository
import com.android.systemui.statusbar.policy.domain.interactor.zenModeInteractor
-import com.android.systemui.statusbar.policy.ui.dialog.mockModesDialogDelegate
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -37,21 +34,16 @@
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.Mockito.clearInvocations
-import org.mockito.kotlin.argumentCaptor
-import org.mockito.kotlin.verify
@SmallTest
@RunWith(AndroidJUnit4::class)
class ModesDialogViewModelTest : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
- private val repository = kosmos.fakeZenModeRepository
- private val interactor = kosmos.zenModeInteractor
- private val mockDialogDelegate = kosmos.mockModesDialogDelegate
+ val repository = kosmos.fakeZenModeRepository
+ val interactor = kosmos.zenModeInteractor
- private val underTest =
- ModesDialogViewModel(context, interactor, kosmos.testDispatcher, mockDialogDelegate)
+ val underTest = ModesDialogViewModel(context, interactor, kosmos.testDispatcher)
@Test
fun tiles_filtersOutDisabledModes() =
@@ -72,8 +64,7 @@
.setEnabled(false)
.setManualInvocationAllowed(true)
.build(),
- )
- )
+ ))
runCurrent()
assertThat(tiles?.size).isEqualTo(2)
@@ -117,8 +108,7 @@
.setActive(false)
.setManualInvocationAllowed(false)
.build(),
- )
- )
+ ))
runCurrent()
assertThat(tiles?.size).isEqualTo(3)
@@ -171,56 +161,4 @@
assertThat(tiles?.first()?.enabled).isFalse()
}
-
- @Test
- fun onLongClick_launchesIntent() =
- testScope.runTest {
- val tiles by collectLastValue(underTest.tiles)
- val intentCaptor = argumentCaptor<Intent>()
-
- val modeId = "id"
- repository.addModes(
- listOf(
- TestModeBuilder()
- .setId(modeId)
- .setId("A")
- .setActive(true)
- .setManualInvocationAllowed(true)
- .build(),
- TestModeBuilder()
- .setId(modeId)
- .setId("B")
- .setActive(false)
- .setManualInvocationAllowed(true)
- .build(),
- )
- )
- runCurrent()
-
- assertThat(tiles?.size).isEqualTo(2)
-
- // Trigger onLongClick for A
- tiles?.first()?.onLongClick?.let { it() }
- runCurrent()
-
- // Check that it launched the correct intent
- verify(mockDialogDelegate).launchFromDialog(intentCaptor.capture())
- var intent = intentCaptor.lastValue
- assertThat(intent.action).isEqualTo(Settings.ACTION_AUTOMATIC_ZEN_RULE_SETTINGS)
- assertThat(intent.extras?.getString(Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID))
- .isEqualTo("A")
-
- clearInvocations(mockDialogDelegate)
-
- // Trigger onLongClick for B
- tiles?.last()?.onLongClick?.let { it() }
- runCurrent()
-
- // Check that it launched the correct intent
- verify(mockDialogDelegate).launchFromDialog(intentCaptor.capture())
- intent = intentCaptor.lastValue
- assertThat(intent.action).isEqualTo(Settings.ACTION_AUTOMATIC_ZEN_RULE_SETTINGS)
- assertThat(intent.extras?.getString(Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID))
- .isEqualTo("B")
- }
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
index 0f11717..1342dd0 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
@@ -287,7 +287,7 @@
/**
* Helper used to receive device state info from {@link DeviceStateManager}.
*/
- static class DeviceStateHelper implements DeviceStateManager.DeviceStateCallback {
+ public static class DeviceStateHelper implements DeviceStateManager.DeviceStateCallback {
@Nullable
private final DisplayAddress.Physical mRearDisplayPhysicalAddress;
diff --git a/packages/SystemUI/src/com/android/systemui/activatable/Activatable.kt b/packages/SystemUI/src/com/android/systemui/activatable/Activatable.kt
new file mode 100644
index 0000000..dc2d931
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/activatable/Activatable.kt
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.activatable
+
+/** Defines interface for classes that can be activated to do coroutine work. */
+interface Activatable {
+
+ /**
+ * Activates this object.
+ *
+ * Serves as an entrypoint to kick off coroutine work that the object requires in order to keep
+ * its state fresh and/or perform side-effects.
+ *
+ * The method suspends and doesn't return until all work required by the object is finished. In
+ * most cases, it's expected for the work to remain ongoing forever so this method will forever
+ * suspend its caller until the coroutine that called it is canceled.
+ *
+ * Implementations could follow this pattern:
+ * ```kotlin
+ * override suspend fun activate() {
+ * coroutineScope {
+ * launch { ... }
+ * launch { ... }
+ * launch { ... }
+ * }
+ * }
+ * ```
+ *
+ * **Must be invoked** by the owner of the object when the object is to become active.
+ * Similarly, the work must be canceled by the owner when the objects is to be deactivated.
+ *
+ * One way to have a parent call this would be by using a `LaunchedEffect` in Compose:
+ * ```kotlin
+ * @Composable
+ * fun MyUi(activatable: Activatable) {
+ * LaunchedEffect(activatable) {
+ * activatable.activate()
+ * }
+ * }
+ * ```
+ */
+ suspend fun activate()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.java b/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.java
deleted file mode 100644
index 636bc5b..0000000
--- a/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.java
+++ /dev/null
@@ -1,395 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.ambient.touch;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ValueAnimator;
-import android.graphics.Rect;
-import android.graphics.Region;
-import android.util.Log;
-import android.view.GestureDetector;
-import android.view.InputEvent;
-import android.view.MotionEvent;
-import android.view.VelocityTracker;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.VisibleForTesting;
-
-import com.android.internal.logging.UiEvent;
-import com.android.internal.logging.UiEventLogger;
-import com.android.internal.widget.LockPatternUtils;
-import com.android.systemui.Flags;
-import com.android.systemui.ambient.touch.dagger.BouncerSwipeModule;
-import com.android.systemui.ambient.touch.scrim.ScrimController;
-import com.android.systemui.ambient.touch.scrim.ScrimManager;
-import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants;
-import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.settings.UserTracker;
-import com.android.systemui.shade.ShadeExpansionChangeEvent;
-import com.android.systemui.statusbar.NotificationShadeWindowController;
-import com.android.systemui.statusbar.phone.CentralSurfaces;
-import com.android.wm.shell.animation.FlingAnimationUtils;
-
-import java.util.Optional;
-
-import javax.inject.Inject;
-import javax.inject.Named;
-
-/**
- * Monitor for tracking touches on the DreamOverlay to bring up the bouncer.
- */
-public class BouncerSwipeTouchHandler implements TouchHandler {
- /**
- * An interface for creating ValueAnimators.
- */
- public interface ValueAnimatorCreator {
- /**
- * Creates {@link ValueAnimator}.
- */
- ValueAnimator create(float start, float finish);
- }
-
- /**
- * An interface for obtaining VelocityTrackers.
- */
- public interface VelocityTrackerFactory {
- /**
- * Obtains {@link VelocityTracker}.
- */
- VelocityTracker obtain();
- }
-
- public static final float FLING_PERCENTAGE_THRESHOLD = 0.5f;
-
- private static final String TAG = "BouncerSwipeTouchHandler";
- private final NotificationShadeWindowController mNotificationShadeWindowController;
- private final LockPatternUtils mLockPatternUtils;
- private final UserTracker mUserTracker;
- private final float mBouncerZoneScreenPercentage;
- private final float mMinBouncerZoneScreenPercentage;
-
- private final ScrimManager mScrimManager;
- private ScrimController mCurrentScrimController;
- private float mCurrentExpansion;
- private final Optional<CentralSurfaces> mCentralSurfaces;
-
- private VelocityTracker mVelocityTracker;
-
- private final FlingAnimationUtils mFlingAnimationUtils;
- private final FlingAnimationUtils mFlingAnimationUtilsClosing;
-
- private Boolean mCapture;
- private Boolean mExpanded;
-
- private TouchSession mTouchSession;
-
- private final ValueAnimatorCreator mValueAnimatorCreator;
-
- private final VelocityTrackerFactory mVelocityTrackerFactory;
-
- private final UiEventLogger mUiEventLogger;
-
- private final ActivityStarter mActivityStarter;
-
- private final ScrimManager.Callback mScrimManagerCallback = new ScrimManager.Callback() {
- @Override
- public void onScrimControllerChanged(ScrimController controller) {
- if (mCurrentScrimController != null) {
- mCurrentScrimController.reset();
- }
-
- mCurrentScrimController = controller;
- }
- };
-
- private final GestureDetector.OnGestureListener mOnGestureListener =
- new GestureDetector.SimpleOnGestureListener() {
- @Override
- public boolean onScroll(MotionEvent e1, @NonNull MotionEvent e2, float distanceX,
- float distanceY) {
- if (mCapture == null) {
- if (Flags.dreamOverlayBouncerSwipeDirectionFiltering()) {
- mCapture = Math.abs(distanceY) > Math.abs(distanceX)
- && distanceY > 0;
- } else {
- // If the user scrolling favors a vertical direction, begin capturing
- // scrolls.
- mCapture = Math.abs(distanceY) > Math.abs(distanceX);
- }
- if (mCapture) {
- // reset expanding
- mExpanded = false;
- // Since the user is dragging the bouncer up, set scrimmed to false.
- mCurrentScrimController.show();
- }
- }
-
- if (!mCapture) {
- return false;
- }
-
- // Don't set expansion for downward scroll.
- if (e1.getY() < e2.getY()) {
- return true;
- }
-
- if (!mCentralSurfaces.isPresent()) {
- return true;
- }
-
- // If scrolling up and keyguard is not locked, dismiss both keyguard and the
- // dream since there's no bouncer to show.
- if (e1.getY() > e2.getY()
- && !mLockPatternUtils.isSecure(mUserTracker.getUserId())) {
- mActivityStarter.executeRunnableDismissingKeyguard(
- () -> mCentralSurfaces.get().awakenDreams(),
- /* cancelAction= */ null,
- /* dismissShade= */ true,
- /* afterKeyguardGone= */ true,
- /* deferred= */ false);
- return true;
- }
-
- // For consistency, we adopt the expansion definition found in the
- // PanelViewController. In this case, expansion refers to the view above the
- // bouncer. As that view's expansion shrinks, the bouncer appears. The bouncer
- // is fully hidden at full expansion (1) and fully visible when fully collapsed
- // (0).
- final float screenTravelPercentage = Math.abs(e1.getY() - e2.getY())
- / mTouchSession.getBounds().height();
- setPanelExpansion(1 - screenTravelPercentage);
- return true;
- }
- };
-
- private void setPanelExpansion(float expansion) {
- mCurrentExpansion = expansion;
- ShadeExpansionChangeEvent event =
- new ShadeExpansionChangeEvent(
- /* fraction= */ mCurrentExpansion,
- /* expanded= */ mExpanded,
- /* tracking= */ true);
- mCurrentScrimController.expand(event);
- }
-
-
- @VisibleForTesting
- public enum DreamEvent implements UiEventLogger.UiEventEnum {
- @UiEvent(doc = "The screensaver has been swiped up.")
- DREAM_SWIPED(988),
-
- @UiEvent(doc = "The bouncer has become fully visible over dream.")
- DREAM_BOUNCER_FULLY_VISIBLE(1056);
-
- private final int mId;
-
- DreamEvent(int id) {
- mId = id;
- }
-
- @Override
- public int getId() {
- return mId;
- }
- }
-
- @Inject
- public BouncerSwipeTouchHandler(
- ScrimManager scrimManager,
- Optional<CentralSurfaces> centralSurfaces,
- NotificationShadeWindowController notificationShadeWindowController,
- ValueAnimatorCreator valueAnimatorCreator,
- VelocityTrackerFactory velocityTrackerFactory,
- LockPatternUtils lockPatternUtils,
- UserTracker userTracker,
- @Named(BouncerSwipeModule.SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_OPENING)
- FlingAnimationUtils flingAnimationUtils,
- @Named(BouncerSwipeModule.SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_CLOSING)
- FlingAnimationUtils flingAnimationUtilsClosing,
- @Named(BouncerSwipeModule.SWIPE_TO_BOUNCER_START_REGION) float swipeRegionPercentage,
- @Named(BouncerSwipeModule.MIN_BOUNCER_ZONE_SCREEN_PERCENTAGE) float minRegionPercentage,
- UiEventLogger uiEventLogger,
- ActivityStarter activityStarter) {
- mCentralSurfaces = centralSurfaces;
- mScrimManager = scrimManager;
- mNotificationShadeWindowController = notificationShadeWindowController;
- mLockPatternUtils = lockPatternUtils;
- mUserTracker = userTracker;
- mBouncerZoneScreenPercentage = swipeRegionPercentage;
- mMinBouncerZoneScreenPercentage = minRegionPercentage;
- mFlingAnimationUtils = flingAnimationUtils;
- mFlingAnimationUtilsClosing = flingAnimationUtilsClosing;
- mValueAnimatorCreator = valueAnimatorCreator;
- mVelocityTrackerFactory = velocityTrackerFactory;
- mUiEventLogger = uiEventLogger;
- mActivityStarter = activityStarter;
- }
-
- @Override
- public void getTouchInitiationRegion(Rect bounds, Region region, Rect exclusionRect) {
- final int width = bounds.width();
- final int height = bounds.height();
- final int minAllowableBottom = Math.round(height * (1 - mMinBouncerZoneScreenPercentage));
-
- final Rect normalRegion = new Rect(0,
- Math.round(height * (1 - mBouncerZoneScreenPercentage)),
- width, height);
-
- if (exclusionRect != null) {
- int lowestBottom = Math.min(Math.max(0, exclusionRect.bottom), minAllowableBottom);
- normalRegion.top = Math.max(normalRegion.top, lowestBottom);
- }
- region.union(normalRegion);
- }
-
-
- @Override
- public void onSessionStart(TouchSession session) {
- mVelocityTracker = mVelocityTrackerFactory.obtain();
- mTouchSession = session;
- mVelocityTracker.clear();
-
- if (!Flags.communalBouncerDoNotModifyPluginOpen()) {
- mNotificationShadeWindowController.setForcePluginOpen(true, this);
- }
-
- mScrimManager.addCallback(mScrimManagerCallback);
- mCurrentScrimController = mScrimManager.getCurrentController();
-
- session.registerCallback(() -> {
- if (mVelocityTracker != null) {
- mVelocityTracker.recycle();
- mVelocityTracker = null;
- }
- mScrimManager.removeCallback(mScrimManagerCallback);
- mCapture = null;
- mTouchSession = null;
-
- if (!Flags.communalBouncerDoNotModifyPluginOpen()) {
- mNotificationShadeWindowController.setForcePluginOpen(false, this);
- }
- });
-
- session.registerGestureListener(mOnGestureListener);
- session.registerInputListener(ev -> onMotionEvent(ev));
-
- }
-
- private void onMotionEvent(InputEvent event) {
- if (!(event instanceof MotionEvent)) {
- Log.e(TAG, "non MotionEvent received:" + event);
- return;
- }
-
- final MotionEvent motionEvent = (MotionEvent) event;
-
- switch (motionEvent.getAction()) {
- case MotionEvent.ACTION_CANCEL:
- case MotionEvent.ACTION_UP:
- mTouchSession.pop();
- // If we are not capturing any input, there is no need to consider animating to
- // finish transition.
- if (mCapture == null || !mCapture) {
- break;
- }
-
- // We must capture the resulting velocities as resetMonitor() will clear these
- // values.
- mVelocityTracker.computeCurrentVelocity(1000);
- final float verticalVelocity = mVelocityTracker.getYVelocity();
- final float horizontalVelocity = mVelocityTracker.getXVelocity();
-
- final float velocityVector =
- (float) Math.hypot(horizontalVelocity, verticalVelocity);
-
- mExpanded = !flingRevealsOverlay(verticalVelocity, velocityVector);
- final float expansion = mExpanded
- ? KeyguardBouncerConstants.EXPANSION_VISIBLE
- : KeyguardBouncerConstants.EXPANSION_HIDDEN;
-
- // Log the swiping up to show Bouncer event.
- if (expansion == KeyguardBouncerConstants.EXPANSION_VISIBLE) {
- mUiEventLogger.log(DreamEvent.DREAM_SWIPED);
- }
-
- flingToExpansion(verticalVelocity, expansion);
- break;
- default:
- mVelocityTracker.addMovement(motionEvent);
- break;
- }
- }
-
- private ValueAnimator createExpansionAnimator(float targetExpansion) {
- final ValueAnimator animator =
- mValueAnimatorCreator.create(mCurrentExpansion, targetExpansion);
- animator.addUpdateListener(
- animation -> {
- float expansionFraction = (float) animation.getAnimatedValue();
- setPanelExpansion(expansionFraction);
- });
- if (targetExpansion == KeyguardBouncerConstants.EXPANSION_VISIBLE) {
- animator.addListener(
- new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- mUiEventLogger.log(DreamEvent.DREAM_BOUNCER_FULLY_VISIBLE);
- }
- });
- }
- return animator;
- }
-
- protected boolean flingRevealsOverlay(float velocity, float velocityVector) {
- // Fully expand the space above the bouncer, if the user has expanded the bouncer less
- // than halfway or final velocity was positive, indicating a downward direction.
- if (Math.abs(velocityVector) < mFlingAnimationUtils.getMinVelocityPxPerSecond()) {
- return mCurrentExpansion > FLING_PERCENTAGE_THRESHOLD;
- } else {
- return velocity > 0;
- }
- }
-
- protected void flingToExpansion(float velocity, float expansion) {
- if (!mCentralSurfaces.isPresent()) {
- return;
- }
-
- // Don't set expansion if the user doesn't have a pin/password set.
- if (!mLockPatternUtils.isSecure(mUserTracker.getUserId())) {
- return;
- }
-
- // The animation utils deal in pixel units, rather than expansion height.
- final float viewHeight = mTouchSession.getBounds().height();
- final float currentHeight = viewHeight * mCurrentExpansion;
- final float targetHeight = viewHeight * expansion;
- final ValueAnimator animator = createExpansionAnimator(expansion);
- if (expansion == KeyguardBouncerConstants.EXPANSION_HIDDEN) {
- // Hides the bouncer, i.e., fully expands the space above the bouncer.
- mFlingAnimationUtilsClosing.apply(animator, currentHeight, targetHeight, velocity,
- viewHeight);
- } else {
- // Shows the bouncer, i.e., fully collapses the space above the bouncer.
- mFlingAnimationUtils.apply(
- animator, currentHeight, targetHeight, velocity, viewHeight);
- }
-
- animator.start();
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.kt b/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.kt
new file mode 100644
index 0000000..d5790a4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.kt
@@ -0,0 +1,371 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.ambient.touch
+
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.ValueAnimator
+import android.graphics.Rect
+import android.graphics.Region
+import android.util.Log
+import android.view.GestureDetector
+import android.view.GestureDetector.SimpleOnGestureListener
+import android.view.InputEvent
+import android.view.MotionEvent
+import android.view.VelocityTracker
+import androidx.annotation.VisibleForTesting
+import com.android.internal.logging.UiEvent
+import com.android.internal.logging.UiEventLogger
+import com.android.internal.widget.LockPatternUtils
+import com.android.systemui.Flags
+import com.android.systemui.ambient.touch.TouchHandler.TouchSession
+import com.android.systemui.ambient.touch.dagger.BouncerSwipeModule
+import com.android.systemui.ambient.touch.scrim.ScrimController
+import com.android.systemui.ambient.touch.scrim.ScrimManager
+import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants
+import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.shade.ShadeExpansionChangeEvent
+import com.android.systemui.statusbar.NotificationShadeWindowController
+import com.android.systemui.statusbar.phone.CentralSurfaces
+import com.android.wm.shell.animation.FlingAnimationUtils
+import java.util.Optional
+import javax.inject.Inject
+import javax.inject.Named
+import kotlin.math.abs
+import kotlin.math.hypot
+import kotlin.math.max
+import kotlin.math.min
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+
+/** Monitor for tracking touches on the DreamOverlay to bring up the bouncer. */
+class BouncerSwipeTouchHandler
+@Inject
+constructor(
+ scope: CoroutineScope,
+ private val scrimManager: ScrimManager,
+ private val centralSurfaces: Optional<CentralSurfaces>,
+ private val notificationShadeWindowController: NotificationShadeWindowController,
+ private val valueAnimatorCreator: ValueAnimatorCreator,
+ private val velocityTrackerFactory: VelocityTrackerFactory,
+ private val lockPatternUtils: LockPatternUtils,
+ private val userTracker: UserTracker,
+ private val communalViewModel: CommunalViewModel,
+ @param:Named(BouncerSwipeModule.SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_OPENING)
+ private val flingAnimationUtils: FlingAnimationUtils,
+ @param:Named(BouncerSwipeModule.SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_CLOSING)
+ private val flingAnimationUtilsClosing: FlingAnimationUtils,
+ @param:Named(BouncerSwipeModule.SWIPE_TO_BOUNCER_START_REGION)
+ private val bouncerZoneScreenPercentage: Float,
+ @param:Named(BouncerSwipeModule.MIN_BOUNCER_ZONE_SCREEN_PERCENTAGE)
+ private val minBouncerZoneScreenPercentage: Float,
+ private val uiEventLogger: UiEventLogger,
+ private val activityStarter: ActivityStarter
+) : TouchHandler {
+ /** An interface for creating ValueAnimators. */
+ interface ValueAnimatorCreator {
+ /** Creates [ValueAnimator]. */
+ fun create(start: Float, finish: Float): ValueAnimator
+ }
+
+ /** An interface for obtaining VelocityTrackers. */
+ interface VelocityTrackerFactory {
+ /** Obtains [VelocityTracker]. */
+ fun obtain(): VelocityTracker?
+ }
+
+ private var currentScrimController: ScrimController? = null
+ private var currentExpansion = 0f
+ private var velocityTracker: VelocityTracker? = null
+ private var capture: Boolean? = null
+ private var expanded: Boolean = false
+ private var touchSession: TouchSession? = null
+ private val scrimManagerCallback =
+ ScrimManager.Callback { controller ->
+ currentScrimController?.reset()
+
+ currentScrimController = controller
+ }
+
+ /** Determines whether the touch handler should process touches in fullscreen swiping mode */
+ private var touchAvailable = false
+
+ private val onGestureListener: GestureDetector.OnGestureListener =
+ object : SimpleOnGestureListener() {
+ override fun onScroll(
+ e1: MotionEvent?,
+ e2: MotionEvent,
+ distanceX: Float,
+ distanceY: Float
+ ): Boolean {
+ if (capture == null) {
+ capture =
+ if (Flags.dreamOverlayBouncerSwipeDirectionFiltering()) {
+ (abs(distanceY.toDouble()) > abs(distanceX.toDouble()) &&
+ distanceY > 0) &&
+ if (Flags.hubmodeFullscreenVerticalSwipe()) touchAvailable else true
+ } else {
+ // If the user scrolling favors a vertical direction, begin capturing
+ // scrolls.
+ abs(distanceY.toDouble()) > abs(distanceX.toDouble())
+ }
+ if (capture == true) {
+ // reset expanding
+ expanded = false
+ // Since the user is dragging the bouncer up, set scrimmed to false.
+ currentScrimController?.show()
+ }
+ }
+ if (capture != true) {
+ return false
+ }
+
+ if (!centralSurfaces.isPresent) {
+ return true
+ }
+
+ e1?.apply outer@{
+ // Don't set expansion for downward scroll.
+ if (y < e2.y) {
+ return true
+ }
+
+ // If scrolling up and keyguard is not locked, dismiss both keyguard and the
+ // dream since there's no bouncer to show.
+ if (y > e2.y && !lockPatternUtils.isSecure(userTracker.userId)) {
+ activityStarter.executeRunnableDismissingKeyguard(
+ { centralSurfaces.get().awakenDreams() },
+ /* cancelAction= */ null,
+ /* dismissShade= */ true,
+ /* afterKeyguardGone= */ true,
+ /* deferred= */ false
+ )
+ return true
+ }
+
+ // For consistency, we adopt the expansion definition found in the
+ // PanelViewController. In this case, expansion refers to the view above the
+ // bouncer. As that view's expansion shrinks, the bouncer appears. The bouncer
+ // is fully hidden at full expansion (1) and fully visible when fully collapsed
+ // (0).
+ touchSession?.apply {
+ val screenTravelPercentage =
+ (abs((this@outer.y - e2.y).toDouble()) / getBounds().height()).toFloat()
+ setPanelExpansion(1 - screenTravelPercentage)
+ }
+ }
+
+ return true
+ }
+ }
+
+ init {
+ if (Flags.hubmodeFullscreenVerticalSwipe()) {
+ scope.launch {
+ communalViewModel.glanceableTouchAvailable.collect {
+ onGlanceableTouchAvailable(it)
+ }
+ }
+ }
+ }
+
+ @VisibleForTesting
+ fun onGlanceableTouchAvailable(available: Boolean) {
+ touchAvailable = available
+ }
+
+ private fun setPanelExpansion(expansion: Float) {
+ currentExpansion = expansion
+ val event =
+ ShadeExpansionChangeEvent(
+ /* fraction= */ currentExpansion,
+ /* expanded= */ expanded,
+ /* tracking= */ true
+ )
+ currentScrimController?.expand(event)
+ }
+
+ @VisibleForTesting
+ enum class DreamEvent(private val mId: Int) : UiEventLogger.UiEventEnum {
+ @UiEvent(doc = "The screensaver has been swiped up.") DREAM_SWIPED(988),
+ @UiEvent(doc = "The bouncer has become fully visible over dream.")
+ DREAM_BOUNCER_FULLY_VISIBLE(1056);
+
+ override fun getId(): Int {
+ return mId
+ }
+ }
+
+ override fun getTouchInitiationRegion(bounds: Rect, region: Region, exclusionRect: Rect?) {
+ val width = bounds.width()
+ val height = bounds.height()
+ val minAllowableBottom = Math.round(height * (1 - minBouncerZoneScreenPercentage))
+ val normalRegion =
+ Rect(0, Math.round(height * (1 - bouncerZoneScreenPercentage)), width, height)
+
+ if (Flags.hubmodeFullscreenVerticalSwipe()) {
+ region.op(bounds, Region.Op.UNION)
+ exclusionRect?.apply { region.op(this, Region.Op.DIFFERENCE) }
+ }
+
+ if (exclusionRect != null) {
+ val lowestBottom =
+ min(max(0.0, exclusionRect.bottom.toDouble()), minAllowableBottom.toDouble())
+ .toInt()
+ normalRegion.top = max(normalRegion.top.toDouble(), lowestBottom.toDouble()).toInt()
+ }
+ region.union(normalRegion)
+ }
+
+ override fun onSessionStart(session: TouchSession) {
+ velocityTracker = velocityTrackerFactory.obtain()
+ touchSession = session
+ velocityTracker?.apply { clear() }
+ if (!Flags.communalBouncerDoNotModifyPluginOpen()) {
+ notificationShadeWindowController.setForcePluginOpen(true, this)
+ }
+ scrimManager.addCallback(scrimManagerCallback)
+ currentScrimController = scrimManager.currentController
+ session.registerCallback {
+ velocityTracker?.apply { recycle() }
+ velocityTracker = null
+
+ scrimManager.removeCallback(scrimManagerCallback)
+ capture = null
+ touchSession = null
+ if (!Flags.communalBouncerDoNotModifyPluginOpen()) {
+ notificationShadeWindowController.setForcePluginOpen(false, this)
+ }
+ }
+ session.registerGestureListener(onGestureListener)
+ session.registerInputListener { ev: InputEvent -> onMotionEvent(ev) }
+ }
+
+ private fun onMotionEvent(event: InputEvent) {
+ if (event !is MotionEvent) {
+ Log.e(TAG, "non MotionEvent received:$event")
+ return
+ }
+ val motionEvent = event
+ when (motionEvent.action) {
+ MotionEvent.ACTION_CANCEL,
+ MotionEvent.ACTION_UP -> {
+ if (Flags.hubmodeFullscreenVerticalSwipe() && capture == true) {
+ communalViewModel.onResetTouchState()
+ }
+ touchSession?.apply { pop() }
+ // If we are not capturing any input, there is no need to consider animating to
+ // finish transition.
+ if (capture == null || !capture!!) {
+ return
+ }
+
+ // We must capture the resulting velocities as resetMonitor() will clear these
+ // values.
+ velocityTracker!!.computeCurrentVelocity(1000)
+ val verticalVelocity = velocityTracker!!.yVelocity
+ val horizontalVelocity = velocityTracker!!.xVelocity
+ val velocityVector =
+ hypot(horizontalVelocity.toDouble(), verticalVelocity.toDouble()).toFloat()
+ expanded = !flingRevealsOverlay(verticalVelocity, velocityVector)
+ val expansion =
+ if (expanded!!) KeyguardBouncerConstants.EXPANSION_VISIBLE
+ else KeyguardBouncerConstants.EXPANSION_HIDDEN
+
+ // Log the swiping up to show Bouncer event.
+ if (expansion == KeyguardBouncerConstants.EXPANSION_VISIBLE) {
+ uiEventLogger.log(DreamEvent.DREAM_SWIPED)
+ }
+ flingToExpansion(verticalVelocity, expansion)
+ }
+ else -> velocityTracker!!.addMovement(motionEvent)
+ }
+ }
+
+ private fun createExpansionAnimator(targetExpansion: Float): ValueAnimator {
+ val animator = valueAnimatorCreator.create(currentExpansion, targetExpansion)
+ animator.addUpdateListener { animation: ValueAnimator ->
+ val expansionFraction = animation.animatedValue as Float
+ setPanelExpansion(expansionFraction)
+ }
+ if (targetExpansion == KeyguardBouncerConstants.EXPANSION_VISIBLE) {
+ animator.addListener(
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ uiEventLogger.log(DreamEvent.DREAM_BOUNCER_FULLY_VISIBLE)
+ }
+ }
+ )
+ }
+ return animator
+ }
+
+ protected fun flingRevealsOverlay(velocity: Float, velocityVector: Float): Boolean {
+ // Fully expand the space above the bouncer, if the user has expanded the bouncer less
+ // than halfway or final velocity was positive, indicating a downward direction.
+ return if (abs(velocityVector.toDouble()) < flingAnimationUtils.minVelocityPxPerSecond) {
+ currentExpansion > FLING_PERCENTAGE_THRESHOLD
+ } else {
+ velocity > 0
+ }
+ }
+
+ protected fun flingToExpansion(velocity: Float, expansion: Float) {
+ if (!centralSurfaces.isPresent) {
+ return
+ }
+
+ // Don't set expansion if the user doesn't have a pin/password set.
+ if (!lockPatternUtils.isSecure(userTracker.userId)) {
+ return
+ }
+
+ touchSession?.apply {
+ // The animation utils deal in pixel units, rather than expansion height.
+ val viewHeight = getBounds().height().toFloat()
+ val currentHeight = viewHeight * currentExpansion
+ val targetHeight = viewHeight * expansion
+ val animator = createExpansionAnimator(expansion)
+ if (expansion == KeyguardBouncerConstants.EXPANSION_HIDDEN) {
+ // Hides the bouncer, i.e., fully expands the space above the bouncer.
+ flingAnimationUtilsClosing.apply(
+ animator,
+ currentHeight,
+ targetHeight,
+ velocity,
+ viewHeight
+ )
+ } else {
+ // Shows the bouncer, i.e., fully collapses the space above the bouncer.
+ flingAnimationUtils.apply(
+ animator,
+ currentHeight,
+ targetHeight,
+ velocity,
+ viewHeight
+ )
+ }
+ animator.start()
+ }
+ }
+
+ companion object {
+ const val FLING_PERCENTAGE_THRESHOLD = 0.5f
+ private const val TAG = "BouncerSwipeTouchHandler"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/ambient/touch/ShadeTouchHandler.java b/packages/SystemUI/src/com/android/systemui/ambient/touch/ShadeTouchHandler.java
deleted file mode 100644
index baca959..0000000
--- a/packages/SystemUI/src/com/android/systemui/ambient/touch/ShadeTouchHandler.java
+++ /dev/null
@@ -1,132 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.ambient.touch;
-
-import static com.android.systemui.ambient.touch.dagger.ShadeModule.NOTIFICATION_SHADE_GESTURE_INITIATION_HEIGHT;
-
-import android.app.DreamManager;
-import android.graphics.Rect;
-import android.graphics.Region;
-import android.view.GestureDetector;
-import android.view.MotionEvent;
-
-import androidx.annotation.NonNull;
-
-import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor;
-import com.android.systemui.shade.ShadeViewController;
-import com.android.systemui.statusbar.phone.CentralSurfaces;
-
-import java.util.Optional;
-
-import javax.inject.Inject;
-import javax.inject.Named;
-
-/**
- * {@link ShadeTouchHandler} is responsible for handling swipe down gestures over dream
- * to bring down the shade.
- */
-public class ShadeTouchHandler implements TouchHandler {
- private final Optional<CentralSurfaces> mSurfaces;
- private final ShadeViewController mShadeViewController;
- private final DreamManager mDreamManager;
- private final int mInitiationHeight;
- private final CommunalSettingsInteractor
- mCommunalSettingsInteractor;
-
- /**
- * Tracks whether or not we are capturing a given touch. Will be null before and after a touch.
- */
- private Boolean mCapture;
-
- @Inject
- ShadeTouchHandler(Optional<CentralSurfaces> centralSurfaces,
- ShadeViewController shadeViewController,
- DreamManager dreamManager,
- CommunalSettingsInteractor communalSettingsInteractor,
- @Named(NOTIFICATION_SHADE_GESTURE_INITIATION_HEIGHT) int initiationHeight) {
- mSurfaces = centralSurfaces;
- mShadeViewController = shadeViewController;
- mDreamManager = dreamManager;
- mCommunalSettingsInteractor = communalSettingsInteractor;
- mInitiationHeight = initiationHeight;
- }
-
- @Override
- public void onSessionStart(TouchSession session) {
- if (mSurfaces.isEmpty()) {
- session.pop();
- return;
- }
-
- session.registerCallback(() -> mCapture = null);
-
- session.registerInputListener(ev -> {
- if (ev instanceof MotionEvent) {
- if (mCapture != null && mCapture) {
- sendTouchEvent((MotionEvent) ev);
- }
- if (((MotionEvent) ev).getAction() == MotionEvent.ACTION_UP
- || ((MotionEvent) ev).getAction() == MotionEvent.ACTION_CANCEL) {
- session.pop();
- }
- }
- });
-
- session.registerGestureListener(new GestureDetector.SimpleOnGestureListener() {
- @Override
- public boolean onScroll(MotionEvent e1, @NonNull MotionEvent e2, float distanceX,
- float distanceY) {
- if (mCapture == null) {
- // Only capture swipes that are going downwards.
- mCapture = Math.abs(distanceY) > Math.abs(distanceX) && distanceY < 0;
- if (mCapture) {
- // Send the initial touches over, as the input listener has already
- // processed these touches.
- sendTouchEvent(e1);
- sendTouchEvent(e2);
- }
- }
- return mCapture;
- }
-
- @Override
- public boolean onFling(MotionEvent e1, @NonNull MotionEvent e2, float velocityX,
- float velocityY) {
- return mCapture;
- }
- });
- }
-
- private void sendTouchEvent(MotionEvent event) {
- if (mCommunalSettingsInteractor.isCommunalFlagEnabled() && !mDreamManager.isDreaming()) {
- // Send touches to central surfaces only when on the glanceable hub while not dreaming.
- // While sending touches where while dreaming will open the shade, the shade
- // while closing if opened then closed in the same gesture.
- mSurfaces.get().handleExternalShadeWindowTouch(event);
- } else {
- // Send touches to the shade view when dreaming.
- mShadeViewController.handleExternalTouch(event);
- }
- }
-
- @Override
- public void getTouchInitiationRegion(Rect bounds, Region region, Rect exclusionRect) {
- final Rect outBounds = new Rect(bounds);
- outBounds.inset(0, 0, 0, outBounds.height() - mInitiationHeight);
- region.op(outBounds, Region.Op.UNION);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/ambient/touch/ShadeTouchHandler.kt b/packages/SystemUI/src/com/android/systemui/ambient/touch/ShadeTouchHandler.kt
new file mode 100644
index 0000000..06b41de
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/ShadeTouchHandler.kt
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.ambient.touch
+
+import android.app.DreamManager
+import android.graphics.Rect
+import android.graphics.Region
+import android.view.GestureDetector.SimpleOnGestureListener
+import android.view.InputEvent
+import android.view.MotionEvent
+import androidx.annotation.VisibleForTesting
+import com.android.systemui.Flags
+import com.android.systemui.ambient.touch.TouchHandler.TouchSession
+import com.android.systemui.ambient.touch.dagger.ShadeModule
+import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
+import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
+import com.android.systemui.shade.ShadeViewController
+import com.android.systemui.statusbar.phone.CentralSurfaces
+import java.util.Optional
+import javax.inject.Inject
+import javax.inject.Named
+import kotlin.math.abs
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+
+/**
+ * [ShadeTouchHandler] is responsible for handling swipe down gestures over dream to bring down the
+ * shade.
+ */
+class ShadeTouchHandler
+@Inject
+constructor(
+ scope: CoroutineScope,
+ private val surfaces: Optional<CentralSurfaces>,
+ private val shadeViewController: ShadeViewController,
+ private val dreamManager: DreamManager,
+ private val communalViewModel: CommunalViewModel,
+ private val communalSettingsInteractor: CommunalSettingsInteractor,
+ @param:Named(ShadeModule.NOTIFICATION_SHADE_GESTURE_INITIATION_HEIGHT)
+ private val initiationHeight: Int
+) : TouchHandler {
+ /**
+ * Tracks whether or not we are capturing a given touch. Will be null before and after a touch.
+ */
+ private var capture: Boolean? = null
+
+ /** Determines whether the touch handler should process touches in fullscreen swiping mode */
+ private var touchAvailable = false
+
+ init {
+ if (Flags.hubmodeFullscreenVerticalSwipe()) {
+ scope.launch {
+ communalViewModel.glanceableTouchAvailable.collect {
+ onGlanceableTouchAvailable(it)
+ }
+ }
+ }
+ }
+
+ @VisibleForTesting
+ fun onGlanceableTouchAvailable(available: Boolean) {
+ touchAvailable = available
+ }
+
+ override fun onSessionStart(session: TouchSession) {
+ if (surfaces.isEmpty) {
+ session.pop()
+ return
+ }
+ session.registerCallback { capture = null }
+ session.registerInputListener { ev: InputEvent? ->
+ if (ev is MotionEvent) {
+ if (capture == true) {
+ sendTouchEvent(ev)
+ }
+ if (ev.action == MotionEvent.ACTION_UP || ev.action == MotionEvent.ACTION_CANCEL) {
+ if (capture == true) {
+ communalViewModel.onResetTouchState()
+ }
+ session.pop()
+ }
+ }
+ }
+ session.registerGestureListener(
+ object : SimpleOnGestureListener() {
+ override fun onScroll(
+ e1: MotionEvent?,
+ e2: MotionEvent,
+ distanceX: Float,
+ distanceY: Float
+ ): Boolean {
+ if (capture == null) {
+ // Only capture swipes that are going downwards.
+ capture =
+ abs(distanceY.toDouble()) > abs(distanceX.toDouble()) &&
+ distanceY < 0 &&
+ if (Flags.hubmodeFullscreenVerticalSwipe()) touchAvailable else true
+ if (capture == true) {
+ // Send the initial touches over, as the input listener has already
+ // processed these touches.
+ e1?.apply { sendTouchEvent(this) }
+ sendTouchEvent(e2)
+ }
+ }
+ return capture == true
+ }
+
+ override fun onFling(
+ e1: MotionEvent?,
+ e2: MotionEvent,
+ velocityX: Float,
+ velocityY: Float
+ ): Boolean {
+ return capture == true
+ }
+ }
+ )
+ }
+
+ private fun sendTouchEvent(event: MotionEvent) {
+ if (communalSettingsInteractor.isCommunalFlagEnabled() && !dreamManager.isDreaming) {
+ // Send touches to central surfaces only when on the glanceable hub while not dreaming.
+ // While sending touches where while dreaming will open the shade, the shade
+ // while closing if opened then closed in the same gesture.
+ surfaces.get().handleExternalShadeWindowTouch(event)
+ } else {
+ // Send touches to the shade view when dreaming.
+ shadeViewController.handleExternalTouch(event)
+ }
+ }
+
+ override fun getTouchInitiationRegion(bounds: Rect, region: Region, exclusionRect: Rect?) {
+ // If fullscreen swipe, use entire space minus exclusion region
+ if (Flags.hubmodeFullscreenVerticalSwipe()) {
+ region.op(bounds, Region.Op.UNION)
+
+ exclusionRect?.apply { region.op(this, Region.Op.DIFFERENCE) }
+ }
+
+ val outBounds = Rect(bounds)
+ outBounds.inset(0, 0, 0, outBounds.height() - initiationHeight)
+ region.op(outBounds, Region.Op.UNION)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/ambient/touch/dagger/AmbientTouchModule.kt b/packages/SystemUI/src/com/android/systemui/ambient/touch/dagger/AmbientTouchModule.kt
index a4924d1..ae21e56 100644
--- a/packages/SystemUI/src/com/android/systemui/ambient/touch/dagger/AmbientTouchModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/dagger/AmbientTouchModule.kt
@@ -17,12 +17,14 @@
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.coroutineScope
import com.android.systemui.ambient.dagger.AmbientModule
import com.android.systemui.ambient.touch.TouchHandler
import dagger.Module
import dagger.Provides
import dagger.multibindings.ElementsIntoSet
import javax.inject.Named
+import kotlinx.coroutines.CoroutineScope
@Module
interface AmbientTouchModule {
@@ -33,6 +35,12 @@
return lifecycleOwner.lifecycle
}
+ @JvmStatic
+ @Provides
+ fun providesLifecycleScope(lifecycle: Lifecycle): CoroutineScope {
+ return lifecycle.coroutineScope
+ }
+
@Provides
@ElementsIntoSet
fun providesDreamTouchHandlers(
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
index 40a141d..e2089bb 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
@@ -24,7 +24,6 @@
import androidx.compose.ui.input.key.type
import androidx.core.graphics.drawable.toBitmap
import com.android.compose.animation.scene.Back
-import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.Swipe
import com.android.compose.animation.scene.SwipeDirection
import com.android.compose.animation.scene.UserAction
@@ -43,7 +42,6 @@
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.inputmethod.domain.interactor.InputMethodInteractor
-import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.user.ui.viewmodel.UserActionViewModel
import com.android.systemui.user.ui.viewmodel.UserSwitcherViewModel
@@ -67,7 +65,9 @@
/** Holds UI state and handles user input on bouncer UIs. */
class BouncerViewModel(
@Application private val applicationContext: Context,
- @Application private val applicationScope: CoroutineScope,
+ @Deprecated("TODO(b/354270224): remove this. Injecting CoroutineScope to view-models is banned")
+ @Application
+ private val applicationScope: CoroutineScope,
@Main private val mainDispatcher: CoroutineDispatcher,
private val bouncerInteractor: BouncerInteractor,
private val inputMethodInteractor: InputMethodInteractor,
@@ -91,14 +91,13 @@
initialValue = null,
)
- val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
- bouncerInteractor.dismissDestination
- .map(::destinationSceneMap)
- .stateIn(
- applicationScope,
- SharingStarted.WhileSubscribed(),
- initialValue = destinationSceneMap(Scenes.Lockscreen),
+ val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
+ bouncerInteractor.dismissDestination.map { prevScene ->
+ mapOf(
+ Back to UserActionResult(prevScene),
+ Swipe(SwipeDirection.Down) to UserActionResult(prevScene),
)
+ }
val message: BouncerMessageViewModel = bouncerMessageViewModel
@@ -328,8 +327,7 @@
{ message },
failedAttempts,
remainingAttempts,
- )
- ?: message
+ ) ?: message
} else {
message
}
@@ -346,8 +344,7 @@
.KEYGUARD_DIALOG_FAILED_ATTEMPTS_ERASING_PROFILE,
{ message },
failedAttempts,
- )
- ?: message
+ ) ?: message
} else {
message
}
@@ -375,12 +372,6 @@
}
}
- private fun destinationSceneMap(prevScene: SceneKey) =
- mapOf(
- Back to UserActionResult(prevScene),
- Swipe(SwipeDirection.Down) to UserActionResult(prevScene),
- )
-
/**
* Notifies that a key event has occurred.
*
@@ -390,8 +381,7 @@
return (authMethodViewModel.value as? PinBouncerViewModel)?.onKeyEvent(
keyEvent.type,
keyEvent.nativeKeyEvent.keyCode
- )
- ?: false
+ ) ?: false
}
data class DialogViewModel(
diff --git a/packages/SystemUI/src/com/android/systemui/communal/log/CommunalLoggerStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/log/CommunalLoggerStartable.kt
index 81feb44..2352841f 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/log/CommunalLoggerStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/log/CommunalLoggerStartable.kt
@@ -19,14 +19,16 @@
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.internal.logging.UiEventLogger
import com.android.systemui.CoreStartable
-import com.android.systemui.communal.domain.interactor.CommunalInteractor
+import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
import com.android.systemui.communal.shared.log.CommunalUiEvent
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.util.kotlin.pairwise
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.drop
import kotlinx.coroutines.flow.filterNotNull
@@ -40,12 +42,13 @@
@Inject
constructor(
@Background private val backgroundScope: CoroutineScope,
- private val communalInteractor: CommunalInteractor,
+ private val communalSceneInteractor: CommunalSceneInteractor,
+ private val keyguardInteractor: KeyguardInteractor,
private val uiEventLogger: UiEventLogger,
) : CoreStartable {
override fun start() {
- communalInteractor.transitionState
+ communalSceneInteractor.transitionState
.map { state ->
when {
state.isOnCommunal() -> CommunalUiEvent.COMMUNAL_HUB_SHOWN
@@ -60,22 +63,46 @@
.onEach { uiEvent -> uiEventLogger.log(uiEvent) }
.launchIn(backgroundScope)
- communalInteractor.transitionState
+ communalSceneInteractor.transitionState
.pairwise()
- .map { (old, new) ->
+ .combine(keyguardInteractor.isDreamingWithOverlay) { (old, new), isDreaming ->
when {
new.isOnCommunal() && old.isSwipingToCommunal() ->
- CommunalUiEvent.COMMUNAL_HUB_SWIPE_TO_ENTER_FINISH
+ if (isDreaming) {
+ CommunalUiEvent.DREAM_TO_COMMUNAL_HUB_SWIPE_FINISH
+ } else {
+ CommunalUiEvent.COMMUNAL_HUB_SWIPE_TO_ENTER_FINISH
+ }
new.isOnCommunal() && old.isSwipingFromCommunal() ->
- CommunalUiEvent.COMMUNAL_HUB_SWIPE_TO_EXIT_CANCEL
+ if (isDreaming) {
+ CommunalUiEvent.COMMUNAL_HUB_TO_DREAM_SWIPE_CANCEL
+ } else {
+ CommunalUiEvent.COMMUNAL_HUB_SWIPE_TO_EXIT_CANCEL
+ }
new.isNotOnCommunal() && old.isSwipingFromCommunal() ->
- CommunalUiEvent.COMMUNAL_HUB_SWIPE_TO_EXIT_FINISH
+ if (isDreaming) {
+ CommunalUiEvent.COMMUNAL_HUB_TO_DREAM_SWIPE_FINISH
+ } else {
+ CommunalUiEvent.COMMUNAL_HUB_SWIPE_TO_EXIT_FINISH
+ }
new.isNotOnCommunal() && old.isSwipingToCommunal() ->
- CommunalUiEvent.COMMUNAL_HUB_SWIPE_TO_ENTER_CANCEL
+ if (isDreaming) {
+ CommunalUiEvent.DREAM_TO_COMMUNAL_HUB_SWIPE_CANCEL
+ } else {
+ CommunalUiEvent.COMMUNAL_HUB_SWIPE_TO_ENTER_CANCEL
+ }
new.isSwipingToCommunal() && old.isNotOnCommunal() ->
- CommunalUiEvent.COMMUNAL_HUB_SWIPE_TO_ENTER_START
+ if (isDreaming) {
+ CommunalUiEvent.DREAM_TO_COMMUNAL_HUB_SWIPE_START
+ } else {
+ CommunalUiEvent.COMMUNAL_HUB_SWIPE_TO_ENTER_START
+ }
new.isSwipingFromCommunal() && old.isOnCommunal() ->
- CommunalUiEvent.COMMUNAL_HUB_SWIPE_TO_EXIT_START
+ if (isDreaming) {
+ CommunalUiEvent.COMMUNAL_HUB_TO_DREAM_SWIPE_START
+ } else {
+ CommunalUiEvent.COMMUNAL_HUB_SWIPE_TO_EXIT_START
+ }
else -> null
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalMetricsLogger.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalMetricsLogger.kt
index 6e345fe..9ce8cf7 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalMetricsLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalMetricsLogger.kt
@@ -56,6 +56,19 @@
)
}
+ /** Logs a tap widget event for metrics. No-op if widget is not loggable. */
+ fun logTapWidget(componentName: String, rank: Int) {
+ if (!componentName.isLoggable()) {
+ return
+ }
+
+ statsLogProxy.writeCommunalHubWidgetEventReported(
+ SysUiStatsLog.COMMUNAL_HUB_WIDGET_EVENT_REPORTED__ACTION__TAP,
+ componentName,
+ rank,
+ )
+ }
+
/** Logs loggable widgets and the total widget count as a [StatsEvent]. */
fun logWidgetsSnapshot(
statsEvents: MutableList<StatsEvent>,
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalUiEvent.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalUiEvent.kt
index 4ab56cc..4711d88 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalUiEvent.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalUiEvent.kt
@@ -54,14 +54,20 @@
COMMUNAL_HUB_SWIPE_UP_TO_BOUNCER(1573),
@UiEvent(doc = "User performs a swipe down gesture from top to enter shade")
COMMUNAL_HUB_SWIPE_DOWN_TO_SHADE(1574),
- @UiEvent(doc = "User performs a tap gesture on the UMO in Communal Hub")
- COMMUNAL_HUB_UMO_TAP(1858),
- @UiEvent(
- doc =
- "A transition from dream to Communal Hub starts. This can be triggered by a tap on " +
- "the dream."
- )
- FROM_DREAM_TO_COMMUNAL_HUB_TRANSITION_START(1859);
+ @UiEvent(doc = "User starts the swipe gesture to enter the Communal Hub from Dream")
+ DREAM_TO_COMMUNAL_HUB_SWIPE_START(1860),
+ @UiEvent(doc = "User finishes the swipe gesture to enter the Communal Hub from Dream")
+ DREAM_TO_COMMUNAL_HUB_SWIPE_FINISH(1861),
+ @UiEvent(doc = "User cancels the swipe gesture to enter the Communal Hub from Dream")
+ DREAM_TO_COMMUNAL_HUB_SWIPE_CANCEL(1862),
+ @UiEvent(doc = "User starts the swipe gesture to exit the Communal Hub to go to Dream")
+ COMMUNAL_HUB_TO_DREAM_SWIPE_START(1863),
+ @UiEvent(doc = "User finishes the swipe gesture to exit the Communal Hub to go to Dream")
+ COMMUNAL_HUB_TO_DREAM_SWIPE_FINISH(1864),
+ @UiEvent(doc = "User cancels the swipe gesture to exit the Communal Hub to go to Dream")
+ COMMUNAL_HUB_TO_DREAM_SWIPE_CANCEL(1865),
+ @UiEvent(doc = "A transition from Dream to Communal Hub starts due to dream awakening")
+ DREAM_TO_COMMUNAL_HUB_DREAM_AWAKE_START(1866);
override fun getId(): Int {
return id
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
index 623e702..4be93cc 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
@@ -29,9 +29,12 @@
import com.android.systemui.communal.widgets.WidgetConfigurator
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.media.controls.ui.view.MediaHost
+import com.android.systemui.util.kotlin.BooleanFlowOperators.anyOf
+import com.android.systemui.util.kotlin.BooleanFlowOperators.not
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.flowOf
/** The base view model for the communal hub. */
@@ -57,6 +60,26 @@
val selectedKey: StateFlow<String?>
get() = _selectedKey
+ private val _isTouchConsumed: MutableStateFlow<Boolean> = MutableStateFlow(false)
+
+ /** Whether an element inside the lazy grid is actively consuming touches */
+ val isTouchConsumed: Flow<Boolean> = _isTouchConsumed.asStateFlow()
+
+ private val _isNestedScrolling: MutableStateFlow<Boolean> = MutableStateFlow(false)
+
+ /** Whether the lazy grid is reporting scrolling within itself */
+ val isNestedScrolling: Flow<Boolean> = _isNestedScrolling.asStateFlow()
+
+ /**
+ * Whether touch is available to be consumed by a touch handler. Touch is available during
+ * nested scrolling as lazy grid reports this for all scroll directions that it detects. In the
+ * case that there is consumed scrolling on a nested element, such as an AndroidView, no nested
+ * scrolling will be reported. It is up to the flow consumer to determine whether the nested
+ * scroll can be applied. In the communal case, this would be identifying the scroll as
+ * vertical, which the lazy horizontal grid does not handle.
+ */
+ val glanceableTouchAvailable: Flow<Boolean> = anyOf(not(isTouchConsumed), isNestedScrolling)
+
/** Accessibility delegate to be set on CommunalAppWidgetHostView. */
open val widgetAccessibilityDelegate: View.AccessibilityDelegate? = null
@@ -139,6 +162,12 @@
priority: Int,
) {}
+ /** Called as the UI detects a tap event on the widget. */
+ open fun onTapWidget(
+ componentName: ComponentName,
+ priority: Int,
+ ) {}
+
/**
* Called as the UI requests reordering widgets.
*
@@ -194,4 +223,28 @@
fun setSelectedKey(key: String?) {
_selectedKey.value = key
}
+
+ /** Invoked once touches inside the lazy grid are consumed */
+ fun onHubTouchConsumed() {
+ if (_isTouchConsumed.value) {
+ return
+ }
+
+ _isTouchConsumed.value = true
+ }
+
+ /** Invoked when nested scrolling begins on the lazy grid */
+ fun onNestedScrolling() {
+ if (_isNestedScrolling.value) {
+ return
+ }
+
+ _isNestedScrolling.value = true
+ }
+
+ /** Resets nested scroll and touch consumption state */
+ fun onResetTouchState() {
+ _isTouchConsumed.value = false
+ _isNestedScrolling.value = false
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt
index e1408a0..bbd8596 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt
@@ -19,6 +19,7 @@
import android.graphics.Color
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
+import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.communal.util.CommunalColors
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
@@ -58,9 +59,17 @@
dreamToGlanceableHubTransitionViewModel: DreamingToGlanceableHubTransitionViewModel,
glanceableHubToDreamTransitionViewModel: GlanceableHubToDreamingTransitionViewModel,
communalInteractor: CommunalInteractor,
- communalSceneInteractor: CommunalSceneInteractor,
+ private val communalSceneInteractor: CommunalSceneInteractor,
keyguardTransitionInteractor: KeyguardTransitionInteractor
) {
+ /**
+ * Snaps to [CommunalScenes.Communal], showing the glanceable hub immediately without any
+ * transition.
+ */
+ fun snapToCommunal() {
+ communalSceneInteractor.snapToScene(CommunalScenes.Communal)
+ }
+
// Show UMO on glanceable hub immediately on transition into glanceable hub
private val showUmoFromOccludedToGlanceableHub: Flow<Boolean> =
keyguardTransitionInteractor
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
index 1e087f7..3fc8b09 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
@@ -16,6 +16,7 @@
package com.android.systemui.communal.ui.viewmodel
+import android.content.ComponentName
import android.content.res.Resources
import android.os.Bundle
import android.view.View
@@ -25,6 +26,7 @@
import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
import com.android.systemui.communal.domain.interactor.CommunalTutorialInteractor
import com.android.systemui.communal.domain.model.CommunalContentModel
+import com.android.systemui.communal.shared.log.CommunalMetricsLogger
import com.android.systemui.communal.shared.model.CommunalBackgroundType
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
@@ -92,6 +94,7 @@
private val shadeInteractor: ShadeInteractor,
@Named(MediaModule.COMMUNAL_HUB) mediaHost: MediaHost,
@CommunalLog logBuffer: LogBuffer,
+ private val metricsLogger: CommunalMetricsLogger,
) : BaseCommunalViewModel(communalSceneInteractor, communalInteractor, mediaHost) {
private val _isMediaHostVisible =
@@ -260,6 +263,10 @@
}
}
+ override fun onTapWidget(componentName: ComponentName, priority: Int) {
+ metricsLogger.logTapWidget(componentName.flattenToString(), priority)
+ }
+
fun onClick() {
keyguardIndicationController.showActionToUnlock()
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
index 3985769..03ef17b 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
@@ -128,7 +128,7 @@
Box(
modifier =
Modifier.fillMaxSize()
- .background(LocalAndroidColorScheme.current.onSecondaryFixed),
+ .background(LocalAndroidColorScheme.current.surfaceDim),
) {
CommunalHub(
viewModel = communalViewModel,
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
index 83fa001..9823985 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
@@ -56,6 +56,7 @@
import com.android.systemui.ambient.touch.dagger.AmbientTouchComponent;
import com.android.systemui.ambient.touch.scrim.ScrimManager;
import com.android.systemui.communal.domain.interactor.CommunalInteractor;
+import com.android.systemui.communal.shared.log.CommunalUiEvent;
import com.android.systemui.communal.shared.model.CommunalScenes;
import com.android.systemui.complication.Complication;
import com.android.systemui.complication.dagger.ComplicationComponent;
@@ -407,6 +408,7 @@
@Override
public void onWakeRequested() {
+ mUiEventLogger.log(CommunalUiEvent.DREAM_TO_COMMUNAL_HUB_DREAM_AWAKE_START);
mCommunalInteractor.changeScene(CommunalScenes.Communal, null);
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index d0beb7a..8990505 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -292,15 +292,6 @@
val WM_ENABLE_SHELL_TRANSITIONS =
sysPropBooleanFlag("persist.wm.debug.shell_transit", default = true)
- // TODO(b/254513207): Tracking Bug
- @Keep
- @JvmField
- val WM_ENABLE_PARTIAL_SCREEN_SHARING =
- releasedFlag(
- name = "enable_record_task_content",
- namespace = DeviceConfig.NAMESPACE_WINDOW_MANAGER,
- )
-
// TODO(b/256873975): Tracking Bug
@JvmField
@Keep
diff --git a/packages/SystemUI/src/com/android/systemui/inputmethod/data/model/InputMethodModel.kt b/packages/SystemUI/src/com/android/systemui/inputmethod/data/model/InputMethodModel.kt
index bdc18b3..0e19d87 100644
--- a/packages/SystemUI/src/com/android/systemui/inputmethod/data/model/InputMethodModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputmethod/data/model/InputMethodModel.kt
@@ -22,6 +22,8 @@
* @see android.view.inputmethod.InputMethodInfo
*/
data class InputMethodModel(
+ /** A unique ID for the user associated with this input method. */
+ val userId: Int,
/** A unique ID for this input method. */
val imeId: String,
/** The subtypes of this IME (may be empty). */
diff --git a/packages/SystemUI/src/com/android/systemui/inputmethod/data/repository/InputMethodRepository.kt b/packages/SystemUI/src/com/android/systemui/inputmethod/data/repository/InputMethodRepository.kt
index 5f316c4..c6fdc32 100644
--- a/packages/SystemUI/src/com/android/systemui/inputmethod/data/repository/InputMethodRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputmethod/data/repository/InputMethodRepository.kt
@@ -18,7 +18,6 @@
import android.annotation.SuppressLint
import android.os.UserHandle
-import android.view.inputmethod.InputMethodInfo
import android.view.inputmethod.InputMethodManager
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
@@ -34,18 +33,27 @@
/** Provides access to input-method related application state in the bouncer. */
interface InputMethodRepository {
+
/**
* Creates and returns a new `Flow` of installed input methods that are enabled for the
* specified user.
*
+ * @param user The user to query.
* @param fetchSubtypes Whether to fetch the IME Subtypes as well (requires an additional IPC
* call for each IME, avoid if not needed).
* @see InputMethodManager.getEnabledInputMethodListAsUser
*/
- suspend fun enabledInputMethods(userId: Int, fetchSubtypes: Boolean): Flow<InputMethodModel>
+ suspend fun enabledInputMethods(
+ user: UserHandle,
+ fetchSubtypes: Boolean,
+ ): Flow<InputMethodModel>
- /** Returns enabled subtypes for the currently selected input method. */
- suspend fun selectedInputMethodSubtypes(): List<InputMethodModel.Subtype>
+ /**
+ * Returns enabled subtypes for the currently selected input method.
+ *
+ * @param user The user to query.
+ */
+ suspend fun selectedInputMethodSubtypes(user: UserHandle): List<InputMethodModel.Subtype>
/**
* Shows the system's input method picker dialog.
@@ -67,20 +75,22 @@
) : InputMethodRepository {
override suspend fun enabledInputMethods(
- userId: Int,
+ user: UserHandle,
fetchSubtypes: Boolean
): Flow<InputMethodModel> {
return withContext(backgroundDispatcher) {
- inputMethodManager.getEnabledInputMethodListAsUser(UserHandle.of(userId))
+ inputMethodManager.getEnabledInputMethodListAsUser(user)
}
.asFlow()
.map { inputMethodInfo ->
InputMethodModel(
+ userId = user.identifier,
imeId = inputMethodInfo.id,
subtypes =
if (fetchSubtypes) {
enabledInputMethodSubtypes(
- inputMethodInfo,
+ user = user,
+ imeId = inputMethodInfo.id,
allowsImplicitlyEnabledSubtypes = true
)
} else {
@@ -90,11 +100,19 @@
}
}
- override suspend fun selectedInputMethodSubtypes(): List<InputMethodModel.Subtype> {
- return enabledInputMethodSubtypes(
- inputMethodInfo = null, // Fetch subtypes for the currently-selected IME.
- allowsImplicitlyEnabledSubtypes = false
- )
+ override suspend fun selectedInputMethodSubtypes(
+ user: UserHandle,
+ ): List<InputMethodModel.Subtype> {
+ val selectedIme = inputMethodManager.getCurrentInputMethodInfoAsUser(user)
+ return if (selectedIme == null) {
+ emptyList()
+ } else {
+ enabledInputMethodSubtypes(
+ user = user,
+ imeId = selectedIme.id,
+ allowsImplicitlyEnabledSubtypes = false
+ )
+ }
}
@SuppressLint("MissingPermission")
@@ -107,21 +125,23 @@
/**
* Returns a list of enabled input method subtypes for the specified input method info.
*
- * @param inputMethodInfo The [InputMethodInfo] whose subtypes list will be returned. If `null`,
- * returns enabled subtypes for the currently selected [InputMethodInfo].
+ * @param user The user to query.
+ * @param imeId The ID of the input method whose subtypes list will be returned.
* @param allowsImplicitlyEnabledSubtypes Whether to allow to return the implicitly enabled
* subtypes. If an input method info doesn't have enabled subtypes, the framework will
* implicitly enable subtypes according to the current system language.
- * @see InputMethodManager.getEnabledInputMethodSubtypeList
+ * @see InputMethodManager.getEnabledInputMethodSubtypeListAsUser
*/
private suspend fun enabledInputMethodSubtypes(
- inputMethodInfo: InputMethodInfo?,
+ user: UserHandle,
+ imeId: String,
allowsImplicitlyEnabledSubtypes: Boolean
): List<InputMethodModel.Subtype> {
return withContext(backgroundDispatcher) {
- inputMethodManager.getEnabledInputMethodSubtypeList(
- inputMethodInfo,
- allowsImplicitlyEnabledSubtypes
+ inputMethodManager.getEnabledInputMethodSubtypeListAsUser(
+ imeId,
+ allowsImplicitlyEnabledSubtypes,
+ user
)
}
.map {
diff --git a/packages/SystemUI/src/com/android/systemui/inputmethod/domain/interactor/InputMethodInteractor.kt b/packages/SystemUI/src/com/android/systemui/inputmethod/domain/interactor/InputMethodInteractor.kt
index c54aa7f..d3ef178 100644
--- a/packages/SystemUI/src/com/android/systemui/inputmethod/domain/interactor/InputMethodInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputmethod/domain/interactor/InputMethodInteractor.kt
@@ -16,6 +16,7 @@
package com.android.systemui.inputmethod.domain.interactor
+import android.os.UserHandle
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.inputmethod.data.repository.InputMethodRepository
import javax.inject.Inject
@@ -36,14 +37,16 @@
* Method adapted from `com.android.inputmethod.latin.Utils`.
*/
suspend fun hasMultipleEnabledImesOrSubtypes(userId: Int): Boolean {
+ val user = UserHandle.of(userId)
// Count IMEs that either have no subtypes, or have at least one non-auxiliary subtype.
val matchingInputMethods =
repository
- .enabledInputMethods(userId, fetchSubtypes = true)
+ .enabledInputMethods(user, fetchSubtypes = true)
.filter { ime -> ime.subtypes.isEmpty() || ime.subtypes.any { !it.isAuxiliary } }
.take(2) // Short-circuit if we find at least 2 matching IMEs.
- return matchingInputMethods.count() > 1 || repository.selectedInputMethodSubtypes().size > 1
+ return matchingInputMethods.count() > 1 ||
+ repository.selectedInputMethodSubtypes(user).size > 1
}
/** Shows the system's input method picker dialog. */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
index d1a8463..2f41c0b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
@@ -62,21 +62,21 @@
const val TAG = "KeyguardUnlock"
/**
- * Starting scale factor for the app/launcher surface behind the keyguard, when it's animating
- * in during keyguard exit.
+ * Starting scale factor for the app/launcher surface behind the keyguard, when it's animating in
+ * during keyguard exit.
*/
const val SURFACE_BEHIND_START_SCALE_FACTOR = 0.95f
/**
- * How much to translate the surface behind the keyguard at the beginning of the exit animation,
- * in terms of percentage of the surface's height.
+ * How much to translate the surface behind the keyguard at the beginning of the exit animation, in
+ * terms of percentage of the surface's height.
*/
const val SURFACE_BEHIND_START_TRANSLATION_Y = 0.05f
/**
- * Y coordinate of the pivot point for the scale effect on the surface behind the keyguard. This
- * is expressed as percentage of the surface's height, so 0.66f means the surface will scale up
- * from the point at (width / 2, height * 0.66).
+ * Y coordinate of the pivot point for the scale effect on the surface behind the keyguard. This is
+ * expressed as percentage of the surface's height, so 0.66f means the surface will scale up from
+ * the point at (width / 2, height * 0.66).
*/
const val SURFACE_BEHIND_SCALE_PIVOT_Y = 0.66f
@@ -155,19 +155,20 @@
* [notifyStartSurfaceBehindRemoteAnimation] by [KeyguardViewMediator].
*/
@SysUISingleton
-class KeyguardUnlockAnimationController @Inject constructor(
- private val windowManager: WindowManager,
- @Main private val resources: Resources,
- private val keyguardStateController: KeyguardStateController,
- private val
- keyguardViewMediator: Lazy<KeyguardViewMediator>,
- private val keyguardViewController: KeyguardViewController,
- private val featureFlags: FeatureFlags,
- private val biometricUnlockControllerLazy: Lazy<BiometricUnlockController>,
- private val statusBarStateController: SysuiStatusBarStateController,
- private val notificationShadeWindowController: NotificationShadeWindowController,
- private val powerManager: PowerManager,
- private val wallpaperManager: WallpaperManager,
+open class KeyguardUnlockAnimationController
+@Inject
+constructor(
+ private val windowManager: WindowManager,
+ @Main private val resources: Resources,
+ private val keyguardStateController: KeyguardStateController,
+ private val keyguardViewMediator: Lazy<KeyguardViewMediator>,
+ private val keyguardViewController: KeyguardViewController,
+ private val featureFlags: FeatureFlags,
+ private val biometricUnlockControllerLazy: Lazy<BiometricUnlockController>,
+ private val statusBarStateController: SysuiStatusBarStateController,
+ private val notificationShadeWindowController: NotificationShadeWindowController,
+ private val powerManager: PowerManager,
+ private val wallpaperManager: WallpaperManager,
) : KeyguardStateController.Callback, ISysuiUnlockAnimationController.Stub() {
interface KeyguardUnlockAnimationListener {
@@ -221,8 +222,8 @@
var playingCannedUnlockAnimation = false
/**
- * Whether we reached the swipe gesture threshold to dismiss keyguard, or restore it, once
- * and should ignore any future changes to the dismiss amount before the animation finishes.
+ * Whether we reached the swipe gesture threshold to dismiss keyguard, or restore it, once and
+ * should ignore any future changes to the dismiss amount before the animation finishes.
*/
var dismissAmountThresholdsReached = false
@@ -235,9 +236,7 @@
*/
private var launcherUnlockController: ILauncherUnlockAnimationController? = null
- /**
- * Fully qualified class name of the launcher activity
- */
+ /** Fully qualified class name of the launcher activity */
private var launcherActivityClass: String? = null
private val listeners = ArrayList<KeyguardUnlockAnimationListener>()
@@ -248,8 +247,8 @@
* transition, but that's okay!
*/
override fun setLauncherUnlockController(
- activityClass: String,
- callback: ILauncherUnlockAnimationController?
+ activityClass: String,
+ callback: ILauncherUnlockAnimationController?
) {
launcherActivityClass = activityClass
launcherUnlockController = callback
@@ -274,8 +273,7 @@
* If we're unlocking via biometrics, PIN entry, or from clicking a notification, a canned
* animation is started in [playCannedUnlockAnimation].
*/
- @VisibleForTesting
- var surfaceTransactionApplier: SyncRtSurfaceTransactionApplier? = null
+ @VisibleForTesting var surfaceTransactionApplier: SyncRtSurfaceTransactionApplier? = null
private var surfaceBehindRemoteAnimationTargets: Array<RemoteAnimationTarget>? = null
private var openingWallpaperTargets: Array<RemoteAnimationTarget>? = null
private var closingWallpaperTargets: Array<RemoteAnimationTarget>? = null
@@ -291,8 +289,7 @@
*/
private var surfaceBehindAlpha = 1f
- @VisibleForTesting
- var surfaceBehindAlphaAnimator = ValueAnimator.ofFloat(0f, 1f)
+ @VisibleForTesting var surfaceBehindAlphaAnimator = ValueAnimator.ofFloat(0f, 1f)
var wallpaperCannedUnlockAnimator = ValueAnimator.ofFloat(0f, 1f)
@@ -310,8 +307,7 @@
* Animator that animates in the surface behind the keyguard. This is used to play a canned
* animation on the surface, if we're not doing a swipe gesture.
*/
- @VisibleForTesting
- val surfaceBehindEntryAnimator = ValueAnimator.ofFloat(0f, 1f)
+ @VisibleForTesting val surfaceBehindEntryAnimator = ValueAnimator.ofFloat(0f, 1f)
/** Rounded corner radius to apply to the surface behind the keyguard. */
private var roundedCornerRadius = 0f
@@ -322,8 +318,7 @@
* window like any other app. This can be true while [willUnlockWithSmartspaceTransition] is
* false, if the smartspace is not available or was not ready in time.
*/
- @VisibleForTesting
- var willUnlockWithInWindowLauncherAnimations: Boolean = false
+ @VisibleForTesting var willUnlockWithInWindowLauncherAnimations: Boolean = false
/**
* Whether we called [ILauncherUnlockAnimationController.prepareForUnlock], but have not yet
@@ -353,49 +348,64 @@
surfaceBehindAlpha = valueAnimator.animatedValue as Float
updateSurfaceBehindAppearAmount()
}
- addListener(object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator) {
- // If we animated the surface alpha to 0f, it means we cancelled a swipe to
- // dismiss. In this case, we should ask the KeyguardViewMediator to end the
- // remote animation to hide the surface behind the keyguard, but should *not*
- // call onKeyguardExitRemoteAnimationFinished since that will hide the keyguard
- // and unlock the device as well as hiding the surface.
- if (surfaceBehindAlpha == 0f) {
- Log.d(TAG, "surfaceBehindAlphaAnimator#onAnimationEnd")
- surfaceBehindRemoteAnimationTargets = null
- openingWallpaperTargets = null
- closingWallpaperTargets = null
- keyguardViewMediator.get().finishSurfaceBehindRemoteAnimation(
- false /* cancelled */)
- } else {
- Log.d(TAG, "skip finishSurfaceBehindRemoteAnimation" +
- " surfaceBehindAlpha=$surfaceBehindAlpha")
+ addListener(
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ // If we animated the surface alpha to 0f, it means we cancelled a swipe to
+ // dismiss. In this case, we should ask the KeyguardViewMediator to end the
+ // remote animation to hide the surface behind the keyguard, but should
+ // *not* call onKeyguardExitRemoteAnimationFinished since that will hide the
+ // keyguard and unlock the device as well as hiding the surface.
+ if (surfaceBehindAlpha == 0f) {
+ Log.d(TAG, "surfaceBehindAlphaAnimator#onAnimationEnd")
+ surfaceBehindRemoteAnimationTargets = null
+ openingWallpaperTargets = null
+ closingWallpaperTargets = null
+ keyguardViewMediator
+ .get()
+ .finishSurfaceBehindRemoteAnimation(false /* cancelled */)
+ } else {
+ Log.d(
+ TAG,
+ "skip finishSurfaceBehindRemoteAnimation" +
+ " surfaceBehindAlpha=$surfaceBehindAlpha"
+ )
+ }
}
}
- })
+ )
}
with(wallpaperCannedUnlockAnimator) {
- duration = if (fasterUnlockTransition()) UNLOCK_ANIMATION_DURATION_MS
- else LAUNCHER_ICONS_ANIMATION_DURATION_MS
- interpolator = if (fasterUnlockTransition()) Interpolators.LINEAR
- else Interpolators.ALPHA_OUT
+ duration =
+ if (fasterUnlockTransition()) UNLOCK_ANIMATION_DURATION_MS
+ else LAUNCHER_ICONS_ANIMATION_DURATION_MS
+ interpolator =
+ if (fasterUnlockTransition()) Interpolators.LINEAR else Interpolators.ALPHA_OUT
addUpdateListener { valueAnimator: ValueAnimator ->
setWallpaperAppearAmount(
- valueAnimator.animatedValue as Float, openingWallpaperTargets)
+ valueAnimator.animatedValue as Float,
+ openingWallpaperTargets
+ )
}
- addListener(object : AnimatorListenerAdapter() {
- override fun onAnimationStart(animation: Animator) {
- super.onAnimationStart(animation)
- Trace.asyncTraceBegin(Trace.TRACE_TAG_APP, "WallpaperAlphaAnimation", 0)
+ addListener(
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationStart(animation: Animator) {
+ super.onAnimationStart(animation)
+ Trace.asyncTraceBegin(Trace.TRACE_TAG_APP, "WallpaperAlphaAnimation", 0)
+ }
+
+ override fun onAnimationEnd(animation: Animator) {
+ Log.d(TAG, "wallpaperCannedUnlockAnimator#onAnimationEnd")
+ keyguardViewMediator
+ .get()
+ .exitKeyguardAndFinishSurfaceBehindRemoteAnimation(
+ false /* cancelled */
+ )
+ Trace.asyncTraceEnd(Trace.TRACE_TAG_APP, "WallpaperAlphaAnimation", 0)
+ }
}
- override fun onAnimationEnd(animation: Animator) {
- Log.d(TAG, "wallpaperCannedUnlockAnimator#onAnimationEnd")
- keyguardViewMediator.get().exitKeyguardAndFinishSurfaceBehindRemoteAnimation(
- false /* cancelled */)
- Trace.asyncTraceEnd(Trace.TRACE_TAG_APP, "WallpaperAlphaAnimation", 0)
- }
- })
+ )
}
if (fasterUnlockTransition()) {
@@ -405,7 +415,9 @@
interpolator = Interpolators.LINEAR
addUpdateListener { valueAnimator: ValueAnimator ->
setWallpaperAppearAmount(
- valueAnimator.animatedValue as Float, closingWallpaperTargets)
+ valueAnimator.animatedValue as Float,
+ closingWallpaperTargets
+ )
}
}
}
@@ -418,15 +430,19 @@
surfaceBehindAlpha = valueAnimator.animatedValue as Float
setSurfaceBehindAppearAmount(valueAnimator.animatedValue as Float)
}
- addListener(object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator) {
- Log.d(TAG, "surfaceBehindEntryAnimator#onAnimationEnd")
- playingCannedUnlockAnimation = false
- keyguardViewMediator.get().exitKeyguardAndFinishSurfaceBehindRemoteAnimation(
- false /* cancelled */
- )
+ addListener(
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ Log.d(TAG, "surfaceBehindEntryAnimator#onAnimationEnd")
+ playingCannedUnlockAnimation = false
+ keyguardViewMediator
+ .get()
+ .exitKeyguardAndFinishSurfaceBehindRemoteAnimation(
+ false /* cancelled */
+ )
+ }
}
- })
+ )
}
// Listen for changes in the dismiss amount.
@@ -436,9 +452,7 @@
resources.getDimensionPixelSize(R.dimen.rounded_corner_radius).toFloat()
}
- /**
- * Add a listener to be notified of various stages of the unlock animation.
- */
+ /** Add a listener to be notified of various stages of the unlock animation. */
fun addKeyguardUnlockAnimationListener(listener: KeyguardUnlockAnimationListener) {
listeners.add(listener)
}
@@ -454,11 +468,11 @@
fun canPerformInWindowLauncherAnimations(): Boolean {
// TODO(b/278086361): Refactor in-window animations.
return !KeyguardWmStateRefactor.isEnabled &&
- isSupportedLauncherUnderneath() &&
- // If the launcher is underneath, but we're about to launch an activity, don't do
- // the animations since they won't be visible.
- !notificationShadeWindowController.isLaunchingActivity &&
- launcherUnlockController != null
+ isSupportedLauncherUnderneath() &&
+ // If the launcher is underneath, but we're about to launch an activity, don't do
+ // the animations since they won't be visible.
+ !notificationShadeWindowController.isLaunchingActivity &&
+ launcherUnlockController != null
}
/**
@@ -469,8 +483,11 @@
private fun logInWindowAnimationConditions() {
Log.wtf(TAG, "canPerformInWindowLauncherAnimations expected all of these to be true: ")
Log.wtf(TAG, " isNexusLauncherUnderneath: ${isSupportedLauncherUnderneath()}")
- Log.wtf(TAG, " !notificationShadeWindowController.isLaunchingActivity: " +
- "${!notificationShadeWindowController.isLaunchingActivity}")
+ Log.wtf(
+ TAG,
+ " !notificationShadeWindowController.isLaunchingActivity: " +
+ "${!notificationShadeWindowController.isLaunchingActivity}"
+ )
Log.wtf(TAG, " launcherUnlockController != null: ${launcherUnlockController != null}")
Log.wtf(TAG, " !isFoldable(context): ${!isFoldable(resources)}")
}
@@ -480,8 +497,10 @@
* changed.
*/
override fun onKeyguardGoingAwayChanged() {
- if (keyguardStateController.isKeyguardGoingAway &&
- !statusBarStateController.leaveOpenOnKeyguardHide()) {
+ if (
+ keyguardStateController.isKeyguardGoingAway &&
+ !statusBarStateController.leaveOpenOnKeyguardHide()
+ ) {
prepareForInWindowLauncherAnimations()
}
@@ -489,16 +508,22 @@
// make sure that we've left the launcher at 100% unlocked. This is a fail-safe to prevent
// against "tiny launcher" and similar states where the launcher is left in the prepared to
// animate state.
- if (!keyguardStateController.isKeyguardGoingAway &&
- willUnlockWithInWindowLauncherAnimations) {
+ if (
+ !keyguardStateController.isKeyguardGoingAway && willUnlockWithInWindowLauncherAnimations
+ ) {
try {
- launcherUnlockController?.setUnlockAmount(1f,
- biometricUnlockControllerLazy.get().isWakeAndUnlock /* forceIfAnimating */)
+ launcherUnlockController?.setUnlockAmount(
+ 1f,
+ biometricUnlockControllerLazy.get().isWakeAndUnlock /* forceIfAnimating */
+ )
} catch (e: DeadObjectException) {
- Log.e(TAG, "launcherUnlockAnimationController was dead, but non-null in " +
+ Log.e(
+ TAG,
+ "launcherUnlockAnimationController was dead, but non-null in " +
"onKeyguardGoingAwayChanged(). Catching exception as this should mean " +
"Launcher is in the process of being destroyed, but the IPC to System UI " +
- "telling us hasn't arrived yet.")
+ "telling us hasn't arrived yet."
+ )
}
}
}
@@ -525,22 +550,26 @@
// Grab the bounds of our lockscreen smartspace and send them to launcher so they can
// position their smartspace there initially, then animate it to its resting position.
if (willUnlockWithSmartspaceTransition) {
- lockscreenSmartspaceBounds = Rect().apply {
- lockscreenSmartspace!!.getBoundsOnScreen(this)
+ lockscreenSmartspaceBounds =
+ Rect().apply {
+ lockscreenSmartspace!!.getBoundsOnScreen(this)
- // The smartspace container on the lockscreen has left and top padding to align it
- // with other lockscreen content. This padding is inside the bounds on screen, so
- // add it to those bounds so that the padding-less launcher smartspace is properly
- // aligned.
- offset(lockscreenSmartspace!!.paddingLeft, lockscreenSmartspace!!.paddingTop)
+ // The smartspace container on the lockscreen has left and top padding to align
+ // it with other lockscreen content. This padding is inside the bounds on
+ // screen, so add it to those bounds so that the padding-less launcher
+ // smartspace is properly aligned.
+ offset(lockscreenSmartspace!!.paddingLeft, lockscreenSmartspace!!.paddingTop)
- // Also offset by the current card's top padding, if it has any. This allows us to
- // align the tops of the lockscreen/launcher smartspace cards. Some cards, such as
- // the three-line date/weather/alarm card, only have three lines on lockscreen but
- // two on launcher.
- offset(0, (lockscreenSmartspace
- as? BcSmartspaceDataPlugin.SmartspaceView)?.currentCardTopPadding ?: 0)
- }
+ // Also offset by the current card's top padding, if it has any. This allows us
+ // to align the tops of the lockscreen/launcher smartspace cards. Some cards,
+ // such as the three-line date/weather/alarm card, only have three lines on
+ // lockscreen but two on launcher.
+ offset(
+ 0,
+ (lockscreenSmartspace as? BcSmartspaceDataPlugin.SmartspaceView)
+ ?.currentCardTopPadding ?: 0
+ )
+ }
}
// Currently selected lockscreen smartspace page, or -1 if it's not available.
@@ -583,8 +612,8 @@
requestedShowSurfaceBehindKeyguard: Boolean
) {
if (surfaceTransactionApplier == null) {
- surfaceTransactionApplier = SyncRtSurfaceTransactionApplier(
- keyguardViewController.viewRootImpl.view)
+ surfaceTransactionApplier =
+ SyncRtSurfaceTransactionApplier(keyguardViewController.viewRootImpl.view)
}
surfaceBehindRemoteAnimationTargets = targets
@@ -603,8 +632,10 @@
// surface behind the keyguard to finish unlocking.
if (keyguardStateController.isFlingingToDismissKeyguard) {
playCannedUnlockAnimation()
- } else if (keyguardStateController.isDismissingFromSwipe &&
- willUnlockWithInWindowLauncherAnimations) {
+ } else if (
+ keyguardStateController.isDismissingFromSwipe &&
+ willUnlockWithInWindowLauncherAnimations
+ ) {
// If we're swiping to unlock to the Launcher, and can play in-window animations,
// make the launcher surface fully visible and play the in-window unlock animation
// on the launcher icons. System UI will remain locked, using the swipe-to-unlock
@@ -615,19 +646,23 @@
try {
launcherUnlockController?.playUnlockAnimation(
- true,
- unlockAnimationDurationMs() + cannedUnlockStartDelayMs(),
- 0 /* startDelay */)
+ true,
+ unlockAnimationDurationMs() + cannedUnlockStartDelayMs(),
+ 0 /* startDelay */
+ )
} catch (e: DeadObjectException) {
// Hello! If you are here investigating a bug where Launcher is blank (no icons)
// then the below assumption about Launcher's destruction was incorrect. This
// would mean prepareToUnlock was called (blanking Launcher in preparation for
// the beginning of the unlock animation), but then somehow we were unable to
// call playUnlockAnimation to animate the icons back in.
- Log.e(TAG, "launcherUnlockAnimationController was dead, but non-null. " +
+ Log.e(
+ TAG,
+ "launcherUnlockAnimationController was dead, but non-null. " +
"Catching exception as this should mean Launcher is in the process " +
"of being destroyed, but the IPC to System UI telling us hasn't " +
- "arrived yet.")
+ "arrived yet."
+ )
}
launcherPreparedForUnlock = false
@@ -643,15 +678,18 @@
}
// Notify if waking from AOD only
- val isWakeAndUnlockNotFromDream = biometricUnlockControllerLazy.get().isWakeAndUnlock &&
- biometricUnlockControllerLazy.get().mode != MODE_WAKE_AND_UNLOCK_FROM_DREAM
+ val isWakeAndUnlockNotFromDream =
+ biometricUnlockControllerLazy.get().isWakeAndUnlock &&
+ biometricUnlockControllerLazy.get().mode != MODE_WAKE_AND_UNLOCK_FROM_DREAM
listeners.forEach {
it.onUnlockAnimationStarted(
playingCannedUnlockAnimation /* playingCannedAnimation */,
isWakeAndUnlockNotFromDream /* isWakeAndUnlockNotFromDream */,
cannedUnlockStartDelayMs() /* unlockStartDelay */,
- LAUNCHER_ICONS_ANIMATION_DURATION_MS /* unlockAnimationDuration */) }
+ LAUNCHER_ICONS_ANIMATION_DURATION_MS /* unlockAnimationDuration */
+ )
+ }
// Finish the keyguard remote animation if the dismiss amount has crossed the threshold.
// Check it here in case there is no more change to the dismiss amount after the last change
@@ -685,8 +723,9 @@
biometricUnlockControllerLazy.get().isWakeAndUnlock -> {
Log.d(TAG, "playCannedUnlockAnimation, isWakeAndUnlock")
setSurfaceBehindAppearAmount(1f)
- keyguardViewMediator.get().exitKeyguardAndFinishSurfaceBehindRemoteAnimation(
- false /* cancelled */)
+ keyguardViewMediator
+ .get()
+ .exitKeyguardAndFinishSurfaceBehindRemoteAnimation(false /* cancelled */)
}
// Otherwise, we're doing a normal full-window unlock. Start this animator, which will
@@ -698,8 +737,11 @@
}
if (launcherPreparedForUnlock && !willUnlockWithInWindowLauncherAnimations) {
- Log.wtf(TAG, "Launcher is prepared for unlock, so we should have started the " +
- "in-window animation, however we apparently did not.")
+ Log.wtf(
+ TAG,
+ "Launcher is prepared for unlock, so we should have started the " +
+ "in-window animation, however we apparently did not."
+ )
logInWindowAnimationConditions()
}
}
@@ -708,7 +750,6 @@
* Unlock to the launcher, using in-window animations, and the smartspace shared element
* transition if possible.
*/
-
@VisibleForTesting
fun unlockToLauncherWithInWindowAnimations() {
surfaceBehindAlpha = 1f
@@ -717,26 +758,32 @@
try {
// Begin the animation, waiting for the shade to animate out.
launcherUnlockController?.playUnlockAnimation(
- true /* unlocked */,
- LAUNCHER_ICONS_ANIMATION_DURATION_MS /* duration */,
- cannedUnlockStartDelayMs() /* startDelay */)
+ true /* unlocked */,
+ LAUNCHER_ICONS_ANIMATION_DURATION_MS /* duration */,
+ cannedUnlockStartDelayMs() /* startDelay */
+ )
} catch (e: DeadObjectException) {
// Hello! If you are here investigating a bug where Launcher is blank (no icons)
// then the below assumption about Launcher's destruction was incorrect. This
// would mean prepareToUnlock was called (blanking Launcher in preparation for
// the beginning of the unlock animation), but then somehow we were unable to
// call playUnlockAnimation to animate the icons back in.
- Log.e(TAG, "launcherUnlockAnimationController was dead, but non-null. " +
+ Log.e(
+ TAG,
+ "launcherUnlockAnimationController was dead, but non-null. " +
"Catching exception as this should mean Launcher is in the process " +
"of being destroyed, but the IPC to System UI telling us hasn't " +
- "arrived yet.")
+ "arrived yet."
+ )
}
launcherPreparedForUnlock = false
// Now that the Launcher surface (with its smartspace positioned identically to ours) is
// visible, hide our smartspace.
- if (lockscreenSmartspace?.visibility == View.VISIBLE) {
+ if (
+ shouldPerformSmartspaceTransition() && lockscreenSmartspace?.visibility == View.VISIBLE
+ ) {
lockscreenSmartspace?.visibility = View.INVISIBLE
}
@@ -747,22 +794,31 @@
fadeOutWallpaper()
}
- handler.postDelayed({
- if (keyguardViewMediator.get().isShowingAndNotOccluded &&
- !keyguardStateController.isKeyguardGoingAway) {
- Log.e(TAG, "Finish keyguard exit animation delayed Runnable ran, but we are " +
- "showing and not going away.")
- return@postDelayed
- }
+ handler.postDelayed(
+ {
+ if (
+ keyguardViewMediator.get().isShowingAndNotOccluded &&
+ !keyguardStateController.isKeyguardGoingAway
+ ) {
+ Log.e(
+ TAG,
+ "Finish keyguard exit animation delayed Runnable ran, but we are " +
+ "showing and not going away."
+ )
+ return@postDelayed
+ }
- if (openingWallpaperTargets?.isNotEmpty() == true) {
- fadeInWallpaper()
- hideKeyguardViewAfterRemoteAnimation()
- } else {
- keyguardViewMediator.get().exitKeyguardAndFinishSurfaceBehindRemoteAnimation(
- false /* cancelled */)
- }
- }, cannedUnlockStartDelayMs())
+ if (openingWallpaperTargets?.isNotEmpty() == true) {
+ fadeInWallpaper()
+ hideKeyguardViewAfterRemoteAnimation()
+ } else {
+ keyguardViewMediator
+ .get()
+ .exitKeyguardAndFinishSurfaceBehindRemoteAnimation(false /* cancelled */)
+ }
+ },
+ cannedUnlockStartDelayMs()
+ )
}
/**
@@ -784,12 +840,14 @@
// interaction tight.
if (keyguardStateController.isFlingingToDismissKeyguard) {
setSurfaceBehindAppearAmount(keyguardStateController.dismissAmount)
- } else if (keyguardStateController.isDismissingFromSwipe ||
- keyguardStateController.isSnappingKeyguardBackAfterSwipe) {
+ } else if (
+ keyguardStateController.isDismissingFromSwipe ||
+ keyguardStateController.isSnappingKeyguardBackAfterSwipe
+ ) {
val totalSwipeDistanceToDismiss =
- (DISMISS_AMOUNT_EXIT_KEYGUARD_THRESHOLD - DISMISS_AMOUNT_SHOW_SURFACE_THRESHOLD)
+ (DISMISS_AMOUNT_EXIT_KEYGUARD_THRESHOLD - DISMISS_AMOUNT_SHOW_SURFACE_THRESHOLD)
val swipedDistanceSoFar: Float =
- keyguardStateController.dismissAmount - DISMISS_AMOUNT_SHOW_SURFACE_THRESHOLD
+ keyguardStateController.dismissAmount - DISMISS_AMOUNT_SHOW_SURFACE_THRESHOLD
val progress = swipedDistanceSoFar / totalSwipeDistanceToDismiss
setSurfaceBehindAppearAmount(progress)
}
@@ -801,10 +859,13 @@
// If the surface is visible or it's about to be, start updating its appearance to
// reflect the new dismiss amount.
- if ((keyguardViewMediator.get().requestedShowSurfaceBehindKeyguard() ||
- keyguardViewMediator.get()
+ if (
+ (keyguardViewMediator.get().requestedShowSurfaceBehindKeyguard() ||
+ keyguardViewMediator
+ .get()
.isAnimatingBetweenKeyguardAndSurfaceBehindOrWillBe) &&
- !playingCannedUnlockAnimation) {
+ !playingCannedUnlockAnimation
+ ) {
updateSurfaceBehindAppearAmount()
}
}
@@ -838,11 +899,15 @@
val dismissAmount = keyguardStateController.dismissAmount
- if (dismissAmount >= DISMISS_AMOUNT_SHOW_SURFACE_THRESHOLD &&
- !keyguardViewMediator.get().requestedShowSurfaceBehindKeyguard()) {
+ if (
+ dismissAmount >= DISMISS_AMOUNT_SHOW_SURFACE_THRESHOLD &&
+ !keyguardViewMediator.get().requestedShowSurfaceBehindKeyguard()
+ ) {
keyguardViewMediator.get().showSurfaceBehindKeyguard()
- } else if (dismissAmount < DISMISS_AMOUNT_SHOW_SURFACE_THRESHOLD &&
- keyguardViewMediator.get().requestedShowSurfaceBehindKeyguard()) {
+ } else if (
+ dismissAmount < DISMISS_AMOUNT_SHOW_SURFACE_THRESHOLD &&
+ keyguardViewMediator.get().requestedShowSurfaceBehindKeyguard()
+ ) {
// We're no longer past the threshold but we are showing the surface. Animate it
// out.
keyguardViewMediator.get().hideSurfaceBehindKeyguard()
@@ -868,22 +933,27 @@
}
// no-op if animation is not requested yet.
- if (!keyguardViewMediator.get().requestedShowSurfaceBehindKeyguard() ||
- !keyguardViewMediator.get().isAnimatingBetweenKeyguardAndSurfaceBehindOrWillBe) {
+ if (
+ !keyguardViewMediator.get().requestedShowSurfaceBehindKeyguard() ||
+ !keyguardViewMediator.get().isAnimatingBetweenKeyguardAndSurfaceBehindOrWillBe
+ ) {
return
}
val dismissAmount = keyguardStateController.dismissAmount
- if (dismissAmount >= 1f ||
+ if (
+ dismissAmount >= 1f ||
(keyguardStateController.isDismissingFromSwipe &&
- // Don't hide if we're flinging during a swipe, since we need to finish
- // animating it out. This will be called again after the fling ends.
- !keyguardStateController.isFlingingToDismissKeyguardDuringSwipeGesture &&
- dismissAmount >= DISMISS_AMOUNT_EXIT_KEYGUARD_THRESHOLD)) {
+ // Don't hide if we're flinging during a swipe, since we need to finish
+ // animating it out. This will be called again after the fling ends.
+ !keyguardStateController.isFlingingToDismissKeyguardDuringSwipeGesture &&
+ dismissAmount >= DISMISS_AMOUNT_EXIT_KEYGUARD_THRESHOLD)
+ ) {
setSurfaceBehindAppearAmount(1f)
dismissAmountThresholdsReached = true
- keyguardViewMediator.get().exitKeyguardAndFinishSurfaceBehindRemoteAnimation(
- false /* cancelled */)
+ keyguardViewMediator
+ .get()
+ .exitKeyguardAndFinishSurfaceBehindRemoteAnimation(false /* cancelled */)
}
}
@@ -894,51 +964,56 @@
* wallpapers, this transitions between the two wallpapers
*/
fun setSurfaceBehindAppearAmount(amount: Float, wallpapers: Boolean = true) {
- val animationAlpha = when {
- // If we're snapping the keyguard back, immediately begin fading it out.
- keyguardStateController.isSnappingKeyguardBackAfterSwipe -> amount
- // If the screen has turned back off, the unlock animation is going to be cancelled,
- // so set the surface alpha to 0f so it's no longer visible.
- !powerManager.isInteractive -> 0f
- else -> surfaceBehindAlpha
- }
+ val animationAlpha =
+ when {
+ // If we're snapping the keyguard back, immediately begin fading it out.
+ keyguardStateController.isSnappingKeyguardBackAfterSwipe -> amount
+ // If the screen has turned back off, the unlock animation is going to be cancelled,
+ // so set the surface alpha to 0f so it's no longer visible.
+ !powerManager.isInteractive -> 0f
+ else -> surfaceBehindAlpha
+ }
surfaceBehindRemoteAnimationTargets?.forEach { surfaceBehindRemoteAnimationTarget ->
if (!KeyguardWmStateRefactor.isEnabled) {
val surfaceHeight: Int =
- surfaceBehindRemoteAnimationTarget.screenSpaceBounds.height()
+ surfaceBehindRemoteAnimationTarget.screenSpaceBounds.height()
- var scaleFactor = (SURFACE_BEHIND_START_SCALE_FACTOR +
- (1f - SURFACE_BEHIND_START_SCALE_FACTOR) *
- MathUtils.clamp(amount, 0f, 1f))
+ var scaleFactor =
+ (SURFACE_BEHIND_START_SCALE_FACTOR +
+ (1f - SURFACE_BEHIND_START_SCALE_FACTOR) * MathUtils.clamp(amount, 0f, 1f))
// If we're dismissing via swipe to the Launcher, we'll play in-window scale
// animations, so don't also scale the window.
- if (keyguardStateController.isDismissingFromSwipe &&
- willUnlockWithInWindowLauncherAnimations) {
+ if (
+ keyguardStateController.isDismissingFromSwipe &&
+ willUnlockWithInWindowLauncherAnimations
+ ) {
scaleFactor = 1f
}
// Translate up from the bottom.
surfaceBehindMatrix.setTranslate(
- surfaceBehindRemoteAnimationTarget.screenSpaceBounds.left.toFloat(),
- surfaceBehindRemoteAnimationTarget.screenSpaceBounds.top.toFloat() +
- surfaceHeight * SURFACE_BEHIND_START_TRANSLATION_Y * (1f - amount)
+ surfaceBehindRemoteAnimationTarget.screenSpaceBounds.left.toFloat(),
+ surfaceBehindRemoteAnimationTarget.screenSpaceBounds.top.toFloat() +
+ surfaceHeight * SURFACE_BEHIND_START_TRANSLATION_Y * (1f - amount)
)
// Scale up from a point at the center-bottom of the surface.
surfaceBehindMatrix.postScale(
- scaleFactor,
- scaleFactor,
- keyguardViewController.viewRootImpl.width / 2f,
- surfaceHeight * SURFACE_BEHIND_SCALE_PIVOT_Y
+ scaleFactor,
+ scaleFactor,
+ keyguardViewController.viewRootImpl.width / 2f,
+ surfaceHeight * SURFACE_BEHIND_SCALE_PIVOT_Y
)
// SyncRtSurfaceTransactionApplier cannot apply transaction when the target view is
// unable to draw
val sc: SurfaceControl? = surfaceBehindRemoteAnimationTarget.leash
- if (keyguardViewController.viewRootImpl.view?.visibility != View.VISIBLE &&
- sc?.isValid == true) {
+ if (
+ keyguardViewController.viewRootImpl.view?.visibility != View.VISIBLE &&
+ sc?.isValid == true
+ ) {
with(SurfaceControl.Transaction()) {
setMatrix(sc, surfaceBehindMatrix, tmpFloat)
setCornerRadius(sc, roundedCornerRadius)
@@ -947,12 +1022,13 @@
}
} else {
applyParamsToSurface(
- SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(
- surfaceBehindRemoteAnimationTarget.leash)
- .withMatrix(surfaceBehindMatrix)
- .withCornerRadius(roundedCornerRadius)
- .withAlpha(animationAlpha)
- .build()
+ SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(
+ surfaceBehindRemoteAnimationTarget.leash
+ )
+ .withMatrix(surfaceBehindMatrix)
+ .withCornerRadius(roundedCornerRadius)
+ .withAlpha(animationAlpha)
+ .build()
)
}
}
@@ -969,8 +1045,8 @@
val fadeOutStart = LOCK_WALLPAPER_FADE_OUT_START_DELAY / total
val fadeOutEnd = fadeOutStart + LOCK_WALLPAPER_FADE_OUT_DURATION / total
- val fadeOutAmount = ((amount - fadeOutStart) / (fadeOutEnd - fadeOutStart))
- .coerceIn(0f, 1f)
+ val fadeOutAmount =
+ ((amount - fadeOutStart) / (fadeOutEnd - fadeOutStart)).coerceIn(0f, 1f)
setWallpaperAppearAmount(fadeInAmount, openingWallpaperTargets)
setWallpaperAppearAmount(1 - fadeOutAmount, closingWallpaperTargets)
@@ -984,18 +1060,19 @@
// SyncRtSurfaceTransactionApplier cannot apply transaction when the target view is
// unable to draw
val sc: SurfaceControl? = wallpaper.leash
- if (keyguardViewController.viewRootImpl.view?.visibility != View.VISIBLE &&
- sc?.isValid == true) {
+ if (
+ keyguardViewController.viewRootImpl.view?.visibility != View.VISIBLE &&
+ sc?.isValid == true
+ ) {
with(SurfaceControl.Transaction()) {
setAlpha(sc, animationAlpha)
apply()
}
} else {
applyParamsToSurface(
- SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(
- wallpaper.leash)
- .withAlpha(animationAlpha)
- .build()
+ SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(wallpaper.leash)
+ .withAlpha(animationAlpha)
+ .build()
)
}
}
@@ -1019,9 +1096,9 @@
}
if (!showKeyguard) {
- // Make sure we made the surface behind fully visible, just in case. It should already be
- // fully visible. The exit animation is finished, and we should not hold the leash anymore,
- // so forcing it to 1f.
+ // Make sure we made the surface behind fully visible, just in case. It should already
+ // be fully visible. The exit animation is finished, and we should not hold the leash
+ // anymore, so forcing it to 1f.
surfaceBehindAlpha = 1f
setSurfaceBehindAppearAmount(1f)
@@ -1061,13 +1138,16 @@
if (!KeyguardWmStateRefactor.isEnabled) {
keyguardViewController.hide(
- surfaceBehindRemoteAnimationStartTime,
- 0 /* fadeOutDuration */
+ surfaceBehindRemoteAnimationStartTime,
+ 0 /* fadeOutDuration */
)
}
} else {
- Log.i(TAG, "#hideKeyguardViewAfterRemoteAnimation called when keyguard view is not " +
- "showing. Ignoring...")
+ Log.i(
+ TAG,
+ "#hideKeyguardViewAfterRemoteAnimation called when keyguard view is not " +
+ "showing. Ignoring..."
+ )
}
}
@@ -1099,7 +1179,8 @@
surfaceBehindAlphaAnimator.reverse()
}
- private fun shouldPerformSmartspaceTransition(): Boolean {
+ /** Note: declared open for ease of testing */
+ open fun shouldPerformSmartspaceTransition(): Boolean {
// Feature is disabled, so we don't want to.
if (!featureFlags.isEnabled(Flags.SMARTSPACE_SHARED_ELEMENT_TRANSITION_ENABLED)) {
return false
@@ -1107,9 +1188,11 @@
// If our controllers are null, or we haven't received a smartspace state from Launcher yet,
// we will not be doing any smartspace transitions today.
- if (launcherUnlockController == null ||
- lockscreenSmartspace == null ||
- launcherSmartspaceState == null) {
+ if (
+ launcherUnlockController == null ||
+ lockscreenSmartspace == null ||
+ launcherSmartspaceState == null
+ ) {
return false
}
@@ -1135,8 +1218,10 @@
// element transition is if we're doing a biometric unlock. Otherwise, it means the bouncer
// is showing, and you can't see the lockscreen smartspace, so a shared element transition
// would not make sense.
- if (!keyguardStateController.canDismissLockScreen() &&
- !biometricUnlockControllerLazy.get().isBiometricUnlock) {
+ if (
+ !keyguardStateController.canDismissLockScreen() &&
+ !biometricUnlockControllerLazy.get().isBiometricUnlock
+ ) {
return false
}
@@ -1175,9 +1260,7 @@
return willUnlockWithSmartspaceTransition
}
- /**
- * Whether the RemoteAnimation on the app/launcher surface behind the keyguard is 'running'.
- */
+ /** Whether the RemoteAnimation on the app/launcher surface behind the keyguard is 'running'. */
fun isAnimatingBetweenKeyguardAndSurfaceBehind(): Boolean {
return keyguardViewMediator.get().isAnimatingBetweenKeyguardAndSurfaceBehind
}
@@ -1196,39 +1279,38 @@
* in-window/shared element transitions!
*/
fun isSupportedLauncherUnderneath(): Boolean {
- return launcherActivityClass?.let { ActivityManagerWrapper.getInstance()
- .runningTask?.topActivity?.className?.equals(it) }
- ?: false
+ return launcherActivityClass?.let {
+ ActivityManagerWrapper.getInstance().runningTask?.topActivity?.className?.equals(it)
+ } ?: false
}
/**
- * Temporary method for b/298186160
- * TODO (b/298186160) replace references with the constant itself when flag is removed
+ * Temporary method for b/298186160 TODO (b/298186160) replace references with the constant
+ * itself when flag is removed
*/
private fun cannedUnlockStartDelayMs(): Long {
return if (fasterUnlockTransition()) CANNED_UNLOCK_START_DELAY
- else LEGACY_CANNED_UNLOCK_START_DELAY
+ else LEGACY_CANNED_UNLOCK_START_DELAY
}
/**
- * Temporary method for b/298186160
- * TODO (b/298186160) replace references with the constant itself when flag is removed
+ * Temporary method for b/298186160 TODO (b/298186160) replace references with the constant
+ * itself when flag is removed
*/
private fun unlockAnimationDurationMs(): Long {
return if (fasterUnlockTransition()) UNLOCK_ANIMATION_DURATION_MS
- else LEGACY_UNLOCK_ANIMATION_DURATION_MS
+ else LEGACY_UNLOCK_ANIMATION_DURATION_MS
}
/**
- * Temporary method for b/298186160
- * TODO (b/298186160) replace references with the constant itself when flag is removed
+ * Temporary method for b/298186160 TODO (b/298186160) replace references with the constant
+ * itself when flag is removed
*/
private fun surfaceBehindFadeOutStartDelayMs(): Long {
return if (fasterUnlockTransition()) UNLOCK_ANIMATION_SURFACE_BEHIND_START_DELAY_MS
- else LEGACY_UNLOCK_ANIMATION_SURFACE_BEHIND_START_DELAY_MS
+ else LEGACY_UNLOCK_ANIMATION_SURFACE_BEHIND_START_DELAY_MS
}
-
companion object {
fun isFoldable(resources: Resources): Boolean {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index e46a7cb..0b3d0f7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -1278,6 +1278,8 @@
initAlphaForAnimationTargets(wallpapers);
if (isDream) {
mDreamViewModel.get().startTransitionFromDream();
+ } else {
+ mCommunalTransitionViewModel.get().snapToCommunal();
}
mUnoccludeFinishedCallback = finishedCallback;
return;
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/DeviceEntryIconView.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/DeviceEntryIconView.kt
index 1c63235..3e6d5da 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/DeviceEntryIconView.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/DeviceEntryIconView.kt
@@ -247,6 +247,7 @@
lp.height = ViewGroup.LayoutParams.MATCH_PARENT
lp.width = ViewGroup.LayoutParams.MATCH_PARENT
bgView.layoutParams = lp
+ bgView.alpha = 0f
}
fun getIconState(icon: IconType, aod: Boolean): IntArray {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
index 630dcca..15892e9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
@@ -25,7 +25,6 @@
import com.android.compose.animation.scene.UserActionResult
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
import com.android.systemui.scene.shared.model.SceneFamilies
import com.android.systemui.scene.shared.model.Scenes
@@ -35,93 +34,70 @@
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
import com.android.systemui.util.kotlin.filterValuesNotNull
import javax.inject.Inject
-import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.flow.stateIn
/** Models UI state and handles user input for the lockscreen scene. */
@SysUISingleton
class LockscreenSceneViewModel
@Inject
constructor(
- @Application applicationScope: CoroutineScope,
- deviceEntryInteractor: DeviceEntryInteractor,
- communalInteractor: CommunalInteractor,
- shadeInteractor: ShadeInteractor,
+ private val deviceEntryInteractor: DeviceEntryInteractor,
+ private val communalInteractor: CommunalInteractor,
+ private val shadeInteractor: ShadeInteractor,
val touchHandling: KeyguardTouchHandlingViewModel,
val notifications: NotificationsPlaceholderViewModel,
) {
- val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
- shadeInteractor.isShadeTouchable
- .flatMapLatest { isShadeTouchable ->
- if (!isShadeTouchable) {
- flowOf(emptyMap())
- } else {
- combine(
- deviceEntryInteractor.isUnlocked,
- communalInteractor.isCommunalAvailable,
- shadeInteractor.shadeMode,
- ) { isDeviceUnlocked, isCommunalAvailable, shadeMode ->
- destinationScenes(
- isDeviceUnlocked = isDeviceUnlocked,
- isCommunalAvailable = isCommunalAvailable,
- shadeMode = shadeMode,
+ val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
+ shadeInteractor.isShadeTouchable.flatMapLatest { isShadeTouchable ->
+ if (!isShadeTouchable) {
+ flowOf(emptyMap())
+ } else {
+ combine(
+ deviceEntryInteractor.isUnlocked,
+ communalInteractor.isCommunalAvailable,
+ shadeInteractor.shadeMode,
+ ) { isDeviceUnlocked, isCommunalAvailable, shadeMode ->
+ val notifShadeSceneKey =
+ UserActionResult(
+ toScene = SceneFamilies.NotifShade,
+ transitionKey = ToSplitShade.takeIf { shadeMode is ShadeMode.Split },
)
- }
+
+ mapOf(
+ Swipe.Left to
+ UserActionResult(Scenes.Communal).takeIf { isCommunalAvailable },
+ Swipe.Up to if (isDeviceUnlocked) Scenes.Gone else Scenes.Bouncer,
+
+ // Swiping down from the top edge goes to QS (or shade if in split shade
+ // mode).
+ swipeDownFromTop(pointerCount = 1) to
+ if (shadeMode is ShadeMode.Single) {
+ UserActionResult(Scenes.QuickSettings)
+ } else {
+ notifShadeSceneKey
+ },
+
+ // TODO(b/338577208): Remove once we add Dual Shade invocation zones.
+ swipeDownFromTop(pointerCount = 2) to
+ UserActionResult(
+ toScene = SceneFamilies.QuickSettings,
+ transitionKey =
+ ToSplitShade.takeIf { shadeMode is ShadeMode.Split }
+ ),
+
+ // Swiping down, not from the edge, always navigates to the notif shade
+ // scene.
+ swipeDown(pointerCount = 1) to notifShadeSceneKey,
+ swipeDown(pointerCount = 2) to notifShadeSceneKey,
+ )
+ .filterValuesNotNull()
}
}
- .stateIn(
- scope = applicationScope,
- started = SharingStarted.WhileSubscribed(),
- initialValue =
- destinationScenes(
- isDeviceUnlocked = deviceEntryInteractor.isUnlocked.value,
- isCommunalAvailable = false,
- shadeMode = shadeInteractor.shadeMode.value,
- ),
- )
-
- private fun destinationScenes(
- isDeviceUnlocked: Boolean,
- isCommunalAvailable: Boolean,
- shadeMode: ShadeMode,
- ): Map<UserAction, UserActionResult> {
- val notifShadeSceneKey =
- UserActionResult(
- toScene = SceneFamilies.NotifShade,
- transitionKey = ToSplitShade.takeIf { shadeMode is ShadeMode.Split },
- )
-
- return mapOf(
- Swipe.Left to UserActionResult(Scenes.Communal).takeIf { isCommunalAvailable },
- Swipe.Up to if (isDeviceUnlocked) Scenes.Gone else Scenes.Bouncer,
-
- // Swiping down from the top edge goes to QS (or shade if in split shade mode).
- swipeDownFromTop(pointerCount = 1) to
- if (shadeMode is ShadeMode.Single) {
- UserActionResult(Scenes.QuickSettings)
- } else {
- notifShadeSceneKey
- },
-
- // TODO(b/338577208): Remove once we add Dual Shade invocation zones.
- swipeDownFromTop(pointerCount = 2) to
- UserActionResult(
- toScene = SceneFamilies.QuickSettings,
- transitionKey = ToSplitShade.takeIf { shadeMode is ShadeMode.Split }
- ),
-
- // Swiping down, not from the edge, always navigates to the notif shade scene.
- swipeDown(pointerCount = 1) to notifShadeSceneKey,
- swipeDown(pointerCount = 2) to notifShadeSceneKey,
- )
- .filterValuesNotNull()
- }
+ }
private fun swipeDownFromTop(pointerCount: Int): Swipe {
return Swipe(
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
index 6c53374..cc4a92c 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
@@ -187,70 +187,40 @@
}
}
- CharSequence dialogText = null;
- CharSequence dialogTitle = null;
-
final String appName = extractAppName(aInfo, packageManager);
final boolean hasCastingCapabilities =
Utils.isHeadlessRemoteDisplayProvider(packageManager, mPackageName);
- if (hasCastingCapabilities) {
- dialogText = getString(R.string.media_projection_sys_service_dialog_warning);
- dialogTitle = getString(R.string.media_projection_sys_service_dialog_title);
- } else {
- String actionText = getString(R.string.media_projection_dialog_warning, appName);
- SpannableString message = new SpannableString(actionText);
-
- int appNameIndex = actionText.indexOf(appName);
- if (appNameIndex >= 0) {
- message.setSpan(new StyleSpan(Typeface.BOLD),
- appNameIndex, appNameIndex + appName.length(), 0);
- }
- dialogText = message;
- dialogTitle = getString(R.string.media_projection_dialog_title, appName);
- }
-
// Using application context for the dialog, instead of the activity context, so we get
// the correct screen width when in split screen.
Context dialogContext = getApplicationContext();
- if (isPartialScreenSharingEnabled()) {
- final boolean overrideDisableSingleAppOption =
- CompatChanges.isChangeEnabled(
- OVERRIDE_DISABLE_MEDIA_PROJECTION_SINGLE_APP_OPTION,
- mPackageName, getHostUserHandle());
- MediaProjectionPermissionDialogDelegate delegate =
- new MediaProjectionPermissionDialogDelegate(
- dialogContext,
- getMediaProjectionConfig(),
- dialog -> {
- ScreenShareOption selectedOption =
- dialog.getSelectedScreenShareOption();
- grantMediaProjectionPermission(selectedOption.getMode());
- },
- () -> finish(RECORD_CANCEL, /* projection= */ null),
- hasCastingCapabilities,
- appName,
- overrideDisableSingleAppOption,
- mUid,
- mMediaProjectionMetricsLogger);
- mDialog =
- new AlertDialogWithDelegate(
- dialogContext, R.style.Theme_SystemUI_Dialog, delegate);
- } else {
- AlertDialog.Builder dialogBuilder =
- new AlertDialog.Builder(dialogContext, R.style.Theme_SystemUI_Dialog)
- .setTitle(dialogTitle)
- .setIcon(R.drawable.ic_media_projection_permission)
- .setMessage(dialogText)
- .setPositiveButton(R.string.media_projection_action_text, this)
- .setNeutralButton(android.R.string.cancel, this);
- mDialog = dialogBuilder.create();
- }
+ final boolean overrideDisableSingleAppOption =
+ CompatChanges.isChangeEnabled(
+ OVERRIDE_DISABLE_MEDIA_PROJECTION_SINGLE_APP_OPTION,
+ mPackageName, getHostUserHandle());
+ MediaProjectionPermissionDialogDelegate delegate =
+ new MediaProjectionPermissionDialogDelegate(
+ dialogContext,
+ getMediaProjectionConfig(),
+ dialog -> {
+ ScreenShareOption selectedOption =
+ dialog.getSelectedScreenShareOption();
+ grantMediaProjectionPermission(selectedOption.getMode());
+ },
+ () -> finish(RECORD_CANCEL, /* projection= */ null),
+ hasCastingCapabilities,
+ appName,
+ overrideDisableSingleAppOption,
+ mUid,
+ mMediaProjectionMetricsLogger);
+ mDialog =
+ new AlertDialogWithDelegate(
+ dialogContext, R.style.Theme_SystemUI_Dialog, delegate);
if (savedInstanceState == null) {
mMediaProjectionMetricsLogger.notifyProjectionInitiated(
mUid,
- appName == null
+ hasCastingCapabilities
? SessionCreationSource.CAST
: SessionCreationSource.APP);
}
@@ -366,7 +336,7 @@
setResult(RESULT_OK, intent);
finish(RECORD_CONTENT_DISPLAY, projection);
}
- if (isPartialScreenSharingEnabled() && screenShareMode == SINGLE_APP) {
+ if (screenShareMode == SINGLE_APP) {
IMediaProjection projection = MediaProjectionServiceHelper.createOrReuseProjection(
mUid, mPackageName, mReviewGrantedConsentRequired);
final Intent intent = new Intent(this,
@@ -437,8 +407,4 @@
return intent.getParcelableExtra(
MediaProjectionManager.EXTRA_MEDIA_PROJECTION_CONFIG);
}
-
- private boolean isPartialScreenSharingEnabled() {
- return mFeatureFlags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING);
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneViewModel.kt
index 1f74716..fa8e13a 100644
--- a/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneViewModel.kt
@@ -25,9 +25,8 @@
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.shade.shared.model.ShadeAlignment
import javax.inject.Inject
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
/** Models UI state and handles user input for the Notifications Shade scene. */
@SysUISingleton
@@ -36,16 +35,15 @@
constructor(
shadeInteractor: ShadeInteractor,
) {
- val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
- MutableStateFlow(
- mapOf(
- if (shadeInteractor.shadeAlignment == ShadeAlignment.Top) {
- Swipe.Up
- } else {
- Swipe.Down
- } to SceneFamilies.Home,
- Back to SceneFamilies.Home,
- )
+ val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
+ flowOf(
+ mapOf(
+ if (shadeInteractor.shadeAlignment == ShadeAlignment.Top) {
+ Swipe.Up
+ } else {
+ Swipe.Down
+ } to SceneFamilies.Home,
+ Back to SceneFamilies.Home,
)
- .asStateFlow()
+ )
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractor.kt
index 083bf05..4c6563d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractor.kt
@@ -16,10 +16,14 @@
package com.android.systemui.qs.tiles.impl.modes.domain.interactor
+//noinspection CleanArchitectureDependencyViolation: dialog needs to be opened on click
import android.content.Intent
import android.provider.Settings
+import com.android.internal.jank.InteractionJankMonitor
+import com.android.systemui.animation.DialogCuj
+import com.android.systemui.animation.DialogTransitionAnimator
import com.android.systemui.animation.Expandable
-import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandler
import com.android.systemui.qs.tiles.base.interactor.QSTileInput
import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor
@@ -27,13 +31,15 @@
import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
import com.android.systemui.statusbar.policy.ui.dialog.ModesDialogDelegate
import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.withContext
-@SysUISingleton
class ModesTileUserActionInteractor
@Inject
constructor(
- private val qsTileIntentUserInputHandler: QSTileIntentUserInputHandler,
- // TODO(b/353896370): The domain layer should not have to depend on the UI layer.
+ @Main private val coroutineContext: CoroutineContext,
+ private val qsTileIntentUserActionHandler: QSTileIntentUserInputHandler,
+ private val dialogTransitionAnimator: DialogTransitionAnimator,
private val dialogDelegate: ModesDialogDelegate,
) : QSTileUserActionInteractor<ModesTileModel> {
val longClickIntent = Intent(Settings.ACTION_ZEN_MODE_SETTINGS)
@@ -45,14 +51,29 @@
handleClick(action.expandable)
}
is QSTileUserAction.LongClick -> {
- qsTileIntentUserInputHandler.handle(action.expandable, longClickIntent)
+ qsTileIntentUserActionHandler.handle(action.expandable, longClickIntent)
}
}
}
}
suspend fun handleClick(expandable: Expandable?) {
- // Show a dialog with the list of modes to configure.
- dialogDelegate.showDialog(expandable)
+ // Show a dialog with the list of modes to configure. Dialogs shown by the
+ // DialogTransitionAnimator must be created and shown on the main thread, so we post it to
+ // the UI handler.
+ withContext(coroutineContext) {
+ val dialog = dialogDelegate.createDialog()
+
+ expandable
+ ?.dialogTransitionController(
+ DialogCuj(InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN, INTERACTION_JANK_TAG)
+ )
+ ?.let { controller -> dialogTransitionAnimator.show(dialog, controller) }
+ ?: dialog.show()
+ }
+ }
+
+ companion object {
+ private const val INTERACTION_JANK_TAG = "configure_priority_modes"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt
index 79cdfec..b1cc55d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt
@@ -25,7 +25,6 @@
import com.android.compose.animation.scene.UserAction
import com.android.compose.animation.scene.UserActionResult
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.media.controls.domain.pipeline.interactor.MediaCarouselInteractor
import com.android.systemui.qs.FooterActionsController
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
@@ -38,20 +37,17 @@
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
import java.util.concurrent.atomic.AtomicBoolean
import javax.inject.Inject
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.stateIn
/** Models UI state and handles user input for the quick settings scene. */
@SysUISingleton
class QuickSettingsSceneViewModel
@Inject
constructor(
- @Application private val applicationScope: CoroutineScope,
val brightnessMirrorViewModel: BrightnessMirrorViewModel,
val shadeHeaderViewModel: ShadeHeaderViewModel,
val qsSceneAdapter: QSSceneAdapter,
@@ -61,55 +57,35 @@
sceneBackInteractor: SceneBackInteractor,
val mediaCarouselInteractor: MediaCarouselInteractor,
) {
- private val backScene: StateFlow<SceneKey> =
+ private val backScene: Flow<SceneKey> =
sceneBackInteractor.backScene
.filter { it != Scenes.QuickSettings }
.map { it ?: Scenes.Shade }
- .stateIn(
- scope = applicationScope,
- started = SharingStarted.WhileSubscribed(),
- initialValue = Scenes.Shade,
- )
- val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
+ val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
combine(
- qsSceneAdapter.isCustomizerShowing,
- backScene,
- transform = ::destinationScenes,
- )
- .stateIn(
- scope = applicationScope,
- started = SharingStarted.WhileSubscribed(),
- initialValue =
- destinationScenes(
- isCustomizing = qsSceneAdapter.isCustomizerShowing.value,
- backScene = backScene.value,
- ),
- )
+ qsSceneAdapter.isCustomizerShowing,
+ backScene,
+ ) { isCustomizing, backScene ->
+ buildMap<UserAction, UserActionResult> {
+ if (isCustomizing) {
+ // TODO(b/332749288) Empty map so there are no back handlers and back can close
+ // customizer
- val isMediaVisible: StateFlow<Boolean> = mediaCarouselInteractor.hasAnyMediaOrRecommendation
-
- private fun destinationScenes(
- isCustomizing: Boolean,
- backScene: SceneKey?,
- ): Map<UserAction, UserActionResult> {
- return buildMap {
- if (isCustomizing) {
- // TODO(b/332749288) Empty map so there are no back handlers and back can close
- // customizer
-
- // TODO(b/330200163) Add an Up from Bottom to be able to collapse the shade
- // while customizing
- } else {
- put(Back, UserActionResult(backScene ?: Scenes.Shade))
- put(Swipe(SwipeDirection.Up), UserActionResult(backScene ?: Scenes.Shade))
- put(
- Swipe(fromSource = Edge.Bottom, direction = SwipeDirection.Up),
- UserActionResult(SceneFamilies.Home),
- )
+ // TODO(b/330200163) Add an Up from Bottom to be able to collapse the shade
+ // while customizing
+ } else {
+ put(Back, UserActionResult(backScene))
+ put(Swipe(SwipeDirection.Up), UserActionResult(backScene))
+ put(
+ Swipe(fromSource = Edge.Bottom, direction = SwipeDirection.Up),
+ UserActionResult(SceneFamilies.Home),
+ )
+ }
}
}
- }
+
+ val isMediaVisible: StateFlow<Boolean> = mediaCarouselInteractor.hasAnyMediaOrRecommendation
private val footerActionsControllerInitialized = AtomicBoolean(false)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModel.kt
index 66fcbac..e012f2c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModel.kt
@@ -21,17 +21,13 @@
import com.android.compose.animation.scene.UserAction
import com.android.compose.animation.scene.UserActionResult
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.scene.shared.model.SceneFamilies
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.shade.shared.model.ShadeAlignment
import com.android.systemui.shade.ui.viewmodel.OverlayShadeViewModel
import javax.inject.Inject
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.stateIn
/** Models UI state and handles user input for the Quick Settings Shade scene. */
@SysUISingleton
@@ -41,33 +37,22 @@
private val shadeInteractor: ShadeInteractor,
val overlayShadeViewModel: OverlayShadeViewModel,
val quickSettingsContainerViewModel: QuickSettingsContainerViewModel,
- @Application applicationScope: CoroutineScope,
) {
- val isEditing: StateFlow<Boolean> = quickSettingsContainerViewModel.editModeViewModel.isEditing
-
- val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
- isEditing
- .map { editing -> destinations(editing) }
- .stateIn(
- applicationScope,
- SharingStarted.WhileSubscribed(),
- destinations(isEditing.value)
- )
-
- private fun destinations(editing: Boolean): Map<UserAction, UserActionResult> {
- return buildMap {
- put(
- if (shadeInteractor.shadeAlignment == ShadeAlignment.Top) {
- Swipe.Up
- } else {
- Swipe.Down
- },
- UserActionResult(SceneFamilies.Home)
- )
- if (!editing) {
- put(Back, UserActionResult(SceneFamilies.Home))
+ val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
+ quickSettingsContainerViewModel.editModeViewModel.isEditing.map { editing ->
+ buildMap {
+ put(
+ if (shadeInteractor.shadeAlignment == ShadeAlignment.Top) {
+ Swipe.Up
+ } else {
+ Swipe.Down
+ },
+ UserActionResult(SceneFamilies.Home)
+ )
+ if (!editing) {
+ put(Back, UserActionResult(SceneFamilies.Home))
+ }
}
}
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
index dd2dbf3..f8b3ce1 100644
--- a/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
@@ -35,7 +35,6 @@
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.flags.FeatureFlagsClassic
-import com.android.systemui.flags.Flags
import com.android.systemui.flags.Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES
import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger
import com.android.systemui.mediaprojection.SessionCreationSource
@@ -154,10 +153,7 @@
SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER
)
- if (
- flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING) &&
- !state.hasUserApprovedScreenRecording
- ) {
+ if (!state.hasUserApprovedScreenRecording) {
mainExecutor.execute {
ScreenCapturePermissionDialogDelegate(factory, state).createDialog().apply {
setOnCancelListener { screenRecordSwitch.isChecked = false }
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/Scene.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/Scene.kt
index 939d5bc..c7190c3 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/model/Scene.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/Scene.kt
@@ -19,7 +19,8 @@
import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.UserAction
import com.android.compose.animation.scene.UserActionResult
-import kotlinx.coroutines.flow.StateFlow
+import com.android.systemui.activatable.Activatable
+import kotlinx.coroutines.flow.Flow
/**
* Defines interface for classes that can describe a "scene".
@@ -29,11 +30,13 @@
* based on either user action (for example, swiping down while on the lock screen scene may switch
* to the shade scene).
*/
-interface Scene {
+interface Scene : Activatable {
/** Uniquely-identifying key for this scene. The key must be unique within its container. */
val key: SceneKey
+ override suspend fun activate() = Unit
+
/**
* The mapping between [UserAction] and destination [UserActionResult]s.
*
@@ -54,5 +57,5 @@
* type is not currently active in the scene and should be ignored by the framework, while the
* current scene is this one.
*/
- val destinationScenes: StateFlow<Map<UserAction, UserActionResult>>
+ val destinationScenes: Flow<Map<UserAction, UserActionResult>>
}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt
index c20d577..d31d6f4 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt
@@ -103,6 +103,7 @@
windowInsets = windowInsets,
sceneByKey = sortedSceneByKey,
dataSourceDelegator = dataSourceDelegator,
+ containerConfig = containerConfig,
)
.also { it.id = R.id.scene_container_root_composable }
)
@@ -141,6 +142,7 @@
windowInsets: StateFlow<WindowInsets?>,
sceneByKey: Map<SceneKey, Scene>,
dataSourceDelegator: SceneDataSourceDelegator,
+ containerConfig: SceneContainerConfig,
): View {
return ComposeView(context).apply {
setContent {
@@ -153,6 +155,7 @@
viewModel = viewModel,
sceneByKey =
sceneByKey.mapValues { (_, scene) -> scene as ComposableScene },
+ initialSceneKey = containerConfig.initialSceneKey,
dataSourceDelegator = dataSourceDelegator,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModel.kt
index 9f48ee9..b739ffe 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModel.kt
@@ -23,79 +23,61 @@
import com.android.compose.animation.scene.UserAction
import com.android.compose.animation.scene.UserActionResult
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.scene.shared.model.SceneFamilies
import com.android.systemui.scene.shared.model.TransitionKeys.OpenBottomShade
import com.android.systemui.scene.shared.model.TransitionKeys.ToSplitShade
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.shade.shared.model.ShadeMode
import javax.inject.Inject
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.stateIn
@SysUISingleton
class GoneSceneViewModel
@Inject
constructor(
- @Application private val applicationScope: CoroutineScope,
private val shadeInteractor: ShadeInteractor,
) {
- val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
- shadeInteractor.shadeMode
- .map(::destinationScenes)
- .stateIn(
- scope = applicationScope,
- started = SharingStarted.WhileSubscribed(),
- initialValue =
- destinationScenes(
- shadeMode = shadeInteractor.shadeMode.value,
- )
- )
+ val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
+ shadeInteractor.shadeMode.map { shadeMode ->
+ buildMap {
+ if (
+ shadeMode is ShadeMode.Single ||
+ // TODO(b/338577208): Remove this once we add Dual Shade invocation zones.
+ shadeMode is ShadeMode.Dual
+ ) {
+ if (shadeInteractor.shadeAlignment == Alignment.BottomEnd) {
+ put(
+ Swipe(
+ pointerCount = 2,
+ fromSource = Edge.Bottom,
+ direction = SwipeDirection.Up,
+ ),
+ UserActionResult(SceneFamilies.QuickSettings, OpenBottomShade)
+ )
+ } else {
+ put(
+ Swipe(
+ pointerCount = 2,
+ fromSource = Edge.Top,
+ direction = SwipeDirection.Down,
+ ),
+ UserActionResult(SceneFamilies.QuickSettings)
+ )
+ }
+ }
- private fun destinationScenes(
- shadeMode: ShadeMode,
- ): Map<UserAction, UserActionResult> {
- return buildMap {
- if (
- shadeMode is ShadeMode.Single ||
- // TODO(b/338577208): Remove this once we add Dual Shade invocation zones.
- shadeMode is ShadeMode.Dual
- ) {
if (shadeInteractor.shadeAlignment == Alignment.BottomEnd) {
- put(
- Swipe(
- pointerCount = 2,
- fromSource = Edge.Bottom,
- direction = SwipeDirection.Up,
- ),
- UserActionResult(SceneFamilies.QuickSettings, OpenBottomShade)
- )
+ put(Swipe.Up, UserActionResult(SceneFamilies.NotifShade, OpenBottomShade))
} else {
put(
- Swipe(
- pointerCount = 2,
- fromSource = Edge.Top,
- direction = SwipeDirection.Down,
- ),
- UserActionResult(SceneFamilies.QuickSettings)
+ Swipe.Down,
+ UserActionResult(
+ SceneFamilies.NotifShade,
+ ToSplitShade.takeIf { shadeMode is ShadeMode.Split }
+ )
)
}
}
-
- if (shadeInteractor.shadeAlignment == Alignment.BottomEnd) {
- put(Swipe.Up, UserActionResult(SceneFamilies.NotifShade, OpenBottomShade))
- } else {
- put(
- Swipe.Down,
- UserActionResult(
- SceneFamilies.NotifShade,
- ToSplitShade.takeIf { shadeMode is ShadeMode.Split }
- )
- )
- }
}
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
index 46c5861..a8a78a9 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
@@ -171,11 +171,8 @@
mMediaProjectionMetricsLogger.notifyProjectionInitiated(
getHostUid(), SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER);
- return (flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)
- ? mScreenRecordPermissionDialogDelegateFactory
- .create(this, getHostUserHandle(), getHostUid(), onStartRecordingClicked)
- : mScreenRecordDialogFactory
- .create(this, onStartRecordingClicked))
+ return mScreenRecordPermissionDialogDelegateFactory
+ .create(this, getHostUserHandle(), getHostUid(), onStartRecordingClicked)
.createDialog();
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
index b468d0e..25d1cd1 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
@@ -39,6 +39,7 @@
import androidx.lifecycle.repeatOnLifecycle
import com.android.compose.theme.PlatformTheme
import com.android.internal.annotations.VisibleForTesting
+import com.android.systemui.Flags
import com.android.systemui.Flags.glanceableHubBackGesture
import com.android.systemui.ambient.touch.TouchMonitor
import com.android.systemui.ambient.touch.dagger.AmbientTouchComponent
@@ -294,24 +295,37 @@
}
containerView.systemGestureExclusionRects =
- listOf(
- // Only allow swipe up to bouncer and swipe down to shade in the very
- // top/bottom to avoid conflicting with widgets in the hub grid.
- Rect(
- insets.left,
- topEdgeSwipeRegionWidth,
- containerView.right - insets.right,
- containerView.bottom - bottomEdgeSwipeRegionWidth
- ),
- // Disable back gestures on the left side of the screen, to avoid
- // conflicting with scene transitions.
- Rect(
- 0,
- 0,
- insets.right,
- containerView.bottom,
+ if (Flags.hubmodeFullscreenVerticalSwipe()) {
+ listOf(
+ // Disable back gestures on the left side of the screen, to avoid
+ // conflicting with scene transitions.
+ Rect(
+ 0,
+ 0,
+ insets.right,
+ containerView.bottom,
+ )
)
- )
+ } else {
+ listOf(
+ // Only allow swipe up to bouncer and swipe down to shade in the very
+ // top/bottom to avoid conflicting with widgets in the hub grid.
+ Rect(
+ insets.left,
+ topEdgeSwipeRegionWidth,
+ containerView.right - insets.right,
+ containerView.bottom - bottomEdgeSwipeRegionWidth
+ ),
+ // Disable back gestures on the left side of the screen, to avoid
+ // conflicting with scene transitions.
+ Rect(
+ 0,
+ 0,
+ insets.right,
+ containerView.bottom,
+ )
+ )
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
index 2b2aac64..f90dd3c 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
@@ -24,8 +24,8 @@
import com.android.compose.animation.scene.SwipeDirection
import com.android.compose.animation.scene.UserAction
import com.android.compose.animation.scene.UserActionResult
+import com.android.systemui.activatable.Activatable
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.media.controls.domain.pipeline.interactor.MediaCarouselInteractor
import com.android.systemui.qs.FooterActionsController
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
@@ -41,22 +41,23 @@
import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated
import java.util.concurrent.atomic.AtomicBoolean
import javax.inject.Inject
-import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
/** Models UI state and handles user input for the shade scene. */
@SysUISingleton
class ShadeSceneViewModel
@Inject
constructor(
- @Application private val applicationScope: CoroutineScope,
val qsSceneAdapter: QSSceneAdapter,
val shadeHeaderViewModel: ShadeHeaderViewModel,
val brightnessMirrorViewModel: BrightnessMirrorViewModel,
@@ -66,42 +67,61 @@
private val footerActionsController: FooterActionsController,
private val sceneInteractor: SceneInteractor,
private val unfoldTransitionInteractor: UnfoldTransitionInteractor,
-) {
- val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
+) : Activatable {
+ val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
combine(
- shadeInteractor.shadeMode,
- qsSceneAdapter.isCustomizerShowing,
- ) { shadeMode, isCustomizerShowing ->
- destinationScenes(
- shadeMode = shadeMode,
- isCustomizing = isCustomizerShowing,
- )
+ shadeInteractor.shadeMode,
+ qsSceneAdapter.isCustomizerShowing,
+ ) { shadeMode, isCustomizerShowing ->
+ buildMap {
+ if (!isCustomizerShowing) {
+ set(
+ Swipe(SwipeDirection.Up),
+ UserActionResult(
+ SceneFamilies.Home,
+ ToSplitShade.takeIf { shadeMode is ShadeMode.Split }
+ )
+ )
+ }
+
+ // TODO(b/330200163) Add an else to be able to collapse the shade while customizing
+ if (shadeMode is ShadeMode.Single) {
+ set(Swipe(SwipeDirection.Down), UserActionResult(Scenes.QuickSettings))
+ }
}
- .stateIn(
- scope = applicationScope,
- started = SharingStarted.WhileSubscribed(),
- initialValue =
- destinationScenes(
- shadeMode = shadeInteractor.shadeMode.value,
- isCustomizing = qsSceneAdapter.isCustomizerShowing.value,
- ),
- )
+ }
private val upDestinationSceneKey: Flow<SceneKey?> =
destinationScenes.map { it[Swipe(SwipeDirection.Up)]?.toScene }
+ private val _isClickable = MutableStateFlow(false)
/** Whether or not the shade container should be clickable. */
- val isClickable: StateFlow<Boolean> =
- upDestinationSceneKey
- .flatMapLatestConflated { key ->
- key?.let { sceneInteractor.resolveSceneFamily(key) } ?: flowOf(null)
+ val isClickable: StateFlow<Boolean> = _isClickable.asStateFlow()
+
+ /**
+ * Activates the view-model.
+ *
+ * Serves as an entrypoint to kick off coroutine work that the view-model requires in order to
+ * keep its state fresh and/or perform side-effects.
+ *
+ * Suspends the caller forever as it will keep doing work until canceled.
+ *
+ * **Must be invoked** when the scene becomes the current scene or when it becomes visible
+ * during a transition (the choice is the responsibility of the parent). Similarly, the work
+ * must be canceled when the scene stops being visible or the current scene.
+ */
+ override suspend fun activate() {
+ coroutineScope {
+ launch {
+ upDestinationSceneKey
+ .flatMapLatestConflated { key ->
+ key?.let { sceneInteractor.resolveSceneFamily(key) } ?: flowOf(null)
+ }
+ .map { it == Scenes.Lockscreen }
+ .collectLatest { _isClickable.value = it }
}
- .map { it == Scenes.Lockscreen }
- .stateIn(
- scope = applicationScope,
- started = SharingStarted.WhileSubscribed(),
- initialValue = false
- )
+ }
+ }
val shadeMode: StateFlow<ShadeMode> = shadeInteractor.shadeMode
@@ -132,24 +152,4 @@
}
return footerActionsViewModelFactory.create(lifecycleOwner)
}
-
- private fun destinationScenes(
- shadeMode: ShadeMode,
- isCustomizing: Boolean,
- ): Map<UserAction, UserActionResult> {
- return buildMap {
- if (!isCustomizing) {
- set(
- Swipe(SwipeDirection.Up),
- UserActionResult(
- SceneFamilies.Home,
- ToSplitShade.takeIf { shadeMode is ShadeMode.Split }
- )
- )
- } // TODO(b/330200163) Add an else to be able to collapse the shade while customizing
- if (shadeMode is ShadeMode.Single) {
- set(Swipe(SwipeDirection.Down), UserActionResult(Scenes.QuickSettings))
- }
- }
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinator.kt
index a6605f6..a621b2a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinator.kt
@@ -18,12 +18,9 @@
import android.annotation.SuppressLint
import android.app.NotificationManager
-import android.os.UserHandle
-import android.provider.Settings
import androidx.annotation.VisibleForTesting
import com.android.systemui.Dumpable
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.shade.domain.interactor.ShadeInteractor
@@ -43,23 +40,16 @@
import com.android.systemui.statusbar.notification.stack.BUCKET_TOP_UNSEEN
import com.android.systemui.util.asIndenting
import com.android.systemui.util.printCollection
-import com.android.systemui.util.settings.SecureSettings
-import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
import java.io.PrintWriter
import javax.inject.Inject
import kotlin.time.Duration.Companion.seconds
-import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.collectLatest
-import kotlinx.coroutines.flow.conflate
-import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.launch
/**
@@ -73,12 +63,10 @@
class LockScreenMinimalismCoordinator
@Inject
constructor(
- @Background private val bgDispatcher: CoroutineDispatcher,
private val dumpManager: DumpManager,
private val headsUpInteractor: HeadsUpNotificationInteractor,
private val logger: LockScreenMinimalismCoordinatorLogger,
@Application private val scope: CoroutineScope,
- private val secureSettings: SecureSettings,
private val seenNotificationsInteractor: SeenNotificationsInteractor,
private val statusBarStateController: StatusBarStateController,
private val shadeInteractor: ShadeInteractor,
@@ -147,29 +135,7 @@
if (NotificationMinimalismPrototype.isEnabled) {
return flowOf(true)
}
- return secureSettings
- // emit whenever the setting has changed
- .observerFlow(
- UserHandle.USER_ALL,
- Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
- )
- // perform a query immediately
- .onStart { emit(Unit) }
- // for each change, lookup the new value
- .map {
- secureSettings.getIntForUser(
- name = Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
- def = 0,
- userHandle = UserHandle.USER_CURRENT,
- ) == 1
- }
- // don't emit anything if nothing has changed
- .distinctUntilChanged()
- // perform lookups on the bg thread pool
- .flowOn(bgDispatcher)
- // only track the most recent emission, if events are happening faster than they can be
- // consumed
- .conflate()
+ return seenNotificationsInteractor.isLockScreenShowOnlyUnseenNotificationsEnabled()
}
private suspend fun trackUnseenFilterSettingChanges() {
@@ -177,6 +143,7 @@
// update local field and invalidate if necessary
if (isSettingEnabled != unseenFilterEnabled) {
unseenFilterEnabled = isSettingEnabled
+ unseenNotifications.clear()
unseenNotifPromoter.invalidateList("unseen setting changed")
}
// if the setting is enabled, then start tracking and filtering unseen notifications
@@ -190,21 +157,21 @@
private val collectionListener =
object : NotifCollectionListener {
override fun onEntryAdded(entry: NotificationEntry) {
- if (!isShadeVisible) {
+ if (unseenFilterEnabled && !isShadeVisible) {
logger.logUnseenAdded(entry.key)
unseenNotifications.add(entry)
}
}
override fun onEntryUpdated(entry: NotificationEntry) {
- if (!isShadeVisible) {
+ if (unseenFilterEnabled && !isShadeVisible) {
logger.logUnseenUpdated(entry.key)
unseenNotifications.add(entry)
}
}
override fun onEntryRemoved(entry: NotificationEntry, reason: Int) {
- if (unseenNotifications.remove(entry)) {
+ if (unseenFilterEnabled && unseenNotifications.remove(entry)) {
logger.logUnseenRemoved(entry.key)
}
}
@@ -212,6 +179,7 @@
private fun pickOutTopUnseenNotifs(list: List<ListEntry>) {
if (NotificationMinimalismPrototype.isUnexpectedlyInLegacyMode()) return
+ if (!unseenFilterEnabled) return
// Only ever elevate a top unseen notification on keyguard, not even locked shade
if (statusBarStateController.state != StatusBarState.KEYGUARD) {
seenNotificationsInteractor.setTopOngoingNotification(null)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinator.kt
index 0103fff..bfea2ba 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinator.kt
@@ -17,12 +17,9 @@
package com.android.systemui.statusbar.notification.collection.coordinator
import android.annotation.SuppressLint
-import android.os.UserHandle
-import android.provider.Settings
import androidx.annotation.VisibleForTesting
import com.android.systemui.Dumpable
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dump.DumpManager
import com.android.systemui.keyguard.data.repository.KeyguardRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
@@ -43,12 +40,9 @@
import com.android.systemui.statusbar.policy.headsUpEvents
import com.android.systemui.util.asIndenting
import com.android.systemui.util.indentIfPossible
-import com.android.systemui.util.settings.SecureSettings
-import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
import java.io.PrintWriter
import javax.inject.Inject
import kotlin.time.Duration.Companion.seconds
-import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.coroutineScope
@@ -56,13 +50,10 @@
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.collectLatest
-import kotlinx.coroutines.flow.conflate
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.launch
import kotlinx.coroutines.yield
@@ -79,14 +70,12 @@
class OriginalUnseenKeyguardCoordinator
@Inject
constructor(
- @Background private val bgDispatcher: CoroutineDispatcher,
private val dumpManager: DumpManager,
private val headsUpManager: HeadsUpManager,
private val keyguardRepository: KeyguardRepository,
private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
private val logger: KeyguardCoordinatorLogger,
@Application private val scope: CoroutineScope,
- private val secureSettings: SecureSettings,
private val seenNotificationsInteractor: SeenNotificationsInteractor,
private val statusBarStateController: StatusBarStateController,
private val sceneInteractor: SceneInteractor,
@@ -268,29 +257,7 @@
// TODO(b/330387368): should this really just be turned off? If so, hide the setting.
return flowOf(false)
}
- return secureSettings
- // emit whenever the setting has changed
- .observerFlow(
- UserHandle.USER_ALL,
- Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
- )
- // perform a query immediately
- .onStart { emit(Unit) }
- // for each change, lookup the new value
- .map {
- secureSettings.getIntForUser(
- name = Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
- def = 0,
- userHandle = UserHandle.USER_CURRENT,
- ) == 1
- }
- // don't emit anything if nothing has changed
- .distinctUntilChanged()
- // perform lookups on the bg thread pool
- .flowOn(bgDispatcher)
- // only track the most recent emission, if events are happening faster than they can be
- // consumed
- .conflate()
+ return seenNotificationsInteractor.isLockScreenShowOnlyUnseenNotificationsEnabled()
}
private suspend fun trackUnseenFilterSettingChanges() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractor.kt
index 948a3c2..90a05ef 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractor.kt
@@ -16,21 +16,35 @@
package com.android.systemui.statusbar.notification.domain.interactor
+import android.os.UserHandle
+import android.provider.Settings
import android.util.IndentingPrintWriter
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
import com.android.systemui.statusbar.notification.shared.NotificationMinimalismPrototype
import com.android.systemui.util.printSection
+import com.android.systemui.util.settings.SecureSettings
+import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.conflate
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
/** Interactor for business logic associated with the notification stack. */
@SysUISingleton
class SeenNotificationsInteractor
@Inject
constructor(
+ @Background private val bgDispatcher: CoroutineDispatcher,
private val notificationListRepository: ActiveNotificationListRepository,
+ private val secureSettings: SecureSettings,
) {
/** Are any already-seen notifications currently filtered out of the shade? */
val hasFilteredOutSeenNotifications: StateFlow<Boolean> =
@@ -81,4 +95,29 @@
)
}
}
+
+ fun isLockScreenShowOnlyUnseenNotificationsEnabled(): Flow<Boolean> =
+ secureSettings
+ // emit whenever the setting has changed
+ .observerFlow(
+ UserHandle.USER_ALL,
+ Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
+ )
+ // perform a query immediately
+ .onStart { emit(Unit) }
+ // for each change, lookup the new value
+ .map {
+ secureSettings.getIntForUser(
+ name = Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
+ def = 0,
+ userHandle = UserHandle.USER_CURRENT,
+ ) == 1
+ }
+ // don't emit anything if nothing has changed
+ .distinctUntilChanged()
+ // perform lookups on the bg thread pool
+ .flowOn(bgDispatcher)
+ // only track the most recent emission, if events are happening faster than they can be
+ // consumed
+ .conflate()
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
index e04e0fa..9d09595 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
@@ -389,6 +389,7 @@
.setTicker(titleStr)
.setContentTitle(titleStr)
.setContentText(textStr)
+ .setStyle(Notification.BigTextStyle().bigText(textStr))
.setSmallIcon(com.android.systemui.res.R.drawable.ic_settings)
.setCategory(Notification.CATEGORY_SYSTEM)
.setTimeoutAfter(/* one day in ms */ 24 * 60 * 60 * 1000L)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 8f2ad40..41b69a7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -802,7 +802,10 @@
hideAlternateBouncer(false);
if (mKeyguardStateController.isShowing() && !isBouncerShowing()) {
if (SceneContainerFlag.isEnabled()) {
- mDeviceEntryInteractorLazy.get().attemptDeviceEntry();
+ mSceneInteractorLazy.get().changeScene(
+ Scenes.Bouncer,
+ "primary bouncer requested"
+ );
} else {
mPrimaryBouncerInteractor.show(scrimmed);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
index a88c6d7..a963826 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
@@ -38,9 +38,10 @@
class AvalancheController
@Inject
constructor(
- dumpManager: DumpManager,
- private val uiEventLogger: UiEventLogger,
- @Background private val bgHandler: Handler
+ dumpManager: DumpManager,
+ private val uiEventLogger: UiEventLogger,
+ private val headsUpManagerLogger: HeadsUpManagerLogger,
+ @Background private val bgHandler: Handler
) : Dumpable {
private val tag = "AvalancheController"
@@ -109,32 +110,36 @@
}
/** Run or delay Runnable for given HeadsUpEntry */
- fun update(entry: HeadsUpEntry?, runnable: Runnable?, label: String) {
+ fun update(entry: HeadsUpEntry?, runnable: Runnable?, caller: String) {
+ val isEnabled = isEnabled()
+ val key = getKey(entry)
+
if (runnable == null) {
- log { "Runnable is NULL, stop update." }
+ headsUpManagerLogger.logAvalancheUpdate(caller, isEnabled, key, "Runnable NULL, stop")
return
}
- if (!isEnabled()) {
+ if (!isEnabled) {
+ headsUpManagerLogger.logAvalancheUpdate(caller, isEnabled, key,
+ "NOT ENABLED, run runnable")
runnable.run()
return
}
- log { "\n " }
- val fn = "$label => AvalancheController.update ${getKey(entry)}"
if (entry == null) {
- log { "Entry is NULL, stop update." }
+ headsUpManagerLogger.logAvalancheUpdate(caller, isEnabled, key, "Entry NULL, stop")
return
}
if (debug) {
- debugRunnableLabelMap[runnable] = label
+ debugRunnableLabelMap[runnable] = caller
}
+ var outcome = ""
if (isShowing(entry)) {
- log { "\n$fn => update showing" }
+ outcome = "update showing"
runnable.run()
} else if (entry in nextMap) {
- log { "\n$fn => update next" }
+ outcome = "update next"
nextMap[entry]?.add(runnable)
} else if (headsUpEntryShowing == null) {
- log { "\n$fn => showNow" }
+ outcome = "show now"
showNow(entry, arrayListOf(runnable))
} else {
// Clean up invalid state when entry is in list but not map and vice versa
@@ -156,7 +161,8 @@
)
}
}
- logState("after $fn")
+ outcome += getStateStr()
+ headsUpManagerLogger.logAvalancheUpdate(caller, isEnabled, key, outcome)
}
@VisibleForTesting
@@ -169,32 +175,37 @@
* Run or ignore Runnable for given HeadsUpEntry. If entry was never shown, ignore and delete
* all Runnables associated with that entry.
*/
- fun delete(entry: HeadsUpEntry?, runnable: Runnable?, label: String) {
+ fun delete(entry: HeadsUpEntry?, runnable: Runnable?, caller: String) {
+ val isEnabled = isEnabled()
+ val key = getKey(entry)
+
if (runnable == null) {
- log { "Runnable is NULL, stop delete." }
+ headsUpManagerLogger.logAvalancheDelete(caller, isEnabled, key, "Runnable NULL, stop")
return
}
- if (!isEnabled()) {
+ if (!isEnabled) {
+ headsUpManagerLogger.logAvalancheDelete(caller, isEnabled, key,
+ "NOT ENABLED, run runnable")
runnable.run()
return
}
- log { "\n " }
- val fn = "$label => AvalancheController.delete " + getKey(entry)
if (entry == null) {
- log { "$fn => entry NULL, running runnable" }
+ headsUpManagerLogger.logAvalancheDelete(caller, isEnabled, key,
+ "Entry NULL, run runnable")
runnable.run()
return
}
+ var outcome = ""
if (entry in nextMap) {
- log { "$fn => remove from next" }
+ outcome = "remove from next"
if (entry in nextMap) nextMap.remove(entry)
if (entry in nextList) nextList.remove(entry)
uiEventLogger.log(ThrottleEvent.AVALANCHE_THROTTLING_HUN_REMOVED)
} else if (entry in debugDropSet) {
- log { "$fn => remove from dropset" }
+ outcome = "remove from dropset"
debugDropSet.remove(entry)
} else if (isShowing(entry)) {
- log { "$fn => remove showing ${getKey(entry)}" }
+ outcome = "remove showing"
previousHunKey = getKey(headsUpEntryShowing)
// Show the next HUN before removing this one, so that we don't tell listeners
// onHeadsUpPinnedModeChanged, which causes
@@ -203,10 +214,10 @@
showNext()
runnable.run()
} else {
- log { "$fn => run runnable for untracked shown ${getKey(entry)}" }
+ outcome = "run runnable for untracked shown"
runnable.run()
}
- logState("after $fn")
+ headsUpManagerLogger.logAvalancheDelete(caller, isEnabled(), getKey(entry), outcome)
}
/**
@@ -384,23 +395,12 @@
}
private fun getStateStr(): String {
- return "SHOWING: [${getKey(headsUpEntryShowing)}]" +
- "\nPREVIOUS: [$previousHunKey]" +
- "\nNEXT LIST: $nextListStr" +
- "\nNEXT MAP: $nextMapStr" +
- "\nDROPPED: $dropSetStr" +
- "\nENABLED: $enableAtRuntime"
- }
-
- private fun logState(reason: String) {
- log {
- "\n================================================================================="
- }
- log { "STATE $reason" }
- log { getStateStr() }
- log {
- "=================================================================================\n"
- }
+ return "\navalanche state:" +
+ "\n\tshowing: [${getKey(headsUpEntryShowing)}]" +
+ "\n\tprevious: [$previousHunKey]" +
+ "\n\tnext list: $nextListStr" +
+ "\n\tnext map: $nextMapStr" +
+ "\n\tdropped: $dropSetStr"
}
private val dropSetStr: String
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt
index 6ffb162..80c595f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt
@@ -66,6 +66,30 @@
})
}
+ fun logAvalancheUpdate(caller: String, isEnabled: Boolean, notifEntryKey: String,
+ outcome: String) {
+ buffer.log(TAG, INFO, {
+ str1 = caller
+ str2 = notifEntryKey
+ str3 = outcome
+ bool1 = isEnabled
+ }, {
+ "$str1\n\t=> AC[isEnabled:$bool1] update: $str2\n\t=> $str3"
+ })
+ }
+
+ fun logAvalancheDelete(caller: String, isEnabled: Boolean, notifEntryKey: String,
+ outcome: String) {
+ buffer.log(TAG, INFO, {
+ str1 = caller
+ str2 = notifEntryKey
+ str3 = outcome
+ bool1 = isEnabled
+ }, {
+ "$str1\n\t=> AC[isEnabled:$bool1] delete: $str2\n\t=> $str3"
+ })
+ }
+
fun logShowNotification(entry: NotificationEntry) {
buffer.log(TAG, INFO, {
str1 = entry.logKey
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegate.kt
index 8aa989f..2b094d6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegate.kt
@@ -18,155 +18,67 @@
import android.content.Intent
import android.provider.Settings
-import android.util.Log
import androidx.compose.material3.Text
-import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource
-import androidx.lifecycle.DefaultLifecycleObserver
-import androidx.lifecycle.LifecycleOwner
import com.android.compose.PlatformButton
import com.android.compose.PlatformOutlinedButton
-import com.android.internal.annotations.VisibleForTesting
-import com.android.internal.jank.InteractionJankMonitor
-import com.android.systemui.animation.DialogCuj
import com.android.systemui.animation.DialogTransitionAnimator
-import com.android.systemui.animation.Expandable
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dialog.ui.composable.AlertDialogContent
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.res.R
-import com.android.systemui.statusbar.phone.ComponentSystemUIDialog
import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.statusbar.phone.SystemUIDialogFactory
import com.android.systemui.statusbar.phone.create
import com.android.systemui.statusbar.policy.ui.dialog.composable.ModeTileGrid
import com.android.systemui.statusbar.policy.ui.dialog.viewmodel.ModesDialogViewModel
-import com.android.systemui.util.Assert
import javax.inject.Inject
-import javax.inject.Provider
-import kotlin.coroutines.CoroutineContext
-import kotlinx.coroutines.withContext
-@SysUISingleton
class ModesDialogDelegate
@Inject
constructor(
private val sysuiDialogFactory: SystemUIDialogFactory,
private val dialogTransitionAnimator: DialogTransitionAnimator,
private val activityStarter: ActivityStarter,
- // Using a provider to avoid a circular dependency.
- private val viewModel: Provider<ModesDialogViewModel>,
- @Main private val mainCoroutineContext: CoroutineContext,
+ private val viewModel: ModesDialogViewModel,
) : SystemUIDialog.Delegate {
- // NOTE: This should only be accessed/written from the main thread.
- @VisibleForTesting var currentDialog: ComponentSystemUIDialog? = null
-
override fun createDialog(): SystemUIDialog {
- Assert.isMainThread()
- if (currentDialog != null) {
- Log.w(TAG, "Dialog is already open, dismissing it and creating a new one.")
- currentDialog?.dismiss()
- }
-
- currentDialog = sysuiDialogFactory.create() { ModesDialogContent(it) }
- currentDialog
- ?.lifecycle
- ?.addObserver(
- object : DefaultLifecycleObserver {
- override fun onStop(owner: LifecycleOwner) {
- Assert.isMainThread()
- currentDialog = null
+ return sysuiDialogFactory.create { dialog ->
+ AlertDialogContent(
+ title = { Text(stringResource(R.string.zen_modes_dialog_title)) },
+ content = { ModeTileGrid(viewModel) },
+ neutralButton = {
+ PlatformOutlinedButton(
+ onClick = {
+ val animationController =
+ dialogTransitionAnimator.createActivityTransitionController(
+ dialog.getButton(SystemUIDialog.BUTTON_NEUTRAL)
+ )
+ if (animationController == null) {
+ // The controller will take care of dismissing for us after the
+ // animation, but let's make sure we dismiss the dialog if we don't
+ // animate it.
+ dialog.dismiss()
+ }
+ activityStarter.startActivity(
+ ZEN_MODE_SETTINGS_INTENT,
+ true /* dismissShade */,
+ animationController
+ )
+ }
+ ) {
+ Text(stringResource(R.string.zen_modes_dialog_settings))
}
- }
- )
-
- return currentDialog!!
- }
-
- @Composable
- private fun ModesDialogContent(dialog: SystemUIDialog) {
- AlertDialogContent(
- title = { Text(stringResource(R.string.zen_modes_dialog_title)) },
- content = { ModeTileGrid(viewModel.get()) },
- neutralButton = {
- PlatformOutlinedButton(onClick = { openSettings(dialog) }) {
- Text(stringResource(R.string.zen_modes_dialog_settings))
- }
- },
- positiveButton = {
- PlatformButton(onClick = { dialog.dismiss() }) {
- Text(stringResource(R.string.zen_modes_dialog_done))
- }
- },
- )
- }
-
- private fun openSettings(dialog: SystemUIDialog) {
- val animationController =
- dialogTransitionAnimator.createActivityTransitionController(dialog)
- if (animationController == null) {
- // The controller will take care of dismissing for us after
- // the animation, but let's make sure we dismiss the dialog
- // if we don't animate it.
- dialog.dismiss()
- }
- activityStarter.startActivity(
- ZEN_MODE_SETTINGS_INTENT,
- true /* dismissShade */,
- animationController
- )
- }
-
- suspend fun showDialog(expandable: Expandable? = null): SystemUIDialog {
- // Dialogs shown by the DialogTransitionAnimator must be created and shown on the main
- // thread, so we post it to the UI handler.
- withContext(mainCoroutineContext) {
- // Create the dialog if necessary
- if (currentDialog == null) {
- createDialog()
- }
-
- expandable
- ?.dialogTransitionController(
- DialogCuj(InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN, INTERACTION_JANK_TAG)
- )
- ?.let { controller -> dialogTransitionAnimator.show(currentDialog!!, controller) }
- ?: currentDialog!!.show()
- }
-
- return currentDialog!!
- }
-
- /**
- * Launches the [intent] by animating from the dialog. If the dialog is not showing, just
- * launches it normally without animating.
- */
- fun launchFromDialog(intent: Intent) {
- Assert.isMainThread()
- if (currentDialog == null) {
- Log.w(
- TAG,
- "Cannot launch from dialog, the dialog is not present. " +
- "Will launch activity without animating."
+ },
+ positiveButton = {
+ PlatformButton(onClick = { dialog.dismiss() }) {
+ Text(stringResource(R.string.zen_modes_dialog_done))
+ }
+ },
)
}
-
- val animationController =
- currentDialog?.let { dialogTransitionAnimator.createActivityTransitionController(it) }
- if (animationController == null) {
- currentDialog?.dismiss()
- }
- activityStarter.startActivity(
- intent,
- true, /* dismissShade */
- animationController,
- )
}
companion object {
- private const val TAG = "ModesDialogDelegate"
private val ZEN_MODE_SETTINGS_INTENT = Intent(Settings.ACTION_ZEN_MODE_SETTINGS)
- private const val INTERACTION_JANK_TAG = "configure_priority_modes"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt
index 7e64090..e84c8b6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt
@@ -17,15 +17,11 @@
package com.android.systemui.statusbar.policy.ui.dialog.viewmodel
import android.content.Context
-import android.content.Intent
-import android.provider.Settings.ACTION_AUTOMATIC_ZEN_RULE_SETTINGS
-import android.provider.Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID
import com.android.settingslib.notification.modes.ZenMode
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.res.R
import com.android.systemui.statusbar.policy.domain.interactor.ZenModeInteractor
-import com.android.systemui.statusbar.policy.ui.dialog.ModesDialogDelegate
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow
@@ -43,7 +39,6 @@
val context: Context,
zenModeInteractor: ZenModeInteractor,
@Background val bgDispatcher: CoroutineDispatcher,
- private val dialogDelegate: ModesDialogDelegate,
) {
// Modes that should be displayed in the dialog
// TODO(b/346519570): Include modes that have not been set up yet.
@@ -76,10 +71,7 @@
}
},
onLongClick = {
- val intent: Intent =
- Intent(ACTION_AUTOMATIC_ZEN_RULE_SETTINGS)
- .putExtra(EXTRA_AUTOMATIC_ZEN_RULE_ID, mode.id)
- dialogDelegate.launchFromDialog(intent)
+ // TODO(b/346519570): Open settings page for mode.
}
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt
index f726aae..e251ab5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt
@@ -29,6 +29,7 @@
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argThat
import com.android.systemui.util.mockito.whenever
+import java.util.function.Predicate
import junit.framework.Assert.assertEquals
import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertTrue
@@ -46,7 +47,6 @@
import org.mockito.Mockito.verifyNoMoreInteractions
import org.mockito.MockitoAnnotations
import org.mockito.kotlin.clearInvocations
-import java.util.function.Predicate
@RunWith(AndroidJUnit4::class)
@RunWithLooper
@@ -54,70 +54,134 @@
class KeyguardUnlockAnimationControllerTest : SysuiTestCase() {
private lateinit var keyguardUnlockAnimationController: KeyguardUnlockAnimationController
- @Mock
- private lateinit var windowManager: WindowManager
- @Mock
- private lateinit var keyguardViewMediator: KeyguardViewMediator
- @Mock
- private lateinit var keyguardStateController: KeyguardStateController
- @Mock
- private lateinit var keyguardViewController: KeyguardViewController
- @Mock
- private lateinit var featureFlags: FeatureFlags
- @Mock
- private lateinit var biometricUnlockController: BiometricUnlockController
- @Mock
- private lateinit var surfaceTransactionApplier: SyncRtSurfaceTransactionApplier
- @Mock
- private lateinit var statusBarStateController: SysuiStatusBarStateController
- @Mock
- private lateinit var notificationShadeWindowController: NotificationShadeWindowController
- @Mock
- private lateinit var powerManager: PowerManager
- @Mock
- private lateinit var wallpaperManager: WallpaperManager
+ @Mock private lateinit var windowManager: WindowManager
+ @Mock private lateinit var keyguardViewMediator: KeyguardViewMediator
+ @Mock private lateinit var keyguardStateController: KeyguardStateController
+ @Mock private lateinit var keyguardViewController: KeyguardViewController
+ @Mock private lateinit var featureFlags: FeatureFlags
+ @Mock private lateinit var biometricUnlockController: BiometricUnlockController
+ @Mock private lateinit var surfaceTransactionApplier: SyncRtSurfaceTransactionApplier
+ @Mock private lateinit var statusBarStateController: SysuiStatusBarStateController
+ @Mock private lateinit var notificationShadeWindowController: NotificationShadeWindowController
+ @Mock private lateinit var powerManager: PowerManager
+ @Mock private lateinit var wallpaperManager: WallpaperManager
@Mock
private lateinit var launcherUnlockAnimationController: ILauncherUnlockAnimationController.Stub
private var surfaceControl1 = mock(SurfaceControl::class.java)
- private var remoteTarget1 = RemoteAnimationTarget(
- 0 /* taskId */, 0, surfaceControl1, false, Rect(), Rect(), 0, Point(), Rect(), Rect(),
- mock(WindowConfiguration::class.java), false, surfaceControl1, Rect(),
- mock(ActivityManager.RunningTaskInfo::class.java), false)
+ private var remoteTarget1 =
+ RemoteAnimationTarget(
+ 0 /* taskId */,
+ 0,
+ surfaceControl1,
+ false,
+ Rect(),
+ Rect(),
+ 0,
+ Point(),
+ Rect(),
+ Rect(),
+ mock(WindowConfiguration::class.java),
+ false,
+ surfaceControl1,
+ Rect(),
+ mock(ActivityManager.RunningTaskInfo::class.java),
+ false
+ )
private var surfaceControl2 = mock(SurfaceControl::class.java)
- private var remoteTarget2 = RemoteAnimationTarget(
- 1 /* taskId */, 0, surfaceControl2, false, Rect(), Rect(), 0, Point(), Rect(), Rect(),
- mock(WindowConfiguration::class.java), false, surfaceControl2, Rect(),
- mock(ActivityManager.RunningTaskInfo::class.java), false)
+ private var remoteTarget2 =
+ RemoteAnimationTarget(
+ 1 /* taskId */,
+ 0,
+ surfaceControl2,
+ false,
+ Rect(),
+ Rect(),
+ 0,
+ Point(),
+ Rect(),
+ Rect(),
+ mock(WindowConfiguration::class.java),
+ false,
+ surfaceControl2,
+ Rect(),
+ mock(ActivityManager.RunningTaskInfo::class.java),
+ false
+ )
private lateinit var remoteAnimationTargets: Array<RemoteAnimationTarget>
private var surfaceControlWp = mock(SurfaceControl::class.java)
- private var wallpaperTarget = RemoteAnimationTarget(
- 2 /* taskId */, 0, surfaceControlWp, false, Rect(), Rect(), 0, Point(), Rect(), Rect(),
- mock(WindowConfiguration::class.java), false, surfaceControlWp, Rect(),
- mock(ActivityManager.RunningTaskInfo::class.java), false)
+ private var wallpaperTarget =
+ RemoteAnimationTarget(
+ 2 /* taskId */,
+ 0,
+ surfaceControlWp,
+ false,
+ Rect(),
+ Rect(),
+ 0,
+ Point(),
+ Rect(),
+ Rect(),
+ mock(WindowConfiguration::class.java),
+ false,
+ surfaceControlWp,
+ Rect(),
+ mock(ActivityManager.RunningTaskInfo::class.java),
+ false
+ )
private lateinit var wallpaperTargets: Array<RemoteAnimationTarget>
private var surfaceControlLockWp = mock(SurfaceControl::class.java)
- private var lockWallpaperTarget = RemoteAnimationTarget(
- 3 /* taskId */, 0, surfaceControlLockWp, false, Rect(), Rect(), 0, Point(), Rect(),
- Rect(), mock(WindowConfiguration::class.java), false, surfaceControlLockWp,
- Rect(), mock(ActivityManager.RunningTaskInfo::class.java), false)
+ private var lockWallpaperTarget =
+ RemoteAnimationTarget(
+ 3 /* taskId */,
+ 0,
+ surfaceControlLockWp,
+ false,
+ Rect(),
+ Rect(),
+ 0,
+ Point(),
+ Rect(),
+ Rect(),
+ mock(WindowConfiguration::class.java),
+ false,
+ surfaceControlLockWp,
+ Rect(),
+ mock(ActivityManager.RunningTaskInfo::class.java),
+ false
+ )
private lateinit var lockWallpaperTargets: Array<RemoteAnimationTarget>
+ private var shouldPerformSmartspaceTransition = false
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- keyguardUnlockAnimationController = KeyguardUnlockAnimationController(
- windowManager, context.resources,
- keyguardStateController, { keyguardViewMediator }, keyguardViewController,
- featureFlags, { biometricUnlockController }, statusBarStateController,
- notificationShadeWindowController, powerManager, wallpaperManager
- )
+ keyguardUnlockAnimationController =
+ object :
+ KeyguardUnlockAnimationController(
+ windowManager,
+ context.resources,
+ keyguardStateController,
+ { keyguardViewMediator },
+ keyguardViewController,
+ featureFlags,
+ { biometricUnlockController },
+ statusBarStateController,
+ notificationShadeWindowController,
+ powerManager,
+ wallpaperManager
+ ) {
+ override fun shouldPerformSmartspaceTransition(): Boolean =
+ shouldPerformSmartspaceTransition
+ }
keyguardUnlockAnimationController.setLauncherUnlockController(
- "", launcherUnlockAnimationController)
+ "",
+ launcherUnlockAnimationController
+ )
whenever(keyguardViewController.viewRootImpl).thenReturn(mock(ViewRootImpl::class.java))
whenever(powerManager.isInteractive).thenReturn(true)
@@ -159,8 +223,8 @@
)
val captorSb = ArgThatCaptor<SyncRtSurfaceTransactionApplier.SurfaceParams>()
- verify(surfaceTransactionApplier, times(1)).scheduleApply(
- captorSb.capture { sp -> sp.surface == surfaceControl1 })
+ verify(surfaceTransactionApplier, times(1))
+ .scheduleApply(captorSb.capture { sp -> sp.surface == surfaceControl1 })
val params = captorSb.getLastValue()
@@ -171,15 +235,13 @@
// Also expect we've immediately asked the keyguard view mediator to finish the remote
// animation.
- verify(keyguardViewMediator, times(1)).exitKeyguardAndFinishSurfaceBehindRemoteAnimation(
- false /* cancelled */)
+ verify(keyguardViewMediator, times(1))
+ .exitKeyguardAndFinishSurfaceBehindRemoteAnimation(false /* cancelled */)
verifyNoMoreInteractions(surfaceTransactionApplier)
}
- /**
- * If we are not wake and unlocking, we expect the unlock animation to play normally.
- */
+ /** If we are not wake and unlocking, we expect the unlock animation to play normally. */
@Test
fun surfaceAnimation_ifNotWakeAndUnlocking() {
whenever(biometricUnlockController.isWakeAndUnlock).thenReturn(false)
@@ -193,18 +255,18 @@
)
// Since the animation is running, we should not have finished the remote animation.
- verify(keyguardViewMediator, times(0)).exitKeyguardAndFinishSurfaceBehindRemoteAnimation(
- false /* cancelled */)
+ verify(keyguardViewMediator, times(0))
+ .exitKeyguardAndFinishSurfaceBehindRemoteAnimation(false /* cancelled */)
}
@Test
fun onWakeAndUnlock_notifiesListenerWithTrue() {
whenever(biometricUnlockController.isWakeAndUnlock).thenReturn(true)
- whenever(biometricUnlockController.mode).thenReturn(
- BiometricUnlockController.MODE_WAKE_AND_UNLOCK)
+ whenever(biometricUnlockController.mode)
+ .thenReturn(BiometricUnlockController.MODE_WAKE_AND_UNLOCK)
- val listener = mock(
- KeyguardUnlockAnimationController.KeyguardUnlockAnimationListener::class.java)
+ val listener =
+ mock(KeyguardUnlockAnimationController.KeyguardUnlockAnimationListener::class.java)
keyguardUnlockAnimationController.addKeyguardUnlockAnimationListener(listener)
keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation(
@@ -221,11 +283,11 @@
@Test
fun onWakeAndUnlockFromDream_notifiesListenerWithFalse() {
whenever(biometricUnlockController.isWakeAndUnlock).thenReturn(true)
- whenever(biometricUnlockController.mode).thenReturn(
- BiometricUnlockController.MODE_WAKE_AND_UNLOCK_FROM_DREAM)
+ whenever(biometricUnlockController.mode)
+ .thenReturn(BiometricUnlockController.MODE_WAKE_AND_UNLOCK_FROM_DREAM)
- val listener = mock(
- KeyguardUnlockAnimationController.KeyguardUnlockAnimationListener::class.java)
+ val listener =
+ mock(KeyguardUnlockAnimationController.KeyguardUnlockAnimationListener::class.java)
keyguardUnlockAnimationController.addKeyguardUnlockAnimationListener(listener)
keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation(
@@ -269,8 +331,8 @@
* keyguard. This means this was a swipe to dismiss gesture but the user flung the keyguard and
* lifted their finger while we were requesting the surface be made visible.
*
- * In this case, we should verify that we are playing the canned unlock animation and not
- * simply fading in the surface.
+ * In this case, we should verify that we are playing the canned unlock animation and not simply
+ * fading in the surface.
*/
@Test
fun playCannedUnlockAnimation_ifRequestedShowSurface_andFlinging() {
@@ -293,8 +355,8 @@
* ever happened and we're just playing the simple canned animation (happens via UDFPS unlock,
* long press on the lock icon, etc).
*
- * In this case, we should verify that we are playing the canned unlock animation and not
- * simply fading in the surface.
+ * In this case, we should verify that we are playing the canned unlock animation and not simply
+ * fading in the surface.
*/
@Test
fun playCannedUnlockAnimation_ifDidNotRequestShowSurface() {
@@ -332,11 +394,11 @@
keyguardUnlockAnimationController.willUnlockWithInWindowLauncherAnimations = true
keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation(
- remoteAnimationTargets,
- wallpaperTargets,
- arrayOf(),
- 0 /* startTime */,
- false /* requestedShowSurfaceBehindKeyguard */
+ remoteAnimationTargets,
+ wallpaperTargets,
+ arrayOf(),
+ 0 /* startTime */,
+ false /* requestedShowSurfaceBehindKeyguard */
)
assertTrue(keyguardUnlockAnimationController.isPlayingCannedUnlockAnimation())
@@ -353,11 +415,11 @@
var lastFadeOutAlpha = -1f
keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation(
- arrayOf(remoteTarget1, remoteTarget2),
- wallpaperTargets,
- lockWallpaperTargets,
- 0 /* startTime */,
- false /* requestedShowSurfaceBehindKeyguard */
+ arrayOf(remoteTarget1, remoteTarget2),
+ wallpaperTargets,
+ lockWallpaperTargets,
+ 0 /* startTime */,
+ false /* requestedShowSurfaceBehindKeyguard */
)
for (i in 0..10) {
@@ -367,19 +429,22 @@
keyguardUnlockAnimationController.setSurfaceBehindAppearAmount(amount)
val captorSb = ArgThatCaptor<SyncRtSurfaceTransactionApplier.SurfaceParams>()
- verify(surfaceTransactionApplier, times(2)).scheduleApply(
+ verify(surfaceTransactionApplier, times(2))
+ .scheduleApply(
captorSb.capture { sp ->
- sp.surface == surfaceControlWp || sp.surface == surfaceControlLockWp })
+ sp.surface == surfaceControlWp || sp.surface == surfaceControlLockWp
+ }
+ )
val fadeInAlpha = captorSb.getLastValue { it.surface == surfaceControlWp }.alpha
val fadeOutAlpha = captorSb.getLastValue { it.surface == surfaceControlLockWp }.alpha
if (amount == 0f) {
- assertTrue (fadeInAlpha == 0f)
- assertTrue (fadeOutAlpha == 1f)
+ assertTrue(fadeInAlpha == 0f)
+ assertTrue(fadeOutAlpha == 1f)
} else if (amount == 1f) {
- assertTrue (fadeInAlpha == 1f)
- assertTrue (fadeOutAlpha == 0f)
+ assertTrue(fadeInAlpha == 1f)
+ assertTrue(fadeOutAlpha == 0f)
} else {
assertTrue(fadeInAlpha >= lastFadeInAlpha)
assertTrue(fadeOutAlpha <= lastFadeOutAlpha)
@@ -389,18 +454,16 @@
}
}
- /**
- * If we are not wake and unlocking, we expect the unlock animation to play normally.
- */
+ /** If we are not wake and unlocking, we expect the unlock animation to play normally. */
@Test
@DisableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
fun surfaceAnimation_multipleTargets() {
keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation(
- arrayOf(remoteTarget1, remoteTarget2),
- wallpaperTargets,
- arrayOf(),
- 0 /* startTime */,
- false /* requestedShowSurfaceBehindKeyguard */
+ arrayOf(remoteTarget1, remoteTarget2),
+ wallpaperTargets,
+ arrayOf(),
+ 0 /* startTime */,
+ false /* requestedShowSurfaceBehindKeyguard */
)
// Cancel the animator so we can verify only the setSurfaceBehind call below.
@@ -412,12 +475,18 @@
keyguardUnlockAnimationController.setSurfaceBehindAppearAmount(0.5f)
val captorSb = ArgThatCaptor<SyncRtSurfaceTransactionApplier.SurfaceParams>()
- verify(surfaceTransactionApplier, times(2)).scheduleApply(captorSb
- .capture { sp -> sp.surface == surfaceControl1 || sp.surface == surfaceControl2 })
+ verify(surfaceTransactionApplier, times(2))
+ .scheduleApply(
+ captorSb.capture { sp ->
+ sp.surface == surfaceControl1 || sp.surface == surfaceControl2
+ }
+ )
val captorWp = ArgThatCaptor<SyncRtSurfaceTransactionApplier.SurfaceParams>()
- verify(surfaceTransactionApplier, times(1).description(
- "WallpaperSurface was expected to receive scheduleApply once"
- )).scheduleApply(captorWp.capture { sp -> sp.surface == surfaceControlWp})
+ verify(
+ surfaceTransactionApplier,
+ times(1).description("WallpaperSurface was expected to receive scheduleApply once")
+ )
+ .scheduleApply(captorWp.capture { sp -> sp.surface == surfaceControlWp })
val allParams = captorSb.getAllValues()
@@ -432,8 +501,8 @@
assertTrue(remainingTargets.isEmpty())
// Since the animation is running, we should not have finished the remote animation.
- verify(keyguardViewMediator, times(0)).exitKeyguardAndFinishSurfaceBehindRemoteAnimation(
- false /* cancelled */)
+ verify(keyguardViewMediator, times(0))
+ .exitKeyguardAndFinishSurfaceBehindRemoteAnimation(false /* cancelled */)
}
@Test
@@ -442,11 +511,11 @@
whenever(powerManager.isInteractive).thenReturn(false)
keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation(
- remoteAnimationTargets,
- wallpaperTargets,
- arrayOf(),
- 0 /* startTime */,
- false /* requestedShowSurfaceBehindKeyguard */
+ remoteAnimationTargets,
+ wallpaperTargets,
+ arrayOf(),
+ 0 /* startTime */,
+ false /* requestedShowSurfaceBehindKeyguard */
)
// Cancel the animator so we can verify only the setSurfaceBehind call below.
@@ -457,12 +526,14 @@
keyguardUnlockAnimationController.setWallpaperAppearAmount(1f, wallpaperTargets)
val captorSb = ArgThatCaptor<SyncRtSurfaceTransactionApplier.SurfaceParams>()
- verify(surfaceTransactionApplier, times(1)).scheduleApply(
- captorSb.capture { sp -> sp.surface == surfaceControl1})
+ verify(surfaceTransactionApplier, times(1))
+ .scheduleApply(captorSb.capture { sp -> sp.surface == surfaceControl1 })
val captorWp = ArgThatCaptor<SyncRtSurfaceTransactionApplier.SurfaceParams>()
- verify(surfaceTransactionApplier, atLeastOnce().description("Wallpaper surface has not " +
- "received scheduleApply")).scheduleApply(
- captorWp.capture { sp -> sp.surface == surfaceControlWp })
+ verify(
+ surfaceTransactionApplier,
+ atLeastOnce().description("Wallpaper surface has not " + "received scheduleApply")
+ )
+ .scheduleApply(captorWp.capture { sp -> sp.surface == surfaceControlWp })
val params = captorSb.getLastValue()
@@ -479,11 +550,11 @@
whenever(powerManager.isInteractive).thenReturn(true)
keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation(
- remoteAnimationTargets,
- wallpaperTargets,
- arrayOf(),
- 0 /* startTime */,
- false /* requestedShowSurfaceBehindKeyguard */
+ remoteAnimationTargets,
+ wallpaperTargets,
+ arrayOf(),
+ 0 /* startTime */,
+ false /* requestedShowSurfaceBehindKeyguard */
)
// Stop the animator - we just want to test whether the override is not applied.
@@ -494,24 +565,31 @@
keyguardUnlockAnimationController.setWallpaperAppearAmount(1f, wallpaperTargets)
val captorSb = ArgThatCaptor<SyncRtSurfaceTransactionApplier.SurfaceParams>()
- verify(surfaceTransactionApplier, times(1)).scheduleApply(
- captorSb.capture { sp -> sp.surface == surfaceControl1 })
+ verify(surfaceTransactionApplier, times(1))
+ .scheduleApply(captorSb.capture { sp -> sp.surface == surfaceControl1 })
val captorWp = ArgThatCaptor<SyncRtSurfaceTransactionApplier.SurfaceParams>()
- verify(surfaceTransactionApplier, atLeastOnce().description("Wallpaper surface has not " +
- "received scheduleApply")).scheduleApply(
- captorWp.capture { sp -> sp.surface == surfaceControlWp })
+ verify(
+ surfaceTransactionApplier,
+ atLeastOnce().description("Wallpaper surface has not " + "received scheduleApply")
+ )
+ .scheduleApply(captorWp.capture { sp -> sp.surface == surfaceControlWp })
val params = captorSb.getLastValue()
assertEquals(1f, params.alpha)
assertTrue(params.matrix.isIdentity)
- assertEquals("Wallpaper surface was expected to have opacity 1",
- 1f, captorWp.getLastValue().alpha)
+ assertEquals(
+ "Wallpaper surface was expected to have opacity 1",
+ 1f,
+ captorWp.getLastValue().alpha
+ )
verifyNoMoreInteractions(surfaceTransactionApplier)
}
@Test
- fun unlockToLauncherWithInWindowAnimations_ssViewIsVisible() {
+ fun unlockToLauncherWithInWindowAnimations_ssViewInVisible_whenPerformSSTransition() {
+ shouldPerformSmartspaceTransition = true
+
val mockLockscreenSmartspaceView = mock(View::class.java)
whenever(mockLockscreenSmartspaceView.visibility).thenReturn(View.VISIBLE)
keyguardUnlockAnimationController.lockscreenSmartspace = mockLockscreenSmartspaceView
@@ -522,6 +600,19 @@
}
@Test
+ fun unlockToLauncherWithInWindowAnimations_ssViewVisible_whenNotPerformSSTransition() {
+ shouldPerformSmartspaceTransition = false
+
+ val mockLockscreenSmartspaceView = mock(View::class.java)
+ whenever(mockLockscreenSmartspaceView.visibility).thenReturn(View.VISIBLE)
+ keyguardUnlockAnimationController.lockscreenSmartspace = mockLockscreenSmartspaceView
+
+ keyguardUnlockAnimationController.unlockToLauncherWithInWindowAnimations()
+
+ verify(mockLockscreenSmartspaceView, never()).visibility = View.INVISIBLE
+ }
+
+ @Test
fun unlockToLauncherWithInWindowAnimations_ssViewIsInvisible() {
val mockLockscreenSmartspaceView = mock(View::class.java)
whenever(mockLockscreenSmartspaceView.visibility).thenReturn(View.INVISIBLE)
@@ -591,7 +682,7 @@
private var allArgs: MutableList<T> = mutableListOf()
fun capture(predicate: Predicate<T>): T {
- return argThat{x: T ->
+ return argThat { x: T ->
if (predicate.test(x)) {
allArgs.add(x)
return@argThat true
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt
index 7dd8028..f884b87 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt
@@ -25,8 +25,6 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.flags.FeatureFlagsClassic
-import com.android.systemui.flags.Flags
import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger
import com.android.systemui.res.R
import com.android.systemui.statusbar.phone.AlertDialogWithDelegate
@@ -34,10 +32,8 @@
import com.android.systemui.util.mockito.mock
import junit.framework.Assert.assertEquals
import org.junit.After
-import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.Mockito.`when` as whenever
@SmallTest
@RunWith(AndroidJUnit4::class)
@@ -46,7 +42,6 @@
private lateinit var dialog: AlertDialog
- private val flags = mock<FeatureFlagsClassic>()
private val appName = "Test App"
private val resIdSingleApp = R.string.screen_share_permission_dialog_option_single_app
@@ -54,11 +49,6 @@
private val resIdSingleAppDisabled =
R.string.media_projection_entry_app_permission_dialog_single_app_disabled
- @Before
- fun setUp() {
- whenever(flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)).thenReturn(true)
- }
-
@After
fun teardown() {
if (::dialog.isInitialized) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ModesTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ModesTileTest.kt
index 74d9692..27b6ea6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ModesTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ModesTileTest.kt
@@ -27,6 +27,7 @@
import com.android.internal.logging.MetricsLogger
import com.android.settingslib.notification.data.repository.FakeZenModeRepository
import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.DialogTransitionAnimator
import com.android.systemui.classifier.FalsingManagerFake
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.statusbar.StatusBarStateController
@@ -46,6 +47,7 @@
import com.android.systemui.util.settings.FakeSettings
import com.android.systemui.util.settings.SecureSettings
import com.google.common.truth.Truth.assertThat
+import kotlin.coroutines.EmptyCoroutineContext
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
@@ -80,6 +82,8 @@
@Mock private lateinit var qsTileConfigProvider: QSTileConfigProvider
+ @Mock private lateinit var dialogTransitionAnimator: DialogTransitionAnimator
+
@Mock private lateinit var dialogDelegate: ModesDialogDelegate
private val inputHandler = FakeQSTileIntentUserInputHandler()
@@ -127,7 +131,9 @@
userActionInteractor =
ModesTileUserActionInteractor(
+ EmptyCoroutineContext,
inputHandler,
+ dialogTransitionAnimator,
dialogDelegate,
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
index ce1a885..8d84c3e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
@@ -177,7 +177,6 @@
.thenReturn(false)
whenever(devicePolicyResolver.isScreenCaptureCompletelyDisabled(any<UserHandle>()))
.thenReturn(false)
- whenever(flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)).thenReturn(true)
whenever(state.hasUserApprovedScreenRecording).thenReturn(false)
val screenRecordSwitch = dialog.requireViewById<Switch>(R.id.screenrecord_switch)
@@ -200,7 +199,6 @@
.thenReturn(false)
whenever(devicePolicyResolver.isScreenCaptureCompletelyDisabled(any<UserHandle>()))
.thenReturn(false)
- whenever(flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)).thenReturn(false)
val screenRecordSwitch = dialog.requireViewById<Switch>(R.id.screenrecord_switch)
screenRecordSwitch.isChecked = true
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
index 477c50b..6b16e78 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
@@ -39,10 +39,8 @@
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
-import androidx.annotation.Nullable;
import androidx.test.filters.SmallTest;
-import com.android.systemui.Dependency;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.DialogTransitionAnimator;
import com.android.systemui.broadcast.BroadcastDispatcher;
@@ -52,12 +50,9 @@
import com.android.systemui.mediaprojection.SessionCreationSource;
import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver;
import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialogDelegate;
-import com.android.systemui.model.SysUiState;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.settings.UserTracker;
-import com.android.systemui.statusbar.phone.DialogDelegate;
import com.android.systemui.statusbar.phone.SystemUIDialog;
-import com.android.systemui.statusbar.phone.SystemUIDialogManager;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;
@@ -110,7 +105,6 @@
private FakeFeatureFlags mFeatureFlags;
private RecordingController mController;
- private TestSystemUIDialogFactory mDialogFactory;
private static final int USER_ID = 10;
@@ -120,14 +114,6 @@
Context spiedContext = spy(mContext);
when(spiedContext.getUserId()).thenReturn(TEST_USER_ID);
- mDialogFactory = new TestSystemUIDialogFactory(
- mContext,
- Dependency.get(SystemUIDialogManager.class),
- Dependency.get(SysUiState.class),
- Dependency.get(BroadcastDispatcher.class),
- Dependency.get(DialogTransitionAnimator.class)
- );
-
mFeatureFlags = new FakeFeatureFlags();
when(mScreenCaptureDisabledDialogDelegate.createSysUIDialog())
.thenReturn(mScreenCaptureDisabledDialog);
@@ -251,7 +237,6 @@
@Test
public void testPoliciesFlagDisabled_screenCapturingNotAllowed_returnsNullDevicePolicyDialog() {
- mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING, true);
mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES, false);
when(mDevicePolicyResolver.isScreenCaptureCompletelyDisabled((any()))).thenReturn(true);
@@ -269,19 +254,7 @@
}
@Test
- public void testPartialScreenSharingDisabled_returnsLegacyDialog() {
- mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING, false);
- mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES, false);
-
- Dialog dialog = mController.createScreenRecordDialog(mContext, mFeatureFlags,
- mDialogTransitionAnimator, mActivityStarter, /* onStartRecordingClicked= */ null);
-
- assertThat(dialog).isEqualTo(mScreenRecordSystemUIDialog);
- }
-
- @Test
public void testPoliciesFlagEnabled_screenCapturingNotAllowed_returnsDevicePolicyDialog() {
- mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING, true);
mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES, true);
when(mDevicePolicyResolver.isScreenCaptureCompletelyDisabled((any()))).thenReturn(true);
@@ -293,7 +266,6 @@
@Test
public void testPoliciesFlagEnabled_screenCapturingAllowed_returnsNullDevicePolicyDialog() {
- mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING, true);
mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES, true);
when(mDevicePolicyResolver.isScreenCaptureCompletelyDisabled((any()))).thenReturn(false);
@@ -312,7 +284,6 @@
@Test
public void testPoliciesFlagEnabled_screenCapturingAllowed_logsProjectionInitiated() {
- mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING, true);
mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES, true);
when(mDevicePolicyResolver.isScreenCaptureCompletelyDisabled((any()))).thenReturn(false);
@@ -324,32 +295,4 @@
/* hostUid= */ myUid(),
SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER);
}
-
- private static class TestSystemUIDialogFactory extends SystemUIDialog.Factory {
-
- @Nullable private DialogDelegate<SystemUIDialog> mLastDelegate;
- @Nullable private SystemUIDialog mLastCreatedDialog;
-
- TestSystemUIDialogFactory(
- Context context,
- SystemUIDialogManager systemUIDialogManager,
- SysUiState sysUiState,
- BroadcastDispatcher broadcastDispatcher,
- DialogTransitionAnimator dialogTransitionAnimator) {
- super(
- context,
- systemUIDialogManager,
- sysUiState,
- broadcastDispatcher,
- dialogTransitionAnimator);
- }
-
- @Override
- public SystemUIDialog create(SystemUIDialog.Delegate delegate) {
- SystemUIDialog dialog = super.create(delegate);
- mLastDelegate = delegate;
- mLastCreatedDialog = dialog;
- return dialog;
- }
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt
index cc8d7d5..11b0bdf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt
@@ -28,7 +28,6 @@
import com.android.systemui.animation.DialogTransitionAnimator
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger
import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorActivity
import com.android.systemui.mediaprojection.permission.ENTIRE_SCREEN
@@ -51,7 +50,6 @@
import org.mockito.Mockito.eq
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
@SmallTest
@@ -72,8 +70,6 @@
fun setUp() {
MockitoAnnotations.initMocks(this)
- whenever(flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)).thenReturn(true)
-
val systemUIDialogFactory =
SystemUIDialog.Factory(
context,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractorTest.kt
deleted file mode 100644
index 3908529..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractorTest.kt
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
- * except in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
- *
- */
-
-package com.android.systemui.statusbar.notification.domain.interactor
-
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.test.runTest
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(AndroidJUnit4::class)
-@SmallTest
-class SeenNotificationsInteractorTest : SysuiTestCase() {
-
- private val repository = ActiveNotificationListRepository()
- private val underTest = SeenNotificationsInteractor(repository)
-
- @Test
- fun testNoFilteredOutSeenNotifications() = runTest {
- val hasFilteredOutSeenNotifications by
- collectLastValue(underTest.hasFilteredOutSeenNotifications)
-
- underTest.setHasFilteredOutSeenNotifications(false)
-
- assertThat(hasFilteredOutSeenNotifications).isFalse()
- }
-
- @Test
- fun testHasFilteredOutSeenNotifications() = runTest {
- val hasFilteredOutSeenNotifications by
- collectLastValue(underTest.hasFilteredOutSeenNotifications)
-
- underTest.setHasFilteredOutSeenNotifications(true)
-
- assertThat(hasFilteredOutSeenNotifications).isTrue()
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
index 1060b62..c36a046 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
@@ -91,7 +91,6 @@
import com.android.systemui.statusbar.notification.collection.render.NotifStats;
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController;
-import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository;
import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor;
import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor;
import com.android.systemui.statusbar.notification.init.NotificationsController;
@@ -188,12 +187,8 @@
@Captor
private ArgumentCaptor<StatusBarStateController.StateListener> mStateListenerArgumentCaptor;
-
- private final ActiveNotificationListRepository mActiveNotificationsRepository =
- new ActiveNotificationListRepository();
-
private final SeenNotificationsInteractor mSeenNotificationsInteractor =
- new SeenNotificationsInteractor(mActiveNotificationsRepository);
+ mKosmos.getSeenNotificationsInteractor();
private NotificationStackScrollLayoutController mController;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt
index fd2dead..e670884 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt
@@ -334,6 +334,7 @@
/* typeVisibilityMap = */ booleanArrayOf(),
/* isRound = */ false,
/* forceConsumingTypes = */ 0,
+ /* forceConsumingCaptionBar = */ false,
/* suppressScrimTypes = */ 0,
/* displayCutout = */ DisplayCutout.NO_CUTOUT,
/* roundedCorners = */ RoundedCorners.NO_ROUNDED_CORNERS,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index 31f93b4..af5e60e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -27,6 +27,7 @@
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.mock;
@@ -94,6 +95,7 @@
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
import com.android.systemui.scene.domain.interactor.SceneInteractor;
+import com.android.systemui.scene.shared.model.Scenes;
import com.android.systemui.shade.NotificationShadeWindowView;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.shade.ShadeExpansionChangeEvent;
@@ -166,6 +168,7 @@
@Mock private StatusBarKeyguardViewManager.KeyguardViewManagerCallback mCallback;
@Mock private SelectedUserInteractor mSelectedUserInteractor;
@Mock private DeviceEntryInteractor mDeviceEntryInteractor;
+ @Mock private SceneInteractor mSceneInteractor;
private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
private PrimaryBouncerCallbackInteractor.PrimaryBouncerExpansionCallback
@@ -233,7 +236,7 @@
mSelectedUserInteractor,
() -> mock(KeyguardSurfaceBehindInteractor.class),
mock(JavaAdapter.class),
- () -> mock(SceneInteractor.class),
+ () -> mSceneInteractor,
mock(StatusBarKeyguardViewManagerInteractor.class),
() -> mDeviceEntryInteractor) {
@Override
@@ -270,21 +273,23 @@
}
@Test
- public void showBouncer_onlyWhenShowing() {
+ public void showPrimaryBouncer_onlyWhenShowing() {
mStatusBarKeyguardViewManager.hide(0 /* startTime */, 0 /* fadeoutDuration */);
mStatusBarKeyguardViewManager.showPrimaryBouncer(true /* scrimmed */);
verify(mPrimaryBouncerInteractor, never()).show(anyBoolean());
verify(mDeviceEntryInteractor, never()).attemptDeviceEntry();
+ verify(mSceneInteractor, never()).changeScene(any(), any());
}
@Test
- public void showBouncer_notWhenBouncerAlreadyShowing() {
+ public void showPrimaryBouncer_notWhenBouncerAlreadyShowing() {
mStatusBarKeyguardViewManager.hide(0 /* startTime */, 0 /* fadeoutDuration */);
when(mKeyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(
KeyguardSecurityModel.SecurityMode.Password);
mStatusBarKeyguardViewManager.showPrimaryBouncer(true /* scrimmed */);
verify(mPrimaryBouncerInteractor, never()).show(anyBoolean());
verify(mDeviceEntryInteractor, never()).attemptDeviceEntry();
+ verify(mSceneInteractor, never()).changeScene(any(), any());
}
@Test
@@ -753,7 +758,7 @@
mSelectedUserInteractor,
() -> mock(KeyguardSurfaceBehindInteractor.class),
mock(JavaAdapter.class),
- () -> mock(SceneInteractor.class),
+ () -> mSceneInteractor,
mock(StatusBarKeyguardViewManagerInteractor.class),
() -> mDeviceEntryInteractor) {
@Override
@@ -1104,9 +1109,9 @@
@Test
@EnableSceneContainer
- public void showPrimaryBouncer_attemptDeviceEntry() {
+ public void showPrimaryBouncer() {
mStatusBarKeyguardViewManager.showPrimaryBouncer(false);
- verify(mDeviceEntryInteractor).attemptDeviceEntry();
+ verify(mSceneInteractor).changeScene(eq(Scenes.Bouncer), anyString());
}
@Test
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/activatable/ActivatableExt.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/activatable/ActivatableExt.kt
new file mode 100644
index 0000000..1f04a44
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/activatable/ActivatableExt.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.activatable
+
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.TestScope
+
+/** Activates [activatable] for the duration of the test. */
+fun Activatable.activateIn(testScope: TestScope) {
+ testScope.backgroundScope.launch { activate() }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/ActivityTransitionAnimatorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/ActivityTransitionAnimatorKosmos.kt
index 5ac41ec..b23767e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/ActivityTransitionAnimatorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/ActivityTransitionAnimatorKosmos.kt
@@ -18,10 +18,6 @@
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testCase
-import com.android.systemui.util.mockito.mock
-
-val Kosmos.mockActivityTransitionAnimatorController by
- Kosmos.Fixture { mock<ActivityTransitionAnimator.Controller>() }
val Kosmos.activityTransitionAnimator by
Kosmos.Fixture {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/inputmethod/data/repository/FakeInputMethodRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/inputmethod/data/repository/FakeInputMethodRepository.kt
index 8e4461d..444baa0 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/inputmethod/data/repository/FakeInputMethodRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/inputmethod/data/repository/FakeInputMethodRepository.kt
@@ -16,6 +16,7 @@
package com.android.systemui.inputmethod.data.repository
+import android.os.UserHandle
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.inputmethod.data.model.InputMethodModel
import kotlinx.coroutines.flow.Flow
@@ -40,14 +41,15 @@
}
override suspend fun enabledInputMethods(
- userId: Int,
- fetchSubtypes: Boolean,
+ user: UserHandle,
+ fetchSubtypes: Boolean
): Flow<InputMethodModel> {
- return usersToEnabledInputMethods[userId] ?: flowOf()
+ return usersToEnabledInputMethods[user.identifier] ?: flowOf()
}
- override suspend fun selectedInputMethodSubtypes(): List<InputMethodModel.Subtype> =
- selectedInputMethodSubtypes
+ override suspend fun selectedInputMethodSubtypes(
+ user: UserHandle,
+ ): List<InputMethodModel.Subtype> = selectedInputMethodSubtypes
override suspend fun showInputMethodPicker(displayId: Int, showAuxiliarySubtypes: Boolean) {
inputMethodPickerShownDisplayId = displayId
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
index 0666820..8614fc9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
@@ -60,6 +60,7 @@
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.shade.shadeController
import com.android.systemui.statusbar.chips.ui.viewmodel.ongoingActivityChipsViewModel
+import com.android.systemui.statusbar.notification.domain.interactor.seenNotificationsInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.headsUpNotificationInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor
import com.android.systemui.statusbar.phone.scrimController
@@ -98,6 +99,7 @@
val communalRepository by lazy { kosmos.fakeCommunalSceneRepository }
val communalTransitionViewModel by lazy { kosmos.communalTransitionViewModel }
val headsUpNotificationInteractor by lazy { kosmos.headsUpNotificationInteractor }
+ val seenNotificationsInteractor by lazy { kosmos.seenNotificationsInteractor }
val keyguardRepository by lazy { kosmos.fakeKeyguardRepository }
val keyguardBouncerRepository by lazy { kosmos.fakeKeyguardBouncerRepository }
val keyguardInteractor by lazy { kosmos.keyguardInteractor }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorKosmos.kt
deleted file mode 100644
index 2ecfb45..0000000
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorKosmos.kt
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.qs.tiles.impl.modes.domain.interactor
-
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.qs.tiles.base.actions.qsTileIntentUserInputHandler
-import com.android.systemui.statusbar.policy.ui.dialog.modesDialogDelegate
-import javax.inject.Provider
-
-val Kosmos.modesTileUserActionInteractor: ModesTileUserActionInteractor by
- Kosmos.Fixture {
- ModesTileUserActionInteractor(
- qsTileIntentUserInputHandler,
- Provider { modesDialogDelegate }.get(),
- )
- }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModelKosmos.kt
index c56c56c..299b22e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModelKosmos.kt
@@ -17,7 +17,6 @@
package com.android.systemui.qs.ui.viewmodel
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.shade.ui.viewmodel.overlayShadeViewModel
@@ -27,6 +26,5 @@
shadeInteractor = shadeInteractor,
overlayShadeViewModel = overlayShadeViewModel,
quickSettingsContainerViewModel = quickSettingsContainerViewModel,
- applicationScope = applicationCoroutineScope,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt
index fff3b14..dd93141 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt
@@ -2,7 +2,6 @@
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.kosmos.testScope
import com.android.systemui.scene.shared.model.FakeScene
import com.android.systemui.scene.shared.model.SceneContainerConfig
import com.android.systemui.scene.shared.model.Scenes
@@ -18,16 +17,7 @@
)
}
-val Kosmos.fakeScenes by Fixture {
- sceneKeys
- .map { key ->
- FakeScene(
- scope = testScope.backgroundScope,
- key = key,
- )
- }
- .toSet()
-}
+val Kosmos.fakeScenes by Fixture { sceneKeys.map { key -> FakeScene(key) }.toSet() }
val Kosmos.scenes by Fixture { fakeScenes }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/FakeScene.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/FakeScene.kt
index eeaa9db..64e3526 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/FakeScene.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/FakeScene.kt
@@ -19,16 +19,12 @@
import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.UserAction
import com.android.compose.animation.scene.UserActionResult
-import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.Channel
-import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.receiveAsFlow
-import kotlinx.coroutines.flow.stateIn
class FakeScene(
- val scope: CoroutineScope,
override val key: SceneKey,
) : Scene {
var isDestinationScenesBeingCollected = false
@@ -40,11 +36,6 @@
.receiveAsFlow()
.onStart { isDestinationScenesBeingCollected = true }
.onCompletion { isDestinationScenesBeingCollected = false }
- .stateIn(
- scope = scope,
- started = SharingStarted.WhileSubscribed(),
- initialValue = emptyMap(),
- )
suspend fun setDestinationScenes(value: Map<UserAction, UserActionResult>) {
destinationScenesChannel.send(value)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelKosmos.kt
index 989c3a5..2c5a0f4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelKosmos.kt
@@ -17,7 +17,6 @@
package com.android.systemui.shade.ui.viewmodel
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.media.controls.domain.pipeline.interactor.mediaCarouselInteractor
import com.android.systemui.qs.footerActionsController
import com.android.systemui.qs.footerActionsViewModelFactory
@@ -30,7 +29,6 @@
val Kosmos.shadeSceneViewModel: ShadeSceneViewModel by
Kosmos.Fixture {
ShadeSceneViewModel(
- applicationScope = applicationCoroutineScope,
shadeHeaderViewModel = shadeHeaderViewModel,
qsSceneAdapter = qsSceneAdapter,
brightnessMirrorViewModel = brightnessMirrorViewModel,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinatorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinatorKosmos.kt
index 77d97bb..933ebf0 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinatorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinatorKosmos.kt
@@ -18,23 +18,19 @@
import com.android.systemui.dump.dumpManager
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.plugins.statusbar.statusBarStateController
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.statusbar.notification.domain.interactor.seenNotificationsInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.headsUpNotificationInteractor
-import com.android.systemui.util.settings.fakeSettings
var Kosmos.lockScreenMinimalismCoordinator by
Kosmos.Fixture {
LockScreenMinimalismCoordinator(
- bgDispatcher = testDispatcher,
dumpManager = dumpManager,
headsUpInteractor = headsUpNotificationInteractor,
logger = lockScreenMinimalismCoordinatorLogger,
scope = testScope.backgroundScope,
- secureSettings = fakeSettings,
seenNotificationsInteractor = seenNotificationsInteractor,
statusBarStateController = statusBarStateController,
shadeInteractor = shadeInteractor,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractorKosmos.kt
index c1e0419..b19e221 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractorKosmos.kt
@@ -16,12 +16,32 @@
package com.android.systemui.statusbar.notification.domain.interactor
+import android.os.UserHandle
+import android.provider.Settings
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
+import com.android.systemui.util.settings.fakeSettings
val Kosmos.seenNotificationsInteractor by Fixture {
SeenNotificationsInteractor(
+ bgDispatcher = testDispatcher,
notificationListRepository = activeNotificationListRepository,
+ secureSettings = fakeSettings,
)
}
+
+var Kosmos.lockScreenShowOnlyUnseenNotificationsSetting: Boolean
+ get() =
+ fakeSettings.getIntForUser(
+ Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
+ UserHandle.USER_CURRENT,
+ ) == 1
+ set(value) {
+ fakeSettings.putIntForUser(
+ Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
+ if (value) 1 else 2,
+ UserHandle.USER_CURRENT,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateKosmos.kt
deleted file mode 100644
index 99bb479..0000000
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateKosmos.kt
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.policy.ui.dialog
-
-import com.android.systemui.animation.dialogTransitionAnimator
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.mainCoroutineContext
-import com.android.systemui.plugins.activityStarter
-import com.android.systemui.statusbar.phone.systemUIDialogFactory
-import com.android.systemui.statusbar.policy.ui.dialog.viewmodel.modesDialogViewModel
-import com.android.systemui.util.mockito.mock
-
-val Kosmos.mockModesDialogDelegate by Kosmos.Fixture { mock<ModesDialogDelegate>() }
-
-var Kosmos.modesDialogDelegate: ModesDialogDelegate by
- Kosmos.Fixture {
- ModesDialogDelegate(
- systemUIDialogFactory,
- dialogTransitionAnimator,
- activityStarter,
- { modesDialogViewModel },
- mainCoroutineContext,
- )
- }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelKosmos.kt
deleted file mode 100644
index 00020f8..0000000
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelKosmos.kt
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.policy.ui.dialog.viewmodel
-
-import android.content.mockedContext
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.testDispatcher
-import com.android.systemui.statusbar.policy.domain.interactor.zenModeInteractor
-import com.android.systemui.statusbar.policy.ui.dialog.modesDialogDelegate
-import javax.inject.Provider
-
-val Kosmos.modesDialogViewModel: ModesDialogViewModel by
- Kosmos.Fixture {
- ModesDialogViewModel(
- mockedContext,
- zenModeInteractor,
- testDispatcher,
- Provider { modesDialogDelegate }.get(),
- )
- }
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index f821e00..69478bb 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -4616,6 +4616,9 @@
opPackageName,
visibleAccountTypes,
includeUserManagedNotVisible);
+ } catch (SQLiteException e) {
+ Log.w(TAG, "Could not get accounts for user " + userId, e);
+ return new Account[]{};
} finally {
restoreCallingIdentity(identityToken);
}
@@ -4703,12 +4706,17 @@
public Account[] getSharedAccountsAsUser(int userId) {
userId = handleIncomingUser(userId);
- UserAccounts accounts = getUserAccounts(userId);
- synchronized (accounts.dbLock) {
- List<Account> accountList = accounts.accountsDb.getSharedAccounts();
- Account[] accountArray = new Account[accountList.size()];
- accountList.toArray(accountArray);
- return accountArray;
+ try {
+ UserAccounts accounts = getUserAccounts(userId);
+ synchronized (accounts.dbLock) {
+ List<Account> accountList = accounts.accountsDb.getSharedAccounts();
+ Account[] accountArray = new Account[accountList.size()];
+ accountList.toArray(accountArray);
+ return accountArray;
+ }
+ } catch (SQLiteException e) {
+ Log.w(TAG, "Could not get shared accounts for user " + userId, e);
+ return new Account[]{};
}
}
diff --git a/services/core/java/com/android/server/am/AppExitInfoTracker.java b/services/core/java/com/android/server/am/AppExitInfoTracker.java
index 47b65eb..1f88657 100644
--- a/services/core/java/com/android/server/am/AppExitInfoTracker.java
+++ b/services/core/java/com/android/server/am/AppExitInfoTracker.java
@@ -353,8 +353,8 @@
}
/** Called when there is a low memory kill */
- void scheduleNoteLmkdProcKilled(final int pid, final int uid) {
- mKillHandler.obtainMessage(KillHandler.MSG_LMKD_PROC_KILLED, pid, uid)
+ void scheduleNoteLmkdProcKilled(final int pid, final int uid, final int rssKb) {
+ mKillHandler.obtainMessage(KillHandler.MSG_LMKD_PROC_KILLED, pid, uid, Long.valueOf(rssKb))
.sendToTarget();
}
@@ -401,9 +401,9 @@
if (lmkd != null) {
updateExistingExitInfoRecordLocked(info, null,
- ApplicationExitInfo.REASON_LOW_MEMORY);
+ ApplicationExitInfo.REASON_LOW_MEMORY, (Long) lmkd.second);
} else if (zygote != null) {
- updateExistingExitInfoRecordLocked(info, (Integer) zygote.second, null);
+ updateExistingExitInfoRecordLocked(info, (Integer) zygote.second, null, null);
} else {
scheduleLogToStatsdLocked(info, false);
}
@@ -486,7 +486,7 @@
*/
@GuardedBy("mLock")
private void updateExistingExitInfoRecordLocked(ApplicationExitInfo info,
- Integer status, Integer reason) {
+ Integer status, Integer reason, Long rssKb) {
if (info == null || !isFresh(info.getTimestamp())) {
// if the record is way outdated, don't update it then (because of potential pid reuse)
return;
@@ -513,6 +513,9 @@
immediateLog = true;
}
}
+ if (rssKb != null) {
+ info.setRss(rssKb.longValue());
+ }
scheduleLogToStatsdLocked(info, immediateLog);
}
@@ -523,7 +526,7 @@
*/
@GuardedBy("mLock")
private boolean updateExitInfoIfNecessaryLocked(
- int pid, int uid, Integer status, Integer reason) {
+ int pid, int uid, Integer status, Integer reason, Long rssKb) {
Integer k = mIsolatedUidRecords.getUidByIsolatedUid(uid);
if (k != null) {
uid = k;
@@ -552,7 +555,7 @@
// always be the first one we se as `getExitInfosLocked()` returns them sorted
// by most-recent-first.
isModified[0] = true;
- updateExistingExitInfoRecordLocked(info, status, reason);
+ updateExistingExitInfoRecordLocked(info, status, reason, rssKb);
return FOREACH_ACTION_STOP_ITERATION;
}
return FOREACH_ACTION_NONE;
@@ -1668,11 +1671,11 @@
switch (msg.what) {
case MSG_LMKD_PROC_KILLED:
mAppExitInfoSourceLmkd.onProcDied(msg.arg1 /* pid */, msg.arg2 /* uid */,
- null /* status */);
+ null /* status */, (Long) msg.obj /* rss_kb */);
break;
case MSG_CHILD_PROC_DIED:
mAppExitInfoSourceZygote.onProcDied(msg.arg1 /* pid */, msg.arg2 /* uid */,
- (Integer) msg.obj /* status */);
+ (Integer) msg.obj /* status */, null /* rss_kb */);
break;
case MSG_PROC_DIED: {
ApplicationExitInfo raw = (ApplicationExitInfo) msg.obj;
@@ -1833,7 +1836,7 @@
}
}
- void onProcDied(final int pid, final int uid, final Integer status) {
+ void onProcDied(final int pid, final int uid, final Integer status, final Long rssKb) {
if (DEBUG_PROCESSES) {
Slog.i(TAG, mTag + ": proc died: pid=" + pid + " uid=" + uid
+ ", status=" + status);
@@ -1846,8 +1849,12 @@
// Unlikely but possible: the record has been created
// Let's update it if we could find a ApplicationExitInfo record
synchronized (mLock) {
- if (!updateExitInfoIfNecessaryLocked(pid, uid, status, mPresetReason)) {
- addLocked(pid, uid, status);
+ if (!updateExitInfoIfNecessaryLocked(pid, uid, status, mPresetReason, rssKb)) {
+ if (rssKb != null) {
+ addLocked(pid, uid, rssKb); // lmkd
+ } else {
+ addLocked(pid, uid, status); // zygote
+ }
}
// Notify any interesed party regarding the lmkd kills
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 779aabe..726e827 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -951,12 +951,14 @@
try {
switch (inputData.readInt()) {
case LMK_PROCKILL:
- if (receivedLen != 12) {
+ if (receivedLen != 16) {
return false;
}
final int pid = inputData.readInt();
final int uid = inputData.readInt();
- mAppExitInfoTracker.scheduleNoteLmkdProcKilled(pid, uid);
+ final int rssKb = inputData.readInt();
+ mAppExitInfoTracker.scheduleNoteLmkdProcKilled(pid, uid,
+ rssKb);
return true;
case LMK_KILL_OCCURRED:
if (receivedLen
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 7a24e9df..1183768 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -3692,6 +3692,11 @@
ensureValidStreamType(streamType);
final int resolvedStream = sStreamVolumeAlias.get(streamType, /*valueIfKeyNotFound=*/-1);
+ if (resolvedStream == -1) {
+ Log.e(TAG, "adjustSuggestedStreamVolume: no stream vol alias for stream type "
+ + streamType);
+ return;
+ }
// Play sounds on STREAM_RING only.
if ((flags & AudioManager.FLAG_PLAY_SOUND) != 0 &&
@@ -3809,6 +3814,10 @@
// including with regard to silent mode control (e.g the use of STREAM_RING below and in
// checkForRingerModeChange() in place of STREAM_RING or STREAM_NOTIFICATION)
int streamTypeAlias = sStreamVolumeAlias.get(streamType, /*valueIfKeyNotFound=*/-1);
+ if (streamTypeAlias == -1) {
+ Log.e(TAG,
+ "adjustStreamVolume: no stream vol alias for stream type " + streamType);
+ }
VolumeStreamState streamState = getVssForStreamOrDefault(streamTypeAlias);
@@ -4077,8 +4086,8 @@
synchronized (mSettingsLock) {
synchronized (VolumeStreamState.class) {
List<Integer> streamsToMute = new ArrayList<>();
- for (int stream = 0; stream < mStreamStates.size(); stream++) {
- final VolumeStreamState vss = mStreamStates.valueAt(stream);
+ for (int streamIdx = 0; streamIdx < mStreamStates.size(); streamIdx++) {
+ final VolumeStreamState vss = mStreamStates.valueAt(streamIdx);
if (vss != null && streamAlias == sStreamVolumeAlias.get(vss.getStreamType())
&& vss.isMutable()) {
if (!(mCameraSoundForced && (vss.getStreamType()
@@ -4219,6 +4228,10 @@
/*package*/ void onSetStreamVolume(int streamType, int index, int flags, int device,
String caller, boolean hasModifyAudioSettings, boolean canChangeMute) {
final int stream = sStreamVolumeAlias.get(streamType, /*valueIfKeyNotFound=*/-1);
+ if (stream == -1) {
+ Log.e(TAG, "onSetStreamVolume: no stream vol alias for stream type " + stream);
+ return;
+ }
// setting volume on ui sounds stream type also controls silent mode
if (((flags & AudioManager.FLAG_ALLOW_RINGER_MODES) != 0) ||
(stream == getUiSoundsStreamType())) {
@@ -4868,6 +4881,10 @@
ensureValidStreamType(streamType);
int streamTypeAlias = sStreamVolumeAlias.get(streamType, /*valueIfKeyNotFound*/-1);
+ if (streamTypeAlias == -1) {
+ Log.e(TAG, "setStreamVolume: no stream vol alias for stream type " + streamType);
+ return;
+ }
final VolumeStreamState streamState = getVssForStreamOrDefault(streamTypeAlias);
if (!replaceStreamBtSco() && (streamType == AudioManager.STREAM_VOICE_CALL)
@@ -5586,9 +5603,9 @@
return new ArrayList<>(Arrays.stream(AudioManager.getPublicStreamTypes())
.boxed().toList());
}
- ArrayList<Integer> res = new ArrayList(1);
- for (int stream = 0; stream < sStreamVolumeAlias.size(); ++stream) {
- final int streamAlias = sStreamVolumeAlias.valueAt(stream);
+ ArrayList<Integer> res = new ArrayList<>(1);
+ for (int streamIdx = 0; streamIdx < sStreamVolumeAlias.size(); ++streamIdx) {
+ final int streamAlias = sStreamVolumeAlias.valueAt(streamIdx);
if (!res.contains(streamAlias)) {
res.add(streamAlias);
}
@@ -6408,6 +6425,10 @@
final int device = getDeviceForStream(streamType);
final int streamAlias = sStreamVolumeAlias.get(streamType, /*valueIfKeyNotFound=*/
-1);
+ if (streamAlias == -1) {
+ Log.e(TAG,
+ "onUpdateAudioMode: no stream vol alias for stream type " + streamType);
+ }
if (DEBUG_MODE) {
Log.v(TAG, "onUpdateAudioMode: streamType=" + streamType
@@ -7614,8 +7635,8 @@
? MIN_STREAM_VOLUME[AudioSystem.STREAM_ALARM]
: Math.min(idx + 1, MAX_STREAM_VOLUME[AudioSystem.STREAM_ALARM]);
// update the VolumeStreamState for STREAM_ALARM and its aliases
- for (int stream = 0; stream < sStreamVolumeAlias.size(); ++stream) {
- final int streamAlias = sStreamVolumeAlias.valueAt(stream);
+ for (int streamIdx = 0; streamIdx < sStreamVolumeAlias.size(); ++streamIdx) {
+ final int streamAlias = sStreamVolumeAlias.valueAt(streamIdx);
if (streamAlias == AudioSystem.STREAM_ALARM) {
getVssForStreamOrDefault(streamAlias).updateNoPermMinIndex(safeIndex);
}
@@ -9454,7 +9475,7 @@
// must be sync'd on mSettingsLock before VolumeStreamState.class
@GuardedBy("VolumeStreamState.class")
public void setAllIndexes(VolumeStreamState srcStream, String caller) {
- if (mStreamType == srcStream.mStreamType) {
+ if (srcStream == null || mStreamType == srcStream.mStreamType) {
return;
}
int srcStreamType = srcStream.getStreamType();
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 1177be2..8b21d98 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -1755,6 +1755,7 @@
mTempBrightnessEvent.setPhysicalDisplayId(mUniqueDisplayId);
mTempBrightnessEvent.setPhysicalDisplayName(mPhysicalDisplayName);
mTempBrightnessEvent.setDisplayState(state);
+ mTempBrightnessEvent.setDisplayStateReason(stateAndReason.second);
mTempBrightnessEvent.setDisplayPolicy(mPowerRequest.policy);
mTempBrightnessEvent.setReason(mBrightnessReason);
mTempBrightnessEvent.setHbmMax(hbmMax);
diff --git a/services/core/java/com/android/server/display/ExternalDisplayStatsService.java b/services/core/java/com/android/server/display/ExternalDisplayStatsService.java
index f6f23d9..608fb35 100644
--- a/services/core/java/com/android/server/display/ExternalDisplayStatsService.java
+++ b/services/core/java/com/android/server/display/ExternalDisplayStatsService.java
@@ -518,18 +518,24 @@
private void logExternalDisplayIdleStarted() {
synchronized (mExternalDisplayStates) {
for (var i = 0; i < mExternalDisplayStates.size(); i++) {
- mInjector.writeLog(FrameworkStatsLog.EXTERNAL_DISPLAY_STATE_CHANGED,
- KEYGUARD, i + 1, mIsExternalDisplayUsedForAudio);
- if (DEBUG) {
- final int displayId = mExternalDisplayStates.keyAt(i);
- final int state = mExternalDisplayStates.get(displayId, DISCONNECTED_STATE);
- Slog.d(TAG, "logExternalDisplayIdleStarted"
- + " displayId=" + displayId
- + " currentState=" + state
- + " countOfExternalDisplays=" + (i + 1)
- + " state=" + KEYGUARD
- + " mIsExternalDisplayUsedForAudio="
- + mIsExternalDisplayUsedForAudio);
+ final int displayId = mExternalDisplayStates.keyAt(i);
+ final int state = mExternalDisplayStates.get(displayId, DISCONNECTED_STATE);
+ // Don't try to stop "connected" session by keyguard event.
+ // There is no purpose to measure how long keyguard is shown while external
+ // display is connected but not used for mirroring or extended display.
+ // Therefore there no need to log this event.
+ if (state != DISCONNECTED_STATE && state != CONNECTED_STATE) {
+ mInjector.writeLog(FrameworkStatsLog.EXTERNAL_DISPLAY_STATE_CHANGED,
+ KEYGUARD, i + 1, mIsExternalDisplayUsedForAudio);
+ if (DEBUG) {
+ Slog.d(TAG, "logExternalDisplayIdleStarted"
+ + " displayId=" + displayId
+ + " currentState=" + state
+ + " countOfExternalDisplays=" + (i + 1)
+ + " state=" + KEYGUARD
+ + " mIsExternalDisplayUsedForAudio="
+ + mIsExternalDisplayUsedForAudio);
+ }
}
}
}
@@ -540,7 +546,11 @@
for (var i = 0; i < mExternalDisplayStates.size(); i++) {
final int displayId = mExternalDisplayStates.keyAt(i);
final int state = mExternalDisplayStates.get(displayId, DISCONNECTED_STATE);
- if (state == DISCONNECTED_STATE) {
+ // No need to restart "connected" session after keyguard is stopped.
+ // This is because the connection is continuous even if keyguard is shown.
+ // In case in the future keyguard needs to be measured also while display
+ // is not used, then a 'keyguard finished' event needs to be emitted in this case.
+ if (state == DISCONNECTED_STATE || state == CONNECTED_STATE) {
return;
}
mInjector.writeLog(FrameworkStatsLog.EXTERNAL_DISPLAY_STATE_CHANGED,
diff --git a/services/core/java/com/android/server/display/brightness/BrightnessEvent.java b/services/core/java/com/android/server/display/brightness/BrightnessEvent.java
index 5cc603c..ad57ebf 100644
--- a/services/core/java/com/android/server/display/brightness/BrightnessEvent.java
+++ b/services/core/java/com/android/server/display/brightness/BrightnessEvent.java
@@ -52,6 +52,8 @@
private String mPhysicalDisplayId;
private String mPhysicalDisplayName;
private int mDisplayState;
+ @Display.StateReason
+ private int mDisplayStateReason;
private int mDisplayPolicy;
private long mTime;
private float mLux;
@@ -96,6 +98,7 @@
mPhysicalDisplayId = that.getPhysicalDisplayId();
mPhysicalDisplayName = that.getPhysicalDisplayName();
mDisplayState = that.mDisplayState;
+ mDisplayStateReason = that.mDisplayStateReason;
mDisplayPolicy = that.mDisplayPolicy;
mTime = that.getTime();
// Lux values
@@ -133,6 +136,7 @@
mPhysicalDisplayId = "";
mPhysicalDisplayName = "";
mDisplayState = Display.STATE_UNKNOWN;
+ mDisplayStateReason = Display.STATE_REASON_UNKNOWN;
mDisplayPolicy = POLICY_OFF;
// Lux values
mLux = INVALID_LUX;
@@ -176,6 +180,7 @@
&& mPhysicalDisplayId.equals(that.mPhysicalDisplayId)
&& mPhysicalDisplayName.equals(that.mPhysicalDisplayName)
&& mDisplayState == that.mDisplayState
+ && mDisplayStateReason == that.mDisplayStateReason
&& mDisplayPolicy == that.mDisplayPolicy
&& Float.floatToRawIntBits(mLux) == Float.floatToRawIntBits(that.mLux)
&& Float.floatToRawIntBits(mPreThresholdLux)
@@ -221,6 +226,7 @@
+ ", reason=" + mReason.toString(mAdjustmentFlags)
+ ", strat=" + mDisplayBrightnessStrategyName
+ ", state=" + Display.stateToString(mDisplayState)
+ + ", stateReason=" + Display.stateReasonToString(mDisplayStateReason)
+ ", policy=" + policyToString(mDisplayPolicy)
+ ", flags=" + flagsToString()
// Autobrightness
@@ -293,6 +299,10 @@
mDisplayState = state;
}
+ public void setDisplayStateReason(@Display.StateReason int reason) {
+ mDisplayStateReason = reason;
+ }
+
public void setDisplayPolicy(int policy) {
mDisplayPolicy = policy;
}
diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java
index 886857c..d43e783 100644
--- a/services/core/java/com/android/server/dreams/DreamManagerService.java
+++ b/services/core/java/com/android/server/dreams/DreamManagerService.java
@@ -544,11 +544,10 @@
private void startDozingInternal(IBinder token, int screenState,
@Display.StateReason int reason, int screenBrightness) {
- if (DEBUG) {
- Slog.d(TAG, "Dream requested to start dozing: " + token
- + ", screenState=" + screenState
- + ", screenBrightness=" + screenBrightness);
- }
+ Slog.d(TAG, "Dream requested to start dozing: " + token
+ + ", screenState=" + Display.stateToString(screenState)
+ + ", reason=" + Display.stateReasonToString(reason)
+ + ", screenBrightness=" + screenBrightness);
synchronized (mLock) {
if (mCurrentDream != null && mCurrentDream.token == token && mCurrentDream.canDoze) {
diff --git a/services/core/java/com/android/server/input/KeyboardLayoutManager.java b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
index 4993412..1d1a178 100644
--- a/services/core/java/com/android/server/input/KeyboardLayoutManager.java
+++ b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
@@ -22,6 +22,8 @@
import static android.hardware.input.KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD;
import static android.hardware.input.KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_DEFAULT;
+import static com.android.hardware.input.Flags.keyboardLayoutManagerMultiUserImeSetup;
+
import android.annotation.AnyThread;
import android.annotation.MainThread;
import android.annotation.NonNull;
@@ -1066,9 +1068,15 @@
for (InputMethodInfo imeInfo :
inputMethodManagerInternal.getEnabledInputMethodListAsUser(
userId)) {
- for (InputMethodSubtype imeSubtype :
- inputMethodManager.getEnabledInputMethodSubtypeList(
- imeInfo, true /* allowsImplicitlyEnabledSubtypes */)) {
+ final List<InputMethodSubtype> imeSubtypes;
+ if (keyboardLayoutManagerMultiUserImeSetup()) {
+ imeSubtypes = inputMethodManagerInternal.getEnabledInputMethodSubtypeListAsUser(
+ imeInfo.getId(), true /* allowsImplicitlyEnabledSubtypes */, userId);
+ } else {
+ imeSubtypes = inputMethodManager.getEnabledInputMethodSubtypeList(imeInfo,
+ true /* allowsImplicitlyEnabledSubtypes */);
+ }
+ for (InputMethodSubtype imeSubtype : imeSubtypes) {
if (!imeSubtype.isSuitableForPhysicalKeyboardLayoutMapping()) {
continue;
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 64b6dbe..4716e6c 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -271,7 +271,6 @@
private static final int MSG_HARD_KEYBOARD_SWITCH_CHANGED = 4000;
- private static final int MSG_SYSTEM_UNLOCK_USER = 5000;
private static final int MSG_DISPATCH_ON_INPUT_METHOD_LIST_UPDATED = 5010;
private static final int MSG_NOTIFY_IME_UID_TO_AUDIO_SERVICE = 7000;
@@ -760,7 +759,6 @@
.getMethodMap();
synchronized (ImfLock.class) {
- final boolean isCurrentUser = (userId == mCurrentUserId);
final AdditionalSubtypeMap additionalSubtypeMap =
AdditionalSubtypeMapRepository.get(userId);
final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
@@ -812,9 +810,6 @@
final InputMethodSettings newSettings =
InputMethodSettings.create(newMethodMap, userId);
InputMethodSettingsRepository.put(userId, newSettings);
- if (!isCurrentUser) {
- return;
- }
postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */, userId);
boolean changed = false;
@@ -1024,10 +1019,24 @@
@Override
public void onUserUnlocking(@NonNull TargetUser user) {
- // Called on ActivityManager thread.
- SecureSettingsWrapper.onUserUnlocking(user.getUserIdentifier());
- mService.mHandler.obtainMessage(MSG_SYSTEM_UNLOCK_USER, user.getUserIdentifier(), 0)
- .sendToTarget();
+ // Called on ActivityManager thread. Do not block the calling thread.
+ final int userId = user.getUserIdentifier();
+ SecureSettingsWrapper.onUserUnlocking(userId);
+ mService.mIoHandler.post(() -> {
+ final var settings = queryInputMethodServicesInternal(mService.mContext, userId,
+ AdditionalSubtypeMapRepository.get(userId), DirectBootAwareness.AUTO);
+ InputMethodSettingsRepository.put(userId, settings);
+ synchronized (ImfLock.class) {
+ if (!mService.mSystemReady) {
+ return;
+ }
+ // We need to rebuild IMEs.
+ mService.postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */,
+ userId);
+ mService.updateInputMethodsFromSettingsLocked(true /* enabledChanged */,
+ userId);
+ }
+ });
}
@Override
@@ -1078,23 +1087,6 @@
}
}
- void onUnlockUser(@UserIdInt int userId) {
- synchronized (ImfLock.class) {
- if (DEBUG) {
- Slog.d(TAG, "onUnlockUser: userId=" + userId + " curUserId=" + mCurrentUserId);
- }
- if (!mSystemReady) {
- return;
- }
- final InputMethodSettings newSettings = queryInputMethodServicesInternal(mContext,
- userId, AdditionalSubtypeMapRepository.get(userId), DirectBootAwareness.AUTO);
- InputMethodSettingsRepository.put(userId, newSettings);
- // We need to rebuild IMEs.
- postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */, userId);
- updateInputMethodsFromSettingsLocked(true /* enabledChanged */, userId);
- }
- }
-
@GuardedBy("ImfLock.class")
void scheduleSwitchUserTaskLocked(@UserIdInt int userId,
@Nullable IInputMethodClientInvoker clientToBeReset) {
@@ -3966,6 +3958,10 @@
@Override
public void showInputMethodPickerFromClient(IInputMethodClient client,
int auxiliarySubtypeMode) {
+ if (mConcurrentMultiUserModeEnabled) {
+ Slog.w(TAG, "showInputMethodPickerFromClient is not enabled on automotive");
+ return;
+ }
final int callingUserId = UserHandle.getCallingUserId();
synchronized (ImfLock.class) {
if (!canShowInputMethodPickerLocked(client)) {
@@ -5135,11 +5131,6 @@
sendOnNavButtonFlagsChangedToAllImesLocked();
}
return true;
- case MSG_SYSTEM_UNLOCK_USER: {
- final int userId = msg.arg1;
- onUnlockUser(userId);
- return true;
- }
case MSG_DISPATCH_ON_INPUT_METHOD_LIST_UPDATED: {
final int userId = msg.arg1;
final List<InputMethodInfo> imes = (List<InputMethodInfo>) msg.obj;
@@ -5863,22 +5854,7 @@
if (!settings.getMethodMap().containsKey(imeId)) {
return false; // IME is not found.
}
- if (userId == mCurrentUserId) {
- setInputMethodEnabledLocked(imeId, enabled, userId);
- return true;
- }
- if (enabled) {
- final String enabledImeIdsStr = settings.getEnabledInputMethodsStr();
- final String newEnabledImeIdsStr = InputMethodUtils.concatEnabledImeIds(
- enabledImeIdsStr, imeId);
- if (!TextUtils.equals(enabledImeIdsStr, newEnabledImeIdsStr)) {
- settings.putEnabledInputMethodsStr(newEnabledImeIdsStr);
- }
- } else {
- settings.buildAndPutEnabledInputMethodsStrRemovingId(
- new StringBuilder(),
- settings.getEnabledInputMethodsAndSubtypeList(), imeId);
- }
+ setInputMethodEnabledLocked(imeId, enabled, userId);
return true;
}
}
diff --git a/services/core/java/com/android/server/pm/PackageArchiver.java b/services/core/java/com/android/server/pm/PackageArchiver.java
index 6b7b702..5e45b4c 100644
--- a/services/core/java/com/android/server/pm/PackageArchiver.java
+++ b/services/core/java/com/android/server/pm/PackageArchiver.java
@@ -869,18 +869,25 @@
private int createDraftSession(String packageName, String installerPackage,
String callerPackageName,
IntentSender statusReceiver, int userId) throws IOException {
+ Computer snapshot = mPm.snapshotComputer();
PackageInstaller.SessionParams sessionParams = new PackageInstaller.SessionParams(
PackageInstaller.SessionParams.MODE_FULL_INSTALL);
sessionParams.setAppPackageName(packageName);
sessionParams.setAppLabel(
mContext.getString(com.android.internal.R.string.unarchival_session_app_label));
- sessionParams.setAppIcon(
- getArchivedAppIcon(packageName, UserHandle.of(userId), callerPackageName));
+ // The draft session's app icon is based on the current launcher's icon overlay appops mode
+ String launcherPackageName = getCurrentLauncherPackageName(userId);
+ int launcherUid = launcherPackageName != null
+ ? snapshot.getPackageUid(launcherPackageName, 0, userId)
+ : Process.SYSTEM_UID;
+ sessionParams.setAppIcon(getArchivedAppIcon(packageName, UserHandle.of(userId),
+ isOverlayEnabled(launcherUid,
+ launcherPackageName == null ? callerPackageName : launcherPackageName)));
// To make sure SessionInfo::isUnarchival returns true for draft sessions,
// INSTALL_UNARCHIVE is also set.
sessionParams.installFlags = (INSTALL_UNARCHIVE_DRAFT | INSTALL_UNARCHIVE);
- int installerUid = mPm.snapshotComputer().getPackageUid(installerPackage, 0, userId);
+ int installerUid = snapshot.getPackageUid(installerPackage, 0, userId);
// Handles case of repeated unarchival calls for the same package.
int existingSessionId = mPm.mInstallerService.getExistingDraftSessionId(installerUid,
sessionParams,
@@ -926,12 +933,27 @@
/**
* Returns the icon of an archived app. This is the icon of the main activity of the app.
*
- * <p> The icon is returned without any treatment/overlay. In the rare case the app had multiple
- * launcher activities, only one of the icons is returned arbitrarily.
+ * <p> In the rare case the app had multiple launcher activities, only one of the icons is
+ * returned arbitrarily.
+ *
+ * <p> By default, the icon will be overlay'd with a cloud icon on top. A launcher app can
+ * disable the cloud overlay via the
+ * {@link LauncherApps.ArchiveCompatibilityParams#setEnableIconOverlay(boolean)} API.
+ * The default launcher's cloud overlay mode determines the cloud overlay status returned by
+ * any other callers. That is, if the current launcher has the cloud overlay disabled, any other
+ * app that fetches the app icon will also get an icon that has the cloud overlay disabled.
+ * This is to prevent style mismatch caused by icons that are fetched by different callers.
*/
@Nullable
public Bitmap getArchivedAppIcon(@NonNull String packageName, @NonNull UserHandle user,
String callingPackageName) {
+ return getArchivedAppIcon(packageName, user,
+ isOverlayEnabled(Binder.getCallingUid(), callingPackageName));
+ }
+
+ @Nullable
+ private Bitmap getArchivedAppIcon(@NonNull String packageName, @NonNull UserHandle user,
+ boolean isOverlayEnabled) {
Objects.requireNonNull(packageName);
Objects.requireNonNull(user);
@@ -955,14 +977,19 @@
// In the rare case the archived app defined more than two launcher activities, we choose
// the first one arbitrarily.
Bitmap icon = decodeIcon(archiveState.getActivityInfos().get(0));
- if (icon != null && getAppOpsManager().checkOp(
- AppOpsManager.OP_ARCHIVE_ICON_OVERLAY, callingUid, callingPackageName)
- == MODE_ALLOWED) {
+
+ if (icon != null && isOverlayEnabled) {
icon = includeCloudOverlay(icon);
}
return icon;
}
+ private boolean isOverlayEnabled(int callingUid, String packageName) {
+ return getAppOpsManager().checkOp(
+ AppOpsManager.OP_ARCHIVE_ICON_OVERLAY, callingUid, packageName)
+ == MODE_ALLOWED;
+ }
+
/**
* This method first checks the ArchiveState for the provided userId and then tries to fallback
* to other users if the current user is not archived.
diff --git a/services/core/java/com/android/server/pm/UserVisibilityMediator.java b/services/core/java/com/android/server/pm/UserVisibilityMediator.java
index 613ebd3..46207c1 100644
--- a/services/core/java/com/android/server/pm/UserVisibilityMediator.java
+++ b/services/core/java/com/android/server/pm/UserVisibilityMediator.java
@@ -579,11 +579,11 @@
+ " to user %d on start", userId, displayId, userAssignedToDisplay);
return false;
}
- // Then if was assigned extra
- userAssignedToDisplay = mExtraDisplaysAssignedToUsers.get(userId, USER_NULL);
+ // Then if the display was assigned before
+ userAssignedToDisplay = mExtraDisplaysAssignedToUsers.get(displayId, USER_NULL);
if (userAssignedToDisplay != USER_NULL) {
Slogf.w(TAG, "assignUserToExtraDisplay(%d, %d): failed because user %d was already "
- + "assigned that extra display", userId, displayId, userAssignedToDisplay);
+ + "assigned to extra display", userId, displayId, userAssignedToDisplay);
return false;
}
if (DBG) {
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index a84598d..1c14c5d 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -5522,7 +5522,7 @@
final int procCount = procs.size();
for (int i = 0; i < procCount; i++) {
final int procUid = procs.keyAt(i);
- if (UserHandle.isApp(procUid) || !UserHandle.isSameUser(procUid, uid)) {
+ if (!UserHandle.isCore(procUid) || !UserHandle.isSameUser(procUid, uid)) {
// Don't use an app process or different user process for system component.
continue;
}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 1efb3ef..475b473 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -550,7 +550,6 @@
/** Save allocating when calculating rects */
private final Rect mTmpRect = new Rect();
- private final Rect mTmpRect2 = new Rect();
private final Region mTmpRegion = new Region();
private final Configuration mTmpConfiguration = new Configuration();
diff --git a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
index e6dac88..dab3978 100644
--- a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
+++ b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
@@ -354,6 +354,9 @@
private static void createAndUploadReport(ProfcollectForwardingService pfs) {
BackgroundThread.get().getThreadHandler().post(() -> {
+ if (pfs.mIProfcollect == null) {
+ return;
+ }
String reportName;
try {
reportName = pfs.mIProfcollect.report(pfs.mUsageSetting) + ".zip";
@@ -401,6 +404,9 @@
final int traceDuration = 5000;
final String traceTag = "camera";
BackgroundThread.get().getThreadHandler().post(() -> {
+ if (mIProfcollect == null) {
+ return;
+ }
try {
mIProfcollect.trace_process(traceTag, "android.hardware.camera.provider",
traceDuration);
diff --git a/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayStatsServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayStatsServiceTest.java
index 98ba9ae..abb354b 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayStatsServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayStatsServiceTest.java
@@ -151,9 +151,10 @@
}
@Test
- public void testDisplayInteractivityChanges(
+ public void testDisplayInteractivityChangesWhileMirroring(
@TestParameter final boolean isExternalDisplayUsedForAudio) {
mExternalDisplayStatsService.onDisplayConnected(mMockedLogicalDisplay);
+ mExternalDisplayStatsService.onDisplayAdded(EXTERNAL_DISPLAY_ID);
mHandler.flush();
assertThat(mInteractivityReceiver).isNotNull();
@@ -180,12 +181,38 @@
mInteractivityReceiver.onReceive(null, null);
assertThat(mExternalDisplayStatsService.isInteractiveExternalDisplays()).isTrue();
verify(mMockedInjector).writeLog(FrameworkStatsLog.EXTERNAL_DISPLAY_STATE_CHANGED,
- FrameworkStatsLog.EXTERNAL_DISPLAY_STATE_CHANGED__STATE__CONNECTED,
+ FrameworkStatsLog.EXTERNAL_DISPLAY_STATE_CHANGED__STATE__MIRRORING,
/*numberOfDisplays=*/ 1,
isExternalDisplayUsedForAudio);
}
@Test
+ public void testDisplayInteractivityChangesWhileConnected() {
+ mExternalDisplayStatsService.onDisplayConnected(mMockedLogicalDisplay);
+ mHandler.flush();
+ assertThat(mInteractivityReceiver).isNotNull();
+ clearInvocations(mMockedInjector);
+
+ // Default is 'interactive', so no log should be written.
+ mInteractivityReceiver.onReceive(null, null);
+ assertThat(mExternalDisplayStatsService.isInteractiveExternalDisplays()).isTrue();
+ verify(mMockedInjector, never()).writeLog(anyInt(), anyInt(), anyInt(), anyBoolean());
+
+ // Change to non-interactive should not produce log
+ when(mMockedInjector.isInteractive(eq(EXTERNAL_DISPLAY_ID))).thenReturn(false);
+ mInteractivityReceiver.onReceive(null, null);
+ assertThat(mExternalDisplayStatsService.isInteractiveExternalDisplays()).isFalse();
+ verify(mMockedInjector, never()).writeLog(anyInt(), anyInt(), anyInt(), anyBoolean());
+ clearInvocations(mMockedInjector);
+
+ // Change back to interactive should not produce log
+ when(mMockedInjector.isInteractive(eq(EXTERNAL_DISPLAY_ID))).thenReturn(true);
+ mInteractivityReceiver.onReceive(null, null);
+ assertThat(mExternalDisplayStatsService.isInteractiveExternalDisplays()).isTrue();
+ verify(mMockedInjector, never()).writeLog(anyInt(), anyInt(), anyInt(), anyBoolean());
+ }
+
+ @Test
public void testAudioPlaybackChanges() {
mExternalDisplayStatsService.onDisplayConnected(mMockedLogicalDisplay);
mHandler.flush();
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/BrightnessEventTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/BrightnessEventTest.java
index 26f6e91..df09b04 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/BrightnessEventTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/BrightnessEventTest.java
@@ -46,6 +46,7 @@
mBrightnessEvent.setPhysicalDisplayId("987654321");
mBrightnessEvent.setPhysicalDisplayName("display_name");
mBrightnessEvent.setDisplayState(Display.STATE_ON);
+ mBrightnessEvent.setDisplayStateReason(Display.STATE_REASON_DEFAULT_POLICY);
mBrightnessEvent.setDisplayPolicy(POLICY_BRIGHT);
mBrightnessEvent.setLux(100.0f);
mBrightnessEvent.setPercent(46.5f);
@@ -82,9 +83,9 @@
String actualString = mBrightnessEvent.toString(false);
String expectedString =
"BrightnessEvent: brt=0.6 (46.5%), nits= 893.8, lux=100.0, reason=doze [ "
- + "low_pwr ], strat=strategy_name, state=ON, policy=BRIGHT, flags=, "
- + "initBrt=25.0, rcmdBrt=0.6, preBrt=NaN, preLux=150.0, "
- + "wasShortTermModelActive=true, autoBrightness=true (idle), "
+ + "low_pwr ], strat=strategy_name, state=ON, stateReason=DEFAULT_POLICY, "
+ + "policy=BRIGHT, flags=, initBrt=25.0, rcmdBrt=0.6, preBrt=NaN, "
+ + "preLux=150.0, wasShortTermModelActive=true, autoBrightness=true (idle), "
+ "unclampedBrt=0.65, hbmMax=0.62, hbmMode=off, thrmMax=0.65, "
+ "rbcStrength=-1, powerFactor=0.2, physDisp=display_name(987654321), "
+ "logicalId=1";
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java
index adcbf5c..194bf4b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java
@@ -442,20 +442,24 @@
IMPORTANCE_FOREGROUND_SERVICE, // importance
null); // description
- // Case 4: Create a process from another package with kill from lmkd
+ /*
+ * Case 4: Create a process from another package with kill from lmkd
+ * We expect LMKD's reported RSS to be the process' last seen RSS.
+ */
final int app2UidUser2 = 1010234;
final int app2PidUser2 = 12348;
final long app2Pss1 = 54321;
final long app2Rss1 = 65432;
+ final long lmkd_reported_rss = 43215;
final String app2ProcessName = "com.android.test.stub2:process";
final String app2PackageName = "com.android.test.stub2";
sleep(1);
final long now4 = System.currentTimeMillis();
- doReturn(new Pair<Long, Object>(now4, Integer.valueOf(0)))
+ doReturn(null)
.when(mAppExitInfoTracker.mAppExitInfoSourceZygote)
.remove(anyInt(), anyInt());
- doReturn(new Pair<Long, Object>(now4, null))
+ doReturn(new Pair<Long, Object>(now4, Long.valueOf(lmkd_reported_rss)))
.when(mAppExitInfoTracker.mAppExitInfoSourceLmkd)
.remove(anyInt(), anyInt());
@@ -490,7 +494,7 @@
null, // subReason
0, // status
app2Pss1, // pss
- app2Rss1, // rss
+ lmkd_reported_rss, // rss
IMPORTANCE_CACHED, // importance
null); // description
@@ -499,6 +503,11 @@
mAppExitInfoTracker.getExitInfo(null, app2UidUser2, 0, 0, list);
assertEquals(1, list.size());
+ info = list.get(0);
+
+ // Verify the AppExitInfo has the LMKD reported RSS
+ assertEquals(lmkd_reported_rss, info.getRss());
+
// Case 5: App native crash
final int app3UidUser2 = 1010345;
final int app3PidUser2 = 12349;
@@ -599,7 +608,7 @@
null, // subReason
0, // status
app2Pss1, // pss
- app2Rss1, // rss
+ lmkd_reported_rss, // rss
IMPORTANCE_CACHED, // importance
null); // description
diff --git a/services/tests/wmtests/src/com/android/server/wm/ScreenshotTests.java b/services/tests/wmtests/src/com/android/server/wm/ScreenshotTests.java
index 280fe4c..34f7ebb 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ScreenshotTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ScreenshotTests.java
@@ -66,6 +66,7 @@
import org.junit.Test;
import org.junit.rules.TestName;
+import java.time.Duration;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -131,7 +132,7 @@
assertTrue("Failed to wait for transaction to get committed",
countDownLatch.await(WAIT_TIME_S, TimeUnit.SECONDS));
assertTrue("Failed to wait for stable geometry",
- waitForStableWindowGeometry(WAIT_TIME_S, TimeUnit.SECONDS));
+ waitForStableWindowGeometry(Duration.ofSeconds(WAIT_TIME_S)));
ScreenCapture.LayerCaptureArgs args = new ScreenCapture.LayerCaptureArgs.Builder(secureSC)
.setCaptureSecureLayers(true)
@@ -212,7 +213,7 @@
assertTrue("Failed to wait for transaction to get committed",
countDownLatch.await(WAIT_TIME_S, TimeUnit.SECONDS));
assertTrue("Failed to wait for stable geometry",
- waitForStableWindowGeometry(WAIT_TIME_S, TimeUnit.SECONDS));
+ waitForStableWindowGeometry(Duration.ofSeconds(WAIT_TIME_S)));
ScreenshotHardwareBuffer[] screenCapture = new ScreenshotHardwareBuffer[1];
Bitmap screenshot = null;
diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
index d5d2847..b92af87 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
@@ -77,6 +77,7 @@
import android.view.SurfaceControl;
import com.android.dx.mockito.inline.extended.StaticMockitoSession;
+import com.android.internal.os.BackgroundThread;
import com.android.server.AnimationThread;
import com.android.server.DisplayThread;
import com.android.server.LocalServices;
@@ -553,6 +554,9 @@
// This is a different handler object than the wm.mAnimationHandler above.
waitHandlerIdle(AnimationThread.getHandler());
waitHandlerIdle(SurfaceAnimationThread.getHandler());
+ // Some binder calls are posted to BackgroundThread.getHandler(), we should wait for them
+ // to finish to run next test.
+ waitHandlerIdle(BackgroundThread.getHandler());
}
static void waitHandlerIdle(Handler handler) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/TrustedOverlayTests.java b/services/tests/wmtests/src/com/android/server/wm/TrustedOverlayTests.java
index f1d84cf..529e9b7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TrustedOverlayTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TrustedOverlayTests.java
@@ -49,6 +49,7 @@
import org.junit.Test;
import org.junit.rules.TestName;
+import java.time.Duration;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -144,7 +145,7 @@
}
}
return false;
- }, TIMEOUT_S, TimeUnit.SECONDS);
+ }, Duration.ofSeconds(TIMEOUT_S));
assertAndDumpWindowState(TAG, "Failed to find window or was not marked trusted",
foundTrusted[0]);
@@ -209,7 +210,7 @@
}
}
return foundTrusted[0] && foundTrusted[1];
- }, TIMEOUT_S, TimeUnit.SECONDS);
+ }, Duration.ofSeconds(TIMEOUT_S));
if (!foundTrusted[0] || !foundTrusted[1]) {
CtsWindowInfoUtils.dumpWindowsOnScreen(TAG, mName.getMethodName());
diff --git a/tests/Input/src/com/android/test/input/AnrTest.kt b/tests/Input/src/com/android/test/input/AnrTest.kt
index 8d1fc50..d32cedb 100644
--- a/tests/Input/src/com/android/test/input/AnrTest.kt
+++ b/tests/Input/src/com/android/test/input/AnrTest.kt
@@ -40,7 +40,7 @@
import com.android.cts.input.DebugInputRule
import com.android.cts.input.UinputTouchScreen
-import java.util.concurrent.TimeUnit
+import java.time.Duration
import org.junit.After
import org.junit.Assert.assertEquals
@@ -193,6 +193,6 @@
val flags = " -W -n "
val startCmd = "am start $flags $PACKAGE_NAME/.UnresponsiveGestureMonitorActivity"
instrumentation.uiAutomation.executeShellCommand(startCmd)
- waitForStableWindowGeometry(5L, TimeUnit.SECONDS)
+ waitForStableWindowGeometry(Duration.ofSeconds(5))
}
}
diff --git a/tests/inputmethod/ConcurrentMultiSessionImeTest/Android.bp b/tests/inputmethod/ConcurrentMultiSessionImeTest/Android.bp
index 4c531b8..a4085e5 100644
--- a/tests/inputmethod/ConcurrentMultiSessionImeTest/Android.bp
+++ b/tests/inputmethod/ConcurrentMultiSessionImeTest/Android.bp
@@ -23,6 +23,7 @@
resource_dirs: ["res"],
libs: ["android.test.runner"],
static_libs: [
+ "androidx.core_core",
"androidx.test.ext.junit",
"androidx.test.rules",
"compatibility-device-util-axt",
diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp
index 2df9418..45bf8e3 100644
--- a/tools/aapt2/ResourceParser.cpp
+++ b/tools/aapt2/ResourceParser.cpp
@@ -690,7 +690,9 @@
resource_format = item_iter->second.format;
}
- if (!ParseItem(parser, out_resource, resource_format)) {
+ // Don't bother parsing the item if it is behind a disabled flag
+ if (out_resource->flag_status != FlagStatus::Disabled &&
+ !ParseItem(parser, out_resource, resource_format)) {
return false;
}
return true;
diff --git a/tools/aapt2/ResourceParser_test.cpp b/tools/aapt2/ResourceParser_test.cpp
index b59b165..2e6ad13 100644
--- a/tools/aapt2/ResourceParser_test.cpp
+++ b/tools/aapt2/ResourceParser_test.cpp
@@ -69,8 +69,13 @@
return TestParse(str, ConfigDescription{});
}
- ::testing::AssertionResult TestParse(StringPiece str, const ConfigDescription& config) {
- ResourceParserOptions parserOptions;
+ ::testing::AssertionResult TestParse(StringPiece str, ResourceParserOptions parserOptions) {
+ return TestParse(str, ConfigDescription{}, parserOptions);
+ }
+
+ ::testing::AssertionResult TestParse(
+ StringPiece str, const ConfigDescription& config,
+ ResourceParserOptions parserOptions = ResourceParserOptions()) {
ResourceParser parser(context_->GetDiagnostics(), &table_, android::Source{"test"}, config,
parserOptions);
@@ -242,6 +247,19 @@
EXPECT_FALSE(TestParse(R"(<string name="foo4" translatable="yes">Translate</string>)"));
}
+TEST_F(ResourceParserTest, ParseStringBehindDisabledFlag) {
+ FeatureFlagProperties flag_properties(true, false);
+ ResourceParserOptions options;
+ options.feature_flag_values = {{"falseFlag", flag_properties}};
+ ASSERT_TRUE(TestParse(
+ R"(<string name="foo" android:featureFlag="falseFlag"
+ xmlns:android="http://schemas.android.com/apk/res/android">foo</string>)",
+ options));
+
+ String* str = test::GetValue<String>(&table_, "string/foo");
+ ASSERT_THAT(str, IsNull());
+}
+
TEST_F(ResourceParserTest, IgnoreXliffTagsOtherThanG) {
std::string input = R"(
<string name="foo" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/AppInfoTest.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/AppInfoTest.java
index 7806061..b4a7663 100644
--- a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/AppInfoTest.java
+++ b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/AppInfoTest.java
@@ -86,11 +86,12 @@
@Test
public void testAllFieldsValidV1() throws Exception {
System.out.println("starting testAllFieldsValidV1.");
- new AppInfoFactory()
- .createFromOdElement(
- TestUtils.getElementFromResource(
- Paths.get(APP_INFO_OD_PATH, ALL_FIELDS_VALID_V1_FILE_NAME)),
- 1L);
+ var unused =
+ new AppInfoFactory()
+ .createFromOdElement(
+ TestUtils.getElementFromResource(
+ Paths.get(APP_INFO_OD_PATH, ALL_FIELDS_VALID_V1_FILE_NAME)),
+ 1L);
}
/** Test for unrecognized field v1. */
@@ -133,7 +134,7 @@
TestUtils.getElementFromResource(
Paths.get(APP_INFO_OD_PATH, ALL_FIELDS_VALID_V1_FILE_NAME));
TestUtils.removeOdChildEleWithName(appInfoEle, optField);
- new AppInfoFactory().createFromOdElement(appInfoEle, 1L);
+ var unused = new AppInfoFactory().createFromOdElement(appInfoEle, 1L);
}
}
@@ -202,7 +203,7 @@
Paths.get(APP_INFO_HR_PATH, ALL_FIELDS_VALID_FILE_NAME));
ele.removeAttribute(optField);
AppInfo appInfo = new AppInfoFactory().createFromHrElement(ele, DEFAULT_VERSION);
- appInfo.toOdDomElement(TestUtils.document());
+ var unused = appInfo.toOdDomElement(TestUtils.document());
}
for (String optField : OPTIONAL_FIELD_NAMES_OD) {
@@ -211,7 +212,7 @@
Paths.get(APP_INFO_OD_PATH, ALL_FIELDS_VALID_FILE_NAME));
TestUtils.removeOdChildEleWithName(ele, optField);
AppInfo appInfo = new AppInfoFactory().createFromOdElement(ele, DEFAULT_VERSION);
- appInfo.toHrDomElement(TestUtils.document());
+ var unused = appInfo.toHrDomElement(TestUtils.document());
}
}
diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/DeveloperInfoTest.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/DeveloperInfoTest.java
index a4472b1..2746800 100644
--- a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/DeveloperInfoTest.java
+++ b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/DeveloperInfoTest.java
@@ -99,7 +99,7 @@
developerInfoEle.removeAttribute(optField);
DeveloperInfo developerInfo =
new DeveloperInfoFactory().createFromHrElement(developerInfoEle);
- developerInfo.toOdDomElement(TestUtils.document());
+ var unused = developerInfo.toOdDomElement(TestUtils.document());
}
for (String optField : OPTIONAL_FIELD_NAMES_OD) {
@@ -109,7 +109,7 @@
TestUtils.removeOdChildEleWithName(developerInfoEle, optField);
DeveloperInfo developerInfo =
new DeveloperInfoFactory().createFromOdElement(developerInfoEle);
- developerInfo.toHrDomElement(TestUtils.document());
+ var unused = developerInfo.toHrDomElement(TestUtils.document());
}
}
diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SecurityLabelsTest.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SecurityLabelsTest.java
index 9d197a2..27f8720 100644
--- a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SecurityLabelsTest.java
+++ b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SecurityLabelsTest.java
@@ -64,7 +64,7 @@
Paths.get(SECURITY_LABELS_HR_PATH, ALL_FIELDS_VALID_FILE_NAME));
ele.removeAttribute(optField);
SecurityLabels securityLabels = new SecurityLabelsFactory().createFromHrElement(ele);
- securityLabels.toOdDomElement(TestUtils.document());
+ var unused = securityLabels.toOdDomElement(TestUtils.document());
}
for (String optField : OPTIONAL_FIELD_NAMES_OD) {
var ele =
@@ -72,7 +72,7 @@
Paths.get(SECURITY_LABELS_OD_PATH, ALL_FIELDS_VALID_FILE_NAME));
TestUtils.removeOdChildEleWithName(ele, optField);
SecurityLabels securityLabels = new SecurityLabelsFactory().createFromOdElement(ele);
- securityLabels.toHrDomElement(TestUtils.document());
+ var unused = securityLabels.toHrDomElement(TestUtils.document());
}
}